@oh-my-pi/omptype 17.2.6 → 17.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/js/ir.js ADDED
@@ -0,0 +1,1700 @@
1
+ /**
2
+ * Schema IR and the ArkType-compatible definition parser.
3
+ *
4
+ * `parseDef` turns the definition subset this repo uses — string DSL
5
+ * (primitives, literals, unions, arrays, bounds, `number.integer`,
6
+ * `string.url`, inline `= literal` defaults), object literals (optional `?`
7
+ * keys, `"+"` undeclared-key policy, `"[string]"` index signatures), tuple
8
+ * `[def, "[]"]` arrays, and embedded `Type` instances — into a small IR tree
9
+ * consumed by the interpreter (`interp.ts`), the JIT compiler (`compile.ts`),
10
+ * and the JSON Schema emitter (`json-schema.ts`).
11
+ */
12
+ import { OmpErrors, OmpTypeError } from "./errors.js";
13
+ import { keywordIR, patternIR, templateIR } from "./keywords.js";
14
+ /** Brand carried by `Type` instances so the parser can embed them in defs. */
15
+ export const IR_BRAND = Symbol("omptype.schema");
16
+ const kMorph = Symbol("omptype.hasMorph");
17
+ const kMorphOwner = Symbol("omptype.hasMorphOwner");
18
+ const kAlias = Symbol("omptype.hasAlias");
19
+ const kAliasOwner = Symbol("omptype.hasAliasOwner");
20
+ const kSimple = Symbol("omptype.simple");
21
+ const kSimpleOwner = Symbol("omptype.simpleOwner");
22
+ const SIMPLE_OPS = "|&()[]=?%,#";
23
+ function tokenize(src) {
24
+ const toks = [];
25
+ let i = 0;
26
+ const n = src.length;
27
+ while (i < n) {
28
+ const c = src[i];
29
+ if (c === " " || c === "\t" || c === "\n" || c === "\r") {
30
+ i++;
31
+ continue;
32
+ }
33
+ if (c === "d" && (src[i + 1] === "'" || src[i + 1] === '"')) {
34
+ const quote = src[i + 1];
35
+ const end = src.indexOf(quote, i + 2);
36
+ if (end < 0)
37
+ throw new OmpTypeError(`unterminated date literal in "${src}"`);
38
+ const source = src.slice(i + 2, end).trim();
39
+ const value = /^\d+$/.test(source) ? new Date(Number(source)) : new Date(source);
40
+ if (Number.isNaN(value.valueOf()))
41
+ throw new OmpTypeError(`invalid date literal in "${src}"`);
42
+ toks.push({ t: "date", v: value });
43
+ i = end + 1;
44
+ continue;
45
+ }
46
+ if (c === "'" || c === '"') {
47
+ let j = i + 1;
48
+ let value = "";
49
+ for (; j < n && src[j] !== c; j++) {
50
+ if (src[j] === "\\") {
51
+ j++;
52
+ if (j >= n)
53
+ break;
54
+ }
55
+ value += src[j];
56
+ }
57
+ if (j >= n)
58
+ throw new OmpTypeError(`unterminated string literal in "${src}"`);
59
+ toks.push({ t: "str", v: value });
60
+ i = j + 1;
61
+ continue;
62
+ }
63
+ if (c === "/") {
64
+ let j = i + 1;
65
+ for (; j < n; j++) {
66
+ if (src[j] === "\\")
67
+ j++;
68
+ else if (src[j] === "/")
69
+ break;
70
+ }
71
+ if (j >= n)
72
+ throw new OmpTypeError(`unterminated regular expression in "${src}"`);
73
+ let end = j + 1;
74
+ while (end < n && /[dgimsuvy]/.test(src[end]))
75
+ end++;
76
+ const source = src.slice(i + 1, j);
77
+ const flags = src.slice(j + 1, end);
78
+ try {
79
+ toks.push({ t: "regex", v: new RegExp(source, flags) });
80
+ }
81
+ catch {
82
+ throw new OmpTypeError(`invalid regular expression "${src.slice(i, end)}"`);
83
+ }
84
+ i = end;
85
+ continue;
86
+ }
87
+ if ((c >= "0" && c <= "9") || (c === "-" && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9")) {
88
+ let j = i + 1;
89
+ while (j < n && /[\w.+-]/.test(src[j]))
90
+ j++;
91
+ const raw = src.slice(i, j);
92
+ if (/^-?(?:0|[1-9]\d*)n$/.test(raw) && raw !== "-0n") {
93
+ toks.push({ t: "bigint", v: BigInt(raw.slice(0, -1)) });
94
+ }
95
+ else {
96
+ const valid = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/.test(raw) && !Object.is(Number(raw), -0) && String(Number(raw)) === raw;
97
+ if (!valid)
98
+ throw new OmpTypeError(`Malformed number literal '${raw}'`);
99
+ toks.push({ t: "num", v: Number(raw) });
100
+ }
101
+ i = j;
102
+ continue;
103
+ }
104
+ if (/[a-zA-Z_$]/.test(c)) {
105
+ let j = i + 1;
106
+ while (j < n && /[\w.$]/.test(src[j]))
107
+ j++;
108
+ toks.push({ t: "id", v: src.slice(i, j) });
109
+ i = j;
110
+ continue;
111
+ }
112
+ if (c === "<" || c === ">") {
113
+ if (src[i + 1] === "=") {
114
+ toks.push({ t: "op", v: `${c}=` });
115
+ i += 2;
116
+ }
117
+ else {
118
+ toks.push({ t: "op", v: c });
119
+ i++;
120
+ }
121
+ continue;
122
+ }
123
+ if (c === "=" && src[i + 1] === "=") {
124
+ toks.push({ t: "op", v: "==" });
125
+ i += 2;
126
+ continue;
127
+ }
128
+ if (SIMPLE_OPS.includes(c)) {
129
+ toks.push({ t: "op", v: c });
130
+ i++;
131
+ continue;
132
+ }
133
+ throw new OmpTypeError(`unexpected character '${c}' in "${src}"`);
134
+ }
135
+ return toks;
136
+ }
137
+ // ── string-definition parser ─────────────────────────────────────────────────
138
+ const CMP = { "<": true, "<=": true, ">": true, ">=": true };
139
+ const KEYWORDS = {
140
+ number: () => ({ k: "number" }),
141
+ "number.integer": () => ({ k: "number", int: true }),
142
+ boolean: () => ({ k: "boolean" }),
143
+ bigint: () => ({ k: "bigint" }),
144
+ symbol: () => ({ k: "symbol" }),
145
+ never: () => ({ k: "never" }),
146
+ null: () => ({ k: "null" }),
147
+ undefined: () => ({ k: "undefined" }),
148
+ unknown: () => ({ k: "unknown" }),
149
+ any: () => ({ k: "unknown" }),
150
+ object: () => ({ k: "anyobject" }),
151
+ Date: () => ({ k: "instance", ctor: Date, expected: "a Date" }),
152
+ true: () => ({ k: "lit", v: true }),
153
+ false: () => ({ k: "lit", v: false }),
154
+ };
155
+ /**
156
+ * Resolvers that only intercept the `this` self-reference. A parse under such
157
+ * a resolver of a source with no `this` token is identical to a resolver-free
158
+ * parse, so it may read and populate the string-definition cache.
159
+ */
160
+ const THIS_ONLY_RESOLVERS = new WeakSet();
161
+ /** Declare that `resolve` only intercepts `this` (see THIS_ONLY_RESOLVERS). */
162
+ export function markThisOnlyResolver(resolve) {
163
+ THIS_ONLY_RESOLVERS.add(resolve);
164
+ }
165
+ class StrParser {
166
+ #toks;
167
+ #pos = 0;
168
+ #src;
169
+ #resolve;
170
+ constructor(src, resolve) {
171
+ this.#src = src;
172
+ this.#resolve = resolve;
173
+ this.#toks = tokenize(src);
174
+ }
175
+ #peek(offset = 0) {
176
+ return this.#toks[this.#pos + offset];
177
+ }
178
+ #next() {
179
+ const t = this.#toks[this.#pos++];
180
+ if (!t)
181
+ throw new OmpTypeError(`unexpected end of definition "${this.#src}"`);
182
+ return t;
183
+ }
184
+ #eatOp(v) {
185
+ const t = this.#peek();
186
+ if (t?.t === "op" && t.v === v) {
187
+ this.#pos++;
188
+ return true;
189
+ }
190
+ return false;
191
+ }
192
+ /** Full definition with optional trailing `= literal` default and/or `?` optional marker. */
193
+ parseTop() {
194
+ const ir = this.parseUnion();
195
+ let def;
196
+ let hasDefault = false;
197
+ if (this.#eatOp("=")) {
198
+ const t = this.#next();
199
+ if (t.t === "num" || t.t === "bigint" || t.t === "date" || t.t === "str")
200
+ def = t.v;
201
+ else if (t.t === "id" && (t.v === "true" || t.v === "false"))
202
+ def = t.v === "true";
203
+ else if (t.t === "id" && t.v === "null")
204
+ def = null;
205
+ else if (t.t === "id" && t.v === "undefined")
206
+ def = undefined;
207
+ else
208
+ throw new OmpTypeError(`unsupported default literal in "${this.#src}"`);
209
+ hasDefault = true;
210
+ }
211
+ const optional = this.#eatOp("?");
212
+ this.#expectEnd();
213
+ return { ir, def, hasDefault, optional };
214
+ }
215
+ #expectEnd() {
216
+ if (this.#pos < this.#toks.length) {
217
+ throw new OmpTypeError(`trailing tokens in definition "${this.#src}"`);
218
+ }
219
+ }
220
+ parseUnion() {
221
+ const first = this.parseIntersection();
222
+ if (!this.#eatOp("|"))
223
+ return first;
224
+ const members = [first, this.parseIntersection()];
225
+ while (this.#eatOp("|"))
226
+ members.push(this.parseIntersection());
227
+ return { k: "union", members };
228
+ }
229
+ parseIntersection() {
230
+ const first = this.parseBounded();
231
+ if (!this.#eatOp("&"))
232
+ return first;
233
+ const members = [first, this.parseBounded()];
234
+ while (this.#eatOp("&"))
235
+ members.push(this.parseBounded());
236
+ const literal = members.find((member) => member.k === "lit");
237
+ if (literal && typeof literal.v === "number") {
238
+ for (const member of members) {
239
+ if (member.k === "number" &&
240
+ ((member.int && !Number.isInteger(literal.v)) ||
241
+ (member.divisor !== undefined && literal.v % member.divisor !== 0) ||
242
+ (member.min !== undefined && (member.xmin ? literal.v <= member.min : literal.v < member.min)) ||
243
+ (member.max !== undefined && (member.xmax ? literal.v >= member.max : literal.v > member.max)))) {
244
+ throw new OmpTypeError("literal is excluded by intersection");
245
+ }
246
+ }
247
+ return literal;
248
+ }
249
+ return { k: "intersection", members };
250
+ }
251
+ /**
252
+ * `NUM CMP base (CMP NUM)?` or `base (CMP NUM)?`, with `[]*` postfix on the
253
+ * base AND after a trailing bound — `string>0[]` is an array of bounded
254
+ * strings, matching ArkType precedence (bounds bind tighter than `[]`).
255
+ */
256
+ parseBounded() {
257
+ const t = this.#peek();
258
+ const t1 = this.#peek(1);
259
+ if ((t?.t === "num" || t?.t === "date") && t1?.t === "op" && (t1.v === "<" || t1.v === "<=")) {
260
+ const lo = t.v;
261
+ this.#pos += 2;
262
+ let node = this.#eatDivisor(this.parsePostfix());
263
+ node = applyBound(node, flip(t1.v), lo, this.#src);
264
+ const t2 = this.#peek();
265
+ if (!(t2?.t === "op" && CMP[t2.v])) {
266
+ throw new OmpTypeError(`left bound requires a corresponding right bound in "${this.#src}"`);
267
+ }
268
+ if (t2.v === ">" || t2.v === ">=") {
269
+ throw new OmpTypeError(`right bound must use < or <= in "${this.#src}"`);
270
+ }
271
+ this.#pos++;
272
+ const hi = this.#next();
273
+ if (hi.t !== "num" && hi.t !== "date") {
274
+ throw new OmpTypeError(`expected bound after comparator in "${this.#src}"`);
275
+ }
276
+ node = applyBound(node, t2.v, hi.v, this.#src);
277
+ return this.#eatArraySuffixes(node);
278
+ }
279
+ let node = this.#eatDivisor(this.parsePostfix());
280
+ const t2 = this.#peek();
281
+ if (t2?.t === "op" && t2.v === "==") {
282
+ this.#pos++;
283
+ const limit = this.#next();
284
+ if (limit.t !== "num" && limit.t !== "bigint" && limit.t !== "date") {
285
+ throw new OmpTypeError(`expected literal after == in "${this.#src}"`);
286
+ }
287
+ node = applyEquality(node, limit.v, this.#src);
288
+ }
289
+ else if (t2?.t === "op" && CMP[t2.v] && (this.#peek(1)?.t === "num" || this.#peek(1)?.t === "date")) {
290
+ this.#pos++;
291
+ const limit = this.#next();
292
+ node = applyBound(node, t2.v, limit.v, this.#src);
293
+ node = this.#eatArraySuffixes(node);
294
+ }
295
+ return node;
296
+ }
297
+ #eatDivisor(node) {
298
+ if (!this.#eatOp("%"))
299
+ return node;
300
+ const divisor = this.#next();
301
+ if (divisor.t !== "num")
302
+ throw new OmpTypeError(`expected number after % in "${this.#src}"`);
303
+ if (node.k !== "number")
304
+ throw new OmpTypeError(`% requires number in "${this.#src}"`);
305
+ if (!Number.isFinite(divisor.v) || !Number.isInteger(divisor.v) || divisor.v === 0)
306
+ throw new OmpTypeError(`divisor must be a non-zero integer in "${this.#src}"`);
307
+ // Copy-on-write: the primary may be a shared node (string-def cache,
308
+ // generic arguments); stamping it in place would leak into other schemas.
309
+ return { ...node, divisor: Math.abs(divisor.v) };
310
+ }
311
+ /** Wrap `node` in array IR for each `[]` pair at the cursor. */
312
+ #eatArraySuffixes(node) {
313
+ for (;;) {
314
+ const t = this.#peek();
315
+ if (!(t?.t === "op" && t.v === "["))
316
+ return node;
317
+ this.#pos++;
318
+ if (!this.#eatOp("]"))
319
+ throw new OmpTypeError(`expected ']' in "${this.#src}"`);
320
+ node = { k: "array", el: node };
321
+ }
322
+ }
323
+ parsePostfix() {
324
+ let node = this.parsePrimary();
325
+ for (;;) {
326
+ const t = this.#peek();
327
+ if (t?.t === "op" && t.v === "[") {
328
+ this.#pos++;
329
+ if (!this.#eatOp("]"))
330
+ throw new OmpTypeError(`expected ']' in "${this.#src}"`);
331
+ node = { k: "array", el: node };
332
+ continue;
333
+ }
334
+ if (t?.t === "op" && t.v === "#") {
335
+ this.#pos++;
336
+ const name = this.#next();
337
+ if (name.t !== "id")
338
+ throw new OmpTypeError(`expected brand name after # in "${this.#src}"`);
339
+ continue;
340
+ }
341
+ break;
342
+ }
343
+ return node;
344
+ }
345
+ #parseGenericArguments() {
346
+ this.#eatOp("<");
347
+ const arguments_ = [];
348
+ if (this.#eatOp(">"))
349
+ return arguments_;
350
+ for (;;) {
351
+ arguments_.push(this.parseUnion());
352
+ if (this.#eatOp(">"))
353
+ return arguments_;
354
+ if (!this.#eatOp(","))
355
+ throw new OmpTypeError(`expected ',' or '>' in "${this.#src}"`);
356
+ const next = this.#peek();
357
+ if (next?.t === "op" && (next.v === "," || next.v === ">")) {
358
+ throw new OmpTypeError(`generic arguments cannot be empty in "${this.#src}"`);
359
+ }
360
+ }
361
+ }
362
+ parsePrimary() {
363
+ const t = this.#next();
364
+ if (t.t === "op" && t.v === "(") {
365
+ const inner = this.parseUnion();
366
+ if (!this.#eatOp(")"))
367
+ throw new OmpTypeError(`expected ')' in "${this.#src}"`);
368
+ return inner;
369
+ }
370
+ if (t.t === "str" || t.t === "num" || t.t === "bigint" || t.t === "date")
371
+ return { k: "lit", v: t.v };
372
+ if (t.t === "regex")
373
+ return patternIR(t.v);
374
+ if (t.t === "id") {
375
+ if (t.v === "keyof") {
376
+ try {
377
+ return keyOf(this.parsePostfix());
378
+ }
379
+ catch (error) {
380
+ if (error instanceof OmpTypeError)
381
+ throw new OmpTypeError("keyof operand must be an object");
382
+ throw error;
383
+ }
384
+ }
385
+ if (t.v === "Array.liftFrom" && this.#peek()?.t === "op" && this.#peek()?.v === "<") {
386
+ this.#pos++;
387
+ const element = this.parsePrimary();
388
+ if (!this.#eatOp(">"))
389
+ throw new OmpTypeError(`expected '>' in "${this.#src}"`);
390
+ const array = { k: "array", el: element, desc: "an object" };
391
+ return {
392
+ k: "morph",
393
+ input: { k: "union", members: [element, array] },
394
+ fn: value => (Array.isArray(value) ? value : [value]),
395
+ out: array,
396
+ };
397
+ }
398
+ if (t.v === "Record" && this.#peek()?.t === "op" && this.#peek()?.v === "<") {
399
+ const arguments_ = this.#parseGenericArguments();
400
+ if (arguments_.length !== 2)
401
+ throw new OmpTypeError("Record requires two arguments");
402
+ return { k: "object", props: [], index: arguments_[1], extras: "keep" };
403
+ }
404
+ if (this.#peek()?.t === "op" && this.#peek()?.v === "<" && this.#resolve?.hasGeneric?.(t.v)) {
405
+ const arguments_ = this.#parseGenericArguments();
406
+ const instantiated = this.#resolve.generic?.(t.v, arguments_);
407
+ if (!instantiated)
408
+ throw new OmpTypeError(`unknown generic "${t.v}" in "${this.#src}"`);
409
+ return instantiated;
410
+ }
411
+ const scoped = this.#resolve?.(t.v);
412
+ const make = KEYWORDS[t.v];
413
+ const keyword = scoped ?? make?.() ?? keywordIR(t.v);
414
+ if (!keyword)
415
+ throw new OmpTypeError(`unknown keyword "${t.v}" in "${this.#src}"`);
416
+ return keyword;
417
+ }
418
+ throw new OmpTypeError(`unexpected token in "${this.#src}"`);
419
+ }
420
+ }
421
+ const STRING_DEF_CACHE_MAX = 1_024;
422
+ const stringDefCache = new Map();
423
+ function isWhitespaceAt(src, index) {
424
+ const code = src.charCodeAt(index);
425
+ return code === 32 || (code >= 9 && code <= 13) || (code > 127 && /\s/.test(src[index]));
426
+ }
427
+ /** Fast path for the literal unions pervasive in command schemas. */
428
+ function parseLiteralUnion(src) {
429
+ const members = [];
430
+ let index = 0;
431
+ while (index < src.length && isWhitespaceAt(src, index))
432
+ index++;
433
+ for (;;) {
434
+ const quote = src[index];
435
+ if (quote !== "'" && quote !== '"')
436
+ return undefined;
437
+ const end = src.indexOf(quote, index + 1);
438
+ if (end < 0)
439
+ return undefined;
440
+ members.push({ k: "lit", v: src.slice(index + 1, end) });
441
+ index = end + 1;
442
+ while (index < src.length && isWhitespaceAt(src, index))
443
+ index++;
444
+ if (index === src.length) {
445
+ const ir = members.length === 1 ? members[0] : { k: "union", members };
446
+ let simple = true;
447
+ for (let member = 1; simple && member < members.length; member++) {
448
+ for (let previous = 0; previous < member; previous++) {
449
+ if (members[previous].k === "lit" && members[previous].v === members[member].v) {
450
+ simple = false;
451
+ break;
452
+ }
453
+ }
454
+ }
455
+ ir[kSimple] = simple;
456
+ ir[kSimpleOwner] = ir;
457
+ return ir;
458
+ }
459
+ if (src[index] !== "|")
460
+ return undefined;
461
+ index++;
462
+ while (index < src.length && isWhitespaceAt(src, index))
463
+ index++;
464
+ }
465
+ }
466
+ function genericArguments(src) {
467
+ const open = src.indexOf("<");
468
+ if (open < 1 || !src.endsWith(">"))
469
+ return undefined;
470
+ const name = src.slice(0, open).trim();
471
+ const body = src.slice(open + 1, -1);
472
+ const args = [];
473
+ let depth = 0;
474
+ let quote = "";
475
+ let start = 0;
476
+ for (let index = 0; index < body.length; index++) {
477
+ const char = body[index];
478
+ if (quote !== "") {
479
+ if (char === quote && body[index - 1] !== "\\")
480
+ quote = "";
481
+ continue;
482
+ }
483
+ if (char === "'" || char === '"' || char === "`") {
484
+ quote = char;
485
+ }
486
+ else if (char === "<" || char === "(" || char === "[") {
487
+ depth++;
488
+ }
489
+ else if (char === ">" || char === ")" || char === "]") {
490
+ depth--;
491
+ }
492
+ else if (char === "," && depth === 0) {
493
+ args.push(body.slice(start, index).trim());
494
+ start = index + 1;
495
+ }
496
+ }
497
+ args.push(body.slice(start).trim());
498
+ return { name, args };
499
+ }
500
+ function genericKeys(ir) {
501
+ const keys = new Set();
502
+ const visit = (node) => {
503
+ if (node.k === "lit" && typeof node.v === "string")
504
+ keys.add(node.v);
505
+ else if (node.k === "union")
506
+ for (const member of node.members)
507
+ visit(member);
508
+ };
509
+ visit(ir);
510
+ return keys;
511
+ }
512
+ function resolveStructuralIR(ir) {
513
+ const seen = new Set();
514
+ while (ir.k === "alias") {
515
+ if (seen.has(ir))
516
+ throw new OmpTypeError(`cannot structurally transform recursive alias "${ir.name}"`);
517
+ seen.add(ir);
518
+ ir = ir.resolve();
519
+ }
520
+ return ir;
521
+ }
522
+ function mergeObjectIR(left, right) {
523
+ left = resolveStructuralIR(left);
524
+ right = resolveStructuralIR(right);
525
+ if (left.k !== "object" || right.k !== "object") {
526
+ throw new OmpTypeError("Merge requires object arguments");
527
+ }
528
+ const props = [...left.props];
529
+ for (const prop of right.props) {
530
+ const index = props.findIndex(candidate => candidate.key === prop.key);
531
+ if (index < 0)
532
+ props.push(prop);
533
+ else
534
+ props[index] = prop;
535
+ }
536
+ return {
537
+ k: "object",
538
+ props,
539
+ index: right.index ?? left.index,
540
+ symbolIndex: right.symbolIndex ?? left.symbolIndex,
541
+ patternIndexes: left.patternIndexes === undefined && right.patternIndexes === undefined
542
+ ? undefined
543
+ : [...(left.patternIndexes ?? []), ...(right.patternIndexes ?? [])],
544
+ extras: right.extras === "keep" ? left.extras : right.extras,
545
+ };
546
+ }
547
+ function parseGeneric(src, resolve) {
548
+ const generic = genericArguments(src);
549
+ if (generic === undefined)
550
+ return undefined;
551
+ if (generic.name === "Array.liftFrom" && generic.args.length === 1) {
552
+ const element = parseDef(generic.args[0], resolve);
553
+ const array = { k: "array", el: element, desc: "an object" };
554
+ return {
555
+ k: "morph",
556
+ input: { k: "union", members: [element, array] },
557
+ fn: value => (Array.isArray(value) ? value : [value]),
558
+ out: array,
559
+ };
560
+ }
561
+ if (generic.name === "Record" && generic.args.length === 2) {
562
+ return { k: "object", props: [], index: parseDef(generic.args[1], resolve), extras: "keep" };
563
+ }
564
+ if ((generic.name === "Extract" || generic.name === "Exclude") && generic.args.length === 2) {
565
+ return distributeFilter(parseDef(generic.args[0], resolve), parseDef(generic.args[1], resolve), generic.name === "Extract");
566
+ }
567
+ if ((generic.name === "Partial" || generic.name === "Required") && generic.args.length === 1) {
568
+ const object = resolveStructuralIR(parseDef(generic.args[0], resolve));
569
+ if (object.k !== "object")
570
+ throw new OmpTypeError(`${generic.name} requires an object`);
571
+ const optional = generic.name === "Partial";
572
+ return { ...object, props: object.props.map(prop => ({ ...prop, opt: optional })) };
573
+ }
574
+ if ((generic.name === "Pick" || generic.name === "Omit") && generic.args.length === 2) {
575
+ const object = resolveStructuralIR(parseDef(generic.args[0], resolve));
576
+ if (object.k !== "object")
577
+ throw new OmpTypeError(`${generic.name} requires an object`);
578
+ const keys = genericKeys(parseDef(generic.args[1], resolve));
579
+ const pick = generic.name === "Pick";
580
+ return { ...object, props: object.props.filter(prop => keys.has(prop.key) === pick) };
581
+ }
582
+ if (generic.name === "Merge" && generic.args.length === 2) {
583
+ return mergeObjectIR(parseDef(generic.args[0], resolve), parseDef(generic.args[1], resolve));
584
+ }
585
+ return undefined;
586
+ }
587
+ /**
588
+ * Subtype comparison lives in `type.ts` (it needs full traversal), so it is
589
+ * installed here at module load for the parser's `Extract`/`Exclude` support.
590
+ */
591
+ let isAssignable = () => false;
592
+ /** Install the assignability comparator used by `Extract`/`Exclude`. */
593
+ export function useAssignability(compare) {
594
+ isAssignable = compare;
595
+ }
596
+ /**
597
+ * Distribute `base` over its union members, keeping those assignable to
598
+ * `target` (`keepAssignable`) or those that are not (`Exclude`).
599
+ */
600
+ export function distributeFilter(base, target, keepAssignable) {
601
+ const resolved = base.k === "alias" ? base.resolve() : base;
602
+ const members = resolved.k === "union" ? resolved.members : [resolved];
603
+ const retained = members.filter(member => isAssignable(member, target) === keepAssignable);
604
+ if (retained.length === 0)
605
+ return { k: "never" };
606
+ return retained.length === 1 ? retained[0] : { k: "union", members: retained };
607
+ }
608
+ /** Parse recurring global DSL fragments once; scoped aliases bypass the cache. */
609
+ function parseRegexExec(src) {
610
+ if (!src.startsWith("x/"))
611
+ return undefined;
612
+ const end = src.lastIndexOf("/");
613
+ if (end < 2)
614
+ throw new OmpTypeError(`unterminated regular expression in "${src}"`);
615
+ let regex;
616
+ try {
617
+ regex = new RegExp(src.slice(2, end), src.slice(end + 1));
618
+ }
619
+ catch {
620
+ throw new OmpTypeError(`invalid regular expression "${src.slice(1)}"`);
621
+ }
622
+ return {
623
+ k: "morph",
624
+ input: patternIR(regex),
625
+ fn: (value, context) => {
626
+ regex.lastIndex = 0;
627
+ return regex.exec(value) ?? context.error(`a string matching ${regex}`);
628
+ },
629
+ };
630
+ }
631
+ function parseStringDef(src, resolve) {
632
+ const cacheable = resolve === undefined || (!src.includes("this") && THIS_ONLY_RESOLVERS.has(resolve));
633
+ if (cacheable) {
634
+ const cached = stringDefCache.get(src);
635
+ if (cached)
636
+ return cached;
637
+ }
638
+ const pipeIndex = src.indexOf("|>");
639
+ if (pipeIndex >= 0) {
640
+ const input = src.slice(0, pipeIndex).trim();
641
+ const output = src.slice(pipeIndex + 2).trim();
642
+ if (input.length === 0 || output.length === 0) {
643
+ throw new OmpTypeError(`pipe expression requires operands in "${src}"`);
644
+ }
645
+ const parsedInput = parseStringDef(input, resolve);
646
+ if (parsedInput.hasDefault) {
647
+ throw new OmpTypeError(`unexpected pipe expression after default in "${src}"`);
648
+ }
649
+ const parsed = {
650
+ ir: {
651
+ k: "morph",
652
+ input: parsedInput.ir,
653
+ fn: value => value,
654
+ out: parseStringDef(output, resolve).ir,
655
+ },
656
+ hasDefault: false,
657
+ optional: false,
658
+ };
659
+ if (cacheable && stringDefCache.size < STRING_DEF_CACHE_MAX)
660
+ stringDefCache.set(src, parsed);
661
+ return parsed;
662
+ }
663
+ let ir = parseLiteralUnion(src) ?? parseRegexExec(src) ?? parseGeneric(src, resolve);
664
+ if (ir === undefined && src.startsWith("`") && src.endsWith("`")) {
665
+ ir = templateIR(src.slice(1, -1));
666
+ }
667
+ const parsed = ir === undefined ? new StrParser(src, resolve).parseTop() : { ir, hasDefault: false, optional: false };
668
+ if (cacheable && stringDefCache.size < STRING_DEF_CACHE_MAX)
669
+ stringDefCache.set(src, parsed);
670
+ return parsed;
671
+ }
672
+ function flip(op) {
673
+ switch (op) {
674
+ case "<":
675
+ return ">";
676
+ case "<=":
677
+ return ">=";
678
+ case ">":
679
+ return "<";
680
+ default:
681
+ return "<=";
682
+ }
683
+ }
684
+ function applyEquality(node, value, src) {
685
+ if (node.k === "union") {
686
+ return { k: "union", members: node.members.map(member => applyEquality(member, value, src)) };
687
+ }
688
+ if (value instanceof Date) {
689
+ if (!acceptsDate(node))
690
+ throw new OmpTypeError(`Date equality requires Date in "${src}"`);
691
+ return { k: "lit", v: value };
692
+ }
693
+ if (node.k === "number" && typeof value === "number")
694
+ return { k: "lit", v: value };
695
+ if (node.k === "bigint" && typeof value === "bigint")
696
+ return { k: "lit", v: value };
697
+ if ((node.k === "string" || node.k === "array") && typeof value === "number") {
698
+ if (!Number.isInteger(value) || value < 0) {
699
+ throw new OmpTypeError(`exact length must be a non-negative integer in "${src}"`);
700
+ }
701
+ return { ...node, min: value, max: value };
702
+ }
703
+ throw new OmpTypeError(`equality literal is incompatible with ${node.k} in "${src}"`);
704
+ }
705
+ /**
706
+ * Apply `node CMP value` — numeric/string/array ranges or Date bounds.
707
+ * Copy-on-write: `node` may be shared (string-def cache, generic arguments,
708
+ * resolved aliases), so bounds land on a fresh node, never in place.
709
+ */
710
+ function applyBound(node, op, value, src) {
711
+ if (node.k === "alias")
712
+ return applyBound(node.resolve(), op, value, src);
713
+ if (node.k === "refine" && !(value instanceof Date)) {
714
+ return { ...node, base: applyBound(node.base, op, value, src) };
715
+ }
716
+ if (node.k === "union") {
717
+ const kinds = new Set(node.members.map(boundKind));
718
+ if (kinds.size !== 1)
719
+ throw new OmpTypeError(`cannot apply one bound to multiple bound kinds in "${src}"`);
720
+ return { k: "union", members: node.members.map(member => applyBound(member, op, value, src)) };
721
+ }
722
+ if (!(value instanceof Date) && acceptsDate(node)) {
723
+ return applyBound(node, op, new Date(value), src);
724
+ }
725
+ if (value instanceof Date) {
726
+ if (!acceptsDate(node))
727
+ throw new OmpTypeError(`date bound requires Date in "${src}"`);
728
+ const limit = value.valueOf();
729
+ const relation = op === ">=" ? "on or after" : op === ">" ? "later than" : op === "<=" ? "on or before" : "earlier than";
730
+ return {
731
+ k: "refine",
732
+ base: node,
733
+ pred: input => {
734
+ if (!(input instanceof Date))
735
+ return false;
736
+ const time = input.valueOf();
737
+ return op === ">=" ? time >= limit : op === ">" ? time > limit : op === "<=" ? time <= limit : time < limit;
738
+ },
739
+ expected: `a Date ${relation} ${value.toISOString()}`,
740
+ };
741
+ }
742
+ if (node.k === "number") {
743
+ const bounded = { ...node };
744
+ switch (op) {
745
+ case ">=":
746
+ bounded.min = value;
747
+ bounded.xmin = false;
748
+ break;
749
+ case ">":
750
+ bounded.min = value;
751
+ bounded.xmin = true;
752
+ break;
753
+ case "<=":
754
+ bounded.max = value;
755
+ bounded.xmax = false;
756
+ break;
757
+ case "<":
758
+ bounded.max = value;
759
+ bounded.xmax = true;
760
+ break;
761
+ }
762
+ if (bounded.min !== undefined &&
763
+ bounded.max !== undefined &&
764
+ (bounded.min > bounded.max || (bounded.min === bounded.max && (bounded.xmin || bounded.xmax)))) {
765
+ throw new OmpTypeError(`numeric range is unsatisfiable in "${src}"`);
766
+ }
767
+ return bounded;
768
+ }
769
+ if (node.k === "string" || node.k === "array") {
770
+ if (!Number.isInteger(value) || value < 0) {
771
+ throw new OmpTypeError(`length bound must be a non-negative integer in "${src}"`);
772
+ }
773
+ const bounded = { ...node };
774
+ switch (op) {
775
+ case ">=":
776
+ bounded.min = value;
777
+ break;
778
+ case ">":
779
+ bounded.min = value + 1;
780
+ break;
781
+ case "<=":
782
+ bounded.max = value;
783
+ break;
784
+ case "<":
785
+ bounded.max = value - 1;
786
+ break;
787
+ }
788
+ if ((bounded.min !== undefined && bounded.max !== undefined && bounded.min > bounded.max) ||
789
+ (bounded.max ?? 0) < 0) {
790
+ throw new OmpTypeError(`length range is unsatisfiable in "${src}"`);
791
+ }
792
+ return bounded;
793
+ }
794
+ throw new OmpTypeError(`cannot bound ${node.k} in "${src}"`);
795
+ }
796
+ function acceptsDate(node) {
797
+ return (node.k === "instance" && node.ctor === Date) || (node.k === "refine" && acceptsDate(node.base));
798
+ }
799
+ // ── definition parser ────────────────────────────────────────────────────────
800
+ function isEmbedded(def) {
801
+ return (typeof def === "function" || (typeof def === "object" && def !== null)) && IR_BRAND in def;
802
+ }
803
+ /** Embed a schema value: inline pure structure, keep `sub` nodes for stepped schemas. */
804
+ export function embed(schema) {
805
+ if (schema.hasSteps)
806
+ return { k: "sub", schema, desc: schema.description, descAuto: schema.ir.desc === undefined };
807
+ if (schema.description !== undefined && schema.ir.desc === undefined) {
808
+ return { ...schema.ir, desc: schema.description, descAuto: true };
809
+ }
810
+ return schema.ir;
811
+ }
812
+ function boundKind(node) {
813
+ if (node.k === "number")
814
+ return "number";
815
+ if (node.k === "string" || node.k === "array")
816
+ return "length";
817
+ if (acceptsDate(node))
818
+ return "date";
819
+ return undefined;
820
+ }
821
+ function isCallback(value) {
822
+ return typeof value === "function";
823
+ }
824
+ function isConstructor(value) {
825
+ return typeof value === "function" && value.prototype !== undefined;
826
+ }
827
+ function parseTupleItem(def, resolve) {
828
+ if (Array.isArray(def) && def.length === 2 && def[1] === "?") {
829
+ return { val: parseDef(def[0], resolve), opt: true };
830
+ }
831
+ if (Array.isArray(def) && def.length === 3 && def[1] === "=") {
832
+ return {
833
+ val: parseDef(def[0], resolve),
834
+ opt: true,
835
+ def: def[2],
836
+ defFactory: typeof def[2] === "function",
837
+ hasDefault: true,
838
+ };
839
+ }
840
+ if (typeof def === "string") {
841
+ const parsed = parseStringDef(def, resolve);
842
+ return {
843
+ val: parsed.ir,
844
+ opt: parsed.optional || parsed.hasDefault,
845
+ def: parsed.def,
846
+ hasDefault: parsed.hasDefault,
847
+ };
848
+ }
849
+ return { val: parseDef(def, resolve), opt: false };
850
+ }
851
+ function cloneTuple(tuple) {
852
+ return {
853
+ ...tuple,
854
+ prefix: tuple.prefix.map(item => ({ ...item })),
855
+ postfix: [...tuple.postfix],
856
+ };
857
+ }
858
+ function hasOptionalPrefix(tuple) {
859
+ return tuple.prefix.some(item => item.opt || item.hasDefault === true);
860
+ }
861
+ function appendTupleItem(tuple, item) {
862
+ if (tuple.variadic !== undefined) {
863
+ if (item.opt || item.hasDefault) {
864
+ throw new OmpTypeError("An optional element may not follow a variadic element");
865
+ }
866
+ if (hasOptionalPrefix(tuple)) {
867
+ throw new OmpTypeError("A postfix required element cannot follow an optional or defaultable element");
868
+ }
869
+ tuple.postfix.push(item.val);
870
+ return;
871
+ }
872
+ if (item.hasDefault && tuple.prefix.some(prefixItem => prefixItem.opt && !prefixItem.hasDefault)) {
873
+ throw new OmpTypeError("A defaultable element may not follow an optional element without a default");
874
+ }
875
+ if (hasOptionalPrefix(tuple) && !item.opt) {
876
+ throw new OmpTypeError("required tuple elements cannot follow optional elements");
877
+ }
878
+ tuple.prefix.push(item);
879
+ }
880
+ function appendTuple(target, spread) {
881
+ if (target.variadic !== undefined && spread.variadic !== undefined) {
882
+ throw new OmpTypeError("a tuple may have one spread followed by an array definition");
883
+ }
884
+ for (const item of spread.prefix)
885
+ appendTupleItem(target, { ...item });
886
+ if (spread.variadic !== undefined) {
887
+ target.variadic = spread.variadic;
888
+ }
889
+ for (const item of spread.postfix)
890
+ appendTupleItem(target, { val: item, opt: false });
891
+ }
892
+ function spreadAlternatives(spread) {
893
+ if (spread.k === "alias")
894
+ return spreadAlternatives(spread.resolve());
895
+ if (spread.k === "sub")
896
+ return spreadAlternatives(spread.schema.ir);
897
+ if (spread.k === "union")
898
+ return spread.members.flatMap(spreadAlternatives);
899
+ if (spread.k === "array")
900
+ return [{ k: "tuple", prefix: [], variadic: spread.el, postfix: [] }];
901
+ if (spread.k === "tuple")
902
+ return [spread];
903
+ throw new OmpTypeError("tuple spread element must be an array");
904
+ }
905
+ function parseTuple(def, resolve) {
906
+ let branches = [{ k: "tuple", prefix: [], postfix: [] }];
907
+ for (let index = 0; index < def.length; index++) {
908
+ if (def[index] === "...") {
909
+ if (index + 1 >= def.length) {
910
+ throw new OmpTypeError("a tuple may have one spread followed by an array definition");
911
+ }
912
+ const alternatives = spreadAlternatives(parseDef(def[++index], resolve));
913
+ const distributed = [];
914
+ for (const branch of branches) {
915
+ for (const alternative of alternatives) {
916
+ const next = cloneTuple(branch);
917
+ appendTuple(next, alternative);
918
+ distributed.push(next);
919
+ }
920
+ }
921
+ branches = distributed;
922
+ continue;
923
+ }
924
+ const item = parseTupleItem(def[index], resolve);
925
+ for (const branch of branches)
926
+ appendTupleItem(branch, { ...item });
927
+ }
928
+ return branches.length === 1 ? branches[0] : { k: "union", members: branches };
929
+ }
930
+ /** Build the runtime schema for an object's or tuple's keys. */
931
+ export function keyOf(node) {
932
+ if (node.k === "alias")
933
+ return keyOf(node.resolve());
934
+ if (node.k === "sub")
935
+ return keyOf(node.schema.ir);
936
+ if (node.k === "refine")
937
+ return keyOf(node.base);
938
+ if (node.k === "intersection") {
939
+ const members = node.members.flatMap(member => {
940
+ const keys = keyOf(member);
941
+ return keys.k === "union" ? keys.members : [keys];
942
+ });
943
+ return members.length === 1 ? members[0] : { k: "union", members };
944
+ }
945
+ if (node.k === "object") {
946
+ const members = node.props.map(prop => ({ k: "lit", v: prop.key }));
947
+ if (node.index !== undefined || (node.patternIndexes?.length ?? 0) > 0)
948
+ members.push({ k: "string" });
949
+ if (node.symbolIndex !== undefined)
950
+ members.push({ k: "symbol" });
951
+ if (members.length === 0)
952
+ return { k: "never" };
953
+ return members.length === 1 ? members[0] : { k: "union", members };
954
+ }
955
+ if (node.k === "tuple")
956
+ return { k: "number", int: true, min: 0 };
957
+ if (node.k === "union") {
958
+ if (node.members.length === 0)
959
+ return { k: "never" };
960
+ const literalSets = node.members.map(member => {
961
+ const keyed = keyOf(member);
962
+ const literals = keyed.k === "union" ? keyed.members : [keyed];
963
+ const keys = new Set();
964
+ for (const literal of literals) {
965
+ if (literal.k === "lit" &&
966
+ (typeof literal.v === "string" || typeof literal.v === "number" || typeof literal.v === "symbol")) {
967
+ keys.add(literal.v);
968
+ }
969
+ }
970
+ return keys;
971
+ });
972
+ const common = [...literalSets[0]].filter(key => literalSets.slice(1).every(keys => keys.has(key)));
973
+ if (common.length === 0)
974
+ throw new OmpTypeError("keyof operand must be an object");
975
+ const members = common.map(value => ({ k: "lit", v: value }));
976
+ return members.length === 1 ? members[0] : { k: "union", members };
977
+ }
978
+ throw new OmpTypeError("keyof operand must be an object");
979
+ }
980
+ function parseArrayExpression(def, resolve) {
981
+ if (def.length === 2 && def[1] === "[]")
982
+ return { k: "array", el: parseDef(def[0], resolve) };
983
+ if (def.length === 2 && def[0] === "keyof")
984
+ return keyOf(parseDef(def[1], resolve));
985
+ if (def[0] === "instanceof") {
986
+ const members = [];
987
+ for (let index = 1; index < def.length; index++) {
988
+ const ctor = def[index];
989
+ if (!isConstructor(ctor))
990
+ throw new OmpTypeError("instanceof operands must be constructors");
991
+ members.push({
992
+ k: "instance",
993
+ ctor,
994
+ expected: ctor === Error ? "an Error" : `an instance of ${ctor.name || "the constructor"}`,
995
+ });
996
+ }
997
+ return members.length === 1 ? members[0] : { k: "union", members };
998
+ }
999
+ if (def[0] === "===") {
1000
+ const members = def.slice(1).map(value => ({ k: "lit", v: value }));
1001
+ if (members.length === 0)
1002
+ return { k: "never" };
1003
+ return members.length === 1 ? members[0] : { k: "union", members };
1004
+ }
1005
+ if (def.length >= 3 && def[1] === "|") {
1006
+ return { k: "union", members: [parseDef(def[0], resolve), parseDef(def[2], resolve)] };
1007
+ }
1008
+ if (def.length >= 3 && def[1] === "&") {
1009
+ return { k: "intersection", members: [parseDef(def[0], resolve), parseDef(def[2], resolve)] };
1010
+ }
1011
+ if (def.length === 3 && def[1] === "=>") {
1012
+ if (!isCallback(def[2]))
1013
+ throw new OmpTypeError("morph operator requires a function");
1014
+ return { k: "morph", input: parseDef(def[0], resolve), fn: def[2] };
1015
+ }
1016
+ if (def.length === 3 && def[1] === "|>") {
1017
+ return { k: "morph", input: parseDef(def[0], resolve), fn: value => value, out: parseDef(def[2], resolve) };
1018
+ }
1019
+ if (def.length === 3 && def[1] === ":") {
1020
+ if (!isCallback(def[2]))
1021
+ throw new OmpTypeError("narrow operator requires a predicate");
1022
+ const predicate = def[2];
1023
+ const name = predicate.name;
1024
+ const expected = name.length === 0 ? "valid according to an anonymous predicate" : `valid according to ${name}`;
1025
+ return {
1026
+ k: "refine",
1027
+ base: parseDef(def[0], resolve),
1028
+ pred: value => {
1029
+ let errors;
1030
+ const error = (input) => {
1031
+ const detail = typeof input === "string" ? { expected: input } : input;
1032
+ const next = OmpErrors.single([...(detail.path ?? detail.relativePath ?? [])], detail.expected, value, {
1033
+ preserveActual: true,
1034
+ ...(Object.hasOwn(detail, "actual") ? { actual: String(detail.actual) } : {}),
1035
+ });
1036
+ if (errors)
1037
+ errors.append(next);
1038
+ else
1039
+ errors = next;
1040
+ return next;
1041
+ };
1042
+ const result = predicate(value, { error, reject: error });
1043
+ return errors ?? (result instanceof OmpErrors ? result : result === true);
1044
+ },
1045
+ expected,
1046
+ };
1047
+ }
1048
+ if (def.length >= 3 && def[1] === "@") {
1049
+ const base = parseDef(def[0], resolve);
1050
+ const meta = def[2];
1051
+ if (typeof meta === "string")
1052
+ return { ...base, desc: meta, cfg: { ...base.cfg, expected: meta } };
1053
+ if (typeof meta === "object" && meta !== null) {
1054
+ const config = meta;
1055
+ return {
1056
+ ...base,
1057
+ cfg: {
1058
+ ...(typeof config.description === "string" ? { expected: config.description } : {}),
1059
+ ...config,
1060
+ },
1061
+ ...(typeof config.description === "string" ? { desc: config.description } : {}),
1062
+ };
1063
+ }
1064
+ return base;
1065
+ }
1066
+ return parseTuple(def, resolve);
1067
+ }
1068
+ function isObjectDefinition(def) {
1069
+ return (typeof def === "object" &&
1070
+ def !== null &&
1071
+ !Array.isArray(def) &&
1072
+ !(def instanceof RegExp) &&
1073
+ !(def instanceof Date));
1074
+ }
1075
+ function spreadObjectOf(ir) {
1076
+ if (ir.k === "alias")
1077
+ return spreadObjectOf(ir.resolve());
1078
+ if (ir.k === "sub")
1079
+ return spreadObjectOf(ir.schema.ir);
1080
+ if (ir.k === "refine")
1081
+ return spreadObjectOf(ir.base);
1082
+ if (ir.k === "anyobject")
1083
+ return { k: "object", props: [], extras: "keep" };
1084
+ if (ir.k === "object")
1085
+ return ir;
1086
+ if (ir.k !== "intersection")
1087
+ return undefined;
1088
+ let result = { k: "object", props: [], extras: "keep" };
1089
+ for (const member of ir.members) {
1090
+ const object = spreadObjectOf(member);
1091
+ if (object === undefined)
1092
+ return undefined;
1093
+ result = mergeObjectIR(result, object);
1094
+ }
1095
+ return result;
1096
+ }
1097
+ function indexKeyKind(key, value, props, indexes) {
1098
+ if (key.k === "alias")
1099
+ return indexKeyKind(key.resolve(), value, props, indexes);
1100
+ if (key.k === "union") {
1101
+ for (const member of key.members)
1102
+ indexKeyKind(member, value, props, indexes);
1103
+ return;
1104
+ }
1105
+ if (key.k === "lit" && (typeof key.v === "string" || typeof key.v === "symbol")) {
1106
+ props.push({ key: key.v, opt: false, val: value });
1107
+ return;
1108
+ }
1109
+ if (key.k === "string") {
1110
+ indexes.string = value;
1111
+ return;
1112
+ }
1113
+ if (key.k === "symbol") {
1114
+ indexes.symbol = value;
1115
+ return;
1116
+ }
1117
+ if (key.k === "refine" && key.base.k === "string") {
1118
+ indexes.patterns.push({ key, val: value });
1119
+ return;
1120
+ }
1121
+ throw new OmpTypeError(`indexed key definition must resolve to a string or symbol (was ${expectedOf(key)})`);
1122
+ }
1123
+ function addObjectProp(props, spreadKeys, prop) {
1124
+ if (spreadKeys === undefined) {
1125
+ props.push(prop);
1126
+ return;
1127
+ }
1128
+ const previous = props.findIndex(candidate => candidate.key === prop.key);
1129
+ if (previous < 0) {
1130
+ props.push(prop);
1131
+ return;
1132
+ }
1133
+ if (!spreadKeys.delete(prop.key))
1134
+ throw new OmpTypeError(`duplicate object key ${String(prop.key)}`);
1135
+ props[previous] = prop;
1136
+ }
1137
+ function parseObjectDefinition(def, resolve) {
1138
+ const props = [];
1139
+ let spreadKeys;
1140
+ let normalizedKey;
1141
+ let normalizedKeys;
1142
+ let indexes;
1143
+ let extras = "keep";
1144
+ let simple = true;
1145
+ for (const originalKey in def) {
1146
+ if (!Object.hasOwn(def, originalKey))
1147
+ continue;
1148
+ const val = def[originalKey];
1149
+ if (originalKey === "+") {
1150
+ if (val === "reject" || val === "delete") {
1151
+ extras = val;
1152
+ if (val === "delete")
1153
+ simple = false;
1154
+ }
1155
+ else if (val === "ignore")
1156
+ extras = "keep";
1157
+ else
1158
+ throw new OmpTypeError(`bad "+" value ${String(val)}`);
1159
+ continue;
1160
+ }
1161
+ if (originalKey === "...") {
1162
+ const parsed = parseDef(val, resolve);
1163
+ const spread = spreadObjectOf(parsed);
1164
+ if (spread === undefined) {
1165
+ throw new OmpTypeError(`object spread must resolve to an object literal (was ${expectedOf(parsed)})`);
1166
+ }
1167
+ if (simple && !isSimpleIR(spread))
1168
+ simple = false;
1169
+ spreadKeys ??= new Set();
1170
+ for (const prop of spread.props) {
1171
+ const previous = props.findIndex(candidate => candidate.key === prop.key);
1172
+ if (previous < 0)
1173
+ props.push(prop);
1174
+ else
1175
+ props[previous] = prop;
1176
+ spreadKeys.add(prop.key);
1177
+ }
1178
+ if (spread.index !== undefined || spread.symbolIndex !== undefined || spread.patternIndexes !== undefined) {
1179
+ const objectIndexes = indexes ?? { patterns: [] };
1180
+ indexes = objectIndexes;
1181
+ objectIndexes.string ??= spread.index;
1182
+ objectIndexes.symbol ??= spread.symbolIndex;
1183
+ if (spread.patternIndexes !== undefined)
1184
+ objectIndexes.patterns.push(...spread.patternIndexes);
1185
+ }
1186
+ if (spread.extras !== "keep")
1187
+ extras = spread.extras;
1188
+ continue;
1189
+ }
1190
+ if (typeof originalKey === "string" && originalKey.startsWith("[") && originalKey.endsWith("]")) {
1191
+ let value;
1192
+ if (typeof val === "string") {
1193
+ const parsed = parseStringDef(val, resolve);
1194
+ if (parsed.hasDefault)
1195
+ throw new OmpTypeError("index signatures cannot specify a default");
1196
+ value = parsed.ir;
1197
+ }
1198
+ else {
1199
+ if (Array.isArray(val) && val.length === 3 && val[1] === "=") {
1200
+ throw new OmpTypeError("index signatures cannot specify a default");
1201
+ }
1202
+ value = parseDef(val, resolve);
1203
+ if (isEmbedded(val) && val.hasDefault) {
1204
+ throw new OmpTypeError("index signatures cannot specify a default");
1205
+ }
1206
+ }
1207
+ const keyDefinition = originalKey.slice(1, -1);
1208
+ const regex = /^\/((?:\\.|[^\\/])*)\/([dgimsuvy]*)$/.exec(keyDefinition);
1209
+ let key;
1210
+ if (regex === null) {
1211
+ key = parseDef(keyDefinition, resolve);
1212
+ }
1213
+ else {
1214
+ try {
1215
+ key = patternIR(new RegExp(regex[1], regex[2]));
1216
+ }
1217
+ catch {
1218
+ throw new OmpTypeError(`invalid index signature pattern ${keyDefinition}`);
1219
+ }
1220
+ }
1221
+ const objectIndexes = indexes ?? { patterns: [] };
1222
+ indexes = objectIndexes;
1223
+ indexKeyKind(key, value, props, objectIndexes);
1224
+ if (simple && (!isSimpleIR(key) || !isSimpleIR(value)))
1225
+ simple = false;
1226
+ continue;
1227
+ }
1228
+ const escapedOptional = typeof originalKey === "string" && originalKey.endsWith("\\?");
1229
+ const escapedMeta = typeof originalKey === "string" &&
1230
+ (originalKey === "\\+" || originalKey === "\\..." || originalKey.startsWith("\\["));
1231
+ const rawKey = escapedOptional
1232
+ ? `${originalKey.slice(0, -2)}?`
1233
+ : escapedMeta
1234
+ ? originalKey.slice(1)
1235
+ : originalKey;
1236
+ const opt = typeof rawKey === "string" && !escapedOptional && !escapedMeta && rawKey.endsWith("?");
1237
+ const key = opt ? rawKey.slice(0, -1) : rawKey;
1238
+ let prop;
1239
+ if (typeof val === "string") {
1240
+ const parsed = parseStringDef(val, resolve);
1241
+ prop = { key, opt: opt || parsed.optional, val: parsed.ir };
1242
+ if (parsed.hasDefault) {
1243
+ prop.def = parsed.def;
1244
+ prop.hasDefault = true;
1245
+ }
1246
+ }
1247
+ else if (Array.isArray(val) && val.length === 2 && val[1] === "?") {
1248
+ prop = { key, opt: true, val: parseDef(val[0], resolve) };
1249
+ }
1250
+ else if (Array.isArray(val) && val.length === 3 && val[1] === "=") {
1251
+ prop = {
1252
+ key,
1253
+ opt,
1254
+ val: parseDef(val[0], resolve),
1255
+ def: val[2],
1256
+ defFactory: typeof val[2] === "function",
1257
+ hasDefault: true,
1258
+ };
1259
+ }
1260
+ else if (isEmbedded(val)) {
1261
+ prop = val.hasDefault
1262
+ ? {
1263
+ key,
1264
+ opt,
1265
+ val: embed(val),
1266
+ def: val.hasDefaultOutput ? val.defaultOutput : val.defaultValue,
1267
+ defFactory: typeof val.defaultValue === "function",
1268
+ hasDefault: true,
1269
+ defValidated: val.hasDefaultOutput,
1270
+ }
1271
+ : { key, opt, val: embed(val) };
1272
+ }
1273
+ else {
1274
+ prop = {
1275
+ key,
1276
+ opt,
1277
+ val: isObjectDefinition(val) ? parseObjectDefinition(val, resolve) : parseDef(val, resolve),
1278
+ };
1279
+ }
1280
+ if (key !== originalKey) {
1281
+ if (!spreadKeys?.has(key) && props.some(candidate => candidate.key === key)) {
1282
+ throw new OmpTypeError(`duplicate object key ${String(key)}`);
1283
+ }
1284
+ if (normalizedKey === undefined)
1285
+ normalizedKey = key;
1286
+ else {
1287
+ normalizedKeys ??= [normalizedKey];
1288
+ normalizedKeys.push(key);
1289
+ }
1290
+ }
1291
+ else if (!spreadKeys?.has(key) && (key === normalizedKey || normalizedKeys?.includes(key))) {
1292
+ throw new OmpTypeError(`duplicate object key ${String(key)}`);
1293
+ }
1294
+ if (opt && prop.hasDefault)
1295
+ throw new OmpTypeError(`optional key ${String(key)} cannot specify a default`);
1296
+ if (simple && (prop.hasDefault || !isSimpleIR(prop.val)))
1297
+ simple = false;
1298
+ addObjectProp(props, spreadKeys, prop);
1299
+ }
1300
+ for (const key of Object.getOwnPropertySymbols(def)) {
1301
+ if (!Object.prototype.propertyIsEnumerable.call(def, key))
1302
+ continue;
1303
+ const val = def[key];
1304
+ let prop;
1305
+ if (typeof val === "string") {
1306
+ const parsed = parseStringDef(val, resolve);
1307
+ prop = { key, opt: parsed.optional, val: parsed.ir };
1308
+ if (parsed.hasDefault) {
1309
+ prop.def = parsed.def;
1310
+ prop.hasDefault = true;
1311
+ }
1312
+ }
1313
+ else if (Array.isArray(val) && val.length === 2 && val[1] === "?") {
1314
+ prop = { key, opt: true, val: parseDef(val[0], resolve) };
1315
+ }
1316
+ else if (Array.isArray(val) && val.length === 3 && val[1] === "=") {
1317
+ prop = {
1318
+ key,
1319
+ opt: false,
1320
+ val: parseDef(val[0], resolve),
1321
+ def: val[2],
1322
+ defFactory: typeof val[2] === "function",
1323
+ hasDefault: true,
1324
+ };
1325
+ }
1326
+ else if (isEmbedded(val)) {
1327
+ prop = val.hasDefault
1328
+ ? {
1329
+ key,
1330
+ opt: false,
1331
+ val: embed(val),
1332
+ def: val.hasDefaultOutput ? val.defaultOutput : val.defaultValue,
1333
+ defFactory: typeof val.defaultValue === "function",
1334
+ hasDefault: true,
1335
+ defValidated: val.hasDefaultOutput,
1336
+ }
1337
+ : { key, opt: false, val: embed(val) };
1338
+ }
1339
+ else {
1340
+ prop = {
1341
+ key,
1342
+ opt: false,
1343
+ val: isObjectDefinition(val) ? parseObjectDefinition(val, resolve) : parseDef(val, resolve),
1344
+ };
1345
+ }
1346
+ if (simple && (prop.hasDefault || !isSimpleIR(prop.val)))
1347
+ simple = false;
1348
+ addObjectProp(props, spreadKeys, prop);
1349
+ }
1350
+ const object = {
1351
+ k: "object",
1352
+ props,
1353
+ index: indexes?.string,
1354
+ symbolIndex: indexes?.symbol,
1355
+ patternIndexes: indexes === undefined || indexes.patterns.length === 0 ? undefined : indexes.patterns,
1356
+ extras,
1357
+ };
1358
+ object[kSimple] = simple;
1359
+ object[kSimpleOwner] = object;
1360
+ return object;
1361
+ }
1362
+ /** Parse a definition, optionally resolving names from an enclosing scope. */
1363
+ export function parseDef(def, resolve) {
1364
+ if (typeof def === "string") {
1365
+ const parsed = parseStringDef(def, resolve);
1366
+ if (parsed.hasDefault) {
1367
+ throw new OmpTypeError("A default may only be specified for an object property or tuple element");
1368
+ }
1369
+ if (parsed.optional) {
1370
+ throw new OmpTypeError(`optional "?" marker is only valid on object property values`);
1371
+ }
1372
+ return parsed.ir;
1373
+ }
1374
+ if (Array.isArray(def)) {
1375
+ if (def.length === 3 && def[1] === "=") {
1376
+ throw new OmpTypeError("A default may only be specified for an object property or tuple element");
1377
+ }
1378
+ return parseArrayExpression(def, resolve);
1379
+ }
1380
+ if (def instanceof RegExp)
1381
+ return patternIR(def);
1382
+ if (def instanceof Date)
1383
+ return { k: "lit", v: def };
1384
+ if (isEmbedded(def))
1385
+ return embed(def);
1386
+ if (typeof def === "function") {
1387
+ const resolved = Reflect.apply(def, undefined, []);
1388
+ if (!isEmbedded(resolved)) {
1389
+ throw new OmpTypeError(`thunk must return a Type (was ${typeof resolved})`);
1390
+ }
1391
+ return embed(resolved);
1392
+ }
1393
+ if (isObjectDefinition(def))
1394
+ return parseObjectDefinition(def, resolve);
1395
+ throw new OmpTypeError(`unsupported definition ${String(def)} (was ${typeof def})`);
1396
+ }
1397
+ /** Whether `ir` needs no construction-time normalization or morph analysis. */
1398
+ export function isSimpleIR(ir) {
1399
+ const cached = ir[kSimpleOwner] === ir ? ir[kSimple] : undefined;
1400
+ if (cached !== undefined)
1401
+ return cached;
1402
+ const simple = scanSimpleIR(ir);
1403
+ ir[kSimple] = simple;
1404
+ ir[kSimpleOwner] = ir;
1405
+ return simple;
1406
+ }
1407
+ function scanSimpleIR(ir) {
1408
+ switch (ir.k) {
1409
+ case "intersection":
1410
+ case "morph":
1411
+ case "sub":
1412
+ case "alias":
1413
+ return false;
1414
+ case "refine":
1415
+ return scanSimpleIR(ir.base);
1416
+ case "union":
1417
+ if (ir.members.length < 2)
1418
+ return false;
1419
+ if (ir.members.length === 2 &&
1420
+ ir.members.every(member => member.k === "lit" && typeof member.v === "boolean")) {
1421
+ return false;
1422
+ }
1423
+ for (let index = 0; index < ir.members.length; index++) {
1424
+ const member = ir.members[index];
1425
+ if (member.k !== "lit" ||
1426
+ (member.v !== null && (typeof member.v === "object" || typeof member.v === "function"))) {
1427
+ return false;
1428
+ }
1429
+ for (let previous = 0; previous < index; previous++) {
1430
+ const candidate = ir.members[previous];
1431
+ if (candidate.k === "lit" && candidate.v === member.v)
1432
+ return false;
1433
+ }
1434
+ }
1435
+ return true;
1436
+ case "array":
1437
+ return scanSimpleIR(ir.el);
1438
+ case "tuple":
1439
+ for (const item of ir.prefix) {
1440
+ if (item.hasDefault || !scanSimpleIR(item.val))
1441
+ return false;
1442
+ }
1443
+ if (ir.variadic !== undefined && !scanSimpleIR(ir.variadic))
1444
+ return false;
1445
+ for (const item of ir.postfix)
1446
+ if (!scanSimpleIR(item))
1447
+ return false;
1448
+ return true;
1449
+ case "object":
1450
+ if (ir.extras === "delete")
1451
+ return false;
1452
+ for (const prop of ir.props) {
1453
+ if (prop.hasDefault || !scanSimpleIR(prop.val))
1454
+ return false;
1455
+ }
1456
+ if (ir.index !== undefined && !scanSimpleIR(ir.index))
1457
+ return false;
1458
+ if (ir.symbolIndex !== undefined && !scanSimpleIR(ir.symbolIndex))
1459
+ return false;
1460
+ if (ir.patternIndexes !== undefined) {
1461
+ for (const pattern of ir.patternIndexes) {
1462
+ if (!scanSimpleIR(pattern.key) || !scanSimpleIR(pattern.val))
1463
+ return false;
1464
+ }
1465
+ }
1466
+ return true;
1467
+ default:
1468
+ return true;
1469
+ }
1470
+ }
1471
+ /** True when validating `ir` can produce an output different from its input. */
1472
+ export function hasMorph(ir) {
1473
+ const cached = ir[kMorphOwner] === ir ? ir[kMorph] : undefined;
1474
+ if (cached !== undefined)
1475
+ return cached;
1476
+ const result = scanMorph(ir);
1477
+ ir[kMorph] = result;
1478
+ ir[kMorphOwner] = ir;
1479
+ return result;
1480
+ }
1481
+ function scanMorph(ir, activeAliases) {
1482
+ const cached = ir[kMorphOwner] === ir ? ir[kMorph] : undefined;
1483
+ if (cached !== undefined)
1484
+ return cached;
1485
+ let result = false;
1486
+ switch (ir.k) {
1487
+ case "sub":
1488
+ case "morph":
1489
+ result = true;
1490
+ break;
1491
+ case "alias": {
1492
+ if (activeAliases?.has(ir))
1493
+ return false;
1494
+ const aliases = activeAliases ?? new Set();
1495
+ aliases.add(ir);
1496
+ result = scanMorph(ir.resolve(), aliases);
1497
+ aliases.delete(ir);
1498
+ return result;
1499
+ }
1500
+ case "object":
1501
+ result = ir.extras === "delete";
1502
+ for (let index = 0; !result && index < ir.props.length; index++) {
1503
+ const prop = ir.props[index];
1504
+ result = prop.hasDefault === true || scanMorph(prop.val, activeAliases);
1505
+ }
1506
+ if (!result && ir.index !== undefined)
1507
+ result = scanMorph(ir.index, activeAliases);
1508
+ if (!result && ir.symbolIndex !== undefined)
1509
+ result = scanMorph(ir.symbolIndex, activeAliases);
1510
+ if (!result && ir.patternIndexes !== undefined) {
1511
+ for (const pattern of ir.patternIndexes) {
1512
+ if (scanMorph(pattern.val, activeAliases)) {
1513
+ result = true;
1514
+ break;
1515
+ }
1516
+ }
1517
+ }
1518
+ break;
1519
+ case "array":
1520
+ result = scanMorph(ir.el, activeAliases);
1521
+ break;
1522
+ case "union":
1523
+ case "intersection":
1524
+ for (const member of ir.members) {
1525
+ if (scanMorph(member, activeAliases)) {
1526
+ result = true;
1527
+ break;
1528
+ }
1529
+ }
1530
+ break;
1531
+ case "refine":
1532
+ result = scanMorph(ir.base, activeAliases);
1533
+ break;
1534
+ case "tuple":
1535
+ for (const item of ir.prefix) {
1536
+ if (item.hasDefault === true || scanMorph(item.val, activeAliases)) {
1537
+ result = true;
1538
+ break;
1539
+ }
1540
+ }
1541
+ if (!result && ir.variadic !== undefined)
1542
+ result = scanMorph(ir.variadic, activeAliases);
1543
+ if (!result) {
1544
+ for (const item of ir.postfix) {
1545
+ if (scanMorph(item, activeAliases)) {
1546
+ result = true;
1547
+ break;
1548
+ }
1549
+ }
1550
+ }
1551
+ break;
1552
+ }
1553
+ return result;
1554
+ }
1555
+ /**
1556
+ * True when a traversal of `ir` can revisit nodes through recursive aliases,
1557
+ * requiring cycle guards in the interpreter. Embedded sub-schemas run their
1558
+ * own guarded traversal and are intentionally not inspected.
1559
+ */
1560
+ export function hasAlias(ir) {
1561
+ const cached = ir[kAliasOwner] === ir ? ir[kAlias] : undefined;
1562
+ if (cached !== undefined)
1563
+ return cached;
1564
+ const result = scanAlias(ir);
1565
+ ir[kAlias] = result;
1566
+ ir[kAliasOwner] = ir;
1567
+ return result;
1568
+ }
1569
+ function scanAlias(ir) {
1570
+ const cached = ir[kAliasOwner] === ir ? ir[kAlias] : undefined;
1571
+ if (cached !== undefined)
1572
+ return cached;
1573
+ switch (ir.k) {
1574
+ case "alias":
1575
+ return true;
1576
+ case "object":
1577
+ for (const prop of ir.props)
1578
+ if (scanAlias(prop.val))
1579
+ return true;
1580
+ if (ir.index !== undefined && scanAlias(ir.index))
1581
+ return true;
1582
+ if (ir.symbolIndex !== undefined && scanAlias(ir.symbolIndex))
1583
+ return true;
1584
+ if (ir.patternIndexes !== undefined) {
1585
+ for (const pattern of ir.patternIndexes) {
1586
+ if (scanAlias(pattern.key) || scanAlias(pattern.val))
1587
+ return true;
1588
+ }
1589
+ }
1590
+ return false;
1591
+ case "array":
1592
+ return scanAlias(ir.el);
1593
+ case "tuple":
1594
+ for (const item of ir.prefix)
1595
+ if (scanAlias(item.val))
1596
+ return true;
1597
+ if (ir.variadic !== undefined && scanAlias(ir.variadic))
1598
+ return true;
1599
+ for (const item of ir.postfix)
1600
+ if (scanAlias(item))
1601
+ return true;
1602
+ return false;
1603
+ case "union":
1604
+ case "intersection":
1605
+ for (const member of ir.members)
1606
+ if (scanAlias(member))
1607
+ return true;
1608
+ return false;
1609
+ case "refine":
1610
+ return scanAlias(ir.base);
1611
+ case "morph":
1612
+ return scanAlias(ir.input) || (ir.out !== undefined && scanAlias(ir.out));
1613
+ default:
1614
+ return false;
1615
+ }
1616
+ }
1617
+ /** Human-readable expectation for error messages, e.g. `"a string"`. */
1618
+ export function expectedOf(ir) {
1619
+ if (ir.desc !== undefined)
1620
+ return ir.desc;
1621
+ switch (ir.k) {
1622
+ case "unknown":
1623
+ return "unknown";
1624
+ case "null":
1625
+ return "null";
1626
+ case "undefined":
1627
+ return "undefined";
1628
+ case "boolean":
1629
+ return "boolean";
1630
+ case "bigint":
1631
+ return "a bigint";
1632
+ case "symbol":
1633
+ return "a symbol";
1634
+ case "never":
1635
+ return "never";
1636
+ case "anyobject":
1637
+ return "an object";
1638
+ case "string": {
1639
+ let out = ir.url ? "a URL string" : "a string";
1640
+ if (ir.min !== undefined && ir.max !== undefined)
1641
+ out += ` (length ${ir.min} to ${ir.max})`;
1642
+ else if (ir.min !== undefined)
1643
+ out += ` (length at least ${ir.min})`;
1644
+ else if (ir.max !== undefined)
1645
+ out += ` (length at most ${ir.max})`;
1646
+ return out;
1647
+ }
1648
+ case "number": {
1649
+ let out = ir.int ? "an integer" : "a number";
1650
+ if (ir.min !== undefined)
1651
+ out += ` ${ir.xmin ? "more than" : "at least"} ${ir.min}`;
1652
+ if (ir.max !== undefined)
1653
+ out += `${ir.min !== undefined ? " and" : ""} ${ir.xmax ? "less than" : "at most"} ${ir.max}`;
1654
+ if (ir.divisor !== undefined)
1655
+ out += ` divisible by ${ir.divisor}`;
1656
+ return out;
1657
+ }
1658
+ case "lit":
1659
+ return ir.v instanceof Date
1660
+ ? `the date ${ir.v.toISOString()}`
1661
+ : typeof ir.v === "string"
1662
+ ? JSON.stringify(ir.v)
1663
+ : String(ir.v);
1664
+ case "union": {
1665
+ if (ir.members.length === 0)
1666
+ return "";
1667
+ const first = expectedOf(ir.members[0]);
1668
+ if (ir.members.length === 1)
1669
+ return first;
1670
+ const second = expectedOf(ir.members[1]);
1671
+ if (ir.members.length === 2)
1672
+ return first === second ? first : `${first} or ${second}`;
1673
+ const expectations = first === second ? [first] : [first, second];
1674
+ for (let i = 2; i < ir.members.length; i++) {
1675
+ const expected = expectedOf(ir.members[i]);
1676
+ if (!expectations.includes(expected))
1677
+ expectations.push(expected);
1678
+ }
1679
+ return expectations.join(" or ");
1680
+ }
1681
+ case "intersection":
1682
+ return ir.members.map(expectedOf).join(" and ");
1683
+ case "array":
1684
+ return "an array";
1685
+ case "tuple":
1686
+ return "a tuple";
1687
+ case "object":
1688
+ return "an object";
1689
+ case "instance":
1690
+ return ir.expected;
1691
+ case "refine":
1692
+ return ir.expected;
1693
+ case "morph":
1694
+ return expectedOf(ir.input);
1695
+ case "alias":
1696
+ return ir.name === "this" ? expectedOf(ir.resolve()) : ir.name;
1697
+ case "sub":
1698
+ return ir.desc ?? ir.schema.description ?? expectedOf(ir.schema.ir);
1699
+ }
1700
+ }