@routier/core 0.5.0 → 0.7.0
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/README.md +1 -1
- package/dist/assertions/index.cjs +19 -8
- package/dist/assertions/index.cjs.map +1 -1
- package/dist/assertions/index.d.ts +5 -1
- package/dist/assertions/index.js +21 -9
- package/dist/assertions/index.js.map +1 -1
- package/dist/collections/MemoryDataCollection.d.ts +10 -0
- package/dist/collections/index.cjs +29 -4
- package/dist/collections/index.cjs.map +1 -1
- package/dist/collections/index.js +29 -4
- package/dist/collections/index.js.map +1 -1
- package/dist/expressions/callSource.d.ts +41 -0
- package/dist/expressions/evaluate.d.ts +3 -0
- package/dist/expressions/fold.d.ts +7 -0
- package/dist/expressions/index.cjs +1754 -233
- package/dist/expressions/index.cjs.map +1 -1
- package/dist/expressions/index.d.ts +2 -0
- package/dist/expressions/index.js +1765 -234
- package/dist/expressions/index.js.map +1 -1
- package/dist/expressions/types.d.ts +45 -26
- package/dist/expressions/utils.d.ts +19 -1
- package/dist/index.cjs +2429 -363
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2769 -682
- package/dist/index.js.map +1 -1
- package/dist/performance/index.cjs +6 -4
- package/dist/performance/index.cjs.map +1 -1
- package/dist/performance/index.js +6 -4
- package/dist/performance/index.js.map +1 -1
- package/dist/pipeline/index.cjs +6 -4
- package/dist/pipeline/index.cjs.map +1 -1
- package/dist/pipeline/index.js +6 -4
- package/dist/pipeline/index.js.map +1 -1
- package/dist/plugins/index.cjs +2323 -316
- package/dist/plugins/index.cjs.map +1 -1
- package/dist/plugins/index.d.ts +1 -0
- package/dist/plugins/index.js +2328 -311
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/query/QueryOptionsCollection.d.ts +38 -10
- package/dist/plugins/query/describeFilter.d.ts +83 -0
- package/dist/plugins/query/explain.d.ts +71 -9
- package/dist/plugins/query/index.d.ts +1 -0
- package/dist/plugins/query/join.d.ts +4 -1
- package/dist/plugins/query/types.d.ts +36 -4
- package/dist/plugins/resultShape.d.ts +35 -0
- package/dist/schema/PropertyInfo.d.ts +0 -1
- package/dist/schema/index.cjs +7 -14
- package/dist/schema/index.cjs.map +1 -1
- package/dist/schema/index.js +7 -14
- package/dist/schema/index.js.map +1 -1
- package/dist/transfer/ChunkEncoder.d.ts +60 -0
- package/dist/transfer/decoder.d.ts +29 -0
- package/dist/transfer/fillers.d.ts +36 -0
- package/dist/transfer/index.cjs +873 -0
- package/dist/transfer/index.cjs.map +1 -0
- package/dist/transfer/index.d.ts +47 -0
- package/dist/transfer/index.js +872 -0
- package/dist/transfer/index.js.map +1 -0
- package/dist/transfer/plan.d.ts +94 -0
- package/dist/transfer/types.d.ts +138 -0
- package/dist/utilities/index.cjs +242 -49
- package/dist/utilities/index.cjs.map +1 -1
- package/dist/utilities/index.js +242 -49
- package/dist/utilities/index.js.map +1 -1
- package/package.json +9 -1
package/dist/plugins/index.js
CHANGED
|
@@ -4,6 +4,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
4
4
|
Cv: () => (assertString),
|
|
5
5
|
S6: () => (isValueExpression),
|
|
6
6
|
e3: () => (isPropertyExpression),
|
|
7
|
+
fm: () => (isCallExpression),
|
|
7
8
|
jf: () => (assertIsNotNull),
|
|
8
9
|
vg: () => (isOperatorExpression),
|
|
9
10
|
xH: () => (isComparatorExpression),
|
|
@@ -79,6 +80,11 @@ function isObjectWithType(value) {
|
|
|
79
80
|
*/ function isValueExpression(value) {
|
|
80
81
|
return isObjectWithType(value) && value.type === "value";
|
|
81
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Type guard: narrows `value` to `CallExpression` when it is an object with `type === "call"`.
|
|
85
|
+
*/ function isCallExpression(value) {
|
|
86
|
+
return isObjectWithType(value) && value.type === "call";
|
|
87
|
+
}
|
|
82
88
|
/**
|
|
83
89
|
* Type guard: narrows `value` to `EmptyExpression` when it is an object with `type === "empty"`.
|
|
84
90
|
*/ function isEmptyExpression(value) {
|
|
@@ -399,45 +405,374 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
399
405
|
}
|
|
400
406
|
|
|
401
407
|
|
|
408
|
+
},
|
|
409
|
+
429(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
410
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
411
|
+
a: () => (renderCallAsJs)
|
|
412
|
+
});
|
|
413
|
+
const CALL_SOURCE = {
|
|
414
|
+
"to-lower-case": {
|
|
415
|
+
form: "method",
|
|
416
|
+
name: "toLowerCase"
|
|
417
|
+
},
|
|
418
|
+
"to-upper-case": {
|
|
419
|
+
form: "method",
|
|
420
|
+
name: "toUpperCase"
|
|
421
|
+
},
|
|
422
|
+
"length": {
|
|
423
|
+
form: "property",
|
|
424
|
+
name: "length"
|
|
425
|
+
},
|
|
426
|
+
"trim": {
|
|
427
|
+
form: "method",
|
|
428
|
+
name: "trim"
|
|
429
|
+
},
|
|
430
|
+
"trim-start": {
|
|
431
|
+
form: "method",
|
|
432
|
+
name: "trimStart"
|
|
433
|
+
},
|
|
434
|
+
"trim-end": {
|
|
435
|
+
form: "method",
|
|
436
|
+
name: "trimEnd"
|
|
437
|
+
},
|
|
438
|
+
"index-of": {
|
|
439
|
+
form: "method",
|
|
440
|
+
name: "indexOf"
|
|
441
|
+
},
|
|
442
|
+
"substring": {
|
|
443
|
+
form: "method",
|
|
444
|
+
name: "substring"
|
|
445
|
+
},
|
|
446
|
+
"concat": {
|
|
447
|
+
form: "method",
|
|
448
|
+
name: "concat"
|
|
449
|
+
},
|
|
450
|
+
"replace": {
|
|
451
|
+
form: "method",
|
|
452
|
+
name: "replace"
|
|
453
|
+
},
|
|
454
|
+
"replace-all": {
|
|
455
|
+
form: "method",
|
|
456
|
+
name: "replaceAll"
|
|
457
|
+
},
|
|
458
|
+
"absolute": {
|
|
459
|
+
form: "function",
|
|
460
|
+
name: "Math.abs"
|
|
461
|
+
},
|
|
462
|
+
"floor": {
|
|
463
|
+
form: "function",
|
|
464
|
+
name: "Math.floor"
|
|
465
|
+
},
|
|
466
|
+
"ceiling": {
|
|
467
|
+
form: "function",
|
|
468
|
+
name: "Math.ceil"
|
|
469
|
+
},
|
|
470
|
+
"round": {
|
|
471
|
+
form: "function",
|
|
472
|
+
name: "Math.round"
|
|
473
|
+
},
|
|
474
|
+
"sign": {
|
|
475
|
+
form: "function",
|
|
476
|
+
name: "Math.sign"
|
|
477
|
+
},
|
|
478
|
+
"square-root": {
|
|
479
|
+
form: "function",
|
|
480
|
+
name: "Math.sqrt"
|
|
481
|
+
},
|
|
482
|
+
"add": {
|
|
483
|
+
form: "operator",
|
|
484
|
+
symbol: "+"
|
|
485
|
+
},
|
|
486
|
+
"subtract": {
|
|
487
|
+
form: "operator",
|
|
488
|
+
symbol: "-"
|
|
489
|
+
},
|
|
490
|
+
"multiply": {
|
|
491
|
+
form: "operator",
|
|
492
|
+
symbol: "*"
|
|
493
|
+
},
|
|
494
|
+
"divide": {
|
|
495
|
+
form: "operator",
|
|
496
|
+
symbol: "/"
|
|
497
|
+
},
|
|
498
|
+
"modulo": {
|
|
499
|
+
form: "operator",
|
|
500
|
+
symbol: "%"
|
|
501
|
+
},
|
|
502
|
+
"utc-year": {
|
|
503
|
+
form: "method",
|
|
504
|
+
name: "getUTCFullYear"
|
|
505
|
+
},
|
|
506
|
+
"utc-month": {
|
|
507
|
+
form: "method",
|
|
508
|
+
name: "getUTCMonth"
|
|
509
|
+
},
|
|
510
|
+
"utc-day-of-month": {
|
|
511
|
+
form: "method",
|
|
512
|
+
name: "getUTCDate"
|
|
513
|
+
},
|
|
514
|
+
"utc-day-of-week": {
|
|
515
|
+
form: "method",
|
|
516
|
+
name: "getUTCDay"
|
|
517
|
+
},
|
|
518
|
+
"utc-hour": {
|
|
519
|
+
form: "method",
|
|
520
|
+
name: "getUTCHours"
|
|
521
|
+
},
|
|
522
|
+
"utc-minute": {
|
|
523
|
+
form: "method",
|
|
524
|
+
name: "getUTCMinutes"
|
|
525
|
+
},
|
|
526
|
+
"utc-second": {
|
|
527
|
+
form: "method",
|
|
528
|
+
name: "getUTCSeconds"
|
|
529
|
+
},
|
|
530
|
+
"utc-millisecond": {
|
|
531
|
+
form: "method",
|
|
532
|
+
name: "getUTCMilliseconds"
|
|
533
|
+
},
|
|
534
|
+
"epoch-ms": {
|
|
535
|
+
form: "method",
|
|
536
|
+
name: "getTime"
|
|
537
|
+
},
|
|
538
|
+
"to-string": {
|
|
539
|
+
form: "function",
|
|
540
|
+
name: "String"
|
|
541
|
+
},
|
|
542
|
+
"to-number": {
|
|
543
|
+
form: "function",
|
|
544
|
+
name: "Number"
|
|
545
|
+
},
|
|
546
|
+
"to-boolean": {
|
|
547
|
+
form: "function",
|
|
548
|
+
name: "Boolean"
|
|
549
|
+
},
|
|
550
|
+
"type-of": {
|
|
551
|
+
form: "prefix",
|
|
552
|
+
keyword: "typeof"
|
|
553
|
+
},
|
|
554
|
+
"some": {
|
|
555
|
+
form: "method",
|
|
556
|
+
name: "some"
|
|
557
|
+
},
|
|
558
|
+
"every": {
|
|
559
|
+
form: "method",
|
|
560
|
+
name: "every"
|
|
561
|
+
},
|
|
562
|
+
// `Math.pow(a, b)` parses to the same call; `**` is the shorter of the two spellings
|
|
563
|
+
"power": {
|
|
564
|
+
form: "operator",
|
|
565
|
+
symbol: "**"
|
|
566
|
+
},
|
|
567
|
+
"bit-and": {
|
|
568
|
+
form: "operator",
|
|
569
|
+
symbol: "&"
|
|
570
|
+
},
|
|
571
|
+
"bit-or": {
|
|
572
|
+
form: "operator",
|
|
573
|
+
symbol: "|"
|
|
574
|
+
},
|
|
575
|
+
"bit-xor": {
|
|
576
|
+
form: "operator",
|
|
577
|
+
symbol: "^"
|
|
578
|
+
},
|
|
579
|
+
"shift-left": {
|
|
580
|
+
form: "operator",
|
|
581
|
+
symbol: "<<"
|
|
582
|
+
},
|
|
583
|
+
"shift-right": {
|
|
584
|
+
form: "operator",
|
|
585
|
+
symbol: ">>"
|
|
586
|
+
},
|
|
587
|
+
"shift-right-unsigned": {
|
|
588
|
+
form: "operator",
|
|
589
|
+
symbol: ">>>"
|
|
590
|
+
},
|
|
591
|
+
"bit-not": {
|
|
592
|
+
form: "prefix",
|
|
593
|
+
keyword: "~"
|
|
594
|
+
},
|
|
595
|
+
"coalesce": {
|
|
596
|
+
form: "operator",
|
|
597
|
+
symbol: "??"
|
|
598
|
+
},
|
|
599
|
+
"conditional": {
|
|
600
|
+
form: "conditional"
|
|
601
|
+
},
|
|
602
|
+
"matches": {
|
|
603
|
+
form: "regex-test"
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
/**
|
|
607
|
+
* A call rendered as the JavaScript that produced it, from operand and argument text already
|
|
608
|
+
* rendered by the caller.
|
|
609
|
+
*
|
|
610
|
+
* Takes strings so one implementation serves a live tree and a serialized one.
|
|
611
|
+
*/ /**
|
|
612
|
+
* Thunked because rendering a side can record a parameter, and `regex-test` emits its argument
|
|
613
|
+
* before its operand — so the two orders have to agree.
|
|
614
|
+
*/ const renderCallAsJs = (call, renderOperand, renderArgs)=>{
|
|
615
|
+
const source = CALL_SOURCE[call];
|
|
616
|
+
if (source == null) {
|
|
617
|
+
const operand = renderOperand();
|
|
618
|
+
return `${operand}.${call}(${renderArgs().join(", ")})`;
|
|
619
|
+
}
|
|
620
|
+
if (source.form === "property") {
|
|
621
|
+
return `${renderOperand()}.${source.name}`;
|
|
622
|
+
}
|
|
623
|
+
if (source.form === "regex-test") {
|
|
624
|
+
const pattern = renderArgs()[0] ?? "?";
|
|
625
|
+
return `${pattern}.test(${renderOperand()})`;
|
|
626
|
+
}
|
|
627
|
+
if (source.form === "method") {
|
|
628
|
+
const operand = renderOperand();
|
|
629
|
+
return `${operand}.${source.name}(${renderArgs().join(", ")})`;
|
|
630
|
+
}
|
|
631
|
+
if (source.form === "function") {
|
|
632
|
+
const operand = renderOperand();
|
|
633
|
+
return `${source.name}(${[
|
|
634
|
+
operand,
|
|
635
|
+
...renderArgs()
|
|
636
|
+
].join(", ")})`;
|
|
637
|
+
}
|
|
638
|
+
if (source.form === "prefix") {
|
|
639
|
+
// `~x`, not `~ x` — a bitwise complement is written tight, unlike `typeof`
|
|
640
|
+
const operand = renderOperand();
|
|
641
|
+
return source.keyword === "~" ? `${source.keyword}${operand}` : `${source.keyword} ${operand}`;
|
|
642
|
+
}
|
|
643
|
+
if (source.form === "conditional") {
|
|
644
|
+
const operand = renderOperand();
|
|
645
|
+
const args = renderArgs();
|
|
646
|
+
return `${operand} ? ${args[0] ?? "?"} : ${args[1] ?? "?"}`;
|
|
647
|
+
}
|
|
648
|
+
const operand = renderOperand();
|
|
649
|
+
return `${[
|
|
650
|
+
operand,
|
|
651
|
+
...renderArgs()
|
|
652
|
+
].join(` ${source.symbol} `)}`;
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
|
|
402
656
|
},
|
|
403
657
|
379(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
404
658
|
__webpack_require__.d(__webpack_exports__, {
|
|
659
|
+
Vv: () => (operandValue),
|
|
660
|
+
_3: () => (evaluate),
|
|
661
|
+
gm: () => (UNRESOLVED),
|
|
405
662
|
wS: () => (toStrictPredicate)
|
|
406
663
|
});
|
|
407
664
|
/* import */ var _assertions__rspack_import_0 = __webpack_require__(126);
|
|
408
665
|
|
|
409
666
|
/** Reads a property or literal operand, or `UNRESOLVED` when the node is not one. */ const UNRESOLVED = Symbol("unresolved");
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
|
|
667
|
+
const ARITHMETIC = {
|
|
668
|
+
"add": (left, right)=>left + right,
|
|
669
|
+
"subtract": (left, right)=>left - right,
|
|
670
|
+
"multiply": (left, right)=>left * right,
|
|
671
|
+
"divide": (left, right)=>left / right,
|
|
672
|
+
"modulo": (left, right)=>left % right,
|
|
673
|
+
"power": (left, right)=>left ** right,
|
|
674
|
+
"bit-and": (left, right)=>left & right,
|
|
675
|
+
"bit-or": (left, right)=>left | right,
|
|
676
|
+
"bit-xor": (left, right)=>left ^ right,
|
|
677
|
+
"shift-left": (left, right)=>left << right,
|
|
678
|
+
"shift-right": (left, right)=>left >> right,
|
|
679
|
+
"shift-right-unsigned": (left, right)=>left >>> right
|
|
680
|
+
};
|
|
681
|
+
const applyCall = (call, value, args)=>{
|
|
682
|
+
// Above the guard: a template renders null as "null" in JavaScript, so these two are total.
|
|
683
|
+
if (call === "to-string") {
|
|
684
|
+
return String(value);
|
|
685
|
+
}
|
|
686
|
+
if (call === "concat") {
|
|
687
|
+
return [
|
|
688
|
+
value,
|
|
689
|
+
...args
|
|
690
|
+
].map(String).join("");
|
|
413
691
|
}
|
|
414
|
-
// A
|
|
415
|
-
//
|
|
692
|
+
// A call applied to an absent value has no answer, and inventing one ("" for a missing string)
|
|
693
|
+
// is how a filter starts matching rows it should not.
|
|
416
694
|
if (value == null) {
|
|
417
695
|
return UNRESOLVED;
|
|
418
696
|
}
|
|
419
|
-
if (
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
697
|
+
if (call === "to-lower-case" || call === "to-upper-case") {
|
|
698
|
+
if (typeof value !== "string") {
|
|
699
|
+
return UNRESOLVED;
|
|
700
|
+
}
|
|
701
|
+
const lower = call === "to-lower-case";
|
|
702
|
+
if (args.length === 0 || args[0] == null) {
|
|
703
|
+
return lower ? value.toLowerCase() : value.toUpperCase();
|
|
704
|
+
}
|
|
705
|
+
if (typeof args[0] !== "string") {
|
|
706
|
+
return UNRESOLVED;
|
|
707
|
+
}
|
|
708
|
+
try {
|
|
709
|
+
// An explicit locale is deterministic; dropping it answers a different question in Turkish.
|
|
710
|
+
return lower ? value.toLocaleLowerCase(args[0]) : value.toLocaleUpperCase(args[0]);
|
|
711
|
+
} catch {
|
|
712
|
+
// An invalid language tag throws RangeError; no answer beats the host's default.
|
|
713
|
+
return UNRESOLVED;
|
|
714
|
+
}
|
|
424
715
|
}
|
|
425
|
-
if (
|
|
716
|
+
if (call === "length") {
|
|
426
717
|
return typeof value === "string" || Array.isArray(value) ? value.length : UNRESOLVED;
|
|
427
718
|
}
|
|
719
|
+
if (call === "bit-not") {
|
|
720
|
+
return typeof value === "number" ? ~value : UNRESOLVED;
|
|
721
|
+
}
|
|
722
|
+
if (call === "matches") {
|
|
723
|
+
if (typeof value !== "string" || !(args[0] instanceof RegExp)) {
|
|
724
|
+
return UNRESOLVED;
|
|
725
|
+
}
|
|
726
|
+
// `test` advances `lastIndex` on a global or sticky pattern, and the pattern is shared with
|
|
727
|
+
// the cached template, where a source evaluates fresh in JavaScript.
|
|
728
|
+
return args[0].global || args[0].sticky ? new RegExp(args[0].source, args[0].flags).test(value) : args[0].test(value);
|
|
729
|
+
}
|
|
730
|
+
const arithmetic = ARITHMETIC[call];
|
|
731
|
+
if (arithmetic != null) {
|
|
732
|
+
return typeof value === "number" && typeof args[0] === "number" ? arithmetic(value, args[0]) : UNRESOLVED;
|
|
733
|
+
}
|
|
428
734
|
return UNRESOLVED;
|
|
429
735
|
};
|
|
430
|
-
const
|
|
736
|
+
const operandValue = (expression, row)=>{
|
|
431
737
|
if (expression == null) {
|
|
432
738
|
return UNRESOLVED;
|
|
433
739
|
}
|
|
434
740
|
if ((0,_assertions__rspack_import_0/* .isValueExpression */.S6)(expression)) {
|
|
435
|
-
return
|
|
741
|
+
return expression.value;
|
|
436
742
|
}
|
|
437
743
|
if ((0,_assertions__rspack_import_0/* .isPropertyExpression */.e3)(expression)) {
|
|
438
744
|
// Through the PropertyInfo, so a nested path and a `from`-renamed segment resolve the same
|
|
439
745
|
// way every other consumer of the tree resolves them.
|
|
440
|
-
return
|
|
746
|
+
return expression.property.getValue(row);
|
|
747
|
+
}
|
|
748
|
+
if ((0,_assertions__rspack_import_0/* .isCallExpression */.fm)(expression)) {
|
|
749
|
+
/**
|
|
750
|
+
* `??` and `? :` are the two calls whose whole job is to answer when something is absent, so
|
|
751
|
+
* they run before the guard that refuses an absent operand.
|
|
752
|
+
*/ if (expression.call === "coalesce") {
|
|
753
|
+
const left = operandValue(expression.expression, row);
|
|
754
|
+
return left === UNRESOLVED || left == null ? operandValue(expression.arguments[0], row) : left;
|
|
755
|
+
}
|
|
756
|
+
if (expression.call === "conditional") {
|
|
757
|
+
const condition = evaluate(expression.expression, row);
|
|
758
|
+
if (condition === undefined) {
|
|
759
|
+
return UNRESOLVED;
|
|
760
|
+
}
|
|
761
|
+
return operandValue(expression.arguments[condition === true ? 0 : 1], row);
|
|
762
|
+
}
|
|
763
|
+
const inner = operandValue(expression.expression, row);
|
|
764
|
+
if (inner === UNRESOLVED) {
|
|
765
|
+
return UNRESOLVED;
|
|
766
|
+
}
|
|
767
|
+
const args = [];
|
|
768
|
+
for (const argument of expression.arguments){
|
|
769
|
+
const resolved = operandValue(argument, row);
|
|
770
|
+
if (resolved === UNRESOLVED) {
|
|
771
|
+
return UNRESOLVED;
|
|
772
|
+
}
|
|
773
|
+
args.push(resolved);
|
|
774
|
+
}
|
|
775
|
+
return applyCall(expression.call, inner, args);
|
|
441
776
|
}
|
|
442
777
|
return UNRESOLVED;
|
|
443
778
|
};
|
|
@@ -522,8 +857,8 @@ const evaluateComparator = (comparator, left, right, strict)=>{
|
|
|
522
857
|
return left === false && right === false ? false : undefined;
|
|
523
858
|
}
|
|
524
859
|
if ((0,_assertions__rspack_import_0/* .isComparatorExpression */.xH)(expression)) {
|
|
525
|
-
const left =
|
|
526
|
-
const right =
|
|
860
|
+
const left = operandValue(expression.left, row);
|
|
861
|
+
const right = operandValue(expression.right, row);
|
|
527
862
|
if (left === UNRESOLVED || right === UNRESOLVED) {
|
|
528
863
|
return undefined;
|
|
529
864
|
}
|
|
@@ -561,19 +896,129 @@ const evaluateComparator = (comparator, left, right, strict)=>{
|
|
|
561
896
|
};
|
|
562
897
|
|
|
563
898
|
|
|
899
|
+
},
|
|
900
|
+
43(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
901
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
902
|
+
F5: () => (foldConstantCalls)
|
|
903
|
+
});
|
|
904
|
+
/* import */ var _assertions__rspack_import_0 = __webpack_require__(126);
|
|
905
|
+
/* import */ var _evaluate__rspack_import_3 = __webpack_require__(379);
|
|
906
|
+
/* import */ var _types__rspack_import_2 = __webpack_require__(27);
|
|
907
|
+
/* import */ var _utils__rspack_import_1 = __webpack_require__(63);
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
/** Calls fold may compute. Absent means a plugin declines it, so a new call is opt-in. */ const FOLDABLE = new Set([
|
|
913
|
+
"to-lower-case",
|
|
914
|
+
"to-upper-case",
|
|
915
|
+
"length",
|
|
916
|
+
"bit-not",
|
|
917
|
+
"matches",
|
|
918
|
+
"to-string",
|
|
919
|
+
"concat",
|
|
920
|
+
"add",
|
|
921
|
+
"subtract",
|
|
922
|
+
"multiply",
|
|
923
|
+
"divide",
|
|
924
|
+
"modulo",
|
|
925
|
+
"power",
|
|
926
|
+
"bit-and",
|
|
927
|
+
"bit-or",
|
|
928
|
+
"bit-xor",
|
|
929
|
+
"shift-left",
|
|
930
|
+
"shift-right",
|
|
931
|
+
"shift-right-unsigned",
|
|
932
|
+
"coalesce",
|
|
933
|
+
"conditional"
|
|
934
|
+
]);
|
|
935
|
+
/** `String(value)` on an object is the host's rendering — a Date carries its timezone. */ const COERCES_TO_TEXT = new Set([
|
|
936
|
+
"to-string",
|
|
937
|
+
"concat"
|
|
938
|
+
]);
|
|
939
|
+
const isFrozenPrimitive = (value)=>value == null || typeof value !== "object";
|
|
940
|
+
const readsAProperty = (expression)=>{
|
|
941
|
+
if ((0,_assertions__rspack_import_0/* .isPropertyExpression */.e3)(expression)) {
|
|
942
|
+
return true;
|
|
943
|
+
}
|
|
944
|
+
return (0,_utils__rspack_import_1/* .childrenOf */.LU)(expression).some(readsAProperty);
|
|
945
|
+
};
|
|
946
|
+
/** A `conditional` holds a condition where every other call holds a value. */ const isConstant = (call)=>{
|
|
947
|
+
if (!FOLDABLE.has(call.call) || !call.arguments.every(_assertions__rspack_import_0/* .isValueExpression */.S6)) {
|
|
948
|
+
return false;
|
|
949
|
+
}
|
|
950
|
+
if (COERCES_TO_TEXT.has(call.call) && [
|
|
951
|
+
call.expression,
|
|
952
|
+
...call.arguments
|
|
953
|
+
].some((operand)=>(0,_assertions__rspack_import_0/* .isValueExpression */.S6)(operand) && !isFrozenPrimitive(operand.value))) {
|
|
954
|
+
return false;
|
|
955
|
+
}
|
|
956
|
+
return call.call === "conditional" ? !readsAProperty(call.expression) : (0,_assertions__rspack_import_0/* .isValueExpression */.S6)(call.expression);
|
|
957
|
+
};
|
|
958
|
+
/** Computes every call whose operand and arguments are all literals. Runs after `bindExpression`. */ const foldConstantCalls = (expression)=>{
|
|
959
|
+
if ((0,_assertions__rspack_import_0/* .isCallExpression */.fm)(expression)) {
|
|
960
|
+
const folded = new _types__rspack_import_2/* .CallExpression */.DG({
|
|
961
|
+
call: expression.call,
|
|
962
|
+
expression: foldConstantCalls(expression.expression),
|
|
963
|
+
arguments: expression.arguments.map(foldConstantCalls)
|
|
964
|
+
});
|
|
965
|
+
if (!isConstant(folded)) {
|
|
966
|
+
return folded;
|
|
967
|
+
}
|
|
968
|
+
const value = (0,_evaluate__rspack_import_3/* .operandValue */.Vv)(folded, {});
|
|
969
|
+
return value === _evaluate__rspack_import_3/* .UNRESOLVED */.gm ? folded : new _types__rspack_import_2/* .ValueExpression */.Ko({
|
|
970
|
+
value
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
if ((0,_assertions__rspack_import_0/* .isComparatorExpression */.xH)(expression)) {
|
|
974
|
+
return new _types__rspack_import_2/* .ComparatorExpression */.bQ({
|
|
975
|
+
comparator: expression.comparator,
|
|
976
|
+
negated: expression.negated,
|
|
977
|
+
strict: expression.strict,
|
|
978
|
+
left: expression.left == null ? undefined : foldConstantCalls(expression.left),
|
|
979
|
+
right: expression.right == null ? undefined : foldConstantCalls(expression.right)
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
if ((0,_assertions__rspack_import_0/* .isOperatorExpression */.vg)(expression)) {
|
|
983
|
+
return new _types__rspack_import_2/* .OperatorExpression */.fw({
|
|
984
|
+
operator: expression.operator,
|
|
985
|
+
left: expression.left == null ? undefined : foldConstantCalls(expression.left),
|
|
986
|
+
right: expression.right == null ? undefined : foldConstantCalls(expression.right)
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
return expression;
|
|
990
|
+
};
|
|
991
|
+
/** The value a literal operand binds as once the calls on it are computed. Throws if it cannot. */ const foldedOperandValue = (operand, calls)=>{
|
|
992
|
+
if (calls.length === 0) {
|
|
993
|
+
return operand.value;
|
|
994
|
+
}
|
|
995
|
+
// `peelCalls` returns calls innermost first, so the last one evaluates the whole chain.
|
|
996
|
+
const outermost = calls[calls.length - 1];
|
|
997
|
+
const value = readsAProperty(outermost) ? UNRESOLVED : operandValue(outermost, {});
|
|
998
|
+
if (value === UNRESOLVED) {
|
|
999
|
+
throw new Error(`'${calls.map((call)=>call.call).join("', '")}' cannot be computed on the literal ` + `'${String(operand.value)}'.`);
|
|
1000
|
+
}
|
|
1001
|
+
return value;
|
|
1002
|
+
};
|
|
1003
|
+
|
|
1004
|
+
|
|
564
1005
|
},
|
|
565
1006
|
91(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
566
1007
|
__webpack_require__.d(__webpack_exports__, {
|
|
567
1008
|
MY: () => (toExpression)
|
|
568
1009
|
});
|
|
569
|
-
/* import */ var
|
|
1010
|
+
/* import */ var _utilities__rspack_import_4 = __webpack_require__(581);
|
|
570
1011
|
/* import */ var _assertions__rspack_import_0 = __webpack_require__(126);
|
|
571
1012
|
/* import */ var _schema__rspack_import_2 = __webpack_require__(537);
|
|
1013
|
+
/* import */ var _evaluate__rspack_import_3 = __webpack_require__(379);
|
|
1014
|
+
/* import */ var _fold__rspack_import_5 = __webpack_require__(43);
|
|
572
1015
|
/* import */ var _types__rspack_import_1 = __webpack_require__(27);
|
|
573
1016
|
|
|
574
1017
|
|
|
575
1018
|
|
|
576
1019
|
|
|
1020
|
+
|
|
1021
|
+
|
|
577
1022
|
// Error message constants
|
|
578
1023
|
const ERROR_MESSAGES = {
|
|
579
1024
|
PROPERTY_NOT_FOUND: (path)=>`Error parsing query, could not find PropertyInfo for path: ${path}`,
|
|
@@ -619,8 +1064,13 @@ const converters = {
|
|
|
619
1064
|
};
|
|
620
1065
|
// Longest first so multi-character punctuation wins over its prefixes
|
|
621
1066
|
const MULTI_CHARACTER_PUNCTUATION = [
|
|
1067
|
+
">>>",
|
|
622
1068
|
"===",
|
|
623
1069
|
"!==",
|
|
1070
|
+
"**",
|
|
1071
|
+
"<<",
|
|
1072
|
+
">>",
|
|
1073
|
+
"??",
|
|
624
1074
|
"?.",
|
|
625
1075
|
"&&",
|
|
626
1076
|
"||",
|
|
@@ -657,9 +1107,17 @@ const SINGLE_CHARACTER_PUNCTUATION = new Set([
|
|
|
657
1107
|
"?",
|
|
658
1108
|
":",
|
|
659
1109
|
"&",
|
|
660
|
-
"|"
|
|
1110
|
+
"|",
|
|
1111
|
+
"^",
|
|
1112
|
+
"~"
|
|
661
1113
|
]);
|
|
662
|
-
|
|
1114
|
+
/**
|
|
1115
|
+
* A lookup table keyed by source text.
|
|
1116
|
+
*
|
|
1117
|
+
* Null-prototype: `TRANSFORM_METHODS["toString"]` otherwise returns `Object.prototype.toString`,
|
|
1118
|
+
* which is truthy, and the parser reads a method it does not support as one it does.
|
|
1119
|
+
*/ const sourceKeyed = (entries)=>Object.assign(Object.create(null), entries);
|
|
1120
|
+
const STRING_ESCAPES = sourceKeyed({
|
|
663
1121
|
"n": "\n",
|
|
664
1122
|
"r": "\r",
|
|
665
1123
|
"t": "\t",
|
|
@@ -667,6 +1125,24 @@ const STRING_ESCAPES = {
|
|
|
667
1125
|
"f": "\f",
|
|
668
1126
|
"v": "\v",
|
|
669
1127
|
"0": "\0"
|
|
1128
|
+
});
|
|
1129
|
+
/**
|
|
1130
|
+
* Whether a `/` here opens a regex rather than dividing.
|
|
1131
|
+
*
|
|
1132
|
+
* A regex cannot follow a value. Everything else — the start of the source, an operator, an opening
|
|
1133
|
+
* bracket, a comma — is a position where only a regex makes sense.
|
|
1134
|
+
*/ const regexCanStartHere = (tokens)=>{
|
|
1135
|
+
const previous = tokens[tokens.length - 1];
|
|
1136
|
+
if (previous == null) {
|
|
1137
|
+
return true;
|
|
1138
|
+
}
|
|
1139
|
+
if (previous.kind === "number" || previous.kind === "string" || previous.kind === "bigint" || previous.kind === "regex") {
|
|
1140
|
+
return false;
|
|
1141
|
+
}
|
|
1142
|
+
if (previous.kind === "identifier") {
|
|
1143
|
+
return false;
|
|
1144
|
+
}
|
|
1145
|
+
return previous.value !== ")" && previous.value !== "]";
|
|
670
1146
|
};
|
|
671
1147
|
const isIdentifierStart = (char)=>/[a-zA-Z_$]/.test(char);
|
|
672
1148
|
const isIdentifierPart = (char)=>/[a-zA-Z0-9_$]/.test(char);
|
|
@@ -716,6 +1192,51 @@ const isHexDigit = (char)=>isDigit(char) || char >= "a" && char <= "f" || char >
|
|
|
716
1192
|
i++;
|
|
717
1193
|
continue;
|
|
718
1194
|
}
|
|
1195
|
+
/**
|
|
1196
|
+
* A regex literal, told from division by what came before it.
|
|
1197
|
+
*
|
|
1198
|
+
* `/` after a value — a number, string, identifier, `)` or `]` — is division. Anywhere else
|
|
1199
|
+
* it opens a regex. That is the same rule a JavaScript lexer uses, and it is why `x.a / 2`
|
|
1200
|
+
* and `/^a/.test(x.a)` can share a character.
|
|
1201
|
+
*/ if (char === "/" && source[i + 1] !== "/" && source[i + 1] !== "*" && regexCanStartHere(tokens)) {
|
|
1202
|
+
let value = "";
|
|
1203
|
+
let inClass = false;
|
|
1204
|
+
let j = i + 1;
|
|
1205
|
+
while(j < source.length){
|
|
1206
|
+
const current = source[j];
|
|
1207
|
+
if (current === "\\") {
|
|
1208
|
+
value += current + (source[j + 1] ?? "");
|
|
1209
|
+
j += 2;
|
|
1210
|
+
continue;
|
|
1211
|
+
}
|
|
1212
|
+
if (current === "[") {
|
|
1213
|
+
inClass = true;
|
|
1214
|
+
} else if (current === "]") {
|
|
1215
|
+
inClass = false;
|
|
1216
|
+
} else if (current === "/" && inClass === false) {
|
|
1217
|
+
break;
|
|
1218
|
+
} else if (current === "\n") {
|
|
1219
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("unterminated regular expression"));
|
|
1220
|
+
}
|
|
1221
|
+
value += current;
|
|
1222
|
+
j++;
|
|
1223
|
+
}
|
|
1224
|
+
if (j >= source.length) {
|
|
1225
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("unterminated regular expression"));
|
|
1226
|
+
}
|
|
1227
|
+
j++;
|
|
1228
|
+
let flags = "";
|
|
1229
|
+
while(j < source.length && isIdentifierPart(source[j])){
|
|
1230
|
+
flags += source[j];
|
|
1231
|
+
j++;
|
|
1232
|
+
}
|
|
1233
|
+
i = j;
|
|
1234
|
+
tokens.push({
|
|
1235
|
+
kind: "regex",
|
|
1236
|
+
value: `${value}\u0000${flags}`
|
|
1237
|
+
});
|
|
1238
|
+
continue;
|
|
1239
|
+
}
|
|
719
1240
|
// Comments
|
|
720
1241
|
if (char === "/" && source[i + 1] === "/") {
|
|
721
1242
|
while(i < source.length && source[i] !== "\n"){
|
|
@@ -735,6 +1256,8 @@ const isHexDigit = (char)=>isDigit(char) || char >= "a" && char <= "f" || char >
|
|
|
735
1256
|
if (char === "'" || char === "\"" || char === "`") {
|
|
736
1257
|
const quote = char;
|
|
737
1258
|
let value = "";
|
|
1259
|
+
const chunks = [];
|
|
1260
|
+
const expressions = [];
|
|
738
1261
|
i++;
|
|
739
1262
|
while(i < source.length && source[i] !== quote){
|
|
740
1263
|
if (source[i] === "\\") {
|
|
@@ -749,8 +1272,43 @@ const isHexDigit = (char)=>isDigit(char) || char >= "a" && char <= "f" || char >
|
|
|
749
1272
|
i += 2;
|
|
750
1273
|
continue;
|
|
751
1274
|
}
|
|
752
|
-
|
|
753
|
-
|
|
1275
|
+
/**
|
|
1276
|
+
* An interpolation. The literal so far becomes a chunk and the expression source is
|
|
1277
|
+
* kept whole, to be parsed by its own stream — nesting means the inner source can
|
|
1278
|
+
* hold anything, including another template.
|
|
1279
|
+
*/ if (quote === "`" && source[i] === "$" && source[i + 1] === "{") {
|
|
1280
|
+
let depth = 1;
|
|
1281
|
+
let expression = "";
|
|
1282
|
+
let at = i + 2;
|
|
1283
|
+
while(at < source.length && depth > 0){
|
|
1284
|
+
const current = source[at];
|
|
1285
|
+
if (current === "{") {
|
|
1286
|
+
depth++;
|
|
1287
|
+
} else if (current === "}") {
|
|
1288
|
+
depth--;
|
|
1289
|
+
if (depth === 0) {
|
|
1290
|
+
break;
|
|
1291
|
+
}
|
|
1292
|
+
} else if (current === "'" || current === '"' || current === "`") {
|
|
1293
|
+
const closing = current;
|
|
1294
|
+
expression += current;
|
|
1295
|
+
at++;
|
|
1296
|
+
while(at < source.length && source[at] !== closing){
|
|
1297
|
+
expression += source[at] === "\\" ? source[at] + (source[at + 1] ?? "") : source[at];
|
|
1298
|
+
at += source[at] === "\\" ? 2 : 1;
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
expression += source[at];
|
|
1302
|
+
at++;
|
|
1303
|
+
}
|
|
1304
|
+
if (depth > 0) {
|
|
1305
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("unterminated template interpolation"));
|
|
1306
|
+
}
|
|
1307
|
+
chunks.push(value);
|
|
1308
|
+
expressions.push(expression);
|
|
1309
|
+
value = "";
|
|
1310
|
+
i = at + 1;
|
|
1311
|
+
continue;
|
|
754
1312
|
}
|
|
755
1313
|
value += source[i];
|
|
756
1314
|
i++;
|
|
@@ -759,6 +1317,17 @@ const isHexDigit = (char)=>isDigit(char) || char >= "a" && char <= "f" || char >
|
|
|
759
1317
|
throw new Error(ERROR_MESSAGES.UNSUPPORTED("unterminated string literal"));
|
|
760
1318
|
}
|
|
761
1319
|
i++; // consume closing quote
|
|
1320
|
+
if (expressions.length > 0) {
|
|
1321
|
+
chunks.push(value);
|
|
1322
|
+
tokens.push({
|
|
1323
|
+
kind: "template",
|
|
1324
|
+
value: JSON.stringify({
|
|
1325
|
+
chunks,
|
|
1326
|
+
expressions
|
|
1327
|
+
})
|
|
1328
|
+
});
|
|
1329
|
+
continue;
|
|
1330
|
+
}
|
|
762
1331
|
tokens.push({
|
|
763
1332
|
kind: "string",
|
|
764
1333
|
value
|
|
@@ -802,6 +1371,14 @@ const isHexDigit = (char)=>isDigit(char) || char >= "a" && char <= "f" || char >
|
|
|
802
1371
|
}
|
|
803
1372
|
}
|
|
804
1373
|
}
|
|
1374
|
+
if (source[i] === "n") {
|
|
1375
|
+
i++;
|
|
1376
|
+
tokens.push({
|
|
1377
|
+
kind: "bigint",
|
|
1378
|
+
value: value.replace(/_/g, "")
|
|
1379
|
+
});
|
|
1380
|
+
continue;
|
|
1381
|
+
}
|
|
805
1382
|
tokens.push({
|
|
806
1383
|
kind: "number",
|
|
807
1384
|
value: value.replace(/_/g, "")
|
|
@@ -852,6 +1429,24 @@ const isHexDigit = (char)=>isDigit(char) || char >= "a" && char <= "f" || char >
|
|
|
852
1429
|
constructor(tokens){
|
|
853
1430
|
this.tokens = tokens;
|
|
854
1431
|
}
|
|
1432
|
+
/** Inserts tokens at the cursor, bracketed so they keep their own precedence. */ splice(tokens) {
|
|
1433
|
+
const bracketed = [
|
|
1434
|
+
{
|
|
1435
|
+
kind: "punctuation",
|
|
1436
|
+
value: "("
|
|
1437
|
+
},
|
|
1438
|
+
...tokens,
|
|
1439
|
+
{
|
|
1440
|
+
kind: "punctuation",
|
|
1441
|
+
value: ")"
|
|
1442
|
+
}
|
|
1443
|
+
];
|
|
1444
|
+
this.tokens = [
|
|
1445
|
+
...this.tokens.slice(0, this.index),
|
|
1446
|
+
...bracketed,
|
|
1447
|
+
...this.tokens.slice(this.index)
|
|
1448
|
+
];
|
|
1449
|
+
}
|
|
855
1450
|
get isAtEnd() {
|
|
856
1451
|
return this.index >= this.tokens.length;
|
|
857
1452
|
}
|
|
@@ -866,10 +1461,108 @@ const isHexDigit = (char)=>isDigit(char) || char >= "a" && char <= "f" || char >
|
|
|
866
1461
|
this.index++;
|
|
867
1462
|
return token;
|
|
868
1463
|
}
|
|
1464
|
+
/** The tokens of one statement's value, through the `;` or block end that closes it. */ takeStatementTokens() {
|
|
1465
|
+
const tokens = [];
|
|
1466
|
+
let depth = 0;
|
|
1467
|
+
while(!this.isAtEnd){
|
|
1468
|
+
const token = this.peek();
|
|
1469
|
+
if (token.kind === "punctuation") {
|
|
1470
|
+
if (token.value === "(" || token.value === "[" || token.value === "{") {
|
|
1471
|
+
depth++;
|
|
1472
|
+
} else if (token.value === ")" || token.value === "]" || token.value === "}") {
|
|
1473
|
+
if (depth === 0) {
|
|
1474
|
+
break;
|
|
1475
|
+
}
|
|
1476
|
+
depth--;
|
|
1477
|
+
} else if (token.value === ";" && depth === 0) {
|
|
1478
|
+
this.next();
|
|
1479
|
+
break;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
tokens.push(this.next());
|
|
1483
|
+
}
|
|
1484
|
+
if (tokens.length === 0) {
|
|
1485
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("a declaration with no value"));
|
|
1486
|
+
}
|
|
1487
|
+
return tokens;
|
|
1488
|
+
}
|
|
869
1489
|
isPunctuation(value, offset = 0) {
|
|
870
1490
|
const token = this.peek(offset);
|
|
871
1491
|
return token != null && token.kind === "punctuation" && token.value === value;
|
|
872
1492
|
}
|
|
1493
|
+
/**
|
|
1494
|
+
* Whether the group starting here holds a value rather than a condition.
|
|
1495
|
+
*
|
|
1496
|
+
* `(a && b)` is a boolean sub-expression; `(x.name ?? '') === 'ada'` and `(x.name).length` are
|
|
1497
|
+
* values. Only the token after the matching bracket tells them apart, so the decision is made by
|
|
1498
|
+
* looking ahead rather than by parsing one way and catching the failure — a rewind on exception
|
|
1499
|
+
* would swallow a genuine syntax error inside the group and report it as something else.
|
|
1500
|
+
*/ groupIsValue() {
|
|
1501
|
+
let depth = 0;
|
|
1502
|
+
let at = this.index;
|
|
1503
|
+
for(; at < this.tokens.length; at++){
|
|
1504
|
+
const token = this.tokens[at];
|
|
1505
|
+
if (token.kind !== "punctuation") {
|
|
1506
|
+
continue;
|
|
1507
|
+
}
|
|
1508
|
+
if (token.value === "(") {
|
|
1509
|
+
depth++;
|
|
1510
|
+
continue;
|
|
1511
|
+
}
|
|
1512
|
+
if (token.value === ")") {
|
|
1513
|
+
depth--;
|
|
1514
|
+
if (depth === 0) {
|
|
1515
|
+
break;
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
const after = this.tokens[at + 1];
|
|
1520
|
+
if (after == null || after.kind !== "punctuation") {
|
|
1521
|
+
return false;
|
|
1522
|
+
}
|
|
1523
|
+
return COMPARISON_OPERATORS[after.value] != null || after.value === "." || after.value === "?.";
|
|
1524
|
+
}
|
|
1525
|
+
/** Whether a `?` sits at the top level of what is left, so this is a conditional. */ holdsConditional() {
|
|
1526
|
+
let depth = 0;
|
|
1527
|
+
for(let at = this.index; at < this.tokens.length; at++){
|
|
1528
|
+
const token = this.tokens[at];
|
|
1529
|
+
if (token.kind !== "punctuation") {
|
|
1530
|
+
continue;
|
|
1531
|
+
}
|
|
1532
|
+
if (token.value === "(" || token.value === "[") {
|
|
1533
|
+
depth++;
|
|
1534
|
+
} else if (token.value === ")" || token.value === "]") {
|
|
1535
|
+
depth--;
|
|
1536
|
+
} else if (token.value === "?" && depth === 0) {
|
|
1537
|
+
return true;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
return false;
|
|
1541
|
+
}
|
|
1542
|
+
/** Whether the group starting here is `( … ? … : … )` rather than a plain value. */ groupHoldsConditional() {
|
|
1543
|
+
let depth = 0;
|
|
1544
|
+
for(let at = this.index; at < this.tokens.length; at++){
|
|
1545
|
+
const token = this.tokens[at];
|
|
1546
|
+
if (token.kind !== "punctuation") {
|
|
1547
|
+
continue;
|
|
1548
|
+
}
|
|
1549
|
+
if (token.value === "(") {
|
|
1550
|
+
depth++;
|
|
1551
|
+
continue;
|
|
1552
|
+
}
|
|
1553
|
+
if (token.value === ")") {
|
|
1554
|
+
depth--;
|
|
1555
|
+
if (depth === 0) {
|
|
1556
|
+
return false;
|
|
1557
|
+
}
|
|
1558
|
+
continue;
|
|
1559
|
+
}
|
|
1560
|
+
if (token.value === "?" && depth === 1) {
|
|
1561
|
+
return true;
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
return false;
|
|
1565
|
+
}
|
|
873
1566
|
matchPunctuation(value) {
|
|
874
1567
|
if (this.isPunctuation(value)) {
|
|
875
1568
|
this.index++;
|
|
@@ -883,12 +1576,101 @@ const isHexDigit = (char)=>isDigit(char) || char >= "a" && char <= "f" || char >
|
|
|
883
1576
|
}
|
|
884
1577
|
}
|
|
885
1578
|
}
|
|
886
|
-
|
|
1579
|
+
/**
|
|
1580
|
+
* Calls JavaScript binds LOOSER than a comparison.
|
|
1581
|
+
*
|
|
1582
|
+
* This grammar reads a comparison's operands as values, which puts these tighter than they belong:
|
|
1583
|
+
* `x.flags & 6 === 2` is `x.flags & (6 === 2)` in JavaScript and would be read here as
|
|
1584
|
+
* `(x.flags & 6) === 2`. The two answer differently, so an ungrouped one is refused rather than
|
|
1585
|
+
* reinterpreted — the filter then runs in memory against the caller's own function, which is right by
|
|
1586
|
+
* construction. Brackets say which was meant, and JavaScript itself makes an unbracketed `??` mix a
|
|
1587
|
+
* syntax error for the same reason.
|
|
1588
|
+
*/ const LOOSER_THAN_COMPARISON = [
|
|
1589
|
+
"bit-and",
|
|
1590
|
+
"bit-or",
|
|
1591
|
+
"bit-xor",
|
|
1592
|
+
"coalesce"
|
|
1593
|
+
];
|
|
1594
|
+
const needsBrackets = (operand)=>operand.kind === "arithmetic" && operand.grouped !== true && LOOSER_THAN_COMPARISON.includes(operand.call);
|
|
1595
|
+
/** JavaScript precedence: `*`, `/`, `%` bind tighter than `+` and `-`. */ const MULTIPLICATIVE_OPERATORS = sourceKeyed({
|
|
1596
|
+
"*": "multiply",
|
|
1597
|
+
"/": "divide",
|
|
1598
|
+
"%": "modulo"
|
|
1599
|
+
});
|
|
1600
|
+
const ADDITIVE_OPERATORS = sourceKeyed({
|
|
1601
|
+
"+": "add",
|
|
1602
|
+
"-": "subtract"
|
|
1603
|
+
});
|
|
1604
|
+
const SHIFT_OPERATORS = sourceKeyed({
|
|
1605
|
+
"<<": "shift-left",
|
|
1606
|
+
">>": "shift-right",
|
|
1607
|
+
">>>": "shift-right-unsigned"
|
|
1608
|
+
});
|
|
1609
|
+
const BITWISE_AND_OPERATORS = sourceKeyed({
|
|
1610
|
+
"&": "bit-and"
|
|
1611
|
+
});
|
|
1612
|
+
const BITWISE_XOR_OPERATORS = sourceKeyed({
|
|
1613
|
+
"^": "bit-xor"
|
|
1614
|
+
});
|
|
1615
|
+
const BITWISE_OR_OPERATORS = sourceKeyed({
|
|
1616
|
+
"|": "bit-or"
|
|
1617
|
+
});
|
|
1618
|
+
const COALESCE_OPERATORS = sourceKeyed({
|
|
1619
|
+
"??": "coalesce"
|
|
1620
|
+
});
|
|
1621
|
+
/** Whether a schema property is reachable in here, which decides which side of a comparison it is. */ const containsProperty = (operand)=>{
|
|
1622
|
+
if (operand.kind === "property") {
|
|
1623
|
+
return true;
|
|
1624
|
+
}
|
|
1625
|
+
if (operand.kind === "conditional") {
|
|
1626
|
+
// A comparison always names a schema property, so the condition alone settles it
|
|
1627
|
+
return true;
|
|
1628
|
+
}
|
|
1629
|
+
return operand.kind === "arithmetic" && (containsProperty(operand.left) || containsProperty(operand.right) || operand.extra != null && containsProperty(operand.extra));
|
|
1630
|
+
};
|
|
1631
|
+
const DECLARATION_KEYWORDS = new Set([
|
|
1632
|
+
"const",
|
|
1633
|
+
"let",
|
|
1634
|
+
"var"
|
|
1635
|
+
]);
|
|
1636
|
+
/** An operand whose value only a row can supply. */ const UNKNOWN_UNTIL_ROW = Symbol("unknown until row");
|
|
1637
|
+
/** The empty argument slot of a unary call. Compared by identity, so a real `undefined` still counts. */ const NO_ARGUMENT = Object.freeze({
|
|
1638
|
+
kind: "value",
|
|
1639
|
+
value: undefined,
|
|
1640
|
+
transformer: null,
|
|
1641
|
+
locale: null
|
|
1642
|
+
});
|
|
1643
|
+
const noArgument = ()=>NO_ARGUMENT;
|
|
1644
|
+
/** A predicate no row satisfies. Never reaches a tree: no expression node means "match nothing". */ const NEVER = "never";
|
|
1645
|
+
const and = (left, right)=>{
|
|
1646
|
+
if (_types__rspack_import_1/* .Expression.isEmpty */.r4.isEmpty(left)) {
|
|
1647
|
+
return right;
|
|
1648
|
+
}
|
|
1649
|
+
if (_types__rspack_import_1/* .Expression.isEmpty */.r4.isEmpty(right)) {
|
|
1650
|
+
return left;
|
|
1651
|
+
}
|
|
1652
|
+
return new _types__rspack_import_1/* .OperatorExpression */.fw({
|
|
1653
|
+
operator: "&&",
|
|
1654
|
+
left,
|
|
1655
|
+
right
|
|
1656
|
+
});
|
|
1657
|
+
};
|
|
1658
|
+
const or = (left, right)=>{
|
|
1659
|
+
if (_types__rspack_import_1/* .Expression.isEmpty */.r4.isEmpty(left) || _types__rspack_import_1/* .Expression.isEmpty */.r4.isEmpty(right)) {
|
|
1660
|
+
return _types__rspack_import_1/* .Expression.EMPTY */.r4.EMPTY;
|
|
1661
|
+
}
|
|
1662
|
+
return new _types__rspack_import_1/* .OperatorExpression */.fw({
|
|
1663
|
+
operator: "||",
|
|
1664
|
+
left,
|
|
1665
|
+
right
|
|
1666
|
+
});
|
|
1667
|
+
};
|
|
1668
|
+
const COMPARATOR_METHODS = sourceKeyed({
|
|
887
1669
|
startsWith: "starts-with",
|
|
888
1670
|
endsWith: "ends-with",
|
|
889
1671
|
includes: "includes"
|
|
890
|
-
};
|
|
891
|
-
const TRANSFORM_METHODS = {
|
|
1672
|
+
});
|
|
1673
|
+
const TRANSFORM_METHODS = sourceKeyed({
|
|
892
1674
|
toLowerCase: {
|
|
893
1675
|
transformer: "to-lower-case",
|
|
894
1676
|
locale: null
|
|
@@ -905,8 +1687,8 @@ const TRANSFORM_METHODS = {
|
|
|
905
1687
|
transformer: "to-upper-case",
|
|
906
1688
|
locale: "en-US"
|
|
907
1689
|
}
|
|
908
|
-
};
|
|
909
|
-
const COMPARISON_OPERATORS = {
|
|
1690
|
+
});
|
|
1691
|
+
const COMPARISON_OPERATORS = sourceKeyed({
|
|
910
1692
|
"==": {
|
|
911
1693
|
comparator: "equals",
|
|
912
1694
|
negated: false,
|
|
@@ -947,7 +1729,7 @@ const COMPARISON_OPERATORS = {
|
|
|
947
1729
|
negated: false,
|
|
948
1730
|
strict: false
|
|
949
1731
|
}
|
|
950
|
-
};
|
|
1732
|
+
});
|
|
951
1733
|
const SWAPPED_COMPARATORS = {
|
|
952
1734
|
"equals": "equals",
|
|
953
1735
|
"greater-than": "less-than",
|
|
@@ -1018,14 +1800,14 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1018
1800
|
*/ class ExpressionParser {
|
|
1019
1801
|
schema;
|
|
1020
1802
|
stream;
|
|
1021
|
-
|
|
1803
|
+
scope;
|
|
1022
1804
|
paramsName;
|
|
1023
1805
|
params;
|
|
1024
1806
|
/** Set when a param value shaped the tree itself (e.g. x[p.name]) — such templates cannot be cached. */ structurallyDependsOnParams = false;
|
|
1025
|
-
constructor(schema, stream,
|
|
1807
|
+
constructor(schema, stream, scope, paramsName, params){
|
|
1026
1808
|
this.schema = schema;
|
|
1027
1809
|
this.stream = stream;
|
|
1028
|
-
this.
|
|
1810
|
+
this.scope = scope;
|
|
1029
1811
|
this.paramsName = paramsName;
|
|
1030
1812
|
this.params = params;
|
|
1031
1813
|
}
|
|
@@ -1036,6 +1818,180 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1036
1818
|
}
|
|
1037
1819
|
return expression;
|
|
1038
1820
|
}
|
|
1821
|
+
parseBody() {
|
|
1822
|
+
if (!this.stream.isPunctuation("{")) {
|
|
1823
|
+
return this.parse();
|
|
1824
|
+
}
|
|
1825
|
+
const answer = this.parseBlock();
|
|
1826
|
+
if (!this.stream.isAtEnd) {
|
|
1827
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`unexpected token '${this.stream.peek()?.value}'`));
|
|
1828
|
+
}
|
|
1829
|
+
if (answer === NEVER) {
|
|
1830
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("a predicate no row can satisfy"));
|
|
1831
|
+
}
|
|
1832
|
+
return answer;
|
|
1833
|
+
}
|
|
1834
|
+
/** The expression a `{ … }` block answers with. */ parseBlock() {
|
|
1835
|
+
this.stream.expectPunctuation("{");
|
|
1836
|
+
const answer = this.parseStatements();
|
|
1837
|
+
this.stream.expectPunctuation("}");
|
|
1838
|
+
return answer;
|
|
1839
|
+
}
|
|
1840
|
+
/** Statements up to the one that returns. What follows a `return` is never read, as in JavaScript. */ parseStatements() {
|
|
1841
|
+
if (this.stream.isPunctuation("}") || this.stream.isAtEnd) {
|
|
1842
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("a block body that returns nothing"));
|
|
1843
|
+
}
|
|
1844
|
+
const keyword = this.stream.peek();
|
|
1845
|
+
if (keyword == null || keyword.kind !== "identifier") {
|
|
1846
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`a statement starting '${keyword?.value}'`));
|
|
1847
|
+
}
|
|
1848
|
+
if (DECLARATION_KEYWORDS.has(keyword.value)) {
|
|
1849
|
+
this.declare();
|
|
1850
|
+
return this.parseStatements();
|
|
1851
|
+
}
|
|
1852
|
+
if (keyword.value === "return") {
|
|
1853
|
+
this.stream.next();
|
|
1854
|
+
const answer = this.parseReturnedCondition();
|
|
1855
|
+
this.stream.matchPunctuation(";");
|
|
1856
|
+
return answer;
|
|
1857
|
+
}
|
|
1858
|
+
if (keyword.value === "if") {
|
|
1859
|
+
return this.parseIfStatement();
|
|
1860
|
+
}
|
|
1861
|
+
if (keyword.value === "switch") {
|
|
1862
|
+
return this.parseSwitchStatement();
|
|
1863
|
+
}
|
|
1864
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`the statement '${keyword.value}'`));
|
|
1865
|
+
}
|
|
1866
|
+
/**
|
|
1867
|
+
* Binds a `const`/`let`/`var` name to the tokens of its initializer — tokens rather than a parsed
|
|
1868
|
+
* expression, so the name works as an operand, an argument, or a call receiver alike.
|
|
1869
|
+
*/ declare() {
|
|
1870
|
+
this.stream.next();
|
|
1871
|
+
const name = this.stream.next();
|
|
1872
|
+
if (name.kind !== "identifier") {
|
|
1873
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`the declaration '${name.value}'`));
|
|
1874
|
+
}
|
|
1875
|
+
this.stream.expectPunctuation("=");
|
|
1876
|
+
this.scope.set(name.value, {
|
|
1877
|
+
kind: "inlined",
|
|
1878
|
+
tokens: this.stream.takeStatementTokens()
|
|
1879
|
+
});
|
|
1880
|
+
}
|
|
1881
|
+
/** `return false` on its own, which no row satisfies, and every other returned condition. */ parseReturnedCondition() {
|
|
1882
|
+
const next = this.stream.peek();
|
|
1883
|
+
const after = this.stream.peek(1);
|
|
1884
|
+
const endsHere = after == null || after.kind === "punctuation" && (after.value === ";" || after.value === "}");
|
|
1885
|
+
if (next != null && next.kind === "identifier" && next.value === "false" && endsHere) {
|
|
1886
|
+
this.stream.next();
|
|
1887
|
+
return NEVER;
|
|
1888
|
+
}
|
|
1889
|
+
return this.parseOr();
|
|
1890
|
+
}
|
|
1891
|
+
parseIfStatement() {
|
|
1892
|
+
this.stream.next();
|
|
1893
|
+
this.stream.expectPunctuation("(");
|
|
1894
|
+
const condition = this.parseOr();
|
|
1895
|
+
this.stream.expectPunctuation(")");
|
|
1896
|
+
const whenTrue = this.parseBranch();
|
|
1897
|
+
if (this.stream.peek()?.value === "else") {
|
|
1898
|
+
this.stream.next();
|
|
1899
|
+
return this.either(condition, whenTrue, this.parseBranch());
|
|
1900
|
+
}
|
|
1901
|
+
// Without an `else`, the statements after the `if` are the other branch
|
|
1902
|
+
return this.either(condition, whenTrue, this.parseStatements());
|
|
1903
|
+
}
|
|
1904
|
+
/** One arm of an `if`: a block, or a single statement. */ parseBranch() {
|
|
1905
|
+
return this.stream.isPunctuation("{") ? this.parseBlock() : this.parseStatements();
|
|
1906
|
+
}
|
|
1907
|
+
/** A `switch` over one subject, as the disjunction of its cases. */ parseSwitchStatement() {
|
|
1908
|
+
this.stream.next();
|
|
1909
|
+
this.stream.expectPunctuation("(");
|
|
1910
|
+
const subject = this.parseValue();
|
|
1911
|
+
this.stream.expectPunctuation(")");
|
|
1912
|
+
this.stream.expectPunctuation("{");
|
|
1913
|
+
let matching = null;
|
|
1914
|
+
let pending = [];
|
|
1915
|
+
let everyLabel = [];
|
|
1916
|
+
let byDefault = null;
|
|
1917
|
+
let anyCaseBroke = false;
|
|
1918
|
+
while(!this.stream.matchPunctuation("}")){
|
|
1919
|
+
const label = this.stream.next();
|
|
1920
|
+
if (label.kind !== "identifier" || label.value !== "case" && label.value !== "default") {
|
|
1921
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`'${label.value}' inside a switch`));
|
|
1922
|
+
}
|
|
1923
|
+
if (label.value === "case") {
|
|
1924
|
+
const test = this.buildComparison(subject, COMPARISON_OPERATORS["==="], this.parseValue());
|
|
1925
|
+
pending.push(test);
|
|
1926
|
+
everyLabel.push(test);
|
|
1927
|
+
}
|
|
1928
|
+
this.stream.expectPunctuation(":");
|
|
1929
|
+
// `case 'a':` with no body of its own runs the next case's body
|
|
1930
|
+
if (this.stream.peek()?.value === "case" || this.stream.peek()?.value === "default") {
|
|
1931
|
+
continue;
|
|
1932
|
+
}
|
|
1933
|
+
if (this.stream.peek()?.value === "break") {
|
|
1934
|
+
this.stream.next();
|
|
1935
|
+
this.stream.matchPunctuation(";");
|
|
1936
|
+
anyCaseBroke = true;
|
|
1937
|
+
pending = [];
|
|
1938
|
+
continue;
|
|
1939
|
+
}
|
|
1940
|
+
const body = this.parseCaseBody();
|
|
1941
|
+
if (label.value === "default") {
|
|
1942
|
+
byDefault = body === NEVER ? null : body;
|
|
1943
|
+
continue;
|
|
1944
|
+
}
|
|
1945
|
+
if (body !== NEVER && pending.length > 0) {
|
|
1946
|
+
const reached = pending.reduce((left, right)=>or(left, right));
|
|
1947
|
+
const term = _types__rspack_import_1/* .Expression.isEmpty */.r4.isEmpty(body) ? reached : and(reached, body);
|
|
1948
|
+
matching = matching == null ? term : or(matching, term);
|
|
1949
|
+
}
|
|
1950
|
+
pending = [];
|
|
1951
|
+
}
|
|
1952
|
+
// Falling out of the switch continues after it, so the statements there are the default too
|
|
1953
|
+
const afterSwitch = byDefault == null && !this.stream.isPunctuation("}") && !this.stream.isAtEnd ? this.parseStatements() : NEVER;
|
|
1954
|
+
if (afterSwitch !== NEVER) {
|
|
1955
|
+
// A `break` also continues after the switch, so its case would take that answer rather
|
|
1956
|
+
// than none — a distinction this rewrite cannot carry
|
|
1957
|
+
if (anyCaseBroke) {
|
|
1958
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("a switch that breaks and then falls into more statements"));
|
|
1959
|
+
}
|
|
1960
|
+
byDefault = afterSwitch;
|
|
1961
|
+
}
|
|
1962
|
+
// A `default` runs only when every case failed, wherever it was written
|
|
1963
|
+
if (byDefault != null) {
|
|
1964
|
+
const noCaseMatched = everyLabel.length === 0 ? byDefault : and(this.negateExpression(everyLabel.reduce((left, right)=>or(left, right))), byDefault);
|
|
1965
|
+
matching = matching == null ? noCaseMatched : or(matching, noCaseMatched);
|
|
1966
|
+
}
|
|
1967
|
+
return matching ?? NEVER;
|
|
1968
|
+
}
|
|
1969
|
+
/** One case body, and the `break` that may follow its `return`. */ parseCaseBody() {
|
|
1970
|
+
const answer = this.parseStatements();
|
|
1971
|
+
if (this.stream.peek()?.value === "break") {
|
|
1972
|
+
this.stream.next();
|
|
1973
|
+
this.stream.matchPunctuation(";");
|
|
1974
|
+
}
|
|
1975
|
+
return answer;
|
|
1976
|
+
}
|
|
1977
|
+
/**
|
|
1978
|
+
* The predicate an `if`/`else` answers: `(condition && whenTrue) || (!condition && whenFalse)`,
|
|
1979
|
+
* with each case below that form after a constant branch cancels out.
|
|
1980
|
+
*/ either(condition, whenTrue, whenFalse) {
|
|
1981
|
+
if (whenTrue === NEVER) {
|
|
1982
|
+
return whenFalse === NEVER ? NEVER : and(this.negateExpression(condition), whenFalse);
|
|
1983
|
+
}
|
|
1984
|
+
if (whenFalse === NEVER) {
|
|
1985
|
+
return and(condition, whenTrue);
|
|
1986
|
+
}
|
|
1987
|
+
if (_types__rspack_import_1/* .Expression.isEmpty */.r4.isEmpty(whenTrue)) {
|
|
1988
|
+
return or(condition, whenFalse);
|
|
1989
|
+
}
|
|
1990
|
+
if (_types__rspack_import_1/* .Expression.isEmpty */.r4.isEmpty(whenFalse)) {
|
|
1991
|
+
return or(this.negateExpression(condition), whenTrue);
|
|
1992
|
+
}
|
|
1993
|
+
return or(and(condition, whenTrue), and(this.negateExpression(condition), whenFalse));
|
|
1994
|
+
}
|
|
1039
1995
|
// || binds loosest, so it sits at the root of the parse
|
|
1040
1996
|
parseOr() {
|
|
1041
1997
|
let left = this.parseAnd();
|
|
@@ -1083,10 +2039,18 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1083
2039
|
/**
|
|
1084
2040
|
* Applies `!` to an already-parsed expression: comparators flip their
|
|
1085
2041
|
* negated flag, compound expressions distribute via De Morgan's laws.
|
|
2042
|
+
*
|
|
2043
|
+
* Builds a new tree rather than flipping the flag in place, because an `if` uses its condition
|
|
2044
|
+
* twice — once negated — and a shared node would carry the flip into both branches.
|
|
1086
2045
|
*/ negateExpression(expression) {
|
|
1087
2046
|
if (expression instanceof _types__rspack_import_1/* .ComparatorExpression */.bQ) {
|
|
1088
|
-
|
|
1089
|
-
|
|
2047
|
+
return new _types__rspack_import_1/* .ComparatorExpression */.bQ({
|
|
2048
|
+
comparator: expression.comparator,
|
|
2049
|
+
negated: !expression.negated,
|
|
2050
|
+
strict: expression.strict,
|
|
2051
|
+
left: expression.left,
|
|
2052
|
+
right: expression.right
|
|
2053
|
+
});
|
|
1090
2054
|
}
|
|
1091
2055
|
if (expression instanceof _types__rspack_import_1/* .OperatorExpression */.fw && expression.left != null && expression.right != null) {
|
|
1092
2056
|
return new _types__rspack_import_1/* .OperatorExpression */.fw({
|
|
@@ -1098,25 +2062,125 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1098
2062
|
throw new Error(ERROR_MESSAGES.UNSUPPORTED("'!' on this expression"));
|
|
1099
2063
|
}
|
|
1100
2064
|
parseComparison() {
|
|
1101
|
-
|
|
1102
|
-
|
|
2065
|
+
/**
|
|
2066
|
+
* A parenthesised group is either a boolean sub-expression or a VALUE — `(a && b)` against
|
|
2067
|
+
* `(x.name ?? '') === 'ada'` — and which one it is is only known at the closing bracket, by
|
|
2068
|
+
* what follows. So the boolean reading is tried first and rewound if a comparator turns up.
|
|
2069
|
+
*/ if (this.stream.isPunctuation("(") && this.stream.groupIsValue() === false) {
|
|
2070
|
+
this.stream.next();
|
|
1103
2071
|
const expression = this.parseOr();
|
|
1104
2072
|
this.stream.expectPunctuation(")");
|
|
1105
|
-
const trailing = this.stream.peek();
|
|
1106
|
-
if (trailing != null && trailing.kind === "punctuation" && COMPARISON_OPERATORS[trailing.value] != null) {
|
|
1107
|
-
throw new Error(ERROR_MESSAGES.UNSUPPORTED("comparison against a parenthesized expression"));
|
|
1108
|
-
}
|
|
1109
2073
|
return expression;
|
|
1110
2074
|
}
|
|
1111
|
-
const left = this.
|
|
2075
|
+
const left = this.parseValue();
|
|
1112
2076
|
const operatorToken = this.stream.peek();
|
|
1113
2077
|
if (operatorToken != null && operatorToken.kind === "punctuation" && COMPARISON_OPERATORS[operatorToken.value] != null) {
|
|
1114
2078
|
this.stream.next();
|
|
1115
|
-
const right = this.
|
|
2079
|
+
const right = this.parseValue();
|
|
1116
2080
|
return this.buildComparison(left, COMPARISON_OPERATORS[operatorToken.value], right);
|
|
1117
2081
|
}
|
|
1118
2082
|
return this.buildStandalone(left);
|
|
1119
2083
|
}
|
|
2084
|
+
/**
|
|
2085
|
+
* A value, at JavaScript's precedence.
|
|
2086
|
+
*
|
|
2087
|
+
* Lowest first: the conditional operator, then nullish coalescing, then the bitwise levels, then
|
|
2088
|
+
* the shifts, then the arithmetic. Comparison sits between the shifts and the bitwise levels in
|
|
2089
|
+
* JavaScript, but a comparison is a boolean and is handled by `parseComparison` above, so this
|
|
2090
|
+
* chain skips it — a bitwise operand here is always a value.
|
|
2091
|
+
*/ /**
|
|
2092
|
+
* An operand from its own source, sharing this parser's schema and parameter names.
|
|
2093
|
+
*
|
|
2094
|
+
* A structural dependence found inside propagates outward: the template it belongs to cannot be
|
|
2095
|
+
* cached either.
|
|
2096
|
+
*/ parseNested(source) {
|
|
2097
|
+
const nested = new ExpressionParser(this.schema, new TokenStream(tokenize(source)), this.scope, this.paramsName, this.params);
|
|
2098
|
+
const operand = nested.parseInterpolation();
|
|
2099
|
+
// Leftover tokens mean the interpolation held something this reads only part of. Silently
|
|
2100
|
+
// keeping the part it understood is the worst outcome available: `${x.age > 5 ? "a" : "b"}`
|
|
2101
|
+
// would become `x.age`, and the filter would answer a question nobody asked.
|
|
2102
|
+
if (nested.stream.isAtEnd === false) {
|
|
2103
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("an interpolation this parser reads only part of"));
|
|
2104
|
+
}
|
|
2105
|
+
if (nested.structurallyDependsOnParams === true) {
|
|
2106
|
+
this.structurallyDependsOnParams = true;
|
|
2107
|
+
}
|
|
2108
|
+
return operand;
|
|
2109
|
+
}
|
|
2110
|
+
/**
|
|
2111
|
+
* The whole of one `${…}`.
|
|
2112
|
+
*
|
|
2113
|
+
* A conditional is read here rather than in `parseValue`, because an interpolation is the one
|
|
2114
|
+
* place a conditional appears without brackets around it.
|
|
2115
|
+
*/ parseInterpolation() {
|
|
2116
|
+
if (this.stream.holdsConditional()) {
|
|
2117
|
+
const condition = this.parseOr();
|
|
2118
|
+
this.stream.expectPunctuation("?");
|
|
2119
|
+
const whenTrue = this.parseValue();
|
|
2120
|
+
this.stream.expectPunctuation(":");
|
|
2121
|
+
const whenFalse = this.parseValue();
|
|
2122
|
+
return {
|
|
2123
|
+
kind: "conditional",
|
|
2124
|
+
condition,
|
|
2125
|
+
whenTrue,
|
|
2126
|
+
whenFalse
|
|
2127
|
+
};
|
|
2128
|
+
}
|
|
2129
|
+
return this.parseValue();
|
|
2130
|
+
}
|
|
2131
|
+
parseValue() {
|
|
2132
|
+
return this.parseCoalesce();
|
|
2133
|
+
}
|
|
2134
|
+
parseCoalesce() {
|
|
2135
|
+
return this.parseBinary(COALESCE_OPERATORS, ()=>this.parseBitwiseOr());
|
|
2136
|
+
}
|
|
2137
|
+
parseBitwiseOr() {
|
|
2138
|
+
return this.parseBinary(BITWISE_OR_OPERATORS, ()=>this.parseBitwiseXor());
|
|
2139
|
+
}
|
|
2140
|
+
parseBitwiseXor() {
|
|
2141
|
+
return this.parseBinary(BITWISE_XOR_OPERATORS, ()=>this.parseBitwiseAnd());
|
|
2142
|
+
}
|
|
2143
|
+
parseBitwiseAnd() {
|
|
2144
|
+
return this.parseBinary(BITWISE_AND_OPERATORS, ()=>this.parseShift());
|
|
2145
|
+
}
|
|
2146
|
+
parseShift() {
|
|
2147
|
+
return this.parseBinary(SHIFT_OPERATORS, ()=>this.parseAdditive());
|
|
2148
|
+
}
|
|
2149
|
+
parseAdditive() {
|
|
2150
|
+
return this.parseBinary(ADDITIVE_OPERATORS, ()=>this.parseMultiplicative());
|
|
2151
|
+
}
|
|
2152
|
+
parseMultiplicative() {
|
|
2153
|
+
return this.parseBinary(MULTIPLICATIVE_OPERATORS, ()=>this.parseExponent());
|
|
2154
|
+
}
|
|
2155
|
+
/** `**` is RIGHT-associative: `2 ** 3 ** 2` is 2 ** 9, not 8 ** 2. */ parseExponent() {
|
|
2156
|
+
const left = this.parseOperand();
|
|
2157
|
+
if (this.stream.isPunctuation("**") === false) {
|
|
2158
|
+
return left;
|
|
2159
|
+
}
|
|
2160
|
+
this.stream.next();
|
|
2161
|
+
return {
|
|
2162
|
+
kind: "arithmetic",
|
|
2163
|
+
call: "power",
|
|
2164
|
+
left,
|
|
2165
|
+
right: this.parseExponent()
|
|
2166
|
+
};
|
|
2167
|
+
}
|
|
2168
|
+
/** Left-associative, so `a - b - c` is `(a - b) - c` rather than `a - (b - c)`. */ parseBinary(operators, next) {
|
|
2169
|
+
let left = next();
|
|
2170
|
+
for(;;){
|
|
2171
|
+
const token = this.stream.peek();
|
|
2172
|
+
if (token == null || token.kind !== "punctuation" || operators[token.value] == null) {
|
|
2173
|
+
return left;
|
|
2174
|
+
}
|
|
2175
|
+
this.stream.next();
|
|
2176
|
+
left = {
|
|
2177
|
+
kind: "arithmetic",
|
|
2178
|
+
call: operators[token.value],
|
|
2179
|
+
left,
|
|
2180
|
+
right: next()
|
|
2181
|
+
};
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
1120
2184
|
parseOperand() {
|
|
1121
2185
|
const token = this.stream.peek();
|
|
1122
2186
|
if (token == null) {
|
|
@@ -1140,6 +2204,131 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1140
2204
|
locale: null
|
|
1141
2205
|
};
|
|
1142
2206
|
}
|
|
2207
|
+
// A parenthesised VALUE — `(x.price & 1)`, `(x.name ?? '')`. The boolean reading of a group
|
|
2208
|
+
// is handled in parseComparison; by the time an operand sees one it is arithmetic.
|
|
2209
|
+
if (token.kind === "punctuation" && token.value === "(") {
|
|
2210
|
+
const conditional = this.stream.groupHoldsConditional();
|
|
2211
|
+
this.stream.next();
|
|
2212
|
+
if (conditional === true) {
|
|
2213
|
+
const condition = this.parseOr();
|
|
2214
|
+
this.stream.expectPunctuation("?");
|
|
2215
|
+
const whenTrue = this.parseValue();
|
|
2216
|
+
this.stream.expectPunctuation(":");
|
|
2217
|
+
const whenFalse = this.parseValue();
|
|
2218
|
+
this.stream.expectPunctuation(")");
|
|
2219
|
+
return {
|
|
2220
|
+
kind: "conditional",
|
|
2221
|
+
condition,
|
|
2222
|
+
whenTrue,
|
|
2223
|
+
whenFalse
|
|
2224
|
+
};
|
|
2225
|
+
}
|
|
2226
|
+
const inner = this.parseValue();
|
|
2227
|
+
this.stream.expectPunctuation(")");
|
|
2228
|
+
const grouped = inner.kind === "arithmetic" ? {
|
|
2229
|
+
...inner,
|
|
2230
|
+
grouped: true
|
|
2231
|
+
} : inner;
|
|
2232
|
+
return this.withGroupCall(grouped);
|
|
2233
|
+
}
|
|
2234
|
+
/**
|
|
2235
|
+
* A template with interpolation, folded into `concat`.
|
|
2236
|
+
*
|
|
2237
|
+
* Each `${…}` was kept as source by the tokenizer and is parsed by its own stream, so it can
|
|
2238
|
+
* hold anything an operand can — a property, a param, arithmetic, another template. Empty
|
|
2239
|
+
* chunks are dropped: `${a}${b}` is two operands, not two operands and three empty strings.
|
|
2240
|
+
*/ if (token.kind === "template") {
|
|
2241
|
+
this.stream.next();
|
|
2242
|
+
const { chunks, expressions } = JSON.parse(token.value);
|
|
2243
|
+
const pieces = [];
|
|
2244
|
+
for(let at = 0; at < chunks.length; at++){
|
|
2245
|
+
if (chunks[at].length > 0) {
|
|
2246
|
+
pieces.push({
|
|
2247
|
+
kind: "value",
|
|
2248
|
+
value: chunks[at],
|
|
2249
|
+
transformer: null,
|
|
2250
|
+
locale: null
|
|
2251
|
+
});
|
|
2252
|
+
}
|
|
2253
|
+
if (at < expressions.length) {
|
|
2254
|
+
pieces.push(this.parseNested(expressions[at]));
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
if (pieces.length === 0) {
|
|
2258
|
+
return {
|
|
2259
|
+
kind: "value",
|
|
2260
|
+
value: "",
|
|
2261
|
+
transformer: null,
|
|
2262
|
+
locale: null
|
|
2263
|
+
};
|
|
2264
|
+
}
|
|
2265
|
+
// One piece and no chunk means no concat to do the coercion, so the conversion has to be
|
|
2266
|
+
// explicit: `` `${x.age}` `` is the STRING "9", not the number 9.
|
|
2267
|
+
if (pieces.length === 1) {
|
|
2268
|
+
const only = pieces[0];
|
|
2269
|
+
const alreadyText = only.kind === "value" && typeof only.value === "string";
|
|
2270
|
+
return alreadyText ? only : {
|
|
2271
|
+
kind: "arithmetic",
|
|
2272
|
+
call: "to-string",
|
|
2273
|
+
left: only,
|
|
2274
|
+
right: noArgument()
|
|
2275
|
+
};
|
|
2276
|
+
}
|
|
2277
|
+
return pieces.reduce((left, right)=>({
|
|
2278
|
+
kind: "arithmetic",
|
|
2279
|
+
call: "concat",
|
|
2280
|
+
left,
|
|
2281
|
+
right
|
|
2282
|
+
}));
|
|
2283
|
+
}
|
|
2284
|
+
if (token.kind === "bigint") {
|
|
2285
|
+
this.stream.next();
|
|
2286
|
+
return {
|
|
2287
|
+
kind: "value",
|
|
2288
|
+
value: BigInt(token.value),
|
|
2289
|
+
transformer: null,
|
|
2290
|
+
locale: null
|
|
2291
|
+
};
|
|
2292
|
+
}
|
|
2293
|
+
if (token.kind === "regex") {
|
|
2294
|
+
this.stream.next();
|
|
2295
|
+
const [source, flags] = token.value.split("\u0000");
|
|
2296
|
+
const pattern = {
|
|
2297
|
+
kind: "value",
|
|
2298
|
+
value: new RegExp(source, flags),
|
|
2299
|
+
transformer: null,
|
|
2300
|
+
locale: null
|
|
2301
|
+
};
|
|
2302
|
+
// `/^a/.test(x.name)` — the pattern is the literal, the subject is the argument, and the
|
|
2303
|
+
// tree puts them the other way round: the property is what the call applies to.
|
|
2304
|
+
if (this.stream.isPunctuation(".")) {
|
|
2305
|
+
const method = this.stream.peek(1);
|
|
2306
|
+
if (method != null && method.kind === "identifier" && method.value === "test") {
|
|
2307
|
+
this.stream.next();
|
|
2308
|
+
this.stream.next();
|
|
2309
|
+
this.stream.expectPunctuation("(");
|
|
2310
|
+
const subject = this.parseValue();
|
|
2311
|
+
this.stream.expectPunctuation(")");
|
|
2312
|
+
return {
|
|
2313
|
+
kind: "arithmetic",
|
|
2314
|
+
call: "matches",
|
|
2315
|
+
left: subject,
|
|
2316
|
+
right: pattern
|
|
2317
|
+
};
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
return pattern;
|
|
2321
|
+
}
|
|
2322
|
+
if (token.kind === "punctuation" && token.value === "~") {
|
|
2323
|
+
this.stream.next();
|
|
2324
|
+
// Unary, so the tree carries the operand and no argument
|
|
2325
|
+
return {
|
|
2326
|
+
kind: "arithmetic",
|
|
2327
|
+
call: "bit-not",
|
|
2328
|
+
left: this.parseOperand(),
|
|
2329
|
+
right: noArgument()
|
|
2330
|
+
};
|
|
2331
|
+
}
|
|
1143
2332
|
if (token.kind === "punctuation" && token.value === "-") {
|
|
1144
2333
|
this.stream.next();
|
|
1145
2334
|
const numberToken = this.stream.next();
|
|
@@ -1197,6 +2386,9 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1197
2386
|
if (argument.kind === "method-call") {
|
|
1198
2387
|
throw new Error(ERROR_MESSAGES.UNSUPPORTED("nested method call inside .includes()"));
|
|
1199
2388
|
}
|
|
2389
|
+
if (argument.kind === "arithmetic" || argument.kind === "conditional") {
|
|
2390
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("arithmetic inside .includes()"));
|
|
2391
|
+
}
|
|
1200
2392
|
return {
|
|
1201
2393
|
kind: "method-call",
|
|
1202
2394
|
target: array,
|
|
@@ -1242,16 +2434,17 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1242
2434
|
locale: null
|
|
1243
2435
|
};
|
|
1244
2436
|
}
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
if (this.paramsName != null && root === this.paramsName) {
|
|
2437
|
+
const binding = this.scope.get(root);
|
|
2438
|
+
if (binding != null) {
|
|
2439
|
+
if (binding.kind === "inlined") {
|
|
2440
|
+
this.stream.splice(binding.tokens);
|
|
2441
|
+
return this.parseOperand();
|
|
2442
|
+
}
|
|
1252
2443
|
return this.parseChain({
|
|
1253
|
-
kind:
|
|
1254
|
-
|
|
2444
|
+
kind: binding.kind,
|
|
2445
|
+
path: [
|
|
2446
|
+
...binding.path
|
|
2447
|
+
]
|
|
1255
2448
|
});
|
|
1256
2449
|
}
|
|
1257
2450
|
// A bare variable from the outer scope — its value cannot be derived from source text
|
|
@@ -1261,7 +2454,7 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1261
2454
|
* Parses the segments after an entity/params root: dot access, bracket
|
|
1262
2455
|
* access, transform methods and comparator methods.
|
|
1263
2456
|
*/ parseChain(options) {
|
|
1264
|
-
const path =
|
|
2457
|
+
const path = options.path;
|
|
1265
2458
|
let transformer = null;
|
|
1266
2459
|
let locale = null;
|
|
1267
2460
|
while(true){
|
|
@@ -1287,6 +2480,9 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1287
2480
|
if (argument.kind === "method-call") {
|
|
1288
2481
|
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`nested method call inside .${method}()`));
|
|
1289
2482
|
}
|
|
2483
|
+
if (argument.kind === "arithmetic" || argument.kind === "conditional") {
|
|
2484
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`arithmetic inside .${method}()`));
|
|
2485
|
+
}
|
|
1290
2486
|
return {
|
|
1291
2487
|
kind: "method-call",
|
|
1292
2488
|
target: this.resolveChain(options.kind, path, transformer, locale),
|
|
@@ -1323,12 +2519,15 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1323
2519
|
// docs/mutation-backlog.md) — every mutation of this four-conjunct guard reroutes
|
|
1324
2520
|
// bracket access between two paths that both collapse to NOT_PARSABLE; the
|
|
1325
2521
|
// experiment recorded there aimed 30 tests at this line and killed none.
|
|
1326
|
-
|
|
1327
|
-
|
|
2522
|
+
const binding = token.kind === "identifier" ? this.scope.get(token.value) : undefined;
|
|
2523
|
+
if (kind === "property" && binding != null && binding.kind === "param") {
|
|
2524
|
+
const paramPath = [
|
|
2525
|
+
...binding.path
|
|
2526
|
+
];
|
|
1328
2527
|
while(this.stream.matchPunctuation(".") || this.stream.matchPunctuation("?.")){
|
|
1329
2528
|
paramPath.push(this.stream.next().value);
|
|
1330
2529
|
}
|
|
1331
|
-
const resolved = resolveParamPath(this.paramsName, paramPath, this.params);
|
|
2530
|
+
const resolved = resolveParamPath(this.paramsName ?? token.value, paramPath, this.params);
|
|
1332
2531
|
if (typeof resolved !== "string") {
|
|
1333
2532
|
throw new ParamDependentParseError(ERROR_MESSAGES.PROPERTY_NOT_FOUND(paramPath.join(".")));
|
|
1334
2533
|
}
|
|
@@ -1381,6 +2580,67 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1381
2580
|
locale
|
|
1382
2581
|
};
|
|
1383
2582
|
}
|
|
2583
|
+
/**
|
|
2584
|
+
* A call on a parenthesised value: `(x.name).toLowerCase()`, `(x.age + 1).length`. Any operand can
|
|
2585
|
+
* receive one here, unlike a property chain, which carries at most one transform.
|
|
2586
|
+
*/ withGroupCall(operand) {
|
|
2587
|
+
let receiver = operand;
|
|
2588
|
+
while(this.stream.isPunctuation(".") || this.stream.isPunctuation("?.")){
|
|
2589
|
+
const segment = this.stream.peek(1);
|
|
2590
|
+
if (segment == null || segment.kind !== "identifier") {
|
|
2591
|
+
break;
|
|
2592
|
+
}
|
|
2593
|
+
if (segment.value === "length" && !this.stream.isPunctuation("(", 2)) {
|
|
2594
|
+
this.stream.next();
|
|
2595
|
+
this.stream.next();
|
|
2596
|
+
receiver = {
|
|
2597
|
+
kind: "arithmetic",
|
|
2598
|
+
call: "length",
|
|
2599
|
+
left: receiver,
|
|
2600
|
+
right: noArgument()
|
|
2601
|
+
};
|
|
2602
|
+
continue;
|
|
2603
|
+
}
|
|
2604
|
+
const transform = TRANSFORM_METHODS[segment.value];
|
|
2605
|
+
if (transform != null) {
|
|
2606
|
+
this.stream.next();
|
|
2607
|
+
this.stream.next();
|
|
2608
|
+
this.stream.expectPunctuation("(");
|
|
2609
|
+
this.stream.expectPunctuation(")");
|
|
2610
|
+
receiver = {
|
|
2611
|
+
kind: "arithmetic",
|
|
2612
|
+
call: transform.transformer,
|
|
2613
|
+
left: receiver,
|
|
2614
|
+
right: transform.locale == null ? noArgument() : {
|
|
2615
|
+
kind: "value",
|
|
2616
|
+
value: transform.locale,
|
|
2617
|
+
transformer: null,
|
|
2618
|
+
locale: null
|
|
2619
|
+
}
|
|
2620
|
+
};
|
|
2621
|
+
continue;
|
|
2622
|
+
}
|
|
2623
|
+
// A comparator method needs a property target, which only an ungrouped chain produces
|
|
2624
|
+
if (COMPARATOR_METHODS[segment.value] != null && receiver.kind === "property") {
|
|
2625
|
+
this.stream.next();
|
|
2626
|
+
this.stream.next();
|
|
2627
|
+
this.stream.expectPunctuation("(");
|
|
2628
|
+
const argument = this.parseOperand();
|
|
2629
|
+
this.stream.expectPunctuation(")");
|
|
2630
|
+
if (argument.kind !== "property" && argument.kind !== "value" && argument.kind !== "param") {
|
|
2631
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`'.${segment.value}()' on that argument`));
|
|
2632
|
+
}
|
|
2633
|
+
return {
|
|
2634
|
+
kind: "method-call",
|
|
2635
|
+
target: receiver,
|
|
2636
|
+
method: segment.value,
|
|
2637
|
+
argument
|
|
2638
|
+
};
|
|
2639
|
+
}
|
|
2640
|
+
break;
|
|
2641
|
+
}
|
|
2642
|
+
return receiver;
|
|
2643
|
+
}
|
|
1384
2644
|
withValueTransformer(operand) {
|
|
1385
2645
|
if (this.stream.isPunctuation(".")) {
|
|
1386
2646
|
const method = this.stream.peek(1);
|
|
@@ -1414,12 +2674,22 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1414
2674
|
if (right.kind === "method-call") {
|
|
1415
2675
|
throw new Error(ERROR_MESSAGES.UNSUPPORTED("method call on the right side of a comparison"));
|
|
1416
2676
|
}
|
|
1417
|
-
if (left
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
2677
|
+
if (needsBrackets(left) || needsBrackets(right)) {
|
|
2678
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("a bitwise or nullish operator compared without brackets, which JavaScript reads the other way round"));
|
|
2679
|
+
}
|
|
2680
|
+
if (left.kind === "arithmetic" || right.kind === "arithmetic" || left.kind === "conditional" || right.kind === "conditional") {
|
|
2681
|
+
if (containsProperty(left) === false && containsProperty(right) === false) {
|
|
2682
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("arithmetic that references no schema property"));
|
|
1422
2683
|
}
|
|
2684
|
+
return new _types__rspack_import_1/* .ComparatorExpression */.bQ({
|
|
2685
|
+
comparator: operator.comparator,
|
|
2686
|
+
negated: operator.negated,
|
|
2687
|
+
strict: operator.strict,
|
|
2688
|
+
left: this.createOperandExpression(left),
|
|
2689
|
+
right: this.createOperandExpression(right)
|
|
2690
|
+
});
|
|
2691
|
+
}
|
|
2692
|
+
if (left.kind === "property" && right.kind === "property") {
|
|
1423
2693
|
return new _types__rspack_import_1/* .ComparatorExpression */.bQ({
|
|
1424
2694
|
comparator: operator.comparator,
|
|
1425
2695
|
negated: operator.negated,
|
|
@@ -1428,18 +2698,63 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1428
2698
|
right: this.createPropertyExpression(right)
|
|
1429
2699
|
});
|
|
1430
2700
|
}
|
|
2701
|
+
// Only a loose comparison coerces. `===` records `strict` and honouring it is the point.
|
|
1431
2702
|
if (left.kind === "property" && right.kind !== "property") {
|
|
1432
|
-
return this.buildPropertyComparator(left, operator, right, /* applyConverter */
|
|
2703
|
+
return this.buildPropertyComparator(left, operator, right, /* applyConverter */ !operator.strict);
|
|
1433
2704
|
}
|
|
1434
2705
|
if (right.kind === "property" && left.kind !== "property") {
|
|
1435
2706
|
const swapped = {
|
|
1436
2707
|
...operator,
|
|
1437
2708
|
comparator: SWAPPED_COMPARATORS[operator.comparator]
|
|
1438
2709
|
};
|
|
1439
|
-
return this.buildPropertyComparator(right, swapped, left, /* applyConverter */
|
|
2710
|
+
return this.buildPropertyComparator(right, swapped, left, /* applyConverter */ !operator.strict);
|
|
2711
|
+
}
|
|
2712
|
+
const settled = this.settleConstantComparison(left, operator, right);
|
|
2713
|
+
if (settled != null) {
|
|
2714
|
+
return settled;
|
|
1440
2715
|
}
|
|
1441
2716
|
throw new Error(ERROR_MESSAGES.UNSUPPORTED("comparison requires a schema property on at least one side"));
|
|
1442
2717
|
}
|
|
2718
|
+
/**
|
|
2719
|
+
* The answer a comparison of two constants gives, when that answer is `true`. The other answer
|
|
2720
|
+
* excludes every row, which has no expression node.
|
|
2721
|
+
*/ settleConstantComparison(left, operator, right) {
|
|
2722
|
+
const leftValue = this.constantOf(left);
|
|
2723
|
+
const rightValue = this.constantOf(right);
|
|
2724
|
+
if (leftValue === UNKNOWN_UNTIL_ROW || rightValue === UNKNOWN_UNTIL_ROW) {
|
|
2725
|
+
return null;
|
|
2726
|
+
}
|
|
2727
|
+
const answer = (0,_evaluate__rspack_import_3/* .evaluate */._3)(new _types__rspack_import_1/* .ComparatorExpression */.bQ({
|
|
2728
|
+
comparator: operator.comparator,
|
|
2729
|
+
negated: operator.negated,
|
|
2730
|
+
strict: operator.strict,
|
|
2731
|
+
left: new _types__rspack_import_1/* .ValueExpression */.Ko({
|
|
2732
|
+
value: leftValue
|
|
2733
|
+
}),
|
|
2734
|
+
right: new _types__rspack_import_1/* .ValueExpression */.Ko({
|
|
2735
|
+
value: rightValue
|
|
2736
|
+
})
|
|
2737
|
+
}), {});
|
|
2738
|
+
if (answer === true) {
|
|
2739
|
+
return _types__rspack_import_1/* .Expression.EMPTY */.r4.EMPTY;
|
|
2740
|
+
}
|
|
2741
|
+
// Params decided this, so the refusal must not be cached against the source: the same filter
|
|
2742
|
+
// with other params can be a tautology.
|
|
2743
|
+
if (left.kind === "param" || right.kind === "param") {
|
|
2744
|
+
throw new ParamDependentParseError(ERROR_MESSAGES.UNSUPPORTED("a params comparison no row satisfies"));
|
|
2745
|
+
}
|
|
2746
|
+
return null;
|
|
2747
|
+
}
|
|
2748
|
+
/** The value an operand holds already, for the operands that do not depend on a row. */ constantOf(operand) {
|
|
2749
|
+
if (operand.kind === "value" && operand.transformer == null) {
|
|
2750
|
+
return operand.value;
|
|
2751
|
+
}
|
|
2752
|
+
if (operand.kind === "param" && operand.transformer == null) {
|
|
2753
|
+
this.structurallyDependsOnParams = true;
|
|
2754
|
+
return resolveParamPath(this.paramsName ?? "params", operand.path, this.params);
|
|
2755
|
+
}
|
|
2756
|
+
return UNKNOWN_UNTIL_ROW;
|
|
2757
|
+
}
|
|
1443
2758
|
buildStandalone(operand) {
|
|
1444
2759
|
if (operand.kind === "method-call") {
|
|
1445
2760
|
return this.buildMethodComparator(operand);
|
|
@@ -1462,6 +2777,21 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1462
2777
|
locale: null
|
|
1463
2778
|
}, /* applyConverter */ true);
|
|
1464
2779
|
}
|
|
2780
|
+
// A boolean-valued call standing alone IS the predicate
|
|
2781
|
+
if (operand.kind === "arithmetic" && operand.call === "matches") {
|
|
2782
|
+
return new _types__rspack_import_1/* .ComparatorExpression */.bQ({
|
|
2783
|
+
comparator: "equals",
|
|
2784
|
+
negated: false,
|
|
2785
|
+
strict: false,
|
|
2786
|
+
left: this.createOperandExpression(operand),
|
|
2787
|
+
right: new _types__rspack_import_1/* .ValueExpression */.Ko({
|
|
2788
|
+
value: true
|
|
2789
|
+
})
|
|
2790
|
+
});
|
|
2791
|
+
}
|
|
2792
|
+
if (operand.kind === "arithmetic" || operand.kind === "conditional") {
|
|
2793
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("arithmetic used as a condition rather than compared"));
|
|
2794
|
+
}
|
|
1465
2795
|
// Constant `true` — a tautology, which parseAnd/parseOr simplify away
|
|
1466
2796
|
if (operand.kind === "value" && operand.value === true && operand.transformer == null) {
|
|
1467
2797
|
return _types__rspack_import_1/* .Expression.EMPTY */.r4.EMPTY;
|
|
@@ -1514,12 +2844,6 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1514
2844
|
right: this.createValueExpression(value, null, /* applyConverter */ false)
|
|
1515
2845
|
});
|
|
1516
2846
|
}
|
|
1517
|
-
// Casing transformers on a property are only meaningful with string-matching
|
|
1518
|
-
// comparators; on relational comparators the plugins would silently
|
|
1519
|
-
// ignore them and return wrong data
|
|
1520
|
-
if (property.transformer != null && !isStringMatch) {
|
|
1521
|
-
throw new Error(ERROR_MESSAGES.UNSUPPORTED("transform method outside of startsWith/endsWith/includes"));
|
|
1522
|
-
}
|
|
1523
2847
|
return new _types__rspack_import_1/* .ComparatorExpression */.bQ({
|
|
1524
2848
|
comparator: operator.comparator,
|
|
1525
2849
|
negated: operator.negated,
|
|
@@ -1528,31 +2852,60 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1528
2852
|
right: this.createValueExpression(value, property.property, applyConverter)
|
|
1529
2853
|
});
|
|
1530
2854
|
}
|
|
2855
|
+
/**
|
|
2856
|
+
* Any operand as an expression.
|
|
2857
|
+
*
|
|
2858
|
+
* Values inside arithmetic take no paired property: the result is a computed number, so the
|
|
2859
|
+
* property's serializer and type converter do not describe it — the same reason `.length` skips
|
|
2860
|
+
* them.
|
|
2861
|
+
*/ createOperandExpression(operand) {
|
|
2862
|
+
if (operand.kind === "conditional") {
|
|
2863
|
+
return new _types__rspack_import_1/* .CallExpression */.DG({
|
|
2864
|
+
call: "conditional",
|
|
2865
|
+
expression: operand.condition,
|
|
2866
|
+
arguments: [
|
|
2867
|
+
this.createOperandExpression(operand.whenTrue),
|
|
2868
|
+
this.createOperandExpression(operand.whenFalse)
|
|
2869
|
+
]
|
|
2870
|
+
});
|
|
2871
|
+
}
|
|
2872
|
+
if (operand.kind === "arithmetic") {
|
|
2873
|
+
return new _types__rspack_import_1/* .CallExpression */.DG({
|
|
2874
|
+
call: operand.call,
|
|
2875
|
+
expression: this.createOperandExpression(operand.left),
|
|
2876
|
+
arguments: operand.right === NO_ARGUMENT ? [] : operand.extra == null ? [
|
|
2877
|
+
this.createOperandExpression(operand.right)
|
|
2878
|
+
] : [
|
|
2879
|
+
this.createOperandExpression(operand.right),
|
|
2880
|
+
this.createOperandExpression(operand.extra)
|
|
2881
|
+
]
|
|
2882
|
+
});
|
|
2883
|
+
}
|
|
2884
|
+
if (operand.kind === "property") {
|
|
2885
|
+
return this.createPropertyExpression(operand);
|
|
2886
|
+
}
|
|
2887
|
+
if (operand.kind === "method-call") {
|
|
2888
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("a method call inside arithmetic"));
|
|
2889
|
+
}
|
|
2890
|
+
return this.createValueExpression(operand, null, /* applyConverter */ false);
|
|
2891
|
+
}
|
|
1531
2892
|
createPropertyExpression(operand) {
|
|
1532
|
-
|
|
2893
|
+
return asCall(new _types__rspack_import_1/* .PropertyExpression */.ep({
|
|
1533
2894
|
property: operand.property
|
|
1534
|
-
});
|
|
1535
|
-
expression.transformer = operand.transformer;
|
|
1536
|
-
expression.locale = operand.locale;
|
|
1537
|
-
return expression;
|
|
2895
|
+
}), operand.transformer, operand.locale);
|
|
1538
2896
|
}
|
|
1539
2897
|
createValueExpression(operand, pairedProperty, applyConverter) {
|
|
1540
2898
|
if (operand.kind === "param") {
|
|
1541
|
-
|
|
2899
|
+
return asCall(new ParamReferenceExpression({
|
|
1542
2900
|
paramPath: operand.path,
|
|
1543
2901
|
pairedProperty,
|
|
1544
2902
|
applyConverter
|
|
1545
|
-
});
|
|
1546
|
-
expression.transformer = operand.transformer;
|
|
1547
|
-
expression.locale = operand.locale;
|
|
1548
|
-
return expression;
|
|
2903
|
+
}), operand.transformer, operand.locale);
|
|
1549
2904
|
}
|
|
1550
2905
|
const expression = new _types__rspack_import_1/* .ValueExpression */.Ko({
|
|
1551
2906
|
value: resolvePairedValue(operand.value, pairedProperty, applyConverter)
|
|
1552
2907
|
});
|
|
1553
|
-
expression.transformer
|
|
1554
|
-
expression.locale = operand.locale;
|
|
1555
|
-
return expression;
|
|
2908
|
+
return asCall(expression, operand.transformer, operand.locale);
|
|
1556
2909
|
}
|
|
1557
2910
|
}
|
|
1558
2911
|
// #endregion
|
|
@@ -1564,28 +2917,19 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1564
2917
|
*/ const bindExpression = (expression, paramsName, params)=>{
|
|
1565
2918
|
if (expression instanceof ParamReferenceExpression) {
|
|
1566
2919
|
const raw = resolveParamPath(paramsName ?? "params", expression.paramPath, params);
|
|
1567
|
-
|
|
2920
|
+
return new _types__rspack_import_1/* .ValueExpression */.Ko({
|
|
1568
2921
|
value: resolvePairedValue(raw, expression.pairedProperty, expression.applyConverter)
|
|
1569
2922
|
});
|
|
1570
|
-
bound.transformer = expression.transformer;
|
|
1571
|
-
bound.locale = expression.locale;
|
|
1572
|
-
return bound;
|
|
1573
2923
|
}
|
|
1574
2924
|
if (expression instanceof _types__rspack_import_1/* .ValueExpression */.Ko) {
|
|
1575
|
-
|
|
2925
|
+
return new _types__rspack_import_1/* .ValueExpression */.Ko({
|
|
1576
2926
|
value: expression.value
|
|
1577
2927
|
});
|
|
1578
|
-
clone.transformer = expression.transformer;
|
|
1579
|
-
clone.locale = expression.locale;
|
|
1580
|
-
return clone;
|
|
1581
2928
|
}
|
|
1582
2929
|
if (expression instanceof _types__rspack_import_1/* .PropertyExpression */.ep) {
|
|
1583
|
-
|
|
2930
|
+
return new _types__rspack_import_1/* .PropertyExpression */.ep({
|
|
1584
2931
|
property: expression.property
|
|
1585
2932
|
});
|
|
1586
|
-
clone.transformer = expression.transformer;
|
|
1587
|
-
clone.locale = expression.locale;
|
|
1588
|
-
return clone;
|
|
1589
2933
|
}
|
|
1590
2934
|
if (expression instanceof _types__rspack_import_1/* .ComparatorExpression */.bQ) {
|
|
1591
2935
|
return new _types__rspack_import_1/* .ComparatorExpression */.bQ({
|
|
@@ -1603,8 +2947,99 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1603
2947
|
right: expression.right ? bindExpression(expression.right, paramsName, params) : undefined
|
|
1604
2948
|
});
|
|
1605
2949
|
}
|
|
2950
|
+
if (expression instanceof _types__rspack_import_1/* .CallExpression */.DG) {
|
|
2951
|
+
return new _types__rspack_import_1/* .CallExpression */.DG({
|
|
2952
|
+
call: expression.call,
|
|
2953
|
+
expression: bindExpression(expression.expression, paramsName, params),
|
|
2954
|
+
arguments: expression.arguments.map((argument)=>bindExpression(argument, paramsName, params))
|
|
2955
|
+
});
|
|
2956
|
+
}
|
|
1606
2957
|
return expression;
|
|
1607
2958
|
};
|
|
2959
|
+
/**
|
|
2960
|
+
* Wraps an operand in the call a transform method named, if there was one.
|
|
2961
|
+
*
|
|
2962
|
+
* `Transformer` and `Call` share these three names, so the transform IS the call name. A locale
|
|
2963
|
+
* becomes the call's first argument, which is where it belongs — it qualifies the casing, not the
|
|
2964
|
+
* property.
|
|
2965
|
+
*/ const asCall = (inner, transformer, locale)=>{
|
|
2966
|
+
if (transformer == null) {
|
|
2967
|
+
return inner;
|
|
2968
|
+
}
|
|
2969
|
+
return new _types__rspack_import_1/* .CallExpression */.DG({
|
|
2970
|
+
call: transformer,
|
|
2971
|
+
expression: inner,
|
|
2972
|
+
arguments: locale == null ? [] : [
|
|
2973
|
+
new _types__rspack_import_1/* .ValueExpression */.Ko({
|
|
2974
|
+
value: locale
|
|
2975
|
+
})
|
|
2976
|
+
]
|
|
2977
|
+
});
|
|
2978
|
+
};
|
|
2979
|
+
/** Binds every name a destructuring pattern introduces to the path it reads. */ const bindPattern = (stream, kind, path, scope)=>{
|
|
2980
|
+
if (!stream.matchPunctuation("{")) {
|
|
2981
|
+
const name = stream.next();
|
|
2982
|
+
if (name.kind !== "identifier") {
|
|
2983
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`parameter '${name.value}'`));
|
|
2984
|
+
}
|
|
2985
|
+
scope.set(name.value, {
|
|
2986
|
+
kind,
|
|
2987
|
+
path
|
|
2988
|
+
});
|
|
2989
|
+
return;
|
|
2990
|
+
}
|
|
2991
|
+
while(!stream.matchPunctuation("}")){
|
|
2992
|
+
const key = stream.next();
|
|
2993
|
+
if (key.kind !== "identifier") {
|
|
2994
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`destructured key '${key.value}'`));
|
|
2995
|
+
}
|
|
2996
|
+
if (stream.matchPunctuation(":")) {
|
|
2997
|
+
bindPattern(stream, kind, [
|
|
2998
|
+
...path,
|
|
2999
|
+
key.value
|
|
3000
|
+
], scope);
|
|
3001
|
+
} else {
|
|
3002
|
+
scope.set(key.value, {
|
|
3003
|
+
kind,
|
|
3004
|
+
path: [
|
|
3005
|
+
...path,
|
|
3006
|
+
key.value
|
|
3007
|
+
]
|
|
3008
|
+
});
|
|
3009
|
+
}
|
|
3010
|
+
if (!stream.matchPunctuation(",")) {
|
|
3011
|
+
stream.expectPunctuation("}");
|
|
3012
|
+
return;
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
};
|
|
3016
|
+
/** Reads a filter's parameter list — the entity alone, or the `[entity, params]` pair — into a scope. */ const buildScope = (parameterNames, hasParams)=>{
|
|
3017
|
+
const stream = new TokenStream(tokenize(parameterNames));
|
|
3018
|
+
const scope = new Map();
|
|
3019
|
+
if (!stream.matchPunctuation("[")) {
|
|
3020
|
+
bindPattern(stream, "property", [], scope);
|
|
3021
|
+
return {
|
|
3022
|
+
scope,
|
|
3023
|
+
paramsName: null
|
|
3024
|
+
};
|
|
3025
|
+
}
|
|
3026
|
+
bindPattern(stream, "property", [], scope);
|
|
3027
|
+
if (hasParams && stream.matchPunctuation(",") && !stream.isPunctuation("]")) {
|
|
3028
|
+
bindPattern(stream, "param", [], scope);
|
|
3029
|
+
}
|
|
3030
|
+
return {
|
|
3031
|
+
scope,
|
|
3032
|
+
paramsName: wholeParamsName(scope)
|
|
3033
|
+
};
|
|
3034
|
+
};
|
|
3035
|
+
/** The name the whole params object was given, when it was not destructured. Error messages only. */ const wholeParamsName = (scope)=>{
|
|
3036
|
+
for (const [name, binding] of scope){
|
|
3037
|
+
if (binding.kind === "param" && binding.path.length === 0) {
|
|
3038
|
+
return name;
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
return null;
|
|
3042
|
+
};
|
|
1608
3043
|
/**
|
|
1609
3044
|
* Splits stringified filter source into parameter names and the expression
|
|
1610
3045
|
* body, unwrapping single-return block bodies.
|
|
@@ -1634,33 +3069,12 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1634
3069
|
parameterNames = parameterNames.slice(1, -1).trim();
|
|
1635
3070
|
}
|
|
1636
3071
|
}
|
|
1637
|
-
|
|
1638
|
-
let paramsName = null;
|
|
1639
|
-
if (parameterNames.startsWith("[") && parameterNames.endsWith("]")) {
|
|
1640
|
-
const destructured = parameterNames.slice(1, -1).split(",").map((w)=>w.trim());
|
|
1641
|
-
entityName = destructured[0];
|
|
1642
|
-
if (hasParams) {
|
|
1643
|
-
paramsName = destructured[1] ?? null;
|
|
1644
|
-
}
|
|
1645
|
-
} else {
|
|
1646
|
-
entityName = parameterNames;
|
|
1647
|
-
}
|
|
1648
|
-
if (entityName == null || entityName.length === 0) {
|
|
3072
|
+
if (parameterNames.length === 0) {
|
|
1649
3073
|
throw new Error("Invalid Function");
|
|
1650
3074
|
}
|
|
1651
|
-
|
|
1652
|
-
if (body.startsWith("{")) {
|
|
1653
|
-
const inner = body.slice(1, body.lastIndexOf("}")).trim();
|
|
1654
|
-
if (!inner.startsWith("return")) {
|
|
1655
|
-
throw new Error(ERROR_MESSAGES.UNSUPPORTED("block body without a single return statement"));
|
|
1656
|
-
}
|
|
1657
|
-
body = inner.slice("return".length).trim();
|
|
1658
|
-
if (body.endsWith(";")) {
|
|
1659
|
-
body = body.slice(0, -1).trim();
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
3075
|
+
const { scope, paramsName } = buildScope(parameterNames, hasParams);
|
|
1662
3076
|
return {
|
|
1663
|
-
|
|
3077
|
+
scope,
|
|
1664
3078
|
paramsName,
|
|
1665
3079
|
body
|
|
1666
3080
|
};
|
|
@@ -1728,17 +3142,27 @@ const combineExpressions = (...expressions)=>{
|
|
|
1728
3142
|
*/ const parseFragment = (schema, body, rootName)=>{
|
|
1729
3143
|
try {
|
|
1730
3144
|
const stream = new TokenStream(tokenize(body));
|
|
1731
|
-
const
|
|
1732
|
-
|
|
3145
|
+
const scope = new Map([
|
|
3146
|
+
[
|
|
3147
|
+
rootName,
|
|
3148
|
+
{
|
|
3149
|
+
kind: "property",
|
|
3150
|
+
path: []
|
|
3151
|
+
}
|
|
3152
|
+
]
|
|
3153
|
+
]);
|
|
3154
|
+
const parser = new ExpressionParser(schema, stream, scope, null, undefined);
|
|
3155
|
+
return foldConstantCalls(parser.parse());
|
|
1733
3156
|
} catch {
|
|
1734
3157
|
// The failure is expected and informative — see above — so it is not logged. A caller that
|
|
1735
3158
|
// parses one conjunct against two schemas would otherwise warn on every successful split.
|
|
1736
3159
|
return Expression.NOT_PARSABLE;
|
|
1737
3160
|
}
|
|
1738
3161
|
};
|
|
3162
|
+
/** What the parser refused, from a throw that may not be an `Error`. */ const refusalOf = (error)=>error instanceof Error ? error.message : String(error);
|
|
1739
3163
|
const toExpression = (schema, fn, params)=>{
|
|
1740
3164
|
const stringifiedFunction = fn.toString();
|
|
1741
|
-
const warn = (error)=>
|
|
3165
|
+
const warn = (error)=>_utilities__rspack_import_4/* .logger.warn */.vF.warn("Error parsing expression", {
|
|
1742
3166
|
error,
|
|
1743
3167
|
collectionName: schema.collectionName,
|
|
1744
3168
|
params,
|
|
@@ -1746,16 +3170,17 @@ const toExpression = (schema, fn, params)=>{
|
|
|
1746
3170
|
});
|
|
1747
3171
|
const cached = getCachedTemplate(schema, stringifiedFunction);
|
|
1748
3172
|
if (cached != null) {
|
|
1749
|
-
// A cached failure — the warning was already logged when it was discovered
|
|
3173
|
+
// A cached failure — the warning was already logged when it was discovered. The template
|
|
3174
|
+
// carries what was refused, and `.explain()` is usually called once the cache is warm.
|
|
1750
3175
|
if (_types__rspack_import_1/* .Expression.isNotParsable */.r4.isNotParsable(cached.template)) {
|
|
1751
|
-
return
|
|
3176
|
+
return cached.template;
|
|
1752
3177
|
}
|
|
1753
3178
|
try {
|
|
1754
|
-
return bindExpression(cached.template, cached.paramsName, params);
|
|
3179
|
+
return (0,_fold__rspack_import_5/* .foldConstantCalls */.F5)(bindExpression(cached.template, cached.paramsName, params));
|
|
1755
3180
|
} catch (error) {
|
|
1756
3181
|
// Binding failures are param-dependent by nature — never cached
|
|
1757
3182
|
warn(error);
|
|
1758
|
-
return _types__rspack_import_1/* .Expression.
|
|
3183
|
+
return _types__rspack_import_1/* .Expression.notParsable */.r4.notParsable(refusalOf(error));
|
|
1759
3184
|
}
|
|
1760
3185
|
}
|
|
1761
3186
|
let paramsName = null;
|
|
@@ -1764,22 +3189,23 @@ const toExpression = (schema, fn, params)=>{
|
|
|
1764
3189
|
try {
|
|
1765
3190
|
const shape = resolveFunctionShape(stringifiedFunction, params != null);
|
|
1766
3191
|
const stream = new TokenStream(tokenize(shape.body));
|
|
1767
|
-
const parser = new ExpressionParser(schema, stream, shape.
|
|
3192
|
+
const parser = new ExpressionParser(schema, stream, shape.scope, shape.paramsName, params);
|
|
1768
3193
|
paramsName = shape.paramsName;
|
|
1769
|
-
template = parser.
|
|
3194
|
+
template = parser.parseBody();
|
|
1770
3195
|
structurallyDependsOnParams = parser.structurallyDependsOnParams;
|
|
1771
3196
|
} catch (error) {
|
|
1772
3197
|
// Cache the failure so a hot query on an unsupported filter doesn't
|
|
1773
3198
|
// re-parse and re-warn on every execution. Param-dependent failures are
|
|
1774
3199
|
// exempt: the same source can succeed with different params.
|
|
3200
|
+
const refused = _types__rspack_import_1/* .Expression.notParsable */.r4.notParsable(refusalOf(error));
|
|
1775
3201
|
if (!(error instanceof ParamDependentParseError)) {
|
|
1776
3202
|
setCachedTemplate(schema, stringifiedFunction, {
|
|
1777
|
-
template:
|
|
3203
|
+
template: refused,
|
|
1778
3204
|
paramsName: null
|
|
1779
3205
|
});
|
|
1780
3206
|
}
|
|
1781
3207
|
warn(error);
|
|
1782
|
-
return
|
|
3208
|
+
return refused;
|
|
1783
3209
|
}
|
|
1784
3210
|
// Templates whose structure was resolved from param values are only
|
|
1785
3211
|
// valid for this exact params object — parse those fresh every time
|
|
@@ -1790,10 +3216,10 @@ const toExpression = (schema, fn, params)=>{
|
|
|
1790
3216
|
});
|
|
1791
3217
|
}
|
|
1792
3218
|
try {
|
|
1793
|
-
return bindExpression(template, paramsName, params);
|
|
3219
|
+
return (0,_fold__rspack_import_5/* .foldConstantCalls */.F5)(bindExpression(template, paramsName, params));
|
|
1794
3220
|
} catch (error) {
|
|
1795
3221
|
warn(error);
|
|
1796
|
-
return _types__rspack_import_1/* .Expression.
|
|
3222
|
+
return _types__rspack_import_1/* .Expression.notParsable */.r4.notParsable(refusalOf(error));
|
|
1797
3223
|
}
|
|
1798
3224
|
};
|
|
1799
3225
|
|
|
@@ -1801,6 +3227,7 @@ const toExpression = (schema, fn, params)=>{
|
|
|
1801
3227
|
},
|
|
1802
3228
|
27(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
1803
3229
|
__webpack_require__.d(__webpack_exports__, {
|
|
3230
|
+
DG: () => (CallExpression),
|
|
1804
3231
|
Ko: () => (ValueExpression),
|
|
1805
3232
|
bQ: () => (ComparatorExpression),
|
|
1806
3233
|
ep: () => (PropertyExpression),
|
|
@@ -1810,59 +3237,69 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
1810
3237
|
const valueToJson = (value)=>{
|
|
1811
3238
|
if (value === undefined) {
|
|
1812
3239
|
return {
|
|
1813
|
-
|
|
3240
|
+
undefined: true
|
|
1814
3241
|
};
|
|
1815
3242
|
}
|
|
1816
3243
|
if (value === null) {
|
|
1817
|
-
return
|
|
1818
|
-
k: "raw",
|
|
1819
|
-
v: null
|
|
1820
|
-
};
|
|
3244
|
+
return null;
|
|
1821
3245
|
}
|
|
1822
3246
|
if (value instanceof Date) {
|
|
1823
3247
|
// ISO rather than epoch millis: it survives a human reading the payload, and an invalid
|
|
1824
3248
|
// Date has no ISO form — so it is caught here rather than becoming a silent `null`.
|
|
1825
3249
|
return {
|
|
1826
|
-
|
|
1827
|
-
v: value.toISOString()
|
|
3250
|
+
date: value.toISOString()
|
|
1828
3251
|
};
|
|
1829
3252
|
}
|
|
1830
3253
|
if (Array.isArray(value)) {
|
|
1831
|
-
return
|
|
1832
|
-
k: "array",
|
|
1833
|
-
v: value.map(valueToJson)
|
|
1834
|
-
};
|
|
3254
|
+
return value.map(valueToJson);
|
|
1835
3255
|
}
|
|
1836
3256
|
if (typeof value === "number" && Number.isFinite(value) === false) {
|
|
1837
3257
|
// `JSON.stringify` turns all three of these into `null`, which would compare as a different
|
|
1838
3258
|
// value entirely rather than failing.
|
|
1839
3259
|
return {
|
|
1840
|
-
|
|
1841
|
-
|
|
3260
|
+
number: Number.isNaN(value) ? "NaN" : value > 0 ? "Infinity" : "-Infinity"
|
|
3261
|
+
};
|
|
3262
|
+
}
|
|
3263
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
3264
|
+
return value;
|
|
3265
|
+
}
|
|
3266
|
+
if (value instanceof RegExp) {
|
|
3267
|
+
return {
|
|
3268
|
+
regex: {
|
|
3269
|
+
source: value.source,
|
|
3270
|
+
flags: value.flags
|
|
3271
|
+
}
|
|
1842
3272
|
};
|
|
1843
3273
|
}
|
|
1844
|
-
|
|
3274
|
+
// `JSON.stringify` throws outright on a bigint rather than losing it quietly, so this is the one
|
|
3275
|
+
// tag that turns a crash into a value
|
|
3276
|
+
if (typeof value === "bigint") {
|
|
1845
3277
|
return {
|
|
1846
|
-
|
|
1847
|
-
v: value
|
|
3278
|
+
bigint: value.toString()
|
|
1848
3279
|
};
|
|
1849
3280
|
}
|
|
1850
3281
|
throw new Error(`Cannot serialize this filter value: only strings, numbers, booleans, null, undefined, Dates and arrays of those can cross a wire. ` + `Received: ${Object.prototype.toString.call(value)}`);
|
|
1851
3282
|
};
|
|
1852
3283
|
const valueFromJson = (value)=>{
|
|
1853
|
-
if (value
|
|
1854
|
-
return
|
|
3284
|
+
if (value === null || typeof value !== "object") {
|
|
3285
|
+
return value;
|
|
3286
|
+
}
|
|
3287
|
+
if (Array.isArray(value)) {
|
|
3288
|
+
return value.map(valueFromJson);
|
|
3289
|
+
}
|
|
3290
|
+
if ("date" in value) {
|
|
3291
|
+
return new Date(value.date);
|
|
1855
3292
|
}
|
|
1856
|
-
if (
|
|
1857
|
-
return
|
|
3293
|
+
if ("undefined" in value) {
|
|
3294
|
+
return undefined;
|
|
1858
3295
|
}
|
|
1859
|
-
if (
|
|
1860
|
-
return value.
|
|
3296
|
+
if ("regex" in value) {
|
|
3297
|
+
return new RegExp(value.regex.source, value.regex.flags);
|
|
1861
3298
|
}
|
|
1862
|
-
if (
|
|
1863
|
-
return value.
|
|
3299
|
+
if ("bigint" in value) {
|
|
3300
|
+
return BigInt(value.bigint);
|
|
1864
3301
|
}
|
|
1865
|
-
return value.
|
|
3302
|
+
return value.number === "NaN" ? Number.NaN : value.number === "Infinity" ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY;
|
|
1866
3303
|
};
|
|
1867
3304
|
/**
|
|
1868
3305
|
* The base class for all expression types.
|
|
@@ -1879,6 +3316,9 @@ const valueFromJson = (value)=>{
|
|
|
1879
3316
|
static get NOT_PARSABLE() {
|
|
1880
3317
|
return new NotParsableExpression();
|
|
1881
3318
|
}
|
|
3319
|
+
/** `NOT_PARSABLE`, carrying what the parser refused. */ static notParsable(reason) {
|
|
3320
|
+
return new NotParsableExpression(reason);
|
|
3321
|
+
}
|
|
1882
3322
|
static isEmpty(expression) {
|
|
1883
3323
|
return expression.type === "empty" || expression instanceof EmptyExpression;
|
|
1884
3324
|
}
|
|
@@ -1895,7 +3335,7 @@ const valueFromJson = (value)=>{
|
|
|
1895
3335
|
*
|
|
1896
3336
|
* ## Why it is this small
|
|
1897
3337
|
*
|
|
1898
|
-
* Of the
|
|
3338
|
+
* Of the seven node types a bound tree can contain, exactly one holds anything JSON cannot carry:
|
|
1899
3339
|
* `PropertyExpression`, whose live `PropertyInfo` has functions, a parent chain and caches. It
|
|
1900
3340
|
* reduces to a property PATH — `PropertyInfo.id` IS the dotted path, and `getProperty` is keyed by
|
|
1901
3341
|
* exactly that — so rebinding is one lookup.
|
|
@@ -1910,7 +3350,7 @@ const valueFromJson = (value)=>{
|
|
|
1910
3350
|
if (expression.type === "operator") {
|
|
1911
3351
|
const operator = expression;
|
|
1912
3352
|
return {
|
|
1913
|
-
|
|
3353
|
+
type: "operator",
|
|
1914
3354
|
operator: operator.operator,
|
|
1915
3355
|
...operator.left != null && {
|
|
1916
3356
|
left: Expression.toJson(operator.left)
|
|
@@ -1923,7 +3363,7 @@ const valueFromJson = (value)=>{
|
|
|
1923
3363
|
if (expression.type === "comparator") {
|
|
1924
3364
|
const comparator = expression;
|
|
1925
3365
|
return {
|
|
1926
|
-
|
|
3366
|
+
type: "comparator",
|
|
1927
3367
|
comparator: comparator.comparator,
|
|
1928
3368
|
negated: comparator.negated,
|
|
1929
3369
|
strict: comparator.strict,
|
|
@@ -1935,29 +3375,41 @@ const valueFromJson = (value)=>{
|
|
|
1935
3375
|
}
|
|
1936
3376
|
};
|
|
1937
3377
|
}
|
|
3378
|
+
if (expression.type === "call") {
|
|
3379
|
+
const call = expression;
|
|
3380
|
+
return {
|
|
3381
|
+
type: "call",
|
|
3382
|
+
call: call.call,
|
|
3383
|
+
expression: Expression.toJson(call.expression),
|
|
3384
|
+
arguments: call.arguments.map(Expression.toJson)
|
|
3385
|
+
};
|
|
3386
|
+
}
|
|
1938
3387
|
if (expression.type === "property") {
|
|
1939
3388
|
const property = expression;
|
|
1940
3389
|
return {
|
|
1941
|
-
|
|
3390
|
+
type: "property",
|
|
1942
3391
|
// The dotted path, which is exactly the key `getProperty` is looking up
|
|
1943
|
-
path: property.property.id
|
|
1944
|
-
transformer: property.transformer,
|
|
1945
|
-
locale: property.locale
|
|
3392
|
+
path: property.property.id
|
|
1946
3393
|
};
|
|
1947
3394
|
}
|
|
1948
3395
|
if (expression.type === "value") {
|
|
1949
3396
|
const value = expression;
|
|
1950
3397
|
return {
|
|
1951
|
-
|
|
1952
|
-
value: valueToJson(value.value)
|
|
1953
|
-
|
|
1954
|
-
|
|
3398
|
+
type: "value",
|
|
3399
|
+
value: valueToJson(value.value)
|
|
3400
|
+
};
|
|
3401
|
+
}
|
|
3402
|
+
if (expression.type === "empty") {
|
|
3403
|
+
return {
|
|
3404
|
+
type: "empty"
|
|
1955
3405
|
};
|
|
1956
3406
|
}
|
|
1957
|
-
|
|
1958
|
-
|
|
3407
|
+
const reason = expression.reason;
|
|
3408
|
+
return reason == null ? {
|
|
3409
|
+
type: "not-parsable"
|
|
1959
3410
|
} : {
|
|
1960
|
-
|
|
3411
|
+
type: "not-parsable",
|
|
3412
|
+
reason
|
|
1961
3413
|
};
|
|
1962
3414
|
}
|
|
1963
3415
|
/**
|
|
@@ -1973,14 +3425,14 @@ const valueFromJson = (value)=>{
|
|
|
1973
3425
|
* failure here worse than an error.
|
|
1974
3426
|
*/ static fromJson(json, schema) {
|
|
1975
3427
|
const child = (node)=>node == null ? undefined : Expression.fromJson(node, schema);
|
|
1976
|
-
if (json.
|
|
3428
|
+
if (json.type === "operator") {
|
|
1977
3429
|
return new OperatorExpression({
|
|
1978
3430
|
operator: json.operator,
|
|
1979
3431
|
left: child(json.left),
|
|
1980
3432
|
right: child(json.right)
|
|
1981
3433
|
});
|
|
1982
3434
|
}
|
|
1983
|
-
if (json.
|
|
3435
|
+
if (json.type === "comparator") {
|
|
1984
3436
|
return new ComparatorExpression({
|
|
1985
3437
|
comparator: json.comparator,
|
|
1986
3438
|
negated: json.negated,
|
|
@@ -1989,27 +3441,34 @@ const valueFromJson = (value)=>{
|
|
|
1989
3441
|
right: child(json.right)
|
|
1990
3442
|
});
|
|
1991
3443
|
}
|
|
1992
|
-
if (json.
|
|
3444
|
+
if (json.type === "call") {
|
|
3445
|
+
if (json.expression == null) {
|
|
3446
|
+
throw new Error(`Cannot deserialize a filter: a '${json.call}' call carries no operand. ` + `Collection: ${schema.collectionName}.`);
|
|
3447
|
+
}
|
|
3448
|
+
return new CallExpression({
|
|
3449
|
+
call: json.call,
|
|
3450
|
+
expression: Expression.fromJson(json.expression, schema),
|
|
3451
|
+
arguments: (json.arguments ?? []).map((argument)=>Expression.fromJson(argument, schema))
|
|
3452
|
+
});
|
|
3453
|
+
}
|
|
3454
|
+
if (json.type === "property") {
|
|
1993
3455
|
const property = schema.getProperty(json.path);
|
|
1994
3456
|
if (property == null) {
|
|
1995
3457
|
throw new Error(`Cannot deserialize a filter: this schema does not declare the property it names. ` + `Property: ${json.path}, Collection: ${schema.collectionName}. ` + `The two sides disagree about the shape of the data, so the filter cannot be applied.`);
|
|
1996
3458
|
}
|
|
1997
|
-
|
|
3459
|
+
return new PropertyExpression({
|
|
1998
3460
|
property
|
|
1999
3461
|
});
|
|
2000
|
-
rebuilt.transformer = json.transformer;
|
|
2001
|
-
rebuilt.locale = json.locale;
|
|
2002
|
-
return rebuilt;
|
|
2003
3462
|
}
|
|
2004
|
-
if (json.
|
|
2005
|
-
|
|
3463
|
+
if (json.type === "value") {
|
|
3464
|
+
return new ValueExpression({
|
|
2006
3465
|
value: valueFromJson(json.value)
|
|
2007
3466
|
});
|
|
2008
|
-
rebuilt.transformer = json.transformer;
|
|
2009
|
-
rebuilt.locale = json.locale;
|
|
2010
|
-
return rebuilt;
|
|
2011
3467
|
}
|
|
2012
|
-
|
|
3468
|
+
if (json.type === "empty") {
|
|
3469
|
+
return Expression.EMPTY;
|
|
3470
|
+
}
|
|
3471
|
+
return json.reason == null ? Expression.NOT_PARSABLE : Expression.notParsable(json.reason);
|
|
2013
3472
|
}
|
|
2014
3473
|
}
|
|
2015
3474
|
class EmptyExpression extends Expression {
|
|
@@ -2017,6 +3476,11 @@ class EmptyExpression extends Expression {
|
|
|
2017
3476
|
}
|
|
2018
3477
|
class NotParsableExpression extends Expression {
|
|
2019
3478
|
type = "not-parsable";
|
|
3479
|
+
/** What the parser refused, when it knows. `.explain()` prints it beside the source. */ reason;
|
|
3480
|
+
constructor(reason){
|
|
3481
|
+
super();
|
|
3482
|
+
this.reason = reason;
|
|
3483
|
+
}
|
|
2020
3484
|
}
|
|
2021
3485
|
/**
|
|
2022
3486
|
* A class representing a comparison operation (e.g., equals, greater-than).
|
|
@@ -2047,20 +3511,28 @@ class NotParsableExpression extends Expression {
|
|
|
2047
3511
|
*/ class PropertyExpression extends Expression {
|
|
2048
3512
|
/** The type of the expression (always 'property'). */ type = "property";
|
|
2049
3513
|
/** The property info for the path. */ property;
|
|
2050
|
-
transformer = null;
|
|
2051
|
-
locale = null;
|
|
2052
3514
|
constructor(options){
|
|
2053
3515
|
super();
|
|
2054
3516
|
this.property = options.property;
|
|
2055
3517
|
}
|
|
2056
3518
|
}
|
|
3519
|
+
class CallExpression extends Expression {
|
|
3520
|
+
type = "call";
|
|
3521
|
+
call;
|
|
3522
|
+
expression;
|
|
3523
|
+
/** Empty for a unary call. */ arguments;
|
|
3524
|
+
constructor(options){
|
|
3525
|
+
super();
|
|
3526
|
+
this.call = options.call;
|
|
3527
|
+
this.expression = options.expression;
|
|
3528
|
+
this.arguments = options.arguments ?? [];
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
2057
3531
|
/**
|
|
2058
3532
|
* A class representing a literal value.
|
|
2059
3533
|
*/ class ValueExpression extends Expression {
|
|
2060
3534
|
/** The type of the expression (always 'value'). */ type = "value";
|
|
2061
3535
|
/** The literal value. */ value;
|
|
2062
|
-
transformer = null;
|
|
2063
|
-
locale = null;
|
|
2064
3536
|
constructor(options){
|
|
2065
3537
|
super();
|
|
2066
3538
|
this.value = options.value;
|
|
@@ -2071,8 +3543,43 @@ class NotParsableExpression extends Expression {
|
|
|
2071
3543
|
},
|
|
2072
3544
|
63(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
2073
3545
|
__webpack_require__.d(__webpack_exports__, {
|
|
2074
|
-
|
|
3546
|
+
LU: () => (childrenOf),
|
|
3547
|
+
jJ: () => (forEach)
|
|
2075
3548
|
});
|
|
3549
|
+
/**
|
|
3550
|
+
* Separates an operand from the calls applied to it.
|
|
3551
|
+
*
|
|
3552
|
+
* `null` when there is no operand beneath the calls. Every consumer needs this to decide whether a
|
|
3553
|
+
* comparator side is a property or a value, so it lives here rather than in each translator.
|
|
3554
|
+
*/ function peelCalls(expression) {
|
|
3555
|
+
const calls = [];
|
|
3556
|
+
let current = expression;
|
|
3557
|
+
while(current != null && current.type === "call"){
|
|
3558
|
+
calls.unshift(current);
|
|
3559
|
+
current = current.expression;
|
|
3560
|
+
}
|
|
3561
|
+
return current == null ? null : {
|
|
3562
|
+
operand: current,
|
|
3563
|
+
calls
|
|
3564
|
+
};
|
|
3565
|
+
}
|
|
3566
|
+
function childrenOf(expression) {
|
|
3567
|
+
if (expression.type === "call") {
|
|
3568
|
+
const call = expression;
|
|
3569
|
+
return [
|
|
3570
|
+
call.expression,
|
|
3571
|
+
...call.arguments ?? []
|
|
3572
|
+
].filter((child)=>child != null);
|
|
3573
|
+
}
|
|
3574
|
+
const children = [];
|
|
3575
|
+
if (expression.left != null) {
|
|
3576
|
+
children.push(expression.left);
|
|
3577
|
+
}
|
|
3578
|
+
if (expression.right != null) {
|
|
3579
|
+
children.push(expression.right);
|
|
3580
|
+
}
|
|
3581
|
+
return children;
|
|
3582
|
+
}
|
|
2076
3583
|
/**
|
|
2077
3584
|
* Extracts all properties referenced in an expression
|
|
2078
3585
|
* @param expression The expression to analyze
|
|
@@ -2084,12 +3591,8 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
2084
3591
|
if (expr.type === "property") {
|
|
2085
3592
|
properties.push(expr.property);
|
|
2086
3593
|
}
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
traverse(expr.left);
|
|
2090
|
-
}
|
|
2091
|
-
if (expr.right) {
|
|
2092
|
-
traverse(expr.right);
|
|
3594
|
+
for (const child of childrenOf(expr)){
|
|
3595
|
+
traverse(child);
|
|
2093
3596
|
}
|
|
2094
3597
|
}
|
|
2095
3598
|
traverse(expression);
|
|
@@ -2102,14 +3605,8 @@ function forEach(expression, callback) {
|
|
|
2102
3605
|
if (!callback(expr)) {
|
|
2103
3606
|
return false;
|
|
2104
3607
|
}
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
if (!traverse(expr.left)) {
|
|
2108
|
-
return false;
|
|
2109
|
-
}
|
|
2110
|
-
}
|
|
2111
|
-
if (expr.right) {
|
|
2112
|
-
if (!traverse(expr.right)) {
|
|
3608
|
+
for (const child of childrenOf(expr)){
|
|
3609
|
+
if (!traverse(child)) {
|
|
2113
3610
|
return false;
|
|
2114
3611
|
}
|
|
2115
3612
|
}
|
|
@@ -2390,32 +3887,62 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
2390
3887
|
H: () => (QueryOptionsCollection)
|
|
2391
3888
|
});
|
|
2392
3889
|
/* import */ var _assertions__rspack_import_1 = __webpack_require__(126);
|
|
2393
|
-
/* import */ var
|
|
3890
|
+
/* import */ var _expressions_utils__rspack_import_2 = __webpack_require__(63);
|
|
3891
|
+
/* import */ var _schema_types__rspack_import_0 = __webpack_require__(537);
|
|
3892
|
+
/* import */ var _utilities__rspack_import_3 = __webpack_require__(581);
|
|
3893
|
+
|
|
2394
3894
|
|
|
2395
3895
|
|
|
3896
|
+
|
|
3897
|
+
/** What a schema type is called in JavaScript, where one exists. A value of any other type cannot equal it. */ const JAVASCRIPT_TYPE_OF = {
|
|
3898
|
+
[_schema_types__rspack_import_0/* .SchemaTypes.Number */.L.Number]: "number",
|
|
3899
|
+
[_schema_types__rspack_import_0/* .SchemaTypes.String */.L.String]: "string",
|
|
3900
|
+
[_schema_types__rspack_import_0/* .SchemaTypes.Boolean */.L.Boolean]: "boolean",
|
|
3901
|
+
[_schema_types__rspack_import_0/* .SchemaTypes.Date */.L.Date]: "object"
|
|
3902
|
+
};
|
|
3903
|
+
const mismatchedSide = (property, value)=>{
|
|
3904
|
+
if (property == null || value == null || !(0,_assertions__rspack_import_1/* .isPropertyExpression */.e3)(property) || !(0,_assertions__rspack_import_1/* .isValueExpression */.S6)(value)) {
|
|
3905
|
+
return null;
|
|
3906
|
+
}
|
|
3907
|
+
const expected = JAVASCRIPT_TYPE_OF[property.property.type];
|
|
3908
|
+
if (expected == null || value.value == null || typeof value.value === expected) {
|
|
3909
|
+
return null;
|
|
3910
|
+
}
|
|
3911
|
+
return {
|
|
3912
|
+
property,
|
|
3913
|
+
value,
|
|
3914
|
+
expected
|
|
3915
|
+
};
|
|
3916
|
+
};
|
|
3917
|
+
/** A strict comparison whose answer is the same for every row, because the types cannot be equal. */ const comparesTypesThatCannotMatch = (expression)=>{
|
|
3918
|
+
if (!(0,_assertions__rspack_import_1/* .isComparatorExpression */.xH)(expression) || expression.strict !== true) {
|
|
3919
|
+
return false;
|
|
3920
|
+
}
|
|
3921
|
+
if (expression.comparator !== "equals") {
|
|
3922
|
+
return false;
|
|
3923
|
+
}
|
|
3924
|
+
return mismatchedSide(expression.left, expression.right) != null || mismatchedSide(expression.right, expression.left) != null;
|
|
3925
|
+
};
|
|
3926
|
+
/** `JSON.stringify` throws on a BigInt, and this runs inside the guard that exists to catch one. */ const describeLiteral = (value)=>typeof value === "string" ? `"${value}"` : String(value);
|
|
3927
|
+
const mismatchWarning = (expression)=>{
|
|
3928
|
+
const side = mismatchedSide(expression.left, expression.right) ?? mismatchedSide(expression.right, expression.left);
|
|
3929
|
+
const outcome = expression.negated ? "every row matches" : "no row matches";
|
|
3930
|
+
return `Routier: '${side.property.property.getAssignmentPath()}' is a ${side.expected}, and this filter ` + `compares it against ${describeLiteral(side.value.value)}, which is a ${typeof side.value.value}. ` + `A strict comparison between them is the same answer for every row, so ${outcome} and the filter ` + `runs in memory. https://routier.dev/guides/strict-comparison-types`;
|
|
3931
|
+
};
|
|
2396
3932
|
class QueryOptionsCollection {
|
|
2397
3933
|
options = new Map();
|
|
2398
3934
|
nextExecutionTarget = "database";
|
|
2399
3935
|
nextExecutionReason = null;
|
|
2400
3936
|
nextIndex = 0;
|
|
2401
3937
|
enumeratedItems = [];
|
|
3938
|
+
dirty = true;
|
|
3939
|
+
/** The collection a `splitAt`/`split` half came from. A capability report belongs to it. */ origin = null;
|
|
2402
3940
|
/** Cuts over to memory execution, keeping the first cause. See `MemoryExecutionReason`. */ cutOverToMemory(reason) {
|
|
2403
3941
|
this.nextExecutionTarget = "memory";
|
|
2404
3942
|
if (this.nextExecutionReason == null) {
|
|
2405
3943
|
this.nextExecutionReason = reason;
|
|
2406
3944
|
}
|
|
2407
3945
|
}
|
|
2408
|
-
/**
|
|
2409
|
-
* True when `split()` or `splitAt()` produced this collection.
|
|
2410
|
-
*
|
|
2411
|
-
* Those rebuild each half by re-adding its options, which re-derives execution targets
|
|
2412
|
-
* without the options that caused them — a post-join filter alone in the memory half
|
|
2413
|
-
* derives back to `"database"`. Anything reading `target` as a report of where work runs
|
|
2414
|
-
* has to reject a derived collection; see `explainQuery`.
|
|
2415
|
-
*/ derived = false;
|
|
2416
|
-
get isDerived() {
|
|
2417
|
-
return this.derived;
|
|
2418
|
-
}
|
|
2419
3946
|
get items() {
|
|
2420
3947
|
return this.options;
|
|
2421
3948
|
}
|
|
@@ -2450,7 +3977,7 @@ class QueryOptionsCollection {
|
|
|
2450
3977
|
if (filterValue.expression.type === "not-parsable") {
|
|
2451
3978
|
this.cutOverToMemory("not-parsable");
|
|
2452
3979
|
} else {
|
|
2453
|
-
(0,
|
|
3980
|
+
(0,_expressions_utils__rspack_import_2/* .forEach */.jJ)(filterValue.expression, (expression)=>{
|
|
2454
3981
|
if ((0,_assertions__rspack_import_1/* .isPropertyExpression */.e3)(expression) && expression.property.isUnmapped) {
|
|
2455
3982
|
// Cut over to memory execution, unmapped properties are not in the database and
|
|
2456
3983
|
// cannot be queried
|
|
@@ -2465,6 +3992,11 @@ class QueryOptionsCollection {
|
|
|
2465
3992
|
this.cutOverToMemory("renamed-property");
|
|
2466
3993
|
return false;
|
|
2467
3994
|
}
|
|
3995
|
+
if (comparesTypesThatCannotMatch(expression)) {
|
|
3996
|
+
_utilities__rspack_import_3/* .logger.warn */.vF.warn(mismatchWarning(expression));
|
|
3997
|
+
this.cutOverToMemory("predicate-error");
|
|
3998
|
+
return false;
|
|
3999
|
+
}
|
|
2468
4000
|
return true;
|
|
2469
4001
|
});
|
|
2470
4002
|
}
|
|
@@ -2493,6 +4025,11 @@ class QueryOptionsCollection {
|
|
|
2493
4025
|
this.cutOverToMemory("renamed-property");
|
|
2494
4026
|
}
|
|
2495
4027
|
}
|
|
4028
|
+
if ((name === "filter" || name === "sort") && (this.options.has("skip") || this.options.has("take"))) {
|
|
4029
|
+
// SQL emits WHERE before LIMIT and Mongo's find() filters before skipping, so an option
|
|
4030
|
+
// written after a window can only see the windowed rows if it runs after it.
|
|
4031
|
+
this.cutOverToMemory("after-window");
|
|
4032
|
+
}
|
|
2496
4033
|
if (name === "join") {
|
|
2497
4034
|
const joinValue = value;
|
|
2498
4035
|
// A join whose two sides live on different plugins cannot be sent to EITHER of
|
|
@@ -2505,18 +4042,24 @@ class QueryOptionsCollection {
|
|
|
2505
4042
|
this.cutOverToMemory("cross-plugin-join");
|
|
2506
4043
|
}
|
|
2507
4044
|
}
|
|
4045
|
+
// `executed` is the plan, not a record: nothing has run when an option is added. Every
|
|
4046
|
+
// consumer reads it after the plugin returned, so the optimistic window is never observed.
|
|
2508
4047
|
const item = {
|
|
2509
4048
|
index: this.nextIndex,
|
|
2510
|
-
option: {
|
|
4049
|
+
option: this.nextExecutionTarget === "database" ? {
|
|
2511
4050
|
name,
|
|
2512
|
-
target: this.nextExecutionTarget,
|
|
2513
4051
|
value,
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
4052
|
+
target: "database",
|
|
4053
|
+
reason: "executed"
|
|
4054
|
+
} : {
|
|
4055
|
+
name,
|
|
4056
|
+
value,
|
|
4057
|
+
target: "memory",
|
|
4058
|
+
reason: this.nextExecutionReason ?? "not-parsable"
|
|
2517
4059
|
}
|
|
2518
4060
|
};
|
|
2519
4061
|
this.nextIndex++;
|
|
4062
|
+
this.dirty = true;
|
|
2520
4063
|
const found = this.options.get(name);
|
|
2521
4064
|
this.options.set(name, [
|
|
2522
4065
|
...found ?? [],
|
|
@@ -2561,8 +4104,6 @@ class QueryOptionsCollection {
|
|
|
2561
4104
|
const sortedItems = this.enumeratedItems.toSorted((a, b)=>a.index - b.index);
|
|
2562
4105
|
const before = new QueryOptionsCollection();
|
|
2563
4106
|
const after = new QueryOptionsCollection();
|
|
2564
|
-
before.derived = true;
|
|
2565
|
-
after.derived = true;
|
|
2566
4107
|
let at = null;
|
|
2567
4108
|
for(let i = 0, length = sortedItems.length; i < length; i++){
|
|
2568
4109
|
const { option } = sortedItems[i];
|
|
@@ -2571,8 +4112,10 @@ class QueryOptionsCollection {
|
|
|
2571
4112
|
continue;
|
|
2572
4113
|
}
|
|
2573
4114
|
const destination = at == null ? before : after;
|
|
2574
|
-
destination.
|
|
4115
|
+
destination.adopt(sortedItems[i]);
|
|
2575
4116
|
}
|
|
4117
|
+
before.origin = this.origin ?? this;
|
|
4118
|
+
after.origin = this.origin ?? this;
|
|
2576
4119
|
return {
|
|
2577
4120
|
before,
|
|
2578
4121
|
at,
|
|
@@ -2604,23 +4147,99 @@ class QueryOptionsCollection {
|
|
|
2604
4147
|
this.nextExecutionReason = nextExecutionReason;
|
|
2605
4148
|
this.nextIndex = nextIndex;
|
|
2606
4149
|
this.enumeratedItems = [];
|
|
4150
|
+
// Clearing the list is not enough now that staleness is a flag rather than a count:
|
|
4151
|
+
// without this, `resolveEnumeration` believes the empty list is current and every read
|
|
4152
|
+
// of the collection sees no options at all.
|
|
4153
|
+
this.dirty = true;
|
|
2607
4154
|
};
|
|
2608
4155
|
}
|
|
4156
|
+
/** Takes an item as it stands — same object, same index, same target and reason. */ adopt(item) {
|
|
4157
|
+
const found = this.options.get(item.option.name);
|
|
4158
|
+
this.options.set(item.option.name, [
|
|
4159
|
+
...found ?? [],
|
|
4160
|
+
item
|
|
4161
|
+
]);
|
|
4162
|
+
this.nextIndex = Math.max(this.nextIndex, item.index + 1);
|
|
4163
|
+
this.dirty = true;
|
|
4164
|
+
}
|
|
4165
|
+
/**
|
|
4166
|
+
* A plugin reporting that its engine cannot express one option.
|
|
4167
|
+
*
|
|
4168
|
+
* Core marks the rest of the database phase `not-reached`, because the database has to stop
|
|
4169
|
+
* there — a window applied in front of a filter that was not applied returns the wrong rows.
|
|
4170
|
+
* Passing the cascade through core is what makes it impossible for a plugin to mark a
|
|
4171
|
+
* non-contiguous cut.
|
|
4172
|
+
*
|
|
4173
|
+
* A report names a culprit and never un-names one, so reports commute.
|
|
4174
|
+
*
|
|
4175
|
+
* The option is not moved to the memory arm. It stays where it was planned, which is what keeps
|
|
4176
|
+
* a redirect distinguishable from something core sent to memory in the first place.
|
|
4177
|
+
*/ reportMissingCapability(item) {
|
|
4178
|
+
this.report(item, "missing-capability");
|
|
4179
|
+
}
|
|
4180
|
+
/**
|
|
4181
|
+
* A plugin reporting that its engine would answer one option differently from JavaScript.
|
|
4182
|
+
*
|
|
4183
|
+
* Same cascade as `reportMissingCapability`, and a separate reason because the caller can act on
|
|
4184
|
+
* one and not the other. See `DatabaseExecutionReason`.
|
|
4185
|
+
*/ reportEngineDivergence(item) {
|
|
4186
|
+
this.report(item, "engine-divergence");
|
|
4187
|
+
}
|
|
4188
|
+
report(item, reason) {
|
|
4189
|
+
// A half can only see its own slice, and the database has to stop for the whole dispatch.
|
|
4190
|
+
if (this.origin != null) {
|
|
4191
|
+
this.origin.report(item, reason);
|
|
4192
|
+
return;
|
|
4193
|
+
}
|
|
4194
|
+
this.resolveEnumeration();
|
|
4195
|
+
for (const candidate of this.enumeratedItems){
|
|
4196
|
+
if (candidate.option.target !== "database" || candidate.index < item.index) {
|
|
4197
|
+
continue;
|
|
4198
|
+
}
|
|
4199
|
+
if (candidate.index === item.index) {
|
|
4200
|
+
candidate.option.reason = reason;
|
|
4201
|
+
continue;
|
|
4202
|
+
}
|
|
4203
|
+
if (candidate.option.reason === "executed") {
|
|
4204
|
+
candidate.option.reason = "not-reached";
|
|
4205
|
+
}
|
|
4206
|
+
}
|
|
4207
|
+
}
|
|
4208
|
+
/**
|
|
4209
|
+
* Forgets what any previous dispatch reported.
|
|
4210
|
+
*
|
|
4211
|
+
* Capability is answered per dispatch, so a report is only an answer for the execution that
|
|
4212
|
+
* produced it. The items are shared with any snapshot, so a report mutated in place otherwise
|
|
4213
|
+
* survives a restore and a second terminal on the same queryable replays options the plugin
|
|
4214
|
+
* did run — a `skip` applied twice, over rows already windowed.
|
|
4215
|
+
*/ forgetReports() {
|
|
4216
|
+
this.resolveEnumeration();
|
|
4217
|
+
for (const item of this.enumeratedItems){
|
|
4218
|
+
if (item.option.target === "database") {
|
|
4219
|
+
item.option.reason = "executed";
|
|
4220
|
+
}
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4223
|
+
/** The options the database did not run, in the order they were written. */ notExecuted() {
|
|
4224
|
+
this.resolveEnumeration();
|
|
4225
|
+
return this.enumeratedItems.filter((item)=>item.option.target === "database" && item.option.reason !== "executed").toSorted((a, b)=>a.index - b.index);
|
|
4226
|
+
}
|
|
2609
4227
|
split() {
|
|
2610
4228
|
this.resolveEnumeration();
|
|
2611
4229
|
const sortedItems = this.enumeratedItems.toSorted((a, b)=>a.index - b.index);
|
|
2612
4230
|
const memoryQueryOptionsCollection = new QueryOptionsCollection();
|
|
2613
4231
|
const databaseQueryOptionsCollection = new QueryOptionsCollection();
|
|
2614
|
-
memoryQueryOptionsCollection.derived = true;
|
|
2615
|
-
databaseQueryOptionsCollection.derived = true;
|
|
2616
4232
|
for(let i = 0, length = sortedItems.length; i < length; i++){
|
|
2617
4233
|
const sortedItem = sortedItems[i];
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
4234
|
+
const half = sortedItem.option.target === "database" ? databaseQueryOptionsCollection : memoryQueryOptionsCollection;
|
|
4235
|
+
// The ITEM, not its name and value. Re-adding would re-derive target and reason from a
|
|
4236
|
+
// fresh cascade, and a memory option re-added alone comes back out as `database` with no
|
|
4237
|
+
// reason at all. Sharing it also means a plugin's report on the database half is the
|
|
4238
|
+
// same object the explanation reads.
|
|
4239
|
+
half.adopt(sortedItem);
|
|
4240
|
+
}
|
|
4241
|
+
memoryQueryOptionsCollection.origin = this.origin ?? this;
|
|
4242
|
+
databaseQueryOptionsCollection.origin = this.origin ?? this;
|
|
2624
4243
|
return {
|
|
2625
4244
|
memory: memoryQueryOptionsCollection,
|
|
2626
4245
|
database: databaseQueryOptionsCollection
|
|
@@ -2666,8 +4285,11 @@ class QueryOptionsCollection {
|
|
|
2666
4285
|
].flat().toSorted((a, b)=>a.index - b.index);
|
|
2667
4286
|
}
|
|
2668
4287
|
resolveEnumeration() {
|
|
2669
|
-
|
|
4288
|
+
// A flag, not a count: adopting leaves gaps in the indexes, so `length !== nextIndex` is
|
|
4289
|
+
// true forever on a half and the enumeration rebuilds on every read.
|
|
4290
|
+
if (this.dirty === true) {
|
|
2670
4291
|
this.enumeratedItems = this.getEnumeration();
|
|
4292
|
+
this.dirty = false;
|
|
2671
4293
|
}
|
|
2672
4294
|
}
|
|
2673
4295
|
forEach(iterator) {
|
|
@@ -2887,12 +4509,14 @@ const isLogLevel = (value)=>typeof value === 'string' && LOG_LEVELS.includes(val
|
|
|
2887
4509
|
const debug = process.env.DEBUG;
|
|
2888
4510
|
if (debug === 'routier' || debug === '*') return 'debug';
|
|
2889
4511
|
const env = "production"?.toLowerCase();
|
|
2890
|
-
// `test` is deliberately absent. It used to be here, which meant no test suite anywhere
|
|
2891
|
-
// could run Routier quietly. Opt in with DEBUG=routier or ROUTIER_LOG_LEVEL when a test
|
|
2892
|
-
// needs the output.
|
|
2893
4512
|
if (env === 'dev' || env === 'development') return 'debug';
|
|
2894
4513
|
}
|
|
2895
|
-
|
|
4514
|
+
// Warnings are on unless something turns them off.
|
|
4515
|
+
//
|
|
4516
|
+
// Routier warns when a query returns correct rows a slower way than it could, or when a filter
|
|
4517
|
+
// compares types that can never match. Both are the caller's to act on, and a default of
|
|
4518
|
+
// `silent` meant the only people who ever saw them were the ones who already knew to look.
|
|
4519
|
+
return 'warn';
|
|
2896
4520
|
};
|
|
2897
4521
|
let level = resolveLevel();
|
|
2898
4522
|
let rank = RANK[level];
|
|
@@ -3050,34 +4674,44 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
3050
4674
|
_b: () => (/* reexport */ DEFAULT_SEMI_JOIN_KEY_THRESHOLD),
|
|
3051
4675
|
pt: () => (/* reexport */ types_QueryOrdering),
|
|
3052
4676
|
Ib: () => (/* reexport */ TranslatedSingleValue),
|
|
4677
|
+
fp: () => (/* reexport */ describeFilterAsJs),
|
|
3053
4678
|
RK: () => (/* reexport */ distinctJoinKeys),
|
|
3054
4679
|
Bg: () => (/* reexport */ hashJoin),
|
|
4680
|
+
_1: () => (/* reexport */ mappedResultColumns),
|
|
3055
4681
|
iG: () => (/* reexport */ nearestBy),
|
|
3056
4682
|
qy: () => (/* reexport */ readJoinKey),
|
|
3057
4683
|
yR: () => (/* reexport */ serializePersistResult),
|
|
3058
4684
|
y4: () => (/* reexport */ CacheDbPlugin),
|
|
3059
4685
|
lO: () => (/* reexport */ cosineDistance),
|
|
4686
|
+
QC: () => (/* reexport */ DATABASE_EXECUTION_EXPLANATIONS),
|
|
3060
4687
|
d0: () => (/* reexport */ JsonTranslator),
|
|
4688
|
+
Wi: () => (/* reexport */ parameter),
|
|
3061
4689
|
PP: () => (/* reexport */ splitSendableOptions),
|
|
3062
4690
|
f2: () => (/* reexport */ TranslatedGroupValue),
|
|
3063
4691
|
wN: () => (/* reexport */ collectingSink),
|
|
3064
4692
|
QB: () => (/* reexport */ RetryDbPlugin),
|
|
3065
4693
|
m6: () => (/* reexport */ executeJoin),
|
|
4694
|
+
i1: () => (/* reexport */ parameteriseDocument),
|
|
3066
4695
|
__: () => (/* reexport */ toEntityShape),
|
|
3067
4696
|
VW: () => (/* reexport */ applyInnerOptions),
|
|
4697
|
+
B2: () => (/* reexport */ describeUnparsableFilter),
|
|
3068
4698
|
Jd: () => (/* reexport */ EphemeralDataPlugin),
|
|
3069
4699
|
Pl: () => (/* reexport */ deserializePersistResult),
|
|
3070
4700
|
JF: () => (/* reexport */ DataTranslator),
|
|
3071
4701
|
HM: () => (/* reexport */ QueryOptionsCollection/* .QueryOptionsCollection */.H),
|
|
3072
4702
|
KB: () => (/* reexport */ createRequestHandler),
|
|
3073
|
-
|
|
4703
|
+
fN: () => (/* reexport */ executedQueriesOf),
|
|
3074
4704
|
II: () => (/* reexport */ deserializeQueryOptions),
|
|
3075
|
-
|
|
4705
|
+
vZ: () => (/* reexport */ formatExplanation),
|
|
4706
|
+
yX: () => (/* reexport */ isDatabaseStep),
|
|
3076
4707
|
as: () => (/* reexport */ loadJoinInnerSide),
|
|
4708
|
+
n: () => (/* reexport */ serializeBulkPersist),
|
|
3077
4709
|
lA: () => (/* reexport */ semiJoinFilter),
|
|
4710
|
+
oJ: () => (/* reexport */ withInnerSide),
|
|
3078
4711
|
kX: () => (/* reexport */ BatchingDbPlugin),
|
|
3079
4712
|
DF: () => (/* reexport */ SqlTranslator),
|
|
3080
4713
|
qj: () => (/* reexport */ loggerSink),
|
|
4714
|
+
To: () => (/* reexport */ describeFilters),
|
|
3081
4715
|
gH: () => (/* reexport */ MEMORY_EXECUTION_EXPLANATIONS),
|
|
3082
4716
|
BL: () => (/* reexport */ serializeQueryOptions),
|
|
3083
4717
|
Kg: () => (/* reexport */ withExecutedQueries),
|
|
@@ -3088,6 +4722,18 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
3088
4722
|
jE: () => (/* reexport */ EXECUTED_QUERIES_UNSUPPORTED)
|
|
3089
4723
|
});
|
|
3090
4724
|
|
|
4725
|
+
;// CONCATENATED MODULE: ./src/plugins/resultShape.ts
|
|
4726
|
+
/**
|
|
4727
|
+
* The columns a `map` projection selects.
|
|
4728
|
+
*
|
|
4729
|
+
* Named by `sourceName`, which is what the statement actually emits; the rename to
|
|
4730
|
+
* `destinationName` happens in the translator, after the rows come back. A field with no
|
|
4731
|
+
* `property` is an expression.
|
|
4732
|
+
*/ const mappedResultColumns = (fields)=>fields.map((field)=>({
|
|
4733
|
+
name: field.sourceName,
|
|
4734
|
+
property: field.property ?? null
|
|
4735
|
+
}));
|
|
4736
|
+
|
|
3091
4737
|
;// CONCATENATED MODULE: ./src/plugins/translators/TranslatedArrayValue.ts
|
|
3092
4738
|
class TranslatedArrayValue {
|
|
3093
4739
|
value;
|
|
@@ -3182,6 +4828,10 @@ class DataTranslator {
|
|
|
3182
4828
|
translate(data) {
|
|
3183
4829
|
const isTransformed = this.query.options.hasTransformations();
|
|
3184
4830
|
this.query.options.forEach((item)=>{
|
|
4831
|
+
// The plugin reported it could not run this one, so the memory pass owns it now.
|
|
4832
|
+
if (item.target === "database" && item.reason !== "executed") {
|
|
4833
|
+
return;
|
|
4834
|
+
}
|
|
3185
4835
|
data = this.functionMap[item.name](data, item);
|
|
3186
4836
|
});
|
|
3187
4837
|
if (Array.isArray(data)) {
|
|
@@ -3594,7 +5244,7 @@ class Query {
|
|
|
3594
5244
|
* Only a plugin that runs its outer query FIRST can supply these, and most run this loader
|
|
3595
5245
|
* before anything else — so it is optional, and its absence costs a wider inner read rather
|
|
3596
5246
|
* than a wrong one.
|
|
3597
|
-
*/ outerKeys)=>{
|
|
5247
|
+
*/ outerKeys, /** Where the inner read reports what it executed. Defaults to the outer read's own list. */ innerExecutedQueries)=>{
|
|
3598
5248
|
const joinOption = event.operation.options.getLast("join");
|
|
3599
5249
|
if (joinOption == null) {
|
|
3600
5250
|
done({
|
|
@@ -3622,9 +5272,10 @@ class Query {
|
|
|
3622
5272
|
action: "query",
|
|
3623
5273
|
reason: "join inner side",
|
|
3624
5274
|
explain: event.explain,
|
|
3625
|
-
// The
|
|
3626
|
-
//
|
|
3627
|
-
|
|
5275
|
+
// The caller decides where the inner read reports, because only it knows whether the inner
|
|
5276
|
+
// side is the SAME plugin — where both reads belong in one explanation — or a different one,
|
|
5277
|
+
// where a PouchDB scan filed under SqliteDbPlugin is a lie.
|
|
5278
|
+
executedQueries: innerExecutedQueries ?? event.executedQueries
|
|
3628
5279
|
};
|
|
3629
5280
|
query(innerEvent, (result)=>{
|
|
3630
5281
|
if (result.ok === Result/* .PluginEventResult.ERROR */.D.ERROR) {
|
|
@@ -3701,6 +5352,12 @@ class Query {
|
|
|
3701
5352
|
return;
|
|
3702
5353
|
}
|
|
3703
5354
|
const outerRows = outerResult.data.value ?? [];
|
|
5355
|
+
if (at.reason !== "executed") {
|
|
5356
|
+
// The outer read reported something, so the database phase stopped before the join.
|
|
5357
|
+
// The datastore's own join branch pairs these rows.
|
|
5358
|
+
done(Result/* .PluginEventResult.success */.D.success(event.id, new TranslatedArrayValue(outerRows, false)));
|
|
5359
|
+
return;
|
|
5360
|
+
}
|
|
3704
5361
|
// Storage shape: the plugin returns rows as it holds them, and deserialization is what
|
|
3705
5362
|
// `executeJoin` does per side below.
|
|
3706
5363
|
const outerKeys = distinctJoinKeys(outerRows, at.value.outerKey, at.value.semiJoinKeyThreshold, {
|
|
@@ -3816,9 +5473,7 @@ class JsonTranslator extends DataTranslator {
|
|
|
3816
5473
|
if (field.property != null) {
|
|
3817
5474
|
const value = field.property.getValue(data[i]);
|
|
3818
5475
|
if (value != null) {
|
|
3819
|
-
|
|
3820
|
-
const resolvedValue = field.property.supportsDeserialization ? field.property.deserialize(value) : value;
|
|
3821
|
-
field.property.setValue(data[i], resolvedValue);
|
|
5476
|
+
field.property.setValue(data[i], field.property.deserialize(value));
|
|
3822
5477
|
}
|
|
3823
5478
|
}
|
|
3824
5479
|
}
|
|
@@ -3842,9 +5497,7 @@ class JsonTranslator extends DataTranslator {
|
|
|
3842
5497
|
if (field.property != null) {
|
|
3843
5498
|
const value = field.property.getValue(data[i]);
|
|
3844
5499
|
if (value != null) {
|
|
3845
|
-
|
|
3846
|
-
const resolvedValue = field.property.supportsDeserialization ? field.property.deserialize(value) : value;
|
|
3847
|
-
field.property.setValue(item, resolvedValue);
|
|
5500
|
+
field.property.setValue(item, field.property.deserialize(value));
|
|
3848
5501
|
continue;
|
|
3849
5502
|
}
|
|
3850
5503
|
// The property exists, lets set it to the value (null/undefined)
|
|
@@ -4185,9 +5838,12 @@ class SqlTranslator extends DataTranslator {
|
|
|
4185
5838
|
for(let j = 0, l = option.value.fields.length; j < l; j++){
|
|
4186
5839
|
const field = option.value.fields[j];
|
|
4187
5840
|
if (field.property != null) {
|
|
4188
|
-
const
|
|
5841
|
+
const row = data[i];
|
|
5842
|
+
// A nested field arrives FLAT, under the alias the statement emitted, because
|
|
5843
|
+
// the value was read out of a JSON column. `setValue` puts it back on its path.
|
|
5844
|
+
const value = Object.prototype.hasOwnProperty.call(row, field.sourceName) ? row[field.sourceName] : field.property.getValue(row);
|
|
4189
5845
|
if (value != null) {
|
|
4190
|
-
field.property.setValue(
|
|
5846
|
+
field.property.setValue(row, field.property.deserialize(value));
|
|
4191
5847
|
}
|
|
4192
5848
|
}
|
|
4193
5849
|
}
|
|
@@ -4324,6 +5980,183 @@ class SqlTranslator extends DataTranslator {
|
|
|
4324
5980
|
|
|
4325
5981
|
|
|
4326
5982
|
|
|
5983
|
+
// EXTERNAL MODULE: ./src/expressions/callSource.ts
|
|
5984
|
+
var callSource = __webpack_require__(429);
|
|
5985
|
+
;// CONCATENATED MODULE: ./src/plugins/query/describeFilter.ts
|
|
5986
|
+
|
|
5987
|
+
|
|
5988
|
+
const COMPARATOR_OPERATORS = {
|
|
5989
|
+
"equals": "===",
|
|
5990
|
+
"greater-than": ">",
|
|
5991
|
+
"greater-than-equals": ">=",
|
|
5992
|
+
"less-than": "<",
|
|
5993
|
+
"less-than-equals": "<="
|
|
5994
|
+
};
|
|
5995
|
+
/** The three comparators that read as a method call rather than an operator. */ const COMPARATOR_METHODS = {
|
|
5996
|
+
"starts-with": "startsWith",
|
|
5997
|
+
"includes": "includes",
|
|
5998
|
+
"ends-with": "endsWith"
|
|
5999
|
+
};
|
|
6000
|
+
const renderProperty = (property)=>property.property.getPathArray().join(".");
|
|
6001
|
+
/**
|
|
6002
|
+
* The predicate as JavaScript, with every value replaced by `?`.
|
|
6003
|
+
*
|
|
6004
|
+
* Rendered from the parsed tree rather than from the function's source. The tree is what the
|
|
6005
|
+
* backend was actually given, so this cannot drift from what ran; and a value reaching the tree
|
|
6006
|
+
* as a literal is indistinguishable from one arriving through a params object, which is what
|
|
6007
|
+
* makes both come out as `?` the way SQL treats them.
|
|
6008
|
+
*/ const describeFilterAsJs = (expression)=>{
|
|
6009
|
+
const parameters = [];
|
|
6010
|
+
const hold = (value)=>{
|
|
6011
|
+
parameters.push(value);
|
|
6012
|
+
return "?";
|
|
6013
|
+
};
|
|
6014
|
+
const side = (part)=>{
|
|
6015
|
+
if (part == null) {
|
|
6016
|
+
return "?";
|
|
6017
|
+
}
|
|
6018
|
+
if ((0,assertions/* .isPropertyExpression */.e3)(part)) {
|
|
6019
|
+
return renderProperty(part);
|
|
6020
|
+
}
|
|
6021
|
+
if ((0,assertions/* .isValueExpression */.S6)(part)) {
|
|
6022
|
+
return hold(part.value);
|
|
6023
|
+
}
|
|
6024
|
+
if ((0,assertions/* .isCallExpression */.fm)(part)) {
|
|
6025
|
+
return (0,callSource/* .renderCallAsJs */.a)(part.call, ()=>side(part.expression), ()=>part.arguments.map(side));
|
|
6026
|
+
}
|
|
6027
|
+
return walk(part);
|
|
6028
|
+
};
|
|
6029
|
+
const walk = (current)=>{
|
|
6030
|
+
if ((0,assertions/* .isOperatorExpression */.vg)(current)) {
|
|
6031
|
+
const operator = current.operator === "&&" ? "&&" : "||";
|
|
6032
|
+
return `(${side(current.left)} ${operator} ${side(current.right)})`;
|
|
6033
|
+
}
|
|
6034
|
+
if ((0,assertions/* .isComparatorExpression */.xH)(current)) {
|
|
6035
|
+
const method = COMPARATOR_METHODS[current.comparator];
|
|
6036
|
+
// Evaluated LEFT then RIGHT, always: the parameter order has to match the reading
|
|
6037
|
+
// order of the text, or the values line up against the wrong placeholders.
|
|
6038
|
+
const left = side(current.left);
|
|
6039
|
+
const right = side(current.right);
|
|
6040
|
+
if (method != null) {
|
|
6041
|
+
const call = `${left}.${method}(${right})`;
|
|
6042
|
+
return current.negated ? `${call} === false` : call;
|
|
6043
|
+
}
|
|
6044
|
+
const symbol = COMPARATOR_OPERATORS[current.comparator];
|
|
6045
|
+
if (symbol == null) {
|
|
6046
|
+
return `${left} ${current.comparator} ${right}`;
|
|
6047
|
+
}
|
|
6048
|
+
return `${left} ${current.negated ? negate(symbol) : symbol} ${right}`;
|
|
6049
|
+
}
|
|
6050
|
+
if ((0,assertions/* .isCallExpression */.fm)(current)) {
|
|
6051
|
+
return (0,callSource/* .renderCallAsJs */.a)(current.call, ()=>side(current.expression), ()=>current.arguments.map(side));
|
|
6052
|
+
}
|
|
6053
|
+
if (current.type === "empty") {
|
|
6054
|
+
return "(no filter)";
|
|
6055
|
+
}
|
|
6056
|
+
return current.type === "not-parsable" ? "(not parsable)" : `(unsupported: ${current.type})`;
|
|
6057
|
+
};
|
|
6058
|
+
return {
|
|
6059
|
+
text: walk(expression),
|
|
6060
|
+
parameters
|
|
6061
|
+
};
|
|
6062
|
+
};
|
|
6063
|
+
const negate = (symbol)=>{
|
|
6064
|
+
switch(symbol){
|
|
6065
|
+
case "===":
|
|
6066
|
+
return "!==";
|
|
6067
|
+
case ">":
|
|
6068
|
+
return "<=";
|
|
6069
|
+
case ">=":
|
|
6070
|
+
return "<";
|
|
6071
|
+
case "<":
|
|
6072
|
+
return ">=";
|
|
6073
|
+
case "<=":
|
|
6074
|
+
return ">";
|
|
6075
|
+
default:
|
|
6076
|
+
return `!${symbol}`;
|
|
6077
|
+
}
|
|
6078
|
+
};
|
|
6079
|
+
/**
|
|
6080
|
+
* Marks a value inside a query document so it is replaced by `?` rather than printed.
|
|
6081
|
+
*
|
|
6082
|
+
* A document language carries its values inline, so there is nothing in the shape itself to say
|
|
6083
|
+
* which parts are operators and which are data. A dialect wraps the data as it builds the
|
|
6084
|
+
* document, and `parameteriseDocument` reads the wrapper.
|
|
6085
|
+
*/ const PARAMETER = Symbol("routier.parameter");
|
|
6086
|
+
const parameter = (value)=>({
|
|
6087
|
+
[PARAMETER]: value
|
|
6088
|
+
});
|
|
6089
|
+
const isParameter = (value)=>typeof value === "object" && value !== null && PARAMETER in value;
|
|
6090
|
+
/**
|
|
6091
|
+
* Renders a query DOCUMENT with its values replaced by `?`.
|
|
6092
|
+
*
|
|
6093
|
+
* Language-agnostic on purpose: an MQL filter and a Mango selector are both plain objects, and so
|
|
6094
|
+
* is whatever a future document store wants reported. The dialect decides the shape; this only
|
|
6095
|
+
* decides how it is written down.
|
|
6096
|
+
*
|
|
6097
|
+
* A value not wrapped by `parameter` is structural — an operator name, a field path, a nesting
|
|
6098
|
+
* level — and is printed as it is. That is the whole distinction, and it has to be made where the
|
|
6099
|
+
* document is built, because by the time it is an object the two are the same kind of thing.
|
|
6100
|
+
*/ const parameteriseDocument = (document)=>{
|
|
6101
|
+
const parameters = [];
|
|
6102
|
+
const render = (value)=>{
|
|
6103
|
+
if (isParameter(value)) {
|
|
6104
|
+
parameters.push(value[PARAMETER]);
|
|
6105
|
+
return "?";
|
|
6106
|
+
}
|
|
6107
|
+
if (Array.isArray(value)) {
|
|
6108
|
+
return `[${value.map(render).join(", ")}]`;
|
|
6109
|
+
}
|
|
6110
|
+
if (typeof value === "object" && value !== null) {
|
|
6111
|
+
const entries = Object.entries(value).map(([key, nested])=>`${JSON.stringify(key)}: ${render(nested)}`);
|
|
6112
|
+
return `{ ${entries.join(", ")} }`;
|
|
6113
|
+
}
|
|
6114
|
+
return JSON.stringify(value) ?? String(value);
|
|
6115
|
+
};
|
|
6116
|
+
return {
|
|
6117
|
+
text: render(document),
|
|
6118
|
+
parameters
|
|
6119
|
+
};
|
|
6120
|
+
};
|
|
6121
|
+
/**
|
|
6122
|
+
* Every filter on a query, as one description.
|
|
6123
|
+
*
|
|
6124
|
+
* Filters accumulate — `.where(a).where(b)` is `a && b` — so they are reported as one predicate
|
|
6125
|
+
* rather than several, which is how the caller thinks of them and how a SQL plugin renders them
|
|
6126
|
+
* into one `WHERE`. Parameters run left to right across the whole thing, matching the text.
|
|
6127
|
+
*
|
|
6128
|
+
* A filter that could not be parsed falls back to its source. Mixing the two is deliberate: one
|
|
6129
|
+
* unparsable filter does not make the others unreadable, and seeing which one it was is the
|
|
6130
|
+
* point.
|
|
6131
|
+
*/ const describeFilters = (filters)=>{
|
|
6132
|
+
const parameters = [];
|
|
6133
|
+
const parts = filters.map((entry)=>{
|
|
6134
|
+
const described = entry.expression?.type === "not-parsable" ? describeUnparsableFilter(entry.filter, entry.expression.reason) : describeFilterAsJs(entry.expression);
|
|
6135
|
+
parameters.push(...described.parameters);
|
|
6136
|
+
return described.text;
|
|
6137
|
+
});
|
|
6138
|
+
if (parts.length === 0) {
|
|
6139
|
+
return {
|
|
6140
|
+
text: "(no filter)",
|
|
6141
|
+
parameters: []
|
|
6142
|
+
};
|
|
6143
|
+
}
|
|
6144
|
+
return {
|
|
6145
|
+
text: parts.length === 1 ? parts[0] : parts.join(" && "),
|
|
6146
|
+
parameters
|
|
6147
|
+
};
|
|
6148
|
+
};
|
|
6149
|
+
/**
|
|
6150
|
+
* A predicate core could not parse, shown as the caller wrote it.
|
|
6151
|
+
*
|
|
6152
|
+
* This is the case where the source matters most: an unparsable filter is why the query did not
|
|
6153
|
+
* push down, and the reason codes say that it happened without showing what it was. There are no
|
|
6154
|
+
* parameters — nothing was extracted, because nothing was understood.
|
|
6155
|
+
*/ const describeUnparsableFilter = (filter, reason)=>({
|
|
6156
|
+
text: typeof filter === "function" ? `${String(filter)} — ${reason ?? "could not be parsed"}, evaluated in memory` : "(not parsable)",
|
|
6157
|
+
parameters: []
|
|
6158
|
+
});
|
|
6159
|
+
|
|
4327
6160
|
;// CONCATENATED MODULE: ./src/plugins/query/explain.ts
|
|
4328
6161
|
|
|
4329
6162
|
/**
|
|
@@ -4338,12 +6171,23 @@ class SqlTranslator extends DataTranslator {
|
|
|
4338
6171
|
"map-rename": "A map renames or drops properties, so every option after it refers to names the database does not have.",
|
|
4339
6172
|
"after-nearest": "A similarity search orders and limits rows, and the plugin cannot report whether it performed the search, so every option after it runs in memory.",
|
|
4340
6173
|
"after-join": "A join produces [outer, inner] tuples rather than entities, and the plugin cannot report how it joined, so every option after it runs in memory.",
|
|
4341
|
-
"cross-plugin-join": "The two sides of this join live on different plugins, so neither can read the other's rows and the join runs in the datastore."
|
|
6174
|
+
"cross-plugin-join": "The two sides of this join live on different plugins, so neither can read the other's rows and the join runs in the datastore.",
|
|
6175
|
+
"after-window": "A skip or take runs before this option, and SQL applies WHERE before LIMIT, so pushing it down would window rows this option had not seen yet. It runs in memory over the windowed rows instead.",
|
|
6176
|
+
"predicate-error": "A strict comparison compares a column against a value of a type it can never equal, so the answer is the same for every row and the filter runs in memory. Check the types in the filter."
|
|
4342
6177
|
};
|
|
4343
6178
|
const EXECUTED_QUERIES_UNSUPPORTED = "This plugin did not report what it executed. It may not support explain.";
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
6179
|
+
/**
|
|
6180
|
+
* Why an option planned for the database did not run there. `executed` has no sentence: it needs no
|
|
6181
|
+
* explaining, and a step made of executed options is a database step like any other.
|
|
6182
|
+
*/ const DATABASE_EXECUTION_EXPLANATIONS = {
|
|
6183
|
+
"missing-capability": "The plugin's engine cannot express this option, so it runs in memory over the rows the plugin did return. Only the plugin can know this — an engine's capabilities are not visible from here.",
|
|
6184
|
+
"engine-divergence": "The plugin's engine would answer this option differently from JavaScript, so it runs in memory instead and the rows match what the predicate means. Nothing in the query needs changing.",
|
|
6185
|
+
"not-reached": "The database stopped at an option it could not express, so this one runs in memory too. Carrying on would apply it to rows the earlier option had not filtered."
|
|
6186
|
+
};
|
|
6187
|
+
/**
|
|
6188
|
+
* TypeScript does not narrow a union from a discriminant nested inside a property, so the two kinds
|
|
6189
|
+
* of step need a guard rather than an inline check.
|
|
6190
|
+
*/ const isDatabaseStep = (step)=>step.executedIn.kind === "database";
|
|
4347
6191
|
/**
|
|
4348
6192
|
* The reportable shape of one option's value.
|
|
4349
6193
|
*
|
|
@@ -4424,12 +6268,16 @@ const explainedOptionsOf = (options)=>{
|
|
|
4424
6268
|
options.forEach((option)=>explained.push(explainedOptionOf(option, index++)));
|
|
4425
6269
|
return explained;
|
|
4426
6270
|
};
|
|
6271
|
+
/** Every sentence, whoever decided — the summary reads the same either way. */ const EXPLANATIONS = {
|
|
6272
|
+
...MEMORY_EXECUTION_EXPLANATIONS,
|
|
6273
|
+
...DATABASE_EXECUTION_EXPLANATIONS
|
|
6274
|
+
};
|
|
4427
6275
|
const summarize = (steps)=>{
|
|
4428
6276
|
const reasons = [];
|
|
4429
6277
|
let database = 0;
|
|
4430
6278
|
let memory = 0;
|
|
4431
6279
|
for (const step of steps){
|
|
4432
|
-
if (step
|
|
6280
|
+
if (isDatabaseStep(step)) {
|
|
4433
6281
|
database += step.options.length;
|
|
4434
6282
|
continue;
|
|
4435
6283
|
}
|
|
@@ -4439,7 +6287,7 @@ const summarize = (steps)=>{
|
|
|
4439
6287
|
}
|
|
4440
6288
|
}
|
|
4441
6289
|
const counts = `${database} ${database === 1 ? "option ran" : "options ran"} in the database, ${memory} ran in memory.`;
|
|
4442
|
-
const causes = reasons.map((reason)=>
|
|
6290
|
+
const causes = reasons.map((reason)=>EXPLANATIONS[reason]).join(" ");
|
|
4443
6291
|
return {
|
|
4444
6292
|
database,
|
|
4445
6293
|
memory,
|
|
@@ -4447,50 +6295,87 @@ const summarize = (steps)=>{
|
|
|
4447
6295
|
explanation: causes.length === 0 ? counts : `${counts} ${causes}`
|
|
4448
6296
|
};
|
|
4449
6297
|
};
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
6298
|
+
const outcomeOf = (option)=>{
|
|
6299
|
+
if (option.target === "memory") {
|
|
6300
|
+
return {
|
|
6301
|
+
executedIn: "memory",
|
|
6302
|
+
reason: option.reason,
|
|
6303
|
+
explanation: MEMORY_EXECUTION_EXPLANATIONS[option.reason]
|
|
6304
|
+
};
|
|
6305
|
+
}
|
|
6306
|
+
if (option.reason === "executed") {
|
|
6307
|
+
return {
|
|
6308
|
+
executedIn: "database",
|
|
6309
|
+
reason: null,
|
|
6310
|
+
explanation: null
|
|
6311
|
+
};
|
|
6312
|
+
}
|
|
6313
|
+
return {
|
|
6314
|
+
executedIn: "memory",
|
|
6315
|
+
reason: option.reason,
|
|
6316
|
+
explanation: DATABASE_EXECUTION_EXPLANATIONS[option.reason]
|
|
6317
|
+
};
|
|
6318
|
+
};
|
|
6319
|
+
/** Whether an option belongs to the step already open, or starts a new one. */ const continuesStep = (current, outcome)=>{
|
|
6320
|
+
if (current == null) {
|
|
6321
|
+
return false;
|
|
6322
|
+
}
|
|
6323
|
+
if (current.executedIn.kind === "database") {
|
|
6324
|
+
return outcome.reason == null;
|
|
6325
|
+
}
|
|
6326
|
+
// `?? null` because a step with no reason omits the key, and `undefined === null` is false —
|
|
6327
|
+
// without it every option started a step of its own
|
|
6328
|
+
return outcome.reason != null && (current.reason ?? null) === outcome.reason;
|
|
6329
|
+
};
|
|
6330
|
+
const toExecutionSteps = (options, ranIn)=>{
|
|
4462
6331
|
const steps = [];
|
|
4463
6332
|
let index = 0;
|
|
4464
6333
|
options.forEach((option)=>{
|
|
4465
6334
|
const explained = explainedOptionOf(option, index++);
|
|
4466
6335
|
const current = steps[steps.length - 1];
|
|
4467
|
-
|
|
6336
|
+
const outcome = outcomeOf(option);
|
|
6337
|
+
// Grouped by outcome, not by target: an option the database could not express and one core
|
|
6338
|
+
// sent to memory both run in memory, for different reasons a reader needs told apart.
|
|
6339
|
+
if (continuesStep(current, outcome) === true) {
|
|
4468
6340
|
current.options.push(explained);
|
|
4469
6341
|
return;
|
|
4470
6342
|
}
|
|
4471
|
-
steps.push({
|
|
4472
|
-
step:
|
|
6343
|
+
steps.push(outcome.reason == null ? {
|
|
6344
|
+
step: 0,
|
|
4473
6345
|
of: 0,
|
|
4474
|
-
executedIn:
|
|
4475
|
-
description: option.target === "database" ? DATABASE_STEP_DESCRIPTION : MEMORY_STEP_DESCRIPTION,
|
|
6346
|
+
executedIn: ranIn,
|
|
4476
6347
|
options: [
|
|
4477
6348
|
explained
|
|
4478
6349
|
],
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
6350
|
+
executedQueries: []
|
|
6351
|
+
} : {
|
|
6352
|
+
step: 0,
|
|
6353
|
+
of: 0,
|
|
6354
|
+
executedIn: {
|
|
6355
|
+
kind: "memory"
|
|
6356
|
+
},
|
|
6357
|
+
options: [
|
|
6358
|
+
explained
|
|
6359
|
+
],
|
|
6360
|
+
reason: outcome.reason,
|
|
6361
|
+
explanation: outcome.explanation ?? undefined
|
|
4483
6362
|
});
|
|
4484
6363
|
});
|
|
4485
|
-
|
|
6364
|
+
// A database step even when nothing pushed down: the plugin is dispatched either way, so
|
|
6365
|
+
// reporting "0 in the database" while the backend reads the whole table is the opposite of
|
|
6366
|
+
// the truth.
|
|
6367
|
+
if (steps[0]?.executedIn.kind !== "database") {
|
|
4486
6368
|
steps.unshift({
|
|
4487
6369
|
step: 0,
|
|
4488
6370
|
of: 0,
|
|
4489
|
-
executedIn:
|
|
4490
|
-
|
|
4491
|
-
|
|
6371
|
+
executedIn: ranIn,
|
|
6372
|
+
options: [],
|
|
6373
|
+
executedQueries: []
|
|
4492
6374
|
});
|
|
4493
6375
|
}
|
|
6376
|
+
return steps;
|
|
6377
|
+
};
|
|
6378
|
+
/** Numbers a finished list, so `step 1 of 3` reads as the shape of the whole query. */ const numbered = (steps)=>{
|
|
4494
6379
|
for(let i = 0; i < steps.length; i++){
|
|
4495
6380
|
steps[i].step = i + 1;
|
|
4496
6381
|
steps[i].of = steps.length;
|
|
@@ -4505,10 +6390,11 @@ const summarize = (steps)=>{
|
|
|
4505
6390
|
* post-join filter alone in the memory half derives back to `"database"`, and the document
|
|
4506
6391
|
* would report memory work as having run in the database.
|
|
4507
6392
|
*/ const explainQuery = (options, context)=>{
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
6393
|
+
const executionSteps = numbered(toExecutionSteps(options, {
|
|
6394
|
+
kind: "database",
|
|
6395
|
+
database: context.database,
|
|
6396
|
+
plugin: context.pluginKind
|
|
6397
|
+
}));
|
|
4512
6398
|
return {
|
|
4513
6399
|
collection: context.collection,
|
|
4514
6400
|
database: context.database,
|
|
@@ -4535,7 +6421,7 @@ const summarize = (steps)=>{
|
|
|
4535
6421
|
// Only the first database step: a plugin reports what IT ran, and everything it ran
|
|
4536
6422
|
// was sent as one dispatch. Stamping the same statements onto a second database step
|
|
4537
6423
|
// would claim they ran twice.
|
|
4538
|
-
if (step
|
|
6424
|
+
if (isDatabaseStep(step) === false || attached === true) {
|
|
4539
6425
|
return {
|
|
4540
6426
|
...step,
|
|
4541
6427
|
options: [
|
|
@@ -4568,8 +6454,47 @@ const summarize = (steps)=>{
|
|
|
4568
6454
|
executionSteps
|
|
4569
6455
|
};
|
|
4570
6456
|
};
|
|
6457
|
+
/**
|
|
6458
|
+
* Adds the step for a cross-plugin join's inner side.
|
|
6459
|
+
*
|
|
6460
|
+
* Appended by the executor rather than derived from the options, because the inner side's options
|
|
6461
|
+
* live on the join, in its own collection, and were never part of this query's chain. It goes before
|
|
6462
|
+
* the memory steps that consume it — the join cannot run until both sides are read.
|
|
6463
|
+
*/ /**
|
|
6464
|
+
* Every statement the query ran, across every database it touched, in execution order.
|
|
6465
|
+
*
|
|
6466
|
+
* A step is a place, so the statements live on the steps — this is for a caller that wants them all
|
|
6467
|
+
* without caring which plugin ran which.
|
|
6468
|
+
*/ const executedQueriesOf = (explanation)=>explanation.executionSteps.flatMap((step)=>isDatabaseStep(step) ? step.executedQueries : []);
|
|
6469
|
+
const withInnerSide = (explanation, innerSide)=>{
|
|
6470
|
+
const step = {
|
|
6471
|
+
step: 0,
|
|
6472
|
+
of: 0,
|
|
6473
|
+
executedIn: {
|
|
6474
|
+
kind: "database",
|
|
6475
|
+
database: innerSide.database,
|
|
6476
|
+
plugin: innerSide.plugin
|
|
6477
|
+
},
|
|
6478
|
+
options: [],
|
|
6479
|
+
executedQueries: innerSide.executedQueries
|
|
6480
|
+
};
|
|
6481
|
+
const firstMemory = explanation.executionSteps.findIndex((current)=>isDatabaseStep(current) === false);
|
|
6482
|
+
const at = firstMemory === -1 ? explanation.executionSteps.length : firstMemory;
|
|
6483
|
+
const executionSteps = numbered([
|
|
6484
|
+
...explanation.executionSteps.slice(0, at),
|
|
6485
|
+
step,
|
|
6486
|
+
...explanation.executionSteps.slice(at)
|
|
6487
|
+
]);
|
|
6488
|
+
return {
|
|
6489
|
+
...explanation,
|
|
6490
|
+
executionSteps,
|
|
6491
|
+
summary: summarize(executionSteps)
|
|
6492
|
+
};
|
|
6493
|
+
};
|
|
4571
6494
|
|
|
4572
6495
|
;// CONCATENATED MODULE: ./src/plugins/query/formatExplanation.ts
|
|
6496
|
+
|
|
6497
|
+
|
|
4573
6498
|
const OPTION_LABEL_WIDTH = 8;
|
|
4574
6499
|
const WRAP_WIDTH = 68;
|
|
4575
6500
|
/** Wraps `text` to `WRAP_WIDTH`, prefixing every line with `indent`. */ const wrap = (text, indent)=>{
|
|
@@ -4595,29 +6520,41 @@ const COMPARATOR_SYMBOLS = {
|
|
|
4595
6520
|
"less-than": "<",
|
|
4596
6521
|
"less-than-equals": "<="
|
|
4597
6522
|
};
|
|
4598
|
-
const describeValue = (value)=>{
|
|
4599
|
-
if (value
|
|
6523
|
+
/** Typed against the union so a new OBJECT tag is a compile error here, not an "undefined" in output. */ const describeValue = (value)=>{
|
|
6524
|
+
if (value === null) {
|
|
6525
|
+
return "null";
|
|
6526
|
+
}
|
|
6527
|
+
if (value === undefined) {
|
|
4600
6528
|
return "?";
|
|
4601
6529
|
}
|
|
4602
|
-
if (value
|
|
4603
|
-
return
|
|
6530
|
+
if (Array.isArray(value)) {
|
|
6531
|
+
return `[${value.map(describeValue).join(", ")}]`;
|
|
6532
|
+
}
|
|
6533
|
+
if (typeof value !== "object") {
|
|
6534
|
+
return typeof value === "string" ? `"${value}"` : String(value);
|
|
6535
|
+
}
|
|
6536
|
+
if ("date" in value) {
|
|
6537
|
+
return value.date;
|
|
6538
|
+
}
|
|
6539
|
+
if ("undefined" in value) {
|
|
6540
|
+
return "undefined";
|
|
4604
6541
|
}
|
|
4605
|
-
if (
|
|
4606
|
-
return value.
|
|
6542
|
+
if ("regex" in value) {
|
|
6543
|
+
return `/${value.regex.source}/${value.regex.flags}`;
|
|
4607
6544
|
}
|
|
4608
|
-
if (
|
|
4609
|
-
return
|
|
6545
|
+
if ("bigint" in value) {
|
|
6546
|
+
return `${value.bigint}n`;
|
|
4610
6547
|
}
|
|
4611
|
-
return value.
|
|
6548
|
+
return value.number;
|
|
4612
6549
|
};
|
|
4613
6550
|
/** Renders a serialized expression back to something close to the source predicate. */ const describeExpression = (expression)=>{
|
|
4614
6551
|
if (expression == null) {
|
|
4615
6552
|
return "?";
|
|
4616
6553
|
}
|
|
4617
|
-
if (expression.
|
|
6554
|
+
if (expression.type === "operator") {
|
|
4618
6555
|
return `${describeExpression(expression.left)} ${expression.operator} ${describeExpression(expression.right)}`;
|
|
4619
6556
|
}
|
|
4620
|
-
if (expression.
|
|
6557
|
+
if (expression.type === "comparator") {
|
|
4621
6558
|
const left = describeExpression(expression.left);
|
|
4622
6559
|
const right = describeExpression(expression.right);
|
|
4623
6560
|
const symbol = COMPARATOR_SYMBOLS[expression.comparator];
|
|
@@ -4626,13 +6563,24 @@ const describeValue = (value)=>{
|
|
|
4626
6563
|
}
|
|
4627
6564
|
return `${left} ${expression.negated === true ? "!==" : symbol} ${right}`;
|
|
4628
6565
|
}
|
|
4629
|
-
if (expression.
|
|
6566
|
+
if (expression.type === "property") {
|
|
4630
6567
|
return expression.path;
|
|
4631
6568
|
}
|
|
4632
|
-
if (expression.
|
|
6569
|
+
if (expression.type === "value") {
|
|
4633
6570
|
return describeValue(expression.value);
|
|
4634
6571
|
}
|
|
4635
|
-
|
|
6572
|
+
if (expression.type === "call") {
|
|
6573
|
+
return (0,callSource/* .renderCallAsJs */.a)(expression.call, ()=>describeExpression(expression.expression), ()=>(expression.arguments ?? []).map(describeExpression));
|
|
6574
|
+
}
|
|
6575
|
+
if (expression.type === "empty") {
|
|
6576
|
+
return "(no filter)";
|
|
6577
|
+
}
|
|
6578
|
+
// Distinguishable from "(not parsable)", which means the parser gave up and this runs in memory
|
|
6579
|
+
if (expression.type === "not-parsable") {
|
|
6580
|
+
return expression.reason == null ? "(not parsable)" : `(not parsable: ${expression.reason})`;
|
|
6581
|
+
}
|
|
6582
|
+
// Unreachable while the union is exhausted above; a payload from a newer sender is not.
|
|
6583
|
+
return `(unsupported: ${expression.type})`;
|
|
4636
6584
|
};
|
|
4637
6585
|
const describeOption = (option)=>{
|
|
4638
6586
|
const detail = option.detail;
|
|
@@ -4660,18 +6608,35 @@ const describeOption = (option)=>{
|
|
|
4660
6608
|
}
|
|
4661
6609
|
return "";
|
|
4662
6610
|
};
|
|
6611
|
+
/**
|
|
6612
|
+
* The sentence for a kind of step.
|
|
6613
|
+
*
|
|
6614
|
+
* Here rather than on the step: it is one of two constants keyed off `executedIn`, so carrying it in
|
|
6615
|
+
* the payload put prose beside the field it was derived from.
|
|
6616
|
+
*/ const DATABASE_STEP_DESCRIPTION = "These options are sent to the plugin.";
|
|
6617
|
+
const MEMORY_STEP_DESCRIPTION = "Routier runs these over the rows the database returned, after deserializing them.";
|
|
6618
|
+
const UNNARROWED_READ_DESCRIPTION = "No option could be pushed down, so the plugin reads the whole collection.";
|
|
6619
|
+
/** `database · orders.db · SqliteDbPlugin`, so a cross-plugin join says who ran what. */ const whereItRan = (step)=>isDatabaseStep(step) ? `database · ${step.executedIn.database} · ${step.executedIn.plugin}` : "memory";
|
|
4663
6620
|
const formatStep = (step, lines)=>{
|
|
4664
|
-
const reason = step.reason == null ? "" : ` [${step.reason}]`;
|
|
4665
|
-
lines.push(` STEP ${step.step} of ${step.of} — ${step
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
6621
|
+
const reason = isDatabaseStep(step) || step.reason == null ? "" : ` [${step.reason}]`;
|
|
6622
|
+
lines.push(` STEP ${step.step} of ${step.of} — ${whereItRan(step)}${reason}`);
|
|
6623
|
+
if (isDatabaseStep(step)) {
|
|
6624
|
+
lines.push(...wrap(step.options.length === 0 ? UNNARROWED_READ_DESCRIPTION : DATABASE_STEP_DESCRIPTION, " "));
|
|
6625
|
+
} else {
|
|
6626
|
+
lines.push(...wrap(MEMORY_STEP_DESCRIPTION, " "));
|
|
6627
|
+
if (step.explanation != null) {
|
|
6628
|
+
lines.push(...wrap(step.explanation, " "));
|
|
6629
|
+
}
|
|
4669
6630
|
}
|
|
4670
6631
|
lines.push("");
|
|
4671
6632
|
for (const option of step.options){
|
|
4672
6633
|
lines.push(` ${option.name.padEnd(OPTION_LABEL_WIDTH)} ${describeOption(option)}`.trimEnd());
|
|
4673
6634
|
}
|
|
4674
|
-
|
|
6635
|
+
if (isDatabaseStep(step) === false) {
|
|
6636
|
+
lines.push("");
|
|
6637
|
+
return;
|
|
6638
|
+
}
|
|
6639
|
+
for (const executed of step.executedQueries){
|
|
4675
6640
|
lines.push("");
|
|
4676
6641
|
lines.push(...executed.text.split("\n").map((line)=>` ${line}`));
|
|
4677
6642
|
if (executed.parameters != null && executed.parameters.length > 0) {
|
|
@@ -4720,8 +6685,11 @@ var types_QueryOrdering = /*#__PURE__*/ function(QueryOrdering) {
|
|
|
4720
6685
|
|
|
4721
6686
|
|
|
4722
6687
|
|
|
6688
|
+
|
|
4723
6689
|
// EXTERNAL MODULE: ./src/expressions/evaluate.ts
|
|
4724
6690
|
var evaluate = __webpack_require__(379);
|
|
6691
|
+
// EXTERNAL MODULE: ./src/expressions/fold.ts
|
|
6692
|
+
var fold = __webpack_require__(43);
|
|
4725
6693
|
;// CONCATENATED MODULE: ./src/plugins/wire/query.ts
|
|
4726
6694
|
|
|
4727
6695
|
|
|
@@ -4922,7 +6890,7 @@ const serializeQueryOptions = (options)=>{
|
|
|
4922
6890
|
}
|
|
4923
6891
|
case "filter":
|
|
4924
6892
|
{
|
|
4925
|
-
const expression = types/* .Expression.fromJson */.r4.fromJson(option.value.expression, schema);
|
|
6893
|
+
const expression = (0,fold/* .foldConstantCalls */.F5)(types/* .Expression.fromJson */.r4.fromJson(option.value.expression, schema));
|
|
4926
6894
|
options.add("filter", {
|
|
4927
6895
|
filter: (0,evaluate/* .toStrictPredicate */.wS)(expression),
|
|
4928
6896
|
expression,
|
|
@@ -5606,7 +7574,8 @@ var TrampolinePipeline = __webpack_require__(416);
|
|
|
5606
7574
|
if (!(0,assertions/* .isPropertyExpression */.e3)(left) || !(0,assertions/* .isValueExpression */.S6)(right)) {
|
|
5607
7575
|
return null;
|
|
5608
7576
|
}
|
|
5609
|
-
|
|
7577
|
+
// A called property is a CallExpression, so it fails the isPropertyExpression check above
|
|
7578
|
+
if (left.property.isKey !== true || right.value == null) {
|
|
5610
7579
|
return null;
|
|
5611
7580
|
}
|
|
5612
7581
|
return {
|
|
@@ -5979,11 +7948,17 @@ class EphemeralDataPlugin {
|
|
|
5979
7948
|
* collection to pair it with three rows.
|
|
5980
7949
|
*
|
|
5981
7950
|
* `cloned` is in storage shape, so the keys are read by resolved column name.
|
|
5982
|
-
*/
|
|
5983
|
-
|
|
5984
|
-
|
|
7951
|
+
*/ /**
|
|
7952
|
+
* No statement to quote — an ephemeral store walks its own records — so the scan
|
|
7953
|
+
* is said plainly, and the PREDICATE is reported as JavaScript beside it. A count
|
|
7954
|
+
* alone leaves a reader unable to tell a filter that matched nothing from one
|
|
7955
|
+
* that was never applied.
|
|
7956
|
+
*
|
|
7957
|
+
* Before the inner side, to match execution order.
|
|
7958
|
+
*/ const described = describeFilters(operation.options.get("filter").map((entry)=>entry.option.value));
|
|
5985
7959
|
event.executedQueries.push({
|
|
5986
|
-
text: `${operation.schema.collectionName}: scanned ${cloned.length} in-memory
|
|
7960
|
+
text: `${operation.schema.collectionName}: scanned ${cloned.length} in-memory ` + `${cloned.length === 1 ? "record" : "records"}, filter ${described.text}`,
|
|
7961
|
+
parameters: described.parameters.length > 0 ? described.parameters : undefined
|
|
5987
7962
|
});
|
|
5988
7963
|
const joinOption = operation.options.getLast("join");
|
|
5989
7964
|
const outerKeys = joinOption == null ? null : distinctJoinKeys(cloned, joinOption.value.outerKey, joinOption.value.semiJoinKeyThreshold, {
|
|
@@ -6158,6 +8133,29 @@ class TelemetryDbPlugin {
|
|
|
6158
8133
|
return JSON.stringify(option.value ?? null);
|
|
6159
8134
|
}
|
|
6160
8135
|
};
|
|
8136
|
+
/**
|
|
8137
|
+
* Restores a `Date` that `structuredClone` produced outside this realm.
|
|
8138
|
+
*
|
|
8139
|
+
* The clone is a real date and fails `instanceof Date`, which is what a caller checks. Mutated in
|
|
8140
|
+
* place because the clone is already private to this call.
|
|
8141
|
+
*/ const reviveDates = (value)=>{
|
|
8142
|
+
if (value == null || typeof value !== "object") {
|
|
8143
|
+
return value;
|
|
8144
|
+
}
|
|
8145
|
+
if (Object.prototype.toString.call(value) === "[object Date]") {
|
|
8146
|
+
return value instanceof Date ? value : new Date(value);
|
|
8147
|
+
}
|
|
8148
|
+
if (Array.isArray(value)) {
|
|
8149
|
+
for(let i = 0, length = value.length; i < length; i++){
|
|
8150
|
+
value[i] = reviveDates(value[i]);
|
|
8151
|
+
}
|
|
8152
|
+
return value;
|
|
8153
|
+
}
|
|
8154
|
+
for (const key of Object.keys(value)){
|
|
8155
|
+
value[key] = reviveDates(value[key]);
|
|
8156
|
+
}
|
|
8157
|
+
return value;
|
|
8158
|
+
};
|
|
6161
8159
|
class CacheDbPlugin {
|
|
6162
8160
|
plugin;
|
|
6163
8161
|
max;
|
|
@@ -6196,7 +8194,7 @@ class CacheDbPlugin {
|
|
|
6196
8194
|
* the next update would be written UNCHECKED with no error anywhere.
|
|
6197
8195
|
* Pinned by `datastore/src/collections/wrapperStacking.test.ts`.
|
|
6198
8196
|
*/ rebuild(entry) {
|
|
6199
|
-
return new entry.construct(structuredClone(entry.value), entry.isTransformed);
|
|
8197
|
+
return new entry.construct(reviveDates(structuredClone(entry.value)), entry.isTransformed);
|
|
6200
8198
|
}
|
|
6201
8199
|
query(event, done) {
|
|
6202
8200
|
const key = this.keyFor(event);
|
|
@@ -6219,6 +8217,14 @@ class CacheDbPlugin {
|
|
|
6219
8217
|
done(result);
|
|
6220
8218
|
return;
|
|
6221
8219
|
}
|
|
8220
|
+
// A partial answer must never be cached. When the plugin reports an option it cannot
|
|
8221
|
+
// express, these rows are what came back BEFORE the datastore finished the query — and a
|
|
8222
|
+
// later hit skips the plugin entirely, so nothing would report and the rows would be
|
|
8223
|
+
// returned as if they were the whole answer. Unfiltered, silently.
|
|
8224
|
+
if (event.operation.options.notExecuted().length > 0) {
|
|
8225
|
+
done(Result/* .PluginEventResult.success */.D.success(event.id, result.data));
|
|
8226
|
+
return;
|
|
8227
|
+
}
|
|
6222
8228
|
this.store(key, result.data);
|
|
6223
8229
|
// The caller gets a rebuilt value too, not the one just stored, so that mutating
|
|
6224
8230
|
// the result of a MISS cannot corrupt what the next hit returns.
|
|
@@ -6596,11 +8602,13 @@ const DEFAULT_MAX_BATCH_SIZE = 100;
|
|
|
6596
8602
|
|
|
6597
8603
|
|
|
6598
8604
|
|
|
8605
|
+
|
|
6599
8606
|
})();
|
|
6600
8607
|
|
|
6601
8608
|
var __webpack_exports__BatchingDbPlugin = __webpack_exports__.kX;
|
|
6602
8609
|
var __webpack_exports__CacheDbPlugin = __webpack_exports__.y4;
|
|
6603
8610
|
var __webpack_exports__ConcurrencyDbPlugin = __webpack_exports__.bX;
|
|
8611
|
+
var __webpack_exports__DATABASE_EXECUTION_EXPLANATIONS = __webpack_exports__.QC;
|
|
6604
8612
|
var __webpack_exports__DEFAULT_SEMI_JOIN_KEY_THRESHOLD = __webpack_exports__._b;
|
|
6605
8613
|
var __webpack_exports__DataTranslator = __webpack_exports__.JF;
|
|
6606
8614
|
var __webpack_exports__EXECUTED_QUERIES_UNSUPPORTED = __webpack_exports__.jE;
|
|
@@ -6621,18 +8629,26 @@ var __webpack_exports__applyInnerOptions = __webpack_exports__.VW;
|
|
|
6621
8629
|
var __webpack_exports__collectingSink = __webpack_exports__.wN;
|
|
6622
8630
|
var __webpack_exports__cosineDistance = __webpack_exports__.lO;
|
|
6623
8631
|
var __webpack_exports__createRequestHandler = __webpack_exports__.KB;
|
|
8632
|
+
var __webpack_exports__describeFilterAsJs = __webpack_exports__.fp;
|
|
8633
|
+
var __webpack_exports__describeFilters = __webpack_exports__.To;
|
|
8634
|
+
var __webpack_exports__describeUnparsableFilter = __webpack_exports__.B2;
|
|
6624
8635
|
var __webpack_exports__deserializeBulkPersist = __webpack_exports__.Mr;
|
|
6625
8636
|
var __webpack_exports__deserializePersistResult = __webpack_exports__.Pl;
|
|
6626
8637
|
var __webpack_exports__deserializeQueryOptions = __webpack_exports__.II;
|
|
6627
8638
|
var __webpack_exports__distinctJoinKeys = __webpack_exports__.RK;
|
|
6628
8639
|
var __webpack_exports__executeJoin = __webpack_exports__.m6;
|
|
8640
|
+
var __webpack_exports__executedQueriesOf = __webpack_exports__.fN;
|
|
6629
8641
|
var __webpack_exports__explainQuery = __webpack_exports__.ae;
|
|
6630
8642
|
var __webpack_exports__formatExplanation = __webpack_exports__.vZ;
|
|
6631
8643
|
var __webpack_exports__hashJoin = __webpack_exports__.Bg;
|
|
8644
|
+
var __webpack_exports__isDatabaseStep = __webpack_exports__.yX;
|
|
6632
8645
|
var __webpack_exports__joinInPlugin = __webpack_exports__.zH;
|
|
6633
8646
|
var __webpack_exports__loadJoinInnerSide = __webpack_exports__.as;
|
|
6634
8647
|
var __webpack_exports__loggerSink = __webpack_exports__.qj;
|
|
8648
|
+
var __webpack_exports__mappedResultColumns = __webpack_exports__._1;
|
|
6635
8649
|
var __webpack_exports__nearestBy = __webpack_exports__.iG;
|
|
8650
|
+
var __webpack_exports__parameter = __webpack_exports__.Wi;
|
|
8651
|
+
var __webpack_exports__parameteriseDocument = __webpack_exports__.i1;
|
|
6636
8652
|
var __webpack_exports__readJoinKey = __webpack_exports__.qy;
|
|
6637
8653
|
var __webpack_exports__semiJoinFilter = __webpack_exports__.lA;
|
|
6638
8654
|
var __webpack_exports__serializeBulkPersist = __webpack_exports__.n;
|
|
@@ -6641,6 +8657,7 @@ var __webpack_exports__serializeQueryOptions = __webpack_exports__.BL;
|
|
|
6641
8657
|
var __webpack_exports__splitSendableOptions = __webpack_exports__.PP;
|
|
6642
8658
|
var __webpack_exports__toEntityShape = __webpack_exports__.__;
|
|
6643
8659
|
var __webpack_exports__withExecutedQueries = __webpack_exports__.Kg;
|
|
6644
|
-
|
|
8660
|
+
var __webpack_exports__withInnerSide = __webpack_exports__.oJ;
|
|
8661
|
+
export { __webpack_exports__BatchingDbPlugin as BatchingDbPlugin, __webpack_exports__CacheDbPlugin as CacheDbPlugin, __webpack_exports__ConcurrencyDbPlugin as ConcurrencyDbPlugin, __webpack_exports__DATABASE_EXECUTION_EXPLANATIONS as DATABASE_EXECUTION_EXPLANATIONS, __webpack_exports__DEFAULT_SEMI_JOIN_KEY_THRESHOLD as DEFAULT_SEMI_JOIN_KEY_THRESHOLD, __webpack_exports__DataTranslator as DataTranslator, __webpack_exports__EXECUTED_QUERIES_UNSUPPORTED as EXECUTED_QUERIES_UNSUPPORTED, __webpack_exports__EphemeralDataPlugin as EphemeralDataPlugin, __webpack_exports__JsonTranslator as JsonTranslator, __webpack_exports__MEMORY_EXECUTION_EXPLANATIONS as MEMORY_EXECUTION_EXPLANATIONS, __webpack_exports__Query as Query, __webpack_exports__QueryOptionsCollection as QueryOptionsCollection, __webpack_exports__QueryOrdering as QueryOrdering, __webpack_exports__RetryDbPlugin as RetryDbPlugin, __webpack_exports__SqlTranslator as SqlTranslator, __webpack_exports__TelemetryDbPlugin as TelemetryDbPlugin, __webpack_exports__TranslatedArrayValue as TranslatedArrayValue, __webpack_exports__TranslatedGroupValue as TranslatedGroupValue, __webpack_exports__TranslatedSingleValue as TranslatedSingleValue, __webpack_exports__TupleTranslator as TupleTranslator, __webpack_exports__applyInnerOptions as applyInnerOptions, __webpack_exports__collectingSink as collectingSink, __webpack_exports__cosineDistance as cosineDistance, __webpack_exports__createRequestHandler as createRequestHandler, __webpack_exports__describeFilterAsJs as describeFilterAsJs, __webpack_exports__describeFilters as describeFilters, __webpack_exports__describeUnparsableFilter as describeUnparsableFilter, __webpack_exports__deserializeBulkPersist as deserializeBulkPersist, __webpack_exports__deserializePersistResult as deserializePersistResult, __webpack_exports__deserializeQueryOptions as deserializeQueryOptions, __webpack_exports__distinctJoinKeys as distinctJoinKeys, __webpack_exports__executeJoin as executeJoin, __webpack_exports__executedQueriesOf as executedQueriesOf, __webpack_exports__explainQuery as explainQuery, __webpack_exports__formatExplanation as formatExplanation, __webpack_exports__hashJoin as hashJoin, __webpack_exports__isDatabaseStep as isDatabaseStep, __webpack_exports__joinInPlugin as joinInPlugin, __webpack_exports__loadJoinInnerSide as loadJoinInnerSide, __webpack_exports__loggerSink as loggerSink, __webpack_exports__mappedResultColumns as mappedResultColumns, __webpack_exports__nearestBy as nearestBy, __webpack_exports__parameter as parameter, __webpack_exports__parameteriseDocument as parameteriseDocument, __webpack_exports__readJoinKey as readJoinKey, __webpack_exports__semiJoinFilter as semiJoinFilter, __webpack_exports__serializeBulkPersist as serializeBulkPersist, __webpack_exports__serializePersistResult as serializePersistResult, __webpack_exports__serializeQueryOptions as serializeQueryOptions, __webpack_exports__splitSendableOptions as splitSendableOptions, __webpack_exports__toEntityShape as toEntityShape, __webpack_exports__withExecutedQueries as withExecutedQueries, __webpack_exports__withInnerSide as withInnerSide };
|
|
6645
8662
|
|
|
6646
8663
|
//# sourceMappingURL=index.js.map
|