@oh-my-pi/omptype 17.2.7 → 17.2.8
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 +23 -0
- package/dist/js/compile.js +414 -219
- package/dist/js/errors.js +236 -41
- package/dist/js/from-json-schema.js +234 -0
- package/dist/js/index.js +1 -0
- package/dist/js/interp.js +514 -132
- package/dist/js/ir.js +972 -202
- package/dist/js/json-schema.js +73 -34
- package/dist/js/keywords.js +108 -1
- package/dist/js/type.js +2156 -172
- package/dist/js/typebox.js +41 -32
- package/dist/js/zod.js +2 -2
- package/dist/types/compile.d.ts +1 -1
- package/dist/types/errors.d.ts +19 -18
- package/dist/types/from-json-schema.d.ts +9 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/infer.d.ts +45 -6
- package/dist/types/interp.d.ts +9 -2
- package/dist/types/ir.d.ts +57 -6
- package/dist/types/json-schema.d.ts +10 -1
- package/dist/types/type.d.ts +324 -48
- package/dist/types/typebox.d.ts +78 -50
- package/package.json +5 -1
- package/src/compile.ts +598 -243
- package/src/errors.ts +247 -49
- package/src/from-json-schema.ts +231 -0
- package/src/index.ts +1 -0
- package/src/infer.ts +107 -32
- package/src/interp.ts +457 -118
- package/src/ir.ts +981 -212
- package/src/json-schema.ts +82 -34
- package/src/keywords.ts +111 -1
- package/src/type.ts +2760 -271
- package/src/typebox.ts +141 -98
- package/src/zod.ts +1 -1
package/src/type.ts
CHANGED
|
@@ -12,30 +12,46 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { compile, compileAllows } from "./compile";
|
|
14
14
|
import { type ErrorConfig, OmpErrors, OmpTypeError, TraversalError } from "./errors";
|
|
15
|
-
import type { InferDef, InferDefIn, InferObjectLiteral, InferString } from "./infer";
|
|
15
|
+
import type { InferDef, InferDefIn, InferObjectLiteral, InferObjectLiteralIn, InferString } from "./infer";
|
|
16
16
|
import { walk } from "./interp";
|
|
17
17
|
import {
|
|
18
18
|
type AliasResolver,
|
|
19
19
|
type Constructor,
|
|
20
20
|
type Def,
|
|
21
|
+
distributeFilter,
|
|
22
|
+
type EmbeddableSchema,
|
|
21
23
|
embed,
|
|
22
24
|
expectedOf,
|
|
23
25
|
hasMorph,
|
|
24
26
|
type IR,
|
|
25
27
|
IR_BRAND,
|
|
28
|
+
isSimpleIR,
|
|
26
29
|
keyOf,
|
|
30
|
+
markThisOnlyResolver,
|
|
27
31
|
type PropIR,
|
|
28
32
|
parseDef,
|
|
33
|
+
type TupleIR,
|
|
34
|
+
useAssignability,
|
|
29
35
|
} from "./ir";
|
|
30
36
|
import { irToJsonSchema, type JsonSchemaOptions } from "./json-schema";
|
|
31
37
|
import { keywordIR, patternIR } from "./keywords";
|
|
32
38
|
|
|
39
|
+
// `Extract`/`Exclude` in the string DSL need assignability, which is defined here.
|
|
40
|
+
useAssignability(isSubtype);
|
|
41
|
+
|
|
42
|
+
export interface NarrowErrorInput {
|
|
43
|
+
readonly expected: string;
|
|
44
|
+
readonly actual?: unknown;
|
|
45
|
+
readonly path?: readonly PropertyKey[];
|
|
46
|
+
readonly relativePath?: readonly PropertyKey[];
|
|
47
|
+
}
|
|
48
|
+
|
|
33
49
|
/** Context passed to `.narrow()` / `.pipe()` callbacks. */
|
|
34
50
|
export interface NarrowContext {
|
|
35
|
-
|
|
51
|
+
readonly path: readonly PropertyKey[];
|
|
52
|
+
error(error: string | NarrowErrorInput): OmpErrors;
|
|
36
53
|
mustBe(expectation: string): false;
|
|
37
|
-
|
|
38
|
-
reject(problem: string): false;
|
|
54
|
+
reject(problem: string | NarrowErrorInput): OmpErrors | false;
|
|
39
55
|
}
|
|
40
56
|
|
|
41
57
|
/** Schema metadata and validation-message overrides accepted by `.configure()`. */
|
|
@@ -73,6 +89,52 @@ export interface SelectedNode {
|
|
|
73
89
|
readonly unit?: unknown;
|
|
74
90
|
}
|
|
75
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Standard Schema V1 (https://standardschema.dev) — the cross-library
|
|
94
|
+
* validation interface consumed by tools like @t3-oss/env, tRPC, and
|
|
95
|
+
* Hono validators. Inlined per the spec's recommendation; no dependency.
|
|
96
|
+
*/
|
|
97
|
+
export interface StandardSchemaV1<Input = unknown, Output = Input> {
|
|
98
|
+
readonly "~standard": StandardSchemaV1.Props<Input, Output>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export namespace StandardSchemaV1 {
|
|
102
|
+
export interface Props<Input = unknown, Output = Input> {
|
|
103
|
+
readonly version: 1;
|
|
104
|
+
readonly vendor: string;
|
|
105
|
+
readonly validate: (value: unknown) => Result<Output> | Promise<Result<Output>>;
|
|
106
|
+
readonly types?: Types<Input, Output> | undefined;
|
|
107
|
+
readonly jsonSchema: {
|
|
108
|
+
readonly input: (options: StandardJsonSchemaOptions) => Record<string, unknown>;
|
|
109
|
+
readonly output: (options: StandardJsonSchemaOptions) => Record<string, unknown>;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
export type Result<Output> = SuccessResult<Output> | FailureResult;
|
|
113
|
+
export interface SuccessResult<Output> {
|
|
114
|
+
readonly value: Output;
|
|
115
|
+
readonly issues?: undefined;
|
|
116
|
+
}
|
|
117
|
+
export interface FailureResult {
|
|
118
|
+
readonly issues: readonly Issue[];
|
|
119
|
+
}
|
|
120
|
+
export interface Issue {
|
|
121
|
+
readonly message: string;
|
|
122
|
+
readonly path?: readonly PropertyKey[] | undefined;
|
|
123
|
+
}
|
|
124
|
+
export interface Types<Input = unknown, Output = Input> {
|
|
125
|
+
readonly input: Input;
|
|
126
|
+
readonly output: Output;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface StandardJsonSchemaOptions {
|
|
131
|
+
readonly target: "draft-2020-12" | "draft-07" | string;
|
|
132
|
+
readonly libraryOptions?: {
|
|
133
|
+
readonly dialect?: string | null;
|
|
134
|
+
readonly fallback?: JsonSchemaOptions["fallback"];
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
76
138
|
/** A compiled schema: callable validator plus composition methods. */
|
|
77
139
|
export interface Type<out t = unknown, i = t> {
|
|
78
140
|
(data: unknown): t | OmpErrors;
|
|
@@ -84,11 +146,23 @@ export interface Type<out t = unknown, i = t> {
|
|
|
84
146
|
readonly hasDefault: boolean;
|
|
85
147
|
readonly defaultValue?: unknown;
|
|
86
148
|
readonly description?: string;
|
|
149
|
+
/** Canonical ArkType-compatible expression for diagnostics. */
|
|
150
|
+
readonly expression: string;
|
|
151
|
+
/** Canonical structural node representation. */
|
|
152
|
+
readonly json: unknown;
|
|
87
153
|
/** Full validate+morph pipeline; identical to calling the schema. */
|
|
88
154
|
readonly run: (data: unknown) => unknown;
|
|
155
|
+
/** ArkType-compatible inference alias (type-only; undefined at runtime). */
|
|
156
|
+
readonly t: t;
|
|
157
|
+
/** Scope that parsed this schema (or the ambient Ark-compatible scope). */
|
|
158
|
+
readonly $: TypeScope | { readonly internal: { readonly name: "ark" } };
|
|
89
159
|
|
|
90
160
|
/** Inference-only output type (no runtime value). */
|
|
91
161
|
readonly infer: t;
|
|
162
|
+
/** Standalone validator for the schema's accepted input. */
|
|
163
|
+
readonly in: FluentType<i>;
|
|
164
|
+
/** Standalone validator for its known output, or `unknown` after an opaque morph. */
|
|
165
|
+
readonly out: FluentType<t>;
|
|
92
166
|
/** Inference-only input type (no runtime value). */
|
|
93
167
|
readonly inferIn: i;
|
|
94
168
|
|
|
@@ -100,6 +174,8 @@ export interface Type<out t = unknown, i = t> {
|
|
|
100
174
|
from(data: i): t;
|
|
101
175
|
/** JSON Schema for this schema's structural base. */
|
|
102
176
|
toJsonSchema(options?: ToJsonSchemaOptions): Record<string, unknown>;
|
|
177
|
+
/** Standard Schema V1 interop (synchronous validation). */
|
|
178
|
+
readonly "~standard": StandardSchemaV1.Props<i, t>;
|
|
103
179
|
}
|
|
104
180
|
|
|
105
181
|
type MergeTypes<left, right> = left extends object
|
|
@@ -108,27 +184,91 @@ type MergeTypes<left, right> = left extends object
|
|
|
108
184
|
: right
|
|
109
185
|
: right;
|
|
110
186
|
|
|
187
|
+
type SimplifyNary<t> = t extends object ? { [key in keyof t]: t[key] } : t;
|
|
188
|
+
type UnionToIntersection<union> = (union extends unknown ? (value: union) => void : never) extends (
|
|
189
|
+
value: infer intersection,
|
|
190
|
+
) => void
|
|
191
|
+
? intersection
|
|
192
|
+
: never;
|
|
193
|
+
|
|
194
|
+
type NaryOrOutput<definitions extends readonly unknown[]> = InferDef<definitions[number]>;
|
|
195
|
+
type NaryOrInput<definitions extends readonly unknown[]> = InferDefIn<definitions[number]>;
|
|
196
|
+
type NaryAndOutput<definitions extends readonly unknown[]> = definitions extends readonly []
|
|
197
|
+
? unknown
|
|
198
|
+
: SimplifyNary<UnionToIntersection<InferDef<definitions[number]>>>;
|
|
199
|
+
type NaryAndInput<definitions extends readonly unknown[]> = definitions extends readonly []
|
|
200
|
+
? unknown
|
|
201
|
+
: SimplifyNary<UnionToIntersection<InferDefIn<definitions[number]>>>;
|
|
202
|
+
// biome-ignore lint/complexity/noBannedTypes: generic accumulator default
|
|
203
|
+
type ReduceNaryMergeOutput<definitions extends readonly unknown[], result = {}> = definitions extends readonly [
|
|
204
|
+
infer head,
|
|
205
|
+
...infer tail,
|
|
206
|
+
]
|
|
207
|
+
? ReduceNaryMergeOutput<tail, SimplifyNary<MergeTypes<result, InferDef<head>>>>
|
|
208
|
+
: definitions extends readonly []
|
|
209
|
+
? result
|
|
210
|
+
: // biome-ignore lint/complexity/noBannedTypes: empty object fallback
|
|
211
|
+
{};
|
|
212
|
+
// biome-ignore lint/complexity/noBannedTypes: generic accumulator default
|
|
213
|
+
type ReduceNaryMergeInput<definitions extends readonly unknown[], result = {}> = definitions extends readonly [
|
|
214
|
+
infer head,
|
|
215
|
+
...infer tail,
|
|
216
|
+
]
|
|
217
|
+
? ReduceNaryMergeInput<tail, SimplifyNary<MergeTypes<result, InferDefIn<head>>>>
|
|
218
|
+
: definitions extends readonly []
|
|
219
|
+
? result
|
|
220
|
+
: // biome-ignore lint/complexity/noBannedTypes: empty object fallback
|
|
221
|
+
{};
|
|
222
|
+
type NaryMergeOutput<definitions extends readonly unknown[]> = definitions extends readonly []
|
|
223
|
+
? object
|
|
224
|
+
: ReduceNaryMergeOutput<definitions>;
|
|
225
|
+
type NaryMergeInput<definitions extends readonly unknown[]> = definitions extends readonly []
|
|
226
|
+
? object
|
|
227
|
+
: ReduceNaryMergeInput<definitions>;
|
|
228
|
+
|
|
229
|
+
type PipeItemOutput<item> =
|
|
230
|
+
item extends SchemaInference<infer output, unknown>
|
|
231
|
+
? output
|
|
232
|
+
: item extends (data: never, ...arguments_: never[]) => infer output
|
|
233
|
+
? Exclude<output, OmpErrors>
|
|
234
|
+
: InferDef<item>;
|
|
235
|
+
type NaryPipeOutput<items extends readonly unknown[]> = items extends readonly [...(readonly unknown[]), infer last]
|
|
236
|
+
? PipeItemOutput<last>
|
|
237
|
+
: unknown;
|
|
238
|
+
type NaryPipeInput<items extends readonly unknown[]> = items extends readonly [infer first, ...(readonly unknown[])]
|
|
239
|
+
? first extends SchemaInference<unknown, infer input>
|
|
240
|
+
? input
|
|
241
|
+
: first extends (data: infer input, ...arguments_: never[]) => unknown
|
|
242
|
+
? input
|
|
243
|
+
: InferDefIn<first>
|
|
244
|
+
: unknown;
|
|
245
|
+
|
|
111
246
|
interface FluentMethods<t, i> {
|
|
112
247
|
describe(description: string): FluentType<t, i>;
|
|
113
|
-
configure(config: SchemaConfig): FluentType<t, i>;
|
|
114
|
-
|
|
248
|
+
configure(config: SchemaConfig, selector?: "self" | ConfigureSelector): FluentType<t, i>;
|
|
249
|
+
|
|
250
|
+
default(value: i | (() => i)): FluentType<t, i | undefined>;
|
|
115
251
|
optional(): readonly [SchemaInference<t, i>, "?"];
|
|
116
252
|
or<r, ri>(def: SchemaInference<r, ri>): FluentType<t | r, i | ri>;
|
|
117
253
|
or<const def extends string>(def: def): FluentType<t | InferString<def>, i | InferString<def>>;
|
|
118
254
|
or<const def extends Record<string, unknown>>(
|
|
119
255
|
def: def,
|
|
120
|
-
): FluentType<t | InferObjectLiteral<def>, i |
|
|
256
|
+
): FluentType<t | InferObjectLiteral<def>, i | InferObjectLiteralIn<def>>;
|
|
121
257
|
or(def: Def): FluentType<unknown>;
|
|
122
258
|
and<r, ri>(def: SchemaInference<r, ri>): FluentType<t & r, i & ri>;
|
|
123
259
|
and<const def extends Record<string, unknown>>(
|
|
124
260
|
def: def,
|
|
125
|
-
): FluentType<t & InferObjectLiteral<def>, i &
|
|
261
|
+
): FluentType<t & InferObjectLiteral<def>, i & InferObjectLiteralIn<def>>;
|
|
126
262
|
and(def: Def): FluentType<unknown>;
|
|
127
263
|
equals(def: Def): boolean;
|
|
128
264
|
ifEquals(def: Def): FluentType<t, i> | undefined;
|
|
265
|
+
ifExtends(def: Def): FluentType<t, i> | undefined;
|
|
129
266
|
extends(def: Def): boolean;
|
|
130
267
|
overlaps(def: Def): boolean;
|
|
131
|
-
distribute<r>(
|
|
268
|
+
distribute<r>(
|
|
269
|
+
mapper: (branch: FluentType<unknown>) => SchemaInference<r>,
|
|
270
|
+
reducer?: (branches: readonly SchemaInference<r>[]) => SchemaInference<unknown>,
|
|
271
|
+
): FluentType<r>;
|
|
132
272
|
select(kind: string): readonly SelectedNode[];
|
|
133
273
|
array(): FluentType<t[], i[]>;
|
|
134
274
|
atLeastLength(bound: number): FluentType<t, i>;
|
|
@@ -146,18 +286,19 @@ interface FluentMethods<t, i> {
|
|
|
146
286
|
nonNegative(): FluentType<t, i>;
|
|
147
287
|
nonPositive(): FluentType<t, i>;
|
|
148
288
|
matching(pattern: RegExp): FluentType<t, i>;
|
|
149
|
-
atOrAfter(bound: Date): FluentType<t, i>;
|
|
150
|
-
atOrBefore(bound: Date): FluentType<t, i>;
|
|
151
|
-
laterThan(bound: Date): FluentType<t, i>;
|
|
152
|
-
earlierThan(bound: Date): FluentType<t, i>;
|
|
153
|
-
pipe
|
|
289
|
+
atOrAfter(bound: Date | number): FluentType<t, i>;
|
|
290
|
+
atOrBefore(bound: Date | number): FluentType<t, i>;
|
|
291
|
+
laterThan(bound: Date | number): FluentType<t, i>;
|
|
292
|
+
earlierThan(bound: Date | number): FluentType<t, i>;
|
|
293
|
+
readonly pipe: PipeMethod<t, i>;
|
|
154
294
|
to<const def>(def: def): FluentType<InferDef<def>, i>;
|
|
155
295
|
filter<narrowed extends i>(fn: (data: i, ctx: NarrowContext) => data is narrowed): FluentType<t, narrowed>;
|
|
156
|
-
filter(fn: (data: i, ctx: NarrowContext) => boolean): FluentType<t, i>;
|
|
296
|
+
filter(fn: (data: i, ctx: NarrowContext) => boolean | OmpErrors): FluentType<t, i>;
|
|
157
297
|
narrow<narrowed extends t>(fn: (data: t, ctx: NarrowContext) => data is narrowed): FluentType<narrowed, i>;
|
|
158
|
-
narrow(fn: (data: t, ctx: NarrowContext) => boolean): FluentType<t, i>;
|
|
298
|
+
narrow(fn: (data: t, ctx: NarrowContext) => boolean | OmpErrors): FluentType<t, i>;
|
|
159
299
|
brand<const name extends string>(name: name): FluentType<Brand<t, name>, i>;
|
|
160
300
|
as<castTo>(): FluentType<castTo, i>;
|
|
301
|
+
readonly(): FluentType<Readonly<t>, i>;
|
|
161
302
|
extract<r, ri>(def: SchemaInference<r, ri>): FluentType<Extract<t, r>, Extract<i, ri>>;
|
|
162
303
|
extract<const def extends string>(def: def): FluentType<Extract<t, InferString<def>>, Extract<i, InferString<def>>>;
|
|
163
304
|
extract(def: Def): FluentType<unknown>;
|
|
@@ -168,6 +309,16 @@ interface FluentMethods<t, i> {
|
|
|
168
309
|
onDeepUndeclaredKey(behavior: "ignore" | "reject" | "delete"): FluentType<t, i>;
|
|
169
310
|
}
|
|
170
311
|
|
|
312
|
+
interface PipeMethod<t, i> {
|
|
313
|
+
<r>(fn: (data: t, ctx: NarrowContext) => r): FluentType<Exclude<r, OmpErrors>, i>;
|
|
314
|
+
<r, ri>(schema: SchemaInference<r, ri>): FluentType<r, i>;
|
|
315
|
+
(...steps: readonly unknown[]): FluentType<unknown, i>;
|
|
316
|
+
readonly try: {
|
|
317
|
+
<r>(fn: (data: t, ctx: NarrowContext) => r): FluentType<Exclude<r, OmpErrors>, i>;
|
|
318
|
+
(...steps: readonly unknown[]): FluentType<unknown, i>;
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
171
322
|
type InputObject<i> = i extends object ? i : object;
|
|
172
323
|
|
|
173
324
|
interface ObjectMethods<t extends object, i> {
|
|
@@ -176,9 +327,7 @@ interface ObjectMethods<t extends object, i> {
|
|
|
176
327
|
mapper: (property: TypeProperty) => TypeProperty | readonly TypeProperty[],
|
|
177
328
|
): FluentType<Record<PropertyKey, unknown>>;
|
|
178
329
|
keyof(): FluentType<Extract<keyof t, PropertyKey>, Extract<keyof InputObject<i>, PropertyKey>>;
|
|
179
|
-
get<
|
|
180
|
-
key: key,
|
|
181
|
-
): FluentType<t[key], key extends keyof InputObject<i> ? InputObject<i>[key] : unknown>;
|
|
330
|
+
get<const path extends readonly PropertyKey[]>(...path: path): FluentType<unknown>;
|
|
182
331
|
pick<const keys extends readonly (keyof t)[]>(
|
|
183
332
|
...keys: keys
|
|
184
333
|
): FluentType<Pick<t, keys[number]>, Pick<InputObject<i>, Extract<keys[number], keyof InputObject<i>>>>;
|
|
@@ -190,7 +339,7 @@ interface ObjectMethods<t extends object, i> {
|
|
|
190
339
|
merge<r, ri>(def: SchemaInference<r, ri>): FluentType<MergeTypes<t, r>, MergeTypes<i, ri>>;
|
|
191
340
|
merge<const def extends Record<string, unknown>>(
|
|
192
341
|
def: def,
|
|
193
|
-
): FluentType<MergeTypes<t, InferObjectLiteral<def>>, MergeTypes<i,
|
|
342
|
+
): FluentType<MergeTypes<t, InferObjectLiteral<def>>, MergeTypes<i, InferObjectLiteralIn<def>>>;
|
|
194
343
|
merge(def: Def): FluentType<unknown>;
|
|
195
344
|
}
|
|
196
345
|
|
|
@@ -204,59 +353,236 @@ type ObjectMethodsFor<t, i> = [t] extends [never]
|
|
|
204
353
|
/** Callable schema with fluent methods specialized to its output and input. */
|
|
205
354
|
export type FluentType<t = unknown, i = t> = Type<t, i> & FluentMethods<t, i> & ObjectMethodsFor<t, i>;
|
|
206
355
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
356
|
+
type FnDefinition = Def | SchemaInference<unknown, unknown>;
|
|
357
|
+
|
|
358
|
+
/** Function returned by `type.fn`: arguments and an optional return are validated at every call. */
|
|
359
|
+
export type TypedFunction<
|
|
360
|
+
parameters extends readonly unknown[] = readonly unknown[],
|
|
361
|
+
returns = unknown,
|
|
362
|
+
declaredReturns = returns,
|
|
363
|
+
> = ((...arguments_: parameters) => returns) & {
|
|
364
|
+
readonly params: FluentType<parameters>;
|
|
365
|
+
readonly returns: FluentType<declaredReturns>;
|
|
366
|
+
readonly expression: string;
|
|
367
|
+
readonly raw: (...arguments_: parameters) => returns;
|
|
368
|
+
};
|
|
212
369
|
|
|
370
|
+
type InferFnDefinition<definition> =
|
|
371
|
+
definition extends SchemaInference<infer output, unknown> ? output : InferDef<definition>;
|
|
372
|
+
|
|
373
|
+
type InferFnParameters<
|
|
374
|
+
definitions extends readonly unknown[],
|
|
375
|
+
accumulator extends readonly unknown[] = [],
|
|
376
|
+
> = definitions extends readonly [infer head, ...infer tail]
|
|
377
|
+
? head extends ":"
|
|
378
|
+
? accumulator
|
|
379
|
+
: InferFnParameters<tail, readonly [...accumulator, InferFnDefinition<head>]>
|
|
380
|
+
: accumulator;
|
|
381
|
+
|
|
382
|
+
type InferFnReturn<definitions extends readonly unknown[], inferred> = definitions extends readonly [
|
|
383
|
+
...(readonly unknown[]),
|
|
384
|
+
":",
|
|
385
|
+
infer returns,
|
|
386
|
+
]
|
|
387
|
+
? InferFnDefinition<returns>
|
|
388
|
+
: inferred;
|
|
389
|
+
|
|
390
|
+
type DeclaredFnReturn<definitions extends readonly unknown[]> = definitions extends readonly [
|
|
391
|
+
...(readonly unknown[]),
|
|
392
|
+
":",
|
|
393
|
+
infer returns,
|
|
394
|
+
]
|
|
395
|
+
? InferFnDefinition<returns>
|
|
396
|
+
: unknown;
|
|
397
|
+
|
|
398
|
+
type FnFactory<definitions extends readonly FnDefinition[]> = <result>(
|
|
399
|
+
implementation: (...arguments_: InferFnParameters<definitions>) => InferFnReturn<definitions, result>,
|
|
400
|
+
) => TypedFunction<InferFnParameters<definitions>, InferFnReturn<definitions, result>, DeclaredFnReturn<definitions>>;
|
|
401
|
+
|
|
402
|
+
/** Parses function parameter schemas and validates calls and declared returns. */
|
|
403
|
+
export interface FnParser {
|
|
404
|
+
<const definitions extends readonly FnDefinition[]>(...definitions: definitions): FnFactory<definitions>;
|
|
405
|
+
raw<const definitions extends readonly FnDefinition[]>(...definitions: definitions): FnFactory<definitions>;
|
|
406
|
+
}
|
|
213
407
|
interface Step {
|
|
214
408
|
kind: "pipe" | "narrow" | "filter";
|
|
215
409
|
fn: (data: unknown, ctx: NarrowContext) => unknown;
|
|
410
|
+
/** Convert thrown callback exceptions into validation errors. */
|
|
411
|
+
try?: boolean;
|
|
412
|
+
/** Output IR when the step validates its output; drives public `.out`. */
|
|
413
|
+
out?: IR;
|
|
216
414
|
}
|
|
415
|
+
/** Runtime constructor-like value used by ArkType-compatible `instanceof Type` checks. */
|
|
416
|
+
export const Type = Object.defineProperty(function Type(): void {}, Symbol.hasInstance, {
|
|
417
|
+
value: (value: unknown): boolean =>
|
|
418
|
+
(typeof value === "function" || (typeof value === "object" && value !== null)) && IR_BRAND in value,
|
|
419
|
+
});
|
|
217
420
|
|
|
218
421
|
interface TypeMeta {
|
|
219
422
|
description?: string;
|
|
220
423
|
defaultValue?: unknown;
|
|
221
424
|
hasDefault?: boolean;
|
|
425
|
+
defaultOutput?: unknown;
|
|
426
|
+
hasDefaultOutput?: boolean;
|
|
222
427
|
errorConfig?: ErrorConfig;
|
|
428
|
+
clone?: false | ((input: unknown) => unknown);
|
|
429
|
+
}
|
|
430
|
+
function descriptionOf(ir: IR, seen: Set<IR> = new Set()): string {
|
|
431
|
+
if (ir.desc !== undefined) return ir.desc;
|
|
432
|
+
if (seen.has(ir)) return ir.k === "alias" ? ir.name : expectedOf(ir);
|
|
433
|
+
seen.add(ir);
|
|
434
|
+
if (ir.k === "alias") return descriptionOf(ir.resolve(), seen);
|
|
435
|
+
if (ir.k === "object") {
|
|
436
|
+
return `{ ${ir.props.map(prop => `${String(prop.key)}${prop.opt ? "?" : ""}: ${descriptionOf(prop.val, seen)}`).join(", ")} }`;
|
|
437
|
+
}
|
|
438
|
+
return expectedOf(ir);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export interface ConfigureSelector {
|
|
442
|
+
readonly kind?: string;
|
|
443
|
+
readonly where?: (node: { readonly domain?: string; readonly kind: string }) => boolean;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function errorConfigOf(config: SchemaConfig): ErrorConfig {
|
|
447
|
+
return {
|
|
448
|
+
...(config.description === undefined || config.expected !== undefined ? {} : { expected: config.description }),
|
|
449
|
+
...(config.expected === undefined ? {} : { expected: config.expected }),
|
|
450
|
+
...(config.actual === undefined ? {} : { actual: config.actual }),
|
|
451
|
+
...(config.problem === undefined ? {} : { problem: config.problem }),
|
|
452
|
+
...(config.message === undefined ? {} : { message: config.message }),
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function configureNode(ir: IR, config: SchemaConfig): IR {
|
|
457
|
+
return {
|
|
458
|
+
...ir,
|
|
459
|
+
cfg: { ...ir.cfg, ...errorConfigOf(config) },
|
|
460
|
+
...(config.description === undefined ? {} : { desc: config.description }),
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function configureSelected(ir: IR, config: SchemaConfig, selector: ConfigureSelector): IR {
|
|
465
|
+
const domain =
|
|
466
|
+
ir.k === "string" || ir.k === "number" || ir.k === "boolean" || ir.k === "bigint" || ir.k === "symbol"
|
|
467
|
+
? ir.k
|
|
468
|
+
: ir.k === "object" || ir.k === "array" || ir.k === "tuple" || ir.k === "instance" || ir.k === "anyobject"
|
|
469
|
+
? "object"
|
|
470
|
+
: undefined;
|
|
471
|
+
const kind = ir.k === "number" && ir.divisor !== undefined ? "divisor" : "domain";
|
|
472
|
+
if (
|
|
473
|
+
(selector.kind === undefined || selector.kind === kind) &&
|
|
474
|
+
(selector.where === undefined || selector.where({ kind, domain }))
|
|
475
|
+
) {
|
|
476
|
+
return configureNode(ir, config);
|
|
477
|
+
}
|
|
478
|
+
switch (ir.k) {
|
|
479
|
+
case "array":
|
|
480
|
+
return { ...ir, el: configureSelected(ir.el, config, selector) };
|
|
481
|
+
case "tuple":
|
|
482
|
+
return {
|
|
483
|
+
...ir,
|
|
484
|
+
prefix: ir.prefix.map(item => ({ ...item, val: configureSelected(item.val, config, selector) })),
|
|
485
|
+
...(ir.variadic === undefined ? {} : { variadic: configureSelected(ir.variadic, config, selector) }),
|
|
486
|
+
postfix: ir.postfix.map(item => configureSelected(item, config, selector)),
|
|
487
|
+
};
|
|
488
|
+
case "object":
|
|
489
|
+
return {
|
|
490
|
+
...ir,
|
|
491
|
+
props: ir.props.map(prop => ({ ...prop, val: configureSelected(prop.val, config, selector) })),
|
|
492
|
+
...(ir.index === undefined ? {} : { index: configureSelected(ir.index, config, selector) }),
|
|
493
|
+
symbolIndex: ir.symbolIndex === undefined ? undefined : configureSelected(ir.symbolIndex, config, selector),
|
|
494
|
+
patternIndexes: ir.patternIndexes?.map(index => ({
|
|
495
|
+
key: configureSelected(index.key, config, selector),
|
|
496
|
+
val: configureSelected(index.val, config, selector),
|
|
497
|
+
})),
|
|
498
|
+
};
|
|
499
|
+
case "union":
|
|
500
|
+
case "intersection":
|
|
501
|
+
return { ...ir, members: ir.members.map(member => configureSelected(member, config, selector)) };
|
|
502
|
+
case "refine":
|
|
503
|
+
return { ...ir, base: configureSelected(ir.base, config, selector) };
|
|
504
|
+
case "morph":
|
|
505
|
+
return {
|
|
506
|
+
...ir,
|
|
507
|
+
input: configureSelected(ir.input, config, selector),
|
|
508
|
+
...(ir.out === undefined ? {} : { out: configureSelected(ir.out, config, selector) }),
|
|
509
|
+
};
|
|
510
|
+
default:
|
|
511
|
+
return ir;
|
|
512
|
+
}
|
|
223
513
|
}
|
|
224
514
|
|
|
225
515
|
class Ctx implements NarrowContext {
|
|
226
516
|
expectation: string | undefined;
|
|
517
|
+
errors: OmpErrors | undefined;
|
|
518
|
+
readonly path: readonly PropertyKey[];
|
|
519
|
+
#data: unknown;
|
|
520
|
+
|
|
521
|
+
constructor(data: unknown, path: readonly PropertyKey[] = []) {
|
|
522
|
+
this.#data = data;
|
|
523
|
+
this.path = path.map(key => (typeof key === "symbol" ? String(key) : key));
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
error(input: string | NarrowErrorInput): OmpErrors {
|
|
527
|
+
const detail = typeof input === "string" ? { expected: input } : input;
|
|
528
|
+
const error = OmpErrors.single([...(detail.path ?? detail.relativePath ?? [])], detail.expected, this.#data, {
|
|
529
|
+
preserveActual: true,
|
|
530
|
+
...(Object.hasOwn(detail, "actual") ? { actual: String(detail.actual) } : {}),
|
|
531
|
+
});
|
|
532
|
+
if (this.errors) this.errors.append(error);
|
|
533
|
+
else this.errors = error;
|
|
534
|
+
return error;
|
|
535
|
+
}
|
|
227
536
|
|
|
228
537
|
mustBe(expectation: string): false {
|
|
229
538
|
this.expectation = expectation;
|
|
230
539
|
return false;
|
|
231
540
|
}
|
|
232
541
|
|
|
233
|
-
reject(
|
|
234
|
-
|
|
542
|
+
reject(input: string | NarrowErrorInput): OmpErrors | false {
|
|
543
|
+
if (typeof input === "string") {
|
|
544
|
+
this.expectation = input;
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
this.error(input);
|
|
235
548
|
return false;
|
|
236
549
|
}
|
|
237
550
|
}
|
|
238
551
|
|
|
239
|
-
type Validator = (data: unknown) => unknown;
|
|
240
|
-
type Allows = (data: unknown) =>
|
|
552
|
+
type Validator = (data: unknown, path?: readonly PropertyKey[]) => unknown;
|
|
553
|
+
type Allows = (data: unknown) => data is unknown;
|
|
241
554
|
|
|
242
555
|
const kBase = Symbol("omptype.base");
|
|
243
556
|
const kSteps = Symbol("omptype.steps");
|
|
244
557
|
|
|
245
558
|
const EMPTY_STEPS: Step[] = [];
|
|
246
559
|
const EMPTY_META: TypeMeta = {};
|
|
560
|
+
const ARK_COMPAT_SCOPE = Object.freeze({ internal: Object.freeze({ name: "ark" as const }) });
|
|
247
561
|
|
|
248
|
-
interface InternalType
|
|
562
|
+
interface InternalType
|
|
563
|
+
extends Type<unknown, unknown>,
|
|
564
|
+
FluentMethods<unknown, unknown>,
|
|
565
|
+
ObjectMethods<Record<PropertyKey, unknown>, unknown> {
|
|
249
566
|
(data: unknown): unknown;
|
|
250
567
|
[IR_BRAND]: true;
|
|
251
568
|
[kBase]: Validator;
|
|
252
569
|
[kSteps]: Step[];
|
|
570
|
+
clone?: false | ((input: unknown) => unknown);
|
|
253
571
|
allows: Allows;
|
|
254
572
|
ir: IR;
|
|
255
573
|
hasSteps: boolean;
|
|
256
574
|
hasDefault: boolean;
|
|
575
|
+
/** Output IR of the last `.to(target)` step, when any. */
|
|
576
|
+
stepOut?: IR;
|
|
577
|
+
/** True when the last pipe step is bare — output shape statically unknown. */
|
|
578
|
+
opaqueOutput?: boolean;
|
|
257
579
|
defaultValue?: unknown;
|
|
580
|
+
defaultOutput?: unknown;
|
|
581
|
+
hasDefaultOutput?: boolean;
|
|
258
582
|
description?: string;
|
|
259
583
|
errorConfig?: ErrorConfig;
|
|
584
|
+
$: typeof ARK_COMPAT_SCOPE;
|
|
585
|
+
resolver?: AliasResolver;
|
|
260
586
|
run: Validator;
|
|
261
587
|
}
|
|
262
588
|
|
|
@@ -268,35 +594,143 @@ function metaOf(schema: InternalType): TypeMeta {
|
|
|
268
594
|
description: schema.description,
|
|
269
595
|
defaultValue: schema.defaultValue,
|
|
270
596
|
hasDefault: schema.hasDefault,
|
|
597
|
+
defaultOutput: schema.defaultOutput,
|
|
598
|
+
hasDefaultOutput: schema.hasDefaultOutput,
|
|
271
599
|
errorConfig: schema.errorConfig,
|
|
600
|
+
clone: schema.clone,
|
|
272
601
|
};
|
|
273
602
|
}
|
|
603
|
+
function inheritScope(source: InternalType, target: InternalType): InternalType {
|
|
604
|
+
if (source.resolver === undefined) return target;
|
|
605
|
+
target.resolver = source.resolver;
|
|
606
|
+
Reflect.set(target, "$", source.$);
|
|
607
|
+
return target;
|
|
608
|
+
}
|
|
609
|
+
function invalidDefault(label: string, errors: OmpErrors): never {
|
|
610
|
+
const error = errors[0];
|
|
611
|
+
let heading = label;
|
|
612
|
+
for (let index = 0; index < error.path.length; index++) {
|
|
613
|
+
const segment = error.path[index];
|
|
614
|
+
if (typeof segment === "number") {
|
|
615
|
+
if (label === "Default" && index === 0) heading = "Default value";
|
|
616
|
+
heading += ` at [${segment}]`;
|
|
617
|
+
} else if (label === "Default" && index === 0) {
|
|
618
|
+
heading += ` ${String(segment)}`;
|
|
619
|
+
} else {
|
|
620
|
+
heading += `.${String(segment)}`;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
throw new OmpTypeError(`ParseError: ${heading} ${error.problem}`);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function rejectMutableStaticDefault(value: unknown): void {
|
|
627
|
+
if (value !== null && typeof value === "object" && !(value instanceof Date)) {
|
|
628
|
+
throw new OmpTypeError("ParseError: A mutable default value must be specified as a factory");
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function normalizeDefaults(ir: IR, seen = new WeakSet<object>()): void {
|
|
633
|
+
if (seen.has(ir)) return;
|
|
634
|
+
seen.add(ir);
|
|
635
|
+
switch (ir.k) {
|
|
636
|
+
case "object":
|
|
637
|
+
for (const prop of ir.props) {
|
|
638
|
+
normalizeDefaults(prop.val, seen);
|
|
639
|
+
if (!prop.hasDefault || prop.defValidated) continue;
|
|
640
|
+
let candidate: unknown;
|
|
641
|
+
let factory = false;
|
|
642
|
+
if (prop.defFactory && typeof prop.def === "function") {
|
|
643
|
+
candidate = prop.def();
|
|
644
|
+
factory = true;
|
|
645
|
+
} else {
|
|
646
|
+
rejectMutableStaticDefault(prop.def);
|
|
647
|
+
candidate = prop.def;
|
|
648
|
+
}
|
|
649
|
+
const output = walk(prop.val, candidate);
|
|
650
|
+
if (output instanceof OmpErrors) invalidDefault(`Default for ${String(prop.key)}`, output);
|
|
651
|
+
if (!factory) prop.def = output;
|
|
652
|
+
prop.defValidated = true;
|
|
653
|
+
}
|
|
654
|
+
if (ir.index) normalizeDefaults(ir.index, seen);
|
|
655
|
+
if (ir.symbolIndex) normalizeDefaults(ir.symbolIndex, seen);
|
|
656
|
+
if (ir.patternIndexes) {
|
|
657
|
+
for (const index of ir.patternIndexes) {
|
|
658
|
+
normalizeDefaults(index.key, seen);
|
|
659
|
+
normalizeDefaults(index.val, seen);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return;
|
|
663
|
+
case "tuple":
|
|
664
|
+
for (let index = 0; index < ir.prefix.length; index++) {
|
|
665
|
+
const item = ir.prefix[index];
|
|
666
|
+
normalizeDefaults(item.val, seen);
|
|
667
|
+
if (!item.hasDefault || item.defValidated) continue;
|
|
668
|
+
let candidate: unknown;
|
|
669
|
+
let factory = false;
|
|
670
|
+
if (item.defFactory && typeof item.def === "function") {
|
|
671
|
+
candidate = item.def();
|
|
672
|
+
factory = true;
|
|
673
|
+
} else {
|
|
674
|
+
rejectMutableStaticDefault(item.def);
|
|
675
|
+
candidate = item.def;
|
|
676
|
+
}
|
|
677
|
+
const output = walk(item.val, candidate);
|
|
678
|
+
if (output instanceof OmpErrors) invalidDefault(`Default for [${index}]`, output);
|
|
679
|
+
if (!factory) item.def = output;
|
|
680
|
+
item.defValidated = true;
|
|
681
|
+
}
|
|
682
|
+
if (ir.variadic) normalizeDefaults(ir.variadic, seen);
|
|
683
|
+
for (const item of ir.postfix) normalizeDefaults(item, seen);
|
|
684
|
+
return;
|
|
685
|
+
case "array":
|
|
686
|
+
normalizeDefaults(ir.el, seen);
|
|
687
|
+
return;
|
|
688
|
+
case "union":
|
|
689
|
+
case "intersection":
|
|
690
|
+
for (const member of ir.members) normalizeDefaults(member, seen);
|
|
691
|
+
return;
|
|
692
|
+
case "refine":
|
|
693
|
+
normalizeDefaults(ir.base, seen);
|
|
694
|
+
return;
|
|
695
|
+
case "morph":
|
|
696
|
+
normalizeDefaults(ir.input, seen);
|
|
697
|
+
if (ir.out) normalizeDefaults(ir.out, seen);
|
|
698
|
+
return;
|
|
699
|
+
default:
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
274
703
|
|
|
704
|
+
/** Emitted for `io: 'output'` when a bare pipe makes the output unknowable. */
|
|
705
|
+
const OPAQUE_OUTPUT_IR: IR = { k: "unknown" };
|
|
275
706
|
const typeMethods = {
|
|
276
707
|
describe(this: InternalType, description: string): InternalType {
|
|
277
|
-
|
|
708
|
+
const ir = { ...this.ir, desc: description, cfg: { ...this.ir.cfg, expected: description } };
|
|
709
|
+
return makeType(ir, this[kSteps], { ...metaOf(this), description });
|
|
278
710
|
},
|
|
279
711
|
|
|
280
|
-
configure(this: InternalType, config: SchemaConfig): InternalType {
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
...(config.actual === undefined ? {} : { actual: config.actual }),
|
|
285
|
-
...(config.problem === undefined ? {} : { problem: config.problem }),
|
|
286
|
-
...(config.message === undefined ? {} : { message: config.message }),
|
|
287
|
-
};
|
|
288
|
-
const meta = {
|
|
712
|
+
configure(this: InternalType, config: SchemaConfig, selector: "self" | ConfigureSelector = "self"): InternalType {
|
|
713
|
+
const selected =
|
|
714
|
+
selector === "self" ? configureNode(this.ir, config) : configureSelected(this.ir, config, selector);
|
|
715
|
+
return makeType(selected, this[kSteps], {
|
|
289
716
|
...metaOf(this),
|
|
290
|
-
errorConfig,
|
|
717
|
+
errorConfig: { ...this.errorConfig, ...errorConfigOf(config) },
|
|
291
718
|
...(config.description === undefined ? {} : { description: config.description }),
|
|
292
|
-
};
|
|
293
|
-
return config.description === undefined
|
|
294
|
-
? makeType(this.ir, this[kSteps], meta)
|
|
295
|
-
: makeType({ ...this.ir, desc: config.description }, this[kSteps], meta);
|
|
719
|
+
});
|
|
296
720
|
},
|
|
297
721
|
|
|
298
722
|
default(this: InternalType, value: unknown): InternalType {
|
|
299
|
-
|
|
723
|
+
const factory = typeof value === "function";
|
|
724
|
+
if (!factory) rejectMutableStaticDefault(value);
|
|
725
|
+
const candidate = factory ? value() : value;
|
|
726
|
+
const output = this.run(candidate);
|
|
727
|
+
if (output instanceof OmpErrors) invalidDefault("Default", output);
|
|
728
|
+
return makeType(this.ir, this[kSteps], {
|
|
729
|
+
...metaOf(this),
|
|
730
|
+
defaultValue: value,
|
|
731
|
+
hasDefault: true,
|
|
732
|
+
...(factory ? {} : { defaultOutput: output, hasDefaultOutput: true }),
|
|
733
|
+
});
|
|
300
734
|
},
|
|
301
735
|
|
|
302
736
|
optional(this: InternalType): readonly [InternalType, "?"] {
|
|
@@ -304,27 +738,31 @@ const typeMethods = {
|
|
|
304
738
|
},
|
|
305
739
|
|
|
306
740
|
or(this: InternalType, def: Def): InternalType {
|
|
307
|
-
const other = parseDef(def);
|
|
741
|
+
const other = parseDef(def, this.resolver);
|
|
308
742
|
const a = embed(this);
|
|
309
743
|
const members = [...(a.k === "union" ? a.members : [a]), ...(other.k === "union" ? other.members : [other])];
|
|
310
|
-
return makeType({ k: "union", members }, [], {});
|
|
744
|
+
return inheritScope(this, makeType({ k: "union", members }, [], {}));
|
|
311
745
|
},
|
|
312
746
|
|
|
313
747
|
equals(this: InternalType, def: Def): boolean {
|
|
314
|
-
return irEquals(embed(this), parseDef(def));
|
|
748
|
+
return irEquals(embed(this), parseDef(def, this.resolver));
|
|
315
749
|
},
|
|
316
750
|
|
|
317
751
|
ifEquals(this: InternalType, def: Def): InternalType | undefined {
|
|
318
|
-
return irEquals(embed(this), parseDef(def)) ? this : undefined;
|
|
752
|
+
return irEquals(embed(this), parseDef(def, this.resolver)) ? this : undefined;
|
|
753
|
+
},
|
|
754
|
+
|
|
755
|
+
ifExtends(this: InternalType, def: Def): InternalType | undefined {
|
|
756
|
+
return isSubtype(embed(this), parseDef(def, this.resolver)) ? this : undefined;
|
|
319
757
|
},
|
|
320
758
|
|
|
321
759
|
extends(this: InternalType, def: Def): boolean {
|
|
322
|
-
return isSubtype(embed(this), parseDef(def));
|
|
760
|
+
return isSubtype(embed(this), parseDef(def, this.resolver));
|
|
323
761
|
},
|
|
324
762
|
|
|
325
763
|
overlaps(this: InternalType, def: Def): boolean {
|
|
326
764
|
try {
|
|
327
|
-
intersect(embed(this), parseDef(def));
|
|
765
|
+
intersect(embed(this), parseDef(def, this.resolver));
|
|
328
766
|
return true;
|
|
329
767
|
} catch (error) {
|
|
330
768
|
if (error instanceof OmpTypeError) return false;
|
|
@@ -332,9 +770,15 @@ const typeMethods = {
|
|
|
332
770
|
}
|
|
333
771
|
},
|
|
334
772
|
|
|
335
|
-
distribute(
|
|
773
|
+
distribute(
|
|
774
|
+
this: InternalType,
|
|
775
|
+
mapper: (branch: BaseType) => InternalType,
|
|
776
|
+
reducer?: (branches: readonly InternalType[]) => InternalType,
|
|
777
|
+
): InternalType {
|
|
336
778
|
const branches = this.ir.k === "union" ? this.ir.members : [embed(this)];
|
|
337
|
-
const
|
|
779
|
+
const mapped = branches.map(branch => mapper(makeType(branch, [], {}) as unknown as BaseType));
|
|
780
|
+
if (reducer !== undefined) return reducer(mapped);
|
|
781
|
+
const members = mapped.map(branch => embed(branch));
|
|
338
782
|
return makeType(members.length === 1 ? members[0] : { k: "union", members }, [], {});
|
|
339
783
|
},
|
|
340
784
|
|
|
@@ -343,11 +787,11 @@ const typeMethods = {
|
|
|
343
787
|
},
|
|
344
788
|
|
|
345
789
|
and(this: InternalType, def: Def): InternalType {
|
|
346
|
-
return makeType(intersect(embed(this), parseDef(def)), [], {});
|
|
790
|
+
return inheritScope(this, makeType(intersect(embed(this), parseDef(def, this.resolver)), [], {}));
|
|
347
791
|
},
|
|
348
792
|
|
|
349
793
|
array(this: InternalType): InternalType {
|
|
350
|
-
return makeType({ k: "array", el: embed(this) }, [], {});
|
|
794
|
+
return inheritScope(this, makeType({ k: "array", el: embed(this) }, [], {}));
|
|
351
795
|
},
|
|
352
796
|
|
|
353
797
|
atLeastLength(this: InternalType, bound: number): InternalType {
|
|
@@ -413,39 +857,32 @@ const typeMethods = {
|
|
|
413
857
|
return makeType(intersect(this.ir, patternIR(pattern)), this[kSteps], metaOf(this));
|
|
414
858
|
},
|
|
415
859
|
|
|
416
|
-
atOrAfter(this: InternalType, bound: Date): InternalType {
|
|
417
|
-
|
|
860
|
+
atOrAfter(this: InternalType, bound: Date | number): InternalType {
|
|
861
|
+
const timestamp = bound instanceof Date ? bound.valueOf() : bound;
|
|
862
|
+
return dateRefinement(this, timestamp, "at or after", value => value >= timestamp);
|
|
418
863
|
},
|
|
419
864
|
|
|
420
|
-
atOrBefore(this: InternalType, bound: Date): InternalType {
|
|
421
|
-
|
|
865
|
+
atOrBefore(this: InternalType, bound: Date | number): InternalType {
|
|
866
|
+
const timestamp = bound instanceof Date ? bound.valueOf() : bound;
|
|
867
|
+
return dateRefinement(this, timestamp, "at or before", value => value <= timestamp);
|
|
422
868
|
},
|
|
423
869
|
|
|
424
|
-
laterThan(this: InternalType, bound: Date): InternalType {
|
|
425
|
-
|
|
870
|
+
laterThan(this: InternalType, bound: Date | number): InternalType {
|
|
871
|
+
const timestamp = bound instanceof Date ? bound.valueOf() : bound;
|
|
872
|
+
return dateRefinement(this, timestamp, "later than", value => value > timestamp);
|
|
426
873
|
},
|
|
427
874
|
|
|
428
|
-
earlierThan(this: InternalType, bound: Date): InternalType {
|
|
429
|
-
|
|
875
|
+
earlierThan(this: InternalType, bound: Date | number): InternalType {
|
|
876
|
+
const timestamp = bound instanceof Date ? bound.valueOf() : bound;
|
|
877
|
+
return dateRefinement(this, timestamp, "earlier than", value => value < timestamp);
|
|
430
878
|
},
|
|
431
879
|
|
|
432
|
-
pipe(this: InternalType,
|
|
433
|
-
return
|
|
880
|
+
pipe(this: InternalType, ...pipes: readonly unknown[]): InternalType {
|
|
881
|
+
return appendPipes(this, pipes, false);
|
|
434
882
|
},
|
|
435
883
|
|
|
436
884
|
to(this: InternalType, def: unknown): InternalType {
|
|
437
|
-
|
|
438
|
-
return makeType(
|
|
439
|
-
this.ir,
|
|
440
|
-
[
|
|
441
|
-
...this[kSteps],
|
|
442
|
-
{
|
|
443
|
-
kind: "pipe",
|
|
444
|
-
fn: value => output(value),
|
|
445
|
-
},
|
|
446
|
-
],
|
|
447
|
-
metaOf(this),
|
|
448
|
-
);
|
|
885
|
+
return appendPipes(this, [makeType(parseDef(def, this.resolver), [], {})], false, true);
|
|
449
886
|
},
|
|
450
887
|
|
|
451
888
|
filter(this: InternalType, fn: Step["fn"]): InternalType {
|
|
@@ -457,98 +894,103 @@ const typeMethods = {
|
|
|
457
894
|
},
|
|
458
895
|
|
|
459
896
|
brand(this: InternalType): InternalType {
|
|
460
|
-
return
|
|
897
|
+
return this;
|
|
461
898
|
},
|
|
462
899
|
|
|
463
900
|
as(this: InternalType): InternalType {
|
|
464
|
-
return
|
|
901
|
+
return this;
|
|
902
|
+
},
|
|
903
|
+
|
|
904
|
+
readonly(this: InternalType): InternalType {
|
|
905
|
+
return this;
|
|
465
906
|
},
|
|
466
907
|
|
|
467
908
|
keyof(this: InternalType): InternalType {
|
|
468
909
|
return makeType(keyOf(this.ir), [], {});
|
|
469
910
|
},
|
|
470
911
|
|
|
471
|
-
get(this: InternalType,
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
return makeType(ir, [], {});
|
|
912
|
+
get(this: InternalType, ...path: PropertyKey[]): InternalType {
|
|
913
|
+
if (path.length === 0) return this;
|
|
914
|
+
let result = this.ir;
|
|
915
|
+
for (const key of path) result = getPathIR(result, key);
|
|
916
|
+
return makeType(result, [], {});
|
|
477
917
|
},
|
|
478
918
|
|
|
479
919
|
pick(this: InternalType, ...keys: PropertyKey[]): InternalType {
|
|
480
|
-
|
|
481
|
-
const selected = new Set(keys.map(String));
|
|
482
|
-
return makeType({ ...object, props: object.props.filter(prop => selected.has(prop.key)) }, [], {});
|
|
920
|
+
return makeType(selectObjectProps(this.ir, keys, true, "pick"), [], {});
|
|
483
921
|
},
|
|
484
922
|
|
|
485
923
|
omit(this: InternalType, ...keys: PropertyKey[]): InternalType {
|
|
486
|
-
|
|
487
|
-
const omitted = new Set(keys.map(String));
|
|
488
|
-
return makeType({ ...object, props: object.props.filter(prop => !omitted.has(prop.key)) }, [], {});
|
|
924
|
+
return makeType(selectObjectProps(this.ir, keys, false, "omit"), [], {});
|
|
489
925
|
},
|
|
490
926
|
|
|
491
927
|
partial(this: InternalType): InternalType {
|
|
492
|
-
|
|
493
|
-
return makeType({ ...object, props: object.props.map(prop => ({ ...prop, opt: true })) }, [], {});
|
|
928
|
+
return makeType(setObjectOptionality(this.ir, true, "partial"), [], {});
|
|
494
929
|
},
|
|
495
930
|
|
|
496
931
|
required(this: InternalType): InternalType {
|
|
497
|
-
|
|
498
|
-
return makeType({ ...object, props: object.props.map(prop => ({ ...prop, opt: false })) }, [], {});
|
|
932
|
+
return makeType(setObjectOptionality(this.ir, false, "required"), [], {});
|
|
499
933
|
},
|
|
500
934
|
|
|
501
935
|
map(this: InternalType, mapper: (property: TypeProperty) => TypeProperty | readonly TypeProperty[]): InternalType {
|
|
502
936
|
const object = requireObject(this.ir, "map");
|
|
503
937
|
const props = object.props.flatMap(prop => {
|
|
504
|
-
const
|
|
505
|
-
|
|
938
|
+
const original = propertyFromIR(prop);
|
|
939
|
+
const mapped = mapper(original);
|
|
940
|
+
return (Array.isArray(mapped) ? mapped : [mapped]).map(property =>
|
|
941
|
+
propertyToIR(
|
|
942
|
+
(property.kind === "required" || property.kind === "optional"
|
|
943
|
+
? property
|
|
944
|
+
: { ...property, kind: original.kind }) as TypeProperty,
|
|
945
|
+
),
|
|
946
|
+
);
|
|
506
947
|
});
|
|
507
948
|
return makeType({ ...object, props }, [], {});
|
|
508
949
|
},
|
|
509
950
|
|
|
510
951
|
merge(this: InternalType, def: unknown): InternalType {
|
|
511
|
-
|
|
952
|
+
const merged = mergeObjectDefinition(this.ir, def, this.resolver);
|
|
953
|
+
return inheritScope(this, makeType(merged, [], {}));
|
|
512
954
|
},
|
|
513
955
|
|
|
514
956
|
extract(this: InternalType, def: unknown): InternalType {
|
|
515
|
-
|
|
516
|
-
return makeType(
|
|
517
|
-
{
|
|
518
|
-
k: "refine",
|
|
519
|
-
base: embed(this),
|
|
520
|
-
pred: value => !(walk(other, value) instanceof OmpErrors),
|
|
521
|
-
expected: "a value included by the extracted type",
|
|
522
|
-
},
|
|
523
|
-
[],
|
|
524
|
-
{},
|
|
525
|
-
);
|
|
957
|
+
return inheritScope(this, makeType(distributeFilter(this.ir, parseDef(def, this.resolver), true), [], {}));
|
|
526
958
|
},
|
|
527
959
|
|
|
528
960
|
exclude(this: InternalType, def: unknown): InternalType {
|
|
529
|
-
|
|
530
|
-
return makeType(
|
|
531
|
-
{
|
|
532
|
-
k: "refine",
|
|
533
|
-
base: embed(this),
|
|
534
|
-
pred: value => walk(other, value) instanceof OmpErrors,
|
|
535
|
-
expected: "a value not excluded by the type",
|
|
536
|
-
},
|
|
537
|
-
[],
|
|
538
|
-
{},
|
|
539
|
-
);
|
|
961
|
+
return inheritScope(this, makeType(distributeFilter(this.ir, parseDef(def, this.resolver), false), [], {}));
|
|
540
962
|
},
|
|
541
963
|
|
|
542
964
|
onUndeclaredKey(this: InternalType, behavior: "ignore" | "reject" | "delete"): InternalType {
|
|
543
|
-
const
|
|
544
|
-
|
|
965
|
+
const extras = behavior === "ignore" ? "keep" : behavior;
|
|
966
|
+
const ir = withShallowExtras(this.ir, extras);
|
|
967
|
+
if (extras === "delete" && ir.k === "union") {
|
|
968
|
+
const objects = ir.members.filter((member): member is ObjectIR => member.k === "object");
|
|
969
|
+
for (let left = 0; left < objects.length; left++) {
|
|
970
|
+
for (let right = left + 1; right < objects.length; right++) {
|
|
971
|
+
const sharedRequired = objects[left].props.some(
|
|
972
|
+
leftProp =>
|
|
973
|
+
!leftProp.opt &&
|
|
974
|
+
objects[right].props.some(rightProp => !rightProp.opt && rightProp.key === leftProp.key),
|
|
975
|
+
);
|
|
976
|
+
if (!sharedRequired) {
|
|
977
|
+
const leftExpression = expressionOf(objects[left]).replace(/ }$/, ", + (undeclared): delete }");
|
|
978
|
+
const rightExpression = expressionOf(objects[right]).replace(/ }$/, ", + (undeclared): delete }");
|
|
979
|
+
throw new OmpTypeError(
|
|
980
|
+
`ParseError: An unordered union of a type including a morph and a type with overlapping input is indeterminate:\nLeft: ${leftExpression}\nRight: ${rightExpression}`,
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
return makeType(ir, this[kSteps], metaOf(this));
|
|
545
987
|
},
|
|
546
988
|
|
|
547
989
|
onDeepUndeclaredKey(this: InternalType, behavior: "ignore" | "reject" | "delete"): InternalType {
|
|
548
990
|
return makeType(withDeepExtras(this.ir, behavior === "ignore" ? "keep" : behavior), this[kSteps], metaOf(this));
|
|
549
991
|
},
|
|
550
992
|
|
|
551
|
-
allows(this: InternalType, data: unknown):
|
|
993
|
+
allows(this: InternalType, data: unknown): data is unknown {
|
|
552
994
|
const steps = this[kSteps];
|
|
553
995
|
let needsPredicates = false;
|
|
554
996
|
for (const step of steps) {
|
|
@@ -564,12 +1006,12 @@ const typeMethods = {
|
|
|
564
1006
|
return allows(data);
|
|
565
1007
|
}
|
|
566
1008
|
for (const step of steps) {
|
|
567
|
-
if (step.kind === "filter" && !step.fn(data, new Ctx())) return false;
|
|
1009
|
+
if (step.kind === "filter" && !step.fn(data, new Ctx(data))) return false;
|
|
568
1010
|
}
|
|
569
1011
|
const out = this[kBase](data);
|
|
570
1012
|
if (out instanceof OmpErrors) return false;
|
|
571
1013
|
for (const step of steps) {
|
|
572
|
-
if (step.kind === "narrow" && !step.fn(out, new Ctx())) return false;
|
|
1014
|
+
if (step.kind === "narrow" && !step.fn(out, new Ctx(out))) return false;
|
|
573
1015
|
}
|
|
574
1016
|
return true;
|
|
575
1017
|
},
|
|
@@ -587,16 +1029,29 @@ const typeMethods = {
|
|
|
587
1029
|
},
|
|
588
1030
|
|
|
589
1031
|
toJsonSchema(this: InternalType, options?: ToJsonSchemaOptions): Record<string, unknown> {
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
: { ...options, ...(description === undefined ? {} : { description }) },
|
|
596
|
-
);
|
|
1032
|
+
const ir =
|
|
1033
|
+
options?.io === "output" ? (this.opaqueOutput ? OPAQUE_OUTPUT_IR : (this.stepOut ?? this.ir)) : this.ir;
|
|
1034
|
+
const description = options?.description ?? this.ir.desc;
|
|
1035
|
+
if (description === undefined) return irToJsonSchema(ir, options);
|
|
1036
|
+
return irToJsonSchema(ir, { ...options, description });
|
|
597
1037
|
},
|
|
598
1038
|
};
|
|
599
1039
|
|
|
1040
|
+
Object.defineProperty(typeMethods, "expression", {
|
|
1041
|
+
get(this: InternalType): string {
|
|
1042
|
+
const input = expressionOf(this.ir);
|
|
1043
|
+
if (!this.hasSteps) return input;
|
|
1044
|
+
if (this.opaqueOutput) return `(In: ${input}) => Out<unknown>`;
|
|
1045
|
+
return `(In: ${input}) => To<${expressionOf(this.stepOut ?? this.ir)}>`;
|
|
1046
|
+
},
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
Object.defineProperty(typeMethods, "json", {
|
|
1050
|
+
get(this: InternalType): unknown {
|
|
1051
|
+
return arkJsonOf(this.ir);
|
|
1052
|
+
},
|
|
1053
|
+
});
|
|
1054
|
+
|
|
600
1055
|
Object.defineProperty(typeMethods, "props", {
|
|
601
1056
|
get(this: InternalType): readonly TypeProperty[] {
|
|
602
1057
|
const object = requireObject(this.ir, "props");
|
|
@@ -604,6 +1059,87 @@ Object.defineProperty(typeMethods, "props", {
|
|
|
604
1059
|
},
|
|
605
1060
|
});
|
|
606
1061
|
|
|
1062
|
+
Object.defineProperty(typeMethods, "~standard", {
|
|
1063
|
+
get(this: InternalType): StandardSchemaV1.Props {
|
|
1064
|
+
const jsonSchema = (io: "input" | "output", options: StandardJsonSchemaOptions) => {
|
|
1065
|
+
if (options.target !== "draft-2020-12" && options.target !== "draft-07") {
|
|
1066
|
+
throw new OmpTypeError(
|
|
1067
|
+
`JSONSchema target '${options.target}' is not supported (must be "draft-2020-12" or "draft-07")`,
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
return this.toJsonSchema({ ...options.libraryOptions, target: options.target, io });
|
|
1071
|
+
};
|
|
1072
|
+
return {
|
|
1073
|
+
version: 1,
|
|
1074
|
+
vendor: "omptype",
|
|
1075
|
+
validate: (value: unknown): StandardSchemaV1.Result<unknown> => {
|
|
1076
|
+
const out = this.run(value);
|
|
1077
|
+
return out instanceof OmpErrors
|
|
1078
|
+
? { issues: out as unknown as readonly StandardSchemaV1.Issue[] }
|
|
1079
|
+
: { value: out };
|
|
1080
|
+
},
|
|
1081
|
+
jsonSchema: {
|
|
1082
|
+
input: options => jsonSchema("input", options),
|
|
1083
|
+
output: options => jsonSchema("output", options),
|
|
1084
|
+
},
|
|
1085
|
+
};
|
|
1086
|
+
},
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
Object.defineProperty(typeMethods, "in", {
|
|
1090
|
+
get(this: InternalType): InternalType {
|
|
1091
|
+
return makeType(projectIO(this.ir, "in"), [], {});
|
|
1092
|
+
},
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
Object.defineProperty(typeMethods, "out", {
|
|
1096
|
+
get(this: InternalType): InternalType {
|
|
1097
|
+
if (this.opaqueOutput) return makeType({ k: "unknown" }, [], {});
|
|
1098
|
+
return makeType(projectIO(this.stepOut ?? this.ir, "out"), [], {});
|
|
1099
|
+
},
|
|
1100
|
+
});
|
|
1101
|
+
|
|
1102
|
+
const allowsMethod = typeMethods.allows;
|
|
1103
|
+
const assertMethod = typeMethods.assert;
|
|
1104
|
+
const fromMethod = typeMethods.from;
|
|
1105
|
+
Object.defineProperties(typeMethods, {
|
|
1106
|
+
description: {
|
|
1107
|
+
get(this: InternalType): string {
|
|
1108
|
+
return descriptionOf(this.ir);
|
|
1109
|
+
},
|
|
1110
|
+
},
|
|
1111
|
+
allows: {
|
|
1112
|
+
get(this: InternalType): Allows {
|
|
1113
|
+
const allows = (data: unknown): data is unknown => allowsMethod.call(this, data);
|
|
1114
|
+
Object.defineProperty(this, "allows", { value: allows, writable: true });
|
|
1115
|
+
return allows;
|
|
1116
|
+
},
|
|
1117
|
+
},
|
|
1118
|
+
assert: {
|
|
1119
|
+
get(this: InternalType): Validator {
|
|
1120
|
+
const assert = assertMethod.bind(this);
|
|
1121
|
+
Object.defineProperty(this, "assert", { value: assert });
|
|
1122
|
+
return assert;
|
|
1123
|
+
},
|
|
1124
|
+
},
|
|
1125
|
+
from: {
|
|
1126
|
+
get(this: InternalType): Validator {
|
|
1127
|
+
const from = fromMethod.bind(this);
|
|
1128
|
+
Object.defineProperty(this, "from", { value: from });
|
|
1129
|
+
return from;
|
|
1130
|
+
},
|
|
1131
|
+
},
|
|
1132
|
+
pipe: {
|
|
1133
|
+
get(this: InternalType) {
|
|
1134
|
+
const pipe = Object.assign((...pipes: readonly unknown[]): InternalType => appendPipes(this, pipes, false), {
|
|
1135
|
+
try: (...pipes: readonly unknown[]): InternalType => appendPipes(this, pipes, true),
|
|
1136
|
+
});
|
|
1137
|
+
Object.defineProperty(this, "pipe", { value: pipe });
|
|
1138
|
+
return pipe;
|
|
1139
|
+
},
|
|
1140
|
+
},
|
|
1141
|
+
});
|
|
1142
|
+
|
|
607
1143
|
// Share the fluent surface without per-schema method allocations or copies.
|
|
608
1144
|
// Function.prototype remains in the chain, except bind is intentionally hidden
|
|
609
1145
|
// so generic tool wrappers recognize callable schemas rather than rebinding them.
|
|
@@ -613,6 +1149,15 @@ Object.defineProperty(typeMethods, "bind", { value: undefined });
|
|
|
613
1149
|
function makeType(ir: IR, steps: Step[], meta: TypeMeta): InternalType;
|
|
614
1150
|
function makeType<t = unknown, i = t>(ir: IR, steps: Step[], meta: TypeMeta): FluentType<t, i>;
|
|
615
1151
|
function makeType(ir: IR, steps: Step[], meta: TypeMeta): unknown {
|
|
1152
|
+
let morph = false;
|
|
1153
|
+
if (!isSimpleIR(ir)) {
|
|
1154
|
+
ir = normalizeIR(ir);
|
|
1155
|
+
morph = hasMorph(ir);
|
|
1156
|
+
if (morph) {
|
|
1157
|
+
normalizeDefaults(ir);
|
|
1158
|
+
assertDeterminateMorphUnions(ir);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
616
1161
|
let calls = 0;
|
|
617
1162
|
let impl: Validator = (data: unknown): unknown => {
|
|
618
1163
|
if (++calls >= JIT_THRESHOLD) {
|
|
@@ -622,27 +1167,32 @@ function makeType(ir: IR, steps: Step[], meta: TypeMeta): unknown {
|
|
|
622
1167
|
return walk(ir, data);
|
|
623
1168
|
};
|
|
624
1169
|
|
|
625
|
-
const base: Validator =
|
|
626
|
-
|
|
627
|
-
? (data: unknown): unknown => impl(data)
|
|
628
|
-
: (data: unknown): unknown => {
|
|
629
|
-
const result = impl(data);
|
|
630
|
-
return result instanceof OmpErrors ? result.configure(meta.errorConfig ?? {}) : result;
|
|
631
|
-
};
|
|
1170
|
+
const base: Validator = (data: unknown): unknown => impl(data);
|
|
1171
|
+
const errorConfig = meta.errorConfig ?? ir.cfg;
|
|
632
1172
|
|
|
633
|
-
const
|
|
1173
|
+
const filterInput = steps.some(step => step.kind === "filter") ? projectIO(ir, "in") : undefined;
|
|
1174
|
+
const validate: Validator =
|
|
634
1175
|
steps.length === 0
|
|
635
1176
|
? base
|
|
636
|
-
: (data: unknown): unknown => {
|
|
1177
|
+
: (data: unknown, contextPath: readonly PropertyKey[] = []): unknown => {
|
|
1178
|
+
if (filterInput !== undefined) {
|
|
1179
|
+
const inputResult = walk(filterInput, data);
|
|
1180
|
+
if (inputResult instanceof OmpErrors) return inputResult;
|
|
1181
|
+
}
|
|
637
1182
|
for (const step of steps) {
|
|
638
1183
|
if (step.kind !== "filter") continue;
|
|
639
|
-
const ctx = new Ctx();
|
|
640
|
-
|
|
1184
|
+
const ctx = new Ctx(data, contextPath);
|
|
1185
|
+
const result = step.fn(data, ctx);
|
|
1186
|
+
if (result instanceof OmpErrors)
|
|
1187
|
+
return errorConfig === undefined ? result : result.configure(errorConfig);
|
|
1188
|
+
if (ctx.errors) return errorConfig === undefined ? ctx.errors : ctx.errors.configure(errorConfig);
|
|
1189
|
+
if (!result) {
|
|
641
1190
|
return OmpErrors.single(
|
|
642
1191
|
[],
|
|
643
|
-
ctx.expectation ??
|
|
1192
|
+
ctx.expectation ??
|
|
1193
|
+
(step.fn.name ? `valid according to ${step.fn.name}` : "valid (input predicate failed)"),
|
|
644
1194
|
data,
|
|
645
|
-
|
|
1195
|
+
errorConfig,
|
|
646
1196
|
);
|
|
647
1197
|
}
|
|
648
1198
|
}
|
|
@@ -650,25 +1200,85 @@ function makeType(ir: IR, steps: Step[], meta: TypeMeta): unknown {
|
|
|
650
1200
|
if (out instanceof OmpErrors) return out;
|
|
651
1201
|
for (const step of steps) {
|
|
652
1202
|
if (step.kind === "filter") continue;
|
|
653
|
-
const ctx = new Ctx();
|
|
1203
|
+
const ctx = new Ctx(out, contextPath);
|
|
654
1204
|
if (step.kind === "narrow") {
|
|
655
|
-
|
|
1205
|
+
const result = step.fn(out, ctx);
|
|
1206
|
+
if (result instanceof OmpErrors) {
|
|
1207
|
+
return errorConfig === undefined ? result : result.configure(errorConfig);
|
|
1208
|
+
}
|
|
1209
|
+
if (ctx.errors) return errorConfig === undefined ? ctx.errors : ctx.errors.configure(errorConfig);
|
|
1210
|
+
if (!result) {
|
|
656
1211
|
return OmpErrors.single(
|
|
657
1212
|
[],
|
|
658
|
-
ctx.expectation ??
|
|
1213
|
+
ctx.expectation ??
|
|
1214
|
+
(step.fn.name ? `valid according to ${step.fn.name}` : "valid (narrow predicate failed)"),
|
|
659
1215
|
out,
|
|
660
|
-
|
|
1216
|
+
errorConfig,
|
|
661
1217
|
);
|
|
662
1218
|
}
|
|
663
1219
|
} else {
|
|
664
|
-
|
|
1220
|
+
try {
|
|
1221
|
+
out = step.fn(out, ctx);
|
|
1222
|
+
} catch (error) {
|
|
1223
|
+
if (!step.try) throw error;
|
|
1224
|
+
const detail = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
1225
|
+
return OmpErrors.single([], `valid (morph threw ${detail})`, out, errorConfig);
|
|
1226
|
+
}
|
|
665
1227
|
if (out instanceof OmpErrors) {
|
|
666
|
-
return
|
|
1228
|
+
return errorConfig === undefined ? out : out.configure(errorConfig);
|
|
667
1229
|
}
|
|
668
1230
|
}
|
|
669
1231
|
}
|
|
670
1232
|
return out;
|
|
671
1233
|
};
|
|
1234
|
+
const needsClone = morph || steps.some(step => step.kind === "pipe");
|
|
1235
|
+
const clone = meta.clone;
|
|
1236
|
+
let callable: Validator = validate;
|
|
1237
|
+
if (needsClone && clone !== undefined) {
|
|
1238
|
+
if (clone === false) {
|
|
1239
|
+
callable = (data: unknown, path?: readonly PropertyKey[]): unknown => {
|
|
1240
|
+
const out = validate(data, path);
|
|
1241
|
+
if (
|
|
1242
|
+
out instanceof OmpErrors ||
|
|
1243
|
+
out === data ||
|
|
1244
|
+
typeof data !== "object" ||
|
|
1245
|
+
data === null ||
|
|
1246
|
+
typeof out !== "object" ||
|
|
1247
|
+
out === null
|
|
1248
|
+
) {
|
|
1249
|
+
return out;
|
|
1250
|
+
}
|
|
1251
|
+
if (Array.isArray(data) && Array.isArray(out)) {
|
|
1252
|
+
data.splice(0, data.length, ...out);
|
|
1253
|
+
} else {
|
|
1254
|
+
const target = data as Record<PropertyKey, unknown>;
|
|
1255
|
+
const source = out as Record<PropertyKey, unknown>;
|
|
1256
|
+
for (const key of Reflect.ownKeys(target)) {
|
|
1257
|
+
if (!Object.hasOwn(source, key)) Reflect.deleteProperty(target, key);
|
|
1258
|
+
}
|
|
1259
|
+
for (const key of Reflect.ownKeys(source)) target[key] = source[key];
|
|
1260
|
+
}
|
|
1261
|
+
return data;
|
|
1262
|
+
};
|
|
1263
|
+
} else {
|
|
1264
|
+
callable = (data: unknown, path?: readonly PropertyKey[]): unknown => validate(clone(data), path);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// Root defaults materialize for absent input: `schema(undefined)` and the
|
|
1269
|
+
// Standard Schema boundary (`~standard.validate(undefined)`) both yield the
|
|
1270
|
+
// default instead of a base-IR rejection. Factories run per call; static
|
|
1271
|
+
// defaults reuse the precomputed validated output (mutable statics are
|
|
1272
|
+
// rejected at `.default()` time).
|
|
1273
|
+
if (meta.hasDefault === true) {
|
|
1274
|
+
const inner = callable;
|
|
1275
|
+
const value = meta.defaultValue;
|
|
1276
|
+
callable = (data: unknown, path?: readonly PropertyKey[]): unknown => {
|
|
1277
|
+
if (data !== undefined) return inner(data, path);
|
|
1278
|
+
if (meta.hasDefaultOutput === true) return meta.defaultOutput;
|
|
1279
|
+
return inner(typeof value === "function" ? (value as () => unknown)() : value, path);
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
672
1282
|
|
|
673
1283
|
const self = callable as InternalType;
|
|
674
1284
|
self[IR_BRAND] = true;
|
|
@@ -678,55 +1288,504 @@ function makeType(ir: IR, steps: Step[], meta: TypeMeta): unknown {
|
|
|
678
1288
|
self.hasSteps = steps.length > 0;
|
|
679
1289
|
self.hasDefault = meta.hasDefault === true;
|
|
680
1290
|
self.defaultValue = meta.defaultValue;
|
|
681
|
-
self.
|
|
682
|
-
self.
|
|
1291
|
+
self.defaultOutput = meta.defaultOutput;
|
|
1292
|
+
self.hasDefaultOutput = meta.hasDefaultOutput === true;
|
|
1293
|
+
self.errorConfig = meta.errorConfig ?? ir.cfg;
|
|
1294
|
+
self.clone = meta.clone;
|
|
683
1295
|
self.run = callable;
|
|
1296
|
+
self.$ = ARK_COMPAT_SCOPE;
|
|
1297
|
+
for (let i = steps.length - 1; i >= 0; i--) {
|
|
1298
|
+
const step = steps[i];
|
|
1299
|
+
if (step.kind !== "pipe") continue;
|
|
1300
|
+
self.stepOut = step.out;
|
|
1301
|
+
self.opaqueOutput = step.out === undefined;
|
|
1302
|
+
break;
|
|
1303
|
+
}
|
|
684
1304
|
Object.setPrototypeOf(self, typeMethods);
|
|
685
1305
|
return self;
|
|
686
1306
|
}
|
|
687
1307
|
|
|
688
|
-
|
|
1308
|
+
function appendPipes(
|
|
1309
|
+
source: InternalType,
|
|
1310
|
+
pipes: readonly unknown[],
|
|
1311
|
+
catchErrors: boolean,
|
|
1312
|
+
forcePipeline = false,
|
|
1313
|
+
): InternalType {
|
|
1314
|
+
let schema = source;
|
|
1315
|
+
for (const candidate of pipes) {
|
|
1316
|
+
const isSchema =
|
|
1317
|
+
(typeof candidate === "function" || (typeof candidate === "object" && candidate !== null)) &&
|
|
1318
|
+
IR_BRAND in candidate;
|
|
1319
|
+
if (isSchema) {
|
|
1320
|
+
const target = candidate as InternalType;
|
|
1321
|
+
if (
|
|
1322
|
+
!forcePipeline &&
|
|
1323
|
+
schema[kSteps].length === 0 &&
|
|
1324
|
+
!target.hasSteps &&
|
|
1325
|
+
!hasMorph(schema.ir) &&
|
|
1326
|
+
!hasMorph(target.ir)
|
|
1327
|
+
) {
|
|
1328
|
+
schema = makeType(intersect(schema.ir, target.ir), [], metaOf(schema));
|
|
1329
|
+
continue;
|
|
1330
|
+
}
|
|
1331
|
+
const out = target.opaqueOutput ? undefined : (target.stepOut ?? target.ir);
|
|
1332
|
+
schema = makeType(
|
|
1333
|
+
schema.ir,
|
|
1334
|
+
[...schema[kSteps], { kind: "pipe", fn: value => target.run(value), out, try: catchErrors }],
|
|
1335
|
+
metaOf(schema),
|
|
1336
|
+
);
|
|
1337
|
+
continue;
|
|
1338
|
+
}
|
|
1339
|
+
if (typeof candidate !== "function") throw new OmpTypeError("pipe operands must be functions or Types");
|
|
1340
|
+
schema = makeType(
|
|
1341
|
+
schema.ir,
|
|
1342
|
+
[...schema[kSteps], { kind: "pipe", fn: candidate as Step["fn"], try: catchErrors }],
|
|
1343
|
+
metaOf(schema),
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
return inheritScope(source, schema);
|
|
1347
|
+
}
|
|
689
1348
|
|
|
690
|
-
function
|
|
691
|
-
|
|
692
|
-
|
|
1349
|
+
function projectIO(ir: IR, io: "in" | "out"): IR {
|
|
1350
|
+
switch (ir.k) {
|
|
1351
|
+
case "morph":
|
|
1352
|
+
return projectIO(io === "in" || ir.out === undefined ? ir.input : ir.out, io);
|
|
1353
|
+
case "sub":
|
|
1354
|
+
if (io === "out" && ir.schema.opaqueOutput) return { k: "unknown" };
|
|
1355
|
+
return projectIO(io === "out" ? (ir.schema.stepOut ?? ir.schema.ir) : ir.schema.ir, io);
|
|
1356
|
+
case "array":
|
|
1357
|
+
return { ...ir, el: projectIO(ir.el, io) };
|
|
1358
|
+
case "tuple":
|
|
1359
|
+
return {
|
|
1360
|
+
...ir,
|
|
1361
|
+
prefix: ir.prefix.map(item => ({
|
|
1362
|
+
...item,
|
|
1363
|
+
opt: io === "in" ? item.opt || item.hasDefault === true : item.opt && !item.hasDefault,
|
|
1364
|
+
val: projectIO(item.val, io),
|
|
1365
|
+
hasDefault: false,
|
|
1366
|
+
def: undefined,
|
|
1367
|
+
defFactory: false,
|
|
1368
|
+
defValidated: false,
|
|
1369
|
+
})),
|
|
1370
|
+
variadic: ir.variadic === undefined ? undefined : projectIO(ir.variadic, io),
|
|
1371
|
+
postfix: ir.postfix.map(item => projectIO(item, io)),
|
|
1372
|
+
};
|
|
1373
|
+
case "object":
|
|
1374
|
+
return {
|
|
1375
|
+
...ir,
|
|
1376
|
+
props: ir.props.map(prop => ({
|
|
1377
|
+
...prop,
|
|
1378
|
+
opt: io === "in" ? prop.opt || prop.hasDefault === true : prop.opt && !prop.hasDefault,
|
|
1379
|
+
val: projectIO(prop.val, io),
|
|
1380
|
+
hasDefault: false,
|
|
1381
|
+
def: undefined,
|
|
1382
|
+
defFactory: false,
|
|
1383
|
+
defValidated: false,
|
|
1384
|
+
})),
|
|
1385
|
+
index: ir.index === undefined ? undefined : projectIO(ir.index, io),
|
|
1386
|
+
symbolIndex: ir.symbolIndex === undefined ? undefined : projectIO(ir.symbolIndex, io),
|
|
1387
|
+
patternIndexes: ir.patternIndexes?.map(index => ({
|
|
1388
|
+
key: projectIO(index.key, io),
|
|
1389
|
+
val: projectIO(index.val, io),
|
|
1390
|
+
})),
|
|
1391
|
+
extras: ir.extras === "delete" ? (io === "in" ? "keep" : "reject") : ir.extras,
|
|
1392
|
+
};
|
|
1393
|
+
case "union":
|
|
1394
|
+
case "intersection":
|
|
1395
|
+
return { ...ir, members: ir.members.map(member => projectIO(member, io)) };
|
|
1396
|
+
case "refine":
|
|
1397
|
+
return { ...ir, base: projectIO(ir.base, io) };
|
|
1398
|
+
case "alias":
|
|
1399
|
+
return { ...ir, resolve: () => projectIO(ir.resolve(), io) };
|
|
1400
|
+
default:
|
|
1401
|
+
return ir;
|
|
1402
|
+
}
|
|
693
1403
|
}
|
|
694
1404
|
|
|
695
|
-
function
|
|
696
|
-
return
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
1405
|
+
function morphIdentities(ir: IR, identities: unknown[] = [], seen = new Set<IR>()): unknown[] {
|
|
1406
|
+
if (seen.has(ir)) return identities;
|
|
1407
|
+
seen.add(ir);
|
|
1408
|
+
switch (ir.k) {
|
|
1409
|
+
case "morph":
|
|
1410
|
+
identities.push(
|
|
1411
|
+
ir.out === undefined
|
|
1412
|
+
? ir.fn
|
|
1413
|
+
: `declared:${expressionOf(projectIO(ir.input, "in"))}=>${expressionOf(projectIO(ir.out, "out"))}`,
|
|
1414
|
+
);
|
|
1415
|
+
morphIdentities(ir.input, identities, seen);
|
|
1416
|
+
if (ir.out !== undefined) morphIdentities(ir.out, identities, seen);
|
|
1417
|
+
break;
|
|
1418
|
+
case "sub": {
|
|
1419
|
+
const schema = ir.schema as InternalType;
|
|
1420
|
+
for (const step of schema[kSteps]) if (step.kind === "pipe") identities.push(step.fn);
|
|
1421
|
+
morphIdentities(schema.ir, identities, seen);
|
|
1422
|
+
break;
|
|
1423
|
+
}
|
|
1424
|
+
case "array":
|
|
1425
|
+
morphIdentities(ir.el, identities, seen);
|
|
1426
|
+
break;
|
|
1427
|
+
case "tuple":
|
|
1428
|
+
for (const item of ir.prefix) morphIdentities(item.val, identities, seen);
|
|
1429
|
+
if (ir.variadic !== undefined) morphIdentities(ir.variadic, identities, seen);
|
|
1430
|
+
for (const item of ir.postfix) morphIdentities(item, identities, seen);
|
|
1431
|
+
break;
|
|
1432
|
+
case "object":
|
|
1433
|
+
if (ir.extras === "delete") identities.push(ir);
|
|
1434
|
+
for (const prop of ir.props) morphIdentities(prop.val, identities, seen);
|
|
1435
|
+
if (ir.index !== undefined) morphIdentities(ir.index, identities, seen);
|
|
1436
|
+
if (ir.symbolIndex !== undefined) morphIdentities(ir.symbolIndex, identities, seen);
|
|
1437
|
+
for (const index of ir.patternIndexes ?? []) morphIdentities(index.val, identities, seen);
|
|
1438
|
+
break;
|
|
1439
|
+
case "union":
|
|
1440
|
+
case "intersection":
|
|
1441
|
+
for (const member of ir.members) morphIdentities(member, identities, seen);
|
|
1442
|
+
break;
|
|
1443
|
+
case "refine":
|
|
1444
|
+
morphIdentities(ir.base, identities, seen);
|
|
1445
|
+
break;
|
|
1446
|
+
case "alias":
|
|
1447
|
+
morphIdentities(ir.resolve(), identities, seen);
|
|
1448
|
+
break;
|
|
1449
|
+
}
|
|
1450
|
+
return identities;
|
|
703
1451
|
}
|
|
704
1452
|
|
|
705
|
-
function
|
|
706
|
-
if (
|
|
707
|
-
|
|
1453
|
+
function assertDeterminateMorphUnions(ir: IR, seen = new Set<IR>()): void {
|
|
1454
|
+
if (seen.has(ir)) return;
|
|
1455
|
+
seen.add(ir);
|
|
1456
|
+
if (ir.k === "union") {
|
|
1457
|
+
for (let leftIndex = 0; leftIndex < ir.members.length; leftIndex++) {
|
|
1458
|
+
const left = ir.members[leftIndex];
|
|
1459
|
+
const leftMorphs = morphIdentities(left);
|
|
1460
|
+
for (let rightIndex = leftIndex + 1; rightIndex < ir.members.length; rightIndex++) {
|
|
1461
|
+
const right = ir.members[rightIndex];
|
|
1462
|
+
const rightMorphs = morphIdentities(right);
|
|
1463
|
+
if (leftMorphs.length === 0 && rightMorphs.length === 0) continue;
|
|
1464
|
+
if (
|
|
1465
|
+
leftMorphs.length === rightMorphs.length &&
|
|
1466
|
+
leftMorphs.every((identity, index) => identity === rightMorphs[index])
|
|
1467
|
+
) {
|
|
1468
|
+
continue;
|
|
1469
|
+
}
|
|
1470
|
+
// Unwrap one alias level eagerly: the disjointness probe relies on
|
|
1471
|
+
// intersect() throwing, and deferred alias intersections resolve lazily.
|
|
1472
|
+
let leftInput = projectIO(left, "in");
|
|
1473
|
+
let rightInput = projectIO(right, "in");
|
|
1474
|
+
if (leftInput.k === "alias") leftInput = leftInput.resolve();
|
|
1475
|
+
if (rightInput.k === "alias") rightInput = rightInput.resolve();
|
|
1476
|
+
if (leftInput.k === "object" && rightInput.k === "object") {
|
|
1477
|
+
const leftKeys = new Set(leftInput.props.map(prop => prop.key));
|
|
1478
|
+
const rightKeys = new Set(rightInput.props.map(prop => prop.key));
|
|
1479
|
+
const leftRejectsRequiredRight =
|
|
1480
|
+
leftInput.extras === "reject" &&
|
|
1481
|
+
rightInput.props.some(prop => !prop.opt && !prop.hasDefault && !leftKeys.has(prop.key));
|
|
1482
|
+
const rightRejectsRequiredLeft =
|
|
1483
|
+
rightInput.extras === "reject" &&
|
|
1484
|
+
leftInput.props.some(prop => !prop.opt && !prop.hasDefault && !rightKeys.has(prop.key));
|
|
1485
|
+
if (leftRejectsRequiredRight || rightRejectsRequiredLeft) continue;
|
|
1486
|
+
}
|
|
1487
|
+
try {
|
|
1488
|
+
intersect(leftInput, rightInput);
|
|
1489
|
+
} catch (error) {
|
|
1490
|
+
if (error instanceof OmpTypeError) continue;
|
|
1491
|
+
throw error;
|
|
1492
|
+
}
|
|
1493
|
+
throw new OmpTypeError("an unordered union with overlapping morph inputs is indeterminate");
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
708
1496
|
}
|
|
709
|
-
|
|
710
|
-
|
|
1497
|
+
switch (ir.k) {
|
|
1498
|
+
case "alias":
|
|
1499
|
+
assertDeterminateMorphUnions(ir.resolve(), seen);
|
|
1500
|
+
break;
|
|
1501
|
+
case "morph":
|
|
1502
|
+
assertDeterminateMorphUnions(ir.input, seen);
|
|
1503
|
+
if (ir.out !== undefined) assertDeterminateMorphUnions(ir.out, seen);
|
|
1504
|
+
break;
|
|
1505
|
+
case "sub":
|
|
1506
|
+
assertDeterminateMorphUnions(ir.schema.ir, seen);
|
|
1507
|
+
break;
|
|
1508
|
+
case "array":
|
|
1509
|
+
assertDeterminateMorphUnions(ir.el, seen);
|
|
1510
|
+
break;
|
|
1511
|
+
case "tuple":
|
|
1512
|
+
for (const item of ir.prefix) assertDeterminateMorphUnions(item.val, seen);
|
|
1513
|
+
if (ir.variadic !== undefined) assertDeterminateMorphUnions(ir.variadic, seen);
|
|
1514
|
+
for (const item of ir.postfix) assertDeterminateMorphUnions(item, seen);
|
|
1515
|
+
break;
|
|
1516
|
+
case "object":
|
|
1517
|
+
for (const prop of ir.props) assertDeterminateMorphUnions(prop.val, seen);
|
|
1518
|
+
if (ir.index !== undefined) assertDeterminateMorphUnions(ir.index, seen);
|
|
1519
|
+
if (ir.symbolIndex !== undefined) assertDeterminateMorphUnions(ir.symbolIndex, seen);
|
|
1520
|
+
for (const index of ir.patternIndexes ?? []) assertDeterminateMorphUnions(index.val, seen);
|
|
1521
|
+
break;
|
|
1522
|
+
case "union":
|
|
1523
|
+
case "intersection":
|
|
1524
|
+
for (const member of ir.members) assertDeterminateMorphUnions(member, seen);
|
|
1525
|
+
break;
|
|
1526
|
+
case "refine":
|
|
1527
|
+
assertDeterminateMorphUnions(ir.base, seen);
|
|
1528
|
+
break;
|
|
711
1529
|
}
|
|
712
|
-
const hasDefault = Object.hasOwn(property, "default");
|
|
713
|
-
return {
|
|
714
|
-
key: String(property.key),
|
|
715
|
-
opt: property.kind === "optional",
|
|
716
|
-
val: embed(property.value),
|
|
717
|
-
...(hasDefault
|
|
718
|
-
? { def: property.default, defFactory: typeof property.default === "function", hasDefault: true }
|
|
719
|
-
: {}),
|
|
720
|
-
};
|
|
721
1530
|
}
|
|
722
1531
|
|
|
723
|
-
function
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
1532
|
+
function getPathIR(ir: IR, requestedKey: unknown): IR {
|
|
1533
|
+
let key = requestedKey;
|
|
1534
|
+
if ((typeof key === "function" || (typeof key === "object" && key !== null)) && IR_BRAND in key) {
|
|
1535
|
+
const keySchema = key as InternalType;
|
|
1536
|
+
if (keySchema.ir.k === "symbol") key = Symbol.for("omptype.index");
|
|
1537
|
+
else {
|
|
1538
|
+
const arrayIndex: unknown = type.arrayIndex;
|
|
1539
|
+
if (keySchema === arrayIndex) key = 0;
|
|
1540
|
+
else {
|
|
1541
|
+
throw new OmpTypeError(
|
|
1542
|
+
`${keySchema.expression} is not allowed as an array or object index; use a concrete property key`,
|
|
1543
|
+
);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
if (typeof key !== "string" && typeof key !== "number" && typeof key !== "symbol") {
|
|
1548
|
+
throw new OmpTypeError(`get keys must be strings, numbers, or symbols`);
|
|
1549
|
+
}
|
|
1550
|
+
if (ir.k === "alias") return getPathIR(ir.resolve(), key);
|
|
1551
|
+
if (ir.k === "sub") return getPathIR(ir.schema.ir, key);
|
|
1552
|
+
if (ir.k === "union") {
|
|
1553
|
+
return unionOf(ir.members.map(member => getPathIR(member, key)));
|
|
1554
|
+
}
|
|
1555
|
+
if (ir.k === "array") {
|
|
1556
|
+
const index = typeof key === "number" ? key : typeof key === "string" && /^\d+$/.test(key) ? Number(key) : -1;
|
|
1557
|
+
if (!Number.isSafeInteger(index) || index < 0) throw new OmpTypeError(`key ${String(key)} is not declared`);
|
|
1558
|
+
return unionOf([ir.el, { k: "undefined" }]);
|
|
1559
|
+
}
|
|
1560
|
+
if (ir.k === "tuple") {
|
|
1561
|
+
const index = typeof key === "number" ? key : typeof key === "string" && /^\d+$/.test(key) ? Number(key) : -1;
|
|
1562
|
+
if (!Number.isSafeInteger(index) || index < 0) throw new OmpTypeError(`key ${String(key)} is not declared`);
|
|
1563
|
+
if (index < ir.prefix.length) {
|
|
1564
|
+
const item = ir.prefix[index];
|
|
1565
|
+
return item.opt ? unionOf([{ k: "undefined" }, item.val]) : item.val;
|
|
1566
|
+
}
|
|
1567
|
+
if (ir.variadic === undefined) throw new OmpTypeError(`key ${String(key)} is not declared`);
|
|
1568
|
+
return unionOf([{ k: "undefined" }, ir.variadic, ...ir.postfix]);
|
|
1569
|
+
}
|
|
1570
|
+
if (ir.k === "undefined") return ir;
|
|
1571
|
+
if (ir.k !== "object") throw new OmpTypeError("get requires an object schema");
|
|
1572
|
+
|
|
1573
|
+
const matches: IR[] = [];
|
|
1574
|
+
const prop = ir.props.find(candidate => candidate.key === String(key));
|
|
1575
|
+
if (prop !== undefined) matches.push(prop.val);
|
|
1576
|
+
if (typeof key === "string") {
|
|
1577
|
+
if (ir.index !== undefined) matches.push(ir.index);
|
|
1578
|
+
for (const index of ir.patternIndexes ?? []) {
|
|
1579
|
+
if (!(walk(index.key, key) instanceof OmpErrors)) matches.push(index.val);
|
|
1580
|
+
}
|
|
1581
|
+
} else if (typeof key === "symbol" && ir.symbolIndex !== undefined) {
|
|
1582
|
+
matches.push(ir.symbolIndex);
|
|
1583
|
+
}
|
|
1584
|
+
if (matches.length === 0) throw new OmpTypeError(`key ${String(key)} is not declared`);
|
|
1585
|
+
const value = matches.reduce((left, right) => intersect(left, right));
|
|
1586
|
+
return prop !== undefined && !prop.opt ? value : unionOf([value, { k: "undefined" }]);
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
function unionOf(members: IR[]): IR {
|
|
1590
|
+
const flattened = members.flatMap(member => (member.k === "union" ? member.members : [member]));
|
|
1591
|
+
return flattened.length === 1 ? flattened[0] : { k: "union", members: flattened };
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
function expressionOf(ir: IR, ancestors = new Set<IR>()): string {
|
|
1595
|
+
if (ancestors.has(ir)) return ir.k === "alias" ? ir.name : ir.k;
|
|
1596
|
+
const nextAncestors = new Set(ancestors);
|
|
1597
|
+
nextAncestors.add(ir);
|
|
1598
|
+
const child = (node: IR): string => expressionOf(node, nextAncestors);
|
|
1599
|
+
switch (ir.k) {
|
|
1600
|
+
case "alias":
|
|
1601
|
+
return child(ir.resolve());
|
|
1602
|
+
case "sub":
|
|
1603
|
+
return child(ir.schema.ir);
|
|
1604
|
+
case "unknown":
|
|
1605
|
+
case "null":
|
|
1606
|
+
case "undefined":
|
|
1607
|
+
case "boolean":
|
|
1608
|
+
case "bigint":
|
|
1609
|
+
case "symbol":
|
|
1610
|
+
case "never":
|
|
1611
|
+
return ir.k;
|
|
1612
|
+
case "anyobject":
|
|
1613
|
+
return "object";
|
|
1614
|
+
case "string":
|
|
1615
|
+
return "string";
|
|
1616
|
+
case "number":
|
|
1617
|
+
return ir.divisor !== undefined ? `number % ${ir.divisor}` : ir.int ? "number % 1" : "number";
|
|
1618
|
+
case "lit":
|
|
1619
|
+
return typeof ir.v === "string" ? JSON.stringify(ir.v) : String(ir.v);
|
|
1620
|
+
case "array": {
|
|
1621
|
+
const element = child(ir.el);
|
|
1622
|
+
return `${ir.el.k === "union" || ir.el.k === "intersection" ? `(${element})` : element}[]`;
|
|
1623
|
+
}
|
|
1624
|
+
case "tuple": {
|
|
1625
|
+
const items = ir.prefix.map(item => {
|
|
1626
|
+
const value = child(item.val);
|
|
1627
|
+
if (item.hasDefault) return `${value} = ${child({ k: "lit", v: item.def })}`;
|
|
1628
|
+
return `${value}${item.opt ? "?" : ""}`;
|
|
1629
|
+
});
|
|
1630
|
+
if (ir.variadic !== undefined) items.push(`...${child(ir.variadic)}[]`);
|
|
1631
|
+
items.push(...ir.postfix.map(child));
|
|
1632
|
+
return `[${items.join(", ")}]`;
|
|
1633
|
+
}
|
|
1634
|
+
case "object": {
|
|
1635
|
+
const properties = ir.props.map(prop => `${String(prop.key)}${prop.opt ? "?" : ""}: ${child(prop.val)}`);
|
|
1636
|
+
if (ir.index !== undefined) properties.unshift(`[string]: ${child(ir.index)}`);
|
|
1637
|
+
if (ir.symbolIndex !== undefined) properties.unshift(`[symbol]: ${child(ir.symbolIndex)}`);
|
|
1638
|
+
return `{ ${properties.join(", ")} }`;
|
|
1639
|
+
}
|
|
1640
|
+
case "union":
|
|
1641
|
+
return [...new Set(ir.members.map(child))].join(" | ");
|
|
1642
|
+
case "intersection":
|
|
1643
|
+
return ir.members.map(child).join(" & ");
|
|
1644
|
+
case "refine":
|
|
1645
|
+
return child(ir.base);
|
|
1646
|
+
case "morph":
|
|
1647
|
+
return `(In: ${child(ir.input)}) => Out<${child(ir.out ?? { k: "unknown" })}>`;
|
|
1648
|
+
case "instance":
|
|
1649
|
+
return ir.ctor.name || "object";
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
function arkJsonOf(ir: IR, ancestors = new Set<IR>()): unknown {
|
|
1654
|
+
if (ancestors.has(ir)) return ir.k === "alias" ? { alias: ir.name } : { cyclic: ir.k };
|
|
1655
|
+
const nextAncestors = new Set(ancestors);
|
|
1656
|
+
nextAncestors.add(ir);
|
|
1657
|
+
const child = (node: IR): unknown => arkJsonOf(node, nextAncestors);
|
|
1658
|
+
switch (ir.k) {
|
|
1659
|
+
case "alias":
|
|
1660
|
+
return child(ir.resolve());
|
|
1661
|
+
case "sub":
|
|
1662
|
+
return child(ir.schema.ir);
|
|
1663
|
+
case "lit":
|
|
1664
|
+
return {
|
|
1665
|
+
unit: ir.v === undefined ? "undefined" : typeof ir.v === "bigint" ? `${ir.v}n` : ir.v,
|
|
1666
|
+
};
|
|
1667
|
+
case "null":
|
|
1668
|
+
return { unit: null };
|
|
1669
|
+
case "undefined":
|
|
1670
|
+
return { unit: "undefined" };
|
|
1671
|
+
case "boolean":
|
|
1672
|
+
return [{ unit: false }, { unit: true }];
|
|
1673
|
+
case "union":
|
|
1674
|
+
case "intersection":
|
|
1675
|
+
return ir.members.map(child);
|
|
1676
|
+
case "array":
|
|
1677
|
+
return { proto: "Array", sequence: child(ir.el) };
|
|
1678
|
+
case "tuple":
|
|
1679
|
+
return {
|
|
1680
|
+
proto: "Array",
|
|
1681
|
+
sequence: {
|
|
1682
|
+
prefix: ir.prefix.map(item => child(item.val)),
|
|
1683
|
+
...(ir.variadic === undefined ? {} : { variadic: child(ir.variadic) }),
|
|
1684
|
+
...(ir.postfix.length === 0 ? {} : { postfix: ir.postfix.map(child) }),
|
|
1685
|
+
},
|
|
1686
|
+
};
|
|
1687
|
+
case "object":
|
|
1688
|
+
return {
|
|
1689
|
+
domain: "object",
|
|
1690
|
+
required: ir.props
|
|
1691
|
+
.filter(prop => !prop.opt && !prop.hasDefault)
|
|
1692
|
+
.map(prop => ({ key: prop.key, value: child(prop.val) })),
|
|
1693
|
+
optional: ir.props
|
|
1694
|
+
.filter(prop => prop.opt || prop.hasDefault)
|
|
1695
|
+
.map(prop => ({
|
|
1696
|
+
key: prop.key,
|
|
1697
|
+
value: child(prop.val),
|
|
1698
|
+
...(prop.hasDefault
|
|
1699
|
+
? {
|
|
1700
|
+
default:
|
|
1701
|
+
prop.defFactory && typeof prop.def === "function"
|
|
1702
|
+
? `$ark.${prop.def.name || "default"}`
|
|
1703
|
+
: prop.def,
|
|
1704
|
+
}
|
|
1705
|
+
: {}),
|
|
1706
|
+
})),
|
|
1707
|
+
};
|
|
1708
|
+
case "refine":
|
|
1709
|
+
return child(ir.base);
|
|
1710
|
+
case "morph":
|
|
1711
|
+
return { in: child(ir.input), ...(ir.out === undefined ? {} : { declaredOut: child(ir.out) }) };
|
|
1712
|
+
case "instance":
|
|
1713
|
+
return { proto: ir.ctor.name };
|
|
1714
|
+
case "anyobject":
|
|
1715
|
+
return { domain: "object" };
|
|
1716
|
+
default:
|
|
1717
|
+
return ir.k;
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
type ObjectIR = Extract<IR, { k: "object" }>;
|
|
1722
|
+
|
|
1723
|
+
function requireObject(ir: IR, operation: string): ObjectIR {
|
|
1724
|
+
if (ir.k !== "object") throw new OmpTypeError(`${operation} requires an object schema`);
|
|
1725
|
+
return ir;
|
|
1726
|
+
}
|
|
1727
|
+
function selectObjectProps(ir: IR, keys: readonly PropertyKey[], keepSelected: boolean, operation: string): ObjectIR {
|
|
1728
|
+
const object = requireObject(ir, operation);
|
|
1729
|
+
const selected = new Set(keys);
|
|
1730
|
+
for (const key of selected) {
|
|
1731
|
+
if (!object.props.some(prop => prop.key === key)) throw new OmpTypeError(`key ${String(key)} does not exist`);
|
|
1732
|
+
}
|
|
1733
|
+
return { ...object, props: object.props.filter(prop => selected.has(prop.key) === keepSelected) };
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
function setObjectOptionality(ir: IR, optional: boolean, operation: string): ObjectIR {
|
|
1737
|
+
const object = requireObject(ir, operation);
|
|
1738
|
+
return { ...object, props: object.props.map(prop => ({ ...prop, opt: optional })) };
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
function mergeObjectDefinition(ir: IR, definition: unknown, resolve?: AliasResolver): ObjectIR {
|
|
1742
|
+
return mergeObjects(requireObject(ir, "merge"), requireObject(parseDef(definition, resolve), "merge"));
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
function propertyFromIR(prop: PropIR): TypeProperty {
|
|
1746
|
+
return {
|
|
1747
|
+
kind: prop.opt ? "optional" : "required",
|
|
1748
|
+
key: prop.key,
|
|
1749
|
+
value: makeType(prop.val, [], {}) as unknown as FluentType<unknown>,
|
|
1750
|
+
...(prop.hasDefault ? { default: prop.def } : {}),
|
|
1751
|
+
meta: {},
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
function propertyToIR(property: TypeProperty): PropIR {
|
|
1756
|
+
if (property.kind !== "required" && property.kind !== "optional") {
|
|
1757
|
+
throw new OmpTypeError(`mapped property ${String(property.key)} has invalid kind`);
|
|
1758
|
+
}
|
|
1759
|
+
if (!(IR_BRAND in property.value)) {
|
|
1760
|
+
throw new OmpTypeError(`mapped property ${String(property.key)} must contain a schema value`);
|
|
1761
|
+
}
|
|
1762
|
+
const hasDefault = Object.hasOwn(property, "default");
|
|
1763
|
+
return {
|
|
1764
|
+
key: property.key,
|
|
1765
|
+
opt: property.kind === "optional",
|
|
1766
|
+
val: embed(property.value),
|
|
1767
|
+
...(hasDefault
|
|
1768
|
+
? { def: property.default, defFactory: typeof property.default === "function", hasDefault: true }
|
|
1769
|
+
: {}),
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
function acceptsDateIR(ir: IR): boolean {
|
|
1774
|
+
if (ir.k === "instance") return ir.ctor === Date;
|
|
1775
|
+
if (ir.k === "refine") return acceptsDateIR(ir.base);
|
|
1776
|
+
if (ir.k === "union") return ir.members.every(acceptsDateIR);
|
|
1777
|
+
return false;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
function dateRefinement(
|
|
1781
|
+
schema: InternalType,
|
|
1782
|
+
timestamp: number,
|
|
1783
|
+
relation: string,
|
|
1784
|
+
predicate: (value: number) => boolean,
|
|
1785
|
+
): InternalType {
|
|
1786
|
+
if (!Number.isFinite(timestamp)) throw new OmpTypeError("date bound must be valid");
|
|
1787
|
+
if (!acceptsDateIR(schema.ir)) throw new OmpTypeError("date bounds require a Date type");
|
|
1788
|
+
const bound = new Date(timestamp);
|
|
730
1789
|
return makeType(
|
|
731
1790
|
{
|
|
732
1791
|
k: "refine",
|
|
@@ -765,6 +1824,11 @@ function selectNodes(root: IR, kind: string): readonly SelectedNode[] {
|
|
|
765
1824
|
case "object":
|
|
766
1825
|
for (const prop of node.props) visit(prop.val);
|
|
767
1826
|
if (node.index !== undefined) visit(node.index);
|
|
1827
|
+
if (node.symbolIndex !== undefined) visit(node.symbolIndex);
|
|
1828
|
+
for (const index of node.patternIndexes ?? []) {
|
|
1829
|
+
visit(index.key);
|
|
1830
|
+
visit(index.val);
|
|
1831
|
+
}
|
|
768
1832
|
break;
|
|
769
1833
|
case "union":
|
|
770
1834
|
case "intersection":
|
|
@@ -797,10 +1861,22 @@ function mergeObjects(left: ObjectIR, right: ObjectIR): ObjectIR {
|
|
|
797
1861
|
k: "object",
|
|
798
1862
|
props,
|
|
799
1863
|
index: right.index ?? left.index,
|
|
1864
|
+
symbolIndex: right.symbolIndex ?? left.symbolIndex,
|
|
1865
|
+
patternIndexes:
|
|
1866
|
+
left.patternIndexes === undefined && right.patternIndexes === undefined
|
|
1867
|
+
? undefined
|
|
1868
|
+
: [...(left.patternIndexes ?? []), ...(right.patternIndexes ?? [])],
|
|
800
1869
|
extras: right.extras === "keep" ? left.extras : right.extras,
|
|
801
1870
|
};
|
|
802
1871
|
}
|
|
803
1872
|
|
|
1873
|
+
function withShallowExtras(ir: IR, extras: ObjectIR["extras"]): IR {
|
|
1874
|
+
if (ir.k === "object") return { ...ir, extras };
|
|
1875
|
+
if (ir.k === "union") return { ...ir, members: ir.members.map(member => withShallowExtras(member, extras)) };
|
|
1876
|
+
if (ir.k === "alias") return withShallowExtras(ir.resolve(), extras);
|
|
1877
|
+
throw new OmpTypeError("onUndeclaredKey requires an object schema");
|
|
1878
|
+
}
|
|
1879
|
+
|
|
804
1880
|
function withDeepExtras(ir: IR, extras: ObjectIR["extras"]): IR {
|
|
805
1881
|
switch (ir.k) {
|
|
806
1882
|
case "object":
|
|
@@ -809,6 +1885,11 @@ function withDeepExtras(ir: IR, extras: ObjectIR["extras"]): IR {
|
|
|
809
1885
|
extras,
|
|
810
1886
|
props: ir.props.map(prop => ({ ...prop, val: withDeepExtras(prop.val, extras) })),
|
|
811
1887
|
index: ir.index === undefined ? undefined : withDeepExtras(ir.index, extras),
|
|
1888
|
+
symbolIndex: ir.symbolIndex === undefined ? undefined : withDeepExtras(ir.symbolIndex, extras),
|
|
1889
|
+
patternIndexes: ir.patternIndexes?.map(index => ({
|
|
1890
|
+
key: withDeepExtras(index.key, extras),
|
|
1891
|
+
val: withDeepExtras(index.val, extras),
|
|
1892
|
+
})),
|
|
812
1893
|
};
|
|
813
1894
|
case "array":
|
|
814
1895
|
return { ...ir, el: withDeepExtras(ir.el, extras) };
|
|
@@ -835,14 +1916,111 @@ function withDeepExtras(ir: IR, extras: ObjectIR["extras"]): IR {
|
|
|
835
1916
|
}
|
|
836
1917
|
}
|
|
837
1918
|
|
|
1919
|
+
function intersectTupleWithArray(tuple: TupleIR, array: Extract<IR, { k: "array" }>): IR {
|
|
1920
|
+
if (array.min !== undefined || array.max !== undefined) {
|
|
1921
|
+
return { k: "intersection", members: [tuple, array] };
|
|
1922
|
+
}
|
|
1923
|
+
return {
|
|
1924
|
+
...tuple,
|
|
1925
|
+
prefix: tuple.prefix.map(item => ({ ...item, val: intersect(item.val, array.el) })),
|
|
1926
|
+
variadic: tuple.variadic === undefined ? undefined : intersect(tuple.variadic, array.el),
|
|
1927
|
+
postfix: tuple.postfix.map(item => intersect(item, array.el)),
|
|
1928
|
+
};
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
function intersectTuples(left: TupleIR, right: TupleIR): IR {
|
|
1932
|
+
if (
|
|
1933
|
+
left.postfix.length !== 0 ||
|
|
1934
|
+
right.postfix.length !== 0 ||
|
|
1935
|
+
left.prefix.some(item => item.hasDefault) ||
|
|
1936
|
+
right.prefix.some(item => item.hasDefault)
|
|
1937
|
+
) {
|
|
1938
|
+
return { k: "intersection", members: [left, right] };
|
|
1939
|
+
}
|
|
1940
|
+
const leftRequired = left.prefix.filter(item => !item.opt).length;
|
|
1941
|
+
const rightRequired = right.prefix.filter(item => !item.opt).length;
|
|
1942
|
+
const minimum = Math.max(leftRequired, rightRequired);
|
|
1943
|
+
const leftMaximum = left.variadic === undefined ? left.prefix.length : Number.POSITIVE_INFINITY;
|
|
1944
|
+
const rightMaximum = right.variadic === undefined ? right.prefix.length : Number.POSITIVE_INFINITY;
|
|
1945
|
+
const maximum = Math.min(leftMaximum, rightMaximum);
|
|
1946
|
+
if (minimum > maximum) throw new OmpTypeError("tuple length intersection is unsatisfiable");
|
|
1947
|
+
|
|
1948
|
+
const prefixLength = Number.isFinite(maximum) ? maximum : Math.max(left.prefix.length, right.prefix.length);
|
|
1949
|
+
const prefix: TupleIR["prefix"] = [];
|
|
1950
|
+
for (let index = 0; index < prefixLength; index++) {
|
|
1951
|
+
const leftItem = left.prefix[index];
|
|
1952
|
+
const rightItem = right.prefix[index];
|
|
1953
|
+
const leftNode = leftItem?.val ?? left.variadic;
|
|
1954
|
+
const rightNode = rightItem?.val ?? right.variadic;
|
|
1955
|
+
if (leftNode === undefined || rightNode === undefined) break;
|
|
1956
|
+
const required = (leftItem !== undefined && !leftItem.opt) || (rightItem !== undefined && !rightItem.opt);
|
|
1957
|
+
try {
|
|
1958
|
+
prefix.push({ val: intersect(leftNode, rightNode), opt: !required });
|
|
1959
|
+
} catch (error) {
|
|
1960
|
+
if (required || !(error instanceof OmpTypeError)) throw error;
|
|
1961
|
+
break;
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
const variadic =
|
|
1965
|
+
leftMaximum === Number.POSITIVE_INFINITY && rightMaximum === Number.POSITIVE_INFINITY
|
|
1966
|
+
? intersect(left.variadic!, right.variadic!)
|
|
1967
|
+
: undefined;
|
|
1968
|
+
return { k: "tuple", prefix, variadic, postfix: [] };
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
const kIntersections = Symbol("omptype.intersections");
|
|
1972
|
+
|
|
1973
|
+
/** Deferred alias-pair intersections cached on the left node; cyclic references resolve to one node. */
|
|
1974
|
+
interface AliasIntersections {
|
|
1975
|
+
[kIntersections]?: WeakMap<IR, IR>;
|
|
1976
|
+
}
|
|
1977
|
+
|
|
838
1978
|
/** Intersect two IR nodes, rejecting statically disjoint domains. */
|
|
839
1979
|
function intersect(a: IR, b: IR): IR {
|
|
840
|
-
if (a.k === "alias"
|
|
841
|
-
|
|
1980
|
+
if (a.k === "alias" || b.k === "alias") {
|
|
1981
|
+
// Defer through a lazy alias so cyclic references terminate: revisiting
|
|
1982
|
+
// the same pair while it is being resolved returns the same node instead
|
|
1983
|
+
// of recursing forever.
|
|
1984
|
+
const target = a as IR & AliasIntersections;
|
|
1985
|
+
target[kIntersections] ??= new WeakMap<IR, IR>();
|
|
1986
|
+
const cache = target[kIntersections];
|
|
1987
|
+
const existing = cache.get(b);
|
|
1988
|
+
if (existing !== undefined) return existing;
|
|
1989
|
+
let resolved: IR | undefined;
|
|
1990
|
+
const reference: IR = {
|
|
1991
|
+
k: "alias",
|
|
1992
|
+
name: a.k === "alias" ? a.name : b.k === "alias" ? b.name : "intersection",
|
|
1993
|
+
resolve: () =>
|
|
1994
|
+
(resolved ??= intersectResolved(a.k === "alias" ? a.resolve() : a, b.k === "alias" ? b.resolve() : b)),
|
|
1995
|
+
};
|
|
1996
|
+
cache.set(b, reference);
|
|
1997
|
+
return reference;
|
|
1998
|
+
}
|
|
1999
|
+
return intersectResolved(a, b);
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
function intersectResolved(a: IR, b: IR): IR {
|
|
842
2003
|
if (a.k === "never" || b.k === "never") throw new OmpTypeError("intersection with never is unsatisfiable");
|
|
843
2004
|
if (a.k === "unknown") return b;
|
|
844
2005
|
if (b.k === "unknown") return a;
|
|
845
2006
|
if (a === b) return a;
|
|
2007
|
+
if (a.k === "morph" && b.k === "morph") {
|
|
2008
|
+
if (a.fn !== b.fn || a.out !== b.out) {
|
|
2009
|
+
throw new OmpTypeError("intersection of distinct morphs is indeterminate");
|
|
2010
|
+
}
|
|
2011
|
+
return { ...a, input: intersect(a.input, b.input) };
|
|
2012
|
+
}
|
|
2013
|
+
if (a.k === "morph") return { ...a, input: intersect(a.input, b) };
|
|
2014
|
+
if (b.k === "morph") return { ...b, input: intersect(a, b.input) };
|
|
2015
|
+
if (a.k === "sub" && a.schema.hasSteps) {
|
|
2016
|
+
if (b.k === "sub" && b.schema.hasSteps) {
|
|
2017
|
+
if (a.schema === b.schema) return a;
|
|
2018
|
+
throw new OmpTypeError("intersection of distinct morphs is indeterminate");
|
|
2019
|
+
}
|
|
2020
|
+
const schema = a.schema as InternalType;
|
|
2021
|
+
return embed(makeType(intersect(schema.ir, b), schema[kSteps], metaOf(schema)));
|
|
2022
|
+
}
|
|
2023
|
+
if (b.k === "sub" && b.schema.hasSteps) return intersect(b, a);
|
|
846
2024
|
if (a.k === "union" || b.k === "union") {
|
|
847
2025
|
const union = a.k === "union" ? a : b.k === "union" ? b : undefined;
|
|
848
2026
|
if (union === undefined) throw new OmpTypeError("union intersection invariant failed");
|
|
@@ -871,7 +2049,26 @@ function intersect(a: IR, b: IR): IR {
|
|
|
871
2049
|
if (index < 0) props.push(bp);
|
|
872
2050
|
else {
|
|
873
2051
|
const ap = props[index];
|
|
874
|
-
|
|
2052
|
+
const required = (!ap.opt && !ap.hasDefault) || (!bp.opt && !bp.hasDefault);
|
|
2053
|
+
if (ap.hasDefault && bp.hasDefault && !Object.is(ap.def, bp.def)) {
|
|
2054
|
+
throw new OmpTypeError(
|
|
2055
|
+
`ParseError: Invalid intersection of default values ${String(ap.def)} & ${String(bp.def)}`,
|
|
2056
|
+
);
|
|
2057
|
+
}
|
|
2058
|
+
const defaulted = required ? undefined : ap.hasDefault ? ap : bp.hasDefault ? bp : undefined;
|
|
2059
|
+
props[index] = {
|
|
2060
|
+
key: ap.key,
|
|
2061
|
+
opt: ap.opt && bp.opt,
|
|
2062
|
+
val: intersect(ap.val, bp.val),
|
|
2063
|
+
...(defaulted
|
|
2064
|
+
? {
|
|
2065
|
+
def: defaulted.def,
|
|
2066
|
+
defFactory: defaulted.defFactory,
|
|
2067
|
+
hasDefault: true,
|
|
2068
|
+
defValidated: defaulted.defValidated,
|
|
2069
|
+
}
|
|
2070
|
+
: {}),
|
|
2071
|
+
};
|
|
875
2072
|
}
|
|
876
2073
|
}
|
|
877
2074
|
const extras =
|
|
@@ -920,12 +2117,21 @@ function intersect(a: IR, b: IR): IR {
|
|
|
920
2117
|
}
|
|
921
2118
|
return { k: "array", el: intersect(a.el, b.el), min, max };
|
|
922
2119
|
}
|
|
2120
|
+
if (a.k === "tuple" && b.k === "tuple") return intersectTuples(a, b);
|
|
2121
|
+
if (a.k === "tuple" && b.k === "array") return intersectTupleWithArray(a, b);
|
|
2122
|
+
if (a.k === "array" && b.k === "tuple") return intersectTupleWithArray(b, a);
|
|
923
2123
|
if (a.k === "instance" && b.k === "instance") {
|
|
924
2124
|
if (a.ctor === b.ctor || a.ctor.prototype instanceof b.ctor) return a;
|
|
925
2125
|
if (b.ctor.prototype instanceof a.ctor) return b;
|
|
926
2126
|
throw new OmpTypeError(`intersection of ${a.expected} and ${b.expected} is unsatisfiable`);
|
|
927
2127
|
}
|
|
928
2128
|
if (a.k === b.k && ["null", "undefined", "boolean", "bigint", "symbol", "anyobject"].includes(a.k)) return a;
|
|
2129
|
+
if (
|
|
2130
|
+
(a.k === "object" && (b.k === "array" || b.k === "tuple")) ||
|
|
2131
|
+
(b.k === "object" && (a.k === "array" || a.k === "tuple"))
|
|
2132
|
+
) {
|
|
2133
|
+
return { k: "intersection", members: [a, b] };
|
|
2134
|
+
}
|
|
929
2135
|
const leftDomain = domainOf(a);
|
|
930
2136
|
const rightDomain = domainOf(b);
|
|
931
2137
|
if (leftDomain !== undefined && rightDomain !== undefined && leftDomain !== rightDomain) {
|
|
@@ -937,6 +2143,151 @@ function intersect(a: IR, b: IR): IR {
|
|
|
937
2143
|
return { k: "intersection", members };
|
|
938
2144
|
}
|
|
939
2145
|
|
|
2146
|
+
/** Reduce parsed unions/intersections to their observable semantic form. */
|
|
2147
|
+
function normalizeIR(ir: IR): IR {
|
|
2148
|
+
switch (ir.k) {
|
|
2149
|
+
case "intersection": {
|
|
2150
|
+
const members = ir.members.map(normalizeIR);
|
|
2151
|
+
if (members.length === 0) return { k: "unknown" };
|
|
2152
|
+
return members.slice(1).reduce(intersect, members[0]);
|
|
2153
|
+
}
|
|
2154
|
+
case "union": {
|
|
2155
|
+
const members: IR[] = [];
|
|
2156
|
+
let changed = false;
|
|
2157
|
+
for (let index = 0; index < ir.members.length; index++) {
|
|
2158
|
+
const original = ir.members[index];
|
|
2159
|
+
const member = normalizeIR(original);
|
|
2160
|
+
changed ||= member !== original;
|
|
2161
|
+
if (member.k === "union") {
|
|
2162
|
+
members.push(...member.members);
|
|
2163
|
+
changed = true;
|
|
2164
|
+
} else if (member.k === "never") {
|
|
2165
|
+
changed = true;
|
|
2166
|
+
} else if (member.k === "unknown") {
|
|
2167
|
+
return { k: "unknown" };
|
|
2168
|
+
} else {
|
|
2169
|
+
members.push(member);
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
if (
|
|
2173
|
+
members.every(
|
|
2174
|
+
member =>
|
|
2175
|
+
member.k === "lit" &&
|
|
2176
|
+
(member.v === null || (typeof member.v !== "object" && typeof member.v !== "function")),
|
|
2177
|
+
)
|
|
2178
|
+
) {
|
|
2179
|
+
const pruned: IR[] = [];
|
|
2180
|
+
for (const member of members) {
|
|
2181
|
+
if (member.k === "lit" && pruned.some(candidate => candidate.k === "lit" && candidate.v === member.v)) {
|
|
2182
|
+
changed = true;
|
|
2183
|
+
} else {
|
|
2184
|
+
pruned.push(member);
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
if (pruned.length === 0) return { k: "never" };
|
|
2188
|
+
if (pruned.length === 1) return pruned[0];
|
|
2189
|
+
if (pruned.length === 2 && pruned.every(member => member.k === "lit" && typeof member.v === "boolean")) {
|
|
2190
|
+
return { k: "boolean" };
|
|
2191
|
+
}
|
|
2192
|
+
return changed ? { ...ir, members: pruned } : ir;
|
|
2193
|
+
}
|
|
2194
|
+
const pruned = members.filter(
|
|
2195
|
+
(member, index) =>
|
|
2196
|
+
!members.some(
|
|
2197
|
+
(candidate, candidateIndex) =>
|
|
2198
|
+
candidateIndex !== index &&
|
|
2199
|
+
!hasMorph(member) &&
|
|
2200
|
+
!hasMorph(candidate) &&
|
|
2201
|
+
isSubtype(member, candidate) &&
|
|
2202
|
+
(!isSubtype(candidate, member) || candidateIndex < index),
|
|
2203
|
+
),
|
|
2204
|
+
);
|
|
2205
|
+
if (pruned.length === 0) return { k: "never" };
|
|
2206
|
+
if (pruned.length === 1) return pruned[0];
|
|
2207
|
+
if (pruned.length === 2 && pruned.every(member => member.k === "lit" && typeof member.v === "boolean")) {
|
|
2208
|
+
return { k: "boolean" };
|
|
2209
|
+
}
|
|
2210
|
+
if (
|
|
2211
|
+
!changed &&
|
|
2212
|
+
!pruned.some(member => member.k === "alias") &&
|
|
2213
|
+
pruned.length === ir.members.length &&
|
|
2214
|
+
pruned.every((member, index) => member === ir.members[index])
|
|
2215
|
+
) {
|
|
2216
|
+
return ir;
|
|
2217
|
+
}
|
|
2218
|
+
return { ...ir, members: pruned };
|
|
2219
|
+
}
|
|
2220
|
+
case "array": {
|
|
2221
|
+
const element = normalizeIR(ir.el);
|
|
2222
|
+
return element === ir.el && ir.el.k !== "alias" ? ir : { ...ir, el: element };
|
|
2223
|
+
}
|
|
2224
|
+
case "tuple": {
|
|
2225
|
+
const prefix = ir.prefix.map(item => {
|
|
2226
|
+
const value = normalizeIR(item.val);
|
|
2227
|
+
return value === item.val ? item : { ...item, val: value };
|
|
2228
|
+
});
|
|
2229
|
+
const variadic = ir.variadic === undefined ? undefined : normalizeIR(ir.variadic);
|
|
2230
|
+
const postfix = ir.postfix.map(normalizeIR);
|
|
2231
|
+
if (
|
|
2232
|
+
!ir.prefix.some(item => item.val.k === "alias") &&
|
|
2233
|
+
ir.variadic?.k !== "alias" &&
|
|
2234
|
+
!ir.postfix.some(item => item.k === "alias") &&
|
|
2235
|
+
prefix.every((item, index) => item === ir.prefix[index]) &&
|
|
2236
|
+
variadic === ir.variadic &&
|
|
2237
|
+
postfix.every((item, index) => item === ir.postfix[index])
|
|
2238
|
+
) {
|
|
2239
|
+
return ir;
|
|
2240
|
+
}
|
|
2241
|
+
return { ...ir, prefix, variadic, postfix };
|
|
2242
|
+
}
|
|
2243
|
+
case "object": {
|
|
2244
|
+
let props: PropIR[] | undefined;
|
|
2245
|
+
for (let index = 0; index < ir.props.length; index++) {
|
|
2246
|
+
const prop = ir.props[index];
|
|
2247
|
+
const value = normalizeIR(prop.val);
|
|
2248
|
+
if (value === prop.val) continue;
|
|
2249
|
+
props ??= [...ir.props];
|
|
2250
|
+
props[index] = { ...prop, val: value };
|
|
2251
|
+
}
|
|
2252
|
+
const index = ir.index === undefined ? undefined : normalizeIR(ir.index);
|
|
2253
|
+
const symbolIndex = ir.symbolIndex === undefined ? undefined : normalizeIR(ir.symbolIndex);
|
|
2254
|
+
const patternIndexes = ir.patternIndexes?.map(pattern => {
|
|
2255
|
+
const key = normalizeIR(pattern.key);
|
|
2256
|
+
const val = normalizeIR(pattern.val);
|
|
2257
|
+
return key === pattern.key && val === pattern.val ? pattern : { key, val };
|
|
2258
|
+
});
|
|
2259
|
+
if (
|
|
2260
|
+
props === undefined &&
|
|
2261
|
+
!ir.props.some(prop => prop.val.k === "alias") &&
|
|
2262
|
+
ir.index?.k !== "alias" &&
|
|
2263
|
+
ir.symbolIndex?.k !== "alias" &&
|
|
2264
|
+
!ir.patternIndexes?.some(pattern => pattern.key.k === "alias" || pattern.val.k === "alias") &&
|
|
2265
|
+
index === ir.index &&
|
|
2266
|
+
symbolIndex === ir.symbolIndex &&
|
|
2267
|
+
patternIndexes?.every((pattern, patternIndex) => pattern === ir.patternIndexes?.[patternIndex]) !== false
|
|
2268
|
+
) {
|
|
2269
|
+
return ir;
|
|
2270
|
+
}
|
|
2271
|
+
return { ...ir, props: props ?? ir.props, index, symbolIndex, patternIndexes };
|
|
2272
|
+
}
|
|
2273
|
+
case "refine": {
|
|
2274
|
+
const base = normalizeIR(ir.base);
|
|
2275
|
+
return base === ir.base && ir.base.k !== "alias" ? ir : { ...ir, base };
|
|
2276
|
+
}
|
|
2277
|
+
case "morph": {
|
|
2278
|
+
const input = normalizeIR(ir.input);
|
|
2279
|
+
const out = ir.out === undefined ? undefined : normalizeIR(ir.out);
|
|
2280
|
+
return input === ir.input && out === ir.out && ir.input.k !== "alias" && ir.out?.k !== "alias"
|
|
2281
|
+
? ir
|
|
2282
|
+
: { ...ir, input, out };
|
|
2283
|
+
}
|
|
2284
|
+
case "alias":
|
|
2285
|
+
return ir;
|
|
2286
|
+
default:
|
|
2287
|
+
return ir;
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
|
|
940
2291
|
function domainOf(ir: IR): string | undefined {
|
|
941
2292
|
switch (ir.k) {
|
|
942
2293
|
case "null":
|
|
@@ -1111,18 +2462,241 @@ function withLengthBound(ir: IR, side: "min" | "max", bound: number): IR {
|
|
|
1111
2462
|
}
|
|
1112
2463
|
throw new OmpTypeError(`cannot apply length bound to ${ir.k}`);
|
|
1113
2464
|
}
|
|
2465
|
+
|
|
1114
2466
|
function withNumericBound(ir: IR, side: "min" | "max", bound: number, exclusive = false): IR {
|
|
2467
|
+
if (!Number.isFinite(bound)) throw new OmpTypeError("numeric bound must be finite");
|
|
1115
2468
|
if (ir.k === "number") {
|
|
1116
2469
|
return side === "min" ? { ...ir, min: bound, xmin: exclusive } : { ...ir, max: bound, xmax: exclusive };
|
|
1117
2470
|
}
|
|
2471
|
+
if (ir.k === "union") {
|
|
2472
|
+
return { ...ir, members: ir.members.map(member => withNumericBound(member, side, bound, exclusive)) };
|
|
2473
|
+
}
|
|
1118
2474
|
throw new OmpTypeError(`cannot apply numeric bound to ${ir.k}`);
|
|
1119
2475
|
}
|
|
2476
|
+
interface GenericParameter {
|
|
2477
|
+
readonly name: string;
|
|
2478
|
+
readonly constraintDef?: unknown;
|
|
2479
|
+
}
|
|
1120
2480
|
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
2481
|
+
interface GenericMeta {
|
|
2482
|
+
readonly parameters: readonly GenericParameter[];
|
|
2483
|
+
instantiateIR(arguments_: readonly IR[]): IR;
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
const GENERIC_META = Symbol("omptype.generic");
|
|
2487
|
+
|
|
2488
|
+
/** Callable runtime generic returned by `type("<t>", def)` and `type.generic(...)`. */
|
|
2489
|
+
export type Generic = (...arguments_: readonly unknown[]) => BaseType;
|
|
2490
|
+
|
|
2491
|
+
interface RuntimeGeneric extends Generic {
|
|
2492
|
+
readonly [GENERIC_META]: GenericMeta;
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
/** Schema arguments passed to a callback-bodied runtime generic. */
|
|
2496
|
+
export interface GenericArguments {
|
|
2497
|
+
readonly [name: string]: BaseType;
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
export interface GenericBuilder {
|
|
2501
|
+
(definition: (arguments_: GenericArguments) => unknown, hkt?: unknown): Generic;
|
|
2502
|
+
(definition: unknown, hkt?: unknown): Generic;
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
function validateGenericParameters(parameters: readonly GenericParameter[]): void {
|
|
2506
|
+
const names = new Set<string>();
|
|
2507
|
+
for (const parameter of parameters) {
|
|
2508
|
+
if (!/^[A-Za-z_$]\w*$/.test(parameter.name)) {
|
|
2509
|
+
throw new OmpTypeError(`invalid generic parameter "${parameter.name}"`);
|
|
2510
|
+
}
|
|
2511
|
+
if (names.has(parameter.name)) throw new OmpTypeError(`duplicate generic parameter "${parameter.name}"`);
|
|
2512
|
+
names.add(parameter.name);
|
|
2513
|
+
}
|
|
2514
|
+
if (parameters.length === 0) throw new OmpTypeError("generic declarations require at least one parameter");
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
function parseGenericParameters(source: string): GenericParameter[] {
|
|
2518
|
+
const trimmed = source.trim();
|
|
2519
|
+
const body = trimmed.startsWith("<") && trimmed.endsWith(">") ? trimmed.slice(1, -1) : trimmed;
|
|
2520
|
+
const parts: string[] = [];
|
|
2521
|
+
let start = 0;
|
|
2522
|
+
let depth = 0;
|
|
2523
|
+
let quote = "";
|
|
2524
|
+
for (let index = 0; index < body.length; index++) {
|
|
2525
|
+
const char = body[index];
|
|
2526
|
+
if (quote !== "") {
|
|
2527
|
+
if (char === quote && body[index - 1] !== "\\") quote = "";
|
|
2528
|
+
continue;
|
|
2529
|
+
}
|
|
2530
|
+
if (char === "'" || char === '"' || char === "`") quote = char;
|
|
2531
|
+
else if (char === "<" || char === "(" || char === "[") depth++;
|
|
2532
|
+
else if (char === ">" || char === ")" || char === "]") depth = Math.max(0, depth - 1);
|
|
2533
|
+
else if (char === "," && depth === 0) {
|
|
2534
|
+
parts.push(body.slice(start, index).trim());
|
|
2535
|
+
start = index + 1;
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
parts.push(body.slice(start).trim());
|
|
2539
|
+
const parameters = parts.map(part => {
|
|
2540
|
+
const constrained = part.match(/^([A-Za-z_$]\w*)\s+extends\s+(.+)$/s);
|
|
2541
|
+
return constrained === null ? { name: part } : { name: constrained[1], constraintDef: constrained[2].trim() };
|
|
2542
|
+
});
|
|
2543
|
+
validateGenericParameters(parameters);
|
|
2544
|
+
return parameters;
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
function parseGenericDeclaration(source: string): { name: string; parameters: GenericParameter[] } | undefined {
|
|
2548
|
+
const match = source.trim().match(/^([A-Za-z_$]\w*)\s*(<.*>)$/s);
|
|
2549
|
+
if (match === null) return undefined;
|
|
2550
|
+
return { name: match[1], parameters: parseGenericParameters(match[2]) };
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2553
|
+
function isRuntimeGeneric(value: unknown): value is RuntimeGeneric {
|
|
2554
|
+
return typeof value === "function" && GENERIC_META in value;
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
function genericResolver(
|
|
2558
|
+
parameters: readonly GenericParameter[],
|
|
2559
|
+
arguments_: readonly IR[],
|
|
2560
|
+
outer?: AliasResolver,
|
|
2561
|
+
): AliasResolver {
|
|
2562
|
+
const byName = new Map<string, IR>();
|
|
2563
|
+
for (let index = 0; index < parameters.length; index++) byName.set(parameters[index].name, arguments_[index]);
|
|
2564
|
+
const resolve = ((name: string) => byName.get(name) ?? outer?.(name)) as AliasResolver;
|
|
2565
|
+
resolve.hasGeneric = outer?.hasGeneric;
|
|
2566
|
+
resolve.generic = outer?.generic;
|
|
2567
|
+
return resolve;
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
function parseGenericArgument(definition: unknown, outer?: AliasResolver): IR {
|
|
2571
|
+
if (outer === undefined) {
|
|
2572
|
+
try {
|
|
2573
|
+
return parseDef(definition);
|
|
2574
|
+
} catch (error) {
|
|
2575
|
+
if (!(error instanceof OmpTypeError) || !error.message.includes('unknown keyword "this"')) throw error;
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
let root: IR | undefined;
|
|
2579
|
+
const self: IR = {
|
|
2580
|
+
k: "alias",
|
|
2581
|
+
name: "this",
|
|
2582
|
+
resolve: () => {
|
|
2583
|
+
if (root === undefined || root === self) throw new OmpTypeError('"this" cannot be used as a root definition');
|
|
2584
|
+
return root;
|
|
2585
|
+
},
|
|
2586
|
+
};
|
|
2587
|
+
const resolve = ((name: string) => (name === "this" ? self : outer?.(name))) as AliasResolver;
|
|
2588
|
+
if (outer !== undefined) {
|
|
2589
|
+
resolve.hasGeneric = outer.hasGeneric;
|
|
2590
|
+
resolve.generic = outer.generic;
|
|
2591
|
+
} else {
|
|
2592
|
+
// The retry only exists to serve "this"; parses of this-free member
|
|
2593
|
+
// strings inside the definition may still share the string cache.
|
|
2594
|
+
markThisOnlyResolver(resolve);
|
|
2595
|
+
}
|
|
2596
|
+
root = parseDef(definition, resolve);
|
|
2597
|
+
if (root === self) throw new OmpTypeError('"this" cannot be used as a root definition');
|
|
2598
|
+
return root;
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
function genericBodyIR(
|
|
2602
|
+
parameters: readonly GenericParameter[],
|
|
2603
|
+
definition: unknown,
|
|
2604
|
+
arguments_: readonly IR[],
|
|
2605
|
+
outer?: AliasResolver,
|
|
2606
|
+
): IR {
|
|
2607
|
+
const resolve = genericResolver(parameters, arguments_, outer);
|
|
2608
|
+
const body =
|
|
2609
|
+
typeof definition === "function" && !(IR_BRAND in definition)
|
|
2610
|
+
? definition(
|
|
2611
|
+
Object.fromEntries(
|
|
2612
|
+
parameters.map((parameter, index) => [
|
|
2613
|
+
parameter.name,
|
|
2614
|
+
makeType(arguments_[index], EMPTY_STEPS, EMPTY_META) as unknown as BaseType,
|
|
2615
|
+
]),
|
|
2616
|
+
) as unknown as GenericArguments,
|
|
2617
|
+
)
|
|
2618
|
+
: definition;
|
|
2619
|
+
return parseDef(body, resolve);
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2622
|
+
function createRuntimeGeneric(
|
|
2623
|
+
parameters: readonly GenericParameter[],
|
|
2624
|
+
definition: unknown,
|
|
2625
|
+
outer?: AliasResolver,
|
|
2626
|
+
validateBody = true,
|
|
2627
|
+
): RuntimeGeneric {
|
|
2628
|
+
const constraintResolve = ((name: string) => outer?.(name)) as AliasResolver;
|
|
2629
|
+
constraintResolve.hasGeneric = outer?.hasGeneric;
|
|
2630
|
+
constraintResolve.generic = outer?.generic;
|
|
2631
|
+
validateGenericParameters(parameters);
|
|
2632
|
+
const placeholders = parameters.map(parameter =>
|
|
2633
|
+
parameter.constraintDef === undefined
|
|
2634
|
+
? ({ k: "unknown" } as IR)
|
|
2635
|
+
: parseDef(parameter.constraintDef, constraintResolve),
|
|
2636
|
+
);
|
|
2637
|
+
if (validateBody) genericBodyIR(parameters, definition, placeholders, outer);
|
|
2638
|
+
const meta: GenericMeta = {
|
|
2639
|
+
parameters,
|
|
2640
|
+
instantiateIR(arguments_) {
|
|
2641
|
+
if (arguments_.length !== parameters.length) {
|
|
2642
|
+
throw new OmpTypeError(`generic expects ${parameters.length} arguments (received ${arguments_.length})`);
|
|
2643
|
+
}
|
|
2644
|
+
for (let index = 0; index < parameters.length; index++) {
|
|
2645
|
+
const parameter = parameters[index];
|
|
2646
|
+
if (parameter.constraintDef === undefined) continue;
|
|
2647
|
+
const constraint = parseDef(parameter.constraintDef, constraintResolve);
|
|
2648
|
+
if (!isSubtype(arguments_[index], constraint)) {
|
|
2649
|
+
throw new OmpTypeError(`${parameter.name} must be assignable to its constraint`);
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
return genericBodyIR(parameters, definition, arguments_, outer);
|
|
2653
|
+
},
|
|
2654
|
+
};
|
|
2655
|
+
const generic = Object.assign(
|
|
2656
|
+
(...arguments_: readonly unknown[]) =>
|
|
2657
|
+
makeType(
|
|
2658
|
+
meta.instantiateIR(arguments_.map(argument => parseGenericArgument(argument, outer))),
|
|
2659
|
+
EMPTY_STEPS,
|
|
2660
|
+
EMPTY_META,
|
|
2661
|
+
),
|
|
2662
|
+
{ [GENERIC_META]: meta },
|
|
2663
|
+
);
|
|
2664
|
+
Object.defineProperty(generic, GENERIC_META, { value: meta });
|
|
2665
|
+
return generic;
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
export function type<const definition>(parameters: `<${string}>`, definition: definition): Generic;
|
|
2669
|
+
export function type<const def>(def: def): FluentType<InferDef<def>, InferDefIn<def>>;
|
|
2670
|
+
export function type<input, output>(
|
|
2671
|
+
def: SchemaInference<input> | string,
|
|
2672
|
+
operator: "=>",
|
|
2673
|
+
morph: (data: input, ctx: NarrowContext) => output,
|
|
2674
|
+
): FluentType<Exclude<output, OmpErrors>, input>;
|
|
2675
|
+
export function type<const input, const output>(
|
|
2676
|
+
def: input,
|
|
2677
|
+
operator: "|>",
|
|
2678
|
+
out: output,
|
|
2679
|
+
): FluentType<InferDef<output>, InferDefIn<input>>;
|
|
2680
|
+
export function type<const expression extends readonly unknown[]>(
|
|
2681
|
+
...definition: expression
|
|
2682
|
+
): FluentType<InferDef<expression>, InferDefIn<expression>>;
|
|
2683
|
+
export function type(first?: unknown): FluentType<unknown> | Generic {
|
|
2684
|
+
// biome-ignore lint/complexity/noArguments: Avoid allocating a rest array for the dominant single-definition call.
|
|
2685
|
+
const count = arguments.length;
|
|
2686
|
+
if (count === 2 && typeof first === "string" && first.trimStart().startsWith("<")) {
|
|
2687
|
+
// biome-ignore lint/complexity/noArguments: The generic path reads its second positional argument without a rest array.
|
|
2688
|
+
return createRuntimeGeneric(parseGenericParameters(first), arguments[1]);
|
|
2689
|
+
}
|
|
2690
|
+
let definition: unknown = first;
|
|
2691
|
+
if (count !== 1) {
|
|
2692
|
+
const expression: unknown[] = new Array(count);
|
|
2693
|
+
for (let index = 0; index < count; index++) {
|
|
2694
|
+
// biome-ignore lint/complexity/noArguments: Only multi-part expressions pay to materialize an argument array.
|
|
2695
|
+
expression[index] = arguments[index];
|
|
2696
|
+
}
|
|
2697
|
+
definition = expression;
|
|
2698
|
+
}
|
|
2699
|
+
return makeType<unknown>(parseGenericArgument(definition), EMPTY_STEPS, EMPTY_META);
|
|
1126
2700
|
}
|
|
1127
2701
|
|
|
1128
2702
|
/** String keyword with a parser that morphs validated text to another output. */
|
|
@@ -1233,9 +2807,487 @@ function preformattedKeyword(name: string): PreformattedKeyword {
|
|
|
1233
2807
|
});
|
|
1234
2808
|
}
|
|
1235
2809
|
|
|
1236
|
-
|
|
2810
|
+
type MatchDefault<input = unknown, output = unknown> =
|
|
2811
|
+
| "assert"
|
|
2812
|
+
| "never"
|
|
2813
|
+
| "reject"
|
|
2814
|
+
| ((input: input, ...args: readonly unknown[]) => output);
|
|
2815
|
+
|
|
2816
|
+
type MatchCaseOutput<cases> = {
|
|
2817
|
+
[key in keyof cases]: cases[key] extends (...args: never[]) => infer output ? output : never;
|
|
2818
|
+
}[keyof cases];
|
|
2819
|
+
|
|
2820
|
+
/** A finalized matcher. Like a schema, it returns structured errors unless finalized with `"assert"`. */
|
|
2821
|
+
export type Matcher<input = unknown, output = unknown> = FluentType<output, input> &
|
|
2822
|
+
(<const value extends input>(value: value, ...args: readonly unknown[]) => output | OmpErrors);
|
|
2823
|
+
|
|
2824
|
+
/** Fluent first-match parser exposed as `match` and `type.match`. */
|
|
2825
|
+
export interface MatchParser<input = unknown, output = never> {
|
|
2826
|
+
<const cases extends Record<PropertyKey, unknown>>(
|
|
2827
|
+
cases: cases,
|
|
2828
|
+
): MatchParser<input, output | MatchCaseOutput<cases>> | Matcher<input, output | MatchCaseOutput<cases>>;
|
|
2829
|
+
case<const definition, result>(
|
|
2830
|
+
definition: definition,
|
|
2831
|
+
resolver: (value: InferDef<definition>, ...args: readonly unknown[]) => result,
|
|
2832
|
+
): MatchParser<input, output | result>;
|
|
2833
|
+
match<const cases extends Record<PropertyKey, unknown>>(
|
|
2834
|
+
cases: cases,
|
|
2835
|
+
): MatchParser<input, output | MatchCaseOutput<cases>> | Matcher<input, output | MatchCaseOutput<cases>>;
|
|
2836
|
+
default<const result>(fallback: MatchDefault<input, result>): Matcher<input, output | result>;
|
|
2837
|
+
at<const key extends PropertyKey>(key: key): MatchParser<input, output>;
|
|
2838
|
+
at<const key extends PropertyKey, cases extends Record<PropertyKey, unknown>>(
|
|
2839
|
+
key: key,
|
|
2840
|
+
cases: cases,
|
|
2841
|
+
): MatchParser<input, output | MatchCaseOutput<cases>> | Matcher<input, output | MatchCaseOutput<cases>>;
|
|
2842
|
+
strings<const cases extends Record<PropertyKey, unknown>>(
|
|
2843
|
+
cases: cases,
|
|
2844
|
+
): MatchParser<input, output | MatchCaseOutput<cases>> | Matcher<input, output | MatchCaseOutput<cases>>;
|
|
2845
|
+
in<narrowed>(): MatchParser<narrowed, output>;
|
|
2846
|
+
in<const definition>(definition: definition): MatchParser<InferDef<definition>, output>;
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2849
|
+
interface MatchBranch {
|
|
2850
|
+
readonly definition: unknown;
|
|
2851
|
+
readonly schema: BaseType;
|
|
2852
|
+
readonly resolve: (input: unknown, ...args: readonly unknown[]) => unknown;
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
interface MatchState {
|
|
2856
|
+
readonly parse: (definition: unknown) => BaseType;
|
|
2857
|
+
readonly branches: readonly MatchBranch[];
|
|
2858
|
+
readonly input?: BaseType;
|
|
2859
|
+
readonly key?: PropertyKey;
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
function caseResolver(value: unknown): (input: unknown, ...args: readonly unknown[]) => unknown {
|
|
1237
2863
|
if (typeof value !== "function") throw new OmpTypeError("match case values must be functions");
|
|
1238
|
-
return input => Reflect.apply(value, undefined, [input]);
|
|
2864
|
+
return (input, ...args) => Reflect.apply(value, undefined, [input, ...args]);
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2867
|
+
function unionIR(branches: readonly MatchBranch[]): IR {
|
|
2868
|
+
const members = branches.map(branch => branch.schema.ir);
|
|
2869
|
+
if (members.length === 0) return { k: "never" };
|
|
2870
|
+
if (members.length === 1) return members[0];
|
|
2871
|
+
return { k: "union", members };
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2874
|
+
function publicMatcher<input, output>(
|
|
2875
|
+
state: MatchState,
|
|
2876
|
+
fallback: MatchDefault<input, output>,
|
|
2877
|
+
): Matcher<input, output> {
|
|
2878
|
+
const fallbackResolver = typeof fallback === "function" ? caseResolver(fallback) : undefined;
|
|
2879
|
+
const casesIR = unionIR(state.branches);
|
|
2880
|
+
let casesSchema: BaseType;
|
|
2881
|
+
if (state.key === undefined || state.branches.length === 0) {
|
|
2882
|
+
casesSchema =
|
|
2883
|
+
state.key === undefined
|
|
2884
|
+
? (makeType(casesIR, EMPTY_STEPS, EMPTY_META) as unknown as BaseType)
|
|
2885
|
+
: state.parse({ [state.key]: "never" });
|
|
2886
|
+
} else {
|
|
2887
|
+
const first = state.branches[0].schema.ir;
|
|
2888
|
+
if (first.k !== "object") throw new OmpTypeError("match.at cases must define object schemas");
|
|
2889
|
+
const propertyKey = String(state.key);
|
|
2890
|
+
const values: IR[] = [];
|
|
2891
|
+
for (const branch of state.branches) {
|
|
2892
|
+
if (branch.schema.ir.k !== "object") throw new OmpTypeError("match.at cases must define object schemas");
|
|
2893
|
+
const property = branch.schema.ir.props.find(candidate => candidate.key === propertyKey);
|
|
2894
|
+
if (property === undefined) throw new OmpTypeError(`match.at case is missing ${propertyKey}`);
|
|
2895
|
+
values.push(property.val);
|
|
2896
|
+
}
|
|
2897
|
+
const value = values.length === 1 ? values[0] : { k: "union" as const, members: values };
|
|
2898
|
+
casesSchema = makeType(
|
|
2899
|
+
{
|
|
2900
|
+
...first,
|
|
2901
|
+
props: first.props.map(property => (property.key === propertyKey ? { ...property, val: value } : property)),
|
|
2902
|
+
},
|
|
2903
|
+
EMPTY_STEPS,
|
|
2904
|
+
EMPTY_META,
|
|
2905
|
+
) as unknown as BaseType;
|
|
2906
|
+
}
|
|
2907
|
+
const execute = (input: unknown, args: readonly unknown[]): unknown => {
|
|
2908
|
+
let matchedInput = input;
|
|
2909
|
+
if (state.input !== undefined) {
|
|
2910
|
+
const validated = state.input.run(input);
|
|
2911
|
+
if (validated instanceof OmpErrors) return validated;
|
|
2912
|
+
matchedInput = validated;
|
|
2913
|
+
}
|
|
2914
|
+
for (const branch of state.branches) {
|
|
2915
|
+
const matched = branch.schema.run(matchedInput);
|
|
2916
|
+
if (!(matched instanceof OmpErrors)) return branch.resolve(matched, ...args);
|
|
2917
|
+
}
|
|
2918
|
+
if (fallbackResolver !== undefined) return fallbackResolver(matchedInput, ...args);
|
|
2919
|
+
return casesSchema.run(matchedInput);
|
|
2920
|
+
};
|
|
2921
|
+
const schema = makeType({ k: "unknown" }, [{ kind: "pipe", fn: input => execute(input, []) }], EMPTY_META);
|
|
2922
|
+
const callable = ((value: unknown, ...args: readonly unknown[]): unknown => {
|
|
2923
|
+
const result = execute(value, args);
|
|
2924
|
+
if ((fallback === "assert" || fallback === "never") && result instanceof OmpErrors) {
|
|
2925
|
+
throw new TraversalError(result);
|
|
2926
|
+
}
|
|
2927
|
+
return result;
|
|
2928
|
+
}) as unknown as InternalType;
|
|
2929
|
+
Object.assign(callable, schema);
|
|
2930
|
+
Object.setPrototypeOf(callable, typeMethods);
|
|
2931
|
+
return callable as unknown as Matcher<input, output>;
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
function addMatchCases(state: MatchState, cases: Record<PropertyKey, unknown>): MatchParser {
|
|
2935
|
+
const branches = [...state.branches];
|
|
2936
|
+
let fallback: MatchDefault | undefined;
|
|
2937
|
+
for (const rawDefinition of Reflect.ownKeys(cases)) {
|
|
2938
|
+
const value = Reflect.get(cases, rawDefinition);
|
|
2939
|
+
if (rawDefinition === "default") {
|
|
2940
|
+
if (value !== "assert" && value !== "never" && value !== "reject" && typeof value !== "function") {
|
|
2941
|
+
throw new OmpTypeError('match default must be "assert", "never", "reject" or a function');
|
|
2942
|
+
}
|
|
2943
|
+
fallback = value as MatchDefault;
|
|
2944
|
+
continue;
|
|
2945
|
+
}
|
|
2946
|
+
const definition = typeof rawDefinition === "symbol" ? rawDefinition : String(rawDefinition);
|
|
2947
|
+
const caseDefinition = state.key === undefined ? definition : { [state.key]: definition };
|
|
2948
|
+
branches.push({
|
|
2949
|
+
definition,
|
|
2950
|
+
schema: state.parse(caseDefinition),
|
|
2951
|
+
resolve: caseResolver(value),
|
|
2952
|
+
});
|
|
2953
|
+
}
|
|
2954
|
+
const next = { ...state, branches };
|
|
2955
|
+
return fallback === undefined ? createMatchParser(next) : (publicMatcher(next, fallback) as unknown as MatchParser);
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
function createMatchParser(state: MatchState): MatchParser {
|
|
2959
|
+
const parser = ((cases: Record<PropertyKey, unknown>) => addMatchCases(state, cases)) as MatchParser;
|
|
2960
|
+
parser.case = (definition, resolver) => {
|
|
2961
|
+
const caseDefinition = state.key === undefined ? definition : { [state.key]: definition };
|
|
2962
|
+
return createMatchParser({
|
|
2963
|
+
...state,
|
|
2964
|
+
branches: [
|
|
2965
|
+
...state.branches,
|
|
2966
|
+
{
|
|
2967
|
+
definition,
|
|
2968
|
+
schema: state.parse(caseDefinition),
|
|
2969
|
+
resolve: caseResolver(resolver),
|
|
2970
|
+
},
|
|
2971
|
+
],
|
|
2972
|
+
});
|
|
2973
|
+
};
|
|
2974
|
+
parser.match = cases => addMatchCases(state, cases);
|
|
2975
|
+
parser.default = fallback => publicMatcher(state, fallback);
|
|
2976
|
+
function at<const key extends PropertyKey>(key: key): MatchParser<unknown, never>;
|
|
2977
|
+
function at<const key extends PropertyKey, cases extends Record<PropertyKey, unknown>>(
|
|
2978
|
+
key: key,
|
|
2979
|
+
cases: cases,
|
|
2980
|
+
): MatchParser<unknown, MatchCaseOutput<cases>> | Matcher<unknown, MatchCaseOutput<cases>>;
|
|
2981
|
+
function at(key: PropertyKey, cases?: Record<PropertyKey, unknown>): unknown {
|
|
2982
|
+
if (state.key !== undefined) throw new OmpTypeError("match.at may only be specified once");
|
|
2983
|
+
const next = createMatchParser({ ...state, key });
|
|
2984
|
+
return cases === undefined ? next : next.match(cases);
|
|
2985
|
+
}
|
|
2986
|
+
parser.at = at;
|
|
2987
|
+
parser.strings = cases => {
|
|
2988
|
+
if (state.key === undefined) throw new OmpTypeError("match.strings requires match.at(key)");
|
|
2989
|
+
const definitions: Record<PropertyKey, unknown> = {};
|
|
2990
|
+
for (const key of Reflect.ownKeys(cases)) {
|
|
2991
|
+
definitions[key === "default" ? key : JSON.stringify(String(key))] = Reflect.get(cases, key);
|
|
2992
|
+
}
|
|
2993
|
+
return addMatchCases(state, definitions);
|
|
2994
|
+
};
|
|
2995
|
+
parser.in = (...args: unknown[]) => {
|
|
2996
|
+
if (state.input !== undefined) throw new OmpTypeError("match.in may only be specified once");
|
|
2997
|
+
return createMatchParser({
|
|
2998
|
+
...state,
|
|
2999
|
+
...(args.length === 0 ? {} : { input: state.parse(args[0]) }),
|
|
3000
|
+
});
|
|
3001
|
+
};
|
|
3002
|
+
return parser;
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
/** Build a fluent first-match dispatcher from schema definitions. */
|
|
3006
|
+
const matchBuilder: MatchParser = createMatchParser({
|
|
3007
|
+
parse: definition => type.raw(definition),
|
|
3008
|
+
branches: [],
|
|
3009
|
+
});
|
|
3010
|
+
|
|
3011
|
+
export { matchBuilder as match };
|
|
3012
|
+
|
|
3013
|
+
function fnExpression(ir: IR): string {
|
|
3014
|
+
switch (ir.k) {
|
|
3015
|
+
case "unknown":
|
|
3016
|
+
case "null":
|
|
3017
|
+
case "undefined":
|
|
3018
|
+
case "boolean":
|
|
3019
|
+
case "bigint":
|
|
3020
|
+
case "symbol":
|
|
3021
|
+
case "never":
|
|
3022
|
+
case "string":
|
|
3023
|
+
case "number":
|
|
3024
|
+
return ir.k;
|
|
3025
|
+
case "anyobject":
|
|
3026
|
+
return "object";
|
|
3027
|
+
case "lit":
|
|
3028
|
+
return typeof ir.v === "string" ? JSON.stringify(ir.v) : String(ir.v);
|
|
3029
|
+
case "union":
|
|
3030
|
+
return ir.members.map(fnExpression).join(" | ");
|
|
3031
|
+
case "intersection":
|
|
3032
|
+
return ir.members.map(fnExpression).join(" & ");
|
|
3033
|
+
case "array": {
|
|
3034
|
+
const element = fnExpression(ir.el);
|
|
3035
|
+
return ir.el.k === "unknown" ? "Array" : `${element.includes(" | ") ? `(${element})` : element}[]`;
|
|
3036
|
+
}
|
|
3037
|
+
case "tuple": {
|
|
3038
|
+
const elements = ir.prefix.map(item => {
|
|
3039
|
+
const expression = fnExpression(item.val);
|
|
3040
|
+
if (item.hasDefault) return `${expression} = ${String(item.def)}`;
|
|
3041
|
+
return item.opt ? `${expression}?` : expression;
|
|
3042
|
+
});
|
|
3043
|
+
if (ir.variadic !== undefined) elements.push(`...${fnExpression({ k: "array", el: ir.variadic })}`);
|
|
3044
|
+
elements.push(...ir.postfix.map(fnExpression));
|
|
3045
|
+
return `[${elements.join(", ")}]`;
|
|
3046
|
+
}
|
|
3047
|
+
case "object":
|
|
3048
|
+
return `{ ${ir.props
|
|
3049
|
+
.map(property => `${String(property.key)}${property.opt ? "?" : ""}: ${fnExpression(property.val)}`)
|
|
3050
|
+
.join(", ")} }`;
|
|
3051
|
+
case "refine":
|
|
3052
|
+
return fnExpression(ir.base);
|
|
3053
|
+
case "morph":
|
|
3054
|
+
return `(In: ${fnExpression(ir.input)}) => To<${fnExpression(ir.out ?? { k: "unknown" })}>`;
|
|
3055
|
+
case "instance":
|
|
3056
|
+
return ir.ctor.name || "object";
|
|
3057
|
+
case "alias":
|
|
3058
|
+
return fnExpression(ir.resolve());
|
|
3059
|
+
case "sub": {
|
|
3060
|
+
const schema = ir.schema;
|
|
3061
|
+
if (!schema.hasSteps) return fnExpression(schema.ir);
|
|
3062
|
+
return `(In: ${fnExpression(schema.ir)}) => To<${fnExpression(schema.stepOut ?? { k: "unknown" })}>`;
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3067
|
+
function normalizeFnParameter(definition: unknown): unknown {
|
|
3068
|
+
if (typeof definition !== "string") return definition;
|
|
3069
|
+
const optional = definition.match(/^(.*[^\s])\?$/);
|
|
3070
|
+
if (optional) return [optional[1], "?"];
|
|
3071
|
+
const defaulted = definition.match(/^(.*?)\s*=\s*(.+)$/);
|
|
3072
|
+
if (defaulted) {
|
|
3073
|
+
const source = defaulted[2];
|
|
3074
|
+
const value =
|
|
3075
|
+
source === "true"
|
|
3076
|
+
? true
|
|
3077
|
+
: source === "false"
|
|
3078
|
+
? false
|
|
3079
|
+
: source === "null"
|
|
3080
|
+
? null
|
|
3081
|
+
: Number.isNaN(Number(source))
|
|
3082
|
+
? source
|
|
3083
|
+
: Number(source);
|
|
3084
|
+
return [defaulted[1], "=", value];
|
|
3085
|
+
}
|
|
3086
|
+
return definition;
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
function makeFn(resolve?: AliasResolver): FnParser {
|
|
3090
|
+
function parser<const definitions extends readonly FnDefinition[]>(
|
|
3091
|
+
...definitions: definitions
|
|
3092
|
+
): FnFactory<definitions>;
|
|
3093
|
+
function parser(...definitions: readonly unknown[]): unknown {
|
|
3094
|
+
const marker = definitions.indexOf(":");
|
|
3095
|
+
if (marker !== -1 && (marker !== definitions.length - 2 || definitions.lastIndexOf(":") !== marker)) {
|
|
3096
|
+
throw new OmpTypeError(
|
|
3097
|
+
'":" must be followed by exactly one return type e.g:\nfn("string", ":", "number")(s => s.length)',
|
|
3098
|
+
);
|
|
3099
|
+
}
|
|
3100
|
+
const spreadIndexes: number[] = [];
|
|
3101
|
+
for (let index = 0; index < definitions.length; index++) {
|
|
3102
|
+
if (definitions[index] === "...") spreadIndexes.push(index);
|
|
3103
|
+
}
|
|
3104
|
+
if (spreadIndexes.length > 1) {
|
|
3105
|
+
const secondSpread = definitions[spreadIndexes[1] + 1];
|
|
3106
|
+
if (
|
|
3107
|
+
Array.isArray(secondSpread) &&
|
|
3108
|
+
secondSpread.some(
|
|
3109
|
+
element => typeof element === "string" && (element.endsWith("?") || element.includes("=")),
|
|
3110
|
+
)
|
|
3111
|
+
) {
|
|
3112
|
+
throw new OmpTypeError("An optional element may not follow a variadic element");
|
|
3113
|
+
}
|
|
3114
|
+
throw new OmpTypeError("A tuple may have at most one variadic element");
|
|
3115
|
+
}
|
|
3116
|
+
if (spreadIndexes.length === 1 && spreadIndexes[0] + 2 < (marker === -1 ? definitions.length : marker)) {
|
|
3117
|
+
const preceding = definitions.slice(0, spreadIndexes[0]);
|
|
3118
|
+
if (
|
|
3119
|
+
preceding.some(element => typeof element === "string" && (element.endsWith("?") || /\s=\s/.test(element)))
|
|
3120
|
+
) {
|
|
3121
|
+
throw new OmpTypeError("A postfix required element cannot follow an optional or defaultable element");
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
const parameterDefinitions = (marker === -1 ? definitions : definitions.slice(0, marker)).map(
|
|
3125
|
+
normalizeFnParameter,
|
|
3126
|
+
);
|
|
3127
|
+
const params = makeType<readonly unknown[], readonly unknown[]>(parseDef(parameterDefinitions, resolve), [], {});
|
|
3128
|
+
const returns =
|
|
3129
|
+
marker === -1
|
|
3130
|
+
? makeType<unknown>({ k: "unknown" }, [], {})
|
|
3131
|
+
: makeType<unknown>(parseDef(definitions[marker + 1], resolve), [], {});
|
|
3132
|
+
const parameterExpression = fnExpression(params.ir);
|
|
3133
|
+
const returnsExpression = fnExpression(returns.ir);
|
|
3134
|
+
return (implementation: (...arguments_: readonly unknown[]) => unknown) => {
|
|
3135
|
+
if (typeof implementation !== "function") throw new OmpTypeError("type.fn requires a function implementation");
|
|
3136
|
+
const raw = (...arguments_: readonly unknown[]): unknown => {
|
|
3137
|
+
const validatedArguments = params.assert(arguments_);
|
|
3138
|
+
const result = Reflect.apply(implementation, undefined, validatedArguments);
|
|
3139
|
+
return returns.assert(result);
|
|
3140
|
+
};
|
|
3141
|
+
const typed = raw.bind(undefined) as TypedFunction<readonly unknown[], unknown>;
|
|
3142
|
+
Object.defineProperties(typed, {
|
|
3143
|
+
name: { value: `bound typed ${implementation.name}`, configurable: true },
|
|
3144
|
+
raw: { value: implementation, enumerable: true },
|
|
3145
|
+
params: { value: params, enumerable: true },
|
|
3146
|
+
returns: { value: returns, enumerable: true },
|
|
3147
|
+
expression: {
|
|
3148
|
+
value: `(${parameterExpression.slice(1, -1)}) => ${returnsExpression}`,
|
|
3149
|
+
enumerable: true,
|
|
3150
|
+
},
|
|
3151
|
+
});
|
|
3152
|
+
return typed;
|
|
3153
|
+
};
|
|
3154
|
+
}
|
|
3155
|
+
return Object.assign(parser, { raw: parser });
|
|
3156
|
+
}
|
|
3157
|
+
/** Declares a schema output type while preserving its inferred input. */
|
|
3158
|
+
export interface DeclaredParser<declared> {
|
|
3159
|
+
type<const definition>(definition: definition): FluentType<declared, InferDefIn<definition>>;
|
|
3160
|
+
}
|
|
3161
|
+
|
|
3162
|
+
/** Fix a schema's externally declared static type without changing its runtime validation. */
|
|
3163
|
+
// biome-ignore lint/complexity/noBannedTypes: empty default options object
|
|
3164
|
+
export function declare<declared, _options = {}>(): DeclaredParser<declared> {
|
|
3165
|
+
return {
|
|
3166
|
+
type: definition => type(definition) as unknown as FluentType<declared, InferDefIn<typeof definition>>,
|
|
3167
|
+
};
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3170
|
+
function isTypeValue(value: unknown): value is InternalType {
|
|
3171
|
+
return (typeof value === "function" || (typeof value === "object" && value !== null)) && IR_BRAND in value;
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
function resolveAlias(ir: IR): IR {
|
|
3175
|
+
const seen = new Set<IR>();
|
|
3176
|
+
let current = ir;
|
|
3177
|
+
while (current.k === "alias" && !seen.has(current)) {
|
|
3178
|
+
seen.add(current);
|
|
3179
|
+
current = current.resolve();
|
|
3180
|
+
}
|
|
3181
|
+
return current;
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
function sameUnionMember(left: IR, right: IR): boolean {
|
|
3185
|
+
const a = resolveAlias(left);
|
|
3186
|
+
const b = resolveAlias(right);
|
|
3187
|
+
if (a.k === "lit" && b.k === "lit") return Object.is(a.v, b.v);
|
|
3188
|
+
if (a.k !== b.k) return false;
|
|
3189
|
+
switch (a.k) {
|
|
3190
|
+
case "unknown":
|
|
3191
|
+
case "never":
|
|
3192
|
+
case "null":
|
|
3193
|
+
case "undefined":
|
|
3194
|
+
case "boolean":
|
|
3195
|
+
case "bigint":
|
|
3196
|
+
case "symbol":
|
|
3197
|
+
case "anyobject":
|
|
3198
|
+
return true;
|
|
3199
|
+
default:
|
|
3200
|
+
return false;
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
function buildOr(definitions: readonly unknown[], resolve?: AliasResolver): InternalType;
|
|
3205
|
+
function buildOr<output, input>(definitions: readonly unknown[], resolve?: AliasResolver): FluentType<output, input>;
|
|
3206
|
+
function buildOr(definitions: readonly unknown[], resolve?: AliasResolver): InternalType {
|
|
3207
|
+
const members: IR[] = [];
|
|
3208
|
+
const add = (candidate: IR): boolean => {
|
|
3209
|
+
const resolved = resolveAlias(candidate);
|
|
3210
|
+
if (resolved.k === "unknown") {
|
|
3211
|
+
members.length = 0;
|
|
3212
|
+
members.push(resolved);
|
|
3213
|
+
return false;
|
|
3214
|
+
}
|
|
3215
|
+
if (resolved.k === "never") return true;
|
|
3216
|
+
if (resolved.k === "union") {
|
|
3217
|
+
for (const member of resolved.members) {
|
|
3218
|
+
if (!add(member)) return false;
|
|
3219
|
+
}
|
|
3220
|
+
return true;
|
|
3221
|
+
}
|
|
3222
|
+
if (!members.some(member => sameUnionMember(member, candidate))) members.push(candidate);
|
|
3223
|
+
return true;
|
|
3224
|
+
};
|
|
3225
|
+
for (const definition of definitions) {
|
|
3226
|
+
if (!add(parseDef(definition, resolve))) break;
|
|
3227
|
+
}
|
|
3228
|
+
const ir: IR = members.length === 0 ? { k: "never" } : members.length === 1 ? members[0] : { k: "union", members };
|
|
3229
|
+
return makeType(ir, [], {});
|
|
3230
|
+
}
|
|
3231
|
+
|
|
3232
|
+
function buildAnd(definitions: readonly unknown[], resolve?: AliasResolver): InternalType;
|
|
3233
|
+
function buildAnd<output, input>(definitions: readonly unknown[], resolve?: AliasResolver): FluentType<output, input>;
|
|
3234
|
+
function buildAnd(definitions: readonly unknown[], resolve?: AliasResolver): InternalType {
|
|
3235
|
+
if (definitions.length === 0) return makeType({ k: "unknown" }, [], {});
|
|
3236
|
+
let ir = parseDef(definitions[0], resolve);
|
|
3237
|
+
for (let index = 1; index < definitions.length; index++) {
|
|
3238
|
+
ir = intersect(ir, parseDef(definitions[index], resolve));
|
|
3239
|
+
}
|
|
3240
|
+
return makeType(ir, [], {});
|
|
3241
|
+
}
|
|
3242
|
+
|
|
3243
|
+
function requireNaryObject(ir: IR): ObjectIR {
|
|
3244
|
+
const resolved = resolveAlias(ir);
|
|
3245
|
+
if (resolved.k !== "object") throw new OmpTypeError("merge requires an object schema");
|
|
3246
|
+
return resolved;
|
|
3247
|
+
}
|
|
3248
|
+
|
|
3249
|
+
function buildMerge(definitions: readonly unknown[], resolve?: AliasResolver): InternalType;
|
|
3250
|
+
function buildMerge<output, input>(definitions: readonly unknown[], resolve?: AliasResolver): FluentType<output, input>;
|
|
3251
|
+
function buildMerge(definitions: readonly unknown[], resolve?: AliasResolver): InternalType {
|
|
3252
|
+
if (definitions.length === 0) return makeType({ k: "anyobject" }, [], {});
|
|
3253
|
+
let object = requireNaryObject(parseDef(definitions[0], resolve));
|
|
3254
|
+
for (let index = 1; index < definitions.length; index++) {
|
|
3255
|
+
object = mergeObjects(object, requireNaryObject(parseDef(definitions[index], resolve)));
|
|
3256
|
+
}
|
|
3257
|
+
return makeType(object, [], {});
|
|
3258
|
+
}
|
|
3259
|
+
|
|
3260
|
+
function buildPipe(definitions: readonly unknown[], resolve?: AliasResolver): InternalType;
|
|
3261
|
+
function buildPipe<output, input>(definitions: readonly unknown[], resolve?: AliasResolver): FluentType<output, input>;
|
|
3262
|
+
function buildPipe(definitions: readonly unknown[], resolve?: AliasResolver): InternalType {
|
|
3263
|
+
if (definitions.length === 0) return makeType({ k: "unknown" }, [], {});
|
|
3264
|
+
const first = definitions[0];
|
|
3265
|
+
let schema =
|
|
3266
|
+
typeof first === "function" && !isTypeValue(first)
|
|
3267
|
+
? appendPipes(makeType({ k: "unknown" }, [], {}), [first], false, true)
|
|
3268
|
+
: isTypeValue(first)
|
|
3269
|
+
? first
|
|
3270
|
+
: makeType(parseDef(first, resolve), [], {});
|
|
3271
|
+
for (let index = 1; index < definitions.length; index++) {
|
|
3272
|
+
const definition = definitions[index];
|
|
3273
|
+
const pipe =
|
|
3274
|
+
typeof definition === "function" && !isTypeValue(definition)
|
|
3275
|
+
? definition
|
|
3276
|
+
: isTypeValue(definition)
|
|
3277
|
+
? definition
|
|
3278
|
+
: makeType(parseDef(definition, resolve), [], {});
|
|
3279
|
+
schema = appendPipes(schema, [pipe], false, true);
|
|
3280
|
+
}
|
|
3281
|
+
return schema;
|
|
3282
|
+
}
|
|
3283
|
+
|
|
3284
|
+
function naryStatics(resolve?: AliasResolver) {
|
|
3285
|
+
return {
|
|
3286
|
+
or: (...definitions: readonly unknown[]) => buildOr(definitions, resolve),
|
|
3287
|
+
and: (...definitions: readonly unknown[]) => buildAnd(definitions, resolve),
|
|
3288
|
+
merge: (...definitions: readonly unknown[]) => buildMerge(definitions, resolve),
|
|
3289
|
+
pipe: (...definitions: readonly unknown[]) => buildPipe(definitions, resolve),
|
|
3290
|
+
};
|
|
1239
3291
|
}
|
|
1240
3292
|
|
|
1241
3293
|
export namespace type {
|
|
@@ -1243,6 +3295,34 @@ export namespace type {
|
|
|
1243
3295
|
export const errors = OmpErrors;
|
|
1244
3296
|
export type errors = OmpErrors;
|
|
1245
3297
|
|
|
3298
|
+
/** Build a union from zero or more definitions. */
|
|
3299
|
+
export function or<const definitions extends readonly unknown[]>(
|
|
3300
|
+
...definitions: definitions
|
|
3301
|
+
): FluentType<NaryOrOutput<definitions>, NaryOrInput<definitions>> {
|
|
3302
|
+
return buildOr<NaryOrOutput<definitions>, NaryOrInput<definitions>>(definitions);
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
/** Build an intersection from zero or more definitions. */
|
|
3306
|
+
export function and<const definitions extends readonly unknown[]>(
|
|
3307
|
+
...definitions: definitions
|
|
3308
|
+
): FluentType<NaryAndOutput<definitions>, NaryAndInput<definitions>> {
|
|
3309
|
+
return buildAnd<NaryAndOutput<definitions>, NaryAndInput<definitions>>(definitions);
|
|
3310
|
+
}
|
|
3311
|
+
|
|
3312
|
+
/** Right-biased object merge over zero or more definitions. */
|
|
3313
|
+
export function merge<const definitions extends readonly unknown[]>(
|
|
3314
|
+
...definitions: definitions
|
|
3315
|
+
): FluentType<NaryMergeOutput<definitions>, NaryMergeInput<definitions>> {
|
|
3316
|
+
return buildMerge<NaryMergeOutput<definitions>, NaryMergeInput<definitions>>(definitions);
|
|
3317
|
+
}
|
|
3318
|
+
|
|
3319
|
+
/** Compose Types, definitions, and morph callbacks from left to right. */
|
|
3320
|
+
export function pipe<const definitions extends readonly unknown[]>(
|
|
3321
|
+
...definitions: definitions
|
|
3322
|
+
): FluentType<NaryPipeOutput<definitions>, NaryPipeInput<definitions>> {
|
|
3323
|
+
return buildPipe<NaryPipeOutput<definitions>, NaryPipeInput<definitions>>(definitions);
|
|
3324
|
+
}
|
|
3325
|
+
|
|
1246
3326
|
const normalize: NormalizeKeyword = Object.assign(keywordSchema<string, string>("string.normalize"), {
|
|
1247
3327
|
preformatted: keywordSchema("string.normalize.NFC.preformatted"),
|
|
1248
3328
|
NFC: preformattedKeyword("string.normalize.NFC"),
|
|
@@ -1273,29 +3353,32 @@ export namespace type {
|
|
|
1273
3353
|
});
|
|
1274
3354
|
|
|
1275
3355
|
/** String validator and its refinement/morph keyword module. */
|
|
1276
|
-
export const string: StringKeyword = Object.
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
3356
|
+
export const string: StringKeyword = Object.defineProperties(
|
|
3357
|
+
makeType<string>({ k: "string" }, [], {}),
|
|
3358
|
+
Object.getOwnPropertyDescriptors({
|
|
3359
|
+
alpha: keywordSchema("string.alpha"),
|
|
3360
|
+
alphanumeric: keywordSchema("string.alphanumeric"),
|
|
3361
|
+
base64,
|
|
3362
|
+
capitalize: preformattedKeyword("string.capitalize"),
|
|
3363
|
+
creditCard: keywordSchema("string.creditCard"),
|
|
3364
|
+
date,
|
|
3365
|
+
digits: keywordSchema("string.digits"),
|
|
3366
|
+
email: keywordSchema("string.email"),
|
|
3367
|
+
hex: keywordSchema("string.hex"),
|
|
3368
|
+
integer: parsedKeyword<number>("string.integer"),
|
|
3369
|
+
ip,
|
|
3370
|
+
json: parsedKeyword<unknown>("string.json"),
|
|
3371
|
+
lower: preformattedKeyword("string.lower"),
|
|
3372
|
+
normalize,
|
|
3373
|
+
numeric: parsedKeyword<number>("string.numeric"),
|
|
3374
|
+
regex: keywordSchema("string.regex"),
|
|
3375
|
+
semver: keywordSchema("string.semver"),
|
|
3376
|
+
trim: preformattedKeyword("string.trim"),
|
|
3377
|
+
upper: preformattedKeyword("string.upper"),
|
|
3378
|
+
url: parsedKeyword<URL>("string.url"),
|
|
3379
|
+
uuid,
|
|
3380
|
+
}),
|
|
3381
|
+
) as unknown as StringKeyword;
|
|
1299
3382
|
|
|
1300
3383
|
/** Runtime parser keyword family. */
|
|
1301
3384
|
export const parse: ParseKeyword = {
|
|
@@ -1313,6 +3396,18 @@ export namespace type {
|
|
|
1313
3396
|
integer: makeType<number>({ k: "number", int: true }, [], {}),
|
|
1314
3397
|
});
|
|
1315
3398
|
|
|
3399
|
+
/** Schema-valued key representing any non-negative integer array index. */
|
|
3400
|
+
export const arrayIndex = makeType<string>(
|
|
3401
|
+
{
|
|
3402
|
+
k: "refine",
|
|
3403
|
+
base: { k: "string" },
|
|
3404
|
+
pred: value => typeof value === "string" && /^(?:0|[1-9]\d*)$/.test(value),
|
|
3405
|
+
expected: "a non-negative integer string",
|
|
3406
|
+
},
|
|
3407
|
+
[],
|
|
3408
|
+
{},
|
|
3409
|
+
);
|
|
3410
|
+
|
|
1316
3411
|
/** Boolean validator. */
|
|
1317
3412
|
export const boolean = makeType<boolean>({ k: "boolean" }, [], {});
|
|
1318
3413
|
/** Bigint validator. */
|
|
@@ -1327,14 +3422,134 @@ export namespace type {
|
|
|
1327
3422
|
export const any = unknown;
|
|
1328
3423
|
/** Validator that rejects every value. */
|
|
1329
3424
|
export const never = makeType<never>({ k: "never" }, [], {});
|
|
3425
|
+
|
|
3426
|
+
/** ArkType's built-in keyword namespace, including invokable utility generics. */
|
|
3427
|
+
export const keywords = {
|
|
3428
|
+
number: { integer: number.integer },
|
|
3429
|
+
Map: keywordSchema<Map<unknown, unknown>>("Map"),
|
|
3430
|
+
Set: keywordSchema<Set<unknown>>("Set"),
|
|
3431
|
+
RegExp: keywordSchema<RegExp>("RegExp"),
|
|
3432
|
+
File: keywordSchema<File>("File"),
|
|
3433
|
+
Error: keywordSchema<Error>("Error"),
|
|
3434
|
+
// biome-ignore lint/complexity/noBannedTypes: built-in Function keyword
|
|
3435
|
+
Function: keywordSchema<Function>("Function"),
|
|
3436
|
+
Array: {
|
|
3437
|
+
liftFrom<const definition>(
|
|
3438
|
+
definition: definition,
|
|
3439
|
+
): FluentType<InferDef<definition>[], InferDefIn<definition> | InferDefIn<definition>[]> {
|
|
3440
|
+
const element = parseDef(definition);
|
|
3441
|
+
const array: IR = { k: "array", el: element, desc: "an object" };
|
|
3442
|
+
return makeType<InferDef<definition>[], InferDefIn<definition> | InferDefIn<definition>[]>(
|
|
3443
|
+
{
|
|
3444
|
+
k: "morph",
|
|
3445
|
+
input: { k: "union", members: [element, array] },
|
|
3446
|
+
fn: value => (globalThis.Array.isArray(value) ? value : [value]),
|
|
3447
|
+
out: array,
|
|
3448
|
+
},
|
|
3449
|
+
[],
|
|
3450
|
+
{},
|
|
3451
|
+
);
|
|
3452
|
+
},
|
|
3453
|
+
},
|
|
3454
|
+
Record<const key, const value>(
|
|
3455
|
+
key: key,
|
|
3456
|
+
value: value,
|
|
3457
|
+
): FluentType<
|
|
3458
|
+
Record<Extract<InferDef<key>, PropertyKey>, InferDef<value>>,
|
|
3459
|
+
Record<Extract<InferDefIn<key>, PropertyKey>, InferDefIn<value>>
|
|
3460
|
+
> {
|
|
3461
|
+
const keyIR = parseDef(key);
|
|
3462
|
+
if (keyIR.k !== "string" && keyIR.k !== "symbol") {
|
|
3463
|
+
throw new OmpTypeError("Record key must be assignable to string or symbol");
|
|
3464
|
+
}
|
|
3465
|
+
const valueIR = parseDef(value);
|
|
3466
|
+
const ir: IR =
|
|
3467
|
+
keyIR.k === "symbol"
|
|
3468
|
+
? { k: "object", props: [], symbolIndex: valueIR, extras: "keep" }
|
|
3469
|
+
: { k: "object", props: [], index: valueIR, extras: "keep" };
|
|
3470
|
+
return makeType<
|
|
3471
|
+
Record<Extract<InferDef<key>, PropertyKey>, InferDef<value>>,
|
|
3472
|
+
Record<Extract<InferDefIn<key>, PropertyKey>, InferDefIn<value>>
|
|
3473
|
+
>(ir, [], {});
|
|
3474
|
+
},
|
|
3475
|
+
Partial<const definition>(
|
|
3476
|
+
definition: definition,
|
|
3477
|
+
): FluentType<Partial<InferDef<definition>>, Partial<InputObject<InferDefIn<definition>>>> {
|
|
3478
|
+
return makeType<Partial<InferDef<definition>>, Partial<InputObject<InferDefIn<definition>>>>(
|
|
3479
|
+
setObjectOptionality(parseDef(definition), true, "partial"),
|
|
3480
|
+
[],
|
|
3481
|
+
{},
|
|
3482
|
+
);
|
|
3483
|
+
},
|
|
3484
|
+
Required<const definition>(
|
|
3485
|
+
definition: definition,
|
|
3486
|
+
): FluentType<Required<InferDef<definition>>, Required<InputObject<InferDefIn<definition>>>> {
|
|
3487
|
+
return makeType<Required<InferDef<definition>>, Required<InputObject<InferDefIn<definition>>>>(
|
|
3488
|
+
setObjectOptionality(parseDef(definition), false, "required"),
|
|
3489
|
+
[],
|
|
3490
|
+
{},
|
|
3491
|
+
);
|
|
3492
|
+
},
|
|
3493
|
+
Pick<const definition, const keys extends readonly PropertyKey[]>(
|
|
3494
|
+
definition: definition,
|
|
3495
|
+
...keys: keys
|
|
3496
|
+
): FluentType<
|
|
3497
|
+
Pick<InferDef<definition>, Extract<keys[number], keyof InferDef<definition>>>,
|
|
3498
|
+
Pick<InputObject<InferDefIn<definition>>, Extract<keys[number], keyof InputObject<InferDefIn<definition>>>>
|
|
3499
|
+
> {
|
|
3500
|
+
return makeType<
|
|
3501
|
+
Pick<InferDef<definition>, Extract<keys[number], keyof InferDef<definition>>>,
|
|
3502
|
+
Pick<InputObject<InferDefIn<definition>>, Extract<keys[number], keyof InputObject<InferDefIn<definition>>>>
|
|
3503
|
+
>(selectObjectProps(parseDef(definition), keys, true, "pick"), [], {});
|
|
3504
|
+
},
|
|
3505
|
+
Omit<const definition, const keys extends readonly PropertyKey[]>(
|
|
3506
|
+
definition: definition,
|
|
3507
|
+
...keys: keys
|
|
3508
|
+
): FluentType<
|
|
3509
|
+
Omit<InferDef<definition>, Extract<keys[number], keyof InferDef<definition>>>,
|
|
3510
|
+
Omit<InputObject<InferDefIn<definition>>, Extract<keys[number], keyof InputObject<InferDefIn<definition>>>>
|
|
3511
|
+
> {
|
|
3512
|
+
return makeType<
|
|
3513
|
+
Omit<InferDef<definition>, Extract<keys[number], keyof InferDef<definition>>>,
|
|
3514
|
+
Omit<InputObject<InferDefIn<definition>>, Extract<keys[number], keyof InputObject<InferDefIn<definition>>>>
|
|
3515
|
+
>(selectObjectProps(parseDef(definition), keys, false, "omit"), [], {});
|
|
3516
|
+
},
|
|
3517
|
+
Merge<const left, const right>(
|
|
3518
|
+
left: left,
|
|
3519
|
+
right: right,
|
|
3520
|
+
): FluentType<MergeTypes<InferDef<left>, InferDef<right>>, MergeTypes<InferDefIn<left>, InferDefIn<right>>> {
|
|
3521
|
+
return makeType<MergeTypes<InferDef<left>, InferDef<right>>, MergeTypes<InferDefIn<left>, InferDefIn<right>>>(
|
|
3522
|
+
mergeObjectDefinition(parseDef(left), right),
|
|
3523
|
+
[],
|
|
3524
|
+
{},
|
|
3525
|
+
);
|
|
3526
|
+
},
|
|
3527
|
+
object: {
|
|
3528
|
+
json: Object.defineProperties(keywordSchema<unknown>("object.json"), {
|
|
3529
|
+
stringify: {
|
|
3530
|
+
value: keywordSchema<string, unknown>("object.json.stringify"),
|
|
3531
|
+
enumerable: true,
|
|
3532
|
+
},
|
|
3533
|
+
}),
|
|
3534
|
+
},
|
|
3535
|
+
unknown: { any: keywordSchema<unknown>("unknown.any") },
|
|
3536
|
+
};
|
|
1330
3537
|
/** Date instance validator. */
|
|
1331
3538
|
// biome-ignore lint/suspicious/noShadowRestrictedNames: ArkType exposes this exact keyword.
|
|
1332
3539
|
export const Date = makeType<globalThis.Date>({ k: "instance", ctor: globalThis.Date, expected: "a Date" }, [], {});
|
|
1333
3540
|
|
|
1334
3541
|
/** Validate instances of `ctor`. */
|
|
1335
3542
|
export function instanceOf<const ctor extends Constructor>(ctor: ctor): FluentType<Constructed<ctor>> {
|
|
3543
|
+
if (typeof ctor !== "function" || ctor.prototype === undefined) {
|
|
3544
|
+
throw new OmpTypeError("instanceof operands must be constructors");
|
|
3545
|
+
}
|
|
1336
3546
|
const name = Reflect.get(ctor, "name");
|
|
1337
|
-
const expected =
|
|
3547
|
+
const expected =
|
|
3548
|
+
ctor.prototype === Error.prototype
|
|
3549
|
+
? "an Error"
|
|
3550
|
+
: typeof name === "string" && name.length > 0
|
|
3551
|
+
? `an instance of ${name}`
|
|
3552
|
+
: "an instance";
|
|
1338
3553
|
return makeType<Constructed<ctor>>({ k: "instance", ctor, expected }, [], {});
|
|
1339
3554
|
}
|
|
1340
3555
|
|
|
@@ -1351,28 +3566,38 @@ export namespace type {
|
|
|
1351
3566
|
return makeType<values[number]>(ir, [], {});
|
|
1352
3567
|
}
|
|
1353
3568
|
|
|
1354
|
-
/**
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
3569
|
+
/** Enumerate an enum-like object's forward values, excluding numeric reverse mappings. */
|
|
3570
|
+
// biome-ignore lint/suspicious/noShadowRestrictedNames: Object.prototype.valueOf method name API
|
|
3571
|
+
export function valueOf<const values extends Record<PropertyKey, unknown>>(
|
|
3572
|
+
values: values,
|
|
3573
|
+
): FluentType<values[keyof values]> {
|
|
3574
|
+
const members: IR[] = [];
|
|
3575
|
+
for (const key in values) {
|
|
3576
|
+
if (/^(?:0|[1-9]\d*)$/.test(key)) continue;
|
|
3577
|
+
members.push({ k: "lit", v: values[key] });
|
|
1362
3578
|
}
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
}
|
|
1367
|
-
if (fallback !== undefined) return fallback(value);
|
|
1368
|
-
throw new OmpTypeError("match requires a matching case or default");
|
|
1369
|
-
};
|
|
3579
|
+
const ir: IR =
|
|
3580
|
+
members.length === 0 ? { k: "never" } : members.length === 1 ? members[0] : { k: "union", members };
|
|
3581
|
+
return makeType<values[keyof values]>(ir, [], {});
|
|
1370
3582
|
}
|
|
3583
|
+
|
|
3584
|
+
/** Fluent first-match dispatcher, also exported as standalone `match`. */
|
|
3585
|
+
export const match: MatchParser = matchBuilder;
|
|
1371
3586
|
/** Preserve a definition's literal type while authoring reusable modules. */
|
|
1372
3587
|
export function define<const definition>(definition: definition): definition {
|
|
1373
3588
|
return definition;
|
|
1374
3589
|
}
|
|
1375
3590
|
|
|
3591
|
+
/** Build a function whose arguments and optional declared return are validated. */
|
|
3592
|
+
export const fn: FnParser = makeFn();
|
|
3593
|
+
|
|
3594
|
+
/** Fix an externally declared static type while retaining runtime validation. */
|
|
3595
|
+
// biome-ignore lint/complexity/noBannedTypes: empty default options object
|
|
3596
|
+
export const declare = <declared, _options = {}>(): DeclaredParser<declared> =>
|
|
3597
|
+
({
|
|
3598
|
+
type: definition => type(definition) as unknown as FluentType<declared, InferDefIn<typeof definition>>,
|
|
3599
|
+
}) as DeclaredParser<declared>;
|
|
3600
|
+
|
|
1376
3601
|
/** Build a lazy named scope from aliases and recursive definitions. */
|
|
1377
3602
|
export function scope(aliases: Record<string, unknown>, options?: ScopeOptions): TypeScope {
|
|
1378
3603
|
return buildScope(aliases, options);
|
|
@@ -1381,30 +3606,32 @@ export namespace type {
|
|
|
1381
3606
|
/** Compile a named schema module whose definitions may reference each other. */
|
|
1382
3607
|
export function module<const definitions extends Record<string, unknown>>(
|
|
1383
3608
|
definitions: definitions,
|
|
3609
|
+
options?: ScopeOptions,
|
|
1384
3610
|
): { [name in keyof definitions]: Type<InferDef<definitions[name]>, InferDefIn<definitions[name]>> } {
|
|
1385
|
-
return scope(definitions).export() as unknown as {
|
|
3611
|
+
return scope(definitions, options).export() as unknown as {
|
|
1386
3612
|
[name in keyof definitions]: Type<InferDef<definitions[name]>, InferDefIn<definitions[name]>>;
|
|
1387
3613
|
};
|
|
1388
3614
|
}
|
|
1389
3615
|
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
if (
|
|
1402
|
-
|
|
3616
|
+
type GenericParameterSpec = string | readonly [name: string, constraint: unknown];
|
|
3617
|
+
|
|
3618
|
+
/** Build a generic directly from an angle-bracket declaration. */
|
|
3619
|
+
export function generic<const definition>(parameters: `<${string}>`, definition: definition): Generic;
|
|
3620
|
+
/** Build a curried generic from named, optionally constrained parameters. */
|
|
3621
|
+
export function generic(...parameters: readonly GenericParameterSpec[]): GenericBuilder;
|
|
3622
|
+
export function generic(...arguments_: readonly (GenericParameterSpec | unknown)[]): Generic | GenericBuilder {
|
|
3623
|
+
if (arguments_.length === 2 && typeof arguments_[0] === "string" && arguments_[0].trimStart().startsWith("<")) {
|
|
3624
|
+
return createRuntimeGeneric(parseGenericParameters(arguments_[0]), arguments_[1]);
|
|
3625
|
+
}
|
|
3626
|
+
const parameters: GenericParameter[] = arguments_.map(parameter => {
|
|
3627
|
+
if (typeof parameter === "string") return { name: parameter.trim() };
|
|
3628
|
+
if (Array.isArray(parameter) && typeof parameter[0] === "string") {
|
|
3629
|
+
return { name: parameter[0].trim(), constraintDef: parameter[1] };
|
|
1403
3630
|
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
3631
|
+
throw new OmpTypeError("generic parameters must be names or [name, constraint] pairs");
|
|
3632
|
+
});
|
|
3633
|
+
validateGenericParameters(parameters);
|
|
3634
|
+
return (definition: unknown) => createRuntimeGeneric(parameters, definition);
|
|
1408
3635
|
}
|
|
1409
3636
|
|
|
1410
3637
|
/** Untyped builder for runtime-assembled definitions. */
|
|
@@ -1424,17 +3651,31 @@ Object.assign(type, {
|
|
|
1424
3651
|
|
|
1425
3652
|
export interface ScopeOptions {
|
|
1426
3653
|
jitless?: boolean;
|
|
3654
|
+
clone?: false | ((input: unknown) => unknown);
|
|
3655
|
+
divisor?: SchemaConfig;
|
|
1427
3656
|
}
|
|
1428
3657
|
|
|
1429
3658
|
/** Callable builder bound to one alias scope. */
|
|
1430
|
-
export type ScopedBuilder = <const definition>(
|
|
3659
|
+
export type ScopedBuilder = (<const definition>(
|
|
1431
3660
|
definition: definition,
|
|
1432
|
-
) => FluentType<InferDef<definition>, InferDefIn<definition
|
|
3661
|
+
) => FluentType<InferDef<definition>, InferDefIn<definition>>) &
|
|
3662
|
+
typeof type;
|
|
3663
|
+
|
|
3664
|
+
const MODULE_SCOPE = Symbol("omptype.moduleScope");
|
|
3665
|
+
|
|
3666
|
+
interface RuntimeModule extends Record<string, BaseType | RuntimeModule> {
|
|
3667
|
+
readonly [MODULE_SCOPE]: TypeScope;
|
|
3668
|
+
}
|
|
1433
3669
|
|
|
1434
|
-
/** Named schema scope with
|
|
3670
|
+
/** Named schema scope with scoped parsing, imports, and bound module exports. */
|
|
1435
3671
|
export interface TypeScope {
|
|
1436
3672
|
readonly type: ScopedBuilder;
|
|
1437
|
-
|
|
3673
|
+
readonly match: MatchParser;
|
|
3674
|
+
readonly json: Record<string, unknown>;
|
|
3675
|
+
define<const definition>(definition: definition): definition;
|
|
3676
|
+
resolve(name: string): BaseType;
|
|
3677
|
+
import(...names: readonly string[]): Record<string, unknown>;
|
|
3678
|
+
export(...names: readonly string[]): Record<string, BaseType>;
|
|
1438
3679
|
}
|
|
1439
3680
|
|
|
1440
3681
|
/** Build a scope whose aliases resolve lazily, including recursive cycles. */
|
|
@@ -1442,11 +3683,92 @@ export function scope(aliases: Record<string, unknown>, options?: ScopeOptions):
|
|
|
1442
3683
|
return buildScope(aliases, options);
|
|
1443
3684
|
}
|
|
1444
3685
|
|
|
1445
|
-
|
|
3686
|
+
export namespace scope {
|
|
3687
|
+
/** Preserve a scope definition's literal shape without constructing it. */
|
|
3688
|
+
export function define<const aliases>(definitions: aliases): aliases {
|
|
3689
|
+
return definitions;
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
|
|
3693
|
+
interface ScopeAlias {
|
|
3694
|
+
readonly name: string;
|
|
3695
|
+
readonly sourceName: string;
|
|
3696
|
+
readonly private: boolean;
|
|
3697
|
+
readonly genericParameters?: readonly GenericParameter[];
|
|
3698
|
+
definition: unknown;
|
|
3699
|
+
generic?: RuntimeGeneric;
|
|
3700
|
+
materialized: boolean;
|
|
3701
|
+
}
|
|
3702
|
+
|
|
3703
|
+
function isRuntimeModule(value: unknown): value is RuntimeModule {
|
|
3704
|
+
return typeof value === "object" && value !== null && MODULE_SCOPE in value;
|
|
3705
|
+
}
|
|
3706
|
+
|
|
3707
|
+
function buildScope(aliases: Record<string, unknown>, options?: ScopeOptions): TypeScope {
|
|
3708
|
+
const scopeMeta: TypeMeta = options?.clone === undefined ? EMPTY_META : { clone: options.clone };
|
|
3709
|
+
const withScopeConfig = (ir: IR): IR =>
|
|
3710
|
+
options?.divisor === undefined ? ir : configureSelected(ir, options.divisor, { kind: "divisor" });
|
|
3711
|
+
const entries = new Map<string, ScopeAlias>();
|
|
3712
|
+
for (const sourceName in aliases) {
|
|
3713
|
+
const isPrivate = sourceName.startsWith("#");
|
|
3714
|
+
const visibleName = isPrivate ? sourceName.slice(1) : sourceName;
|
|
3715
|
+
const declaration = parseGenericDeclaration(visibleName);
|
|
3716
|
+
const external = isRuntimeGeneric(aliases[sourceName]) ? aliases[sourceName] : undefined;
|
|
3717
|
+
const name = declaration?.name ?? visibleName;
|
|
3718
|
+
if (entries.has(name)) throw new OmpTypeError(`alias "${name}" is declared as both public and private`);
|
|
3719
|
+
entries.set(name, {
|
|
3720
|
+
name,
|
|
3721
|
+
sourceName,
|
|
3722
|
+
private: isPrivate,
|
|
3723
|
+
genericParameters: declaration?.parameters ?? external?.[GENERIC_META].parameters,
|
|
3724
|
+
definition: aliases[sourceName],
|
|
3725
|
+
generic: external,
|
|
3726
|
+
materialized: false,
|
|
3727
|
+
});
|
|
3728
|
+
}
|
|
3729
|
+
|
|
1446
3730
|
const references = new Map<string, IR>();
|
|
1447
3731
|
const targets = new Map<string, IR>();
|
|
1448
|
-
|
|
1449
|
-
|
|
3732
|
+
let scopeValue: TypeScope;
|
|
3733
|
+
|
|
3734
|
+
const materialize = (entry: ScopeAlias): unknown => {
|
|
3735
|
+
if (entry.materialized) return entry.definition;
|
|
3736
|
+
entry.materialized = true;
|
|
3737
|
+
if (
|
|
3738
|
+
entry.genericParameters === undefined &&
|
|
3739
|
+
typeof entry.definition === "function" &&
|
|
3740
|
+
!(IR_BRAND in entry.definition)
|
|
3741
|
+
) {
|
|
3742
|
+
entry.definition = Reflect.apply(entry.definition, undefined, []);
|
|
3743
|
+
}
|
|
3744
|
+
return entry.definition;
|
|
3745
|
+
};
|
|
3746
|
+
|
|
3747
|
+
const moduleSchema = (module: RuntimeModule, parts: readonly string[]): EmbeddableSchema | undefined => {
|
|
3748
|
+
let current: BaseType | RuntimeModule = module;
|
|
3749
|
+
for (const part of parts) {
|
|
3750
|
+
if (!isRuntimeModule(current)) return undefined;
|
|
3751
|
+
const next: BaseType | RuntimeModule | undefined = current[part];
|
|
3752
|
+
if (next === undefined) return undefined;
|
|
3753
|
+
current = next;
|
|
3754
|
+
}
|
|
3755
|
+
if (isRuntimeModule(current)) {
|
|
3756
|
+
const root = current.root;
|
|
3757
|
+
return root !== undefined && !isRuntimeModule(root) ? root : undefined;
|
|
3758
|
+
}
|
|
3759
|
+
return current;
|
|
3760
|
+
};
|
|
3761
|
+
|
|
3762
|
+
const resolve = ((path: string): IR | undefined => {
|
|
3763
|
+
const [name, ...parts] = path.split(".");
|
|
3764
|
+
const entry = entries.get(name);
|
|
3765
|
+
if (entry === undefined || entry.genericParameters !== undefined) return undefined;
|
|
3766
|
+
const definition = materialize(entry);
|
|
3767
|
+
if (isRuntimeModule(definition)) {
|
|
3768
|
+
const schema = moduleSchema(definition, parts);
|
|
3769
|
+
return schema === undefined ? undefined : embed(schema);
|
|
3770
|
+
}
|
|
3771
|
+
if (parts.length !== 0) return undefined;
|
|
1450
3772
|
const existing = references.get(name);
|
|
1451
3773
|
if (existing !== undefined) return existing;
|
|
1452
3774
|
const reference: IR = {
|
|
@@ -1455,30 +3777,197 @@ function buildScope(aliases: Record<string, unknown>, _options?: ScopeOptions):
|
|
|
1455
3777
|
resolve: () => {
|
|
1456
3778
|
const target = targets.get(name);
|
|
1457
3779
|
if (target !== undefined) return target;
|
|
1458
|
-
const parsed = parseDef(
|
|
3780
|
+
const parsed = parseDef(definition, resolve);
|
|
1459
3781
|
targets.set(name, parsed);
|
|
1460
3782
|
return parsed;
|
|
1461
3783
|
},
|
|
1462
3784
|
};
|
|
1463
3785
|
references.set(name, reference);
|
|
1464
3786
|
return reference;
|
|
3787
|
+
}) as AliasResolver;
|
|
3788
|
+
|
|
3789
|
+
const genericFor = (entry: ScopeAlias): RuntimeGeneric => {
|
|
3790
|
+
if (entry.generic !== undefined) return entry.generic;
|
|
3791
|
+
const parameters = entry.genericParameters;
|
|
3792
|
+
if (parameters === undefined) throw new OmpTypeError(`alias "${entry.name}" is not generic`);
|
|
3793
|
+
entry.generic = createRuntimeGeneric(parameters, entry.definition, resolve, false);
|
|
3794
|
+
return entry.generic;
|
|
1465
3795
|
};
|
|
1466
|
-
const
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
3796
|
+
const genericInstantiations = new Map<string, IR>();
|
|
3797
|
+
resolve.hasGeneric = name => entries.get(name)?.genericParameters !== undefined;
|
|
3798
|
+
resolve.generic = (name, arguments_) => {
|
|
3799
|
+
const entry = entries.get(name);
|
|
3800
|
+
if (entry === undefined || entry.genericParameters === undefined) return undefined;
|
|
3801
|
+
const key = `${name}<${arguments_.map(expectedOf).join(",")}>`;
|
|
3802
|
+
const existing = genericInstantiations.get(key);
|
|
3803
|
+
if (existing !== undefined) return existing;
|
|
3804
|
+
let target: IR | undefined;
|
|
3805
|
+
const reference: IR = {
|
|
3806
|
+
k: "alias",
|
|
3807
|
+
name: key,
|
|
3808
|
+
resolve: () => {
|
|
3809
|
+
target ??= genericFor(entry)[GENERIC_META].instantiateIR(arguments_);
|
|
3810
|
+
return target;
|
|
3811
|
+
},
|
|
3812
|
+
};
|
|
3813
|
+
genericInstantiations.set(key, reference);
|
|
3814
|
+
target = genericFor(entry)[GENERIC_META].instantiateIR(arguments_);
|
|
3815
|
+
return target;
|
|
3816
|
+
};
|
|
3817
|
+
|
|
3818
|
+
const bind = (schema: InternalType): InternalType => {
|
|
3819
|
+
Reflect.set(schema, "$", scopeValue);
|
|
3820
|
+
Reflect.set(schema, "resolver", resolve);
|
|
3821
|
+
return schema;
|
|
3822
|
+
};
|
|
3823
|
+
const parseScoped = (definition: unknown): InternalType =>
|
|
3824
|
+
bind(makeType(withScopeConfig(parseDef(definition, resolve)), EMPTY_STEPS, scopeMeta));
|
|
3825
|
+
const scopedMatch = createMatchParser({
|
|
3826
|
+
parse: definition => parseScoped(definition) as unknown as BaseType,
|
|
3827
|
+
branches: [],
|
|
3828
|
+
});
|
|
3829
|
+
const scoped = Object.assign((definition: unknown) => parseScoped(definition), type, {
|
|
3830
|
+
fn: makeFn(resolve),
|
|
3831
|
+
match: scopedMatch,
|
|
3832
|
+
...naryStatics(resolve),
|
|
3833
|
+
}) as unknown as ScopedBuilder;
|
|
3834
|
+
|
|
3835
|
+
const targetFor = (name: string): IR => {
|
|
3836
|
+
const resolved = resolve(name);
|
|
3837
|
+
if (resolved === undefined) throw new OmpTypeError(`unknown alias "${name}"`);
|
|
3838
|
+
return resolved.k === "alias" ? resolved.resolve() : resolved;
|
|
3839
|
+
};
|
|
3840
|
+
const schemaFor = (name: string): BaseType =>
|
|
3841
|
+
bind(makeType(withScopeConfig(targetFor(name)), EMPTY_STEPS, scopeMeta)) as unknown as BaseType;
|
|
3842
|
+
|
|
3843
|
+
const bindModule = (names: readonly string[]): RuntimeModule => {
|
|
3844
|
+
const module = {} as RuntimeModule;
|
|
3845
|
+
Object.defineProperty(module, MODULE_SCOPE, { value: scopeValue });
|
|
3846
|
+
for (const name of names) {
|
|
3847
|
+
const entry = entries.get(name);
|
|
3848
|
+
if (entry === undefined) continue;
|
|
3849
|
+
if (entry.genericParameters !== undefined) {
|
|
3850
|
+
module[name] = genericFor(entry) as unknown as BaseType;
|
|
3851
|
+
continue;
|
|
3852
|
+
}
|
|
3853
|
+
const definition = materialize(entry);
|
|
3854
|
+
module[name] = isRuntimeModule(definition) ? definition : schemaFor(name);
|
|
3855
|
+
}
|
|
3856
|
+
return module;
|
|
3857
|
+
};
|
|
3858
|
+
|
|
3859
|
+
scopeValue = {
|
|
1471
3860
|
type: scoped,
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
3861
|
+
match: scopedMatch,
|
|
3862
|
+
define<const definition>(definition: definition): definition {
|
|
3863
|
+
return definition;
|
|
3864
|
+
},
|
|
3865
|
+
resolve(name: string) {
|
|
3866
|
+
return schemaFor(name);
|
|
3867
|
+
},
|
|
3868
|
+
import(...names: readonly string[]) {
|
|
3869
|
+
const selected =
|
|
3870
|
+
names.length === 0
|
|
3871
|
+
? [...entries.values()].filter(entry => !entry.private)
|
|
3872
|
+
: names.map(name => {
|
|
3873
|
+
const entry = entries.get(name);
|
|
3874
|
+
if (entry === undefined) throw new OmpTypeError(`unknown alias "${name}"`);
|
|
3875
|
+
return entry;
|
|
3876
|
+
});
|
|
3877
|
+
const imported: Record<string, unknown> = {};
|
|
3878
|
+
for (const entry of selected) {
|
|
3879
|
+
imported[`#${entry.sourceName.startsWith("#") ? entry.sourceName.slice(1) : entry.sourceName}`] =
|
|
3880
|
+
entry.genericParameters === undefined ? schemaFor(entry.name) : genericFor(entry);
|
|
3881
|
+
}
|
|
3882
|
+
return imported;
|
|
3883
|
+
},
|
|
3884
|
+
export(...names: readonly string[]) {
|
|
3885
|
+
const selected =
|
|
3886
|
+
names.length === 0 ? [...entries.values()].filter(entry => !entry.private).map(entry => entry.name) : names;
|
|
3887
|
+
// Export is the eager boundary: malformed aliases and bad thunks fail here,
|
|
3888
|
+
// while recursive references inside valid definitions stay lazy.
|
|
3889
|
+
for (const entry of entries.values()) {
|
|
3890
|
+
if (entry.genericParameters !== undefined) genericFor(entry);
|
|
3891
|
+
else if (!isRuntimeModule(materialize(entry))) targetFor(entry.name);
|
|
3892
|
+
}
|
|
3893
|
+
return bindModule(selected) as unknown as Record<string, BaseType>;
|
|
3894
|
+
},
|
|
3895
|
+
get json() {
|
|
3896
|
+
const json: Record<string, unknown> = {};
|
|
3897
|
+
const add = (prefix: string, module: RuntimeModule): void => {
|
|
3898
|
+
for (const name of Object.keys(module)) {
|
|
3899
|
+
const value = module[name];
|
|
3900
|
+
const path = prefix === "" ? name : `${prefix}.${name}`;
|
|
3901
|
+
if (isRuntimeModule(value)) add(path, value);
|
|
3902
|
+
else json[path] = Reflect.get(value, "json");
|
|
3903
|
+
}
|
|
3904
|
+
};
|
|
3905
|
+
add("", bindModule([...entries.values()].filter(entry => !entry.private).map(entry => entry.name)));
|
|
3906
|
+
return json;
|
|
1476
3907
|
},
|
|
1477
3908
|
};
|
|
3909
|
+
return scopeValue;
|
|
1478
3910
|
}
|
|
1479
3911
|
|
|
1480
3912
|
/** A schema whose output type is not statically known (`type.raw` results). */
|
|
1481
3913
|
export type BaseType = FluentType<unknown, unknown>;
|
|
1482
3914
|
|
|
3915
|
+
/**
|
|
3916
|
+
* Minimal structural constraint matching any omptype schema.
|
|
3917
|
+
*
|
|
3918
|
+
* `FluentType`'s recursive fluent surface makes `T extends FluentType<...>`
|
|
3919
|
+
* checks descend until TypeScript's depth limiter reports spurious
|
|
3920
|
+
* incompatibilities, and its invariant input parameter rejects concrete
|
|
3921
|
+
* schemas outright. This interface exposes only the schema marker plus the
|
|
3922
|
+
* members generic helpers commonly need — method syntax keeps parameter
|
|
3923
|
+
* positions bivariant, and returns recurse shallowly through `AnyType`.
|
|
3924
|
+
*/
|
|
3925
|
+
export interface AnyType {
|
|
3926
|
+
(data: unknown): unknown;
|
|
3927
|
+
readonly [IR_BRAND]: true;
|
|
3928
|
+
readonly ir: IR;
|
|
3929
|
+
readonly infer: unknown;
|
|
3930
|
+
readonly inferIn: unknown;
|
|
3931
|
+
readonly hasDefault: boolean;
|
|
3932
|
+
readonly description?: string;
|
|
3933
|
+
run(data: unknown): unknown;
|
|
3934
|
+
assert(data: unknown): unknown;
|
|
3935
|
+
allows(data: unknown): boolean;
|
|
3936
|
+
toJsonSchema(options?: ToJsonSchemaOptions): Record<string, unknown>;
|
|
3937
|
+
describe(description: string): AnyType;
|
|
3938
|
+
default(value: unknown): AnyType;
|
|
3939
|
+
or(def: Def): AnyType;
|
|
3940
|
+
and(def: Def): AnyType;
|
|
3941
|
+
pipe(fn: (data: never, ctx: NarrowContext) => unknown): AnyType;
|
|
3942
|
+
narrow(fn: (data: never, ctx: NarrowContext) => unknown): AnyType;
|
|
3943
|
+
array(): AnyType;
|
|
3944
|
+
}
|
|
3945
|
+
declare const submoduleType: unique symbol;
|
|
3946
|
+
|
|
3947
|
+
type BoundAlias<value> = value extends Submodule<infer aliases> ? Submodule<aliases> : FluentType<value>;
|
|
3948
|
+
|
|
3949
|
+
/** Exported aliases from a scope, each bound to that scope's resolver. */
|
|
3950
|
+
export type Module<aliases extends Record<string, unknown>> = {
|
|
3951
|
+
readonly [name in keyof aliases]: BoundAlias<aliases[name]>;
|
|
3952
|
+
};
|
|
3953
|
+
|
|
3954
|
+
/** A module nested under an alias rather than directly parseable as a schema. */
|
|
3955
|
+
export type Submodule<aliases extends Record<string, unknown>> = {
|
|
3956
|
+
readonly [submoduleType]?: aliases;
|
|
3957
|
+
} & {
|
|
3958
|
+
readonly [name in keyof aliases]: BoundAlias<aliases[name]>;
|
|
3959
|
+
};
|
|
3960
|
+
|
|
3961
|
+
/** A selected module export whose schemas retain access to the full scope. */
|
|
3962
|
+
export type BoundModule<
|
|
3963
|
+
exports extends Record<string, unknown>,
|
|
3964
|
+
_allAliases extends Record<string, unknown> = exports,
|
|
3965
|
+
> = Module<exports>;
|
|
3966
|
+
|
|
3967
|
+
/** Type-level view of a named scope. */
|
|
3968
|
+
export type Scope<aliases extends Record<string, unknown>> = TypeScope & {
|
|
3969
|
+
readonly t: aliases;
|
|
3970
|
+
};
|
|
3971
|
+
|
|
1483
3972
|
/** `hasMorph` re-export for diagnostics/tooling. */
|
|
1484
3973
|
export { hasMorph };
|