@routier/core 0.6.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.
Files changed (52) hide show
  1. package/dist/assertions/index.cjs +19 -8
  2. package/dist/assertions/index.cjs.map +1 -1
  3. package/dist/assertions/index.d.ts +5 -1
  4. package/dist/assertions/index.js +21 -9
  5. package/dist/assertions/index.js.map +1 -1
  6. package/dist/collections/MemoryDataCollection.d.ts +10 -0
  7. package/dist/collections/index.cjs +29 -4
  8. package/dist/collections/index.cjs.map +1 -1
  9. package/dist/collections/index.js +29 -4
  10. package/dist/collections/index.js.map +1 -1
  11. package/dist/expressions/callSource.d.ts +41 -0
  12. package/dist/expressions/evaluate.d.ts +3 -0
  13. package/dist/expressions/fold.d.ts +7 -0
  14. package/dist/expressions/index.cjs +1754 -233
  15. package/dist/expressions/index.cjs.map +1 -1
  16. package/dist/expressions/index.d.ts +2 -0
  17. package/dist/expressions/index.js +1765 -234
  18. package/dist/expressions/index.js.map +1 -1
  19. package/dist/expressions/types.d.ts +45 -26
  20. package/dist/expressions/utils.d.ts +19 -1
  21. package/dist/index.cjs +2415 -364
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.js +2755 -684
  24. package/dist/index.js.map +1 -1
  25. package/dist/performance/index.cjs +6 -4
  26. package/dist/performance/index.cjs.map +1 -1
  27. package/dist/performance/index.js +6 -4
  28. package/dist/performance/index.js.map +1 -1
  29. package/dist/pipeline/index.cjs +6 -4
  30. package/dist/pipeline/index.cjs.map +1 -1
  31. package/dist/pipeline/index.js +6 -4
  32. package/dist/pipeline/index.js.map +1 -1
  33. package/dist/plugins/index.cjs +2309 -316
  34. package/dist/plugins/index.cjs.map +1 -1
  35. package/dist/plugins/index.js +2313 -311
  36. package/dist/plugins/index.js.map +1 -1
  37. package/dist/plugins/query/QueryOptionsCollection.d.ts +38 -10
  38. package/dist/plugins/query/describeFilter.d.ts +83 -0
  39. package/dist/plugins/query/explain.d.ts +71 -9
  40. package/dist/plugins/query/index.d.ts +1 -0
  41. package/dist/plugins/query/join.d.ts +4 -1
  42. package/dist/plugins/query/types.d.ts +36 -4
  43. package/dist/schema/PropertyInfo.d.ts +0 -1
  44. package/dist/schema/index.cjs +7 -14
  45. package/dist/schema/index.cjs.map +1 -1
  46. package/dist/schema/index.js +7 -14
  47. package/dist/schema/index.js.map +1 -1
  48. package/dist/utilities/index.cjs +242 -49
  49. package/dist/utilities/index.cjs.map +1 -1
  50. package/dist/utilities/index.js +242 -49
  51. package/dist/utilities/index.js.map +1 -1
  52. package/package.json +1 -1
@@ -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 applyTransformer = (value, transformer)=>{
411
- if (transformer == null) {
412
- return value;
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 transformer applied to an absent value has no answer, and inventing one ("" for a missing
415
- // string) is how a filter starts matching rows it should not.
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 (transformer === "to-lower-case") {
420
- return typeof value === "string" ? value.toLowerCase() : UNRESOLVED;
421
- }
422
- if (transformer === "to-upper-case") {
423
- return typeof value === "string" ? value.toUpperCase() : UNRESOLVED;
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 (transformer === "length") {
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 operand = (expression, row)=>{
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 applyTransformer(expression.value, expression.transformer);
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 applyTransformer(expression.property.getValue(row), expression.transformer);
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 = operand(expression.left, row);
526
- const right = operand(expression.right, row);
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 _utilities__rspack_import_3 = __webpack_require__(581);
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
- const STRING_ESCAPES = {
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
- if (quote === "`" && source[i] === "$" && source[i + 1] === "{") {
753
- throw new Error(ERROR_MESSAGES.UNSUPPORTED("template literal interpolation"));
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
- const COMPARATOR_METHODS = {
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
- entityName;
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, entityName, paramsName, params){
1807
+ constructor(schema, stream, scope, paramsName, params){
1026
1808
  this.schema = schema;
1027
1809
  this.stream = stream;
1028
- this.entityName = entityName;
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
- expression.negated = !expression.negated;
1089
- return expression;
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
- // Parenthesized group
1102
- if (this.stream.matchPunctuation("(")) {
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.parseOperand();
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.parseOperand();
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
- if (root === this.entityName) {
1246
- return this.parseChain({
1247
- kind: "property",
1248
- root
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: "param",
1254
- root
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
- if (kind === "property" && token.kind === "identifier" && this.paramsName != null && token.value === this.paramsName) {
1327
- const paramPath = [];
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.kind === "property" && right.kind === "property") {
1418
- // Casing transformers are only valid with string-matching comparators,
1419
- // which cannot produce a property-to-property comparison
1420
- if (left.transformer === "to-lower-case" || left.transformer === "to-upper-case" || right.transformer === "to-lower-case" || right.transformer === "to-upper-case") {
1421
- throw new Error(ERROR_MESSAGES.UNSUPPORTED("transform method outside of startsWith/endsWith/includes"));
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 */ true);
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 */ true);
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
- const expression = new _types__rspack_import_1/* .PropertyExpression */.ep({
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
- const expression = new ParamReferenceExpression({
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 = operand.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
- const bound = new _types__rspack_import_1/* .ValueExpression */.Ko({
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
- const clone = new _types__rspack_import_1/* .ValueExpression */.Ko({
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
- const clone = new _types__rspack_import_1/* .PropertyExpression */.ep({
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
- let entityName;
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
- // Unwrap a single-return block body: { return <expression>; }
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
- entityName,
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 parser = new ExpressionParser(schema, stream, rootName, null, undefined);
1732
- return parser.parse();
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)=>_utilities__rspack_import_3/* .logger.warn */.vF.warn("Error parsing expression", {
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 _types__rspack_import_1/* .Expression.NOT_PARSABLE */.r4.NOT_PARSABLE;
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.NOT_PARSABLE */.r4.NOT_PARSABLE;
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.entityName, shape.paramsName, params);
3192
+ const parser = new ExpressionParser(schema, stream, shape.scope, shape.paramsName, params);
1768
3193
  paramsName = shape.paramsName;
1769
- template = parser.parse();
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: _types__rspack_import_1/* .Expression.NOT_PARSABLE */.r4.NOT_PARSABLE,
3203
+ template: refused,
1778
3204
  paramsName: null
1779
3205
  });
1780
3206
  }
1781
3207
  warn(error);
1782
- return _types__rspack_import_1/* .Expression.NOT_PARSABLE */.r4.NOT_PARSABLE;
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.NOT_PARSABLE */.r4.NOT_PARSABLE;
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
- k: "undefined"
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
- k: "date",
1827
- v: value.toISOString()
3250
+ date: value.toISOString()
1828
3251
  };
1829
3252
  }
1830
- if (Array.isArray(value)) {
1831
- return {
1832
- k: "array",
1833
- v: value.map(valueToJson)
1834
- };
3253
+ if (Array.isArray(value)) {
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
- k: "number",
1841
- v: Number.isNaN(value) ? "NaN" : value > 0 ? "Infinity" : "-Infinity"
3260
+ number: Number.isNaN(value) ? "NaN" : value > 0 ? "Infinity" : "-Infinity"
1842
3261
  };
1843
3262
  }
1844
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
+ }
3272
+ };
3273
+ }
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
- k: "raw",
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.k === "undefined") {
1854
- return undefined;
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 (value.k === "date") {
1857
- return new Date(value.v);
3293
+ if ("undefined" in value) {
3294
+ return undefined;
1858
3295
  }
1859
- if (value.k === "array") {
1860
- return value.v.map(valueFromJson);
3296
+ if ("regex" in value) {
3297
+ return new RegExp(value.regex.source, value.regex.flags);
1861
3298
  }
1862
- if (value.k === "number") {
1863
- return value.v === "NaN" ? Number.NaN : value.v === "Infinity" ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY;
3299
+ if ("bigint" in value) {
3300
+ return BigInt(value.bigint);
1864
3301
  }
1865
- return value.v;
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 six node types a bound tree can contain, exactly one holds anything JSON cannot carry:
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
- t: "operator",
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
- t: "comparator",
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
- t: "property",
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
- t: "value",
1952
- value: valueToJson(value.value),
1953
- transformer: value.transformer,
1954
- locale: value.locale
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
- return expression.type === "empty" ? {
1958
- t: "empty"
3407
+ const reason = expression.reason;
3408
+ return reason == null ? {
3409
+ type: "not-parsable"
1959
3410
  } : {
1960
- t: "not-parsable"
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.t === "operator") {
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.t === "comparator") {
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.t === "property") {
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
- const rebuilt = new PropertyExpression({
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.t === "value") {
2005
- const rebuilt = new ValueExpression({
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
- return json.t === "empty" ? Expression.EMPTY : Expression.NOT_PARSABLE;
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
- j: () => (forEach)
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
- // Traverse left and right expressions if they exist
2088
- if (expr.left) {
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
- // Traverse left and right expressions if they exist
2106
- if (expr.left) {
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 _expressions_utils__rspack_import_0 = __webpack_require__(63);
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,_expressions_utils__rspack_import_0/* .forEach */.j)(filterValue.expression, (expression)=>{
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
- ...this.nextExecutionReason == null ? {} : {
2515
- reason: this.nextExecutionReason
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.add(option.name, option.value);
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
- if (sortedItem.option.target === "database") {
2619
- databaseQueryOptionsCollection.add(sortedItem.option.name, sortedItem.option.value);
2620
- continue;
2621
- }
2622
- memoryQueryOptionsCollection.add(sortedItem.option.name, sortedItem.option.value);
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
- if (this.enumeratedItems.length != this.nextIndex) {
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
- return 'silent';
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,6 +4674,7 @@ __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),
3055
4680
  _1: () => (/* reexport */ mappedResultColumns),
@@ -3058,27 +4683,35 @@ __webpack_require__.d(__webpack_exports__, {
3058
4683
  yR: () => (/* reexport */ serializePersistResult),
3059
4684
  y4: () => (/* reexport */ CacheDbPlugin),
3060
4685
  lO: () => (/* reexport */ cosineDistance),
4686
+ QC: () => (/* reexport */ DATABASE_EXECUTION_EXPLANATIONS),
3061
4687
  d0: () => (/* reexport */ JsonTranslator),
4688
+ Wi: () => (/* reexport */ parameter),
3062
4689
  PP: () => (/* reexport */ splitSendableOptions),
3063
4690
  f2: () => (/* reexport */ TranslatedGroupValue),
3064
4691
  wN: () => (/* reexport */ collectingSink),
3065
4692
  QB: () => (/* reexport */ RetryDbPlugin),
3066
4693
  m6: () => (/* reexport */ executeJoin),
4694
+ i1: () => (/* reexport */ parameteriseDocument),
3067
4695
  __: () => (/* reexport */ toEntityShape),
3068
4696
  VW: () => (/* reexport */ applyInnerOptions),
4697
+ B2: () => (/* reexport */ describeUnparsableFilter),
3069
4698
  Jd: () => (/* reexport */ EphemeralDataPlugin),
3070
4699
  Pl: () => (/* reexport */ deserializePersistResult),
3071
4700
  JF: () => (/* reexport */ DataTranslator),
3072
4701
  HM: () => (/* reexport */ QueryOptionsCollection/* .QueryOptionsCollection */.H),
3073
4702
  KB: () => (/* reexport */ createRequestHandler),
3074
- vZ: () => (/* reexport */ formatExplanation),
4703
+ fN: () => (/* reexport */ executedQueriesOf),
3075
4704
  II: () => (/* reexport */ deserializeQueryOptions),
3076
- n: () => (/* reexport */ serializeBulkPersist),
4705
+ vZ: () => (/* reexport */ formatExplanation),
4706
+ yX: () => (/* reexport */ isDatabaseStep),
3077
4707
  as: () => (/* reexport */ loadJoinInnerSide),
4708
+ n: () => (/* reexport */ serializeBulkPersist),
3078
4709
  lA: () => (/* reexport */ semiJoinFilter),
4710
+ oJ: () => (/* reexport */ withInnerSide),
3079
4711
  kX: () => (/* reexport */ BatchingDbPlugin),
3080
4712
  DF: () => (/* reexport */ SqlTranslator),
3081
4713
  qj: () => (/* reexport */ loggerSink),
4714
+ To: () => (/* reexport */ describeFilters),
3082
4715
  gH: () => (/* reexport */ MEMORY_EXECUTION_EXPLANATIONS),
3083
4716
  BL: () => (/* reexport */ serializeQueryOptions),
3084
4717
  Kg: () => (/* reexport */ withExecutedQueries),
@@ -3195,6 +4828,10 @@ class DataTranslator {
3195
4828
  translate(data) {
3196
4829
  const isTransformed = this.query.options.hasTransformations();
3197
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
+ }
3198
4835
  data = this.functionMap[item.name](data, item);
3199
4836
  });
3200
4837
  if (Array.isArray(data)) {
@@ -3607,7 +5244,7 @@ class Query {
3607
5244
  * Only a plugin that runs its outer query FIRST can supply these, and most run this loader
3608
5245
  * before anything else — so it is optional, and its absence costs a wider inner read rather
3609
5246
  * than a wrong one.
3610
- */ outerKeys)=>{
5247
+ */ outerKeys, /** Where the inner read reports what it executed. Defaults to the outer read's own list. */ innerExecutedQueries)=>{
3611
5248
  const joinOption = event.operation.options.getLast("join");
3612
5249
  if (joinOption == null) {
3613
5250
  done({
@@ -3635,9 +5272,10 @@ class Query {
3635
5272
  action: "query",
3636
5273
  reason: "join inner side",
3637
5274
  explain: event.explain,
3638
- // The same array the outer read pushes into, so a join reports BOTH reads in execution
3639
- // order. Built fresh rather than spread, so this has to be carried explicitly.
3640
- executedQueries: event.executedQueries
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
3641
5279
  };
3642
5280
  query(innerEvent, (result)=>{
3643
5281
  if (result.ok === Result/* .PluginEventResult.ERROR */.D.ERROR) {
@@ -3714,6 +5352,12 @@ class Query {
3714
5352
  return;
3715
5353
  }
3716
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
+ }
3717
5361
  // Storage shape: the plugin returns rows as it holds them, and deserialization is what
3718
5362
  // `executeJoin` does per side below.
3719
5363
  const outerKeys = distinctJoinKeys(outerRows, at.value.outerKey, at.value.semiJoinKeyThreshold, {
@@ -3829,9 +5473,7 @@ class JsonTranslator extends DataTranslator {
3829
5473
  if (field.property != null) {
3830
5474
  const value = field.property.getValue(data[i]);
3831
5475
  if (value != null) {
3832
- // Some types do not support deserialization (Array, Function, Computed, etc), just directly set the incoming value
3833
- const resolvedValue = field.property.supportsDeserialization ? field.property.deserialize(value) : value;
3834
- field.property.setValue(data[i], resolvedValue);
5476
+ field.property.setValue(data[i], field.property.deserialize(value));
3835
5477
  }
3836
5478
  }
3837
5479
  }
@@ -3855,9 +5497,7 @@ class JsonTranslator extends DataTranslator {
3855
5497
  if (field.property != null) {
3856
5498
  const value = field.property.getValue(data[i]);
3857
5499
  if (value != null) {
3858
- // Some types do not support deserialization (Array, Function, Computed, etc), just directly set the incoming value
3859
- const resolvedValue = field.property.supportsDeserialization ? field.property.deserialize(value) : value;
3860
- field.property.setValue(item, resolvedValue);
5500
+ field.property.setValue(item, field.property.deserialize(value));
3861
5501
  continue;
3862
5502
  }
3863
5503
  // The property exists, lets set it to the value (null/undefined)
@@ -4198,9 +5838,12 @@ class SqlTranslator extends DataTranslator {
4198
5838
  for(let j = 0, l = option.value.fields.length; j < l; j++){
4199
5839
  const field = option.value.fields[j];
4200
5840
  if (field.property != null) {
4201
- const value = field.property.getValue(data[i]);
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);
4202
5845
  if (value != null) {
4203
- field.property.setValue(data[i], field.property.deserialize(value));
5846
+ field.property.setValue(row, field.property.deserialize(value));
4204
5847
  }
4205
5848
  }
4206
5849
  }
@@ -4337,6 +5980,183 @@ class SqlTranslator extends DataTranslator {
4337
5980
 
4338
5981
 
4339
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
+
4340
6160
  ;// CONCATENATED MODULE: ./src/plugins/query/explain.ts
4341
6161
 
4342
6162
  /**
@@ -4351,12 +6171,23 @@ class SqlTranslator extends DataTranslator {
4351
6171
  "map-rename": "A map renames or drops properties, so every option after it refers to names the database does not have.",
4352
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.",
4353
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.",
4354
- "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."
4355
6177
  };
4356
6178
  const EXECUTED_QUERIES_UNSUPPORTED = "This plugin did not report what it executed. It may not support explain.";
4357
- const DATABASE_STEP_DESCRIPTION = "These options are sent to the plugin.";
4358
- const MEMORY_STEP_DESCRIPTION = "Routier runs these over the rows the database returned, after deserializing them.";
4359
- const UNNARROWED_READ_DESCRIPTION = "No option could be pushed down, so the plugin reads the whole collection.";
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";
4360
6191
  /**
4361
6192
  * The reportable shape of one option's value.
4362
6193
  *
@@ -4437,12 +6268,16 @@ const explainedOptionsOf = (options)=>{
4437
6268
  options.forEach((option)=>explained.push(explainedOptionOf(option, index++)));
4438
6269
  return explained;
4439
6270
  };
6271
+ /** Every sentence, whoever decided — the summary reads the same either way. */ const EXPLANATIONS = {
6272
+ ...MEMORY_EXECUTION_EXPLANATIONS,
6273
+ ...DATABASE_EXECUTION_EXPLANATIONS
6274
+ };
4440
6275
  const summarize = (steps)=>{
4441
6276
  const reasons = [];
4442
6277
  let database = 0;
4443
6278
  let memory = 0;
4444
6279
  for (const step of steps){
4445
- if (step.executedIn === "database") {
6280
+ if (isDatabaseStep(step)) {
4446
6281
  database += step.options.length;
4447
6282
  continue;
4448
6283
  }
@@ -4452,7 +6287,7 @@ const summarize = (steps)=>{
4452
6287
  }
4453
6288
  }
4454
6289
  const counts = `${database} ${database === 1 ? "option ran" : "options ran"} in the database, ${memory} ran in memory.`;
4455
- const causes = reasons.map((reason)=>MEMORY_EXECUTION_EXPLANATIONS[reason]).join(" ");
6290
+ const causes = reasons.map((reason)=>EXPLANATIONS[reason]).join(" ");
4456
6291
  return {
4457
6292
  database,
4458
6293
  memory,
@@ -4460,50 +6295,87 @@ const summarize = (steps)=>{
4460
6295
  explanation: causes.length === 0 ? counts : `${counts} ${causes}`
4461
6296
  };
4462
6297
  };
4463
- /**
4464
- * Groups options into consecutive runs that execute in the same place.
4465
- *
4466
- * A step boundary is where execution moves, and a reader has to see the statement as step 1 OF
4467
- * 2 to understand it is not the whole query. Cutting over to memory is a ratchet, so the
4468
- * database options are always a prefix and there are at most two steps.
4469
- *
4470
- * A database step is emitted even when NO option pushed down, because the plugin is dispatched
4471
- * either way — `createQueryPayload` always builds a database event. Without it, the worst case
4472
- * the feature exists to expose reports "0 in the database" while the backend reads the whole
4473
- * table, which is the opposite of the truth.
4474
- */ const toExecutionSteps = (options)=>{
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)=>{
4475
6331
  const steps = [];
4476
6332
  let index = 0;
4477
6333
  options.forEach((option)=>{
4478
6334
  const explained = explainedOptionOf(option, index++);
4479
6335
  const current = steps[steps.length - 1];
4480
- if (current != null && current.executedIn === option.target) {
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) {
4481
6340
  current.options.push(explained);
4482
6341
  return;
4483
6342
  }
4484
- steps.push({
4485
- step: steps.length + 1,
6343
+ steps.push(outcome.reason == null ? {
6344
+ step: 0,
4486
6345
  of: 0,
4487
- executedIn: option.target,
4488
- description: option.target === "database" ? DATABASE_STEP_DESCRIPTION : MEMORY_STEP_DESCRIPTION,
6346
+ executedIn: ranIn,
4489
6347
  options: [
4490
6348
  explained
4491
6349
  ],
4492
- ...option.reason == null ? {} : {
4493
- reason: option.reason,
4494
- explanation: MEMORY_EXECUTION_EXPLANATIONS[option.reason]
4495
- }
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
4496
6362
  });
4497
6363
  });
4498
- if (steps[0]?.executedIn !== "database") {
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") {
4499
6368
  steps.unshift({
4500
6369
  step: 0,
4501
6370
  of: 0,
4502
- executedIn: "database",
4503
- description: UNNARROWED_READ_DESCRIPTION,
4504
- options: []
6371
+ executedIn: ranIn,
6372
+ options: [],
6373
+ executedQueries: []
4505
6374
  });
4506
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)=>{
4507
6379
  for(let i = 0; i < steps.length; i++){
4508
6380
  steps[i].step = i + 1;
4509
6381
  steps[i].of = steps.length;
@@ -4518,10 +6390,11 @@ const summarize = (steps)=>{
4518
6390
  * post-join filter alone in the memory half derives back to `"database"`, and the document
4519
6391
  * would report memory work as having run in the database.
4520
6392
  */ const explainQuery = (options, context)=>{
4521
- if (options.isDerived === true) {
4522
- throw new Error("explainQuery was given a collection produced by split() or splitAt(). Those re-derive " + "execution targets without the options that caused them, so the explanation would report " + "memory work as having run in the database. Pass the collection as it was resolved, " + "before splitting.");
4523
- }
4524
- const executionSteps = toExecutionSteps(options);
6393
+ const executionSteps = numbered(toExecutionSteps(options, {
6394
+ kind: "database",
6395
+ database: context.database,
6396
+ plugin: context.pluginKind
6397
+ }));
4525
6398
  return {
4526
6399
  collection: context.collection,
4527
6400
  database: context.database,
@@ -4548,7 +6421,7 @@ const summarize = (steps)=>{
4548
6421
  // Only the first database step: a plugin reports what IT ran, and everything it ran
4549
6422
  // was sent as one dispatch. Stamping the same statements onto a second database step
4550
6423
  // would claim they ran twice.
4551
- if (step.executedIn !== "database" || attached === true) {
6424
+ if (isDatabaseStep(step) === false || attached === true) {
4552
6425
  return {
4553
6426
  ...step,
4554
6427
  options: [
@@ -4581,8 +6454,47 @@ const summarize = (steps)=>{
4581
6454
  executionSteps
4582
6455
  };
4583
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
+ };
4584
6494
 
4585
6495
  ;// CONCATENATED MODULE: ./src/plugins/query/formatExplanation.ts
6496
+
6497
+
4586
6498
  const OPTION_LABEL_WIDTH = 8;
4587
6499
  const WRAP_WIDTH = 68;
4588
6500
  /** Wraps `text` to `WRAP_WIDTH`, prefixing every line with `indent`. */ const wrap = (text, indent)=>{
@@ -4608,29 +6520,41 @@ const COMPARATOR_SYMBOLS = {
4608
6520
  "less-than": "<",
4609
6521
  "less-than-equals": "<="
4610
6522
  };
4611
- const describeValue = (value)=>{
4612
- if (value == null) {
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) {
4613
6528
  return "?";
4614
6529
  }
4615
- if (value.k === "raw") {
4616
- return typeof value.v === "string" ? `"${value.v}"` : String(value.v);
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;
4617
6538
  }
4618
- if (value.k === "date") {
4619
- return value.v;
6539
+ if ("undefined" in value) {
6540
+ return "undefined";
4620
6541
  }
4621
- if (value.k === "array") {
4622
- return `[${value.v.map(describeValue).join(", ")}]`;
6542
+ if ("regex" in value) {
6543
+ return `/${value.regex.source}/${value.regex.flags}`;
4623
6544
  }
4624
- return value.k === "undefined" ? "undefined" : String(value.v);
6545
+ if ("bigint" in value) {
6546
+ return `${value.bigint}n`;
6547
+ }
6548
+ return value.number;
4625
6549
  };
4626
6550
  /** Renders a serialized expression back to something close to the source predicate. */ const describeExpression = (expression)=>{
4627
6551
  if (expression == null) {
4628
6552
  return "?";
4629
6553
  }
4630
- if (expression.t === "operator") {
6554
+ if (expression.type === "operator") {
4631
6555
  return `${describeExpression(expression.left)} ${expression.operator} ${describeExpression(expression.right)}`;
4632
6556
  }
4633
- if (expression.t === "comparator") {
6557
+ if (expression.type === "comparator") {
4634
6558
  const left = describeExpression(expression.left);
4635
6559
  const right = describeExpression(expression.right);
4636
6560
  const symbol = COMPARATOR_SYMBOLS[expression.comparator];
@@ -4639,13 +6563,24 @@ const describeValue = (value)=>{
4639
6563
  }
4640
6564
  return `${left} ${expression.negated === true ? "!==" : symbol} ${right}`;
4641
6565
  }
4642
- if (expression.t === "property") {
6566
+ if (expression.type === "property") {
4643
6567
  return expression.path;
4644
6568
  }
4645
- if (expression.t === "value") {
6569
+ if (expression.type === "value") {
4646
6570
  return describeValue(expression.value);
4647
6571
  }
4648
- return expression.t === "empty" ? "(no filter)" : "(not parsable)";
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})`;
4649
6584
  };
4650
6585
  const describeOption = (option)=>{
4651
6586
  const detail = option.detail;
@@ -4673,18 +6608,35 @@ const describeOption = (option)=>{
4673
6608
  }
4674
6609
  return "";
4675
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";
4676
6620
  const formatStep = (step, lines)=>{
4677
- const reason = step.reason == null ? "" : ` [${step.reason}]`;
4678
- lines.push(` STEP ${step.step} of ${step.of} — ${step.executedIn}${reason}`);
4679
- lines.push(...wrap(step.description, " "));
4680
- if (step.explanation != null) {
4681
- lines.push(...wrap(step.explanation, " "));
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
+ }
4682
6630
  }
4683
6631
  lines.push("");
4684
6632
  for (const option of step.options){
4685
6633
  lines.push(` ${option.name.padEnd(OPTION_LABEL_WIDTH)} ${describeOption(option)}`.trimEnd());
4686
6634
  }
4687
- for (const executed of step.executedQueries ?? []){
6635
+ if (isDatabaseStep(step) === false) {
6636
+ lines.push("");
6637
+ return;
6638
+ }
6639
+ for (const executed of step.executedQueries){
4688
6640
  lines.push("");
4689
6641
  lines.push(...executed.text.split("\n").map((line)=>` ${line}`));
4690
6642
  if (executed.parameters != null && executed.parameters.length > 0) {
@@ -4733,8 +6685,11 @@ var types_QueryOrdering = /*#__PURE__*/ function(QueryOrdering) {
4733
6685
 
4734
6686
 
4735
6687
 
6688
+
4736
6689
  // EXTERNAL MODULE: ./src/expressions/evaluate.ts
4737
6690
  var evaluate = __webpack_require__(379);
6691
+ // EXTERNAL MODULE: ./src/expressions/fold.ts
6692
+ var fold = __webpack_require__(43);
4738
6693
  ;// CONCATENATED MODULE: ./src/plugins/wire/query.ts
4739
6694
 
4740
6695
 
@@ -4935,7 +6890,7 @@ const serializeQueryOptions = (options)=>{
4935
6890
  }
4936
6891
  case "filter":
4937
6892
  {
4938
- 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));
4939
6894
  options.add("filter", {
4940
6895
  filter: (0,evaluate/* .toStrictPredicate */.wS)(expression),
4941
6896
  expression,
@@ -5619,7 +7574,8 @@ var TrampolinePipeline = __webpack_require__(416);
5619
7574
  if (!(0,assertions/* .isPropertyExpression */.e3)(left) || !(0,assertions/* .isValueExpression */.S6)(right)) {
5620
7575
  return null;
5621
7576
  }
5622
- if (left.property.isKey !== true || left.transformer != null || right.value == null) {
7577
+ // A called property is a CallExpression, so it fails the isPropertyExpression check above
7578
+ if (left.property.isKey !== true || right.value == null) {
5623
7579
  return null;
5624
7580
  }
5625
7581
  return {
@@ -5992,11 +7948,17 @@ class EphemeralDataPlugin {
5992
7948
  * collection to pair it with three rows.
5993
7949
  *
5994
7950
  * `cloned` is in storage shape, so the keys are read by resolved column name.
5995
- */ // No statement to quote — an ephemeral store walks its own records. Said
5996
- // plainly so `.explain()` does not leave a reader wondering whether the
5997
- // plugin simply failed to report. Before the inner side, to match execution order.
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));
5998
7959
  event.executedQueries.push({
5999
- text: `${operation.schema.collectionName}: scanned ${cloned.length} in-memory ${cloned.length === 1 ? "record" : "records"}`
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
6000
7962
  });
6001
7963
  const joinOption = operation.options.getLast("join");
6002
7964
  const outerKeys = joinOption == null ? null : distinctJoinKeys(cloned, joinOption.value.outerKey, joinOption.value.semiJoinKeyThreshold, {
@@ -6171,6 +8133,29 @@ class TelemetryDbPlugin {
6171
8133
  return JSON.stringify(option.value ?? null);
6172
8134
  }
6173
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
+ };
6174
8159
  class CacheDbPlugin {
6175
8160
  plugin;
6176
8161
  max;
@@ -6209,7 +8194,7 @@ class CacheDbPlugin {
6209
8194
  * the next update would be written UNCHECKED with no error anywhere.
6210
8195
  * Pinned by `datastore/src/collections/wrapperStacking.test.ts`.
6211
8196
  */ rebuild(entry) {
6212
- return new entry.construct(structuredClone(entry.value), entry.isTransformed);
8197
+ return new entry.construct(reviveDates(structuredClone(entry.value)), entry.isTransformed);
6213
8198
  }
6214
8199
  query(event, done) {
6215
8200
  const key = this.keyFor(event);
@@ -6232,6 +8217,14 @@ class CacheDbPlugin {
6232
8217
  done(result);
6233
8218
  return;
6234
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
+ }
6235
8228
  this.store(key, result.data);
6236
8229
  // The caller gets a rebuilt value too, not the one just stored, so that mutating
6237
8230
  // the result of a MISS cannot corrupt what the next hit returns.
@@ -6615,6 +8608,7 @@ const DEFAULT_MAX_BATCH_SIZE = 100;
6615
8608
  var __webpack_exports__BatchingDbPlugin = __webpack_exports__.kX;
6616
8609
  var __webpack_exports__CacheDbPlugin = __webpack_exports__.y4;
6617
8610
  var __webpack_exports__ConcurrencyDbPlugin = __webpack_exports__.bX;
8611
+ var __webpack_exports__DATABASE_EXECUTION_EXPLANATIONS = __webpack_exports__.QC;
6618
8612
  var __webpack_exports__DEFAULT_SEMI_JOIN_KEY_THRESHOLD = __webpack_exports__._b;
6619
8613
  var __webpack_exports__DataTranslator = __webpack_exports__.JF;
6620
8614
  var __webpack_exports__EXECUTED_QUERIES_UNSUPPORTED = __webpack_exports__.jE;
@@ -6635,19 +8629,26 @@ var __webpack_exports__applyInnerOptions = __webpack_exports__.VW;
6635
8629
  var __webpack_exports__collectingSink = __webpack_exports__.wN;
6636
8630
  var __webpack_exports__cosineDistance = __webpack_exports__.lO;
6637
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;
6638
8635
  var __webpack_exports__deserializeBulkPersist = __webpack_exports__.Mr;
6639
8636
  var __webpack_exports__deserializePersistResult = __webpack_exports__.Pl;
6640
8637
  var __webpack_exports__deserializeQueryOptions = __webpack_exports__.II;
6641
8638
  var __webpack_exports__distinctJoinKeys = __webpack_exports__.RK;
6642
8639
  var __webpack_exports__executeJoin = __webpack_exports__.m6;
8640
+ var __webpack_exports__executedQueriesOf = __webpack_exports__.fN;
6643
8641
  var __webpack_exports__explainQuery = __webpack_exports__.ae;
6644
8642
  var __webpack_exports__formatExplanation = __webpack_exports__.vZ;
6645
8643
  var __webpack_exports__hashJoin = __webpack_exports__.Bg;
8644
+ var __webpack_exports__isDatabaseStep = __webpack_exports__.yX;
6646
8645
  var __webpack_exports__joinInPlugin = __webpack_exports__.zH;
6647
8646
  var __webpack_exports__loadJoinInnerSide = __webpack_exports__.as;
6648
8647
  var __webpack_exports__loggerSink = __webpack_exports__.qj;
6649
8648
  var __webpack_exports__mappedResultColumns = __webpack_exports__._1;
6650
8649
  var __webpack_exports__nearestBy = __webpack_exports__.iG;
8650
+ var __webpack_exports__parameter = __webpack_exports__.Wi;
8651
+ var __webpack_exports__parameteriseDocument = __webpack_exports__.i1;
6651
8652
  var __webpack_exports__readJoinKey = __webpack_exports__.qy;
6652
8653
  var __webpack_exports__semiJoinFilter = __webpack_exports__.lA;
6653
8654
  var __webpack_exports__serializeBulkPersist = __webpack_exports__.n;
@@ -6656,6 +8657,7 @@ var __webpack_exports__serializeQueryOptions = __webpack_exports__.BL;
6656
8657
  var __webpack_exports__splitSendableOptions = __webpack_exports__.PP;
6657
8658
  var __webpack_exports__toEntityShape = __webpack_exports__.__;
6658
8659
  var __webpack_exports__withExecutedQueries = __webpack_exports__.Kg;
6659
- export { __webpack_exports__BatchingDbPlugin as BatchingDbPlugin, __webpack_exports__CacheDbPlugin as CacheDbPlugin, __webpack_exports__ConcurrencyDbPlugin as ConcurrencyDbPlugin, __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__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__explainQuery as explainQuery, __webpack_exports__formatExplanation as formatExplanation, __webpack_exports__hashJoin as hashJoin, __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__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 };
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 };
6660
8662
 
6661
8663
  //# sourceMappingURL=index.js.map