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 = S['multiple'] extends true ? PatternToType[] : PatternToType;
type IsRequired = S['default'] extends undefined
? (S['required'] extends true ? true : false)
: true;
export type InferResult 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