@oh-my-pi/omptype 17.2.6 → 17.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1078 @@
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.js";
14
+ import { OmpErrors, OmpTypeError, TraversalError } from "./errors.js";
15
+ import { walk } from "./interp.js";
16
+ import { embed, expectedOf, hasMorph, IR_BRAND, keyOf, parseDef, } from "./ir.js";
17
+ import { irToJsonSchema } from "./json-schema.js";
18
+ import { keywordIR, patternIR } from "./keywords.js";
19
+ /** Runtime constructor-like value used by ArkType-compatible `instanceof Type` checks. */
20
+ export const Type = Object.defineProperty(function Type() { }, Symbol.hasInstance, {
21
+ value: (value) => (typeof value === "function" || (typeof value === "object" && value !== null)) && IR_BRAND in value,
22
+ });
23
+ class Ctx {
24
+ expectation;
25
+ mustBe(expectation) {
26
+ this.expectation = expectation;
27
+ return false;
28
+ }
29
+ reject(problem) {
30
+ this.expectation = problem;
31
+ return false;
32
+ }
33
+ }
34
+ const kBase = Symbol("omptype.base");
35
+ const kSteps = Symbol("omptype.steps");
36
+ const EMPTY_STEPS = [];
37
+ const EMPTY_META = {};
38
+ /** Calls before the JIT compiles a schema (first two run the interpreter). */
39
+ const JIT_THRESHOLD = 3;
40
+ function metaOf(schema) {
41
+ return {
42
+ description: schema.description,
43
+ defaultValue: schema.defaultValue,
44
+ hasDefault: schema.hasDefault,
45
+ errorConfig: schema.errorConfig,
46
+ };
47
+ }
48
+ const typeMethods = {
49
+ describe(description) {
50
+ return makeType({ ...this.ir, desc: description }, this[kSteps], { ...metaOf(this), description });
51
+ },
52
+ configure(config) {
53
+ const errorConfig = {
54
+ ...this.errorConfig,
55
+ ...(config.expected === undefined ? {} : { expected: config.expected }),
56
+ ...(config.actual === undefined ? {} : { actual: config.actual }),
57
+ ...(config.problem === undefined ? {} : { problem: config.problem }),
58
+ ...(config.message === undefined ? {} : { message: config.message }),
59
+ };
60
+ const meta = {
61
+ ...metaOf(this),
62
+ errorConfig,
63
+ ...(config.description === undefined ? {} : { description: config.description }),
64
+ };
65
+ return config.description === undefined
66
+ ? makeType(this.ir, this[kSteps], meta)
67
+ : makeType({ ...this.ir, desc: config.description }, this[kSteps], meta);
68
+ },
69
+ default(value) {
70
+ return makeType(this.ir, this[kSteps], { ...metaOf(this), defaultValue: value, hasDefault: true });
71
+ },
72
+ optional() {
73
+ return [this, "?"];
74
+ },
75
+ or(def) {
76
+ const other = parseDef(def);
77
+ const a = embed(this);
78
+ const members = [...(a.k === "union" ? a.members : [a]), ...(other.k === "union" ? other.members : [other])];
79
+ return makeType({ k: "union", members }, [], {});
80
+ },
81
+ equals(def) {
82
+ return irEquals(embed(this), parseDef(def));
83
+ },
84
+ ifEquals(def) {
85
+ return irEquals(embed(this), parseDef(def)) ? this : undefined;
86
+ },
87
+ extends(def) {
88
+ return isSubtype(embed(this), parseDef(def));
89
+ },
90
+ overlaps(def) {
91
+ try {
92
+ intersect(embed(this), parseDef(def));
93
+ return true;
94
+ }
95
+ catch (error) {
96
+ if (error instanceof OmpTypeError)
97
+ return false;
98
+ throw error;
99
+ }
100
+ },
101
+ distribute(mapper) {
102
+ const branches = this.ir.k === "union" ? this.ir.members : [embed(this)];
103
+ const members = branches.map(branch => embed(mapper(makeType(branch, [], {}))));
104
+ return makeType(members.length === 1 ? members[0] : { k: "union", members }, [], {});
105
+ },
106
+ select(kind) {
107
+ return selectNodes(this.ir, kind);
108
+ },
109
+ and(def) {
110
+ return makeType(intersect(embed(this), parseDef(def)), [], {});
111
+ },
112
+ array() {
113
+ return makeType({ k: "array", el: embed(this) }, [], {});
114
+ },
115
+ atLeastLength(bound) {
116
+ return makeType(withLengthBound(this.ir, "min", bound), this[kSteps], metaOf(this));
117
+ },
118
+ atMostLength(bound) {
119
+ return makeType(withLengthBound(this.ir, "max", bound), this[kSteps], metaOf(this));
120
+ },
121
+ moreThanLength(bound) {
122
+ return makeType(withLengthBound(this.ir, "min", bound + 1), this[kSteps], metaOf(this));
123
+ },
124
+ lessThanLength(bound) {
125
+ return makeType(withLengthBound(this.ir, "max", bound - 1), this[kSteps], metaOf(this));
126
+ },
127
+ exactlyLength(bound) {
128
+ const bounded = withLengthBound(withLengthBound(this.ir, "min", bound), "max", bound);
129
+ return makeType(bounded, this[kSteps], metaOf(this));
130
+ },
131
+ atLeast(bound) {
132
+ return makeType(withNumericBound(this.ir, "min", bound), this[kSteps], metaOf(this));
133
+ },
134
+ atMost(bound) {
135
+ return makeType(withNumericBound(this.ir, "max", bound), this[kSteps], metaOf(this));
136
+ },
137
+ moreThan(bound) {
138
+ return makeType(withNumericBound(this.ir, "min", bound, true), this[kSteps], metaOf(this));
139
+ },
140
+ lessThan(bound) {
141
+ return makeType(withNumericBound(this.ir, "max", bound, true), this[kSteps], metaOf(this));
142
+ },
143
+ divisibleBy(divisor) {
144
+ if (this.ir.k !== "number")
145
+ throw new OmpTypeError(`cannot apply divisibility to ${this.ir.k}`);
146
+ if (!Number.isFinite(divisor) || divisor === 0)
147
+ throw new OmpTypeError("divisor must be non-zero");
148
+ return makeType({ ...this.ir, divisor }, this[kSteps], metaOf(this));
149
+ },
150
+ positive() {
151
+ return makeType(withNumericBound(this.ir, "min", 0, true), this[kSteps], metaOf(this));
152
+ },
153
+ negative() {
154
+ return makeType(withNumericBound(this.ir, "max", 0, true), this[kSteps], metaOf(this));
155
+ },
156
+ nonNegative() {
157
+ return makeType(withNumericBound(this.ir, "min", 0), this[kSteps], metaOf(this));
158
+ },
159
+ nonPositive() {
160
+ return makeType(withNumericBound(this.ir, "max", 0), this[kSteps], metaOf(this));
161
+ },
162
+ matching(pattern) {
163
+ return makeType(intersect(this.ir, patternIR(pattern)), this[kSteps], metaOf(this));
164
+ },
165
+ atOrAfter(bound) {
166
+ return dateRefinement(this, bound, "at or after", value => value >= bound.valueOf());
167
+ },
168
+ atOrBefore(bound) {
169
+ return dateRefinement(this, bound, "at or before", value => value <= bound.valueOf());
170
+ },
171
+ laterThan(bound) {
172
+ return dateRefinement(this, bound, "later than", value => value > bound.valueOf());
173
+ },
174
+ earlierThan(bound) {
175
+ return dateRefinement(this, bound, "earlier than", value => value < bound.valueOf());
176
+ },
177
+ pipe(fn) {
178
+ return makeType(this.ir, [...this[kSteps], { kind: "pipe", fn }], metaOf(this));
179
+ },
180
+ to(def) {
181
+ const output = makeType(parseDef(def), [], {});
182
+ return makeType(this.ir, [
183
+ ...this[kSteps],
184
+ {
185
+ kind: "pipe",
186
+ fn: value => output(value),
187
+ },
188
+ ], metaOf(this));
189
+ },
190
+ filter(fn) {
191
+ return makeType(this.ir, [{ kind: "filter", fn }, ...this[kSteps]], metaOf(this));
192
+ },
193
+ narrow(fn) {
194
+ return makeType(this.ir, [...this[kSteps], { kind: "narrow", fn }], metaOf(this));
195
+ },
196
+ brand() {
197
+ return makeType(this.ir, this[kSteps], metaOf(this));
198
+ },
199
+ as() {
200
+ return makeType(this.ir, this[kSteps], metaOf(this));
201
+ },
202
+ keyof() {
203
+ return makeType(keyOf(this.ir), [], {});
204
+ },
205
+ get(key) {
206
+ const object = requireObject(this.ir, "get");
207
+ const prop = object.props.find(candidate => candidate.key === String(key));
208
+ if (!prop)
209
+ throw new OmpTypeError(`key ${String(key)} is not declared`);
210
+ const ir = prop.opt ? { k: "union", members: [prop.val, { k: "undefined" }] } : prop.val;
211
+ return makeType(ir, [], {});
212
+ },
213
+ pick(...keys) {
214
+ const object = requireObject(this.ir, "pick");
215
+ const selected = new Set(keys.map(String));
216
+ return makeType({ ...object, props: object.props.filter(prop => selected.has(prop.key)) }, [], {});
217
+ },
218
+ omit(...keys) {
219
+ const object = requireObject(this.ir, "omit");
220
+ const omitted = new Set(keys.map(String));
221
+ return makeType({ ...object, props: object.props.filter(prop => !omitted.has(prop.key)) }, [], {});
222
+ },
223
+ partial() {
224
+ const object = requireObject(this.ir, "partial");
225
+ return makeType({ ...object, props: object.props.map(prop => ({ ...prop, opt: true })) }, [], {});
226
+ },
227
+ required() {
228
+ const object = requireObject(this.ir, "required");
229
+ return makeType({ ...object, props: object.props.map(prop => ({ ...prop, opt: false })) }, [], {});
230
+ },
231
+ map(mapper) {
232
+ const object = requireObject(this.ir, "map");
233
+ const props = object.props.flatMap(prop => {
234
+ const mapped = mapper(propertyFromIR(prop));
235
+ return (Array.isArray(mapped) ? mapped : [mapped]).map(propertyToIR);
236
+ });
237
+ return makeType({ ...object, props }, [], {});
238
+ },
239
+ merge(def) {
240
+ return makeType(mergeObjects(requireObject(this.ir, "merge"), requireObject(parseDef(def), "merge")), [], {});
241
+ },
242
+ extract(def) {
243
+ const other = parseDef(def);
244
+ return makeType({
245
+ k: "refine",
246
+ base: embed(this),
247
+ pred: value => !(walk(other, value) instanceof OmpErrors),
248
+ expected: "a value included by the extracted type",
249
+ }, [], {});
250
+ },
251
+ exclude(def) {
252
+ const other = parseDef(def);
253
+ return makeType({
254
+ k: "refine",
255
+ base: embed(this),
256
+ pred: value => walk(other, value) instanceof OmpErrors,
257
+ expected: "a value not excluded by the type",
258
+ }, [], {});
259
+ },
260
+ onUndeclaredKey(behavior) {
261
+ const object = requireObject(this.ir, "onUndeclaredKey");
262
+ return makeType({ ...object, extras: behavior === "ignore" ? "keep" : behavior }, this[kSteps], metaOf(this));
263
+ },
264
+ onDeepUndeclaredKey(behavior) {
265
+ return makeType(withDeepExtras(this.ir, behavior === "ignore" ? "keep" : behavior), this[kSteps], metaOf(this));
266
+ },
267
+ allows(data) {
268
+ const steps = this[kSteps];
269
+ let needsPredicates = false;
270
+ for (const step of steps) {
271
+ if (step.kind !== "pipe") {
272
+ needsPredicates = true;
273
+ break;
274
+ }
275
+ }
276
+ if (!needsPredicates) {
277
+ const allows = compileAllows(this.ir);
278
+ // Shadow the shared dispatcher once this schema has its specialized check.
279
+ this.allows = allows;
280
+ return allows(data);
281
+ }
282
+ for (const step of steps) {
283
+ if (step.kind === "filter" && !step.fn(data, new Ctx()))
284
+ return false;
285
+ }
286
+ const out = this[kBase](data);
287
+ if (out instanceof OmpErrors)
288
+ return false;
289
+ for (const step of steps) {
290
+ if (step.kind === "narrow" && !step.fn(out, new Ctx()))
291
+ return false;
292
+ }
293
+ return true;
294
+ },
295
+ assert(data) {
296
+ const out = this.run(data);
297
+ if (out instanceof OmpErrors)
298
+ throw new TraversalError(out);
299
+ return out;
300
+ },
301
+ from(data) {
302
+ const out = this.run(data);
303
+ if (out instanceof OmpErrors)
304
+ throw new TraversalError(out);
305
+ return out;
306
+ },
307
+ toJsonSchema(options) {
308
+ const description = options?.description ?? this.description;
309
+ return irToJsonSchema(this.ir, options === undefined && description === undefined
310
+ ? undefined
311
+ : { ...options, ...(description === undefined ? {} : { description }) });
312
+ },
313
+ };
314
+ Object.defineProperty(typeMethods, "props", {
315
+ get() {
316
+ const object = requireObject(this.ir, "props");
317
+ return object.props.map(prop => propertyFromIR(prop));
318
+ },
319
+ });
320
+ // Share the fluent surface without per-schema method allocations or copies.
321
+ // Function.prototype remains in the chain, except bind is intentionally hidden
322
+ // so generic tool wrappers recognize callable schemas rather than rebinding them.
323
+ Object.setPrototypeOf(typeMethods, Function.prototype);
324
+ Object.defineProperty(typeMethods, "bind", { value: undefined });
325
+ function makeType(ir, steps, meta) {
326
+ let calls = 0;
327
+ let impl = (data) => {
328
+ if (++calls >= JIT_THRESHOLD) {
329
+ impl = compile(ir);
330
+ return impl(data);
331
+ }
332
+ return walk(ir, data);
333
+ };
334
+ const base = meta.errorConfig === undefined
335
+ ? (data) => impl(data)
336
+ : (data) => {
337
+ const result = impl(data);
338
+ return result instanceof OmpErrors ? result.configure(meta.errorConfig ?? {}) : result;
339
+ };
340
+ const callable = steps.length === 0
341
+ ? base
342
+ : (data) => {
343
+ for (const step of steps) {
344
+ if (step.kind !== "filter")
345
+ continue;
346
+ const ctx = new Ctx();
347
+ if (!step.fn(data, ctx)) {
348
+ return OmpErrors.single([], ctx.expectation ?? "valid (input predicate failed)", data, meta.errorConfig);
349
+ }
350
+ }
351
+ let out = base(data);
352
+ if (out instanceof OmpErrors)
353
+ return out;
354
+ for (const step of steps) {
355
+ if (step.kind === "filter")
356
+ continue;
357
+ const ctx = new Ctx();
358
+ if (step.kind === "narrow") {
359
+ if (!step.fn(out, ctx)) {
360
+ return OmpErrors.single([], ctx.expectation ?? "valid (narrow predicate failed)", out, meta.errorConfig);
361
+ }
362
+ }
363
+ else {
364
+ out = step.fn(out, ctx);
365
+ if (out instanceof OmpErrors) {
366
+ return meta.errorConfig === undefined ? out : out.configure(meta.errorConfig);
367
+ }
368
+ }
369
+ }
370
+ return out;
371
+ };
372
+ const self = callable;
373
+ self[IR_BRAND] = true;
374
+ self[kBase] = base;
375
+ self[kSteps] = steps;
376
+ self.ir = ir;
377
+ self.hasSteps = steps.length > 0;
378
+ self.hasDefault = meta.hasDefault === true;
379
+ self.defaultValue = meta.defaultValue;
380
+ self.description = meta.description;
381
+ self.errorConfig = meta.errorConfig;
382
+ self.run = callable;
383
+ Object.setPrototypeOf(self, typeMethods);
384
+ return self;
385
+ }
386
+ function requireObject(ir, operation) {
387
+ if (ir.k !== "object")
388
+ throw new OmpTypeError(`${operation} requires an object schema`);
389
+ return ir;
390
+ }
391
+ function propertyFromIR(prop) {
392
+ return {
393
+ kind: prop.opt ? "optional" : "required",
394
+ key: prop.key,
395
+ value: makeType(prop.val, [], {}),
396
+ ...(prop.hasDefault ? { default: prop.def } : {}),
397
+ meta: {},
398
+ };
399
+ }
400
+ function propertyToIR(property) {
401
+ if (property.kind !== "required" && property.kind !== "optional") {
402
+ throw new OmpTypeError(`mapped property ${String(property.key)} has invalid kind`);
403
+ }
404
+ if (!(IR_BRAND in property.value)) {
405
+ throw new OmpTypeError(`mapped property ${String(property.key)} must contain a schema value`);
406
+ }
407
+ const hasDefault = Object.hasOwn(property, "default");
408
+ return {
409
+ key: String(property.key),
410
+ opt: property.kind === "optional",
411
+ val: embed(property.value),
412
+ ...(hasDefault
413
+ ? { def: property.default, defFactory: typeof property.default === "function", hasDefault: true }
414
+ : {}),
415
+ };
416
+ }
417
+ function dateRefinement(schema, bound, relation, predicate) {
418
+ if (!Number.isFinite(bound.valueOf()))
419
+ throw new OmpTypeError("date bound must be valid");
420
+ return makeType({
421
+ k: "refine",
422
+ base: schema.ir,
423
+ pred: value => value instanceof Date && predicate(value.valueOf()),
424
+ expected: `a Date ${relation} ${bound.toISOString()}`,
425
+ json: relation.includes("after") ? { minimum: bound.toISOString() } : { maximum: bound.toISOString() },
426
+ }, schema[kSteps], metaOf(schema));
427
+ }
428
+ function selectNodes(root, kind) {
429
+ const selected = [];
430
+ const seen = new Set();
431
+ const visit = (node) => {
432
+ if (seen.has(node))
433
+ return;
434
+ seen.add(node);
435
+ const nodeKind = node.k === "lit" ? "unit" : node.k;
436
+ if (kind === nodeKind || kind === node.k) {
437
+ selected.push(node.k === "lit" ? { kind: nodeKind, node, unit: node.v } : { kind: nodeKind, node });
438
+ }
439
+ switch (node.k) {
440
+ case "alias":
441
+ visit(node.resolve());
442
+ break;
443
+ case "array":
444
+ visit(node.el);
445
+ break;
446
+ case "tuple":
447
+ for (const item of node.prefix)
448
+ visit(item.val);
449
+ if (node.variadic !== undefined)
450
+ visit(node.variadic);
451
+ for (const item of node.postfix)
452
+ visit(item);
453
+ break;
454
+ case "object":
455
+ for (const prop of node.props)
456
+ visit(prop.val);
457
+ if (node.index !== undefined)
458
+ visit(node.index);
459
+ break;
460
+ case "union":
461
+ case "intersection":
462
+ for (const member of node.members)
463
+ visit(member);
464
+ break;
465
+ case "refine":
466
+ visit(node.base);
467
+ break;
468
+ case "morph":
469
+ visit(node.input);
470
+ if (node.out !== undefined)
471
+ visit(node.out);
472
+ break;
473
+ case "sub":
474
+ visit(node.schema.ir);
475
+ break;
476
+ }
477
+ };
478
+ visit(root);
479
+ return selected;
480
+ }
481
+ function mergeObjects(left, right) {
482
+ const props = [...left.props];
483
+ for (const prop of right.props) {
484
+ const index = props.findIndex(candidate => candidate.key === prop.key);
485
+ if (index < 0)
486
+ props.push(prop);
487
+ else
488
+ props[index] = prop;
489
+ }
490
+ return {
491
+ k: "object",
492
+ props,
493
+ index: right.index ?? left.index,
494
+ extras: right.extras === "keep" ? left.extras : right.extras,
495
+ };
496
+ }
497
+ function withDeepExtras(ir, extras) {
498
+ switch (ir.k) {
499
+ case "object":
500
+ return {
501
+ ...ir,
502
+ extras,
503
+ props: ir.props.map(prop => ({ ...prop, val: withDeepExtras(prop.val, extras) })),
504
+ index: ir.index === undefined ? undefined : withDeepExtras(ir.index, extras),
505
+ };
506
+ case "array":
507
+ return { ...ir, el: withDeepExtras(ir.el, extras) };
508
+ case "tuple":
509
+ return {
510
+ ...ir,
511
+ prefix: ir.prefix.map(item => ({ ...item, val: withDeepExtras(item.val, extras) })),
512
+ variadic: ir.variadic === undefined ? undefined : withDeepExtras(ir.variadic, extras),
513
+ postfix: ir.postfix.map(item => withDeepExtras(item, extras)),
514
+ };
515
+ case "union":
516
+ case "intersection":
517
+ return { ...ir, members: ir.members.map(member => withDeepExtras(member, extras)) };
518
+ case "refine":
519
+ return { ...ir, base: withDeepExtras(ir.base, extras) };
520
+ case "morph":
521
+ return {
522
+ ...ir,
523
+ input: withDeepExtras(ir.input, extras),
524
+ out: ir.out === undefined ? undefined : withDeepExtras(ir.out, extras),
525
+ };
526
+ default:
527
+ return ir;
528
+ }
529
+ }
530
+ /** Intersect two IR nodes, rejecting statically disjoint domains. */
531
+ function intersect(a, b) {
532
+ if (a.k === "alias")
533
+ return intersect(a.resolve(), b);
534
+ if (b.k === "alias")
535
+ return intersect(a, b.resolve());
536
+ if (a.k === "never" || b.k === "never")
537
+ throw new OmpTypeError("intersection with never is unsatisfiable");
538
+ if (a.k === "unknown")
539
+ return b;
540
+ if (b.k === "unknown")
541
+ return a;
542
+ if (a === b)
543
+ return a;
544
+ if (a.k === "union" || b.k === "union") {
545
+ const union = a.k === "union" ? a : b.k === "union" ? b : undefined;
546
+ if (union === undefined)
547
+ throw new OmpTypeError("union intersection invariant failed");
548
+ const branches = union.members;
549
+ const other = a.k === "union" ? b : a;
550
+ const members = [];
551
+ for (const branch of branches) {
552
+ try {
553
+ members.push(intersect(branch, other));
554
+ }
555
+ catch (error) {
556
+ if (!(error instanceof OmpTypeError))
557
+ throw error;
558
+ }
559
+ }
560
+ if (members.length === 0)
561
+ throw new OmpTypeError("intersection has no satisfiable branches");
562
+ return members.length === 1 ? members[0] : { k: "union", members };
563
+ }
564
+ if (a.k === "lit") {
565
+ if (walk(b, a.v) instanceof OmpErrors)
566
+ throw new OmpTypeError("literal is excluded by the intersection");
567
+ return a;
568
+ }
569
+ if (b.k === "lit")
570
+ return intersect(b, a);
571
+ if (a.k === "object" && b.k === "object") {
572
+ const props = [...a.props];
573
+ for (const bp of b.props) {
574
+ const index = props.findIndex(prop => prop.key === bp.key);
575
+ if (index < 0)
576
+ props.push(bp);
577
+ else {
578
+ const ap = props[index];
579
+ props[index] = { ...ap, opt: ap.opt && bp.opt, val: intersect(ap.val, bp.val) };
580
+ }
581
+ }
582
+ const extras = a.extras === "reject" || b.extras === "reject"
583
+ ? "reject"
584
+ : a.extras === "delete" || b.extras === "delete"
585
+ ? "delete"
586
+ : "keep";
587
+ const index = a.index && b.index ? intersect(a.index, b.index) : (a.index ?? b.index);
588
+ return { k: "object", props, index, extras };
589
+ }
590
+ if (a.k === "string" && b.k === "string") {
591
+ const min = maxOf(a.min, b.min);
592
+ const max = minOf(a.max, b.max);
593
+ if (min !== undefined && max !== undefined && min > max) {
594
+ throw new OmpTypeError("string length intersection is unsatisfiable");
595
+ }
596
+ return { k: "string", min, max, url: a.url || b.url };
597
+ }
598
+ if (a.k === "number" && b.k === "number") {
599
+ const min = maxOf(a.min, b.min);
600
+ const max = minOf(a.max, b.max);
601
+ const xmin = min !== undefined && ((a.min === min && a.xmin === true) || (b.min === min && b.xmin === true));
602
+ const xmax = max !== undefined && ((a.max === max && a.xmax === true) || (b.max === max && b.xmax === true));
603
+ if (min !== undefined && max !== undefined && (min > max || (min === max && (xmin || xmax)))) {
604
+ throw new OmpTypeError("numeric range intersection is unsatisfiable");
605
+ }
606
+ if (a.divisor !== undefined && b.divisor !== undefined && a.divisor !== b.divisor) {
607
+ return { k: "intersection", members: [a, b] };
608
+ }
609
+ return {
610
+ k: "number",
611
+ int: a.int || b.int,
612
+ divisor: a.divisor ?? b.divisor,
613
+ min,
614
+ max,
615
+ xmin,
616
+ xmax,
617
+ };
618
+ }
619
+ if (a.k === "array" && b.k === "array") {
620
+ const min = maxOf(a.min, b.min);
621
+ const max = minOf(a.max, b.max);
622
+ if (min !== undefined && max !== undefined && min > max) {
623
+ throw new OmpTypeError("array length intersection is unsatisfiable");
624
+ }
625
+ return { k: "array", el: intersect(a.el, b.el), min, max };
626
+ }
627
+ if (a.k === "instance" && b.k === "instance") {
628
+ if (a.ctor === b.ctor || a.ctor.prototype instanceof b.ctor)
629
+ return a;
630
+ if (b.ctor.prototype instanceof a.ctor)
631
+ return b;
632
+ throw new OmpTypeError(`intersection of ${a.expected} and ${b.expected} is unsatisfiable`);
633
+ }
634
+ if (a.k === b.k && ["null", "undefined", "boolean", "bigint", "symbol", "anyobject"].includes(a.k))
635
+ return a;
636
+ const leftDomain = domainOf(a);
637
+ const rightDomain = domainOf(b);
638
+ if (leftDomain !== undefined && rightDomain !== undefined && leftDomain !== rightDomain) {
639
+ throw new OmpTypeError(`intersection of ${leftDomain} and ${rightDomain} is unsatisfiable`);
640
+ }
641
+ if (a.k === "anyobject" && rightDomain === "object")
642
+ return b;
643
+ if (b.k === "anyobject" && leftDomain === "object")
644
+ return a;
645
+ const members = [...(a.k === "intersection" ? a.members : [a]), ...(b.k === "intersection" ? b.members : [b])];
646
+ return { k: "intersection", members };
647
+ }
648
+ function domainOf(ir) {
649
+ switch (ir.k) {
650
+ case "null":
651
+ return "null";
652
+ case "undefined":
653
+ case "boolean":
654
+ case "bigint":
655
+ case "symbol":
656
+ case "string":
657
+ case "number":
658
+ return ir.k;
659
+ case "array":
660
+ case "tuple":
661
+ return "array";
662
+ case "object":
663
+ case "anyobject":
664
+ case "instance":
665
+ return "object";
666
+ case "lit":
667
+ return ir.v === null ? "null" : Array.isArray(ir.v) ? "array" : typeof ir.v;
668
+ case "refine":
669
+ return domainOf(ir.base);
670
+ case "morph":
671
+ return domainOf(ir.input);
672
+ case "sub":
673
+ return domainOf(ir.schema.ir);
674
+ case "alias":
675
+ return domainOf(ir.resolve());
676
+ case "union":
677
+ case "intersection": {
678
+ const first = domainOf(ir.members[0] ?? { k: "never" });
679
+ return ir.members.every(member => domainOf(member) === first) ? first : undefined;
680
+ }
681
+ default:
682
+ return undefined;
683
+ }
684
+ }
685
+ function lowerBoundWithin(source, target) {
686
+ if (target.min === undefined)
687
+ return true;
688
+ if (source.min === undefined || source.min < target.min)
689
+ return false;
690
+ return source.min !== target.min || target.xmin !== true || source.xmin === true;
691
+ }
692
+ function upperBoundWithin(source, target) {
693
+ if (target.max === undefined)
694
+ return true;
695
+ if (source.max === undefined || source.max > target.max)
696
+ return false;
697
+ return source.max !== target.max || target.xmax !== true || source.xmax === true;
698
+ }
699
+ function lengthWithin(source, target) {
700
+ return ((target.min === undefined || (source.min !== undefined && source.min >= target.min)) &&
701
+ (target.max === undefined || (source.max !== undefined && source.max <= target.max)));
702
+ }
703
+ function isSubtype(source, target, seen = new WeakMap()) {
704
+ if (source === target || target.k === "unknown" || source.k === "never")
705
+ return true;
706
+ let targets = seen.get(source);
707
+ if (targets?.has(target))
708
+ return true;
709
+ if (targets === undefined) {
710
+ targets = new Set();
711
+ seen.set(source, targets);
712
+ }
713
+ targets.add(target);
714
+ if (source.k === "alias")
715
+ return isSubtype(source.resolve(), target, seen);
716
+ if (target.k === "alias")
717
+ return isSubtype(source, target.resolve(), seen);
718
+ if (source.k === "union")
719
+ return source.members.every(member => isSubtype(member, target, seen));
720
+ if (target.k === "union")
721
+ return target.members.some(member => isSubtype(source, member, seen));
722
+ if (target.k === "intersection")
723
+ return target.members.every(member => isSubtype(source, member, seen));
724
+ if (source.k === "intersection")
725
+ return source.members.some(member => isSubtype(member, target, seen));
726
+ if (source.k === "lit")
727
+ return !(walk(target, source.v) instanceof OmpErrors);
728
+ if (source.k === "refine")
729
+ return isSubtype(source.base, target, seen);
730
+ if (source.k === "morph")
731
+ return isSubtype(source.out ?? source.input, target, seen);
732
+ if (source.k === "sub")
733
+ return isSubtype(source.schema.ir, target, seen);
734
+ if (target.k === "refine" || target.k === "morph" || target.k === "sub")
735
+ return false;
736
+ if (source.k === "string" && target.k === "string") {
737
+ return lengthWithin(source, target) && (!target.url || source.url === true);
738
+ }
739
+ if (source.k === "number" && target.k === "number") {
740
+ return (lowerBoundWithin(source, target) &&
741
+ upperBoundWithin(source, target) &&
742
+ (!target.int || source.int === true) &&
743
+ (target.divisor === undefined || (source.divisor !== undefined && source.divisor % target.divisor === 0)));
744
+ }
745
+ if (source.k === "array" && target.k === "array") {
746
+ return lengthWithin(source, target) && isSubtype(source.el, target.el, seen);
747
+ }
748
+ if (source.k === "object" && target.k === "object") {
749
+ for (const targetProp of target.props) {
750
+ const sourceProp = source.props.find(prop => prop.key === targetProp.key);
751
+ if (sourceProp === undefined) {
752
+ if (!targetProp.opt)
753
+ return false;
754
+ continue;
755
+ }
756
+ if (!targetProp.opt && sourceProp.opt)
757
+ return false;
758
+ if (!isSubtype(sourceProp.val, targetProp.val, seen))
759
+ return false;
760
+ }
761
+ if (target.extras === "reject") {
762
+ if (source.extras !== "reject")
763
+ return false;
764
+ if (target.index === undefined &&
765
+ source.props.some(sourceProp => !target.props.some(targetProp => targetProp.key === sourceProp.key))) {
766
+ return false;
767
+ }
768
+ }
769
+ return true;
770
+ }
771
+ if (source.k === "instance" && target.k === "instance") {
772
+ return source.ctor === target.ctor || source.ctor.prototype instanceof target.ctor;
773
+ }
774
+ if (source.k === "object" && target.k === "anyobject")
775
+ return true;
776
+ if (source.k === "instance" && target.k === "anyobject")
777
+ return true;
778
+ if (source.k === "tuple" && target.k === "array") {
779
+ return (source.prefix.every(item => isSubtype(item.val, target.el, seen)) &&
780
+ source.postfix.every(item => isSubtype(item, target.el, seen)) &&
781
+ (source.variadic === undefined || isSubtype(source.variadic, target.el, seen)));
782
+ }
783
+ if (source.k !== target.k)
784
+ return false;
785
+ switch (source.k) {
786
+ case "null":
787
+ case "undefined":
788
+ case "boolean":
789
+ case "bigint":
790
+ case "symbol":
791
+ case "anyobject":
792
+ return true;
793
+ case "tuple":
794
+ return target.k === "tuple" && expectedTuple(source) === expectedTuple(target);
795
+ case "instance":
796
+ return target.k === "instance" && source.ctor === target.ctor;
797
+ default:
798
+ return false;
799
+ }
800
+ }
801
+ function expectedTuple(tuple) {
802
+ return JSON.stringify({
803
+ prefix: tuple.prefix.map(item => [item.opt, item.hasDefault, expectedOf(item.val)]),
804
+ variadic: tuple.variadic === undefined ? undefined : expectedOf(tuple.variadic),
805
+ postfix: tuple.postfix.map(expectedOf),
806
+ });
807
+ }
808
+ function irEquals(left, right) {
809
+ return isSubtype(left, right) && isSubtype(right, left);
810
+ }
811
+ function maxOf(a, b) {
812
+ if (a === undefined)
813
+ return b;
814
+ if (b === undefined)
815
+ return a;
816
+ return Math.max(a, b);
817
+ }
818
+ function minOf(a, b) {
819
+ if (a === undefined)
820
+ return b;
821
+ if (b === undefined)
822
+ return a;
823
+ return Math.min(a, b);
824
+ }
825
+ function withLengthBound(ir, side, bound) {
826
+ if (ir.k === "array" || ir.k === "string") {
827
+ return side === "min" ? { ...ir, min: bound } : { ...ir, max: bound };
828
+ }
829
+ throw new OmpTypeError(`cannot apply length bound to ${ir.k}`);
830
+ }
831
+ function withNumericBound(ir, side, bound, exclusive = false) {
832
+ if (ir.k === "number") {
833
+ return side === "min" ? { ...ir, min: bound, xmin: exclusive } : { ...ir, max: bound, xmax: exclusive };
834
+ }
835
+ throw new OmpTypeError(`cannot apply numeric bound to ${ir.k}`);
836
+ }
837
+ /**
838
+ * Parse a definition into a callable schema with distinct input/output inference.
839
+ */
840
+ export function type(def) {
841
+ return makeType(parseDef(def), EMPTY_STEPS, EMPTY_META);
842
+ }
843
+ function keywordSchema(name) {
844
+ const ir = keywordIR(name);
845
+ if (ir === undefined)
846
+ throw new OmpTypeError(`missing built-in keyword ${name}`);
847
+ return makeType(ir, [], {});
848
+ }
849
+ function parsedKeyword(name) {
850
+ return Object.assign(keywordSchema(name), {
851
+ parse: keywordSchema(`${name}.parse`),
852
+ });
853
+ }
854
+ function preformattedKeyword(name) {
855
+ return Object.assign(keywordSchema(name), {
856
+ preformatted: keywordSchema(`${name}.preformatted`),
857
+ });
858
+ }
859
+ function caseResolver(value) {
860
+ if (typeof value !== "function")
861
+ throw new OmpTypeError("match case values must be functions");
862
+ return input => Reflect.apply(value, undefined, [input]);
863
+ }
864
+ (function (type) {
865
+ /** Error aggregate returned by failed validations (`result instanceof type.errors`). */
866
+ type.errors = OmpErrors;
867
+ const normalize = Object.assign(keywordSchema("string.normalize"), {
868
+ preformatted: keywordSchema("string.normalize.NFC.preformatted"),
869
+ NFC: preformattedKeyword("string.normalize.NFC"),
870
+ NFD: preformattedKeyword("string.normalize.NFD"),
871
+ NFKC: preformattedKeyword("string.normalize.NFKC"),
872
+ NFKD: preformattedKeyword("string.normalize.NFKD"),
873
+ });
874
+ const base64 = Object.assign(keywordSchema("string.base64"), {
875
+ url: keywordSchema("string.base64.url"),
876
+ });
877
+ const date = Object.assign(parsedKeyword("string.date"), {
878
+ iso: parsedKeyword("string.date.iso"),
879
+ epoch: parsedKeyword("string.date.epoch"),
880
+ });
881
+ const ip = Object.assign(keywordSchema("string.ip"), {
882
+ v4: keywordSchema("string.ip.v4"),
883
+ v6: keywordSchema("string.ip.v6"),
884
+ });
885
+ const uuid = Object.assign(keywordSchema("string.uuid"), {
886
+ v1: keywordSchema("string.uuid.v1"),
887
+ v2: keywordSchema("string.uuid.v2"),
888
+ v3: keywordSchema("string.uuid.v3"),
889
+ v4: keywordSchema("string.uuid.v4"),
890
+ v5: keywordSchema("string.uuid.v5"),
891
+ v6: keywordSchema("string.uuid.v6"),
892
+ v7: keywordSchema("string.uuid.v7"),
893
+ v8: keywordSchema("string.uuid.v8"),
894
+ });
895
+ /** String validator and its refinement/morph keyword module. */
896
+ type.string = Object.assign(makeType({ k: "string" }, [], {}), {
897
+ alpha: keywordSchema("string.alpha"),
898
+ alphanumeric: keywordSchema("string.alphanumeric"),
899
+ base64,
900
+ capitalize: preformattedKeyword("string.capitalize"),
901
+ creditCard: keywordSchema("string.creditCard"),
902
+ date,
903
+ digits: keywordSchema("string.digits"),
904
+ email: keywordSchema("string.email"),
905
+ hex: keywordSchema("string.hex"),
906
+ integer: parsedKeyword("string.integer"),
907
+ ip,
908
+ json: parsedKeyword("string.json"),
909
+ lower: preformattedKeyword("string.lower"),
910
+ normalize,
911
+ numeric: parsedKeyword("string.numeric"),
912
+ regex: keywordSchema("string.regex"),
913
+ semver: keywordSchema("string.semver"),
914
+ trim: preformattedKeyword("string.trim"),
915
+ upper: preformattedKeyword("string.upper"),
916
+ url: parsedKeyword("string.url"),
917
+ uuid,
918
+ });
919
+ /** Runtime parser keyword family. */
920
+ type.parse = {
921
+ number: keywordSchema("parse.number"),
922
+ integer: keywordSchema("parse.integer"),
923
+ json: keywordSchema("parse.json"),
924
+ date: keywordSchema("parse.date"),
925
+ url: keywordSchema("parse.url"),
926
+ boolean: keywordSchema("parse.boolean"),
927
+ bigint: keywordSchema("parse.bigint"),
928
+ };
929
+ /** Number validator with integer refinement. */
930
+ type.number = Object.assign(makeType({ k: "number" }, [], {}), {
931
+ integer: makeType({ k: "number", int: true }, [], {}),
932
+ });
933
+ /** Boolean validator. */
934
+ type.boolean = makeType({ k: "boolean" }, [], {});
935
+ /** Bigint validator. */
936
+ type.bigint = makeType({ k: "bigint" }, [], {});
937
+ /** Symbol validator. */
938
+ type.symbol = makeType({ k: "symbol" }, [], {});
939
+ /** Non-null object validator. */
940
+ type.object = makeType({ k: "anyobject" }, [], {});
941
+ /** Unknown validator. */
942
+ type.unknown = makeType({ k: "unknown" }, [], {});
943
+ /** Alias of the unknown validator. */
944
+ type.any = type.unknown;
945
+ /** Validator that rejects every value. */
946
+ type.never = makeType({ k: "never" }, [], {});
947
+ /** Date instance validator. */
948
+ // biome-ignore lint/suspicious/noShadowRestrictedNames: ArkType exposes this exact keyword.
949
+ type.Date = makeType({ k: "instance", ctor: globalThis.Date, expected: "a Date" }, [], {});
950
+ /** Validate instances of `ctor`. */
951
+ function instanceOf(ctor) {
952
+ const name = Reflect.get(ctor, "name");
953
+ const expected = typeof name === "string" && name.length > 0 ? `an instance of ${name}` : "an instance";
954
+ return makeType({ k: "instance", ctor, expected }, [], {});
955
+ }
956
+ type.instanceOf = instanceOf;
957
+ /** Validate one exact unit value. */
958
+ function unit(value) {
959
+ return makeType({ k: "lit", v: value }, [], {});
960
+ }
961
+ type.unit = unit;
962
+ /** Union of literal values from a runtime array. */
963
+ function enumerated(...values) {
964
+ const members = values.map(value => ({ k: "lit", v: value }));
965
+ const ir = members.length === 0 ? { k: "never" } : members.length === 1 ? members[0] : { k: "union", members };
966
+ return makeType(ir, [], {});
967
+ }
968
+ type.enumerated = enumerated;
969
+ /** Build a first-match dispatcher from schema-expression keys and a `default` case. */
970
+ function match(cases) {
971
+ const branches = [];
972
+ let fallback;
973
+ for (const definition in cases) {
974
+ const resolver = caseResolver(cases[definition]);
975
+ if (definition === "default")
976
+ fallback = resolver;
977
+ else
978
+ branches.push({ schema: raw(definition), resolve: resolver });
979
+ }
980
+ return value => {
981
+ for (const branch of branches) {
982
+ if (branch.schema.allows(value))
983
+ return branch.resolve(value);
984
+ }
985
+ if (fallback !== undefined)
986
+ return fallback(value);
987
+ throw new OmpTypeError("match requires a matching case or default");
988
+ };
989
+ }
990
+ type.match = match;
991
+ /** Preserve a definition's literal type while authoring reusable modules. */
992
+ function define(definition) {
993
+ return definition;
994
+ }
995
+ type.define = define;
996
+ /** Build a lazy named scope from aliases and recursive definitions. */
997
+ function scope(aliases, options) {
998
+ return buildScope(aliases, options);
999
+ }
1000
+ type.scope = scope;
1001
+ /** Compile a named schema module whose definitions may reference each other. */
1002
+ function module(definitions) {
1003
+ return scope(definitions).export();
1004
+ }
1005
+ type.module = module;
1006
+ /** Build a runtime generic whose parameter names are supplied as `"<t, u>"`. */
1007
+ function generic(parameters, definition) {
1008
+ const names = parameters
1009
+ .replace(/^<|>$/g, "")
1010
+ .split(",")
1011
+ .map(name => name.trim())
1012
+ .filter(Boolean);
1013
+ return (...arguments_) => {
1014
+ if (arguments_.length !== names.length) {
1015
+ throw new OmpTypeError(`generic expects ${names.length} arguments (received ${arguments_.length})`);
1016
+ }
1017
+ const aliases = {};
1018
+ for (let index = 0; index < names.length; index++)
1019
+ aliases[names[index]] = arguments_[index];
1020
+ return scope(aliases).type(definition);
1021
+ };
1022
+ }
1023
+ type.generic = generic;
1024
+ /** Untyped builder for runtime-assembled definitions. */
1025
+ function raw(def) {
1026
+ return makeType(parseDef(def), [], {});
1027
+ }
1028
+ type.raw = raw;
1029
+ })(type || (type = {}));
1030
+ // Reserved words cannot be declared as namespace bindings, but ArkType exposes
1031
+ // them as runtime keyword properties.
1032
+ Object.assign(type, {
1033
+ null: makeType({ k: "null" }, [], {}),
1034
+ undefined: makeType({ k: "undefined" }, [], {}),
1035
+ true: makeType({ k: "lit", v: true }, [], {}),
1036
+ false: makeType({ k: "lit", v: false }, [], {}),
1037
+ });
1038
+ /** Build a scope whose aliases resolve lazily, including recursive cycles. */
1039
+ export function scope(aliases, options) {
1040
+ return buildScope(aliases, options);
1041
+ }
1042
+ function buildScope(aliases, _options) {
1043
+ const references = new Map();
1044
+ const targets = new Map();
1045
+ const resolve = name => {
1046
+ if (!Object.hasOwn(aliases, name))
1047
+ return undefined;
1048
+ const existing = references.get(name);
1049
+ if (existing !== undefined)
1050
+ return existing;
1051
+ const reference = {
1052
+ k: "alias",
1053
+ name,
1054
+ resolve: () => {
1055
+ const target = targets.get(name);
1056
+ if (target !== undefined)
1057
+ return target;
1058
+ const parsed = parseDef(aliases[name], resolve);
1059
+ targets.set(name, parsed);
1060
+ return parsed;
1061
+ },
1062
+ };
1063
+ references.set(name, reference);
1064
+ return reference;
1065
+ };
1066
+ const scoped = Object.assign((definition) => makeType(parseDef(definition, resolve), [], {}), type);
1067
+ return {
1068
+ type: scoped,
1069
+ export() {
1070
+ const schemas = {};
1071
+ for (const name in aliases)
1072
+ schemas[name] = scoped(name);
1073
+ return schemas;
1074
+ },
1075
+ };
1076
+ }
1077
+ /** `hasMorph` re-export for diagnostics/tooling. */
1078
+ export { hasMorph };