@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.
@@ -0,0 +1,858 @@
1
+ /**
2
+ * Tree-walking validator used for a schema's first few calls and as the
3
+ * targeted fallback for recursive or predicate-only JIT subtrees.
4
+ *
5
+ * Semantics must stay in lockstep with `compile.ts`:
6
+ * - success returns the output value; the input is returned as-is unless the
7
+ * schema morphs (defaults, `"+": "delete"`, embedded stepped schemas), in
8
+ * which case a fresh object/array is produced and the input is untouched
9
+ * - failure returns an `OmpErrors` with a single fast-fail entry
10
+ */
11
+ import { MISSING, OmpErrors } from "./errors.js";
12
+ import { expectedOf, hasAlias, hasMorph } from "./ir.js";
13
+ const own = Object.prototype.hasOwnProperty;
14
+ /** Return an independent runtime value for a prevalidated static default. */
15
+ export function materializeDefault(payload) {
16
+ if (payload === null || typeof payload !== "object")
17
+ return payload;
18
+ if (payload instanceof Date)
19
+ return new Date(payload);
20
+ return structuredClone(payload);
21
+ }
22
+ let activeVisits;
23
+ let activeChecks;
24
+ /**
25
+ * Validate `value` against `ir`; returns output value or `OmpErrors`.
26
+ * `path` seeds the traversal location so nested step callbacks observe
27
+ * absolute ctx.path values when a compiled parent delegates a subtree;
28
+ * resulting error paths are then already absolute.
29
+ */
30
+ export function walk(ir, value, path = []) {
31
+ const previousVisits = activeVisits;
32
+ const previousChecks = activeChecks;
33
+ // Created lazily by visit/checks only when a recursive-alias node is reached.
34
+ activeVisits = undefined;
35
+ activeChecks = undefined;
36
+ try {
37
+ return visit(ir, value, path);
38
+ }
39
+ finally {
40
+ activeVisits = previousVisits;
41
+ activeChecks = previousChecks;
42
+ }
43
+ }
44
+ function fail(path, expected, data) {
45
+ const storedPath = path.length === 0 ? undefined : path.length === 1 ? path[0] : [...path];
46
+ return new OmpErrors(storedPath, expected, data);
47
+ }
48
+ /** Pure predicate used for union-member scanning (no morphs, no errors). */
49
+ function checks(ir, v) {
50
+ // Cycle guards are only needed when the subtree can revisit nodes through
51
+ // recursive aliases; plain schemas skip the WeakMap bookkeeping entirely.
52
+ if (typeof v !== "object" || v === null || !hasAlias(ir))
53
+ return checkNode(ir, v);
54
+ activeChecks ??= new WeakMap();
55
+ const visits = activeChecks;
56
+ let visited = visits.get(v);
57
+ if (visited?.has(ir))
58
+ return true;
59
+ if (visited === undefined) {
60
+ visited = new Set();
61
+ visits.set(v, visited);
62
+ }
63
+ visited.add(ir);
64
+ try {
65
+ return checkNode(ir, v);
66
+ }
67
+ finally {
68
+ visited.delete(ir);
69
+ }
70
+ }
71
+ function checkNode(ir, v) {
72
+ switch (ir.k) {
73
+ case "unknown":
74
+ return true;
75
+ case "null":
76
+ return v === null;
77
+ case "undefined":
78
+ return v === undefined;
79
+ case "boolean":
80
+ return typeof v === "boolean";
81
+ case "bigint":
82
+ return typeof v === "bigint";
83
+ case "symbol":
84
+ return typeof v === "symbol";
85
+ case "never":
86
+ return false;
87
+ case "anyobject":
88
+ return typeof v === "object" && v !== null;
89
+ case "string":
90
+ return (typeof v === "string" &&
91
+ (ir.min === undefined || v.length >= ir.min) &&
92
+ (ir.max === undefined || v.length <= ir.max) &&
93
+ (!ir.url || URL.canParse(v)));
94
+ case "number":
95
+ if (typeof v !== "number")
96
+ return false;
97
+ return ((ir.int ? Number.isInteger(v) : Number.isFinite(v)) &&
98
+ (ir.divisor === undefined || v % ir.divisor === 0) &&
99
+ (ir.min === undefined || (ir.xmin ? v > ir.min : v >= ir.min)) &&
100
+ (ir.max === undefined || (ir.xmax ? v < ir.max : v <= ir.max)));
101
+ case "lit":
102
+ return ir.v instanceof Date ? v instanceof Date && v.valueOf() === ir.v.valueOf() : v === ir.v;
103
+ case "union":
104
+ return ir.members.some(m => checks(m, v));
105
+ case "intersection":
106
+ return ir.members.every(member => checks(member, v));
107
+ case "array": {
108
+ if (!Array.isArray(v))
109
+ return false;
110
+ if (ir.min !== undefined && v.length < ir.min)
111
+ return false;
112
+ if (ir.max !== undefined && v.length > ir.max)
113
+ return false;
114
+ for (const element of v)
115
+ if (!checks(ir.el, element))
116
+ return false;
117
+ return true;
118
+ }
119
+ case "tuple": {
120
+ if (!Array.isArray(v))
121
+ return false;
122
+ let required = ir.postfix.length;
123
+ for (const item of ir.prefix)
124
+ if (!item.opt && !item.hasDefault)
125
+ required++;
126
+ if (v.length < required)
127
+ return false;
128
+ if (ir.variadic === undefined && v.length > ir.prefix.length + ir.postfix.length)
129
+ return false;
130
+ const postfixStart = v.length - ir.postfix.length;
131
+ const prefixCount = Math.min(ir.prefix.length, postfixStart);
132
+ for (let index = 0; index < prefixCount; index++) {
133
+ if (!checks(ir.prefix[index].val, v[index]))
134
+ return false;
135
+ }
136
+ for (let index = prefixCount; index < ir.prefix.length; index++) {
137
+ const item = ir.prefix[index];
138
+ if (!item.opt && !item.hasDefault)
139
+ return false;
140
+ }
141
+ if (ir.variadic !== undefined) {
142
+ for (let index = prefixCount; index < postfixStart; index++) {
143
+ if (!checks(ir.variadic, v[index]))
144
+ return false;
145
+ }
146
+ }
147
+ for (let index = 0; index < ir.postfix.length; index++) {
148
+ if (!checks(ir.postfix[index], v[postfixStart + index]))
149
+ return false;
150
+ }
151
+ return true;
152
+ }
153
+ case "object": {
154
+ if (typeof v !== "object" || v === null)
155
+ return false;
156
+ const rec = v;
157
+ for (const p of ir.props) {
158
+ const present = p.key in rec;
159
+ if (!present) {
160
+ if (!p.opt && !p.hasDefault)
161
+ return false;
162
+ continue;
163
+ }
164
+ if (!checks(p.val, rec[p.key]))
165
+ return false;
166
+ }
167
+ for (const key in rec) {
168
+ if (!own.call(rec, key))
169
+ continue;
170
+ if (ir.index !== undefined && !checks(ir.index, rec[key]))
171
+ return false;
172
+ let patternMatched = false;
173
+ if (ir.patternIndexes !== undefined) {
174
+ for (const pattern of ir.patternIndexes) {
175
+ if (!checks(pattern.key, key))
176
+ continue;
177
+ patternMatched = true;
178
+ if (!checks(pattern.val, rec[key]))
179
+ return false;
180
+ }
181
+ }
182
+ if (ir.extras === "reject" &&
183
+ ir.index === undefined &&
184
+ !patternMatched &&
185
+ !ir.props.some(prop => prop.key === key)) {
186
+ return false;
187
+ }
188
+ }
189
+ for (const key of Object.getOwnPropertySymbols(rec)) {
190
+ if (!Object.prototype.propertyIsEnumerable.call(rec, key))
191
+ continue;
192
+ if (ir.symbolIndex !== undefined && !checks(ir.symbolIndex, rec[key]))
193
+ return false;
194
+ if (ir.extras === "reject" && ir.symbolIndex === undefined && !ir.props.some(prop => prop.key === key)) {
195
+ return false;
196
+ }
197
+ }
198
+ return true;
199
+ }
200
+ case "instance":
201
+ return v instanceof ir.ctor;
202
+ case "refine":
203
+ if (!checks(ir.base, v))
204
+ return false;
205
+ try {
206
+ return ir.pred(v) === true;
207
+ }
208
+ catch {
209
+ return false;
210
+ }
211
+ case "alias":
212
+ return checks(ir.resolve(), v);
213
+ case "morph":
214
+ return checks(ir.input, v);
215
+ case "sub":
216
+ return !(ir.schema.run(v) instanceof OmpErrors);
217
+ }
218
+ }
219
+ function visit(ir, v, path) {
220
+ if (typeof v !== "object" || v === null || !hasAlias(ir))
221
+ return visitFinish(ir, v, path);
222
+ activeVisits ??= new WeakMap();
223
+ const visits = activeVisits;
224
+ let visited = visits.get(v);
225
+ if (visited?.has(ir))
226
+ return v;
227
+ if (visited === undefined) {
228
+ visited = new Set();
229
+ visits.set(v, visited);
230
+ }
231
+ visited.add(ir);
232
+ try {
233
+ return visitFinish(ir, v, path);
234
+ }
235
+ finally {
236
+ visited.delete(ir);
237
+ }
238
+ }
239
+ /** Run the node visitor, then apply node-local error configuration. */
240
+ function visitFinish(ir, v, path) {
241
+ const out = visitNode(ir, v, path);
242
+ if (!(out instanceof OmpErrors) || ir.cfg === undefined)
243
+ return out;
244
+ for (const error of out) {
245
+ if (error.path.length !== path.length)
246
+ return out;
247
+ }
248
+ return out.configure(ir.cfg);
249
+ }
250
+ function visitNode(ir, v, path) {
251
+ switch (ir.k) {
252
+ case "alias":
253
+ return visit(ir.resolve(), v, path);
254
+ case "refine": {
255
+ const base = visit(ir.base, v, path);
256
+ if (base instanceof OmpErrors)
257
+ return base;
258
+ try {
259
+ const result = ir.pred(base);
260
+ if (result instanceof OmpErrors)
261
+ return path.length === 0 ? result : prefixAll(result, path);
262
+ return result ? base : fail(path, ir.expected, base);
263
+ }
264
+ catch {
265
+ return fail(path, ir.expected, base);
266
+ }
267
+ }
268
+ case "morph": {
269
+ const input = visit(ir.input, v, path);
270
+ if (input instanceof OmpErrors)
271
+ return input;
272
+ const context = {
273
+ error: (expected, data = input) => fail(path, expected, data),
274
+ reject: (problem, data = input) => fail(path, problem, data),
275
+ };
276
+ const output = ir.fn(input, context);
277
+ if (output instanceof OmpErrors)
278
+ return output;
279
+ return ir.out === undefined ? output : visit(ir.out, output, path);
280
+ }
281
+ case "intersection": {
282
+ let output = v;
283
+ for (const member of ir.members) {
284
+ output = visit(member, output, path);
285
+ if (output instanceof OmpErrors)
286
+ return output;
287
+ }
288
+ return output;
289
+ }
290
+ case "sub": {
291
+ const out = ir.schema.run(v, path);
292
+ if (out instanceof OmpErrors) {
293
+ return path.length === 0 ? out : prefixAll(out, path);
294
+ }
295
+ return out;
296
+ }
297
+ case "union": {
298
+ // fast path: any pure member matching returns the input unchanged
299
+ for (const m of ir.members) {
300
+ if (m.k !== "sub" && checks(m, v)) {
301
+ if (hasMorph(m))
302
+ break;
303
+ return v;
304
+ }
305
+ }
306
+ let targeted;
307
+ let targetCount = 0;
308
+ for (const member of ir.members) {
309
+ if (member.k === "sub" || hasMorph(member)) {
310
+ const out = visit(member, v, path);
311
+ if (!(out instanceof OmpErrors))
312
+ return out;
313
+ if (kindMatches(unwrapBase(member), v)) {
314
+ targetCount++;
315
+ targeted ??= out;
316
+ }
317
+ }
318
+ }
319
+ if (targetCount === 1 && targeted !== undefined)
320
+ return targeted;
321
+ return ir.members.some(canRefineUnionFailure) ? unionFail(ir, v, path) : fail(path, expectedOf(ir), v);
322
+ }
323
+ case "array": {
324
+ if (!Array.isArray(v))
325
+ return fail(path, "an array", v);
326
+ if (ir.min !== undefined && v.length < ir.min)
327
+ return fail(path, `at least length ${ir.min}`, v.length);
328
+ if (ir.max !== undefined && v.length > ir.max)
329
+ return fail(path, `at most length ${ir.max}`, v.length);
330
+ const morph = hasMorph(ir.el);
331
+ const out = morph ? new Array(v.length) : v;
332
+ let errors;
333
+ for (let index = 0; index < v.length; index++) {
334
+ path.push(index);
335
+ const element = visit(ir.el, v[index], path);
336
+ path.pop();
337
+ if (element instanceof OmpErrors) {
338
+ if (errors)
339
+ errors.append(element);
340
+ else
341
+ errors = element;
342
+ }
343
+ else if (morph) {
344
+ out[index] = element;
345
+ }
346
+ }
347
+ return errors ?? out;
348
+ }
349
+ case "tuple": {
350
+ if (!Array.isArray(v))
351
+ return fail(path, "an array", v);
352
+ let required = ir.postfix.length;
353
+ for (const item of ir.prefix)
354
+ if (!item.opt && !item.hasDefault)
355
+ required++;
356
+ if (v.length < required)
357
+ return fail(path, `an array of at least length ${required}`, v);
358
+ const maximum = ir.prefix.length + ir.postfix.length;
359
+ if (ir.variadic === undefined && v.length > maximum) {
360
+ return fail(path, `an array of at most length ${maximum}`, v);
361
+ }
362
+ const postfixStart = v.length - ir.postfix.length;
363
+ const prefixCount = Math.min(ir.prefix.length, postfixStart);
364
+ const morph = hasMorph(ir);
365
+ const output = morph ? [...v] : v;
366
+ let errors;
367
+ for (let index = 0; index < prefixCount; index++) {
368
+ path.push(index);
369
+ const item = visit(ir.prefix[index].val, v[index], path);
370
+ path.pop();
371
+ if (item instanceof OmpErrors) {
372
+ if (errors)
373
+ errors.append(item);
374
+ else
375
+ errors = item;
376
+ }
377
+ else if (morph) {
378
+ output[index] = item;
379
+ }
380
+ }
381
+ for (let index = prefixCount; index < ir.prefix.length; index++) {
382
+ const item = ir.prefix[index];
383
+ if (item.hasDefault && morph) {
384
+ const payload = item.def;
385
+ if (item.defFactory && typeof payload === "function") {
386
+ path.push(index);
387
+ const resolved = visit(item.val, payload(), path);
388
+ path.pop();
389
+ if (resolved instanceof OmpErrors) {
390
+ if (errors)
391
+ errors.append(resolved);
392
+ else
393
+ errors = resolved;
394
+ }
395
+ else {
396
+ output[index] = resolved;
397
+ }
398
+ }
399
+ else {
400
+ output[index] = materializeDefault(payload);
401
+ }
402
+ }
403
+ else if (!item.opt) {
404
+ path.push(index);
405
+ const error = fail(path, expectedOf(item.val), MISSING);
406
+ path.pop();
407
+ if (errors)
408
+ errors.append(error);
409
+ else
410
+ errors = error;
411
+ }
412
+ }
413
+ if (ir.variadic !== undefined) {
414
+ for (let index = prefixCount; index < postfixStart; index++) {
415
+ path.push(index);
416
+ const item = visit(ir.variadic, v[index], path);
417
+ path.pop();
418
+ if (item instanceof OmpErrors) {
419
+ if (errors)
420
+ errors.append(item);
421
+ else
422
+ errors = item;
423
+ }
424
+ else if (morph) {
425
+ output[index] = item;
426
+ }
427
+ }
428
+ }
429
+ for (let index = 0; index < ir.postfix.length; index++) {
430
+ const inputIndex = postfixStart + index;
431
+ path.push(inputIndex);
432
+ const item = visit(ir.postfix[index], v[inputIndex], path);
433
+ path.pop();
434
+ if (item instanceof OmpErrors) {
435
+ if (errors)
436
+ errors.append(item);
437
+ else
438
+ errors = item;
439
+ }
440
+ else if (morph) {
441
+ output[inputIndex] = item;
442
+ }
443
+ }
444
+ return errors ?? output;
445
+ }
446
+ case "object": {
447
+ if (typeof v !== "object" || v === null)
448
+ return fail(path, "an object", v);
449
+ const rec = v;
450
+ const morph = hasMorph(ir);
451
+ let out;
452
+ let errors;
453
+ if (morph) {
454
+ if (ir.extras === "delete" &&
455
+ ir.index === undefined &&
456
+ ir.symbolIndex === undefined &&
457
+ ir.patternIndexes === undefined) {
458
+ out = {};
459
+ }
460
+ else {
461
+ out = { ...rec };
462
+ }
463
+ }
464
+ for (const p of ir.props) {
465
+ if (!(p.key in rec)) {
466
+ if (p.hasDefault && out) {
467
+ const payload = p.def;
468
+ if (p.defFactory && typeof payload === "function") {
469
+ path.push(p.key);
470
+ const resolved = visit(p.val, payload(), path);
471
+ path.pop();
472
+ if (resolved instanceof OmpErrors) {
473
+ if (errors)
474
+ errors.append(resolved);
475
+ else
476
+ errors = resolved;
477
+ }
478
+ else {
479
+ out[p.key] = resolved;
480
+ }
481
+ }
482
+ else {
483
+ out[p.key] = materializeDefault(payload);
484
+ }
485
+ continue;
486
+ }
487
+ if (p.opt || p.hasDefault)
488
+ continue;
489
+ path.push(p.key);
490
+ const error = fail(path, expectedOf(p.val), MISSING);
491
+ path.pop();
492
+ if (errors)
493
+ errors.append(error);
494
+ else
495
+ errors = error;
496
+ continue;
497
+ }
498
+ path.push(p.key);
499
+ const result = visit(p.val, rec[p.key], path);
500
+ path.pop();
501
+ if (result instanceof OmpErrors) {
502
+ if (errors)
503
+ errors.append(result);
504
+ else
505
+ errors = result;
506
+ }
507
+ else if (out) {
508
+ out[p.key] = result;
509
+ }
510
+ }
511
+ for (const key in rec) {
512
+ if (!own.call(rec, key))
513
+ continue;
514
+ let indexed = false;
515
+ if (ir.index !== undefined) {
516
+ indexed = true;
517
+ path.push(key);
518
+ const result = visit(ir.index, rec[key], path);
519
+ path.pop();
520
+ if (result instanceof OmpErrors) {
521
+ if (errors)
522
+ errors.append(result);
523
+ else
524
+ errors = result;
525
+ }
526
+ else if (out) {
527
+ out[key] = result;
528
+ }
529
+ }
530
+ if (ir.patternIndexes !== undefined) {
531
+ for (const pattern of ir.patternIndexes) {
532
+ if (!checks(pattern.key, key))
533
+ continue;
534
+ indexed = true;
535
+ path.push(key);
536
+ const result = visit(pattern.val, rec[key], path);
537
+ path.pop();
538
+ if (result instanceof OmpErrors) {
539
+ if (errors)
540
+ errors.append(result);
541
+ else
542
+ errors = result;
543
+ }
544
+ else if (out) {
545
+ out[key] = result;
546
+ }
547
+ }
548
+ }
549
+ if (ir.extras === "reject" && !indexed && !ir.props.some(prop => prop.key === key)) {
550
+ path.push(key);
551
+ const error = fail(path, "removed", rec[key]);
552
+ path.pop();
553
+ if (errors)
554
+ errors.append(error);
555
+ else
556
+ errors = error;
557
+ }
558
+ }
559
+ for (const key of Object.getOwnPropertySymbols(rec)) {
560
+ if (!Object.prototype.propertyIsEnumerable.call(rec, key))
561
+ continue;
562
+ if (ir.symbolIndex !== undefined) {
563
+ path.push(key);
564
+ const result = visit(ir.symbolIndex, rec[key], path);
565
+ path.pop();
566
+ if (result instanceof OmpErrors) {
567
+ if (errors)
568
+ errors.append(result);
569
+ else
570
+ errors = result;
571
+ }
572
+ else if (out) {
573
+ out[key] = result;
574
+ }
575
+ }
576
+ else if (ir.extras === "reject" && !ir.props.some(prop => prop.key === key)) {
577
+ path.push(key);
578
+ const error = fail(path, "removed", rec[key]);
579
+ path.pop();
580
+ if (errors)
581
+ errors.append(error);
582
+ else
583
+ errors = error;
584
+ }
585
+ }
586
+ return errors ?? out ?? v;
587
+ }
588
+ case "string": {
589
+ if (typeof v !== "string")
590
+ return fail(path, "a string", v);
591
+ if (ir.min !== undefined && v.length < ir.min)
592
+ return fail(path, `at least length ${ir.min}`, v.length);
593
+ if (ir.max !== undefined && v.length > ir.max)
594
+ return fail(path, `at most length ${ir.max}`, v.length);
595
+ if (ir.url && !URL.canParse(v))
596
+ return fail(path, "a URL string", v);
597
+ return v;
598
+ }
599
+ case "number": {
600
+ if (typeof v !== "number" || !Number.isFinite(v))
601
+ return fail(path, ir.int ? "an integer" : "a number", v);
602
+ let errors;
603
+ const add = (expected) => {
604
+ const error = fail(path, expected, v);
605
+ if (errors)
606
+ errors.append(error);
607
+ else
608
+ errors = error;
609
+ };
610
+ if (ir.int && !Number.isInteger(v))
611
+ add("an integer");
612
+ if (ir.divisor !== undefined && v % ir.divisor !== 0)
613
+ add(`a number divisible by ${ir.divisor}`);
614
+ if (ir.min !== undefined && (ir.xmin ? v <= ir.min : v < ir.min)) {
615
+ add(ir.min === 0
616
+ ? ir.xmin
617
+ ? "positive"
618
+ : "non-negative"
619
+ : `a number ${ir.xmin ? "more than" : "at least"} ${ir.min}`);
620
+ }
621
+ if (ir.max !== undefined && (ir.xmax ? v >= ir.max : v > ir.max)) {
622
+ add(ir.max === 0
623
+ ? ir.xmax
624
+ ? "negative"
625
+ : "non-positive"
626
+ : `a number ${ir.xmax ? "less than" : "at most"} ${ir.max}`);
627
+ }
628
+ return errors ?? v;
629
+ }
630
+ case "lit": {
631
+ if (checks(ir, v))
632
+ return v;
633
+ if ((typeof ir.v === "object" && ir.v !== null) || typeof ir.v === "function") {
634
+ let expected = "the specified reference";
635
+ try {
636
+ const serialized = JSON.stringify(ir.v);
637
+ if (serialized !== undefined) {
638
+ expected = `reference equal to ${serialized}`;
639
+ if (typeof v === "object" && v !== null && JSON.stringify(v) === serialized) {
640
+ expected += " (serialized to the same value)";
641
+ }
642
+ }
643
+ }
644
+ catch {
645
+ // Cyclic values still get a useful reference-identity expectation.
646
+ }
647
+ return fail(path, expected, v);
648
+ }
649
+ return fail(path, expectedOf(ir), v);
650
+ }
651
+ default:
652
+ return checks(ir, v) ? v : fail(path, expectedOf(ir), v);
653
+ }
654
+ }
655
+ function prefixAll(errs, path) {
656
+ for (let i = path.length - 1; i >= 0; i--)
657
+ errs.prefix(path[i]);
658
+ return errs;
659
+ }
660
+ /** True when a union failure can be replaced with a more specific nested error. */
661
+ export function canRefineUnionFailure(member) {
662
+ const base = unwrapBase(member);
663
+ if (base.k === "array" || base.k === "object")
664
+ return true;
665
+ if (base.k === "string")
666
+ return base.min !== undefined || base.max !== undefined || base.url === true;
667
+ return base.k === "number" && (base.int === true || base.min !== undefined || base.max !== undefined);
668
+ }
669
+ /**
670
+ * Detailed failure for a union: descend into the member the value was clearly
671
+ * aimed at — unique runtime-kind match, else an object member whose literal
672
+ * discriminant property (e.g. `type: "'computer_call'"`) equals the value's —
673
+ * for a precise nested error (paths, narrow messages) instead of the coarse
674
+ * "A or B" expectation.
675
+ */
676
+ export function unionFail(ir, v, path, expected) {
677
+ let best;
678
+ for (const member of ir.members) {
679
+ const base = unwrapBase(member);
680
+ if (!kindMatches(base, v))
681
+ continue;
682
+ if (best !== undefined) {
683
+ best = undefined;
684
+ break;
685
+ }
686
+ best = member;
687
+ }
688
+ if (best === undefined) {
689
+ const discriminated = discriminateFailure(ir.members, v, path);
690
+ if (discriminated !== undefined)
691
+ return discriminated;
692
+ }
693
+ if (best !== undefined) {
694
+ const out = visit(best, v, path);
695
+ if (out instanceof OmpErrors)
696
+ return out;
697
+ }
698
+ if (ir.members.every(member => unwrapBase(member).k === "object")) {
699
+ const branches = ir.members.flatMap(member => {
700
+ const result = visit(member, v, path);
701
+ return result instanceof OmpErrors ? [[...result]] : [];
702
+ });
703
+ if (branches.length !== 0) {
704
+ const common = branches[0].filter((entry, index, first) => first.findIndex(candidate => pathsEqual(candidate.path, entry.path)) === index &&
705
+ branches.every(branch => branch.some(candidate => pathsEqual(candidate.path, entry.path))));
706
+ const alternatives = [];
707
+ if (common.length !== 0) {
708
+ for (const entry of common) {
709
+ const expectations = new Set();
710
+ for (const branch of branches) {
711
+ for (const candidate of branch) {
712
+ if (pathsEqual(candidate.path, entry.path)) {
713
+ expectations.add(candidate.expected.endsWith(" instance") ? "an object" : candidate.expected);
714
+ }
715
+ }
716
+ }
717
+ alternatives.push(new OmpErrors(entry.path, [...expectations].join(" or "), entry.data, { preserveActual: true }));
718
+ }
719
+ }
720
+ else {
721
+ for (const branch of branches) {
722
+ for (const entry of branch) {
723
+ alternatives.push(new OmpErrors(entry.path, entry.expected.endsWith(" instance") ? "an object" : entry.expected, entry.data, { preserveActual: true }));
724
+ }
725
+ }
726
+ }
727
+ const combined = alternatives[0];
728
+ for (let index = 1; index < alternatives.length; index++)
729
+ combined.append(alternatives[index]);
730
+ return alternatives.length === 1 ? combined : combined.asAlternatives();
731
+ }
732
+ }
733
+ return fail(path, expected ?? expectedOf(ir), v);
734
+ }
735
+ function unwrapBase(member, seen = new Set()) {
736
+ if (seen.has(member))
737
+ return member;
738
+ seen.add(member);
739
+ if (member.k === "sub")
740
+ return unwrapBase(member.schema.ir, seen);
741
+ if (member.k === "alias")
742
+ return unwrapBase(member.resolve(), seen);
743
+ if (member.k === "refine")
744
+ return unwrapBase(member.base, seen);
745
+ return member;
746
+ }
747
+ function collectDiscriminants(member, prefix = [], seen = new Set()) {
748
+ if (seen.has(member))
749
+ return [];
750
+ seen.add(member);
751
+ if (member.k === "alias")
752
+ return collectDiscriminants(member.resolve(), prefix, seen);
753
+ if (member.k === "sub")
754
+ return collectDiscriminants(member.schema.ir, prefix, seen);
755
+ if (member.k === "refine")
756
+ return collectDiscriminants(member.base, prefix, seen);
757
+ if (member.k !== "object")
758
+ return [];
759
+ const result = [];
760
+ for (const property of member.props) {
761
+ const propertyPath = [...prefix, property.key];
762
+ const value = unwrapBase(property.val);
763
+ if (value.k === "lit")
764
+ result.push({ path: propertyPath, value: value.v });
765
+ else
766
+ result.push(...collectDiscriminants(property.val, propertyPath, new Set(seen)));
767
+ }
768
+ return result;
769
+ }
770
+ function pathsEqual(left, right) {
771
+ return left.length === right.length && left.every((key, index) => key === right[index]);
772
+ }
773
+ function valueAtPath(value, path) {
774
+ let cursor = value;
775
+ for (const key of path) {
776
+ if ((typeof cursor !== "object" && typeof cursor !== "function") || cursor === null || !(key in cursor)) {
777
+ return { present: false };
778
+ }
779
+ cursor = cursor[key];
780
+ }
781
+ return { present: true, value: cursor };
782
+ }
783
+ function discriminateFailure(members, value, path) {
784
+ if (typeof value !== "object" || value === null || Array.isArray(value))
785
+ return undefined;
786
+ const byMember = members.map(member => collectDiscriminants(member));
787
+ const candidates = [];
788
+ for (const discriminants of byMember) {
789
+ for (const discriminant of discriminants) {
790
+ if (candidates.some(candidate => pathsEqual(candidate.path, discriminant.path)))
791
+ continue;
792
+ const values = [];
793
+ let declared = 0;
794
+ for (const branch of byMember) {
795
+ const match = branch.find(candidate => pathsEqual(candidate.path, discriminant.path));
796
+ if (match === undefined)
797
+ continue;
798
+ declared++;
799
+ if (!values.some(candidate => Object.is(candidate, match.value)))
800
+ values.push(match.value);
801
+ }
802
+ if (values.length > 1)
803
+ candidates.push({ path: discriminant.path, distinct: values.length, declared });
804
+ }
805
+ }
806
+ candidates.sort((left, right) => right.distinct - left.distinct || right.declared - left.declared);
807
+ for (const candidate of candidates) {
808
+ const actual = valueAtPath(value, candidate.path);
809
+ const exact = [];
810
+ const defaults = [];
811
+ const expectedMembers = [];
812
+ for (let index = 0; index < members.length; index++) {
813
+ const discriminant = byMember[index].find(item => pathsEqual(item.path, candidate.path));
814
+ if (discriminant === undefined) {
815
+ defaults.push(members[index]);
816
+ continue;
817
+ }
818
+ expectedMembers.push({ k: "lit", v: discriminant.value });
819
+ if (actual.present && Object.is(actual.value, discriminant.value))
820
+ exact.push(members[index]);
821
+ }
822
+ if (!actual.present) {
823
+ if (defaults.length !== 0 && defaults.length < members.length) {
824
+ return discriminateFailure(defaults, value, path);
825
+ }
826
+ return fail([...path, ...candidate.path], expectedOf({ k: "union", members: expectedMembers }), undefined);
827
+ }
828
+ if (exact.length === 0) {
829
+ if (defaults.length !== 0)
830
+ return discriminateFailure(defaults, value, path);
831
+ return fail([...path, ...candidate.path], expectedOf({ k: "union", members: expectedMembers }), actual.value);
832
+ }
833
+ if (exact.length === 1) {
834
+ const result = visit(exact[0], value, path);
835
+ return result instanceof OmpErrors ? result : undefined;
836
+ }
837
+ const nested = discriminateFailure(exact, value, path);
838
+ if (nested !== undefined)
839
+ return nested;
840
+ }
841
+ return undefined;
842
+ }
843
+ /** True when a value's runtime shape could only be aimed at this member. */
844
+ function kindMatches(base, v) {
845
+ switch (base.k) {
846
+ case "array":
847
+ return Array.isArray(v);
848
+ case "object":
849
+ case "anyobject":
850
+ return typeof v === "object" && v !== null && !Array.isArray(v);
851
+ case "string":
852
+ return typeof v === "string";
853
+ case "number":
854
+ return typeof v === "number";
855
+ default:
856
+ return false;
857
+ }
858
+ }