type Primitive = string | number | boolean; type PatternToType

= P extends NumberConstructor ? number : P extends StringConstructor ? string : P extends BooleanConstructor ? boolean : P extends RegExp ? string : unknown; interface FieldSchema { key: Key; /** * 校验规则 */ pattern?: NumberConstructor | StringConstructor | BooleanConstructor | RegExp; /** * 自定义解析函数 */ parse?: (raw: string) => T; /** * 默认值 */ default?: T; /** * 是否允许重复字段 */ multiple?: boolean; /** 是否必填(无 default 时使用) */ required?: boolean; } type InferField = S['multiple'] extends true ? PatternToType[] : PatternToType; type IsRequired = S['default'] extends undefined ? (S['required'] extends true ? true : false) : true; export type InferResult = & { [S in T[number] as IsRequired extends true ? S['key'] : never]: InferField; } & { [S in T[number] as IsRequired extends true ? never : S['key']]?: InferField; }; export interface AnalysisOptions { /** * 严格模式 */ strict?: boolean; } /** * Streaming normalize * @description 支持 * - 标准 URL (a=1&b=2) * - DSL 语法 (a:1;b:2) * - pipe 语法 (el:a|b|c → el=a&el=b&el=c) * - JSON 自动识别 (meta={"x":1,"y":[1,2]}) * @param input * @return 标准 URL */ function normalizeInput(input?: string): string { if (!input) return ''; // 去掉前导 ? const source = input.startsWith('?') ? input.slice(1) : input; const result: string[] = []; let key = ''; let value = ''; let readingKey = true; let inJSON = false; let jsonDepth = 0; const pushPair = () => { if (!key) return; if (value.includes('|') && !inJSON) value.split('|').forEach((value) => result.push(`${key}=${value}`)); else result.push(`${key}=${value}`); key = ''; value = ''; readingKey = true; }; const jsonStart = ['{', '[']; const jsonEnd = ['}', ']']; for (const char of source) { // JSON 开始 if (!inJSON && jsonStart.includes(char)) { inJSON = true; jsonDepth = 1; value += char; continue; } // JSON 内部 if (inJSON) { value += char; if (jsonStart.includes(char)) jsonDepth++; if (jsonEnd.includes(char)) jsonDepth--; if (jsonDepth === 0) inJSON = false; continue; } if (['&',';'].includes(char) && !inJSON) { pushPair(); continue; } if ([':', '='].includes(char) && readingKey) { readingKey = false; continue; } if (readingKey) key += char; else value += char; } pushPair(); return result.join('&'); } /** * 自动解析 JSON */ function autoParse(raw: string): any { const trimmed = raw.trim(); if ( (trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']')) ) try { return JSON.parse(trimmed); } catch { return raw; } return raw; } /** * 自动解析并转换 pattern 类型 */ function parseValue(raw: string, pattern?: FieldSchema['pattern'], parseFn?: FieldSchema['parse'], strict?: boolean): any { let value = parseFn ? parseFn(raw) : autoParse(raw); // 原始类型自动转换 if (pattern === String) value = String(value); else if (pattern === Number) { value = Number(value); if (isNaN(value) && strict) throw new Error(`Invalid number: ${raw}`); } else if (pattern === Boolean) { if (value === 'true' || value === '1' || value === true) value = true; else if (value === 'false' || value === '0' || value === false) value = false; else if (strict) throw new Error(`Invalid boolean: ${raw}`); else value = Boolean(value); } else if (pattern instanceof RegExp && !pattern.test(String(value)) && strict) throw new Error(`Invalid value: ${raw}`); return value; } /** * 参数解析器 * @description * Raw Input * ↓ normalizeInput * ↓ URLSearchParams * ↓ parseValue (JSON + JS 类型 + pattern) * ↓ 类型安全输出 * @example * ``` * // 标准 URL * preset=1&el=debug&el=btn * // DSL 语法 * preset:1;el:debug * // Pipe 语法 * el:a|b|c * ``` * * @param schema * @param value * @param options */ export function analysis(schema: T, value?: string, options?: AnalysisOptions): InferResult { const input = normalizeInput(value); const params = new URLSearchParams(input); const result: Record = {}; for (const { key, default: defaultValue, multiple, required, pattern, parse } of schema) { const matches = params.getAll(key); if (matches.length === 0) { if (defaultValue != null) result[key] = defaultValue; else if (required && options?.strict) throw new Error(`Missing required field: ${key}`); continue; } const values = matches.map(raw => parseValue(raw, pattern, parse, options?.strict)); result[key] = multiple ? values : values[0]; } return result as InferResult; }