@routier/core 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +1 -1
  2. package/dist/assertions/index.cjs +19 -8
  3. package/dist/assertions/index.cjs.map +1 -1
  4. package/dist/assertions/index.d.ts +5 -1
  5. package/dist/assertions/index.js +21 -9
  6. package/dist/assertions/index.js.map +1 -1
  7. package/dist/collections/MemoryDataCollection.d.ts +10 -0
  8. package/dist/collections/index.cjs +29 -4
  9. package/dist/collections/index.cjs.map +1 -1
  10. package/dist/collections/index.js +29 -4
  11. package/dist/collections/index.js.map +1 -1
  12. package/dist/expressions/callSource.d.ts +41 -0
  13. package/dist/expressions/evaluate.d.ts +3 -0
  14. package/dist/expressions/fold.d.ts +7 -0
  15. package/dist/expressions/index.cjs +1754 -233
  16. package/dist/expressions/index.cjs.map +1 -1
  17. package/dist/expressions/index.d.ts +2 -0
  18. package/dist/expressions/index.js +1765 -234
  19. package/dist/expressions/index.js.map +1 -1
  20. package/dist/expressions/types.d.ts +45 -26
  21. package/dist/expressions/utils.d.ts +19 -1
  22. package/dist/index.cjs +2429 -363
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.js +2769 -682
  25. package/dist/index.js.map +1 -1
  26. package/dist/performance/index.cjs +6 -4
  27. package/dist/performance/index.cjs.map +1 -1
  28. package/dist/performance/index.js +6 -4
  29. package/dist/performance/index.js.map +1 -1
  30. package/dist/pipeline/index.cjs +6 -4
  31. package/dist/pipeline/index.cjs.map +1 -1
  32. package/dist/pipeline/index.js +6 -4
  33. package/dist/pipeline/index.js.map +1 -1
  34. package/dist/plugins/index.cjs +2323 -316
  35. package/dist/plugins/index.cjs.map +1 -1
  36. package/dist/plugins/index.d.ts +1 -0
  37. package/dist/plugins/index.js +2328 -311
  38. package/dist/plugins/index.js.map +1 -1
  39. package/dist/plugins/query/QueryOptionsCollection.d.ts +38 -10
  40. package/dist/plugins/query/describeFilter.d.ts +83 -0
  41. package/dist/plugins/query/explain.d.ts +71 -9
  42. package/dist/plugins/query/index.d.ts +1 -0
  43. package/dist/plugins/query/join.d.ts +4 -1
  44. package/dist/plugins/query/types.d.ts +36 -4
  45. package/dist/plugins/resultShape.d.ts +35 -0
  46. package/dist/schema/PropertyInfo.d.ts +0 -1
  47. package/dist/schema/index.cjs +7 -14
  48. package/dist/schema/index.cjs.map +1 -1
  49. package/dist/schema/index.js +7 -14
  50. package/dist/schema/index.js.map +1 -1
  51. package/dist/transfer/ChunkEncoder.d.ts +60 -0
  52. package/dist/transfer/decoder.d.ts +29 -0
  53. package/dist/transfer/fillers.d.ts +36 -0
  54. package/dist/transfer/index.cjs +873 -0
  55. package/dist/transfer/index.cjs.map +1 -0
  56. package/dist/transfer/index.d.ts +47 -0
  57. package/dist/transfer/index.js +872 -0
  58. package/dist/transfer/index.js.map +1 -0
  59. package/dist/transfer/plan.d.ts +94 -0
  60. package/dist/transfer/types.d.ts +138 -0
  61. package/dist/utilities/index.cjs +242 -49
  62. package/dist/utilities/index.cjs.map +1 -1
  63. package/dist/utilities/index.js +242 -49
  64. package/dist/utilities/index.js.map +1 -1
  65. package/package.json +9 -1
package/dist/index.cjs CHANGED
@@ -9,6 +9,7 @@ __webpack_require__.d(__webpack_exports__, {
9
9
  assertIsNotNull: () => (assertIsNotNull),
10
10
  assertIsNumber: () => (assertIsNumber),
11
11
  assertString: () => (assertString),
12
+ isCallExpression: () => (isCallExpression),
12
13
  isComparatorExpression: () => (isComparatorExpression),
13
14
  isEmptyExpression: () => (isEmptyExpression),
14
15
  isExpression: () => (isExpression),
@@ -89,6 +90,11 @@ function isObjectWithType(value) {
89
90
  */ function isValueExpression(value) {
90
91
  return isObjectWithType(value) && value.type === "value";
91
92
  }
93
+ /**
94
+ * Type guard: narrows `value` to `CallExpression` when it is an object with `type === "call"`.
95
+ */ function isCallExpression(value) {
96
+ return isObjectWithType(value) && value.type === "call";
97
+ }
92
98
  /**
93
99
  * Type guard: narrows `value` to `EmptyExpression` when it is an object with `type === "empty"`.
94
100
  */ function isEmptyExpression(value) {
@@ -973,10 +979,35 @@ class MemoryDataCollection {
973
979
  }
974
980
  throw new Error(`Id Property '${property.name}' must be string or number, found '${property.type}'`);
975
981
  }
982
+ _dateColumns;
983
+ /** Date columns, by the name a stored record uses. Nested dates live inside a JSON column. */ get dateColumns() {
984
+ if (this._dateColumns == null) {
985
+ this._dateColumns = this.schema.properties.filter((property)=>property.type === types/* .SchemaTypes.Date */.L.Date && property.getAssignmentPath().includes(".") === false).map((property)=>property.getResolvedName());
986
+ }
987
+ return this._dateColumns;
988
+ }
989
+ /**
990
+ * The record this collection keeps.
991
+ *
992
+ * A copy, so a caller holding the entity cannot write into the store afterwards. Dates are held
993
+ * as Dates: a predicate compares a Date, and a stored ISO string never matches one.
994
+ */ toStored(item) {
995
+ const columns = this.dateColumns;
996
+ const stored = {
997
+ ...item
998
+ };
999
+ for(let i = 0, length = columns.length; i < length; i++){
1000
+ const value = stored[columns[i]];
1001
+ if (typeof value === "string") {
1002
+ stored[columns[i]] = new Date(value);
1003
+ }
1004
+ }
1005
+ return stored;
1006
+ }
976
1007
  seed(items) {
977
1008
  for(let i = 0, length = items.length; i < length; i++){
978
1009
  const id = this.resolveIdSet(items[i]);
979
- this.data.set(id.toString(), items[i]);
1010
+ this.data.set(id.toString(), this.toStored(items[i]));
980
1011
  }
981
1012
  }
982
1013
  resolveCurrentIdSet(item) {
@@ -1018,7 +1049,7 @@ class MemoryDataCollection {
1018
1049
  }
1019
1050
  add(item) {
1020
1051
  const id = this.resolveIdSet(item);
1021
- this.data.set(id.toString(), item);
1052
+ this.data.set(id.toString(), this.toStored(item));
1022
1053
  }
1023
1054
  /**
1024
1055
  * Adds a record only when no record with the same key is present. Durable
@@ -1028,7 +1059,7 @@ class MemoryDataCollection {
1028
1059
  const id = this.resolveIdSet(item);
1029
1060
  const key = id.toString();
1030
1061
  if (this.data.has(key) === false) {
1031
- this.data.set(key, item);
1062
+ this.data.set(key, this.toStored(item));
1032
1063
  }
1033
1064
  }
1034
1065
  /**
@@ -1044,7 +1075,7 @@ class MemoryDataCollection {
1044
1075
  }
1045
1076
  update(item) {
1046
1077
  const id = this.resolveCurrentIdSet(item);
1047
- this.data.set(id.toString(), item);
1078
+ this.data.set(id.toString(), this.toStored(item));
1048
1079
  }
1049
1080
  destroy(done) {
1050
1081
  this.nextNumericalIds.clear();
@@ -1145,62 +1176,396 @@ __webpack_require__.d(__webpack_exports__, {
1145
1176
 
1146
1177
 
1147
1178
 
1179
+ },
1180
+ 429(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
1181
+ __webpack_require__.d(__webpack_exports__, {
1182
+ N: () => (CALL_SOURCE),
1183
+ a: () => (renderCallAsJs)
1184
+ });
1185
+ const CALL_SOURCE = {
1186
+ "to-lower-case": {
1187
+ form: "method",
1188
+ name: "toLowerCase"
1189
+ },
1190
+ "to-upper-case": {
1191
+ form: "method",
1192
+ name: "toUpperCase"
1193
+ },
1194
+ "length": {
1195
+ form: "property",
1196
+ name: "length"
1197
+ },
1198
+ "trim": {
1199
+ form: "method",
1200
+ name: "trim"
1201
+ },
1202
+ "trim-start": {
1203
+ form: "method",
1204
+ name: "trimStart"
1205
+ },
1206
+ "trim-end": {
1207
+ form: "method",
1208
+ name: "trimEnd"
1209
+ },
1210
+ "index-of": {
1211
+ form: "method",
1212
+ name: "indexOf"
1213
+ },
1214
+ "substring": {
1215
+ form: "method",
1216
+ name: "substring"
1217
+ },
1218
+ "concat": {
1219
+ form: "method",
1220
+ name: "concat"
1221
+ },
1222
+ "replace": {
1223
+ form: "method",
1224
+ name: "replace"
1225
+ },
1226
+ "replace-all": {
1227
+ form: "method",
1228
+ name: "replaceAll"
1229
+ },
1230
+ "absolute": {
1231
+ form: "function",
1232
+ name: "Math.abs"
1233
+ },
1234
+ "floor": {
1235
+ form: "function",
1236
+ name: "Math.floor"
1237
+ },
1238
+ "ceiling": {
1239
+ form: "function",
1240
+ name: "Math.ceil"
1241
+ },
1242
+ "round": {
1243
+ form: "function",
1244
+ name: "Math.round"
1245
+ },
1246
+ "sign": {
1247
+ form: "function",
1248
+ name: "Math.sign"
1249
+ },
1250
+ "square-root": {
1251
+ form: "function",
1252
+ name: "Math.sqrt"
1253
+ },
1254
+ "add": {
1255
+ form: "operator",
1256
+ symbol: "+"
1257
+ },
1258
+ "subtract": {
1259
+ form: "operator",
1260
+ symbol: "-"
1261
+ },
1262
+ "multiply": {
1263
+ form: "operator",
1264
+ symbol: "*"
1265
+ },
1266
+ "divide": {
1267
+ form: "operator",
1268
+ symbol: "/"
1269
+ },
1270
+ "modulo": {
1271
+ form: "operator",
1272
+ symbol: "%"
1273
+ },
1274
+ "utc-year": {
1275
+ form: "method",
1276
+ name: "getUTCFullYear"
1277
+ },
1278
+ "utc-month": {
1279
+ form: "method",
1280
+ name: "getUTCMonth"
1281
+ },
1282
+ "utc-day-of-month": {
1283
+ form: "method",
1284
+ name: "getUTCDate"
1285
+ },
1286
+ "utc-day-of-week": {
1287
+ form: "method",
1288
+ name: "getUTCDay"
1289
+ },
1290
+ "utc-hour": {
1291
+ form: "method",
1292
+ name: "getUTCHours"
1293
+ },
1294
+ "utc-minute": {
1295
+ form: "method",
1296
+ name: "getUTCMinutes"
1297
+ },
1298
+ "utc-second": {
1299
+ form: "method",
1300
+ name: "getUTCSeconds"
1301
+ },
1302
+ "utc-millisecond": {
1303
+ form: "method",
1304
+ name: "getUTCMilliseconds"
1305
+ },
1306
+ "epoch-ms": {
1307
+ form: "method",
1308
+ name: "getTime"
1309
+ },
1310
+ "to-string": {
1311
+ form: "function",
1312
+ name: "String"
1313
+ },
1314
+ "to-number": {
1315
+ form: "function",
1316
+ name: "Number"
1317
+ },
1318
+ "to-boolean": {
1319
+ form: "function",
1320
+ name: "Boolean"
1321
+ },
1322
+ "type-of": {
1323
+ form: "prefix",
1324
+ keyword: "typeof"
1325
+ },
1326
+ "some": {
1327
+ form: "method",
1328
+ name: "some"
1329
+ },
1330
+ "every": {
1331
+ form: "method",
1332
+ name: "every"
1333
+ },
1334
+ // `Math.pow(a, b)` parses to the same call; `**` is the shorter of the two spellings
1335
+ "power": {
1336
+ form: "operator",
1337
+ symbol: "**"
1338
+ },
1339
+ "bit-and": {
1340
+ form: "operator",
1341
+ symbol: "&"
1342
+ },
1343
+ "bit-or": {
1344
+ form: "operator",
1345
+ symbol: "|"
1346
+ },
1347
+ "bit-xor": {
1348
+ form: "operator",
1349
+ symbol: "^"
1350
+ },
1351
+ "shift-left": {
1352
+ form: "operator",
1353
+ symbol: "<<"
1354
+ },
1355
+ "shift-right": {
1356
+ form: "operator",
1357
+ symbol: ">>"
1358
+ },
1359
+ "shift-right-unsigned": {
1360
+ form: "operator",
1361
+ symbol: ">>>"
1362
+ },
1363
+ "bit-not": {
1364
+ form: "prefix",
1365
+ keyword: "~"
1366
+ },
1367
+ "coalesce": {
1368
+ form: "operator",
1369
+ symbol: "??"
1370
+ },
1371
+ "conditional": {
1372
+ form: "conditional"
1373
+ },
1374
+ "matches": {
1375
+ form: "regex-test"
1376
+ }
1377
+ };
1378
+ /**
1379
+ * A call rendered as the JavaScript that produced it, from operand and argument text already
1380
+ * rendered by the caller.
1381
+ *
1382
+ * Takes strings so one implementation serves a live tree and a serialized one.
1383
+ */ /**
1384
+ * Thunked because rendering a side can record a parameter, and `regex-test` emits its argument
1385
+ * before its operand — so the two orders have to agree.
1386
+ */ const renderCallAsJs = (call, renderOperand, renderArgs)=>{
1387
+ const source = CALL_SOURCE[call];
1388
+ if (source == null) {
1389
+ const operand = renderOperand();
1390
+ return `${operand}.${call}(${renderArgs().join(", ")})`;
1391
+ }
1392
+ if (source.form === "property") {
1393
+ return `${renderOperand()}.${source.name}`;
1394
+ }
1395
+ if (source.form === "regex-test") {
1396
+ const pattern = renderArgs()[0] ?? "?";
1397
+ return `${pattern}.test(${renderOperand()})`;
1398
+ }
1399
+ if (source.form === "method") {
1400
+ const operand = renderOperand();
1401
+ return `${operand}.${source.name}(${renderArgs().join(", ")})`;
1402
+ }
1403
+ if (source.form === "function") {
1404
+ const operand = renderOperand();
1405
+ return `${source.name}(${[
1406
+ operand,
1407
+ ...renderArgs()
1408
+ ].join(", ")})`;
1409
+ }
1410
+ if (source.form === "prefix") {
1411
+ // `~x`, not `~ x` — a bitwise complement is written tight, unlike `typeof`
1412
+ const operand = renderOperand();
1413
+ return source.keyword === "~" ? `${source.keyword}${operand}` : `${source.keyword} ${operand}`;
1414
+ }
1415
+ if (source.form === "conditional") {
1416
+ const operand = renderOperand();
1417
+ const args = renderArgs();
1418
+ return `${operand} ? ${args[0] ?? "?"} : ${args[1] ?? "?"}`;
1419
+ }
1420
+ const operand = renderOperand();
1421
+ return `${[
1422
+ operand,
1423
+ ...renderArgs()
1424
+ ].join(` ${source.symbol} `)}`;
1425
+ };
1426
+
1427
+
1148
1428
  },
1149
1429
  835(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
1150
1430
  __webpack_require__.d(__webpack_exports__, {
1151
1431
  t: () => (EXPRESSION_TYPES)
1152
1432
  });
1153
- const EXPRESSION_TYPES = [
1154
- "operator",
1155
- "comparator",
1156
- "property",
1157
- "value",
1158
- "empty",
1159
- "not-parsable"
1160
- ];
1433
+ /**
1434
+ * A `Record` rather than a list, so adding to `ExpressionType` without adding it here is a compile
1435
+ * error. As a list it was not exhaustive, and `call` was silently missing from `isExpression`.
1436
+ */ const EXPRESSION_TYPE_SET = {
1437
+ "operator": true,
1438
+ "comparator": true,
1439
+ "property": true,
1440
+ "value": true,
1441
+ "call": true,
1442
+ "empty": true,
1443
+ "not-parsable": true
1444
+ };
1445
+ const EXPRESSION_TYPES = Object.keys(EXPRESSION_TYPE_SET);
1161
1446
 
1162
1447
 
1163
1448
  },
1164
1449
  379(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
1165
1450
  __webpack_require__.d(__webpack_exports__, {
1166
1451
  Vu: () => (toPredicate),
1452
+ Vv: () => (operandValue),
1167
1453
  _3: () => (evaluate),
1454
+ gm: () => (UNRESOLVED),
1168
1455
  wS: () => (toStrictPredicate)
1169
1456
  });
1170
1457
  /* import */ var _assertions__rspack_import_0 = __webpack_require__(126);
1171
1458
 
1172
1459
  /** Reads a property or literal operand, or `UNRESOLVED` when the node is not one. */ const UNRESOLVED = Symbol("unresolved");
1173
- const applyTransformer = (value, transformer)=>{
1174
- if (transformer == null) {
1175
- return value;
1460
+ const ARITHMETIC = {
1461
+ "add": (left, right)=>left + right,
1462
+ "subtract": (left, right)=>left - right,
1463
+ "multiply": (left, right)=>left * right,
1464
+ "divide": (left, right)=>left / right,
1465
+ "modulo": (left, right)=>left % right,
1466
+ "power": (left, right)=>left ** right,
1467
+ "bit-and": (left, right)=>left & right,
1468
+ "bit-or": (left, right)=>left | right,
1469
+ "bit-xor": (left, right)=>left ^ right,
1470
+ "shift-left": (left, right)=>left << right,
1471
+ "shift-right": (left, right)=>left >> right,
1472
+ "shift-right-unsigned": (left, right)=>left >>> right
1473
+ };
1474
+ const applyCall = (call, value, args)=>{
1475
+ // Above the guard: a template renders null as "null" in JavaScript, so these two are total.
1476
+ if (call === "to-string") {
1477
+ return String(value);
1478
+ }
1479
+ if (call === "concat") {
1480
+ return [
1481
+ value,
1482
+ ...args
1483
+ ].map(String).join("");
1176
1484
  }
1177
- // A transformer applied to an absent value has no answer, and inventing one ("" for a missing
1178
- // string) is how a filter starts matching rows it should not.
1485
+ // A call applied to an absent value has no answer, and inventing one ("" for a missing string)
1486
+ // is how a filter starts matching rows it should not.
1179
1487
  if (value == null) {
1180
1488
  return UNRESOLVED;
1181
1489
  }
1182
- if (transformer === "to-lower-case") {
1183
- return typeof value === "string" ? value.toLowerCase() : UNRESOLVED;
1184
- }
1185
- if (transformer === "to-upper-case") {
1186
- return typeof value === "string" ? value.toUpperCase() : UNRESOLVED;
1490
+ if (call === "to-lower-case" || call === "to-upper-case") {
1491
+ if (typeof value !== "string") {
1492
+ return UNRESOLVED;
1493
+ }
1494
+ const lower = call === "to-lower-case";
1495
+ if (args.length === 0 || args[0] == null) {
1496
+ return lower ? value.toLowerCase() : value.toUpperCase();
1497
+ }
1498
+ if (typeof args[0] !== "string") {
1499
+ return UNRESOLVED;
1500
+ }
1501
+ try {
1502
+ // An explicit locale is deterministic; dropping it answers a different question in Turkish.
1503
+ return lower ? value.toLocaleLowerCase(args[0]) : value.toLocaleUpperCase(args[0]);
1504
+ } catch {
1505
+ // An invalid language tag throws RangeError; no answer beats the host's default.
1506
+ return UNRESOLVED;
1507
+ }
1187
1508
  }
1188
- if (transformer === "length") {
1509
+ if (call === "length") {
1189
1510
  return typeof value === "string" || Array.isArray(value) ? value.length : UNRESOLVED;
1190
1511
  }
1512
+ if (call === "bit-not") {
1513
+ return typeof value === "number" ? ~value : UNRESOLVED;
1514
+ }
1515
+ if (call === "matches") {
1516
+ if (typeof value !== "string" || !(args[0] instanceof RegExp)) {
1517
+ return UNRESOLVED;
1518
+ }
1519
+ // `test` advances `lastIndex` on a global or sticky pattern, and the pattern is shared with
1520
+ // the cached template, where a source evaluates fresh in JavaScript.
1521
+ return args[0].global || args[0].sticky ? new RegExp(args[0].source, args[0].flags).test(value) : args[0].test(value);
1522
+ }
1523
+ const arithmetic = ARITHMETIC[call];
1524
+ if (arithmetic != null) {
1525
+ return typeof value === "number" && typeof args[0] === "number" ? arithmetic(value, args[0]) : UNRESOLVED;
1526
+ }
1191
1527
  return UNRESOLVED;
1192
1528
  };
1193
- const operand = (expression, row)=>{
1529
+ const operandValue = (expression, row)=>{
1194
1530
  if (expression == null) {
1195
1531
  return UNRESOLVED;
1196
1532
  }
1197
1533
  if ((0,_assertions__rspack_import_0.isValueExpression)(expression)) {
1198
- return applyTransformer(expression.value, expression.transformer);
1534
+ return expression.value;
1199
1535
  }
1200
1536
  if ((0,_assertions__rspack_import_0.isPropertyExpression)(expression)) {
1201
1537
  // Through the PropertyInfo, so a nested path and a `from`-renamed segment resolve the same
1202
1538
  // way every other consumer of the tree resolves them.
1203
- return applyTransformer(expression.property.getValue(row), expression.transformer);
1539
+ return expression.property.getValue(row);
1540
+ }
1541
+ if ((0,_assertions__rspack_import_0.isCallExpression)(expression)) {
1542
+ /**
1543
+ * `??` and `? :` are the two calls whose whole job is to answer when something is absent, so
1544
+ * they run before the guard that refuses an absent operand.
1545
+ */ if (expression.call === "coalesce") {
1546
+ const left = operandValue(expression.expression, row);
1547
+ return left === UNRESOLVED || left == null ? operandValue(expression.arguments[0], row) : left;
1548
+ }
1549
+ if (expression.call === "conditional") {
1550
+ const condition = evaluate(expression.expression, row);
1551
+ if (condition === undefined) {
1552
+ return UNRESOLVED;
1553
+ }
1554
+ return operandValue(expression.arguments[condition === true ? 0 : 1], row);
1555
+ }
1556
+ const inner = operandValue(expression.expression, row);
1557
+ if (inner === UNRESOLVED) {
1558
+ return UNRESOLVED;
1559
+ }
1560
+ const args = [];
1561
+ for (const argument of expression.arguments){
1562
+ const resolved = operandValue(argument, row);
1563
+ if (resolved === UNRESOLVED) {
1564
+ return UNRESOLVED;
1565
+ }
1566
+ args.push(resolved);
1567
+ }
1568
+ return applyCall(expression.call, inner, args);
1204
1569
  }
1205
1570
  return UNRESOLVED;
1206
1571
  };
@@ -1285,8 +1650,8 @@ const evaluateComparator = (comparator, left, right, strict)=>{
1285
1650
  return left === false && right === false ? false : undefined;
1286
1651
  }
1287
1652
  if ((0,_assertions__rspack_import_0.isComparatorExpression)(expression)) {
1288
- const left = operand(expression.left, row);
1289
- const right = operand(expression.right, row);
1653
+ const left = operandValue(expression.left, row);
1654
+ const right = operandValue(expression.right, row);
1290
1655
  if (left === UNRESOLVED || right === UNRESOLVED) {
1291
1656
  return undefined;
1292
1657
  }
@@ -1325,30 +1690,152 @@ const evaluateComparator = (comparator, left, right, strict)=>{
1325
1690
 
1326
1691
 
1327
1692
  },
1328
- 138(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
1693
+ 43(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
1329
1694
  __webpack_require__.d(__webpack_exports__, {
1330
- ComparatorExpression: () => (/* reexport safe */ _types__rspack_import_2.bQ),
1331
- EXPRESSION_TYPES: () => (/* reexport safe */ _constants__rspack_import_4.t),
1332
- EmptyExpression: () => (/* reexport safe */ _types__rspack_import_2.Sm),
1333
- Expression: () => (/* reexport safe */ _types__rspack_import_2.r4),
1334
- NotParsableExpression: () => (/* reexport safe */ _types__rspack_import_2.SC),
1335
- OperatorExpression: () => (/* reexport safe */ _types__rspack_import_2.fw),
1336
- PropertyExpression: () => (/* reexport safe */ _types__rspack_import_2.ep),
1337
- ValueExpression: () => (/* reexport safe */ _types__rspack_import_2.Ko),
1338
- combineExpressions: () => (/* reexport safe */ _parser__rspack_import_1.pg),
1339
- evaluate: () => (/* reexport safe */ _evaluate__rspack_import_0._3),
1340
- forEach: () => (/* reexport safe */ _utils__rspack_import_3.j),
1341
- getProperties: () => (/* reexport safe */ _utils__rspack_import_3.o),
1342
- parseFragment: () => (/* reexport safe */ _parser__rspack_import_1.oH),
1343
- toExpression: () => (/* reexport safe */ _parser__rspack_import_1.MY),
1344
- toPredicate: () => (/* reexport safe */ _evaluate__rspack_import_0.Vu),
1345
- toStrictPredicate: () => (/* reexport safe */ _evaluate__rspack_import_0.wS)
1695
+ F5: () => (foldConstantCalls),
1696
+ Sv: () => (FOLDABLE),
1697
+ br: () => (foldedOperandValue)
1346
1698
  });
1347
- /* import */ var _evaluate__rspack_import_0 = __webpack_require__(379);
1348
- /* import */ var _parser__rspack_import_1 = __webpack_require__(91);
1699
+ /* import */ var _assertions__rspack_import_0 = __webpack_require__(126);
1700
+ /* import */ var _evaluate__rspack_import_3 = __webpack_require__(379);
1349
1701
  /* import */ var _types__rspack_import_2 = __webpack_require__(27);
1350
- /* import */ var _utils__rspack_import_3 = __webpack_require__(63);
1351
- /* import */ var _constants__rspack_import_4 = __webpack_require__(835);
1702
+ /* import */ var _utils__rspack_import_1 = __webpack_require__(63);
1703
+
1704
+
1705
+
1706
+
1707
+ /** Calls fold may compute. Absent means a plugin declines it, so a new call is opt-in. */ const FOLDABLE = new Set([
1708
+ "to-lower-case",
1709
+ "to-upper-case",
1710
+ "length",
1711
+ "bit-not",
1712
+ "matches",
1713
+ "to-string",
1714
+ "concat",
1715
+ "add",
1716
+ "subtract",
1717
+ "multiply",
1718
+ "divide",
1719
+ "modulo",
1720
+ "power",
1721
+ "bit-and",
1722
+ "bit-or",
1723
+ "bit-xor",
1724
+ "shift-left",
1725
+ "shift-right",
1726
+ "shift-right-unsigned",
1727
+ "coalesce",
1728
+ "conditional"
1729
+ ]);
1730
+ /** `String(value)` on an object is the host's rendering — a Date carries its timezone. */ const COERCES_TO_TEXT = new Set([
1731
+ "to-string",
1732
+ "concat"
1733
+ ]);
1734
+ const isFrozenPrimitive = (value)=>value == null || typeof value !== "object";
1735
+ const readsAProperty = (expression)=>{
1736
+ if ((0,_assertions__rspack_import_0.isPropertyExpression)(expression)) {
1737
+ return true;
1738
+ }
1739
+ return (0,_utils__rspack_import_1/* .childrenOf */.LU)(expression).some(readsAProperty);
1740
+ };
1741
+ /** A `conditional` holds a condition where every other call holds a value. */ const isConstant = (call)=>{
1742
+ if (!FOLDABLE.has(call.call) || !call.arguments.every(_assertions__rspack_import_0.isValueExpression)) {
1743
+ return false;
1744
+ }
1745
+ if (COERCES_TO_TEXT.has(call.call) && [
1746
+ call.expression,
1747
+ ...call.arguments
1748
+ ].some((operand)=>(0,_assertions__rspack_import_0.isValueExpression)(operand) && !isFrozenPrimitive(operand.value))) {
1749
+ return false;
1750
+ }
1751
+ return call.call === "conditional" ? !readsAProperty(call.expression) : (0,_assertions__rspack_import_0.isValueExpression)(call.expression);
1752
+ };
1753
+ /** Computes every call whose operand and arguments are all literals. Runs after `bindExpression`. */ const foldConstantCalls = (expression)=>{
1754
+ if ((0,_assertions__rspack_import_0.isCallExpression)(expression)) {
1755
+ const folded = new _types__rspack_import_2/* .CallExpression */.DG({
1756
+ call: expression.call,
1757
+ expression: foldConstantCalls(expression.expression),
1758
+ arguments: expression.arguments.map(foldConstantCalls)
1759
+ });
1760
+ if (!isConstant(folded)) {
1761
+ return folded;
1762
+ }
1763
+ const value = (0,_evaluate__rspack_import_3/* .operandValue */.Vv)(folded, {});
1764
+ return value === _evaluate__rspack_import_3/* .UNRESOLVED */.gm ? folded : new _types__rspack_import_2/* .ValueExpression */.Ko({
1765
+ value
1766
+ });
1767
+ }
1768
+ if ((0,_assertions__rspack_import_0.isComparatorExpression)(expression)) {
1769
+ return new _types__rspack_import_2/* .ComparatorExpression */.bQ({
1770
+ comparator: expression.comparator,
1771
+ negated: expression.negated,
1772
+ strict: expression.strict,
1773
+ left: expression.left == null ? undefined : foldConstantCalls(expression.left),
1774
+ right: expression.right == null ? undefined : foldConstantCalls(expression.right)
1775
+ });
1776
+ }
1777
+ if ((0,_assertions__rspack_import_0.isOperatorExpression)(expression)) {
1778
+ return new _types__rspack_import_2/* .OperatorExpression */.fw({
1779
+ operator: expression.operator,
1780
+ left: expression.left == null ? undefined : foldConstantCalls(expression.left),
1781
+ right: expression.right == null ? undefined : foldConstantCalls(expression.right)
1782
+ });
1783
+ }
1784
+ return expression;
1785
+ };
1786
+ /** The value a literal operand binds as once the calls on it are computed. Throws if it cannot. */ const foldedOperandValue = (operand, calls)=>{
1787
+ if (calls.length === 0) {
1788
+ return operand.value;
1789
+ }
1790
+ // `peelCalls` returns calls innermost first, so the last one evaluates the whole chain.
1791
+ const outermost = calls[calls.length - 1];
1792
+ const value = readsAProperty(outermost) ? _evaluate__rspack_import_3/* .UNRESOLVED */.gm : (0,_evaluate__rspack_import_3/* .operandValue */.Vv)(outermost, {});
1793
+ if (value === _evaluate__rspack_import_3/* .UNRESOLVED */.gm) {
1794
+ throw new Error(`'${calls.map((call)=>call.call).join("', '")}' cannot be computed on the literal ` + `'${String(operand.value)}'.`);
1795
+ }
1796
+ return value;
1797
+ };
1798
+
1799
+
1800
+ },
1801
+ 138(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
1802
+ __webpack_require__.d(__webpack_exports__, {
1803
+ CALL_SOURCE: () => (/* reexport safe */ _callSource__rspack_import_0.N),
1804
+ CallExpression: () => (/* reexport safe */ _types__rspack_import_4.DG),
1805
+ ComparatorExpression: () => (/* reexport safe */ _types__rspack_import_4.bQ),
1806
+ EXPRESSION_TYPES: () => (/* reexport safe */ _constants__rspack_import_6.t),
1807
+ EmptyExpression: () => (/* reexport safe */ _types__rspack_import_4.Sm),
1808
+ Expression: () => (/* reexport safe */ _types__rspack_import_4.r4),
1809
+ FOLDABLE: () => (/* reexport safe */ _fold__rspack_import_2.Sv),
1810
+ NotParsableExpression: () => (/* reexport safe */ _types__rspack_import_4.SC),
1811
+ OperatorExpression: () => (/* reexport safe */ _types__rspack_import_4.fw),
1812
+ PropertyExpression: () => (/* reexport safe */ _types__rspack_import_4.ep),
1813
+ UNRESOLVED: () => (/* reexport safe */ _evaluate__rspack_import_1.gm),
1814
+ ValueExpression: () => (/* reexport safe */ _types__rspack_import_4.Ko),
1815
+ childrenOf: () => (/* reexport safe */ _utils__rspack_import_5.LU),
1816
+ combineExpressions: () => (/* reexport safe */ _parser__rspack_import_3.pg),
1817
+ evaluate: () => (/* reexport safe */ _evaluate__rspack_import_1._3),
1818
+ foldConstantCalls: () => (/* reexport safe */ _fold__rspack_import_2.F5),
1819
+ foldedOperandValue: () => (/* reexport safe */ _fold__rspack_import_2.br),
1820
+ forEach: () => (/* reexport safe */ _utils__rspack_import_5.jJ),
1821
+ getProperties: () => (/* reexport safe */ _utils__rspack_import_5.oY),
1822
+ operandValue: () => (/* reexport safe */ _evaluate__rspack_import_1.Vv),
1823
+ parseFragment: () => (/* reexport safe */ _parser__rspack_import_3.oH),
1824
+ peelCalls: () => (/* reexport safe */ _utils__rspack_import_5.CC),
1825
+ renderCallAsJs: () => (/* reexport safe */ _callSource__rspack_import_0.a),
1826
+ toExpression: () => (/* reexport safe */ _parser__rspack_import_3.MY),
1827
+ toPredicate: () => (/* reexport safe */ _evaluate__rspack_import_1.Vu),
1828
+ toStrictPredicate: () => (/* reexport safe */ _evaluate__rspack_import_1.wS)
1829
+ });
1830
+ /* import */ var _callSource__rspack_import_0 = __webpack_require__(429);
1831
+ /* import */ var _evaluate__rspack_import_1 = __webpack_require__(379);
1832
+ /* import */ var _fold__rspack_import_2 = __webpack_require__(43);
1833
+ /* import */ var _parser__rspack_import_3 = __webpack_require__(91);
1834
+ /* import */ var _types__rspack_import_4 = __webpack_require__(27);
1835
+ /* import */ var _utils__rspack_import_5 = __webpack_require__(63);
1836
+ /* import */ var _constants__rspack_import_6 = __webpack_require__(835);
1837
+
1838
+
1352
1839
 
1353
1840
 
1354
1841
 
@@ -1363,14 +1850,18 @@ __webpack_require__.d(__webpack_exports__, {
1363
1850
  oH: () => (parseFragment),
1364
1851
  pg: () => (combineExpressions)
1365
1852
  });
1366
- /* import */ var _utilities__rspack_import_3 = __webpack_require__(581);
1853
+ /* import */ var _utilities__rspack_import_5 = __webpack_require__(581);
1367
1854
  /* import */ var _assertions__rspack_import_0 = __webpack_require__(126);
1368
1855
  /* import */ var _schema__rspack_import_2 = __webpack_require__(537);
1856
+ /* import */ var _evaluate__rspack_import_3 = __webpack_require__(379);
1857
+ /* import */ var _fold__rspack_import_4 = __webpack_require__(43);
1369
1858
  /* import */ var _types__rspack_import_1 = __webpack_require__(27);
1370
1859
 
1371
1860
 
1372
1861
 
1373
1862
 
1863
+
1864
+
1374
1865
  // Error message constants
1375
1866
  const ERROR_MESSAGES = {
1376
1867
  PROPERTY_NOT_FOUND: (path)=>`Error parsing query, could not find PropertyInfo for path: ${path}`,
@@ -1416,8 +1907,13 @@ const converters = {
1416
1907
  };
1417
1908
  // Longest first so multi-character punctuation wins over its prefixes
1418
1909
  const MULTI_CHARACTER_PUNCTUATION = [
1910
+ ">>>",
1419
1911
  "===",
1420
1912
  "!==",
1913
+ "**",
1914
+ "<<",
1915
+ ">>",
1916
+ "??",
1421
1917
  "?.",
1422
1918
  "&&",
1423
1919
  "||",
@@ -1454,9 +1950,17 @@ const SINGLE_CHARACTER_PUNCTUATION = new Set([
1454
1950
  "?",
1455
1951
  ":",
1456
1952
  "&",
1457
- "|"
1953
+ "|",
1954
+ "^",
1955
+ "~"
1458
1956
  ]);
1459
- const STRING_ESCAPES = {
1957
+ /**
1958
+ * A lookup table keyed by source text.
1959
+ *
1960
+ * Null-prototype: `TRANSFORM_METHODS["toString"]` otherwise returns `Object.prototype.toString`,
1961
+ * which is truthy, and the parser reads a method it does not support as one it does.
1962
+ */ const sourceKeyed = (entries)=>Object.assign(Object.create(null), entries);
1963
+ const STRING_ESCAPES = sourceKeyed({
1460
1964
  "n": "\n",
1461
1965
  "r": "\r",
1462
1966
  "t": "\t",
@@ -1464,6 +1968,24 @@ const STRING_ESCAPES = {
1464
1968
  "f": "\f",
1465
1969
  "v": "\v",
1466
1970
  "0": "\0"
1971
+ });
1972
+ /**
1973
+ * Whether a `/` here opens a regex rather than dividing.
1974
+ *
1975
+ * A regex cannot follow a value. Everything else — the start of the source, an operator, an opening
1976
+ * bracket, a comma — is a position where only a regex makes sense.
1977
+ */ const regexCanStartHere = (tokens)=>{
1978
+ const previous = tokens[tokens.length - 1];
1979
+ if (previous == null) {
1980
+ return true;
1981
+ }
1982
+ if (previous.kind === "number" || previous.kind === "string" || previous.kind === "bigint" || previous.kind === "regex") {
1983
+ return false;
1984
+ }
1985
+ if (previous.kind === "identifier") {
1986
+ return false;
1987
+ }
1988
+ return previous.value !== ")" && previous.value !== "]";
1467
1989
  };
1468
1990
  const isIdentifierStart = (char)=>/[a-zA-Z_$]/.test(char);
1469
1991
  const isIdentifierPart = (char)=>/[a-zA-Z0-9_$]/.test(char);
@@ -1513,6 +2035,51 @@ const isHexDigit = (char)=>isDigit(char) || char >= "a" && char <= "f" || char >
1513
2035
  i++;
1514
2036
  continue;
1515
2037
  }
2038
+ /**
2039
+ * A regex literal, told from division by what came before it.
2040
+ *
2041
+ * `/` after a value — a number, string, identifier, `)` or `]` — is division. Anywhere else
2042
+ * it opens a regex. That is the same rule a JavaScript lexer uses, and it is why `x.a / 2`
2043
+ * and `/^a/.test(x.a)` can share a character.
2044
+ */ if (char === "/" && source[i + 1] !== "/" && source[i + 1] !== "*" && regexCanStartHere(tokens)) {
2045
+ let value = "";
2046
+ let inClass = false;
2047
+ let j = i + 1;
2048
+ while(j < source.length){
2049
+ const current = source[j];
2050
+ if (current === "\\") {
2051
+ value += current + (source[j + 1] ?? "");
2052
+ j += 2;
2053
+ continue;
2054
+ }
2055
+ if (current === "[") {
2056
+ inClass = true;
2057
+ } else if (current === "]") {
2058
+ inClass = false;
2059
+ } else if (current === "/" && inClass === false) {
2060
+ break;
2061
+ } else if (current === "\n") {
2062
+ throw new Error(ERROR_MESSAGES.UNSUPPORTED("unterminated regular expression"));
2063
+ }
2064
+ value += current;
2065
+ j++;
2066
+ }
2067
+ if (j >= source.length) {
2068
+ throw new Error(ERROR_MESSAGES.UNSUPPORTED("unterminated regular expression"));
2069
+ }
2070
+ j++;
2071
+ let flags = "";
2072
+ while(j < source.length && isIdentifierPart(source[j])){
2073
+ flags += source[j];
2074
+ j++;
2075
+ }
2076
+ i = j;
2077
+ tokens.push({
2078
+ kind: "regex",
2079
+ value: `${value}\u0000${flags}`
2080
+ });
2081
+ continue;
2082
+ }
1516
2083
  // Comments
1517
2084
  if (char === "/" && source[i + 1] === "/") {
1518
2085
  while(i < source.length && source[i] !== "\n"){
@@ -1532,6 +2099,8 @@ const isHexDigit = (char)=>isDigit(char) || char >= "a" && char <= "f" || char >
1532
2099
  if (char === "'" || char === "\"" || char === "`") {
1533
2100
  const quote = char;
1534
2101
  let value = "";
2102
+ const chunks = [];
2103
+ const expressions = [];
1535
2104
  i++;
1536
2105
  while(i < source.length && source[i] !== quote){
1537
2106
  if (source[i] === "\\") {
@@ -1546,8 +2115,43 @@ const isHexDigit = (char)=>isDigit(char) || char >= "a" && char <= "f" || char >
1546
2115
  i += 2;
1547
2116
  continue;
1548
2117
  }
1549
- if (quote === "`" && source[i] === "$" && source[i + 1] === "{") {
1550
- throw new Error(ERROR_MESSAGES.UNSUPPORTED("template literal interpolation"));
2118
+ /**
2119
+ * An interpolation. The literal so far becomes a chunk and the expression source is
2120
+ * kept whole, to be parsed by its own stream — nesting means the inner source can
2121
+ * hold anything, including another template.
2122
+ */ if (quote === "`" && source[i] === "$" && source[i + 1] === "{") {
2123
+ let depth = 1;
2124
+ let expression = "";
2125
+ let at = i + 2;
2126
+ while(at < source.length && depth > 0){
2127
+ const current = source[at];
2128
+ if (current === "{") {
2129
+ depth++;
2130
+ } else if (current === "}") {
2131
+ depth--;
2132
+ if (depth === 0) {
2133
+ break;
2134
+ }
2135
+ } else if (current === "'" || current === '"' || current === "`") {
2136
+ const closing = current;
2137
+ expression += current;
2138
+ at++;
2139
+ while(at < source.length && source[at] !== closing){
2140
+ expression += source[at] === "\\" ? source[at] + (source[at + 1] ?? "") : source[at];
2141
+ at += source[at] === "\\" ? 2 : 1;
2142
+ }
2143
+ }
2144
+ expression += source[at];
2145
+ at++;
2146
+ }
2147
+ if (depth > 0) {
2148
+ throw new Error(ERROR_MESSAGES.UNSUPPORTED("unterminated template interpolation"));
2149
+ }
2150
+ chunks.push(value);
2151
+ expressions.push(expression);
2152
+ value = "";
2153
+ i = at + 1;
2154
+ continue;
1551
2155
  }
1552
2156
  value += source[i];
1553
2157
  i++;
@@ -1556,6 +2160,17 @@ const isHexDigit = (char)=>isDigit(char) || char >= "a" && char <= "f" || char >
1556
2160
  throw new Error(ERROR_MESSAGES.UNSUPPORTED("unterminated string literal"));
1557
2161
  }
1558
2162
  i++; // consume closing quote
2163
+ if (expressions.length > 0) {
2164
+ chunks.push(value);
2165
+ tokens.push({
2166
+ kind: "template",
2167
+ value: JSON.stringify({
2168
+ chunks,
2169
+ expressions
2170
+ })
2171
+ });
2172
+ continue;
2173
+ }
1559
2174
  tokens.push({
1560
2175
  kind: "string",
1561
2176
  value
@@ -1599,6 +2214,14 @@ const isHexDigit = (char)=>isDigit(char) || char >= "a" && char <= "f" || char >
1599
2214
  }
1600
2215
  }
1601
2216
  }
2217
+ if (source[i] === "n") {
2218
+ i++;
2219
+ tokens.push({
2220
+ kind: "bigint",
2221
+ value: value.replace(/_/g, "")
2222
+ });
2223
+ continue;
2224
+ }
1602
2225
  tokens.push({
1603
2226
  kind: "number",
1604
2227
  value: value.replace(/_/g, "")
@@ -1649,6 +2272,24 @@ const isHexDigit = (char)=>isDigit(char) || char >= "a" && char <= "f" || char >
1649
2272
  constructor(tokens){
1650
2273
  this.tokens = tokens;
1651
2274
  }
2275
+ /** Inserts tokens at the cursor, bracketed so they keep their own precedence. */ splice(tokens) {
2276
+ const bracketed = [
2277
+ {
2278
+ kind: "punctuation",
2279
+ value: "("
2280
+ },
2281
+ ...tokens,
2282
+ {
2283
+ kind: "punctuation",
2284
+ value: ")"
2285
+ }
2286
+ ];
2287
+ this.tokens = [
2288
+ ...this.tokens.slice(0, this.index),
2289
+ ...bracketed,
2290
+ ...this.tokens.slice(this.index)
2291
+ ];
2292
+ }
1652
2293
  get isAtEnd() {
1653
2294
  return this.index >= this.tokens.length;
1654
2295
  }
@@ -1663,10 +2304,108 @@ const isHexDigit = (char)=>isDigit(char) || char >= "a" && char <= "f" || char >
1663
2304
  this.index++;
1664
2305
  return token;
1665
2306
  }
2307
+ /** The tokens of one statement's value, through the `;` or block end that closes it. */ takeStatementTokens() {
2308
+ const tokens = [];
2309
+ let depth = 0;
2310
+ while(!this.isAtEnd){
2311
+ const token = this.peek();
2312
+ if (token.kind === "punctuation") {
2313
+ if (token.value === "(" || token.value === "[" || token.value === "{") {
2314
+ depth++;
2315
+ } else if (token.value === ")" || token.value === "]" || token.value === "}") {
2316
+ if (depth === 0) {
2317
+ break;
2318
+ }
2319
+ depth--;
2320
+ } else if (token.value === ";" && depth === 0) {
2321
+ this.next();
2322
+ break;
2323
+ }
2324
+ }
2325
+ tokens.push(this.next());
2326
+ }
2327
+ if (tokens.length === 0) {
2328
+ throw new Error(ERROR_MESSAGES.UNSUPPORTED("a declaration with no value"));
2329
+ }
2330
+ return tokens;
2331
+ }
1666
2332
  isPunctuation(value, offset = 0) {
1667
2333
  const token = this.peek(offset);
1668
2334
  return token != null && token.kind === "punctuation" && token.value === value;
1669
2335
  }
2336
+ /**
2337
+ * Whether the group starting here holds a value rather than a condition.
2338
+ *
2339
+ * `(a && b)` is a boolean sub-expression; `(x.name ?? '') === 'ada'` and `(x.name).length` are
2340
+ * values. Only the token after the matching bracket tells them apart, so the decision is made by
2341
+ * looking ahead rather than by parsing one way and catching the failure — a rewind on exception
2342
+ * would swallow a genuine syntax error inside the group and report it as something else.
2343
+ */ groupIsValue() {
2344
+ let depth = 0;
2345
+ let at = this.index;
2346
+ for(; at < this.tokens.length; at++){
2347
+ const token = this.tokens[at];
2348
+ if (token.kind !== "punctuation") {
2349
+ continue;
2350
+ }
2351
+ if (token.value === "(") {
2352
+ depth++;
2353
+ continue;
2354
+ }
2355
+ if (token.value === ")") {
2356
+ depth--;
2357
+ if (depth === 0) {
2358
+ break;
2359
+ }
2360
+ }
2361
+ }
2362
+ const after = this.tokens[at + 1];
2363
+ if (after == null || after.kind !== "punctuation") {
2364
+ return false;
2365
+ }
2366
+ return COMPARISON_OPERATORS[after.value] != null || after.value === "." || after.value === "?.";
2367
+ }
2368
+ /** Whether a `?` sits at the top level of what is left, so this is a conditional. */ holdsConditional() {
2369
+ let depth = 0;
2370
+ for(let at = this.index; at < this.tokens.length; at++){
2371
+ const token = this.tokens[at];
2372
+ if (token.kind !== "punctuation") {
2373
+ continue;
2374
+ }
2375
+ if (token.value === "(" || token.value === "[") {
2376
+ depth++;
2377
+ } else if (token.value === ")" || token.value === "]") {
2378
+ depth--;
2379
+ } else if (token.value === "?" && depth === 0) {
2380
+ return true;
2381
+ }
2382
+ }
2383
+ return false;
2384
+ }
2385
+ /** Whether the group starting here is `( … ? … : … )` rather than a plain value. */ groupHoldsConditional() {
2386
+ let depth = 0;
2387
+ for(let at = this.index; at < this.tokens.length; at++){
2388
+ const token = this.tokens[at];
2389
+ if (token.kind !== "punctuation") {
2390
+ continue;
2391
+ }
2392
+ if (token.value === "(") {
2393
+ depth++;
2394
+ continue;
2395
+ }
2396
+ if (token.value === ")") {
2397
+ depth--;
2398
+ if (depth === 0) {
2399
+ return false;
2400
+ }
2401
+ continue;
2402
+ }
2403
+ if (token.value === "?" && depth === 1) {
2404
+ return true;
2405
+ }
2406
+ }
2407
+ return false;
2408
+ }
1670
2409
  matchPunctuation(value) {
1671
2410
  if (this.isPunctuation(value)) {
1672
2411
  this.index++;
@@ -1680,12 +2419,101 @@ const isHexDigit = (char)=>isDigit(char) || char >= "a" && char <= "f" || char >
1680
2419
  }
1681
2420
  }
1682
2421
  }
1683
- const COMPARATOR_METHODS = {
2422
+ /**
2423
+ * Calls JavaScript binds LOOSER than a comparison.
2424
+ *
2425
+ * This grammar reads a comparison's operands as values, which puts these tighter than they belong:
2426
+ * `x.flags & 6 === 2` is `x.flags & (6 === 2)` in JavaScript and would be read here as
2427
+ * `(x.flags & 6) === 2`. The two answer differently, so an ungrouped one is refused rather than
2428
+ * reinterpreted — the filter then runs in memory against the caller's own function, which is right by
2429
+ * construction. Brackets say which was meant, and JavaScript itself makes an unbracketed `??` mix a
2430
+ * syntax error for the same reason.
2431
+ */ const LOOSER_THAN_COMPARISON = [
2432
+ "bit-and",
2433
+ "bit-or",
2434
+ "bit-xor",
2435
+ "coalesce"
2436
+ ];
2437
+ const needsBrackets = (operand)=>operand.kind === "arithmetic" && operand.grouped !== true && LOOSER_THAN_COMPARISON.includes(operand.call);
2438
+ /** JavaScript precedence: `*`, `/`, `%` bind tighter than `+` and `-`. */ const MULTIPLICATIVE_OPERATORS = sourceKeyed({
2439
+ "*": "multiply",
2440
+ "/": "divide",
2441
+ "%": "modulo"
2442
+ });
2443
+ const ADDITIVE_OPERATORS = sourceKeyed({
2444
+ "+": "add",
2445
+ "-": "subtract"
2446
+ });
2447
+ const SHIFT_OPERATORS = sourceKeyed({
2448
+ "<<": "shift-left",
2449
+ ">>": "shift-right",
2450
+ ">>>": "shift-right-unsigned"
2451
+ });
2452
+ const BITWISE_AND_OPERATORS = sourceKeyed({
2453
+ "&": "bit-and"
2454
+ });
2455
+ const BITWISE_XOR_OPERATORS = sourceKeyed({
2456
+ "^": "bit-xor"
2457
+ });
2458
+ const BITWISE_OR_OPERATORS = sourceKeyed({
2459
+ "|": "bit-or"
2460
+ });
2461
+ const COALESCE_OPERATORS = sourceKeyed({
2462
+ "??": "coalesce"
2463
+ });
2464
+ /** Whether a schema property is reachable in here, which decides which side of a comparison it is. */ const containsProperty = (operand)=>{
2465
+ if (operand.kind === "property") {
2466
+ return true;
2467
+ }
2468
+ if (operand.kind === "conditional") {
2469
+ // A comparison always names a schema property, so the condition alone settles it
2470
+ return true;
2471
+ }
2472
+ return operand.kind === "arithmetic" && (containsProperty(operand.left) || containsProperty(operand.right) || operand.extra != null && containsProperty(operand.extra));
2473
+ };
2474
+ const DECLARATION_KEYWORDS = new Set([
2475
+ "const",
2476
+ "let",
2477
+ "var"
2478
+ ]);
2479
+ /** An operand whose value only a row can supply. */ const UNKNOWN_UNTIL_ROW = Symbol("unknown until row");
2480
+ /** The empty argument slot of a unary call. Compared by identity, so a real `undefined` still counts. */ const NO_ARGUMENT = Object.freeze({
2481
+ kind: "value",
2482
+ value: undefined,
2483
+ transformer: null,
2484
+ locale: null
2485
+ });
2486
+ const noArgument = ()=>NO_ARGUMENT;
2487
+ /** A predicate no row satisfies. Never reaches a tree: no expression node means "match nothing". */ const NEVER = "never";
2488
+ const and = (left, right)=>{
2489
+ if (_types__rspack_import_1/* .Expression.isEmpty */.r4.isEmpty(left)) {
2490
+ return right;
2491
+ }
2492
+ if (_types__rspack_import_1/* .Expression.isEmpty */.r4.isEmpty(right)) {
2493
+ return left;
2494
+ }
2495
+ return new _types__rspack_import_1/* .OperatorExpression */.fw({
2496
+ operator: "&&",
2497
+ left,
2498
+ right
2499
+ });
2500
+ };
2501
+ const or = (left, right)=>{
2502
+ if (_types__rspack_import_1/* .Expression.isEmpty */.r4.isEmpty(left) || _types__rspack_import_1/* .Expression.isEmpty */.r4.isEmpty(right)) {
2503
+ return _types__rspack_import_1/* .Expression.EMPTY */.r4.EMPTY;
2504
+ }
2505
+ return new _types__rspack_import_1/* .OperatorExpression */.fw({
2506
+ operator: "||",
2507
+ left,
2508
+ right
2509
+ });
2510
+ };
2511
+ const COMPARATOR_METHODS = sourceKeyed({
1684
2512
  startsWith: "starts-with",
1685
2513
  endsWith: "ends-with",
1686
2514
  includes: "includes"
1687
- };
1688
- const TRANSFORM_METHODS = {
2515
+ });
2516
+ const TRANSFORM_METHODS = sourceKeyed({
1689
2517
  toLowerCase: {
1690
2518
  transformer: "to-lower-case",
1691
2519
  locale: null
@@ -1702,8 +2530,8 @@ const TRANSFORM_METHODS = {
1702
2530
  transformer: "to-upper-case",
1703
2531
  locale: "en-US"
1704
2532
  }
1705
- };
1706
- const COMPARISON_OPERATORS = {
2533
+ });
2534
+ const COMPARISON_OPERATORS = sourceKeyed({
1707
2535
  "==": {
1708
2536
  comparator: "equals",
1709
2537
  negated: false,
@@ -1744,7 +2572,7 @@ const COMPARISON_OPERATORS = {
1744
2572
  negated: false,
1745
2573
  strict: false
1746
2574
  }
1747
- };
2575
+ });
1748
2576
  const SWAPPED_COMPARATORS = {
1749
2577
  "equals": "equals",
1750
2578
  "greater-than": "less-than",
@@ -1815,14 +2643,14 @@ const resolveParamPath = (paramsName, path, data)=>{
1815
2643
  */ class ExpressionParser {
1816
2644
  schema;
1817
2645
  stream;
1818
- entityName;
2646
+ scope;
1819
2647
  paramsName;
1820
2648
  params;
1821
2649
  /** Set when a param value shaped the tree itself (e.g. x[p.name]) — such templates cannot be cached. */ structurallyDependsOnParams = false;
1822
- constructor(schema, stream, entityName, paramsName, params){
2650
+ constructor(schema, stream, scope, paramsName, params){
1823
2651
  this.schema = schema;
1824
2652
  this.stream = stream;
1825
- this.entityName = entityName;
2653
+ this.scope = scope;
1826
2654
  this.paramsName = paramsName;
1827
2655
  this.params = params;
1828
2656
  }
@@ -1833,6 +2661,180 @@ const resolveParamPath = (paramsName, path, data)=>{
1833
2661
  }
1834
2662
  return expression;
1835
2663
  }
2664
+ parseBody() {
2665
+ if (!this.stream.isPunctuation("{")) {
2666
+ return this.parse();
2667
+ }
2668
+ const answer = this.parseBlock();
2669
+ if (!this.stream.isAtEnd) {
2670
+ throw new Error(ERROR_MESSAGES.UNSUPPORTED(`unexpected token '${this.stream.peek()?.value}'`));
2671
+ }
2672
+ if (answer === NEVER) {
2673
+ throw new Error(ERROR_MESSAGES.UNSUPPORTED("a predicate no row can satisfy"));
2674
+ }
2675
+ return answer;
2676
+ }
2677
+ /** The expression a `{ … }` block answers with. */ parseBlock() {
2678
+ this.stream.expectPunctuation("{");
2679
+ const answer = this.parseStatements();
2680
+ this.stream.expectPunctuation("}");
2681
+ return answer;
2682
+ }
2683
+ /** Statements up to the one that returns. What follows a `return` is never read, as in JavaScript. */ parseStatements() {
2684
+ if (this.stream.isPunctuation("}") || this.stream.isAtEnd) {
2685
+ throw new Error(ERROR_MESSAGES.UNSUPPORTED("a block body that returns nothing"));
2686
+ }
2687
+ const keyword = this.stream.peek();
2688
+ if (keyword == null || keyword.kind !== "identifier") {
2689
+ throw new Error(ERROR_MESSAGES.UNSUPPORTED(`a statement starting '${keyword?.value}'`));
2690
+ }
2691
+ if (DECLARATION_KEYWORDS.has(keyword.value)) {
2692
+ this.declare();
2693
+ return this.parseStatements();
2694
+ }
2695
+ if (keyword.value === "return") {
2696
+ this.stream.next();
2697
+ const answer = this.parseReturnedCondition();
2698
+ this.stream.matchPunctuation(";");
2699
+ return answer;
2700
+ }
2701
+ if (keyword.value === "if") {
2702
+ return this.parseIfStatement();
2703
+ }
2704
+ if (keyword.value === "switch") {
2705
+ return this.parseSwitchStatement();
2706
+ }
2707
+ throw new Error(ERROR_MESSAGES.UNSUPPORTED(`the statement '${keyword.value}'`));
2708
+ }
2709
+ /**
2710
+ * Binds a `const`/`let`/`var` name to the tokens of its initializer — tokens rather than a parsed
2711
+ * expression, so the name works as an operand, an argument, or a call receiver alike.
2712
+ */ declare() {
2713
+ this.stream.next();
2714
+ const name = this.stream.next();
2715
+ if (name.kind !== "identifier") {
2716
+ throw new Error(ERROR_MESSAGES.UNSUPPORTED(`the declaration '${name.value}'`));
2717
+ }
2718
+ this.stream.expectPunctuation("=");
2719
+ this.scope.set(name.value, {
2720
+ kind: "inlined",
2721
+ tokens: this.stream.takeStatementTokens()
2722
+ });
2723
+ }
2724
+ /** `return false` on its own, which no row satisfies, and every other returned condition. */ parseReturnedCondition() {
2725
+ const next = this.stream.peek();
2726
+ const after = this.stream.peek(1);
2727
+ const endsHere = after == null || after.kind === "punctuation" && (after.value === ";" || after.value === "}");
2728
+ if (next != null && next.kind === "identifier" && next.value === "false" && endsHere) {
2729
+ this.stream.next();
2730
+ return NEVER;
2731
+ }
2732
+ return this.parseOr();
2733
+ }
2734
+ parseIfStatement() {
2735
+ this.stream.next();
2736
+ this.stream.expectPunctuation("(");
2737
+ const condition = this.parseOr();
2738
+ this.stream.expectPunctuation(")");
2739
+ const whenTrue = this.parseBranch();
2740
+ if (this.stream.peek()?.value === "else") {
2741
+ this.stream.next();
2742
+ return this.either(condition, whenTrue, this.parseBranch());
2743
+ }
2744
+ // Without an `else`, the statements after the `if` are the other branch
2745
+ return this.either(condition, whenTrue, this.parseStatements());
2746
+ }
2747
+ /** One arm of an `if`: a block, or a single statement. */ parseBranch() {
2748
+ return this.stream.isPunctuation("{") ? this.parseBlock() : this.parseStatements();
2749
+ }
2750
+ /** A `switch` over one subject, as the disjunction of its cases. */ parseSwitchStatement() {
2751
+ this.stream.next();
2752
+ this.stream.expectPunctuation("(");
2753
+ const subject = this.parseValue();
2754
+ this.stream.expectPunctuation(")");
2755
+ this.stream.expectPunctuation("{");
2756
+ let matching = null;
2757
+ let pending = [];
2758
+ let everyLabel = [];
2759
+ let byDefault = null;
2760
+ let anyCaseBroke = false;
2761
+ while(!this.stream.matchPunctuation("}")){
2762
+ const label = this.stream.next();
2763
+ if (label.kind !== "identifier" || label.value !== "case" && label.value !== "default") {
2764
+ throw new Error(ERROR_MESSAGES.UNSUPPORTED(`'${label.value}' inside a switch`));
2765
+ }
2766
+ if (label.value === "case") {
2767
+ const test = this.buildComparison(subject, COMPARISON_OPERATORS["==="], this.parseValue());
2768
+ pending.push(test);
2769
+ everyLabel.push(test);
2770
+ }
2771
+ this.stream.expectPunctuation(":");
2772
+ // `case 'a':` with no body of its own runs the next case's body
2773
+ if (this.stream.peek()?.value === "case" || this.stream.peek()?.value === "default") {
2774
+ continue;
2775
+ }
2776
+ if (this.stream.peek()?.value === "break") {
2777
+ this.stream.next();
2778
+ this.stream.matchPunctuation(";");
2779
+ anyCaseBroke = true;
2780
+ pending = [];
2781
+ continue;
2782
+ }
2783
+ const body = this.parseCaseBody();
2784
+ if (label.value === "default") {
2785
+ byDefault = body === NEVER ? null : body;
2786
+ continue;
2787
+ }
2788
+ if (body !== NEVER && pending.length > 0) {
2789
+ const reached = pending.reduce((left, right)=>or(left, right));
2790
+ const term = _types__rspack_import_1/* .Expression.isEmpty */.r4.isEmpty(body) ? reached : and(reached, body);
2791
+ matching = matching == null ? term : or(matching, term);
2792
+ }
2793
+ pending = [];
2794
+ }
2795
+ // Falling out of the switch continues after it, so the statements there are the default too
2796
+ const afterSwitch = byDefault == null && !this.stream.isPunctuation("}") && !this.stream.isAtEnd ? this.parseStatements() : NEVER;
2797
+ if (afterSwitch !== NEVER) {
2798
+ // A `break` also continues after the switch, so its case would take that answer rather
2799
+ // than none — a distinction this rewrite cannot carry
2800
+ if (anyCaseBroke) {
2801
+ throw new Error(ERROR_MESSAGES.UNSUPPORTED("a switch that breaks and then falls into more statements"));
2802
+ }
2803
+ byDefault = afterSwitch;
2804
+ }
2805
+ // A `default` runs only when every case failed, wherever it was written
2806
+ if (byDefault != null) {
2807
+ const noCaseMatched = everyLabel.length === 0 ? byDefault : and(this.negateExpression(everyLabel.reduce((left, right)=>or(left, right))), byDefault);
2808
+ matching = matching == null ? noCaseMatched : or(matching, noCaseMatched);
2809
+ }
2810
+ return matching ?? NEVER;
2811
+ }
2812
+ /** One case body, and the `break` that may follow its `return`. */ parseCaseBody() {
2813
+ const answer = this.parseStatements();
2814
+ if (this.stream.peek()?.value === "break") {
2815
+ this.stream.next();
2816
+ this.stream.matchPunctuation(";");
2817
+ }
2818
+ return answer;
2819
+ }
2820
+ /**
2821
+ * The predicate an `if`/`else` answers: `(condition && whenTrue) || (!condition && whenFalse)`,
2822
+ * with each case below that form after a constant branch cancels out.
2823
+ */ either(condition, whenTrue, whenFalse) {
2824
+ if (whenTrue === NEVER) {
2825
+ return whenFalse === NEVER ? NEVER : and(this.negateExpression(condition), whenFalse);
2826
+ }
2827
+ if (whenFalse === NEVER) {
2828
+ return and(condition, whenTrue);
2829
+ }
2830
+ if (_types__rspack_import_1/* .Expression.isEmpty */.r4.isEmpty(whenTrue)) {
2831
+ return or(condition, whenFalse);
2832
+ }
2833
+ if (_types__rspack_import_1/* .Expression.isEmpty */.r4.isEmpty(whenFalse)) {
2834
+ return or(this.negateExpression(condition), whenTrue);
2835
+ }
2836
+ return or(and(condition, whenTrue), and(this.negateExpression(condition), whenFalse));
2837
+ }
1836
2838
  // || binds loosest, so it sits at the root of the parse
1837
2839
  parseOr() {
1838
2840
  let left = this.parseAnd();
@@ -1880,10 +2882,18 @@ const resolveParamPath = (paramsName, path, data)=>{
1880
2882
  /**
1881
2883
  * Applies `!` to an already-parsed expression: comparators flip their
1882
2884
  * negated flag, compound expressions distribute via De Morgan's laws.
2885
+ *
2886
+ * Builds a new tree rather than flipping the flag in place, because an `if` uses its condition
2887
+ * twice — once negated — and a shared node would carry the flip into both branches.
1883
2888
  */ negateExpression(expression) {
1884
2889
  if (expression instanceof _types__rspack_import_1/* .ComparatorExpression */.bQ) {
1885
- expression.negated = !expression.negated;
1886
- return expression;
2890
+ return new _types__rspack_import_1/* .ComparatorExpression */.bQ({
2891
+ comparator: expression.comparator,
2892
+ negated: !expression.negated,
2893
+ strict: expression.strict,
2894
+ left: expression.left,
2895
+ right: expression.right
2896
+ });
1887
2897
  }
1888
2898
  if (expression instanceof _types__rspack_import_1/* .OperatorExpression */.fw && expression.left != null && expression.right != null) {
1889
2899
  return new _types__rspack_import_1/* .OperatorExpression */.fw({
@@ -1895,25 +2905,125 @@ const resolveParamPath = (paramsName, path, data)=>{
1895
2905
  throw new Error(ERROR_MESSAGES.UNSUPPORTED("'!' on this expression"));
1896
2906
  }
1897
2907
  parseComparison() {
1898
- // Parenthesized group
1899
- if (this.stream.matchPunctuation("(")) {
2908
+ /**
2909
+ * A parenthesised group is either a boolean sub-expression or a VALUE — `(a && b)` against
2910
+ * `(x.name ?? '') === 'ada'` — and which one it is is only known at the closing bracket, by
2911
+ * what follows. So the boolean reading is tried first and rewound if a comparator turns up.
2912
+ */ if (this.stream.isPunctuation("(") && this.stream.groupIsValue() === false) {
2913
+ this.stream.next();
1900
2914
  const expression = this.parseOr();
1901
2915
  this.stream.expectPunctuation(")");
1902
- const trailing = this.stream.peek();
1903
- if (trailing != null && trailing.kind === "punctuation" && COMPARISON_OPERATORS[trailing.value] != null) {
1904
- throw new Error(ERROR_MESSAGES.UNSUPPORTED("comparison against a parenthesized expression"));
1905
- }
1906
2916
  return expression;
1907
2917
  }
1908
- const left = this.parseOperand();
2918
+ const left = this.parseValue();
1909
2919
  const operatorToken = this.stream.peek();
1910
2920
  if (operatorToken != null && operatorToken.kind === "punctuation" && COMPARISON_OPERATORS[operatorToken.value] != null) {
1911
2921
  this.stream.next();
1912
- const right = this.parseOperand();
2922
+ const right = this.parseValue();
1913
2923
  return this.buildComparison(left, COMPARISON_OPERATORS[operatorToken.value], right);
1914
2924
  }
1915
2925
  return this.buildStandalone(left);
1916
2926
  }
2927
+ /**
2928
+ * A value, at JavaScript's precedence.
2929
+ *
2930
+ * Lowest first: the conditional operator, then nullish coalescing, then the bitwise levels, then
2931
+ * the shifts, then the arithmetic. Comparison sits between the shifts and the bitwise levels in
2932
+ * JavaScript, but a comparison is a boolean and is handled by `parseComparison` above, so this
2933
+ * chain skips it — a bitwise operand here is always a value.
2934
+ */ /**
2935
+ * An operand from its own source, sharing this parser's schema and parameter names.
2936
+ *
2937
+ * A structural dependence found inside propagates outward: the template it belongs to cannot be
2938
+ * cached either.
2939
+ */ parseNested(source) {
2940
+ const nested = new ExpressionParser(this.schema, new TokenStream(tokenize(source)), this.scope, this.paramsName, this.params);
2941
+ const operand = nested.parseInterpolation();
2942
+ // Leftover tokens mean the interpolation held something this reads only part of. Silently
2943
+ // keeping the part it understood is the worst outcome available: `${x.age > 5 ? "a" : "b"}`
2944
+ // would become `x.age`, and the filter would answer a question nobody asked.
2945
+ if (nested.stream.isAtEnd === false) {
2946
+ throw new Error(ERROR_MESSAGES.UNSUPPORTED("an interpolation this parser reads only part of"));
2947
+ }
2948
+ if (nested.structurallyDependsOnParams === true) {
2949
+ this.structurallyDependsOnParams = true;
2950
+ }
2951
+ return operand;
2952
+ }
2953
+ /**
2954
+ * The whole of one `${…}`.
2955
+ *
2956
+ * A conditional is read here rather than in `parseValue`, because an interpolation is the one
2957
+ * place a conditional appears without brackets around it.
2958
+ */ parseInterpolation() {
2959
+ if (this.stream.holdsConditional()) {
2960
+ const condition = this.parseOr();
2961
+ this.stream.expectPunctuation("?");
2962
+ const whenTrue = this.parseValue();
2963
+ this.stream.expectPunctuation(":");
2964
+ const whenFalse = this.parseValue();
2965
+ return {
2966
+ kind: "conditional",
2967
+ condition,
2968
+ whenTrue,
2969
+ whenFalse
2970
+ };
2971
+ }
2972
+ return this.parseValue();
2973
+ }
2974
+ parseValue() {
2975
+ return this.parseCoalesce();
2976
+ }
2977
+ parseCoalesce() {
2978
+ return this.parseBinary(COALESCE_OPERATORS, ()=>this.parseBitwiseOr());
2979
+ }
2980
+ parseBitwiseOr() {
2981
+ return this.parseBinary(BITWISE_OR_OPERATORS, ()=>this.parseBitwiseXor());
2982
+ }
2983
+ parseBitwiseXor() {
2984
+ return this.parseBinary(BITWISE_XOR_OPERATORS, ()=>this.parseBitwiseAnd());
2985
+ }
2986
+ parseBitwiseAnd() {
2987
+ return this.parseBinary(BITWISE_AND_OPERATORS, ()=>this.parseShift());
2988
+ }
2989
+ parseShift() {
2990
+ return this.parseBinary(SHIFT_OPERATORS, ()=>this.parseAdditive());
2991
+ }
2992
+ parseAdditive() {
2993
+ return this.parseBinary(ADDITIVE_OPERATORS, ()=>this.parseMultiplicative());
2994
+ }
2995
+ parseMultiplicative() {
2996
+ return this.parseBinary(MULTIPLICATIVE_OPERATORS, ()=>this.parseExponent());
2997
+ }
2998
+ /** `**` is RIGHT-associative: `2 ** 3 ** 2` is 2 ** 9, not 8 ** 2. */ parseExponent() {
2999
+ const left = this.parseOperand();
3000
+ if (this.stream.isPunctuation("**") === false) {
3001
+ return left;
3002
+ }
3003
+ this.stream.next();
3004
+ return {
3005
+ kind: "arithmetic",
3006
+ call: "power",
3007
+ left,
3008
+ right: this.parseExponent()
3009
+ };
3010
+ }
3011
+ /** Left-associative, so `a - b - c` is `(a - b) - c` rather than `a - (b - c)`. */ parseBinary(operators, next) {
3012
+ let left = next();
3013
+ for(;;){
3014
+ const token = this.stream.peek();
3015
+ if (token == null || token.kind !== "punctuation" || operators[token.value] == null) {
3016
+ return left;
3017
+ }
3018
+ this.stream.next();
3019
+ left = {
3020
+ kind: "arithmetic",
3021
+ call: operators[token.value],
3022
+ left,
3023
+ right: next()
3024
+ };
3025
+ }
3026
+ }
1917
3027
  parseOperand() {
1918
3028
  const token = this.stream.peek();
1919
3029
  if (token == null) {
@@ -1937,6 +3047,131 @@ const resolveParamPath = (paramsName, path, data)=>{
1937
3047
  locale: null
1938
3048
  };
1939
3049
  }
3050
+ // A parenthesised VALUE — `(x.price & 1)`, `(x.name ?? '')`. The boolean reading of a group
3051
+ // is handled in parseComparison; by the time an operand sees one it is arithmetic.
3052
+ if (token.kind === "punctuation" && token.value === "(") {
3053
+ const conditional = this.stream.groupHoldsConditional();
3054
+ this.stream.next();
3055
+ if (conditional === true) {
3056
+ const condition = this.parseOr();
3057
+ this.stream.expectPunctuation("?");
3058
+ const whenTrue = this.parseValue();
3059
+ this.stream.expectPunctuation(":");
3060
+ const whenFalse = this.parseValue();
3061
+ this.stream.expectPunctuation(")");
3062
+ return {
3063
+ kind: "conditional",
3064
+ condition,
3065
+ whenTrue,
3066
+ whenFalse
3067
+ };
3068
+ }
3069
+ const inner = this.parseValue();
3070
+ this.stream.expectPunctuation(")");
3071
+ const grouped = inner.kind === "arithmetic" ? {
3072
+ ...inner,
3073
+ grouped: true
3074
+ } : inner;
3075
+ return this.withGroupCall(grouped);
3076
+ }
3077
+ /**
3078
+ * A template with interpolation, folded into `concat`.
3079
+ *
3080
+ * Each `${…}` was kept as source by the tokenizer and is parsed by its own stream, so it can
3081
+ * hold anything an operand can — a property, a param, arithmetic, another template. Empty
3082
+ * chunks are dropped: `${a}${b}` is two operands, not two operands and three empty strings.
3083
+ */ if (token.kind === "template") {
3084
+ this.stream.next();
3085
+ const { chunks, expressions } = JSON.parse(token.value);
3086
+ const pieces = [];
3087
+ for(let at = 0; at < chunks.length; at++){
3088
+ if (chunks[at].length > 0) {
3089
+ pieces.push({
3090
+ kind: "value",
3091
+ value: chunks[at],
3092
+ transformer: null,
3093
+ locale: null
3094
+ });
3095
+ }
3096
+ if (at < expressions.length) {
3097
+ pieces.push(this.parseNested(expressions[at]));
3098
+ }
3099
+ }
3100
+ if (pieces.length === 0) {
3101
+ return {
3102
+ kind: "value",
3103
+ value: "",
3104
+ transformer: null,
3105
+ locale: null
3106
+ };
3107
+ }
3108
+ // One piece and no chunk means no concat to do the coercion, so the conversion has to be
3109
+ // explicit: `` `${x.age}` `` is the STRING "9", not the number 9.
3110
+ if (pieces.length === 1) {
3111
+ const only = pieces[0];
3112
+ const alreadyText = only.kind === "value" && typeof only.value === "string";
3113
+ return alreadyText ? only : {
3114
+ kind: "arithmetic",
3115
+ call: "to-string",
3116
+ left: only,
3117
+ right: noArgument()
3118
+ };
3119
+ }
3120
+ return pieces.reduce((left, right)=>({
3121
+ kind: "arithmetic",
3122
+ call: "concat",
3123
+ left,
3124
+ right
3125
+ }));
3126
+ }
3127
+ if (token.kind === "bigint") {
3128
+ this.stream.next();
3129
+ return {
3130
+ kind: "value",
3131
+ value: BigInt(token.value),
3132
+ transformer: null,
3133
+ locale: null
3134
+ };
3135
+ }
3136
+ if (token.kind === "regex") {
3137
+ this.stream.next();
3138
+ const [source, flags] = token.value.split("\u0000");
3139
+ const pattern = {
3140
+ kind: "value",
3141
+ value: new RegExp(source, flags),
3142
+ transformer: null,
3143
+ locale: null
3144
+ };
3145
+ // `/^a/.test(x.name)` — the pattern is the literal, the subject is the argument, and the
3146
+ // tree puts them the other way round: the property is what the call applies to.
3147
+ if (this.stream.isPunctuation(".")) {
3148
+ const method = this.stream.peek(1);
3149
+ if (method != null && method.kind === "identifier" && method.value === "test") {
3150
+ this.stream.next();
3151
+ this.stream.next();
3152
+ this.stream.expectPunctuation("(");
3153
+ const subject = this.parseValue();
3154
+ this.stream.expectPunctuation(")");
3155
+ return {
3156
+ kind: "arithmetic",
3157
+ call: "matches",
3158
+ left: subject,
3159
+ right: pattern
3160
+ };
3161
+ }
3162
+ }
3163
+ return pattern;
3164
+ }
3165
+ if (token.kind === "punctuation" && token.value === "~") {
3166
+ this.stream.next();
3167
+ // Unary, so the tree carries the operand and no argument
3168
+ return {
3169
+ kind: "arithmetic",
3170
+ call: "bit-not",
3171
+ left: this.parseOperand(),
3172
+ right: noArgument()
3173
+ };
3174
+ }
1940
3175
  if (token.kind === "punctuation" && token.value === "-") {
1941
3176
  this.stream.next();
1942
3177
  const numberToken = this.stream.next();
@@ -1994,6 +3229,9 @@ const resolveParamPath = (paramsName, path, data)=>{
1994
3229
  if (argument.kind === "method-call") {
1995
3230
  throw new Error(ERROR_MESSAGES.UNSUPPORTED("nested method call inside .includes()"));
1996
3231
  }
3232
+ if (argument.kind === "arithmetic" || argument.kind === "conditional") {
3233
+ throw new Error(ERROR_MESSAGES.UNSUPPORTED("arithmetic inside .includes()"));
3234
+ }
1997
3235
  return {
1998
3236
  kind: "method-call",
1999
3237
  target: array,
@@ -2039,16 +3277,17 @@ const resolveParamPath = (paramsName, path, data)=>{
2039
3277
  locale: null
2040
3278
  };
2041
3279
  }
2042
- if (root === this.entityName) {
2043
- return this.parseChain({
2044
- kind: "property",
2045
- root
2046
- });
2047
- }
2048
- if (this.paramsName != null && root === this.paramsName) {
3280
+ const binding = this.scope.get(root);
3281
+ if (binding != null) {
3282
+ if (binding.kind === "inlined") {
3283
+ this.stream.splice(binding.tokens);
3284
+ return this.parseOperand();
3285
+ }
2049
3286
  return this.parseChain({
2050
- kind: "param",
2051
- root
3287
+ kind: binding.kind,
3288
+ path: [
3289
+ ...binding.path
3290
+ ]
2052
3291
  });
2053
3292
  }
2054
3293
  // A bare variable from the outer scope — its value cannot be derived from source text
@@ -2058,7 +3297,7 @@ const resolveParamPath = (paramsName, path, data)=>{
2058
3297
  * Parses the segments after an entity/params root: dot access, bracket
2059
3298
  * access, transform methods and comparator methods.
2060
3299
  */ parseChain(options) {
2061
- const path = [];
3300
+ const path = options.path;
2062
3301
  let transformer = null;
2063
3302
  let locale = null;
2064
3303
  while(true){
@@ -2084,6 +3323,9 @@ const resolveParamPath = (paramsName, path, data)=>{
2084
3323
  if (argument.kind === "method-call") {
2085
3324
  throw new Error(ERROR_MESSAGES.UNSUPPORTED(`nested method call inside .${method}()`));
2086
3325
  }
3326
+ if (argument.kind === "arithmetic" || argument.kind === "conditional") {
3327
+ throw new Error(ERROR_MESSAGES.UNSUPPORTED(`arithmetic inside .${method}()`));
3328
+ }
2087
3329
  return {
2088
3330
  kind: "method-call",
2089
3331
  target: this.resolveChain(options.kind, path, transformer, locale),
@@ -2120,12 +3362,15 @@ const resolveParamPath = (paramsName, path, data)=>{
2120
3362
  // docs/mutation-backlog.md) — every mutation of this four-conjunct guard reroutes
2121
3363
  // bracket access between two paths that both collapse to NOT_PARSABLE; the
2122
3364
  // experiment recorded there aimed 30 tests at this line and killed none.
2123
- if (kind === "property" && token.kind === "identifier" && this.paramsName != null && token.value === this.paramsName) {
2124
- const paramPath = [];
3365
+ const binding = token.kind === "identifier" ? this.scope.get(token.value) : undefined;
3366
+ if (kind === "property" && binding != null && binding.kind === "param") {
3367
+ const paramPath = [
3368
+ ...binding.path
3369
+ ];
2125
3370
  while(this.stream.matchPunctuation(".") || this.stream.matchPunctuation("?.")){
2126
3371
  paramPath.push(this.stream.next().value);
2127
3372
  }
2128
- const resolved = resolveParamPath(this.paramsName, paramPath, this.params);
3373
+ const resolved = resolveParamPath(this.paramsName ?? token.value, paramPath, this.params);
2129
3374
  if (typeof resolved !== "string") {
2130
3375
  throw new ParamDependentParseError(ERROR_MESSAGES.PROPERTY_NOT_FOUND(paramPath.join(".")));
2131
3376
  }
@@ -2178,6 +3423,67 @@ const resolveParamPath = (paramsName, path, data)=>{
2178
3423
  locale
2179
3424
  };
2180
3425
  }
3426
+ /**
3427
+ * A call on a parenthesised value: `(x.name).toLowerCase()`, `(x.age + 1).length`. Any operand can
3428
+ * receive one here, unlike a property chain, which carries at most one transform.
3429
+ */ withGroupCall(operand) {
3430
+ let receiver = operand;
3431
+ while(this.stream.isPunctuation(".") || this.stream.isPunctuation("?.")){
3432
+ const segment = this.stream.peek(1);
3433
+ if (segment == null || segment.kind !== "identifier") {
3434
+ break;
3435
+ }
3436
+ if (segment.value === "length" && !this.stream.isPunctuation("(", 2)) {
3437
+ this.stream.next();
3438
+ this.stream.next();
3439
+ receiver = {
3440
+ kind: "arithmetic",
3441
+ call: "length",
3442
+ left: receiver,
3443
+ right: noArgument()
3444
+ };
3445
+ continue;
3446
+ }
3447
+ const transform = TRANSFORM_METHODS[segment.value];
3448
+ if (transform != null) {
3449
+ this.stream.next();
3450
+ this.stream.next();
3451
+ this.stream.expectPunctuation("(");
3452
+ this.stream.expectPunctuation(")");
3453
+ receiver = {
3454
+ kind: "arithmetic",
3455
+ call: transform.transformer,
3456
+ left: receiver,
3457
+ right: transform.locale == null ? noArgument() : {
3458
+ kind: "value",
3459
+ value: transform.locale,
3460
+ transformer: null,
3461
+ locale: null
3462
+ }
3463
+ };
3464
+ continue;
3465
+ }
3466
+ // A comparator method needs a property target, which only an ungrouped chain produces
3467
+ if (COMPARATOR_METHODS[segment.value] != null && receiver.kind === "property") {
3468
+ this.stream.next();
3469
+ this.stream.next();
3470
+ this.stream.expectPunctuation("(");
3471
+ const argument = this.parseOperand();
3472
+ this.stream.expectPunctuation(")");
3473
+ if (argument.kind !== "property" && argument.kind !== "value" && argument.kind !== "param") {
3474
+ throw new Error(ERROR_MESSAGES.UNSUPPORTED(`'.${segment.value}()' on that argument`));
3475
+ }
3476
+ return {
3477
+ kind: "method-call",
3478
+ target: receiver,
3479
+ method: segment.value,
3480
+ argument
3481
+ };
3482
+ }
3483
+ break;
3484
+ }
3485
+ return receiver;
3486
+ }
2181
3487
  withValueTransformer(operand) {
2182
3488
  if (this.stream.isPunctuation(".")) {
2183
3489
  const method = this.stream.peek(1);
@@ -2211,12 +3517,22 @@ const resolveParamPath = (paramsName, path, data)=>{
2211
3517
  if (right.kind === "method-call") {
2212
3518
  throw new Error(ERROR_MESSAGES.UNSUPPORTED("method call on the right side of a comparison"));
2213
3519
  }
2214
- if (left.kind === "property" && right.kind === "property") {
2215
- // Casing transformers are only valid with string-matching comparators,
2216
- // which cannot produce a property-to-property comparison
2217
- if (left.transformer === "to-lower-case" || left.transformer === "to-upper-case" || right.transformer === "to-lower-case" || right.transformer === "to-upper-case") {
2218
- throw new Error(ERROR_MESSAGES.UNSUPPORTED("transform method outside of startsWith/endsWith/includes"));
3520
+ if (needsBrackets(left) || needsBrackets(right)) {
3521
+ throw new Error(ERROR_MESSAGES.UNSUPPORTED("a bitwise or nullish operator compared without brackets, which JavaScript reads the other way round"));
3522
+ }
3523
+ if (left.kind === "arithmetic" || right.kind === "arithmetic" || left.kind === "conditional" || right.kind === "conditional") {
3524
+ if (containsProperty(left) === false && containsProperty(right) === false) {
3525
+ throw new Error(ERROR_MESSAGES.UNSUPPORTED("arithmetic that references no schema property"));
2219
3526
  }
3527
+ return new _types__rspack_import_1/* .ComparatorExpression */.bQ({
3528
+ comparator: operator.comparator,
3529
+ negated: operator.negated,
3530
+ strict: operator.strict,
3531
+ left: this.createOperandExpression(left),
3532
+ right: this.createOperandExpression(right)
3533
+ });
3534
+ }
3535
+ if (left.kind === "property" && right.kind === "property") {
2220
3536
  return new _types__rspack_import_1/* .ComparatorExpression */.bQ({
2221
3537
  comparator: operator.comparator,
2222
3538
  negated: operator.negated,
@@ -2225,18 +3541,63 @@ const resolveParamPath = (paramsName, path, data)=>{
2225
3541
  right: this.createPropertyExpression(right)
2226
3542
  });
2227
3543
  }
3544
+ // Only a loose comparison coerces. `===` records `strict` and honouring it is the point.
2228
3545
  if (left.kind === "property" && right.kind !== "property") {
2229
- return this.buildPropertyComparator(left, operator, right, /* applyConverter */ true);
3546
+ return this.buildPropertyComparator(left, operator, right, /* applyConverter */ !operator.strict);
2230
3547
  }
2231
3548
  if (right.kind === "property" && left.kind !== "property") {
2232
3549
  const swapped = {
2233
3550
  ...operator,
2234
3551
  comparator: SWAPPED_COMPARATORS[operator.comparator]
2235
3552
  };
2236
- return this.buildPropertyComparator(right, swapped, left, /* applyConverter */ true);
3553
+ return this.buildPropertyComparator(right, swapped, left, /* applyConverter */ !operator.strict);
3554
+ }
3555
+ const settled = this.settleConstantComparison(left, operator, right);
3556
+ if (settled != null) {
3557
+ return settled;
2237
3558
  }
2238
3559
  throw new Error(ERROR_MESSAGES.UNSUPPORTED("comparison requires a schema property on at least one side"));
2239
3560
  }
3561
+ /**
3562
+ * The answer a comparison of two constants gives, when that answer is `true`. The other answer
3563
+ * excludes every row, which has no expression node.
3564
+ */ settleConstantComparison(left, operator, right) {
3565
+ const leftValue = this.constantOf(left);
3566
+ const rightValue = this.constantOf(right);
3567
+ if (leftValue === UNKNOWN_UNTIL_ROW || rightValue === UNKNOWN_UNTIL_ROW) {
3568
+ return null;
3569
+ }
3570
+ const answer = (0,_evaluate__rspack_import_3/* .evaluate */._3)(new _types__rspack_import_1/* .ComparatorExpression */.bQ({
3571
+ comparator: operator.comparator,
3572
+ negated: operator.negated,
3573
+ strict: operator.strict,
3574
+ left: new _types__rspack_import_1/* .ValueExpression */.Ko({
3575
+ value: leftValue
3576
+ }),
3577
+ right: new _types__rspack_import_1/* .ValueExpression */.Ko({
3578
+ value: rightValue
3579
+ })
3580
+ }), {});
3581
+ if (answer === true) {
3582
+ return _types__rspack_import_1/* .Expression.EMPTY */.r4.EMPTY;
3583
+ }
3584
+ // Params decided this, so the refusal must not be cached against the source: the same filter
3585
+ // with other params can be a tautology.
3586
+ if (left.kind === "param" || right.kind === "param") {
3587
+ throw new ParamDependentParseError(ERROR_MESSAGES.UNSUPPORTED("a params comparison no row satisfies"));
3588
+ }
3589
+ return null;
3590
+ }
3591
+ /** The value an operand holds already, for the operands that do not depend on a row. */ constantOf(operand) {
3592
+ if (operand.kind === "value" && operand.transformer == null) {
3593
+ return operand.value;
3594
+ }
3595
+ if (operand.kind === "param" && operand.transformer == null) {
3596
+ this.structurallyDependsOnParams = true;
3597
+ return resolveParamPath(this.paramsName ?? "params", operand.path, this.params);
3598
+ }
3599
+ return UNKNOWN_UNTIL_ROW;
3600
+ }
2240
3601
  buildStandalone(operand) {
2241
3602
  if (operand.kind === "method-call") {
2242
3603
  return this.buildMethodComparator(operand);
@@ -2259,6 +3620,21 @@ const resolveParamPath = (paramsName, path, data)=>{
2259
3620
  locale: null
2260
3621
  }, /* applyConverter */ true);
2261
3622
  }
3623
+ // A boolean-valued call standing alone IS the predicate
3624
+ if (operand.kind === "arithmetic" && operand.call === "matches") {
3625
+ return new _types__rspack_import_1/* .ComparatorExpression */.bQ({
3626
+ comparator: "equals",
3627
+ negated: false,
3628
+ strict: false,
3629
+ left: this.createOperandExpression(operand),
3630
+ right: new _types__rspack_import_1/* .ValueExpression */.Ko({
3631
+ value: true
3632
+ })
3633
+ });
3634
+ }
3635
+ if (operand.kind === "arithmetic" || operand.kind === "conditional") {
3636
+ throw new Error(ERROR_MESSAGES.UNSUPPORTED("arithmetic used as a condition rather than compared"));
3637
+ }
2262
3638
  // Constant `true` — a tautology, which parseAnd/parseOr simplify away
2263
3639
  if (operand.kind === "value" && operand.value === true && operand.transformer == null) {
2264
3640
  return _types__rspack_import_1/* .Expression.EMPTY */.r4.EMPTY;
@@ -2311,12 +3687,6 @@ const resolveParamPath = (paramsName, path, data)=>{
2311
3687
  right: this.createValueExpression(value, null, /* applyConverter */ false)
2312
3688
  });
2313
3689
  }
2314
- // Casing transformers on a property are only meaningful with string-matching
2315
- // comparators; on relational comparators the plugins would silently
2316
- // ignore them and return wrong data
2317
- if (property.transformer != null && !isStringMatch) {
2318
- throw new Error(ERROR_MESSAGES.UNSUPPORTED("transform method outside of startsWith/endsWith/includes"));
2319
- }
2320
3690
  return new _types__rspack_import_1/* .ComparatorExpression */.bQ({
2321
3691
  comparator: operator.comparator,
2322
3692
  negated: operator.negated,
@@ -2325,31 +3695,60 @@ const resolveParamPath = (paramsName, path, data)=>{
2325
3695
  right: this.createValueExpression(value, property.property, applyConverter)
2326
3696
  });
2327
3697
  }
3698
+ /**
3699
+ * Any operand as an expression.
3700
+ *
3701
+ * Values inside arithmetic take no paired property: the result is a computed number, so the
3702
+ * property's serializer and type converter do not describe it — the same reason `.length` skips
3703
+ * them.
3704
+ */ createOperandExpression(operand) {
3705
+ if (operand.kind === "conditional") {
3706
+ return new _types__rspack_import_1/* .CallExpression */.DG({
3707
+ call: "conditional",
3708
+ expression: operand.condition,
3709
+ arguments: [
3710
+ this.createOperandExpression(operand.whenTrue),
3711
+ this.createOperandExpression(operand.whenFalse)
3712
+ ]
3713
+ });
3714
+ }
3715
+ if (operand.kind === "arithmetic") {
3716
+ return new _types__rspack_import_1/* .CallExpression */.DG({
3717
+ call: operand.call,
3718
+ expression: this.createOperandExpression(operand.left),
3719
+ arguments: operand.right === NO_ARGUMENT ? [] : operand.extra == null ? [
3720
+ this.createOperandExpression(operand.right)
3721
+ ] : [
3722
+ this.createOperandExpression(operand.right),
3723
+ this.createOperandExpression(operand.extra)
3724
+ ]
3725
+ });
3726
+ }
3727
+ if (operand.kind === "property") {
3728
+ return this.createPropertyExpression(operand);
3729
+ }
3730
+ if (operand.kind === "method-call") {
3731
+ throw new Error(ERROR_MESSAGES.UNSUPPORTED("a method call inside arithmetic"));
3732
+ }
3733
+ return this.createValueExpression(operand, null, /* applyConverter */ false);
3734
+ }
2328
3735
  createPropertyExpression(operand) {
2329
- const expression = new _types__rspack_import_1/* .PropertyExpression */.ep({
3736
+ return asCall(new _types__rspack_import_1/* .PropertyExpression */.ep({
2330
3737
  property: operand.property
2331
- });
2332
- expression.transformer = operand.transformer;
2333
- expression.locale = operand.locale;
2334
- return expression;
3738
+ }), operand.transformer, operand.locale);
2335
3739
  }
2336
3740
  createValueExpression(operand, pairedProperty, applyConverter) {
2337
3741
  if (operand.kind === "param") {
2338
- const expression = new ParamReferenceExpression({
3742
+ return asCall(new ParamReferenceExpression({
2339
3743
  paramPath: operand.path,
2340
3744
  pairedProperty,
2341
3745
  applyConverter
2342
- });
2343
- expression.transformer = operand.transformer;
2344
- expression.locale = operand.locale;
2345
- return expression;
3746
+ }), operand.transformer, operand.locale);
2346
3747
  }
2347
3748
  const expression = new _types__rspack_import_1/* .ValueExpression */.Ko({
2348
3749
  value: resolvePairedValue(operand.value, pairedProperty, applyConverter)
2349
3750
  });
2350
- expression.transformer = operand.transformer;
2351
- expression.locale = operand.locale;
2352
- return expression;
3751
+ return asCall(expression, operand.transformer, operand.locale);
2353
3752
  }
2354
3753
  }
2355
3754
  // #endregion
@@ -2361,28 +3760,19 @@ const resolveParamPath = (paramsName, path, data)=>{
2361
3760
  */ const bindExpression = (expression, paramsName, params)=>{
2362
3761
  if (expression instanceof ParamReferenceExpression) {
2363
3762
  const raw = resolveParamPath(paramsName ?? "params", expression.paramPath, params);
2364
- const bound = new _types__rspack_import_1/* .ValueExpression */.Ko({
3763
+ return new _types__rspack_import_1/* .ValueExpression */.Ko({
2365
3764
  value: resolvePairedValue(raw, expression.pairedProperty, expression.applyConverter)
2366
3765
  });
2367
- bound.transformer = expression.transformer;
2368
- bound.locale = expression.locale;
2369
- return bound;
2370
3766
  }
2371
3767
  if (expression instanceof _types__rspack_import_1/* .ValueExpression */.Ko) {
2372
- const clone = new _types__rspack_import_1/* .ValueExpression */.Ko({
3768
+ return new _types__rspack_import_1/* .ValueExpression */.Ko({
2373
3769
  value: expression.value
2374
3770
  });
2375
- clone.transformer = expression.transformer;
2376
- clone.locale = expression.locale;
2377
- return clone;
2378
3771
  }
2379
3772
  if (expression instanceof _types__rspack_import_1/* .PropertyExpression */.ep) {
2380
- const clone = new _types__rspack_import_1/* .PropertyExpression */.ep({
3773
+ return new _types__rspack_import_1/* .PropertyExpression */.ep({
2381
3774
  property: expression.property
2382
3775
  });
2383
- clone.transformer = expression.transformer;
2384
- clone.locale = expression.locale;
2385
- return clone;
2386
3776
  }
2387
3777
  if (expression instanceof _types__rspack_import_1/* .ComparatorExpression */.bQ) {
2388
3778
  return new _types__rspack_import_1/* .ComparatorExpression */.bQ({
@@ -2400,8 +3790,99 @@ const resolveParamPath = (paramsName, path, data)=>{
2400
3790
  right: expression.right ? bindExpression(expression.right, paramsName, params) : undefined
2401
3791
  });
2402
3792
  }
3793
+ if (expression instanceof _types__rspack_import_1/* .CallExpression */.DG) {
3794
+ return new _types__rspack_import_1/* .CallExpression */.DG({
3795
+ call: expression.call,
3796
+ expression: bindExpression(expression.expression, paramsName, params),
3797
+ arguments: expression.arguments.map((argument)=>bindExpression(argument, paramsName, params))
3798
+ });
3799
+ }
2403
3800
  return expression;
2404
3801
  };
3802
+ /**
3803
+ * Wraps an operand in the call a transform method named, if there was one.
3804
+ *
3805
+ * `Transformer` and `Call` share these three names, so the transform IS the call name. A locale
3806
+ * becomes the call's first argument, which is where it belongs — it qualifies the casing, not the
3807
+ * property.
3808
+ */ const asCall = (inner, transformer, locale)=>{
3809
+ if (transformer == null) {
3810
+ return inner;
3811
+ }
3812
+ return new _types__rspack_import_1/* .CallExpression */.DG({
3813
+ call: transformer,
3814
+ expression: inner,
3815
+ arguments: locale == null ? [] : [
3816
+ new _types__rspack_import_1/* .ValueExpression */.Ko({
3817
+ value: locale
3818
+ })
3819
+ ]
3820
+ });
3821
+ };
3822
+ /** Binds every name a destructuring pattern introduces to the path it reads. */ const bindPattern = (stream, kind, path, scope)=>{
3823
+ if (!stream.matchPunctuation("{")) {
3824
+ const name = stream.next();
3825
+ if (name.kind !== "identifier") {
3826
+ throw new Error(ERROR_MESSAGES.UNSUPPORTED(`parameter '${name.value}'`));
3827
+ }
3828
+ scope.set(name.value, {
3829
+ kind,
3830
+ path
3831
+ });
3832
+ return;
3833
+ }
3834
+ while(!stream.matchPunctuation("}")){
3835
+ const key = stream.next();
3836
+ if (key.kind !== "identifier") {
3837
+ throw new Error(ERROR_MESSAGES.UNSUPPORTED(`destructured key '${key.value}'`));
3838
+ }
3839
+ if (stream.matchPunctuation(":")) {
3840
+ bindPattern(stream, kind, [
3841
+ ...path,
3842
+ key.value
3843
+ ], scope);
3844
+ } else {
3845
+ scope.set(key.value, {
3846
+ kind,
3847
+ path: [
3848
+ ...path,
3849
+ key.value
3850
+ ]
3851
+ });
3852
+ }
3853
+ if (!stream.matchPunctuation(",")) {
3854
+ stream.expectPunctuation("}");
3855
+ return;
3856
+ }
3857
+ }
3858
+ };
3859
+ /** Reads a filter's parameter list — the entity alone, or the `[entity, params]` pair — into a scope. */ const buildScope = (parameterNames, hasParams)=>{
3860
+ const stream = new TokenStream(tokenize(parameterNames));
3861
+ const scope = new Map();
3862
+ if (!stream.matchPunctuation("[")) {
3863
+ bindPattern(stream, "property", [], scope);
3864
+ return {
3865
+ scope,
3866
+ paramsName: null
3867
+ };
3868
+ }
3869
+ bindPattern(stream, "property", [], scope);
3870
+ if (hasParams && stream.matchPunctuation(",") && !stream.isPunctuation("]")) {
3871
+ bindPattern(stream, "param", [], scope);
3872
+ }
3873
+ return {
3874
+ scope,
3875
+ paramsName: wholeParamsName(scope)
3876
+ };
3877
+ };
3878
+ /** The name the whole params object was given, when it was not destructured. Error messages only. */ const wholeParamsName = (scope)=>{
3879
+ for (const [name, binding] of scope){
3880
+ if (binding.kind === "param" && binding.path.length === 0) {
3881
+ return name;
3882
+ }
3883
+ }
3884
+ return null;
3885
+ };
2405
3886
  /**
2406
3887
  * Splits stringified filter source into parameter names and the expression
2407
3888
  * body, unwrapping single-return block bodies.
@@ -2431,33 +3912,12 @@ const resolveParamPath = (paramsName, path, data)=>{
2431
3912
  parameterNames = parameterNames.slice(1, -1).trim();
2432
3913
  }
2433
3914
  }
2434
- let entityName;
2435
- let paramsName = null;
2436
- if (parameterNames.startsWith("[") && parameterNames.endsWith("]")) {
2437
- const destructured = parameterNames.slice(1, -1).split(",").map((w)=>w.trim());
2438
- entityName = destructured[0];
2439
- if (hasParams) {
2440
- paramsName = destructured[1] ?? null;
2441
- }
2442
- } else {
2443
- entityName = parameterNames;
2444
- }
2445
- if (entityName == null || entityName.length === 0) {
3915
+ if (parameterNames.length === 0) {
2446
3916
  throw new Error("Invalid Function");
2447
3917
  }
2448
- // Unwrap a single-return block body: { return <expression>; }
2449
- if (body.startsWith("{")) {
2450
- const inner = body.slice(1, body.lastIndexOf("}")).trim();
2451
- if (!inner.startsWith("return")) {
2452
- throw new Error(ERROR_MESSAGES.UNSUPPORTED("block body without a single return statement"));
2453
- }
2454
- body = inner.slice("return".length).trim();
2455
- if (body.endsWith(";")) {
2456
- body = body.slice(0, -1).trim();
2457
- }
2458
- }
3918
+ const { scope, paramsName } = buildScope(parameterNames, hasParams);
2459
3919
  return {
2460
- entityName,
3920
+ scope,
2461
3921
  paramsName,
2462
3922
  body
2463
3923
  };
@@ -2525,17 +3985,27 @@ const combineExpressions = (...expressions)=>{
2525
3985
  */ const parseFragment = (schema, body, rootName)=>{
2526
3986
  try {
2527
3987
  const stream = new TokenStream(tokenize(body));
2528
- const parser = new ExpressionParser(schema, stream, rootName, null, undefined);
2529
- return parser.parse();
3988
+ const scope = new Map([
3989
+ [
3990
+ rootName,
3991
+ {
3992
+ kind: "property",
3993
+ path: []
3994
+ }
3995
+ ]
3996
+ ]);
3997
+ const parser = new ExpressionParser(schema, stream, scope, null, undefined);
3998
+ return (0,_fold__rspack_import_4/* .foldConstantCalls */.F5)(parser.parse());
2530
3999
  } catch {
2531
4000
  // The failure is expected and informative — see above — so it is not logged. A caller that
2532
4001
  // parses one conjunct against two schemas would otherwise warn on every successful split.
2533
4002
  return _types__rspack_import_1/* .Expression.NOT_PARSABLE */.r4.NOT_PARSABLE;
2534
4003
  }
2535
4004
  };
4005
+ /** What the parser refused, from a throw that may not be an `Error`. */ const refusalOf = (error)=>error instanceof Error ? error.message : String(error);
2536
4006
  const toExpression = (schema, fn, params)=>{
2537
4007
  const stringifiedFunction = fn.toString();
2538
- const warn = (error)=>_utilities__rspack_import_3/* .logger.warn */.vF.warn("Error parsing expression", {
4008
+ const warn = (error)=>_utilities__rspack_import_5/* .logger.warn */.vF.warn("Error parsing expression", {
2539
4009
  error,
2540
4010
  collectionName: schema.collectionName,
2541
4011
  params,
@@ -2543,16 +4013,17 @@ const toExpression = (schema, fn, params)=>{
2543
4013
  });
2544
4014
  const cached = getCachedTemplate(schema, stringifiedFunction);
2545
4015
  if (cached != null) {
2546
- // A cached failure — the warning was already logged when it was discovered
4016
+ // A cached failure — the warning was already logged when it was discovered. The template
4017
+ // carries what was refused, and `.explain()` is usually called once the cache is warm.
2547
4018
  if (_types__rspack_import_1/* .Expression.isNotParsable */.r4.isNotParsable(cached.template)) {
2548
- return _types__rspack_import_1/* .Expression.NOT_PARSABLE */.r4.NOT_PARSABLE;
4019
+ return cached.template;
2549
4020
  }
2550
4021
  try {
2551
- return bindExpression(cached.template, cached.paramsName, params);
4022
+ return (0,_fold__rspack_import_4/* .foldConstantCalls */.F5)(bindExpression(cached.template, cached.paramsName, params));
2552
4023
  } catch (error) {
2553
4024
  // Binding failures are param-dependent by nature — never cached
2554
4025
  warn(error);
2555
- return _types__rspack_import_1/* .Expression.NOT_PARSABLE */.r4.NOT_PARSABLE;
4026
+ return _types__rspack_import_1/* .Expression.notParsable */.r4.notParsable(refusalOf(error));
2556
4027
  }
2557
4028
  }
2558
4029
  let paramsName = null;
@@ -2561,22 +4032,23 @@ const toExpression = (schema, fn, params)=>{
2561
4032
  try {
2562
4033
  const shape = resolveFunctionShape(stringifiedFunction, params != null);
2563
4034
  const stream = new TokenStream(tokenize(shape.body));
2564
- const parser = new ExpressionParser(schema, stream, shape.entityName, shape.paramsName, params);
4035
+ const parser = new ExpressionParser(schema, stream, shape.scope, shape.paramsName, params);
2565
4036
  paramsName = shape.paramsName;
2566
- template = parser.parse();
4037
+ template = parser.parseBody();
2567
4038
  structurallyDependsOnParams = parser.structurallyDependsOnParams;
2568
4039
  } catch (error) {
2569
4040
  // Cache the failure so a hot query on an unsupported filter doesn't
2570
4041
  // re-parse and re-warn on every execution. Param-dependent failures are
2571
4042
  // exempt: the same source can succeed with different params.
4043
+ const refused = _types__rspack_import_1/* .Expression.notParsable */.r4.notParsable(refusalOf(error));
2572
4044
  if (!(error instanceof ParamDependentParseError)) {
2573
4045
  setCachedTemplate(schema, stringifiedFunction, {
2574
- template: _types__rspack_import_1/* .Expression.NOT_PARSABLE */.r4.NOT_PARSABLE,
4046
+ template: refused,
2575
4047
  paramsName: null
2576
4048
  });
2577
4049
  }
2578
4050
  warn(error);
2579
- return _types__rspack_import_1/* .Expression.NOT_PARSABLE */.r4.NOT_PARSABLE;
4051
+ return refused;
2580
4052
  }
2581
4053
  // Templates whose structure was resolved from param values are only
2582
4054
  // valid for this exact params object — parse those fresh every time
@@ -2587,10 +4059,10 @@ const toExpression = (schema, fn, params)=>{
2587
4059
  });
2588
4060
  }
2589
4061
  try {
2590
- return bindExpression(template, paramsName, params);
4062
+ return (0,_fold__rspack_import_4/* .foldConstantCalls */.F5)(bindExpression(template, paramsName, params));
2591
4063
  } catch (error) {
2592
4064
  warn(error);
2593
- return _types__rspack_import_1/* .Expression.NOT_PARSABLE */.r4.NOT_PARSABLE;
4065
+ return _types__rspack_import_1/* .Expression.notParsable */.r4.notParsable(refusalOf(error));
2594
4066
  }
2595
4067
  };
2596
4068
 
@@ -2598,6 +4070,7 @@ const toExpression = (schema, fn, params)=>{
2598
4070
  },
2599
4071
  27(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
2600
4072
  __webpack_require__.d(__webpack_exports__, {
4073
+ DG: () => (CallExpression),
2601
4074
  Ko: () => (ValueExpression),
2602
4075
  SC: () => (NotParsableExpression),
2603
4076
  Sm: () => (EmptyExpression),
@@ -2609,59 +4082,69 @@ __webpack_require__.d(__webpack_exports__, {
2609
4082
  const valueToJson = (value)=>{
2610
4083
  if (value === undefined) {
2611
4084
  return {
2612
- k: "undefined"
4085
+ undefined: true
2613
4086
  };
2614
4087
  }
2615
4088
  if (value === null) {
2616
- return {
2617
- k: "raw",
2618
- v: null
2619
- };
4089
+ return null;
2620
4090
  }
2621
4091
  if (value instanceof Date) {
2622
4092
  // ISO rather than epoch millis: it survives a human reading the payload, and an invalid
2623
4093
  // Date has no ISO form — so it is caught here rather than becoming a silent `null`.
2624
4094
  return {
2625
- k: "date",
2626
- v: value.toISOString()
4095
+ date: value.toISOString()
2627
4096
  };
2628
4097
  }
2629
4098
  if (Array.isArray(value)) {
2630
- return {
2631
- k: "array",
2632
- v: value.map(valueToJson)
2633
- };
4099
+ return value.map(valueToJson);
2634
4100
  }
2635
4101
  if (typeof value === "number" && Number.isFinite(value) === false) {
2636
4102
  // `JSON.stringify` turns all three of these into `null`, which would compare as a different
2637
4103
  // value entirely rather than failing.
2638
4104
  return {
2639
- k: "number",
2640
- v: Number.isNaN(value) ? "NaN" : value > 0 ? "Infinity" : "-Infinity"
4105
+ number: Number.isNaN(value) ? "NaN" : value > 0 ? "Infinity" : "-Infinity"
2641
4106
  };
2642
4107
  }
2643
4108
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
4109
+ return value;
4110
+ }
4111
+ if (value instanceof RegExp) {
4112
+ return {
4113
+ regex: {
4114
+ source: value.source,
4115
+ flags: value.flags
4116
+ }
4117
+ };
4118
+ }
4119
+ // `JSON.stringify` throws outright on a bigint rather than losing it quietly, so this is the one
4120
+ // tag that turns a crash into a value
4121
+ if (typeof value === "bigint") {
2644
4122
  return {
2645
- k: "raw",
2646
- v: value
4123
+ bigint: value.toString()
2647
4124
  };
2648
4125
  }
2649
4126
  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)}`);
2650
4127
  };
2651
4128
  const valueFromJson = (value)=>{
2652
- if (value.k === "undefined") {
2653
- return undefined;
4129
+ if (value === null || typeof value !== "object") {
4130
+ return value;
4131
+ }
4132
+ if (Array.isArray(value)) {
4133
+ return value.map(valueFromJson);
4134
+ }
4135
+ if ("date" in value) {
4136
+ return new Date(value.date);
2654
4137
  }
2655
- if (value.k === "date") {
2656
- return new Date(value.v);
4138
+ if ("undefined" in value) {
4139
+ return undefined;
2657
4140
  }
2658
- if (value.k === "array") {
2659
- return value.v.map(valueFromJson);
4141
+ if ("regex" in value) {
4142
+ return new RegExp(value.regex.source, value.regex.flags);
2660
4143
  }
2661
- if (value.k === "number") {
2662
- return value.v === "NaN" ? Number.NaN : value.v === "Infinity" ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY;
4144
+ if ("bigint" in value) {
4145
+ return BigInt(value.bigint);
2663
4146
  }
2664
- return value.v;
4147
+ return value.number === "NaN" ? Number.NaN : value.number === "Infinity" ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY;
2665
4148
  };
2666
4149
  /**
2667
4150
  * The base class for all expression types.
@@ -2678,6 +4161,9 @@ const valueFromJson = (value)=>{
2678
4161
  static get NOT_PARSABLE() {
2679
4162
  return new NotParsableExpression();
2680
4163
  }
4164
+ /** `NOT_PARSABLE`, carrying what the parser refused. */ static notParsable(reason) {
4165
+ return new NotParsableExpression(reason);
4166
+ }
2681
4167
  static isEmpty(expression) {
2682
4168
  return expression.type === "empty" || expression instanceof EmptyExpression;
2683
4169
  }
@@ -2694,7 +4180,7 @@ const valueFromJson = (value)=>{
2694
4180
  *
2695
4181
  * ## Why it is this small
2696
4182
  *
2697
- * Of the six node types a bound tree can contain, exactly one holds anything JSON cannot carry:
4183
+ * Of the seven node types a bound tree can contain, exactly one holds anything JSON cannot carry:
2698
4184
  * `PropertyExpression`, whose live `PropertyInfo` has functions, a parent chain and caches. It
2699
4185
  * reduces to a property PATH — `PropertyInfo.id` IS the dotted path, and `getProperty` is keyed by
2700
4186
  * exactly that — so rebinding is one lookup.
@@ -2709,7 +4195,7 @@ const valueFromJson = (value)=>{
2709
4195
  if (expression.type === "operator") {
2710
4196
  const operator = expression;
2711
4197
  return {
2712
- t: "operator",
4198
+ type: "operator",
2713
4199
  operator: operator.operator,
2714
4200
  ...operator.left != null && {
2715
4201
  left: Expression.toJson(operator.left)
@@ -2722,7 +4208,7 @@ const valueFromJson = (value)=>{
2722
4208
  if (expression.type === "comparator") {
2723
4209
  const comparator = expression;
2724
4210
  return {
2725
- t: "comparator",
4211
+ type: "comparator",
2726
4212
  comparator: comparator.comparator,
2727
4213
  negated: comparator.negated,
2728
4214
  strict: comparator.strict,
@@ -2734,29 +4220,41 @@ const valueFromJson = (value)=>{
2734
4220
  }
2735
4221
  };
2736
4222
  }
4223
+ if (expression.type === "call") {
4224
+ const call = expression;
4225
+ return {
4226
+ type: "call",
4227
+ call: call.call,
4228
+ expression: Expression.toJson(call.expression),
4229
+ arguments: call.arguments.map(Expression.toJson)
4230
+ };
4231
+ }
2737
4232
  if (expression.type === "property") {
2738
4233
  const property = expression;
2739
4234
  return {
2740
- t: "property",
4235
+ type: "property",
2741
4236
  // The dotted path, which is exactly the key `getProperty` is looking up
2742
- path: property.property.id,
2743
- transformer: property.transformer,
2744
- locale: property.locale
4237
+ path: property.property.id
2745
4238
  };
2746
4239
  }
2747
4240
  if (expression.type === "value") {
2748
4241
  const value = expression;
2749
4242
  return {
2750
- t: "value",
2751
- value: valueToJson(value.value),
2752
- transformer: value.transformer,
2753
- locale: value.locale
4243
+ type: "value",
4244
+ value: valueToJson(value.value)
4245
+ };
4246
+ }
4247
+ if (expression.type === "empty") {
4248
+ return {
4249
+ type: "empty"
2754
4250
  };
2755
4251
  }
2756
- return expression.type === "empty" ? {
2757
- t: "empty"
4252
+ const reason = expression.reason;
4253
+ return reason == null ? {
4254
+ type: "not-parsable"
2758
4255
  } : {
2759
- t: "not-parsable"
4256
+ type: "not-parsable",
4257
+ reason
2760
4258
  };
2761
4259
  }
2762
4260
  /**
@@ -2772,14 +4270,14 @@ const valueFromJson = (value)=>{
2772
4270
  * failure here worse than an error.
2773
4271
  */ static fromJson(json, schema) {
2774
4272
  const child = (node)=>node == null ? undefined : Expression.fromJson(node, schema);
2775
- if (json.t === "operator") {
4273
+ if (json.type === "operator") {
2776
4274
  return new OperatorExpression({
2777
4275
  operator: json.operator,
2778
4276
  left: child(json.left),
2779
4277
  right: child(json.right)
2780
4278
  });
2781
4279
  }
2782
- if (json.t === "comparator") {
4280
+ if (json.type === "comparator") {
2783
4281
  return new ComparatorExpression({
2784
4282
  comparator: json.comparator,
2785
4283
  negated: json.negated,
@@ -2788,27 +4286,34 @@ const valueFromJson = (value)=>{
2788
4286
  right: child(json.right)
2789
4287
  });
2790
4288
  }
2791
- if (json.t === "property") {
4289
+ if (json.type === "call") {
4290
+ if (json.expression == null) {
4291
+ throw new Error(`Cannot deserialize a filter: a '${json.call}' call carries no operand. ` + `Collection: ${schema.collectionName}.`);
4292
+ }
4293
+ return new CallExpression({
4294
+ call: json.call,
4295
+ expression: Expression.fromJson(json.expression, schema),
4296
+ arguments: (json.arguments ?? []).map((argument)=>Expression.fromJson(argument, schema))
4297
+ });
4298
+ }
4299
+ if (json.type === "property") {
2792
4300
  const property = schema.getProperty(json.path);
2793
4301
  if (property == null) {
2794
4302
  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.`);
2795
4303
  }
2796
- const rebuilt = new PropertyExpression({
4304
+ return new PropertyExpression({
2797
4305
  property
2798
4306
  });
2799
- rebuilt.transformer = json.transformer;
2800
- rebuilt.locale = json.locale;
2801
- return rebuilt;
2802
4307
  }
2803
- if (json.t === "value") {
2804
- const rebuilt = new ValueExpression({
4308
+ if (json.type === "value") {
4309
+ return new ValueExpression({
2805
4310
  value: valueFromJson(json.value)
2806
4311
  });
2807
- rebuilt.transformer = json.transformer;
2808
- rebuilt.locale = json.locale;
2809
- return rebuilt;
2810
4312
  }
2811
- return json.t === "empty" ? Expression.EMPTY : Expression.NOT_PARSABLE;
4313
+ if (json.type === "empty") {
4314
+ return Expression.EMPTY;
4315
+ }
4316
+ return json.reason == null ? Expression.NOT_PARSABLE : Expression.notParsable(json.reason);
2812
4317
  }
2813
4318
  }
2814
4319
  class EmptyExpression extends Expression {
@@ -2816,6 +4321,11 @@ class EmptyExpression extends Expression {
2816
4321
  }
2817
4322
  class NotParsableExpression extends Expression {
2818
4323
  type = "not-parsable";
4324
+ /** What the parser refused, when it knows. `.explain()` prints it beside the source. */ reason;
4325
+ constructor(reason){
4326
+ super();
4327
+ this.reason = reason;
4328
+ }
2819
4329
  }
2820
4330
  /**
2821
4331
  * A class representing a comparison operation (e.g., equals, greater-than).
@@ -2846,20 +4356,28 @@ class NotParsableExpression extends Expression {
2846
4356
  */ class PropertyExpression extends Expression {
2847
4357
  /** The type of the expression (always 'property'). */ type = "property";
2848
4358
  /** The property info for the path. */ property;
2849
- transformer = null;
2850
- locale = null;
2851
4359
  constructor(options){
2852
4360
  super();
2853
4361
  this.property = options.property;
2854
4362
  }
2855
4363
  }
4364
+ class CallExpression extends Expression {
4365
+ type = "call";
4366
+ call;
4367
+ expression;
4368
+ /** Empty for a unary call. */ arguments;
4369
+ constructor(options){
4370
+ super();
4371
+ this.call = options.call;
4372
+ this.expression = options.expression;
4373
+ this.arguments = options.arguments ?? [];
4374
+ }
4375
+ }
2856
4376
  /**
2857
4377
  * A class representing a literal value.
2858
4378
  */ class ValueExpression extends Expression {
2859
4379
  /** The type of the expression (always 'value'). */ type = "value";
2860
4380
  /** The literal value. */ value;
2861
- transformer = null;
2862
- locale = null;
2863
4381
  constructor(options){
2864
4382
  super();
2865
4383
  this.value = options.value;
@@ -2870,9 +4388,45 @@ class NotParsableExpression extends Expression {
2870
4388
  },
2871
4389
  63(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
2872
4390
  __webpack_require__.d(__webpack_exports__, {
2873
- j: () => (forEach),
2874
- o: () => (getProperties)
4391
+ CC: () => (peelCalls),
4392
+ LU: () => (childrenOf),
4393
+ jJ: () => (forEach),
4394
+ oY: () => (getProperties)
2875
4395
  });
4396
+ /**
4397
+ * Separates an operand from the calls applied to it.
4398
+ *
4399
+ * `null` when there is no operand beneath the calls. Every consumer needs this to decide whether a
4400
+ * comparator side is a property or a value, so it lives here rather than in each translator.
4401
+ */ function peelCalls(expression) {
4402
+ const calls = [];
4403
+ let current = expression;
4404
+ while(current != null && current.type === "call"){
4405
+ calls.unshift(current);
4406
+ current = current.expression;
4407
+ }
4408
+ return current == null ? null : {
4409
+ operand: current,
4410
+ calls
4411
+ };
4412
+ }
4413
+ function childrenOf(expression) {
4414
+ if (expression.type === "call") {
4415
+ const call = expression;
4416
+ return [
4417
+ call.expression,
4418
+ ...call.arguments ?? []
4419
+ ].filter((child)=>child != null);
4420
+ }
4421
+ const children = [];
4422
+ if (expression.left != null) {
4423
+ children.push(expression.left);
4424
+ }
4425
+ if (expression.right != null) {
4426
+ children.push(expression.right);
4427
+ }
4428
+ return children;
4429
+ }
2876
4430
  /**
2877
4431
  * Extracts all properties referenced in an expression
2878
4432
  * @param expression The expression to analyze
@@ -2884,12 +4438,8 @@ __webpack_require__.d(__webpack_exports__, {
2884
4438
  if (expr.type === "property") {
2885
4439
  properties.push(expr.property);
2886
4440
  }
2887
- // Traverse left and right expressions if they exist
2888
- if (expr.left) {
2889
- traverse(expr.left);
2890
- }
2891
- if (expr.right) {
2892
- traverse(expr.right);
4441
+ for (const child of childrenOf(expr)){
4442
+ traverse(child);
2893
4443
  }
2894
4444
  }
2895
4445
  traverse(expression);
@@ -2902,14 +4452,8 @@ function forEach(expression, callback) {
2902
4452
  if (!callback(expr)) {
2903
4453
  return false;
2904
4454
  }
2905
- // Traverse left and right expressions if they exist
2906
- if (expr.left) {
2907
- if (!traverse(expr.left)) {
2908
- return false;
2909
- }
2910
- }
2911
- if (expr.right) {
2912
- if (!traverse(expr.right)) {
4455
+ for (const child of childrenOf(expr)){
4456
+ if (!traverse(child)) {
2913
4457
  return false;
2914
4458
  }
2915
4459
  }
@@ -3257,10 +4801,11 @@ var TrampolinePipeline = __webpack_require__(416);
3257
4801
 
3258
4802
 
3259
4803
  },
3260
- 454(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
4804
+ 640(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
3261
4805
 
3262
4806
  // EXPORTS
3263
4807
  __webpack_require__.d(__webpack_exports__, {
4808
+ describeFilters: () => (/* reexport */ describeFilters),
3264
4809
  DEFAULT_SEMI_JOIN_KEY_THRESHOLD: () => (/* reexport */ DEFAULT_SEMI_JOIN_KEY_THRESHOLD),
3265
4810
  TranslatedArrayValue: () => (/* reexport */ TranslatedArrayValue),
3266
4811
  collectingSink: () => (/* reexport */ collectingSink),
@@ -3270,12 +4815,15 @@ __webpack_require__.d(__webpack_exports__, {
3270
4815
  splitSendableOptions: () => (/* reexport */ splitSendableOptions),
3271
4816
  executeJoin: () => (/* reexport */ executeJoin),
3272
4817
  formatExplanation: () => (/* reexport */ formatExplanation),
3273
- serializePersistResult: () => (/* reexport */ serializePersistResult),
4818
+ parameteriseDocument: () => (/* reexport */ parameteriseDocument),
4819
+ mappedResultColumns: () => (/* reexport */ mappedResultColumns),
3274
4820
  explainQuery: () => (/* reexport */ explainQuery),
4821
+ parameter: () => (/* reexport */ parameter),
4822
+ serializePersistResult: () => (/* reexport */ serializePersistResult),
4823
+ applyInnerOptions: () => (/* reexport */ applyInnerOptions),
3275
4824
  cosineDistance: () => (/* reexport */ cosineDistance),
3276
4825
  RetryDbPlugin: () => (/* reexport */ RetryDbPlugin),
3277
4826
  BatchingDbPlugin: () => (/* reexport */ BatchingDbPlugin),
3278
- applyInnerOptions: () => (/* reexport */ applyInnerOptions),
3279
4827
  MEMORY_EXECUTION_EXPLANATIONS: () => (/* reexport */ MEMORY_EXECUTION_EXPLANATIONS),
3280
4828
  TupleTranslator: () => (/* reexport */ TupleTranslator),
3281
4829
  TranslatedSingleValue: () => (/* reexport */ TranslatedSingleValue),
@@ -3283,29 +4831,47 @@ __webpack_require__.d(__webpack_exports__, {
3283
4831
  TranslatedGroupValue: () => (/* reexport */ TranslatedGroupValue),
3284
4832
  deserializeBulkPersist: () => (/* reexport */ deserializeBulkPersist),
3285
4833
  EXECUTED_QUERIES_UNSUPPORTED: () => (/* reexport */ EXECUTED_QUERIES_UNSUPPORTED),
3286
- joinInPlugin: () => (/* reexport */ joinInPlugin),
4834
+ executedQueriesOf: () => (/* reexport */ executedQueriesOf),
3287
4835
  deserializePersistResult: () => (/* reexport */ deserializePersistResult),
4836
+ joinInPlugin: () => (/* reexport */ joinInPlugin),
3288
4837
  loggerSink: () => (/* reexport */ loggerSink),
3289
4838
  nearestBy: () => (/* reexport */ nearestBy),
3290
- readJoinKey: () => (/* reexport */ readJoinKey),
3291
4839
  hashJoin: () => (/* reexport */ hashJoin),
3292
4840
  EphemeralDataPlugin: () => (/* reexport */ EphemeralDataPlugin),
3293
4841
  QueryOptionsCollection: () => (/* reexport */ QueryOptionsCollection/* .QueryOptionsCollection */.H),
3294
- serializeBulkPersist: () => (/* reexport */ serializeBulkPersist),
3295
- toEntityShape: () => (/* reexport */ toEntityShape),
4842
+ describeUnparsableFilter: () => (/* reexport */ describeUnparsableFilter),
4843
+ readJoinKey: () => (/* reexport */ readJoinKey),
3296
4844
  loadJoinInnerSide: () => (/* reexport */ loadJoinInnerSide),
3297
4845
  DataTranslator: () => (/* reexport */ DataTranslator),
3298
- withExecutedQueries: () => (/* reexport */ withExecutedQueries),
4846
+ serializeBulkPersist: () => (/* reexport */ serializeBulkPersist),
4847
+ toEntityShape: () => (/* reexport */ toEntityShape),
3299
4848
  TelemetryDbPlugin: () => (/* reexport */ TelemetryDbPlugin),
4849
+ withExecutedQueries: () => (/* reexport */ withExecutedQueries),
3300
4850
  createRequestHandler: () => (/* reexport */ createRequestHandler),
3301
4851
  SqlTranslator: () => (/* reexport */ SqlTranslator),
4852
+ withInnerSide: () => (/* reexport */ withInnerSide),
3302
4853
  QueryOrdering: () => (/* reexport */ types_QueryOrdering),
4854
+ describeFilterAsJs: () => (/* reexport */ describeFilterAsJs),
3303
4855
  serializeQueryOptions: () => (/* reexport */ serializeQueryOptions),
3304
- distinctJoinKeys: () => (/* reexport */ distinctJoinKeys),
3305
4856
  JsonTranslator: () => (/* reexport */ JsonTranslator),
3306
- deserializeQueryOptions: () => (/* reexport */ deserializeQueryOptions)
4857
+ DATABASE_EXECUTION_EXPLANATIONS: () => (/* reexport */ DATABASE_EXECUTION_EXPLANATIONS),
4858
+ distinctJoinKeys: () => (/* reexport */ distinctJoinKeys),
4859
+ deserializeQueryOptions: () => (/* reexport */ deserializeQueryOptions),
4860
+ isDatabaseStep: () => (/* reexport */ isDatabaseStep)
3307
4861
  });
3308
4862
 
4863
+ ;// CONCATENATED MODULE: ./src/plugins/resultShape.ts
4864
+ /**
4865
+ * The columns a `map` projection selects.
4866
+ *
4867
+ * Named by `sourceName`, which is what the statement actually emits; the rename to
4868
+ * `destinationName` happens in the translator, after the rows come back. A field with no
4869
+ * `property` is an expression.
4870
+ */ const mappedResultColumns = (fields)=>fields.map((field)=>({
4871
+ name: field.sourceName,
4872
+ property: field.property ?? null
4873
+ }));
4874
+
3309
4875
  ;// CONCATENATED MODULE: ./src/plugins/translators/TranslatedArrayValue.ts
3310
4876
  class TranslatedArrayValue {
3311
4877
  value;
@@ -3400,6 +4966,10 @@ class DataTranslator {
3400
4966
  translate(data) {
3401
4967
  const isTransformed = this.query.options.hasTransformations();
3402
4968
  this.query.options.forEach((item)=>{
4969
+ // The plugin reported it could not run this one, so the memory pass owns it now.
4970
+ if (item.target === "database" && item.reason !== "executed") {
4971
+ return;
4972
+ }
3403
4973
  data = this.functionMap[item.name](data, item);
3404
4974
  });
3405
4975
  if (Array.isArray(data)) {
@@ -3812,7 +5382,7 @@ class Query {
3812
5382
  * Only a plugin that runs its outer query FIRST can supply these, and most run this loader
3813
5383
  * before anything else — so it is optional, and its absence costs a wider inner read rather
3814
5384
  * than a wrong one.
3815
- */ outerKeys)=>{
5385
+ */ outerKeys, /** Where the inner read reports what it executed. Defaults to the outer read's own list. */ innerExecutedQueries)=>{
3816
5386
  const joinOption = event.operation.options.getLast("join");
3817
5387
  if (joinOption == null) {
3818
5388
  done({
@@ -3840,9 +5410,10 @@ class Query {
3840
5410
  action: "query",
3841
5411
  reason: "join inner side",
3842
5412
  explain: event.explain,
3843
- // The same array the outer read pushes into, so a join reports BOTH reads in execution
3844
- // order. Built fresh rather than spread, so this has to be carried explicitly.
3845
- executedQueries: event.executedQueries
5413
+ // The caller decides where the inner read reports, because only it knows whether the inner
5414
+ // side is the SAME plugin where both reads belong in one explanation — or a different one,
5415
+ // where a PouchDB scan filed under SqliteDbPlugin is a lie.
5416
+ executedQueries: innerExecutedQueries ?? event.executedQueries
3846
5417
  };
3847
5418
  query(innerEvent, (result)=>{
3848
5419
  if (result.ok === Result/* .PluginEventResult.ERROR */.D.ERROR) {
@@ -3919,6 +5490,12 @@ class Query {
3919
5490
  return;
3920
5491
  }
3921
5492
  const outerRows = outerResult.data.value ?? [];
5493
+ if (at.reason !== "executed") {
5494
+ // The outer read reported something, so the database phase stopped before the join.
5495
+ // The datastore's own join branch pairs these rows.
5496
+ done(Result/* .PluginEventResult.success */.D.success(event.id, new TranslatedArrayValue(outerRows, false)));
5497
+ return;
5498
+ }
3922
5499
  // Storage shape: the plugin returns rows as it holds them, and deserialization is what
3923
5500
  // `executeJoin` does per side below.
3924
5501
  const outerKeys = distinctJoinKeys(outerRows, at.value.outerKey, at.value.semiJoinKeyThreshold, {
@@ -4034,9 +5611,7 @@ class JsonTranslator extends DataTranslator {
4034
5611
  if (field.property != null) {
4035
5612
  const value = field.property.getValue(data[i]);
4036
5613
  if (value != null) {
4037
- // Some types do not support deserialization (Array, Function, Computed, etc), just directly set the incoming value
4038
- const resolvedValue = field.property.supportsDeserialization ? field.property.deserialize(value) : value;
4039
- field.property.setValue(data[i], resolvedValue);
5614
+ field.property.setValue(data[i], field.property.deserialize(value));
4040
5615
  }
4041
5616
  }
4042
5617
  }
@@ -4060,9 +5635,7 @@ class JsonTranslator extends DataTranslator {
4060
5635
  if (field.property != null) {
4061
5636
  const value = field.property.getValue(data[i]);
4062
5637
  if (value != null) {
4063
- // Some types do not support deserialization (Array, Function, Computed, etc), just directly set the incoming value
4064
- const resolvedValue = field.property.supportsDeserialization ? field.property.deserialize(value) : value;
4065
- field.property.setValue(item, resolvedValue);
5638
+ field.property.setValue(item, field.property.deserialize(value));
4066
5639
  continue;
4067
5640
  }
4068
5641
  // The property exists, lets set it to the value (null/undefined)
@@ -4403,9 +5976,12 @@ class SqlTranslator extends DataTranslator {
4403
5976
  for(let j = 0, l = option.value.fields.length; j < l; j++){
4404
5977
  const field = option.value.fields[j];
4405
5978
  if (field.property != null) {
4406
- const value = field.property.getValue(data[i]);
5979
+ const row = data[i];
5980
+ // A nested field arrives FLAT, under the alias the statement emitted, because
5981
+ // the value was read out of a JSON column. `setValue` puts it back on its path.
5982
+ const value = Object.prototype.hasOwnProperty.call(row, field.sourceName) ? row[field.sourceName] : field.property.getValue(row);
4407
5983
  if (value != null) {
4408
- field.property.setValue(data[i], field.property.deserialize(value));
5984
+ field.property.setValue(row, field.property.deserialize(value));
4409
5985
  }
4410
5986
  }
4411
5987
  }
@@ -4542,6 +6118,183 @@ class SqlTranslator extends DataTranslator {
4542
6118
 
4543
6119
 
4544
6120
 
6121
+ // EXTERNAL MODULE: ./src/expressions/callSource.ts
6122
+ var callSource = __webpack_require__(429);
6123
+ ;// CONCATENATED MODULE: ./src/plugins/query/describeFilter.ts
6124
+
6125
+
6126
+ const COMPARATOR_OPERATORS = {
6127
+ "equals": "===",
6128
+ "greater-than": ">",
6129
+ "greater-than-equals": ">=",
6130
+ "less-than": "<",
6131
+ "less-than-equals": "<="
6132
+ };
6133
+ /** The three comparators that read as a method call rather than an operator. */ const COMPARATOR_METHODS = {
6134
+ "starts-with": "startsWith",
6135
+ "includes": "includes",
6136
+ "ends-with": "endsWith"
6137
+ };
6138
+ const renderProperty = (property)=>property.property.getPathArray().join(".");
6139
+ /**
6140
+ * The predicate as JavaScript, with every value replaced by `?`.
6141
+ *
6142
+ * Rendered from the parsed tree rather than from the function's source. The tree is what the
6143
+ * backend was actually given, so this cannot drift from what ran; and a value reaching the tree
6144
+ * as a literal is indistinguishable from one arriving through a params object, which is what
6145
+ * makes both come out as `?` the way SQL treats them.
6146
+ */ const describeFilterAsJs = (expression)=>{
6147
+ const parameters = [];
6148
+ const hold = (value)=>{
6149
+ parameters.push(value);
6150
+ return "?";
6151
+ };
6152
+ const side = (part)=>{
6153
+ if (part == null) {
6154
+ return "?";
6155
+ }
6156
+ if ((0,assertions.isPropertyExpression)(part)) {
6157
+ return renderProperty(part);
6158
+ }
6159
+ if ((0,assertions.isValueExpression)(part)) {
6160
+ return hold(part.value);
6161
+ }
6162
+ if ((0,assertions.isCallExpression)(part)) {
6163
+ return (0,callSource/* .renderCallAsJs */.a)(part.call, ()=>side(part.expression), ()=>part.arguments.map(side));
6164
+ }
6165
+ return walk(part);
6166
+ };
6167
+ const walk = (current)=>{
6168
+ if ((0,assertions.isOperatorExpression)(current)) {
6169
+ const operator = current.operator === "&&" ? "&&" : "||";
6170
+ return `(${side(current.left)} ${operator} ${side(current.right)})`;
6171
+ }
6172
+ if ((0,assertions.isComparatorExpression)(current)) {
6173
+ const method = COMPARATOR_METHODS[current.comparator];
6174
+ // Evaluated LEFT then RIGHT, always: the parameter order has to match the reading
6175
+ // order of the text, or the values line up against the wrong placeholders.
6176
+ const left = side(current.left);
6177
+ const right = side(current.right);
6178
+ if (method != null) {
6179
+ const call = `${left}.${method}(${right})`;
6180
+ return current.negated ? `${call} === false` : call;
6181
+ }
6182
+ const symbol = COMPARATOR_OPERATORS[current.comparator];
6183
+ if (symbol == null) {
6184
+ return `${left} ${current.comparator} ${right}`;
6185
+ }
6186
+ return `${left} ${current.negated ? negate(symbol) : symbol} ${right}`;
6187
+ }
6188
+ if ((0,assertions.isCallExpression)(current)) {
6189
+ return (0,callSource/* .renderCallAsJs */.a)(current.call, ()=>side(current.expression), ()=>current.arguments.map(side));
6190
+ }
6191
+ if (current.type === "empty") {
6192
+ return "(no filter)";
6193
+ }
6194
+ return current.type === "not-parsable" ? "(not parsable)" : `(unsupported: ${current.type})`;
6195
+ };
6196
+ return {
6197
+ text: walk(expression),
6198
+ parameters
6199
+ };
6200
+ };
6201
+ const negate = (symbol)=>{
6202
+ switch(symbol){
6203
+ case "===":
6204
+ return "!==";
6205
+ case ">":
6206
+ return "<=";
6207
+ case ">=":
6208
+ return "<";
6209
+ case "<":
6210
+ return ">=";
6211
+ case "<=":
6212
+ return ">";
6213
+ default:
6214
+ return `!${symbol}`;
6215
+ }
6216
+ };
6217
+ /**
6218
+ * Marks a value inside a query document so it is replaced by `?` rather than printed.
6219
+ *
6220
+ * A document language carries its values inline, so there is nothing in the shape itself to say
6221
+ * which parts are operators and which are data. A dialect wraps the data as it builds the
6222
+ * document, and `parameteriseDocument` reads the wrapper.
6223
+ */ const PARAMETER = Symbol("routier.parameter");
6224
+ const parameter = (value)=>({
6225
+ [PARAMETER]: value
6226
+ });
6227
+ const isParameter = (value)=>typeof value === "object" && value !== null && PARAMETER in value;
6228
+ /**
6229
+ * Renders a query DOCUMENT with its values replaced by `?`.
6230
+ *
6231
+ * Language-agnostic on purpose: an MQL filter and a Mango selector are both plain objects, and so
6232
+ * is whatever a future document store wants reported. The dialect decides the shape; this only
6233
+ * decides how it is written down.
6234
+ *
6235
+ * A value not wrapped by `parameter` is structural — an operator name, a field path, a nesting
6236
+ * level — and is printed as it is. That is the whole distinction, and it has to be made where the
6237
+ * document is built, because by the time it is an object the two are the same kind of thing.
6238
+ */ const parameteriseDocument = (document)=>{
6239
+ const parameters = [];
6240
+ const render = (value)=>{
6241
+ if (isParameter(value)) {
6242
+ parameters.push(value[PARAMETER]);
6243
+ return "?";
6244
+ }
6245
+ if (Array.isArray(value)) {
6246
+ return `[${value.map(render).join(", ")}]`;
6247
+ }
6248
+ if (typeof value === "object" && value !== null) {
6249
+ const entries = Object.entries(value).map(([key, nested])=>`${JSON.stringify(key)}: ${render(nested)}`);
6250
+ return `{ ${entries.join(", ")} }`;
6251
+ }
6252
+ return JSON.stringify(value) ?? String(value);
6253
+ };
6254
+ return {
6255
+ text: render(document),
6256
+ parameters
6257
+ };
6258
+ };
6259
+ /**
6260
+ * Every filter on a query, as one description.
6261
+ *
6262
+ * Filters accumulate — `.where(a).where(b)` is `a && b` — so they are reported as one predicate
6263
+ * rather than several, which is how the caller thinks of them and how a SQL plugin renders them
6264
+ * into one `WHERE`. Parameters run left to right across the whole thing, matching the text.
6265
+ *
6266
+ * A filter that could not be parsed falls back to its source. Mixing the two is deliberate: one
6267
+ * unparsable filter does not make the others unreadable, and seeing which one it was is the
6268
+ * point.
6269
+ */ const describeFilters = (filters)=>{
6270
+ const parameters = [];
6271
+ const parts = filters.map((entry)=>{
6272
+ const described = entry.expression?.type === "not-parsable" ? describeUnparsableFilter(entry.filter, entry.expression.reason) : describeFilterAsJs(entry.expression);
6273
+ parameters.push(...described.parameters);
6274
+ return described.text;
6275
+ });
6276
+ if (parts.length === 0) {
6277
+ return {
6278
+ text: "(no filter)",
6279
+ parameters: []
6280
+ };
6281
+ }
6282
+ return {
6283
+ text: parts.length === 1 ? parts[0] : parts.join(" && "),
6284
+ parameters
6285
+ };
6286
+ };
6287
+ /**
6288
+ * A predicate core could not parse, shown as the caller wrote it.
6289
+ *
6290
+ * This is the case where the source matters most: an unparsable filter is why the query did not
6291
+ * push down, and the reason codes say that it happened without showing what it was. There are no
6292
+ * parameters — nothing was extracted, because nothing was understood.
6293
+ */ const describeUnparsableFilter = (filter, reason)=>({
6294
+ text: typeof filter === "function" ? `${String(filter)} — ${reason ?? "could not be parsed"}, evaluated in memory` : "(not parsable)",
6295
+ parameters: []
6296
+ });
6297
+
4545
6298
  ;// CONCATENATED MODULE: ./src/plugins/query/explain.ts
4546
6299
 
4547
6300
  /**
@@ -4556,12 +6309,23 @@ class SqlTranslator extends DataTranslator {
4556
6309
  "map-rename": "A map renames or drops properties, so every option after it refers to names the database does not have.",
4557
6310
  "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.",
4558
6311
  "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.",
4559
- "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."
6312
+ "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.",
6313
+ "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.",
6314
+ "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."
4560
6315
  };
4561
6316
  const EXECUTED_QUERIES_UNSUPPORTED = "This plugin did not report what it executed. It may not support explain.";
4562
- const DATABASE_STEP_DESCRIPTION = "These options are sent to the plugin.";
4563
- const MEMORY_STEP_DESCRIPTION = "Routier runs these over the rows the database returned, after deserializing them.";
4564
- const UNNARROWED_READ_DESCRIPTION = "No option could be pushed down, so the plugin reads the whole collection.";
6317
+ /**
6318
+ * Why an option planned for the database did not run there. `executed` has no sentence: it needs no
6319
+ * explaining, and a step made of executed options is a database step like any other.
6320
+ */ const DATABASE_EXECUTION_EXPLANATIONS = {
6321
+ "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.",
6322
+ "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.",
6323
+ "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."
6324
+ };
6325
+ /**
6326
+ * TypeScript does not narrow a union from a discriminant nested inside a property, so the two kinds
6327
+ * of step need a guard rather than an inline check.
6328
+ */ const isDatabaseStep = (step)=>step.executedIn.kind === "database";
4565
6329
  /**
4566
6330
  * The reportable shape of one option's value.
4567
6331
  *
@@ -4642,12 +6406,16 @@ const explainedOptionsOf = (options)=>{
4642
6406
  options.forEach((option)=>explained.push(explainedOptionOf(option, index++)));
4643
6407
  return explained;
4644
6408
  };
6409
+ /** Every sentence, whoever decided — the summary reads the same either way. */ const EXPLANATIONS = {
6410
+ ...MEMORY_EXECUTION_EXPLANATIONS,
6411
+ ...DATABASE_EXECUTION_EXPLANATIONS
6412
+ };
4645
6413
  const summarize = (steps)=>{
4646
6414
  const reasons = [];
4647
6415
  let database = 0;
4648
6416
  let memory = 0;
4649
6417
  for (const step of steps){
4650
- if (step.executedIn === "database") {
6418
+ if (isDatabaseStep(step)) {
4651
6419
  database += step.options.length;
4652
6420
  continue;
4653
6421
  }
@@ -4657,7 +6425,7 @@ const summarize = (steps)=>{
4657
6425
  }
4658
6426
  }
4659
6427
  const counts = `${database} ${database === 1 ? "option ran" : "options ran"} in the database, ${memory} ran in memory.`;
4660
- const causes = reasons.map((reason)=>MEMORY_EXECUTION_EXPLANATIONS[reason]).join(" ");
6428
+ const causes = reasons.map((reason)=>EXPLANATIONS[reason]).join(" ");
4661
6429
  return {
4662
6430
  database,
4663
6431
  memory,
@@ -4665,50 +6433,87 @@ const summarize = (steps)=>{
4665
6433
  explanation: causes.length === 0 ? counts : `${counts} ${causes}`
4666
6434
  };
4667
6435
  };
4668
- /**
4669
- * Groups options into consecutive runs that execute in the same place.
4670
- *
4671
- * A step boundary is where execution moves, and a reader has to see the statement as step 1 OF
4672
- * 2 to understand it is not the whole query. Cutting over to memory is a ratchet, so the
4673
- * database options are always a prefix and there are at most two steps.
4674
- *
4675
- * A database step is emitted even when NO option pushed down, because the plugin is dispatched
4676
- * either way — `createQueryPayload` always builds a database event. Without it, the worst case
4677
- * the feature exists to expose reports "0 in the database" while the backend reads the whole
4678
- * table, which is the opposite of the truth.
4679
- */ const toExecutionSteps = (options)=>{
6436
+ const outcomeOf = (option)=>{
6437
+ if (option.target === "memory") {
6438
+ return {
6439
+ executedIn: "memory",
6440
+ reason: option.reason,
6441
+ explanation: MEMORY_EXECUTION_EXPLANATIONS[option.reason]
6442
+ };
6443
+ }
6444
+ if (option.reason === "executed") {
6445
+ return {
6446
+ executedIn: "database",
6447
+ reason: null,
6448
+ explanation: null
6449
+ };
6450
+ }
6451
+ return {
6452
+ executedIn: "memory",
6453
+ reason: option.reason,
6454
+ explanation: DATABASE_EXECUTION_EXPLANATIONS[option.reason]
6455
+ };
6456
+ };
6457
+ /** Whether an option belongs to the step already open, or starts a new one. */ const continuesStep = (current, outcome)=>{
6458
+ if (current == null) {
6459
+ return false;
6460
+ }
6461
+ if (current.executedIn.kind === "database") {
6462
+ return outcome.reason == null;
6463
+ }
6464
+ // `?? null` because a step with no reason omits the key, and `undefined === null` is false —
6465
+ // without it every option started a step of its own
6466
+ return outcome.reason != null && (current.reason ?? null) === outcome.reason;
6467
+ };
6468
+ const toExecutionSteps = (options, ranIn)=>{
4680
6469
  const steps = [];
4681
6470
  let index = 0;
4682
6471
  options.forEach((option)=>{
4683
6472
  const explained = explainedOptionOf(option, index++);
4684
6473
  const current = steps[steps.length - 1];
4685
- if (current != null && current.executedIn === option.target) {
6474
+ const outcome = outcomeOf(option);
6475
+ // Grouped by outcome, not by target: an option the database could not express and one core
6476
+ // sent to memory both run in memory, for different reasons a reader needs told apart.
6477
+ if (continuesStep(current, outcome) === true) {
4686
6478
  current.options.push(explained);
4687
6479
  return;
4688
6480
  }
4689
- steps.push({
4690
- step: steps.length + 1,
6481
+ steps.push(outcome.reason == null ? {
6482
+ step: 0,
4691
6483
  of: 0,
4692
- executedIn: option.target,
4693
- description: option.target === "database" ? DATABASE_STEP_DESCRIPTION : MEMORY_STEP_DESCRIPTION,
6484
+ executedIn: ranIn,
4694
6485
  options: [
4695
6486
  explained
4696
6487
  ],
4697
- ...option.reason == null ? {} : {
4698
- reason: option.reason,
4699
- explanation: MEMORY_EXECUTION_EXPLANATIONS[option.reason]
4700
- }
6488
+ executedQueries: []
6489
+ } : {
6490
+ step: 0,
6491
+ of: 0,
6492
+ executedIn: {
6493
+ kind: "memory"
6494
+ },
6495
+ options: [
6496
+ explained
6497
+ ],
6498
+ reason: outcome.reason,
6499
+ explanation: outcome.explanation ?? undefined
4701
6500
  });
4702
6501
  });
4703
- if (steps[0]?.executedIn !== "database") {
6502
+ // A database step even when nothing pushed down: the plugin is dispatched either way, so
6503
+ // reporting "0 in the database" while the backend reads the whole table is the opposite of
6504
+ // the truth.
6505
+ if (steps[0]?.executedIn.kind !== "database") {
4704
6506
  steps.unshift({
4705
6507
  step: 0,
4706
6508
  of: 0,
4707
- executedIn: "database",
4708
- description: UNNARROWED_READ_DESCRIPTION,
4709
- options: []
6509
+ executedIn: ranIn,
6510
+ options: [],
6511
+ executedQueries: []
4710
6512
  });
4711
6513
  }
6514
+ return steps;
6515
+ };
6516
+ /** Numbers a finished list, so `step 1 of 3` reads as the shape of the whole query. */ const numbered = (steps)=>{
4712
6517
  for(let i = 0; i < steps.length; i++){
4713
6518
  steps[i].step = i + 1;
4714
6519
  steps[i].of = steps.length;
@@ -4723,10 +6528,11 @@ const summarize = (steps)=>{
4723
6528
  * post-join filter alone in the memory half derives back to `"database"`, and the document
4724
6529
  * would report memory work as having run in the database.
4725
6530
  */ const explainQuery = (options, context)=>{
4726
- if (options.isDerived === true) {
4727
- 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.");
4728
- }
4729
- const executionSteps = toExecutionSteps(options);
6531
+ const executionSteps = numbered(toExecutionSteps(options, {
6532
+ kind: "database",
6533
+ database: context.database,
6534
+ plugin: context.pluginKind
6535
+ }));
4730
6536
  return {
4731
6537
  collection: context.collection,
4732
6538
  database: context.database,
@@ -4753,7 +6559,7 @@ const summarize = (steps)=>{
4753
6559
  // Only the first database step: a plugin reports what IT ran, and everything it ran
4754
6560
  // was sent as one dispatch. Stamping the same statements onto a second database step
4755
6561
  // would claim they ran twice.
4756
- if (step.executedIn !== "database" || attached === true) {
6562
+ if (isDatabaseStep(step) === false || attached === true) {
4757
6563
  return {
4758
6564
  ...step,
4759
6565
  options: [
@@ -4786,8 +6592,47 @@ const summarize = (steps)=>{
4786
6592
  executionSteps
4787
6593
  };
4788
6594
  };
6595
+ /**
6596
+ * Adds the step for a cross-plugin join's inner side.
6597
+ *
6598
+ * Appended by the executor rather than derived from the options, because the inner side's options
6599
+ * live on the join, in its own collection, and were never part of this query's chain. It goes before
6600
+ * the memory steps that consume it — the join cannot run until both sides are read.
6601
+ */ /**
6602
+ * Every statement the query ran, across every database it touched, in execution order.
6603
+ *
6604
+ * A step is a place, so the statements live on the steps — this is for a caller that wants them all
6605
+ * without caring which plugin ran which.
6606
+ */ const executedQueriesOf = (explanation)=>explanation.executionSteps.flatMap((step)=>isDatabaseStep(step) ? step.executedQueries : []);
6607
+ const withInnerSide = (explanation, innerSide)=>{
6608
+ const step = {
6609
+ step: 0,
6610
+ of: 0,
6611
+ executedIn: {
6612
+ kind: "database",
6613
+ database: innerSide.database,
6614
+ plugin: innerSide.plugin
6615
+ },
6616
+ options: [],
6617
+ executedQueries: innerSide.executedQueries
6618
+ };
6619
+ const firstMemory = explanation.executionSteps.findIndex((current)=>isDatabaseStep(current) === false);
6620
+ const at = firstMemory === -1 ? explanation.executionSteps.length : firstMemory;
6621
+ const executionSteps = numbered([
6622
+ ...explanation.executionSteps.slice(0, at),
6623
+ step,
6624
+ ...explanation.executionSteps.slice(at)
6625
+ ]);
6626
+ return {
6627
+ ...explanation,
6628
+ executionSteps,
6629
+ summary: summarize(executionSteps)
6630
+ };
6631
+ };
4789
6632
 
4790
6633
  ;// CONCATENATED MODULE: ./src/plugins/query/formatExplanation.ts
6634
+
6635
+
4791
6636
  const OPTION_LABEL_WIDTH = 8;
4792
6637
  const WRAP_WIDTH = 68;
4793
6638
  /** Wraps `text` to `WRAP_WIDTH`, prefixing every line with `indent`. */ const wrap = (text, indent)=>{
@@ -4813,29 +6658,41 @@ const COMPARATOR_SYMBOLS = {
4813
6658
  "less-than": "<",
4814
6659
  "less-than-equals": "<="
4815
6660
  };
4816
- const describeValue = (value)=>{
4817
- if (value == null) {
6661
+ /** Typed against the union so a new OBJECT tag is a compile error here, not an "undefined" in output. */ const describeValue = (value)=>{
6662
+ if (value === null) {
6663
+ return "null";
6664
+ }
6665
+ if (value === undefined) {
4818
6666
  return "?";
4819
6667
  }
4820
- if (value.k === "raw") {
4821
- return typeof value.v === "string" ? `"${value.v}"` : String(value.v);
6668
+ if (Array.isArray(value)) {
6669
+ return `[${value.map(describeValue).join(", ")}]`;
6670
+ }
6671
+ if (typeof value !== "object") {
6672
+ return typeof value === "string" ? `"${value}"` : String(value);
6673
+ }
6674
+ if ("date" in value) {
6675
+ return value.date;
4822
6676
  }
4823
- if (value.k === "date") {
4824
- return value.v;
6677
+ if ("undefined" in value) {
6678
+ return "undefined";
4825
6679
  }
4826
- if (value.k === "array") {
4827
- return `[${value.v.map(describeValue).join(", ")}]`;
6680
+ if ("regex" in value) {
6681
+ return `/${value.regex.source}/${value.regex.flags}`;
4828
6682
  }
4829
- return value.k === "undefined" ? "undefined" : String(value.v);
6683
+ if ("bigint" in value) {
6684
+ return `${value.bigint}n`;
6685
+ }
6686
+ return value.number;
4830
6687
  };
4831
6688
  /** Renders a serialized expression back to something close to the source predicate. */ const describeExpression = (expression)=>{
4832
6689
  if (expression == null) {
4833
6690
  return "?";
4834
6691
  }
4835
- if (expression.t === "operator") {
6692
+ if (expression.type === "operator") {
4836
6693
  return `${describeExpression(expression.left)} ${expression.operator} ${describeExpression(expression.right)}`;
4837
6694
  }
4838
- if (expression.t === "comparator") {
6695
+ if (expression.type === "comparator") {
4839
6696
  const left = describeExpression(expression.left);
4840
6697
  const right = describeExpression(expression.right);
4841
6698
  const symbol = COMPARATOR_SYMBOLS[expression.comparator];
@@ -4844,13 +6701,24 @@ const describeValue = (value)=>{
4844
6701
  }
4845
6702
  return `${left} ${expression.negated === true ? "!==" : symbol} ${right}`;
4846
6703
  }
4847
- if (expression.t === "property") {
6704
+ if (expression.type === "property") {
4848
6705
  return expression.path;
4849
6706
  }
4850
- if (expression.t === "value") {
6707
+ if (expression.type === "value") {
4851
6708
  return describeValue(expression.value);
4852
6709
  }
4853
- return expression.t === "empty" ? "(no filter)" : "(not parsable)";
6710
+ if (expression.type === "call") {
6711
+ return (0,callSource/* .renderCallAsJs */.a)(expression.call, ()=>describeExpression(expression.expression), ()=>(expression.arguments ?? []).map(describeExpression));
6712
+ }
6713
+ if (expression.type === "empty") {
6714
+ return "(no filter)";
6715
+ }
6716
+ // Distinguishable from "(not parsable)", which means the parser gave up and this runs in memory
6717
+ if (expression.type === "not-parsable") {
6718
+ return expression.reason == null ? "(not parsable)" : `(not parsable: ${expression.reason})`;
6719
+ }
6720
+ // Unreachable while the union is exhausted above; a payload from a newer sender is not.
6721
+ return `(unsupported: ${expression.type})`;
4854
6722
  };
4855
6723
  const describeOption = (option)=>{
4856
6724
  const detail = option.detail;
@@ -4878,18 +6746,35 @@ const describeOption = (option)=>{
4878
6746
  }
4879
6747
  return "";
4880
6748
  };
6749
+ /**
6750
+ * The sentence for a kind of step.
6751
+ *
6752
+ * Here rather than on the step: it is one of two constants keyed off `executedIn`, so carrying it in
6753
+ * the payload put prose beside the field it was derived from.
6754
+ */ const DATABASE_STEP_DESCRIPTION = "These options are sent to the plugin.";
6755
+ const MEMORY_STEP_DESCRIPTION = "Routier runs these over the rows the database returned, after deserializing them.";
6756
+ const UNNARROWED_READ_DESCRIPTION = "No option could be pushed down, so the plugin reads the whole collection.";
6757
+ /** `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";
4881
6758
  const formatStep = (step, lines)=>{
4882
- const reason = step.reason == null ? "" : ` [${step.reason}]`;
4883
- lines.push(` STEP ${step.step} of ${step.of} — ${step.executedIn}${reason}`);
4884
- lines.push(...wrap(step.description, " "));
4885
- if (step.explanation != null) {
4886
- lines.push(...wrap(step.explanation, " "));
6759
+ const reason = isDatabaseStep(step) || step.reason == null ? "" : ` [${step.reason}]`;
6760
+ lines.push(` STEP ${step.step} of ${step.of} — ${whereItRan(step)}${reason}`);
6761
+ if (isDatabaseStep(step)) {
6762
+ lines.push(...wrap(step.options.length === 0 ? UNNARROWED_READ_DESCRIPTION : DATABASE_STEP_DESCRIPTION, " "));
6763
+ } else {
6764
+ lines.push(...wrap(MEMORY_STEP_DESCRIPTION, " "));
6765
+ if (step.explanation != null) {
6766
+ lines.push(...wrap(step.explanation, " "));
6767
+ }
4887
6768
  }
4888
6769
  lines.push("");
4889
6770
  for (const option of step.options){
4890
6771
  lines.push(` ${option.name.padEnd(OPTION_LABEL_WIDTH)} ${describeOption(option)}`.trimEnd());
4891
6772
  }
4892
- for (const executed of step.executedQueries ?? []){
6773
+ if (isDatabaseStep(step) === false) {
6774
+ lines.push("");
6775
+ return;
6776
+ }
6777
+ for (const executed of step.executedQueries){
4893
6778
  lines.push("");
4894
6779
  lines.push(...executed.text.split("\n").map((line)=>` ${line}`));
4895
6780
  if (executed.parameters != null && executed.parameters.length > 0) {
@@ -4938,8 +6823,11 @@ var types_QueryOrdering = /*#__PURE__*/ function(QueryOrdering) {
4938
6823
 
4939
6824
 
4940
6825
 
6826
+
4941
6827
  // EXTERNAL MODULE: ./src/expressions/evaluate.ts
4942
6828
  var evaluate = __webpack_require__(379);
6829
+ // EXTERNAL MODULE: ./src/expressions/fold.ts
6830
+ var fold = __webpack_require__(43);
4943
6831
  ;// CONCATENATED MODULE: ./src/plugins/wire/query.ts
4944
6832
 
4945
6833
 
@@ -5140,7 +7028,7 @@ const serializeQueryOptions = (options)=>{
5140
7028
  }
5141
7029
  case "filter":
5142
7030
  {
5143
- const expression = types/* .Expression.fromJson */.r4.fromJson(option.value.expression, schema);
7031
+ const expression = (0,fold/* .foldConstantCalls */.F5)(types/* .Expression.fromJson */.r4.fromJson(option.value.expression, schema));
5144
7032
  options.add("filter", {
5145
7033
  filter: (0,evaluate/* .toStrictPredicate */.wS)(expression),
5146
7034
  expression,
@@ -5824,7 +7712,8 @@ var TrampolinePipeline = __webpack_require__(416);
5824
7712
  if (!(0,assertions.isPropertyExpression)(left) || !(0,assertions.isValueExpression)(right)) {
5825
7713
  return null;
5826
7714
  }
5827
- if (left.property.isKey !== true || left.transformer != null || right.value == null) {
7715
+ // A called property is a CallExpression, so it fails the isPropertyExpression check above
7716
+ if (left.property.isKey !== true || right.value == null) {
5828
7717
  return null;
5829
7718
  }
5830
7719
  return {
@@ -6197,11 +8086,17 @@ class EphemeralDataPlugin {
6197
8086
  * collection to pair it with three rows.
6198
8087
  *
6199
8088
  * `cloned` is in storage shape, so the keys are read by resolved column name.
6200
- */ // No statement to quote — an ephemeral store walks its own records. Said
6201
- // plainly so `.explain()` does not leave a reader wondering whether the
6202
- // plugin simply failed to report. Before the inner side, to match execution order.
8089
+ */ /**
8090
+ * No statement to quote an ephemeral store walks its own records — so the scan
8091
+ * is said plainly, and the PREDICATE is reported as JavaScript beside it. A count
8092
+ * alone leaves a reader unable to tell a filter that matched nothing from one
8093
+ * that was never applied.
8094
+ *
8095
+ * Before the inner side, to match execution order.
8096
+ */ const described = describeFilters(operation.options.get("filter").map((entry)=>entry.option.value));
6203
8097
  event.executedQueries.push({
6204
- text: `${operation.schema.collectionName}: scanned ${cloned.length} in-memory ${cloned.length === 1 ? "record" : "records"}`
8098
+ text: `${operation.schema.collectionName}: scanned ${cloned.length} in-memory ` + `${cloned.length === 1 ? "record" : "records"}, filter ${described.text}`,
8099
+ parameters: described.parameters.length > 0 ? described.parameters : undefined
6205
8100
  });
6206
8101
  const joinOption = operation.options.getLast("join");
6207
8102
  const outerKeys = joinOption == null ? null : distinctJoinKeys(cloned, joinOption.value.outerKey, joinOption.value.semiJoinKeyThreshold, {
@@ -6376,6 +8271,29 @@ class TelemetryDbPlugin {
6376
8271
  return JSON.stringify(option.value ?? null);
6377
8272
  }
6378
8273
  };
8274
+ /**
8275
+ * Restores a `Date` that `structuredClone` produced outside this realm.
8276
+ *
8277
+ * The clone is a real date and fails `instanceof Date`, which is what a caller checks. Mutated in
8278
+ * place because the clone is already private to this call.
8279
+ */ const reviveDates = (value)=>{
8280
+ if (value == null || typeof value !== "object") {
8281
+ return value;
8282
+ }
8283
+ if (Object.prototype.toString.call(value) === "[object Date]") {
8284
+ return value instanceof Date ? value : new Date(value);
8285
+ }
8286
+ if (Array.isArray(value)) {
8287
+ for(let i = 0, length = value.length; i < length; i++){
8288
+ value[i] = reviveDates(value[i]);
8289
+ }
8290
+ return value;
8291
+ }
8292
+ for (const key of Object.keys(value)){
8293
+ value[key] = reviveDates(value[key]);
8294
+ }
8295
+ return value;
8296
+ };
6379
8297
  class CacheDbPlugin {
6380
8298
  plugin;
6381
8299
  max;
@@ -6414,7 +8332,7 @@ class CacheDbPlugin {
6414
8332
  * the next update would be written UNCHECKED with no error anywhere.
6415
8333
  * Pinned by `datastore/src/collections/wrapperStacking.test.ts`.
6416
8334
  */ rebuild(entry) {
6417
- return new entry.construct(structuredClone(entry.value), entry.isTransformed);
8335
+ return new entry.construct(reviveDates(structuredClone(entry.value)), entry.isTransformed);
6418
8336
  }
6419
8337
  query(event, done) {
6420
8338
  const key = this.keyFor(event);
@@ -6437,6 +8355,14 @@ class CacheDbPlugin {
6437
8355
  done(result);
6438
8356
  return;
6439
8357
  }
8358
+ // A partial answer must never be cached. When the plugin reports an option it cannot
8359
+ // express, these rows are what came back BEFORE the datastore finished the query — and a
8360
+ // later hit skips the plugin entirely, so nothing would report and the rows would be
8361
+ // returned as if they were the whole answer. Unfiltered, silently.
8362
+ if (event.operation.options.notExecuted().length > 0) {
8363
+ done(Result/* .PluginEventResult.success */.D.success(event.id, result.data));
8364
+ return;
8365
+ }
6440
8366
  this.store(key, result.data);
6441
8367
  // The caller gets a rebuilt value too, not the one just stored, so that mutating
6442
8368
  // the result of a MISS cannot corrupt what the next hit returns.
@@ -6815,38 +8741,69 @@ const DEFAULT_MAX_BATCH_SIZE = 100;
6815
8741
 
6816
8742
 
6817
8743
 
8744
+
6818
8745
  },
6819
8746
  198(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
6820
8747
  __webpack_require__.d(__webpack_exports__, {
6821
8748
  H: () => (QueryOptionsCollection)
6822
8749
  });
6823
8750
  /* import */ var _assertions__rspack_import_1 = __webpack_require__(126);
6824
- /* import */ var _expressions_utils__rspack_import_0 = __webpack_require__(63);
8751
+ /* import */ var _expressions_utils__rspack_import_2 = __webpack_require__(63);
8752
+ /* import */ var _schema_types__rspack_import_0 = __webpack_require__(537);
8753
+ /* import */ var _utilities__rspack_import_3 = __webpack_require__(581);
8754
+
6825
8755
 
6826
8756
 
8757
+
8758
+ /** What a schema type is called in JavaScript, where one exists. A value of any other type cannot equal it. */ const JAVASCRIPT_TYPE_OF = {
8759
+ [_schema_types__rspack_import_0/* .SchemaTypes.Number */.L.Number]: "number",
8760
+ [_schema_types__rspack_import_0/* .SchemaTypes.String */.L.String]: "string",
8761
+ [_schema_types__rspack_import_0/* .SchemaTypes.Boolean */.L.Boolean]: "boolean",
8762
+ [_schema_types__rspack_import_0/* .SchemaTypes.Date */.L.Date]: "object"
8763
+ };
8764
+ const mismatchedSide = (property, value)=>{
8765
+ if (property == null || value == null || !(0,_assertions__rspack_import_1.isPropertyExpression)(property) || !(0,_assertions__rspack_import_1.isValueExpression)(value)) {
8766
+ return null;
8767
+ }
8768
+ const expected = JAVASCRIPT_TYPE_OF[property.property.type];
8769
+ if (expected == null || value.value == null || typeof value.value === expected) {
8770
+ return null;
8771
+ }
8772
+ return {
8773
+ property,
8774
+ value,
8775
+ expected
8776
+ };
8777
+ };
8778
+ /** A strict comparison whose answer is the same for every row, because the types cannot be equal. */ const comparesTypesThatCannotMatch = (expression)=>{
8779
+ if (!(0,_assertions__rspack_import_1.isComparatorExpression)(expression) || expression.strict !== true) {
8780
+ return false;
8781
+ }
8782
+ if (expression.comparator !== "equals") {
8783
+ return false;
8784
+ }
8785
+ return mismatchedSide(expression.left, expression.right) != null || mismatchedSide(expression.right, expression.left) != null;
8786
+ };
8787
+ /** `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);
8788
+ const mismatchWarning = (expression)=>{
8789
+ const side = mismatchedSide(expression.left, expression.right) ?? mismatchedSide(expression.right, expression.left);
8790
+ const outcome = expression.negated ? "every row matches" : "no row matches";
8791
+ 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`;
8792
+ };
6827
8793
  class QueryOptionsCollection {
6828
8794
  options = new Map();
6829
8795
  nextExecutionTarget = "database";
6830
8796
  nextExecutionReason = null;
6831
8797
  nextIndex = 0;
6832
8798
  enumeratedItems = [];
8799
+ dirty = true;
8800
+ /** The collection a `splitAt`/`split` half came from. A capability report belongs to it. */ origin = null;
6833
8801
  /** Cuts over to memory execution, keeping the first cause. See `MemoryExecutionReason`. */ cutOverToMemory(reason) {
6834
8802
  this.nextExecutionTarget = "memory";
6835
8803
  if (this.nextExecutionReason == null) {
6836
8804
  this.nextExecutionReason = reason;
6837
8805
  }
6838
8806
  }
6839
- /**
6840
- * True when `split()` or `splitAt()` produced this collection.
6841
- *
6842
- * Those rebuild each half by re-adding its options, which re-derives execution targets
6843
- * without the options that caused them — a post-join filter alone in the memory half
6844
- * derives back to `"database"`. Anything reading `target` as a report of where work runs
6845
- * has to reject a derived collection; see `explainQuery`.
6846
- */ derived = false;
6847
- get isDerived() {
6848
- return this.derived;
6849
- }
6850
8807
  get items() {
6851
8808
  return this.options;
6852
8809
  }
@@ -6881,7 +8838,7 @@ class QueryOptionsCollection {
6881
8838
  if (filterValue.expression.type === "not-parsable") {
6882
8839
  this.cutOverToMemory("not-parsable");
6883
8840
  } else {
6884
- (0,_expressions_utils__rspack_import_0/* .forEach */.j)(filterValue.expression, (expression)=>{
8841
+ (0,_expressions_utils__rspack_import_2/* .forEach */.jJ)(filterValue.expression, (expression)=>{
6885
8842
  if ((0,_assertions__rspack_import_1.isPropertyExpression)(expression) && expression.property.isUnmapped) {
6886
8843
  // Cut over to memory execution, unmapped properties are not in the database and
6887
8844
  // cannot be queried
@@ -6896,6 +8853,11 @@ class QueryOptionsCollection {
6896
8853
  this.cutOverToMemory("renamed-property");
6897
8854
  return false;
6898
8855
  }
8856
+ if (comparesTypesThatCannotMatch(expression)) {
8857
+ _utilities__rspack_import_3/* .logger.warn */.vF.warn(mismatchWarning(expression));
8858
+ this.cutOverToMemory("predicate-error");
8859
+ return false;
8860
+ }
6899
8861
  return true;
6900
8862
  });
6901
8863
  }
@@ -6924,6 +8886,11 @@ class QueryOptionsCollection {
6924
8886
  this.cutOverToMemory("renamed-property");
6925
8887
  }
6926
8888
  }
8889
+ if ((name === "filter" || name === "sort") && (this.options.has("skip") || this.options.has("take"))) {
8890
+ // SQL emits WHERE before LIMIT and Mongo's find() filters before skipping, so an option
8891
+ // written after a window can only see the windowed rows if it runs after it.
8892
+ this.cutOverToMemory("after-window");
8893
+ }
6927
8894
  if (name === "join") {
6928
8895
  const joinValue = value;
6929
8896
  // A join whose two sides live on different plugins cannot be sent to EITHER of
@@ -6936,18 +8903,24 @@ class QueryOptionsCollection {
6936
8903
  this.cutOverToMemory("cross-plugin-join");
6937
8904
  }
6938
8905
  }
8906
+ // `executed` is the plan, not a record: nothing has run when an option is added. Every
8907
+ // consumer reads it after the plugin returned, so the optimistic window is never observed.
6939
8908
  const item = {
6940
8909
  index: this.nextIndex,
6941
- option: {
8910
+ option: this.nextExecutionTarget === "database" ? {
6942
8911
  name,
6943
- target: this.nextExecutionTarget,
6944
8912
  value,
6945
- ...this.nextExecutionReason == null ? {} : {
6946
- reason: this.nextExecutionReason
6947
- }
8913
+ target: "database",
8914
+ reason: "executed"
8915
+ } : {
8916
+ name,
8917
+ value,
8918
+ target: "memory",
8919
+ reason: this.nextExecutionReason ?? "not-parsable"
6948
8920
  }
6949
8921
  };
6950
8922
  this.nextIndex++;
8923
+ this.dirty = true;
6951
8924
  const found = this.options.get(name);
6952
8925
  this.options.set(name, [
6953
8926
  ...found ?? [],
@@ -6992,8 +8965,6 @@ class QueryOptionsCollection {
6992
8965
  const sortedItems = this.enumeratedItems.toSorted((a, b)=>a.index - b.index);
6993
8966
  const before = new QueryOptionsCollection();
6994
8967
  const after = new QueryOptionsCollection();
6995
- before.derived = true;
6996
- after.derived = true;
6997
8968
  let at = null;
6998
8969
  for(let i = 0, length = sortedItems.length; i < length; i++){
6999
8970
  const { option } = sortedItems[i];
@@ -7002,8 +8973,10 @@ class QueryOptionsCollection {
7002
8973
  continue;
7003
8974
  }
7004
8975
  const destination = at == null ? before : after;
7005
- destination.add(option.name, option.value);
8976
+ destination.adopt(sortedItems[i]);
7006
8977
  }
8978
+ before.origin = this.origin ?? this;
8979
+ after.origin = this.origin ?? this;
7007
8980
  return {
7008
8981
  before,
7009
8982
  at,
@@ -7035,23 +9008,99 @@ class QueryOptionsCollection {
7035
9008
  this.nextExecutionReason = nextExecutionReason;
7036
9009
  this.nextIndex = nextIndex;
7037
9010
  this.enumeratedItems = [];
9011
+ // Clearing the list is not enough now that staleness is a flag rather than a count:
9012
+ // without this, `resolveEnumeration` believes the empty list is current and every read
9013
+ // of the collection sees no options at all.
9014
+ this.dirty = true;
7038
9015
  };
7039
9016
  }
9017
+ /** Takes an item as it stands — same object, same index, same target and reason. */ adopt(item) {
9018
+ const found = this.options.get(item.option.name);
9019
+ this.options.set(item.option.name, [
9020
+ ...found ?? [],
9021
+ item
9022
+ ]);
9023
+ this.nextIndex = Math.max(this.nextIndex, item.index + 1);
9024
+ this.dirty = true;
9025
+ }
9026
+ /**
9027
+ * A plugin reporting that its engine cannot express one option.
9028
+ *
9029
+ * Core marks the rest of the database phase `not-reached`, because the database has to stop
9030
+ * there — a window applied in front of a filter that was not applied returns the wrong rows.
9031
+ * Passing the cascade through core is what makes it impossible for a plugin to mark a
9032
+ * non-contiguous cut.
9033
+ *
9034
+ * A report names a culprit and never un-names one, so reports commute.
9035
+ *
9036
+ * The option is not moved to the memory arm. It stays where it was planned, which is what keeps
9037
+ * a redirect distinguishable from something core sent to memory in the first place.
9038
+ */ reportMissingCapability(item) {
9039
+ this.report(item, "missing-capability");
9040
+ }
9041
+ /**
9042
+ * A plugin reporting that its engine would answer one option differently from JavaScript.
9043
+ *
9044
+ * Same cascade as `reportMissingCapability`, and a separate reason because the caller can act on
9045
+ * one and not the other. See `DatabaseExecutionReason`.
9046
+ */ reportEngineDivergence(item) {
9047
+ this.report(item, "engine-divergence");
9048
+ }
9049
+ report(item, reason) {
9050
+ // A half can only see its own slice, and the database has to stop for the whole dispatch.
9051
+ if (this.origin != null) {
9052
+ this.origin.report(item, reason);
9053
+ return;
9054
+ }
9055
+ this.resolveEnumeration();
9056
+ for (const candidate of this.enumeratedItems){
9057
+ if (candidate.option.target !== "database" || candidate.index < item.index) {
9058
+ continue;
9059
+ }
9060
+ if (candidate.index === item.index) {
9061
+ candidate.option.reason = reason;
9062
+ continue;
9063
+ }
9064
+ if (candidate.option.reason === "executed") {
9065
+ candidate.option.reason = "not-reached";
9066
+ }
9067
+ }
9068
+ }
9069
+ /**
9070
+ * Forgets what any previous dispatch reported.
9071
+ *
9072
+ * Capability is answered per dispatch, so a report is only an answer for the execution that
9073
+ * produced it. The items are shared with any snapshot, so a report mutated in place otherwise
9074
+ * survives a restore and a second terminal on the same queryable replays options the plugin
9075
+ * did run — a `skip` applied twice, over rows already windowed.
9076
+ */ forgetReports() {
9077
+ this.resolveEnumeration();
9078
+ for (const item of this.enumeratedItems){
9079
+ if (item.option.target === "database") {
9080
+ item.option.reason = "executed";
9081
+ }
9082
+ }
9083
+ }
9084
+ /** The options the database did not run, in the order they were written. */ notExecuted() {
9085
+ this.resolveEnumeration();
9086
+ return this.enumeratedItems.filter((item)=>item.option.target === "database" && item.option.reason !== "executed").toSorted((a, b)=>a.index - b.index);
9087
+ }
7040
9088
  split() {
7041
9089
  this.resolveEnumeration();
7042
9090
  const sortedItems = this.enumeratedItems.toSorted((a, b)=>a.index - b.index);
7043
9091
  const memoryQueryOptionsCollection = new QueryOptionsCollection();
7044
9092
  const databaseQueryOptionsCollection = new QueryOptionsCollection();
7045
- memoryQueryOptionsCollection.derived = true;
7046
- databaseQueryOptionsCollection.derived = true;
7047
9093
  for(let i = 0, length = sortedItems.length; i < length; i++){
7048
9094
  const sortedItem = sortedItems[i];
7049
- if (sortedItem.option.target === "database") {
7050
- databaseQueryOptionsCollection.add(sortedItem.option.name, sortedItem.option.value);
7051
- continue;
7052
- }
7053
- memoryQueryOptionsCollection.add(sortedItem.option.name, sortedItem.option.value);
7054
- }
9095
+ const half = sortedItem.option.target === "database" ? databaseQueryOptionsCollection : memoryQueryOptionsCollection;
9096
+ // The ITEM, not its name and value. Re-adding would re-derive target and reason from a
9097
+ // fresh cascade, and a memory option re-added alone comes back out as `database` with no
9098
+ // reason at all. Sharing it also means a plugin's report on the database half is the
9099
+ // same object the explanation reads.
9100
+ half.adopt(sortedItem);
9101
+ }
9102
+ memoryQueryOptionsCollection.origin = this.origin ?? this;
9103
+ databaseQueryOptionsCollection.origin = this.origin ?? this;
7055
9104
  return {
7056
9105
  memory: memoryQueryOptionsCollection,
7057
9106
  database: databaseQueryOptionsCollection
@@ -7097,8 +9146,11 @@ class QueryOptionsCollection {
7097
9146
  ].flat().toSorted((a, b)=>a.index - b.index);
7098
9147
  }
7099
9148
  resolveEnumeration() {
7100
- if (this.enumeratedItems.length != this.nextIndex) {
9149
+ // A flag, not a count: adopting leaves gaps in the indexes, so `length !== nextIndex` is
9150
+ // true forever on a half and the enumeration rebuilds on every read.
9151
+ if (this.dirty === true) {
7101
9152
  this.enumeratedItems = this.getEnumeration();
9153
+ this.dirty = false;
7102
9154
  }
7103
9155
  }
7104
9156
  forEach(iterator) {
@@ -8076,12 +10128,6 @@ class SchemaComputed extends SchemaBase {
8076
10128
  ;// CONCATENATED MODULE: ./src/schema/PropertyInfo.ts
8077
10129
 
8078
10130
 
8079
- const SUPPORTED_DESERIALIZATION_TYPES = new Set([
8080
- types/* .SchemaTypes.Boolean */.L.Boolean,
8081
- types/* .SchemaTypes.Date */.L.Date,
8082
- types/* .SchemaTypes.Number */.L.Number,
8083
- types/* .SchemaTypes.String */.L.String
8084
- ]);
8085
10131
  /**
8086
10132
  * Represents metadata and utilities for a property in a schema, including its type, name, parent, children, and serialization details.
8087
10133
  */ class PropertyInfo {
@@ -8201,9 +10247,6 @@ const SUPPORTED_DESERIALIZATION_TYPES = new Set([
8201
10247
  get isRenamed() {
8202
10248
  return !!this.from;
8203
10249
  }
8204
- get supportsDeserialization() {
8205
- return this.valueDeserializer != null || SUPPORTED_DESERIALIZATION_TYPES.has(this.type);
8206
- }
8207
10250
  _getPropertyChain() {
8208
10251
  if (this._propertyChainCache) {
8209
10252
  return this._propertyChainCache;
@@ -8456,7 +10499,7 @@ const SUPPORTED_DESERIALIZATION_TYPES = new Set([
8456
10499
  if (this.type === types/* .SchemaTypes.Boolean */.L.Boolean) {
8457
10500
  return Boolean(value);
8458
10501
  }
8459
- throw new Error(`Unsupported deserialization for type. Type: ${this.type}`);
10502
+ return value;
8460
10503
  }
8461
10504
  }
8462
10505
 
@@ -13499,12 +15542,14 @@ const isLogLevel = (value)=>typeof value === 'string' && LOG_LEVELS.includes(val
13499
15542
  const debug = process.env.DEBUG;
13500
15543
  if (debug === 'routier' || debug === '*') return 'debug';
13501
15544
  const env = "production"?.toLowerCase();
13502
- // `test` is deliberately absent. It used to be here, which meant no test suite anywhere
13503
- // could run Routier quietly. Opt in with DEBUG=routier or ROUTIER_LOG_LEVEL when a test
13504
- // needs the output.
13505
15545
  if (env === 'dev' || env === 'development') return 'debug';
13506
15546
  }
13507
- return 'silent';
15547
+ // Warnings are on unless something turns them off.
15548
+ //
15549
+ // Routier warns when a query returns correct rows a slower way than it could, or when a filter
15550
+ // compares types that can never match. Both are the caller's to act on, and a default of
15551
+ // `silent` meant the only people who ever saw them were the ones who already knew to look.
15552
+ return 'warn';
13508
15553
  };
13509
15554
  let level = resolveLevel();
13510
15555
  let rank = RANK[level];
@@ -13819,11 +15864,14 @@ __webpack_require__.d(__webpack_exports__, {
13819
15864
  Block: () => (/* reexport safe */ _codegen__rspack_import_1.Block),
13820
15865
  BulkPersistChanges: () => (/* reexport safe */ _collections__rspack_import_2.BulkPersistChanges),
13821
15866
  BulkPersistResult: () => (/* reexport safe */ _collections__rspack_import_2.BulkPersistResult),
15867
+ CALL_SOURCE: () => (/* reexport safe */ _expressions__rspack_import_4.CALL_SOURCE),
13822
15868
  CacheDbPlugin: () => (/* reexport safe */ _plugins__rspack_import_7.CacheDbPlugin),
15869
+ CallExpression: () => (/* reexport safe */ _expressions__rspack_import_4.CallExpression),
13823
15870
  CodeBuilder: () => (/* reexport safe */ _codegen__rspack_import_1.CodeBuilder),
13824
15871
  ComparatorExpression: () => (/* reexport safe */ _expressions__rspack_import_4.ComparatorExpression),
13825
15872
  ConcurrencyDbPlugin: () => (/* reexport safe */ _plugins__rspack_import_7.ConcurrencyDbPlugin),
13826
15873
  ContainerBlock: () => (/* reexport safe */ _codegen__rspack_import_1.ContainerBlock),
15874
+ DATABASE_EXECUTION_EXPLANATIONS: () => (/* reexport safe */ _plugins__rspack_import_7.DATABASE_EXECUTION_EXPLANATIONS),
13827
15875
  DEFAULT_SEMI_JOIN_KEY_THRESHOLD: () => (/* reexport safe */ _plugins__rspack_import_7.DEFAULT_SEMI_JOIN_KEY_THRESHOLD),
13828
15876
  DataTranslator: () => (/* reexport safe */ _plugins__rspack_import_7.DataTranslator),
13829
15877
  EXECUTED_QUERIES_UNSUPPORTED: () => (/* reexport safe */ _plugins__rspack_import_7.EXECUTED_QUERIES_UNSUPPORTED),
@@ -13831,6 +15879,7 @@ __webpack_require__.d(__webpack_exports__, {
13831
15879
  EmptyExpression: () => (/* reexport safe */ _expressions__rspack_import_4.EmptyExpression),
13832
15880
  EphemeralDataPlugin: () => (/* reexport safe */ _plugins__rspack_import_7.EphemeralDataPlugin),
13833
15881
  Expression: () => (/* reexport safe */ _expressions__rspack_import_4.Expression),
15882
+ FOLDABLE: () => (/* reexport safe */ _expressions__rspack_import_4.FOLDABLE),
13834
15883
  FunctionBuilder: () => (/* reexport safe */ _codegen__rspack_import_1.FunctionBuilder),
13835
15884
  FunctionFactoryBuilder: () => (/* reexport safe */ _codegen__rspack_import_1.FunctionFactoryBuilder),
13836
15885
  HashType: () => (/* reexport safe */ _schema__rspack_import_9.HashType),
@@ -13899,6 +15948,7 @@ __webpack_require__.d(__webpack_exports__, {
13899
15948
  TranslatedGroupValue: () => (/* reexport safe */ _plugins__rspack_import_7.TranslatedGroupValue),
13900
15949
  TranslatedSingleValue: () => (/* reexport safe */ _plugins__rspack_import_7.TranslatedSingleValue),
13901
15950
  TupleTranslator: () => (/* reexport safe */ _plugins__rspack_import_7.TupleTranslator),
15951
+ UNRESOLVED: () => (/* reexport safe */ _expressions__rspack_import_4.UNRESOLVED),
13902
15952
  ValueExpression: () => (/* reexport safe */ _expressions__rspack_import_4.ValueExpression),
13903
15953
  VariableBuilder: () => (/* reexport safe */ _codegen__rspack_import_1.VariableBuilder),
13904
15954
  WorkPipeline: () => (/* reexport safe */ _pipeline__rspack_import_6.WorkPipeline),
@@ -13910,6 +15960,7 @@ __webpack_require__.d(__webpack_exports__, {
13910
15960
  assertIsNumber: () => (/* reexport safe */ _assertions__rspack_import_0.assertIsNumber),
13911
15961
  assertString: () => (/* reexport safe */ _assertions__rspack_import_0.assertString),
13912
15962
  cast: () => (/* reexport safe */ _utilities__rspack_import_10.cast),
15963
+ childrenOf: () => (/* reexport safe */ _expressions__rspack_import_4.childrenOf),
13913
15964
  clone: () => (/* reexport safe */ _utilities__rspack_import_10.clone),
13914
15965
  collectingSink: () => (/* reexport safe */ _plugins__rspack_import_7.collectingSink),
13915
15966
  combineExpressions: () => (/* reexport safe */ _expressions__rspack_import_4.combineExpressions),
@@ -13918,15 +15969,21 @@ __webpack_require__.d(__webpack_exports__, {
13918
15969
  cosineDistance: () => (/* reexport safe */ _plugins__rspack_import_7.cosineDistance),
13919
15970
  createRequestHandler: () => (/* reexport safe */ _plugins__rspack_import_7.createRequestHandler),
13920
15971
  createStandardJsonSchemaProps: () => (/* reexport safe */ _schema__rspack_import_9.createStandardJsonSchemaProps),
15972
+ describeFilterAsJs: () => (/* reexport safe */ _plugins__rspack_import_7.describeFilterAsJs),
15973
+ describeFilters: () => (/* reexport safe */ _plugins__rspack_import_7.describeFilters),
15974
+ describeUnparsableFilter: () => (/* reexport safe */ _plugins__rspack_import_7.describeUnparsableFilter),
13921
15975
  deserializeBulkPersist: () => (/* reexport safe */ _plugins__rspack_import_7.deserializeBulkPersist),
13922
15976
  deserializePersistResult: () => (/* reexport safe */ _plugins__rspack_import_7.deserializePersistResult),
13923
15977
  deserializeQueryOptions: () => (/* reexport safe */ _plugins__rspack_import_7.deserializeQueryOptions),
13924
15978
  distinctJoinKeys: () => (/* reexport safe */ _plugins__rspack_import_7.distinctJoinKeys),
13925
15979
  evaluate: () => (/* reexport safe */ _expressions__rspack_import_4.evaluate),
13926
15980
  executeJoin: () => (/* reexport safe */ _plugins__rspack_import_7.executeJoin),
15981
+ executedQueriesOf: () => (/* reexport safe */ _plugins__rspack_import_7.executedQueriesOf),
13927
15982
  explainQuery: () => (/* reexport safe */ _plugins__rspack_import_7.explainQuery),
13928
15983
  extractTypeInfo: () => (/* reexport safe */ _schema__rspack_import_9.extractTypeInfo),
13929
15984
  fastHash: () => (/* reexport safe */ _utilities__rspack_import_10.fastHash),
15985
+ foldConstantCalls: () => (/* reexport safe */ _expressions__rspack_import_4.foldConstantCalls),
15986
+ foldedOperandValue: () => (/* reexport safe */ _expressions__rspack_import_4.foldedOperandValue),
13930
15987
  forEach: () => (/* reexport safe */ _expressions__rspack_import_4.forEach),
13931
15988
  formatExplanation: () => (/* reexport safe */ _plugins__rspack_import_7.formatExplanation),
13932
15989
  getLogLevel: () => (/* reexport safe */ _utilities__rspack_import_10.getLogLevel),
@@ -13935,7 +15992,9 @@ __webpack_require__.d(__webpack_exports__, {
13935
15992
  hash: () => (/* reexport safe */ _utilities__rspack_import_10.hash),
13936
15993
  hashJoin: () => (/* reexport safe */ _plugins__rspack_import_7.hashJoin),
13937
15994
  isArrayValued: () => (/* reexport safe */ _schema__rspack_import_9.isArrayValued),
15995
+ isCallExpression: () => (/* reexport safe */ _assertions__rspack_import_0.isCallExpression),
13938
15996
  isComparatorExpression: () => (/* reexport safe */ _assertions__rspack_import_0.isComparatorExpression),
15997
+ isDatabaseStep: () => (/* reexport safe */ _plugins__rspack_import_7.isDatabaseStep),
13939
15998
  isDate: () => (/* reexport safe */ _utilities__rspack_import_10.isDate),
13940
15999
  isEmptyExpression: () => (/* reexport safe */ _assertions__rspack_import_0.isEmptyExpression),
13941
16000
  isExpression: () => (/* reexport safe */ _assertions__rspack_import_0.isExpression),
@@ -13949,15 +16008,21 @@ __webpack_require__.d(__webpack_exports__, {
13949
16008
  loadJoinInnerSide: () => (/* reexport safe */ _plugins__rspack_import_7.loadJoinInnerSide),
13950
16009
  logger: () => (/* reexport safe */ _utilities__rspack_import_10.logger),
13951
16010
  loggerSink: () => (/* reexport safe */ _plugins__rspack_import_7.loggerSink),
16011
+ mappedResultColumns: () => (/* reexport safe */ _plugins__rspack_import_7.mappedResultColumns),
13952
16012
  measure: () => (/* reexport safe */ _performance__rspack_import_5.measure),
13953
16013
  nearestBy: () => (/* reexport safe */ _plugins__rspack_import_7.nearestBy),
13954
16014
  noop: () => (/* reexport safe */ _utilities__rspack_import_10.noop),
13955
16015
  now: () => (/* reexport safe */ _performance__rspack_import_5.now),
16016
+ operandValue: () => (/* reexport safe */ _expressions__rspack_import_4.operandValue),
16017
+ parameter: () => (/* reexport safe */ _plugins__rspack_import_7.parameter),
16018
+ parameteriseDocument: () => (/* reexport safe */ _plugins__rspack_import_7.parameteriseDocument),
13956
16019
  parseFragment: () => (/* reexport safe */ _expressions__rspack_import_4.parseFragment),
16020
+ peelCalls: () => (/* reexport safe */ _expressions__rspack_import_4.peelCalls),
13957
16021
  propertyInfoToJsonSchema: () => (/* reexport safe */ _schema__rspack_import_9.propertyInfoToJsonSchema),
13958
16022
  readJoinKey: () => (/* reexport safe */ _plugins__rspack_import_7.readJoinKey),
13959
16023
  rehydrateSchemaFromJsonSchema: () => (/* reexport safe */ _schema__rspack_import_9.rehydrateSchemaFromJsonSchema),
13960
16024
  rehydrateSchemaFromJsonString: () => (/* reexport safe */ _schema__rspack_import_9.rehydrateSchemaFromJsonString),
16025
+ renderCallAsJs: () => (/* reexport safe */ _expressions__rspack_import_4.renderCallAsJs),
13961
16026
  resetLogLevel: () => (/* reexport safe */ _utilities__rspack_import_10.resetLogLevel),
13962
16027
  resolveBulkPersistChanges: () => (/* reexport safe */ _utilities__rspack_import_10.resolveBulkPersistChanges),
13963
16028
  s: () => (/* reexport safe */ _schema__rspack_import_9.s),
@@ -13978,7 +16043,8 @@ __webpack_require__.d(__webpack_exports__, {
13978
16043
  unsafeCast: () => (/* reexport safe */ _utilities__rspack_import_10.unsafeCast),
13979
16044
  uuid: () => (/* reexport safe */ _utilities__rspack_import_10.uuid),
13980
16045
  uuidv4: () => (/* reexport safe */ _utilities__rspack_import_10.uuidv4),
13981
- withExecutedQueries: () => (/* reexport safe */ _plugins__rspack_import_7.withExecutedQueries)
16046
+ withExecutedQueries: () => (/* reexport safe */ _plugins__rspack_import_7.withExecutedQueries),
16047
+ withInnerSide: () => (/* reexport safe */ _plugins__rspack_import_7.withInnerSide)
13982
16048
  });
13983
16049
  /* import */ var _assertions__rspack_import_0 = __webpack_require__(126);
13984
16050
  /* import */ var _codegen__rspack_import_1 = __webpack_require__(80);
@@ -13987,7 +16053,7 @@ __webpack_require__.d(__webpack_exports__, {
13987
16053
  /* import */ var _expressions__rspack_import_4 = __webpack_require__(138);
13988
16054
  /* import */ var _performance__rspack_import_5 = __webpack_require__(971);
13989
16055
  /* import */ var _pipeline__rspack_import_6 = __webpack_require__(314);
13990
- /* import */ var _plugins__rspack_import_7 = __webpack_require__(454);
16056
+ /* import */ var _plugins__rspack_import_7 = __webpack_require__(640);
13991
16057
  /* import */ var _results__rspack_import_8 = __webpack_require__(264);
13992
16058
  /* import */ var _schema__rspack_import_9 = __webpack_require__(755);
13993
16059
  /* import */ var _utilities__rspack_import_10 = __webpack_require__(222);