@buckeyestudio/omptype 18.4.13

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