@oh-my-pi/omptype 17.2.6 → 17.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -2
- package/README.md +83 -14
- package/dist/js/ark.js +16 -0
- package/dist/js/compile.js +747 -0
- package/dist/js/errors.js +199 -0
- package/dist/js/index.js +12 -0
- package/dist/js/infer.js +2 -0
- package/dist/js/interp.js +476 -0
- package/dist/js/ir.js +930 -0
- package/dist/js/json-schema.js +273 -0
- package/dist/js/keywords.js +233 -0
- package/dist/js/type.js +1078 -0
- package/dist/js/typebox.js +346 -0
- package/dist/js/zod.js +269 -0
- package/dist/types/ark.d.ts +3 -11
- package/dist/types/compile.d.ts +2 -0
- package/dist/types/errors.d.ts +51 -15
- package/dist/types/index.d.ts +3 -4
- package/dist/types/infer.d.ts +107 -30
- package/dist/types/interp.d.ts +5 -3
- package/dist/types/ir.d.ts +76 -7
- package/dist/types/json-schema.d.ts +6 -1
- package/dist/types/keywords.d.ts +7 -0
- package/dist/types/type.d.ts +238 -49
- package/dist/types/zod.d.ts +1 -1
- package/package.json +20 -8
- package/src/ark.ts +4 -14
- package/src/compile.ts +432 -54
- package/src/errors.ts +146 -34
- package/src/index.ts +3 -4
- package/src/infer.ts +284 -77
- package/src/interp.ts +153 -9
- package/src/ir.ts +636 -89
- package/src/json-schema.ts +109 -14
- package/src/keywords.ts +270 -0
- package/src/type.ts +1302 -147
- package/src/typebox.ts +1 -1
- package/src/zod.ts +40 -27
package/src/infer.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Type-level output inference for
|
|
1
|
+
/** Type-level input and output inference for definitions accepted by omptype. */
|
|
2
2
|
|
|
3
3
|
type Whitespace = " " | "\n" | "\r" | "\t";
|
|
4
4
|
|
|
@@ -6,48 +6,94 @@ type TrimLeft<s extends string> = s extends `${Whitespace}${infer rest}` ? TrimL
|
|
|
6
6
|
type TrimRight<s extends string> = s extends `${infer rest}${Whitespace}` ? TrimRight<rest> : s;
|
|
7
7
|
type Trim<s extends string> = TrimLeft<TrimRight<s>>;
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Flat keyword lookup. An indexed access is one instantiation level, unlike a
|
|
11
|
+
* nested conditional chain — this sits under every string-DSL property, so its
|
|
12
|
+
* depth is multiplied by every layer of object nesting above it.
|
|
13
|
+
* `never` is intentionally absent: the parser rejects it and the fallback in
|
|
14
|
+
* `InferMember` treats a missing entry as "not a primitive".
|
|
15
|
+
*/
|
|
16
|
+
interface PrimitiveMap {
|
|
17
|
+
string: string;
|
|
18
|
+
"string.url": string;
|
|
19
|
+
number: number;
|
|
20
|
+
"number.integer": number;
|
|
21
|
+
boolean: boolean;
|
|
22
|
+
null: null;
|
|
23
|
+
undefined: undefined;
|
|
24
|
+
unknown: unknown;
|
|
25
|
+
any: unknown;
|
|
26
|
+
object: object;
|
|
27
|
+
bigint: bigint;
|
|
28
|
+
symbol: symbol;
|
|
29
|
+
Date: Date;
|
|
30
|
+
true: true;
|
|
31
|
+
false: false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type Merge<left, right> = left extends object
|
|
35
|
+
? right extends object
|
|
36
|
+
? Omit<left, keyof right> & right
|
|
37
|
+
: never
|
|
38
|
+
: never;
|
|
39
|
+
|
|
40
|
+
type InferUtility<s extends string> = s extends `Record<${string},${infer value}>`
|
|
41
|
+
? Record<string, InferString<value>>
|
|
42
|
+
: s extends `Array<${infer element}>`
|
|
43
|
+
? InferString<element>[]
|
|
44
|
+
: s extends `Partial<${infer value}>`
|
|
45
|
+
? Partial<InferString<value>>
|
|
46
|
+
: s extends `Required<${infer value}>`
|
|
47
|
+
? Required<InferString<value>>
|
|
48
|
+
: s extends `Pick<${infer value},${infer keys}>`
|
|
49
|
+
? Pick<InferString<value>, Extract<InferString<keys>, keyof InferString<value>>>
|
|
50
|
+
: s extends `Omit<${infer value},${infer keys}>`
|
|
51
|
+
? Omit<InferString<value>, Extract<InferString<keys>, keyof InferString<value>>>
|
|
52
|
+
: s extends `Merge<${infer left},${infer right}>`
|
|
53
|
+
? Merge<InferString<left>, InferString<right>>
|
|
54
|
+
: never;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Member inference as a flat false-branch chain: TypeScript tail-evaluates
|
|
58
|
+
* chained conditionals in the false position, so this stays at constant
|
|
59
|
+
* instantiation depth where the previous `extends infer` ladder nested every
|
|
60
|
+
* fallback inside a true branch and accumulated depth per step.
|
|
61
|
+
*/
|
|
62
|
+
type InferMember<member extends string> = Trim<member> extends infer s extends string ? InferMemberTrimmed<s> : unknown;
|
|
63
|
+
|
|
64
|
+
type InferMemberTrimmed<s extends string> = s extends `(${infer inner})`
|
|
65
|
+
? InferString<inner>
|
|
66
|
+
: s extends `${infer element}[]`
|
|
67
|
+
? InferMember<element>[]
|
|
68
|
+
: s extends `'${infer literal}'` | `"${infer literal}"`
|
|
69
|
+
? literal
|
|
70
|
+
: s extends `d'${string}'` | `d"${string}"`
|
|
71
|
+
? Date
|
|
72
|
+
: s extends `\`${string}\``
|
|
73
|
+
? string
|
|
74
|
+
: s extends `/${string}/${string}` | `/${string}/`
|
|
75
|
+
? string
|
|
76
|
+
: s extends `${infer literal extends number}`
|
|
77
|
+
? literal
|
|
78
|
+
: s extends keyof PrimitiveMap
|
|
79
|
+
? PrimitiveMap[s]
|
|
80
|
+
: InferUtility<s> extends infer utility
|
|
81
|
+
? [utility] extends [never]
|
|
82
|
+
? InferMemberFallback<s>
|
|
83
|
+
: utility
|
|
84
|
+
: unknown;
|
|
85
|
+
|
|
86
|
+
type InferMemberFallback<s extends string> = s extends `string.${string}`
|
|
10
87
|
? string
|
|
11
|
-
: s extends
|
|
12
|
-
?
|
|
13
|
-
: s extends
|
|
14
|
-
?
|
|
15
|
-
: s extends
|
|
16
|
-
?
|
|
17
|
-
:
|
|
18
|
-
? undefined
|
|
19
|
-
: s extends "unknown"
|
|
20
|
-
? unknown
|
|
21
|
-
: s extends "object"
|
|
22
|
-
? object
|
|
23
|
-
: s extends "bigint"
|
|
24
|
-
? bigint
|
|
25
|
-
: s extends "true"
|
|
26
|
-
? true
|
|
27
|
-
: s extends "false"
|
|
28
|
-
? false
|
|
29
|
-
: never;
|
|
30
|
-
|
|
31
|
-
type InferMember<member extends string> =
|
|
32
|
-
Trim<member> extends infer s extends string
|
|
33
|
-
? s extends `${infer element}[]`
|
|
34
|
-
? InferMember<element>[]
|
|
35
|
-
: s extends `'${infer literal}'` | `"${infer literal}"`
|
|
36
|
-
? literal
|
|
37
|
-
: s extends `${infer literal extends number}`
|
|
38
|
-
? literal
|
|
39
|
-
: InferPrimitive<s> extends infer primitive
|
|
40
|
-
? [primitive] extends [never]
|
|
41
|
-
? s extends `${string}string${string}`
|
|
42
|
-
? string
|
|
43
|
-
: s extends `${string}number${string}`
|
|
44
|
-
? number
|
|
45
|
-
: unknown
|
|
46
|
-
: primitive
|
|
47
|
-
: unknown
|
|
48
|
-
: unknown;
|
|
88
|
+
: s extends `${string}Date${string}`
|
|
89
|
+
? Date
|
|
90
|
+
: s extends `${string}string${string}`
|
|
91
|
+
? string
|
|
92
|
+
: s extends `${string}number${string}`
|
|
93
|
+
? number
|
|
94
|
+
: unknown;
|
|
49
95
|
|
|
50
|
-
/** Split
|
|
96
|
+
/** Split unions without distributing over the accumulated members. */
|
|
51
97
|
type InferUnion<s extends string, result = never> = s extends `${infer head}|${infer tail}`
|
|
52
98
|
? InferUnion<tail, result | InferMember<head>>
|
|
53
99
|
: result | InferMember<s>;
|
|
@@ -61,61 +107,136 @@ type HasInlineDefault<s extends string> = s extends `${string}=${string}`
|
|
|
61
107
|
type WithoutInlineDefault<s extends string> =
|
|
62
108
|
HasInlineDefault<s> extends true ? (s extends `${infer base}=${string}` ? Trim<base> : s) : s;
|
|
63
109
|
|
|
64
|
-
|
|
110
|
+
type InferStringOutput<s extends string> = s extends
|
|
111
|
+
| "string.numeric.parse"
|
|
112
|
+
| "string.integer.parse"
|
|
113
|
+
| "parse.number"
|
|
114
|
+
| "parse.integer"
|
|
115
|
+
? number
|
|
116
|
+
: s extends "string.date.parse" | "string.date.iso.parse" | "string.date.epoch.parse" | "parse.date"
|
|
117
|
+
? Date
|
|
118
|
+
: s extends "string.url.parse" | "parse.url"
|
|
119
|
+
? URL
|
|
120
|
+
: s extends "string.json.parse" | "parse.json"
|
|
121
|
+
? unknown
|
|
122
|
+
: s extends "parse.boolean"
|
|
123
|
+
? boolean
|
|
124
|
+
: s extends "parse.bigint"
|
|
125
|
+
? bigint
|
|
126
|
+
: InferUnion<s>;
|
|
127
|
+
|
|
128
|
+
/** String-DSL output inference. */
|
|
65
129
|
export type InferString<s extends string> =
|
|
66
130
|
WithoutInlineDefault<Trim<s>> extends infer trimmed extends string
|
|
67
131
|
? trimmed extends `${infer base}?`
|
|
68
132
|
? InferString<base>
|
|
69
133
|
: trimmed extends `(${infer inner})[]`
|
|
70
134
|
? InferUnion<inner>[]
|
|
71
|
-
:
|
|
135
|
+
: InferStringOutput<trimmed>
|
|
136
|
+
: unknown;
|
|
137
|
+
|
|
138
|
+
/** String-DSL input inference, preserving the source side of morph keywords. */
|
|
139
|
+
export type InferStringIn<s extends string> =
|
|
140
|
+
WithoutInlineDefault<Trim<s>> extends infer trimmed extends string
|
|
141
|
+
? trimmed extends `${string}.parse` | `parse.${string}`
|
|
142
|
+
? string
|
|
143
|
+
: InferString<trimmed>
|
|
72
144
|
: unknown;
|
|
73
145
|
|
|
74
146
|
type HasDefault<def> = def extends string
|
|
75
147
|
? HasInlineDefault<def>
|
|
76
|
-
: def extends
|
|
148
|
+
: def extends readonly [unknown, "=", unknown]
|
|
77
149
|
? true
|
|
78
|
-
:
|
|
150
|
+
: def extends { readonly hasDefault: true }
|
|
151
|
+
? true
|
|
152
|
+
: false;
|
|
79
153
|
|
|
80
|
-
type DefinitionKeys<def extends object> = Exclude<keyof def, "+" | "[string]">;
|
|
154
|
+
type DefinitionKeys<def extends object> = Exclude<keyof def, "+" | "[string]" | "...">;
|
|
81
155
|
|
|
82
|
-
/** Optional when the key carries a `?` suffix or the string value does (`limit: "number?"`). */
|
|
83
156
|
type IsOptionalProp<key, def> = key extends `${string}?`
|
|
84
157
|
? true
|
|
85
158
|
: def extends string
|
|
86
159
|
? Trim<def> extends `${string}?`
|
|
87
160
|
? true
|
|
88
161
|
: false
|
|
89
|
-
:
|
|
162
|
+
: def extends readonly [unknown, "?"]
|
|
163
|
+
? true
|
|
164
|
+
: false;
|
|
90
165
|
|
|
91
166
|
type PropName<key extends PropertyKey> = key extends `${infer name}?` ? name : key;
|
|
167
|
+
type UnwrapProperty<def> = def extends readonly [infer value, "?" | "=", ...unknown[]] ? value : def;
|
|
168
|
+
type Simplify<t> = { [key in keyof t]: t[key] };
|
|
92
169
|
|
|
93
|
-
type
|
|
170
|
+
type OutputRequired<def extends object> = {
|
|
94
171
|
-readonly [key in DefinitionKeys<def> as IsOptionalProp<key, def[key]> extends true
|
|
95
172
|
? HasDefault<def[key]> extends true
|
|
96
173
|
? PropName<key>
|
|
97
174
|
: never
|
|
98
|
-
: PropName<key>]-?: InferDef<def[key]
|
|
175
|
+
: PropName<key>]-?: InferDef<UnwrapProperty<def[key]>>;
|
|
99
176
|
};
|
|
100
177
|
|
|
101
|
-
type
|
|
178
|
+
type OutputOptional<def extends object> = {
|
|
102
179
|
-readonly [key in DefinitionKeys<def> as IsOptionalProp<key, def[key]> extends true
|
|
103
180
|
? HasDefault<def[key]> extends true
|
|
104
181
|
? never
|
|
105
182
|
: PropName<key>
|
|
106
|
-
: never]?: InferDef<def[key]
|
|
183
|
+
: never]?: InferDef<UnwrapProperty<def[key]>>;
|
|
107
184
|
};
|
|
108
185
|
|
|
109
|
-
type
|
|
110
|
-
|
|
186
|
+
type InputRequired<def extends object> = {
|
|
187
|
+
-readonly [key in DefinitionKeys<def> as IsOptionalProp<key, def[key]> extends true
|
|
188
|
+
? never
|
|
189
|
+
: HasDefault<def[key]> extends true
|
|
190
|
+
? never
|
|
191
|
+
: PropName<key>]-?: InferDefIn<UnwrapProperty<def[key]>>;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
type InputOptional<def extends object> = {
|
|
195
|
+
-readonly [key in DefinitionKeys<def> as IsOptionalProp<key, def[key]> extends true
|
|
196
|
+
? PropName<key>
|
|
197
|
+
: HasDefault<def[key]> extends true
|
|
198
|
+
? PropName<key>
|
|
199
|
+
: never]?: InferDefIn<UnwrapProperty<def[key]>>;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* CYCLE SAFETY: when a fluent generic method is called on a schema whose def
|
|
204
|
+
* embeds other schemas, TypeScript instantiates `InferDef<def>` while `def`
|
|
205
|
+
* is still generic. `"..." extends keyof def` resolves TRUE under permissive
|
|
206
|
+
* instantiation (`keyof any` contains every literal), so spread/index members
|
|
207
|
+
* cannot be deferred by wrapper conditionals — a bare `InferDef<def["..."]>`
|
|
208
|
+
* member re-enters this expansion and instantiates without bound (TS2589).
|
|
209
|
+
* Interface members only instantiate when resolved, so routing the recursive
|
|
210
|
+
* reference through `DefBox` keeps generic instantiation shallow: it stops at
|
|
211
|
+
* a type reference plus an indexed access instead of expanding `InferDef`.
|
|
212
|
+
*/
|
|
213
|
+
interface DefBox<def> {
|
|
214
|
+
readonly out: InferDef<def>;
|
|
215
|
+
readonly in: InferDefIn<def>;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
type OutputSpread<def extends object> = "..." extends keyof def ? DefBox<def["..."]>["out"] : unknown;
|
|
219
|
+
type InputSpread<def extends object> = "..." extends keyof def ? DefBox<def["..."]>["in"] : unknown;
|
|
220
|
+
type OutputIndex<def extends object> = "[string]" extends keyof def
|
|
221
|
+
? Record<string, DefBox<def["[string]"]>["out"]>
|
|
222
|
+
: unknown;
|
|
223
|
+
type InputIndex<def extends object> = "[string]" extends keyof def
|
|
224
|
+
? Record<string, DefBox<def["[string]"]>["in"]>
|
|
225
|
+
: unknown;
|
|
111
226
|
|
|
112
227
|
type InferObject<def extends object> = "[string]" extends keyof def
|
|
113
228
|
? [DefinitionKeys<def>] extends [never]
|
|
114
|
-
? Record<string,
|
|
115
|
-
:
|
|
116
|
-
:
|
|
229
|
+
? Record<string, DefBox<def["[string]"]>["out"]>
|
|
230
|
+
: Simplify<OutputRequired<def> & OutputOptional<def> & OutputSpread<def>> & OutputIndex<def>
|
|
231
|
+
: Simplify<OutputRequired<def> & OutputOptional<def> & OutputSpread<def>>;
|
|
117
232
|
|
|
118
|
-
|
|
233
|
+
type InferObjectIn<def extends object> = "[string]" extends keyof def
|
|
234
|
+
? [DefinitionKeys<def>] extends [never]
|
|
235
|
+
? Record<string, DefBox<def["[string]"]>["in"]>
|
|
236
|
+
: Simplify<InputRequired<def> & InputOptional<def> & InputSpread<def>> & InputIndex<def>
|
|
237
|
+
: Simplify<InputRequired<def> & InputOptional<def> & InputSpread<def>>;
|
|
238
|
+
|
|
239
|
+
/** Object-literal inference used by fluent composition overloads. */
|
|
119
240
|
export type InferObjectDef<def extends object> = InferObject<def>;
|
|
120
241
|
|
|
121
242
|
type InferLiteralDef<def> = def extends string
|
|
@@ -124,37 +245,123 @@ type InferLiteralDef<def> = def extends string
|
|
|
124
245
|
? InferObjectLiteral<def>
|
|
125
246
|
: unknown;
|
|
126
247
|
|
|
127
|
-
type
|
|
248
|
+
type LiteralRequired<def extends object> = {
|
|
128
249
|
-readonly [key in DefinitionKeys<def> as IsOptionalProp<key, def[key]> extends true
|
|
129
250
|
? HasDefault<def[key]> extends true
|
|
130
251
|
? PropName<key>
|
|
131
252
|
: never
|
|
132
|
-
: PropName<key>]-?: InferLiteralDef<def[key]
|
|
253
|
+
: PropName<key>]-?: InferLiteralDef<UnwrapProperty<def[key]>>;
|
|
133
254
|
};
|
|
134
255
|
|
|
135
|
-
type
|
|
256
|
+
type LiteralOptional<def extends object> = {
|
|
136
257
|
-readonly [key in DefinitionKeys<def> as IsOptionalProp<key, def[key]> extends true
|
|
137
258
|
? HasDefault<def[key]> extends true
|
|
138
259
|
? never
|
|
139
260
|
: PropName<key>
|
|
140
|
-
: never]?: InferLiteralDef<def[key]
|
|
261
|
+
: never]?: InferLiteralDef<UnwrapProperty<def[key]>>;
|
|
141
262
|
};
|
|
142
263
|
|
|
143
|
-
/** Object-literal-only inference
|
|
144
|
-
export type InferObjectLiteral<def extends object> = Simplify<
|
|
145
|
-
|
|
146
|
-
|
|
264
|
+
/** Object-literal-only inference that does not inspect embedded schema internals. */
|
|
265
|
+
export type InferObjectLiteral<def extends object> = Simplify<LiteralRequired<def> & LiteralOptional<def>>;
|
|
266
|
+
|
|
267
|
+
type InstanceOf<ctor> = ctor extends abstract new (...args: never[]) => infer instance ? instance : never;
|
|
268
|
+
type SpreadOutput<def> = InferDef<def> extends readonly (infer element)[] ? element[] : never[];
|
|
269
|
+
type SpreadInput<def> = InferDefIn<def> extends readonly (infer element)[] ? element[] : never[];
|
|
270
|
+
|
|
271
|
+
type InferTupleOutput<defs extends readonly unknown[], result extends unknown[] = []> = defs extends readonly []
|
|
272
|
+
? result
|
|
273
|
+
: defs extends readonly ["...", infer spread, ...infer rest]
|
|
274
|
+
? [...result, ...SpreadOutput<spread>, ...InferTupleOutput<rest>]
|
|
275
|
+
: defs extends readonly [infer head, ...infer rest]
|
|
276
|
+
? head extends readonly [infer value, "?"]
|
|
277
|
+
? InferTupleOutput<rest, [...result, InferDef<value>?]>
|
|
278
|
+
: head extends readonly [infer value, "=", unknown]
|
|
279
|
+
? InferTupleOutput<rest, [...result, InferDef<value>]>
|
|
280
|
+
: InferTupleOutput<rest, [...result, InferDef<head>]>
|
|
281
|
+
: result;
|
|
282
|
+
|
|
283
|
+
type InferTupleInput<defs extends readonly unknown[], result extends unknown[] = []> = defs extends readonly []
|
|
284
|
+
? result
|
|
285
|
+
: defs extends readonly ["...", infer spread, ...infer rest]
|
|
286
|
+
? [...result, ...SpreadInput<spread>, ...InferTupleInput<rest>]
|
|
287
|
+
: defs extends readonly [infer head, ...infer rest]
|
|
288
|
+
? head extends readonly [infer value, "?" | "=", ...unknown[]]
|
|
289
|
+
? InferTupleInput<rest, [...result, InferDefIn<value>?]>
|
|
290
|
+
: InferTupleInput<rest, [...result, InferDefIn<head>]>
|
|
291
|
+
: result;
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* True only for `any` (`1 & any` is `any`; `0 extends any` holds). During
|
|
295
|
+
* relation checking TypeScript instantiates these aliases permissively with
|
|
296
|
+
* every type parameter replaced by `any`, and under `any` the object branch
|
|
297
|
+
* recurses forever (`any["..."]` is `any` again). Cutting `any` off up front
|
|
298
|
+
* makes permissive instantiation terminate immediately, mirroring ArkType's
|
|
299
|
+
* `anyOrNever` guards.
|
|
300
|
+
*/
|
|
301
|
+
type IsAny<def> = 0 extends 1 & def ? true : false;
|
|
147
302
|
|
|
148
303
|
/** Infer the validated output type produced by a definition. */
|
|
149
|
-
export type InferDef<def = unknown> =
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
304
|
+
export type InferDef<def = unknown> =
|
|
305
|
+
IsAny<def> extends true
|
|
306
|
+
? unknown
|
|
307
|
+
: def extends { readonly infer: infer output }
|
|
308
|
+
? output
|
|
309
|
+
: def extends string
|
|
310
|
+
? InferString<def>
|
|
311
|
+
: def extends RegExp
|
|
312
|
+
? string
|
|
313
|
+
: def extends readonly [infer element, "[]"]
|
|
314
|
+
? InferDef<element>[]
|
|
315
|
+
: def extends readonly [infer left, "|", infer right]
|
|
316
|
+
? InferDef<left> | InferDef<right>
|
|
317
|
+
: def extends readonly [infer left, "&", infer right]
|
|
318
|
+
? InferDef<left> & InferDef<right>
|
|
319
|
+
: def extends readonly [unknown, "=>", (...args: never[]) => infer output]
|
|
320
|
+
? output
|
|
321
|
+
: def extends readonly [unknown, "|>", infer output]
|
|
322
|
+
? InferDef<output>
|
|
323
|
+
: def extends readonly [infer base, ":", unknown] | readonly [infer base, "@", unknown]
|
|
324
|
+
? InferDef<base>
|
|
325
|
+
: def extends readonly ["keyof", infer base]
|
|
326
|
+
? keyof InferDef<base>
|
|
327
|
+
: def extends readonly ["instanceof", ...infer constructors]
|
|
328
|
+
? InstanceOf<constructors[number]>
|
|
329
|
+
: def extends readonly ["===", ...infer values]
|
|
330
|
+
? values[number]
|
|
331
|
+
: def extends readonly unknown[]
|
|
332
|
+
? InferTupleOutput<def>
|
|
333
|
+
: def extends object
|
|
334
|
+
? InferObject<def>
|
|
335
|
+
: unknown;
|
|
158
336
|
|
|
159
|
-
/**
|
|
160
|
-
export type InferDefIn<def = unknown> =
|
|
337
|
+
/** Infer values accepted before defaults and morphs are applied. */
|
|
338
|
+
export type InferDefIn<def = unknown> =
|
|
339
|
+
IsAny<def> extends true
|
|
340
|
+
? unknown
|
|
341
|
+
: def extends { readonly inferIn: infer input }
|
|
342
|
+
? input
|
|
343
|
+
: def extends string
|
|
344
|
+
? InferStringIn<def>
|
|
345
|
+
: def extends RegExp
|
|
346
|
+
? string
|
|
347
|
+
: def extends readonly [infer element, "[]"]
|
|
348
|
+
? InferDefIn<element>[]
|
|
349
|
+
: def extends readonly [infer left, "|", infer right]
|
|
350
|
+
? InferDefIn<left> | InferDefIn<right>
|
|
351
|
+
: def extends readonly [infer left, "&", infer right]
|
|
352
|
+
? InferDefIn<left> & InferDefIn<right>
|
|
353
|
+
: def extends readonly [infer input, "=>", unknown] | readonly [infer input, "|>", unknown]
|
|
354
|
+
? InferDefIn<input>
|
|
355
|
+
: def extends readonly [infer base, ":", unknown] | readonly [infer base, "@", unknown]
|
|
356
|
+
? InferDefIn<base>
|
|
357
|
+
: def extends readonly ["keyof", infer base]
|
|
358
|
+
? keyof InferDefIn<base>
|
|
359
|
+
: def extends readonly ["instanceof", ...infer constructors]
|
|
360
|
+
? InstanceOf<constructors[number]>
|
|
361
|
+
: def extends readonly ["===", ...infer values]
|
|
362
|
+
? values[number]
|
|
363
|
+
: def extends readonly unknown[]
|
|
364
|
+
? InferTupleInput<def>
|
|
365
|
+
: def extends object
|
|
366
|
+
? InferObjectIn<def>
|
|
367
|
+
: unknown;
|
package/src/interp.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tree-walking validator used for a schema's first few calls
|
|
3
|
-
*
|
|
2
|
+
* Tree-walking validator used for a schema's first few calls and as the
|
|
3
|
+
* targeted fallback for recursive or predicate-only JIT subtrees.
|
|
4
4
|
*
|
|
5
5
|
* Semantics must stay in lockstep with `compile.ts`:
|
|
6
6
|
* - success returns the output value; the input is returned as-is unless the
|
|
@@ -21,7 +21,8 @@ export function walk(ir: IR, value: unknown): unknown {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
function fail(path: PropertyKey[], expected: string, data: unknown): OmpErrors {
|
|
24
|
-
|
|
24
|
+
const storedPath = path.length === 0 ? undefined : path.length === 1 ? path[0] : [...path];
|
|
25
|
+
return new OmpErrors(storedPath, expected, data);
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
/** Pure predicate used for union-member scanning (no morphs, no errors). */
|
|
@@ -37,6 +38,10 @@ function checks(ir: IR, v: unknown): boolean {
|
|
|
37
38
|
return typeof v === "boolean";
|
|
38
39
|
case "bigint":
|
|
39
40
|
return typeof v === "bigint";
|
|
41
|
+
case "symbol":
|
|
42
|
+
return typeof v === "symbol";
|
|
43
|
+
case "never":
|
|
44
|
+
return false;
|
|
40
45
|
case "anyobject":
|
|
41
46
|
return typeof v === "object" && v !== null;
|
|
42
47
|
case "string":
|
|
@@ -47,16 +52,19 @@ function checks(ir: IR, v: unknown): boolean {
|
|
|
47
52
|
(!ir.url || URL.canParse(v))
|
|
48
53
|
);
|
|
49
54
|
case "number":
|
|
55
|
+
if (typeof v !== "number") return false;
|
|
50
56
|
return (
|
|
51
|
-
|
|
52
|
-
(
|
|
57
|
+
(ir.int ? Number.isInteger(v) : Number.isFinite(v)) &&
|
|
58
|
+
(ir.divisor === undefined || v % ir.divisor === 0) &&
|
|
53
59
|
(ir.min === undefined || (ir.xmin ? v > ir.min : v >= ir.min)) &&
|
|
54
60
|
(ir.max === undefined || (ir.xmax ? v < ir.max : v <= ir.max))
|
|
55
61
|
);
|
|
56
62
|
case "lit":
|
|
57
|
-
return v === ir.v;
|
|
63
|
+
return ir.v instanceof Date ? v instanceof Date && v.valueOf() === ir.v.valueOf() : v === ir.v;
|
|
58
64
|
case "union":
|
|
59
65
|
return ir.members.some(m => checks(m, v));
|
|
66
|
+
case "intersection":
|
|
67
|
+
return ir.members.every(member => checks(member, v));
|
|
60
68
|
case "array": {
|
|
61
69
|
if (!Array.isArray(v)) return false;
|
|
62
70
|
if (ir.min !== undefined && v.length < ir.min) return false;
|
|
@@ -64,6 +72,31 @@ function checks(ir: IR, v: unknown): boolean {
|
|
|
64
72
|
for (const el of v) if (!checks(ir.el, el)) return false;
|
|
65
73
|
return true;
|
|
66
74
|
}
|
|
75
|
+
case "tuple": {
|
|
76
|
+
if (!Array.isArray(v)) return false;
|
|
77
|
+
let required = ir.postfix.length;
|
|
78
|
+
for (const item of ir.prefix) if (!item.opt && !item.hasDefault) required++;
|
|
79
|
+
if (v.length < required) return false;
|
|
80
|
+
if (ir.variadic === undefined && v.length > ir.prefix.length + ir.postfix.length) return false;
|
|
81
|
+
const postfixStart = v.length - ir.postfix.length;
|
|
82
|
+
const prefixCount = Math.min(ir.prefix.length, postfixStart);
|
|
83
|
+
for (let index = 0; index < prefixCount; index++) {
|
|
84
|
+
if (!checks(ir.prefix[index].val, v[index])) return false;
|
|
85
|
+
}
|
|
86
|
+
for (let index = prefixCount; index < ir.prefix.length; index++) {
|
|
87
|
+
const item = ir.prefix[index];
|
|
88
|
+
if (!item.opt && !item.hasDefault) return false;
|
|
89
|
+
}
|
|
90
|
+
if (ir.variadic !== undefined) {
|
|
91
|
+
for (let index = prefixCount; index < postfixStart; index++) {
|
|
92
|
+
if (!checks(ir.variadic, v[index])) return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (let index = 0; index < ir.postfix.length; index++) {
|
|
96
|
+
if (!checks(ir.postfix[index], v[postfixStart + index])) return false;
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
67
100
|
case "object": {
|
|
68
101
|
if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
|
|
69
102
|
const rec = v as Record<string, unknown>;
|
|
@@ -94,6 +127,19 @@ function checks(ir: IR, v: unknown): boolean {
|
|
|
94
127
|
}
|
|
95
128
|
return true;
|
|
96
129
|
}
|
|
130
|
+
case "instance":
|
|
131
|
+
return v instanceof ir.ctor;
|
|
132
|
+
case "refine":
|
|
133
|
+
if (!checks(ir.base, v)) return false;
|
|
134
|
+
try {
|
|
135
|
+
return ir.pred(v);
|
|
136
|
+
} catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
case "alias":
|
|
140
|
+
return checks(ir.resolve(), v);
|
|
141
|
+
case "morph":
|
|
142
|
+
return checks(ir.input, v);
|
|
97
143
|
case "sub":
|
|
98
144
|
return !(ir.schema.run(v) instanceof OmpErrors);
|
|
99
145
|
}
|
|
@@ -101,6 +147,36 @@ function checks(ir: IR, v: unknown): boolean {
|
|
|
101
147
|
|
|
102
148
|
function visit(ir: IR, v: unknown, path: PropertyKey[]): unknown {
|
|
103
149
|
switch (ir.k) {
|
|
150
|
+
case "alias":
|
|
151
|
+
return visit(ir.resolve(), v, path);
|
|
152
|
+
case "refine": {
|
|
153
|
+
const base = visit(ir.base, v, path);
|
|
154
|
+
if (base instanceof OmpErrors) return base;
|
|
155
|
+
try {
|
|
156
|
+
return ir.pred(base) ? base : fail(path, ir.expected, base);
|
|
157
|
+
} catch {
|
|
158
|
+
return fail(path, ir.expected, base);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
case "morph": {
|
|
162
|
+
const input = visit(ir.input, v, path);
|
|
163
|
+
if (input instanceof OmpErrors) return input;
|
|
164
|
+
const context = {
|
|
165
|
+
error: (expected: string, data: unknown = input) => fail(path, expected, data),
|
|
166
|
+
reject: (problem: string, data: unknown = input) => fail(path, problem, data),
|
|
167
|
+
};
|
|
168
|
+
const output = ir.fn(input, context);
|
|
169
|
+
if (output instanceof OmpErrors) return output;
|
|
170
|
+
return ir.out === undefined ? output : visit(ir.out, output, path);
|
|
171
|
+
}
|
|
172
|
+
case "intersection": {
|
|
173
|
+
let output = v;
|
|
174
|
+
for (const member of ir.members) {
|
|
175
|
+
output = visit(member, output, path);
|
|
176
|
+
if (output instanceof OmpErrors) return output;
|
|
177
|
+
}
|
|
178
|
+
return output;
|
|
179
|
+
}
|
|
104
180
|
case "sub": {
|
|
105
181
|
const out = ir.schema.run(v);
|
|
106
182
|
if (out instanceof OmpErrors) {
|
|
@@ -122,7 +198,7 @@ function visit(ir: IR, v: unknown, path: PropertyKey[]): unknown {
|
|
|
122
198
|
if (!(out instanceof OmpErrors)) return out;
|
|
123
199
|
}
|
|
124
200
|
}
|
|
125
|
-
return unionFail(ir, v, path);
|
|
201
|
+
return ir.members.some(canRefineUnionFailure) ? unionFail(ir, v, path) : fail(path, expectedOf(ir), v);
|
|
126
202
|
}
|
|
127
203
|
case "array": {
|
|
128
204
|
if (!Array.isArray(v)) return fail(path, "an array", v);
|
|
@@ -149,6 +225,57 @@ function visit(ir: IR, v: unknown, path: PropertyKey[]): unknown {
|
|
|
149
225
|
}
|
|
150
226
|
return out;
|
|
151
227
|
}
|
|
228
|
+
case "tuple": {
|
|
229
|
+
if (!Array.isArray(v)) return fail(path, "an array", v);
|
|
230
|
+
let required = ir.postfix.length;
|
|
231
|
+
for (const item of ir.prefix) if (!item.opt && !item.hasDefault) required++;
|
|
232
|
+
if (v.length < required) return fail(path, `an array of at least length ${required}`, v);
|
|
233
|
+
const maximum = ir.prefix.length + ir.postfix.length;
|
|
234
|
+
if (ir.variadic === undefined && v.length > maximum) {
|
|
235
|
+
return fail(path, `an array of at most length ${maximum}`, v);
|
|
236
|
+
}
|
|
237
|
+
const postfixStart = v.length - ir.postfix.length;
|
|
238
|
+
const prefixCount = Math.min(ir.prefix.length, postfixStart);
|
|
239
|
+
const morph = hasMorph(ir);
|
|
240
|
+
const output = morph ? [...v] : v;
|
|
241
|
+
for (let index = 0; index < prefixCount; index++) {
|
|
242
|
+
path.push(index);
|
|
243
|
+
const item = visit(ir.prefix[index].val, v[index], path);
|
|
244
|
+
path.pop();
|
|
245
|
+
if (item instanceof OmpErrors) return item;
|
|
246
|
+
if (morph) output[index] = item;
|
|
247
|
+
}
|
|
248
|
+
for (let index = prefixCount; index < ir.prefix.length; index++) {
|
|
249
|
+
const item = ir.prefix[index];
|
|
250
|
+
if (item.hasDefault && morph) {
|
|
251
|
+
const payload = item.def;
|
|
252
|
+
output[index] = item.defFactory && typeof payload === "function" ? payload() : payload;
|
|
253
|
+
} else if (!item.opt) {
|
|
254
|
+
path.push(index);
|
|
255
|
+
const error = fail(path, expectedOf(item.val), MISSING);
|
|
256
|
+
path.pop();
|
|
257
|
+
return error;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (ir.variadic !== undefined) {
|
|
261
|
+
for (let index = prefixCount; index < postfixStart; index++) {
|
|
262
|
+
path.push(index);
|
|
263
|
+
const item = visit(ir.variadic, v[index], path);
|
|
264
|
+
path.pop();
|
|
265
|
+
if (item instanceof OmpErrors) return item;
|
|
266
|
+
if (morph) output[index] = item;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
for (let index = 0; index < ir.postfix.length; index++) {
|
|
270
|
+
const inputIndex = postfixStart + index;
|
|
271
|
+
path.push(inputIndex);
|
|
272
|
+
const item = visit(ir.postfix[index], v[inputIndex], path);
|
|
273
|
+
path.pop();
|
|
274
|
+
if (item instanceof OmpErrors) return item;
|
|
275
|
+
if (morph) output[inputIndex] = item;
|
|
276
|
+
}
|
|
277
|
+
return output;
|
|
278
|
+
}
|
|
152
279
|
case "object": {
|
|
153
280
|
if (typeof v !== "object" || v === null || Array.isArray(v)) return fail(path, "an object", v);
|
|
154
281
|
const rec = v as Record<string, unknown>;
|
|
@@ -220,6 +347,23 @@ function prefixAll(errs: OmpErrors, path: PropertyKey[]): OmpErrors {
|
|
|
220
347
|
return errs;
|
|
221
348
|
}
|
|
222
349
|
|
|
350
|
+
/** True when a union failure can be replaced with a more specific nested error. */
|
|
351
|
+
export function canRefineUnionFailure(member: IR): boolean {
|
|
352
|
+
const base = member.k === "sub" ? member.schema.ir : member;
|
|
353
|
+
if (member.k === "sub") {
|
|
354
|
+
return (
|
|
355
|
+
base.k === "array" ||
|
|
356
|
+
base.k === "object" ||
|
|
357
|
+
base.k === "anyobject" ||
|
|
358
|
+
base.k === "string" ||
|
|
359
|
+
base.k === "number"
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
if (base.k === "array" || base.k === "object") return true;
|
|
363
|
+
if (base.k === "string") return base.min !== undefined || base.max !== undefined || base.url === true;
|
|
364
|
+
return base.k === "number" && (base.int === true || base.min !== undefined || base.max !== undefined);
|
|
365
|
+
}
|
|
366
|
+
|
|
223
367
|
/**
|
|
224
368
|
* Detailed failure for a union: descend into the member the value was clearly
|
|
225
369
|
* aimed at — unique runtime-kind match, else an object member whose literal
|
|
@@ -227,7 +371,7 @@ function prefixAll(errs: OmpErrors, path: PropertyKey[]): OmpErrors {
|
|
|
227
371
|
* for a precise nested error (paths, narrow messages) instead of the coarse
|
|
228
372
|
* "A or B" expectation.
|
|
229
373
|
*/
|
|
230
|
-
export function unionFail(ir: IR & { k: "union" }, v: unknown, path: PropertyKey[]): OmpErrors {
|
|
374
|
+
export function unionFail(ir: IR & { k: "union" }, v: unknown, path: PropertyKey[], expected?: string): OmpErrors {
|
|
231
375
|
let best: IR | undefined;
|
|
232
376
|
for (const m of ir.members) {
|
|
233
377
|
const base = m.k === "sub" ? m.schema.ir : m;
|
|
@@ -243,7 +387,7 @@ export function unionFail(ir: IR & { k: "union" }, v: unknown, path: PropertyKey
|
|
|
243
387
|
const out = visit(best, v, path);
|
|
244
388
|
if (out instanceof OmpErrors) return out;
|
|
245
389
|
}
|
|
246
|
-
return fail(path, expectedOf(ir), v);
|
|
390
|
+
return fail(path, expected ?? expectedOf(ir), v);
|
|
247
391
|
}
|
|
248
392
|
|
|
249
393
|
/** Pick the sole object member whose literal-typed property matches the value's. */
|