@routier/core 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assertions/index.cjs +19 -8
- package/dist/assertions/index.cjs.map +1 -1
- package/dist/assertions/index.d.ts +5 -1
- package/dist/assertions/index.js +21 -9
- package/dist/assertions/index.js.map +1 -1
- package/dist/collections/MemoryDataCollection.d.ts +10 -0
- package/dist/collections/index.cjs +29 -4
- package/dist/collections/index.cjs.map +1 -1
- package/dist/collections/index.js +29 -4
- package/dist/collections/index.js.map +1 -1
- package/dist/expressions/callSource.d.ts +41 -0
- package/dist/expressions/evaluate.d.ts +3 -0
- package/dist/expressions/fold.d.ts +7 -0
- package/dist/expressions/index.cjs +1754 -233
- package/dist/expressions/index.cjs.map +1 -1
- package/dist/expressions/index.d.ts +2 -0
- package/dist/expressions/index.js +1765 -234
- package/dist/expressions/index.js.map +1 -1
- package/dist/expressions/types.d.ts +45 -26
- package/dist/expressions/utils.d.ts +19 -1
- package/dist/index.cjs +2415 -364
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2755 -684
- package/dist/index.js.map +1 -1
- package/dist/performance/index.cjs +6 -4
- package/dist/performance/index.cjs.map +1 -1
- package/dist/performance/index.js +6 -4
- package/dist/performance/index.js.map +1 -1
- package/dist/pipeline/index.cjs +6 -4
- package/dist/pipeline/index.cjs.map +1 -1
- package/dist/pipeline/index.js +6 -4
- package/dist/pipeline/index.js.map +1 -1
- package/dist/plugins/index.cjs +2309 -316
- package/dist/plugins/index.cjs.map +1 -1
- package/dist/plugins/index.js +2313 -311
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/query/QueryOptionsCollection.d.ts +38 -10
- package/dist/plugins/query/describeFilter.d.ts +83 -0
- package/dist/plugins/query/explain.d.ts +71 -9
- package/dist/plugins/query/index.d.ts +1 -0
- package/dist/plugins/query/join.d.ts +4 -1
- package/dist/plugins/query/types.d.ts +36 -4
- package/dist/schema/PropertyInfo.d.ts +0 -1
- package/dist/schema/index.cjs +7 -14
- package/dist/schema/index.cjs.map +1 -1
- package/dist/schema/index.js +7 -14
- package/dist/schema/index.js.map +1 -1
- package/dist/utilities/index.cjs +242 -49
- package/dist/utilities/index.cjs.map +1 -1
- package/dist/utilities/index.js +242 -49
- package/dist/utilities/index.js.map +1 -1
- package/package.json +1 -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
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
"
|
|
1158
|
-
"
|
|
1159
|
-
"
|
|
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
|
|
1174
|
-
|
|
1175
|
-
|
|
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
|
|
1178
|
-
//
|
|
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 (
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
1289
|
-
const right =
|
|
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
|
-
|
|
1693
|
+
43(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
1329
1694
|
__webpack_require__.d(__webpack_exports__, {
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
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
|
|
1348
|
-
/* import */ var
|
|
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
|
|
1351
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1550
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
2650
|
+
constructor(schema, stream, scope, paramsName, params){
|
|
1823
2651
|
this.schema = schema;
|
|
1824
2652
|
this.stream = stream;
|
|
1825
|
-
this.
|
|
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
|
-
|
|
1886
|
-
|
|
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
|
-
|
|
1899
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
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:
|
|
2051
|
-
|
|
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
|
-
|
|
2124
|
-
|
|
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
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
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 */
|
|
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 */
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2529
|
-
|
|
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)=>
|
|
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
|
|
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.
|
|
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.
|
|
4035
|
+
const parser = new ExpressionParser(schema, stream, shape.scope, shape.paramsName, params);
|
|
2565
4036
|
paramsName = shape.paramsName;
|
|
2566
|
-
template = parser.
|
|
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:
|
|
4046
|
+
template: refused,
|
|
2575
4047
|
paramsName: null
|
|
2576
4048
|
});
|
|
2577
4049
|
}
|
|
2578
4050
|
warn(error);
|
|
2579
|
-
return
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2640
|
-
|
|
4105
|
+
number: Number.isNaN(value) ? "NaN" : value > 0 ? "Infinity" : "-Infinity"
|
|
4106
|
+
};
|
|
4107
|
+
}
|
|
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
|
+
}
|
|
2641
4117
|
};
|
|
2642
4118
|
}
|
|
2643
|
-
|
|
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
|
-
|
|
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
|
|
2653
|
-
return
|
|
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 (
|
|
2656
|
-
return
|
|
4138
|
+
if ("undefined" in value) {
|
|
4139
|
+
return undefined;
|
|
2657
4140
|
}
|
|
2658
|
-
if (
|
|
2659
|
-
return value.
|
|
4141
|
+
if ("regex" in value) {
|
|
4142
|
+
return new RegExp(value.regex.source, value.regex.flags);
|
|
2660
4143
|
}
|
|
2661
|
-
if (
|
|
2662
|
-
return value.
|
|
4144
|
+
if ("bigint" in value) {
|
|
4145
|
+
return BigInt(value.bigint);
|
|
2663
4146
|
}
|
|
2664
|
-
return value.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2751
|
-
value: valueToJson(value.value)
|
|
2752
|
-
|
|
2753
|
-
|
|
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
|
-
|
|
2757
|
-
|
|
4252
|
+
const reason = expression.reason;
|
|
4253
|
+
return reason == null ? {
|
|
4254
|
+
type: "not-parsable"
|
|
2758
4255
|
} : {
|
|
2759
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
2804
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2874
|
-
|
|
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
|
-
|
|
2888
|
-
|
|
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
|
-
|
|
2906
|
-
|
|
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
|
-
|
|
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,13 +4815,15 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
3270
4815
|
splitSendableOptions: () => (/* reexport */ splitSendableOptions),
|
|
3271
4816
|
executeJoin: () => (/* reexport */ executeJoin),
|
|
3272
4817
|
formatExplanation: () => (/* reexport */ formatExplanation),
|
|
3273
|
-
|
|
4818
|
+
parameteriseDocument: () => (/* reexport */ parameteriseDocument),
|
|
3274
4819
|
mappedResultColumns: () => (/* reexport */ mappedResultColumns),
|
|
3275
4820
|
explainQuery: () => (/* reexport */ explainQuery),
|
|
4821
|
+
parameter: () => (/* reexport */ parameter),
|
|
4822
|
+
serializePersistResult: () => (/* reexport */ serializePersistResult),
|
|
4823
|
+
applyInnerOptions: () => (/* reexport */ applyInnerOptions),
|
|
3276
4824
|
cosineDistance: () => (/* reexport */ cosineDistance),
|
|
3277
4825
|
RetryDbPlugin: () => (/* reexport */ RetryDbPlugin),
|
|
3278
4826
|
BatchingDbPlugin: () => (/* reexport */ BatchingDbPlugin),
|
|
3279
|
-
applyInnerOptions: () => (/* reexport */ applyInnerOptions),
|
|
3280
4827
|
MEMORY_EXECUTION_EXPLANATIONS: () => (/* reexport */ MEMORY_EXECUTION_EXPLANATIONS),
|
|
3281
4828
|
TupleTranslator: () => (/* reexport */ TupleTranslator),
|
|
3282
4829
|
TranslatedSingleValue: () => (/* reexport */ TranslatedSingleValue),
|
|
@@ -3284,27 +4831,33 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
3284
4831
|
TranslatedGroupValue: () => (/* reexport */ TranslatedGroupValue),
|
|
3285
4832
|
deserializeBulkPersist: () => (/* reexport */ deserializeBulkPersist),
|
|
3286
4833
|
EXECUTED_QUERIES_UNSUPPORTED: () => (/* reexport */ EXECUTED_QUERIES_UNSUPPORTED),
|
|
3287
|
-
|
|
4834
|
+
executedQueriesOf: () => (/* reexport */ executedQueriesOf),
|
|
3288
4835
|
deserializePersistResult: () => (/* reexport */ deserializePersistResult),
|
|
4836
|
+
joinInPlugin: () => (/* reexport */ joinInPlugin),
|
|
3289
4837
|
loggerSink: () => (/* reexport */ loggerSink),
|
|
3290
4838
|
nearestBy: () => (/* reexport */ nearestBy),
|
|
3291
|
-
readJoinKey: () => (/* reexport */ readJoinKey),
|
|
3292
4839
|
hashJoin: () => (/* reexport */ hashJoin),
|
|
3293
4840
|
EphemeralDataPlugin: () => (/* reexport */ EphemeralDataPlugin),
|
|
3294
4841
|
QueryOptionsCollection: () => (/* reexport */ QueryOptionsCollection/* .QueryOptionsCollection */.H),
|
|
3295
|
-
|
|
3296
|
-
|
|
4842
|
+
describeUnparsableFilter: () => (/* reexport */ describeUnparsableFilter),
|
|
4843
|
+
readJoinKey: () => (/* reexport */ readJoinKey),
|
|
3297
4844
|
loadJoinInnerSide: () => (/* reexport */ loadJoinInnerSide),
|
|
3298
4845
|
DataTranslator: () => (/* reexport */ DataTranslator),
|
|
3299
|
-
|
|
4846
|
+
serializeBulkPersist: () => (/* reexport */ serializeBulkPersist),
|
|
4847
|
+
toEntityShape: () => (/* reexport */ toEntityShape),
|
|
3300
4848
|
TelemetryDbPlugin: () => (/* reexport */ TelemetryDbPlugin),
|
|
4849
|
+
withExecutedQueries: () => (/* reexport */ withExecutedQueries),
|
|
3301
4850
|
createRequestHandler: () => (/* reexport */ createRequestHandler),
|
|
3302
4851
|
SqlTranslator: () => (/* reexport */ SqlTranslator),
|
|
4852
|
+
withInnerSide: () => (/* reexport */ withInnerSide),
|
|
3303
4853
|
QueryOrdering: () => (/* reexport */ types_QueryOrdering),
|
|
4854
|
+
describeFilterAsJs: () => (/* reexport */ describeFilterAsJs),
|
|
3304
4855
|
serializeQueryOptions: () => (/* reexport */ serializeQueryOptions),
|
|
3305
|
-
distinctJoinKeys: () => (/* reexport */ distinctJoinKeys),
|
|
3306
4856
|
JsonTranslator: () => (/* reexport */ JsonTranslator),
|
|
3307
|
-
|
|
4857
|
+
DATABASE_EXECUTION_EXPLANATIONS: () => (/* reexport */ DATABASE_EXECUTION_EXPLANATIONS),
|
|
4858
|
+
distinctJoinKeys: () => (/* reexport */ distinctJoinKeys),
|
|
4859
|
+
deserializeQueryOptions: () => (/* reexport */ deserializeQueryOptions),
|
|
4860
|
+
isDatabaseStep: () => (/* reexport */ isDatabaseStep)
|
|
3308
4861
|
});
|
|
3309
4862
|
|
|
3310
4863
|
;// CONCATENATED MODULE: ./src/plugins/resultShape.ts
|
|
@@ -3413,6 +4966,10 @@ class DataTranslator {
|
|
|
3413
4966
|
translate(data) {
|
|
3414
4967
|
const isTransformed = this.query.options.hasTransformations();
|
|
3415
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
|
+
}
|
|
3416
4973
|
data = this.functionMap[item.name](data, item);
|
|
3417
4974
|
});
|
|
3418
4975
|
if (Array.isArray(data)) {
|
|
@@ -3825,7 +5382,7 @@ class Query {
|
|
|
3825
5382
|
* Only a plugin that runs its outer query FIRST can supply these, and most run this loader
|
|
3826
5383
|
* before anything else — so it is optional, and its absence costs a wider inner read rather
|
|
3827
5384
|
* than a wrong one.
|
|
3828
|
-
*/ outerKeys)=>{
|
|
5385
|
+
*/ outerKeys, /** Where the inner read reports what it executed. Defaults to the outer read's own list. */ innerExecutedQueries)=>{
|
|
3829
5386
|
const joinOption = event.operation.options.getLast("join");
|
|
3830
5387
|
if (joinOption == null) {
|
|
3831
5388
|
done({
|
|
@@ -3853,9 +5410,10 @@ class Query {
|
|
|
3853
5410
|
action: "query",
|
|
3854
5411
|
reason: "join inner side",
|
|
3855
5412
|
explain: event.explain,
|
|
3856
|
-
// The
|
|
3857
|
-
//
|
|
3858
|
-
|
|
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
|
|
3859
5417
|
};
|
|
3860
5418
|
query(innerEvent, (result)=>{
|
|
3861
5419
|
if (result.ok === Result/* .PluginEventResult.ERROR */.D.ERROR) {
|
|
@@ -3932,6 +5490,12 @@ class Query {
|
|
|
3932
5490
|
return;
|
|
3933
5491
|
}
|
|
3934
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
|
+
}
|
|
3935
5499
|
// Storage shape: the plugin returns rows as it holds them, and deserialization is what
|
|
3936
5500
|
// `executeJoin` does per side below.
|
|
3937
5501
|
const outerKeys = distinctJoinKeys(outerRows, at.value.outerKey, at.value.semiJoinKeyThreshold, {
|
|
@@ -4047,9 +5611,7 @@ class JsonTranslator extends DataTranslator {
|
|
|
4047
5611
|
if (field.property != null) {
|
|
4048
5612
|
const value = field.property.getValue(data[i]);
|
|
4049
5613
|
if (value != null) {
|
|
4050
|
-
|
|
4051
|
-
const resolvedValue = field.property.supportsDeserialization ? field.property.deserialize(value) : value;
|
|
4052
|
-
field.property.setValue(data[i], resolvedValue);
|
|
5614
|
+
field.property.setValue(data[i], field.property.deserialize(value));
|
|
4053
5615
|
}
|
|
4054
5616
|
}
|
|
4055
5617
|
}
|
|
@@ -4073,9 +5635,7 @@ class JsonTranslator extends DataTranslator {
|
|
|
4073
5635
|
if (field.property != null) {
|
|
4074
5636
|
const value = field.property.getValue(data[i]);
|
|
4075
5637
|
if (value != null) {
|
|
4076
|
-
|
|
4077
|
-
const resolvedValue = field.property.supportsDeserialization ? field.property.deserialize(value) : value;
|
|
4078
|
-
field.property.setValue(item, resolvedValue);
|
|
5638
|
+
field.property.setValue(item, field.property.deserialize(value));
|
|
4079
5639
|
continue;
|
|
4080
5640
|
}
|
|
4081
5641
|
// The property exists, lets set it to the value (null/undefined)
|
|
@@ -4416,9 +5976,12 @@ class SqlTranslator extends DataTranslator {
|
|
|
4416
5976
|
for(let j = 0, l = option.value.fields.length; j < l; j++){
|
|
4417
5977
|
const field = option.value.fields[j];
|
|
4418
5978
|
if (field.property != null) {
|
|
4419
|
-
const
|
|
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);
|
|
4420
5983
|
if (value != null) {
|
|
4421
|
-
field.property.setValue(
|
|
5984
|
+
field.property.setValue(row, field.property.deserialize(value));
|
|
4422
5985
|
}
|
|
4423
5986
|
}
|
|
4424
5987
|
}
|
|
@@ -4555,6 +6118,183 @@ class SqlTranslator extends DataTranslator {
|
|
|
4555
6118
|
|
|
4556
6119
|
|
|
4557
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
|
+
|
|
4558
6298
|
;// CONCATENATED MODULE: ./src/plugins/query/explain.ts
|
|
4559
6299
|
|
|
4560
6300
|
/**
|
|
@@ -4569,12 +6309,23 @@ class SqlTranslator extends DataTranslator {
|
|
|
4569
6309
|
"map-rename": "A map renames or drops properties, so every option after it refers to names the database does not have.",
|
|
4570
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.",
|
|
4571
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.",
|
|
4572
|
-
"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."
|
|
4573
6315
|
};
|
|
4574
6316
|
const EXECUTED_QUERIES_UNSUPPORTED = "This plugin did not report what it executed. It may not support explain.";
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
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";
|
|
4578
6329
|
/**
|
|
4579
6330
|
* The reportable shape of one option's value.
|
|
4580
6331
|
*
|
|
@@ -4655,12 +6406,16 @@ const explainedOptionsOf = (options)=>{
|
|
|
4655
6406
|
options.forEach((option)=>explained.push(explainedOptionOf(option, index++)));
|
|
4656
6407
|
return explained;
|
|
4657
6408
|
};
|
|
6409
|
+
/** Every sentence, whoever decided — the summary reads the same either way. */ const EXPLANATIONS = {
|
|
6410
|
+
...MEMORY_EXECUTION_EXPLANATIONS,
|
|
6411
|
+
...DATABASE_EXECUTION_EXPLANATIONS
|
|
6412
|
+
};
|
|
4658
6413
|
const summarize = (steps)=>{
|
|
4659
6414
|
const reasons = [];
|
|
4660
6415
|
let database = 0;
|
|
4661
6416
|
let memory = 0;
|
|
4662
6417
|
for (const step of steps){
|
|
4663
|
-
if (step
|
|
6418
|
+
if (isDatabaseStep(step)) {
|
|
4664
6419
|
database += step.options.length;
|
|
4665
6420
|
continue;
|
|
4666
6421
|
}
|
|
@@ -4670,7 +6425,7 @@ const summarize = (steps)=>{
|
|
|
4670
6425
|
}
|
|
4671
6426
|
}
|
|
4672
6427
|
const counts = `${database} ${database === 1 ? "option ran" : "options ran"} in the database, ${memory} ran in memory.`;
|
|
4673
|
-
const causes = reasons.map((reason)=>
|
|
6428
|
+
const causes = reasons.map((reason)=>EXPLANATIONS[reason]).join(" ");
|
|
4674
6429
|
return {
|
|
4675
6430
|
database,
|
|
4676
6431
|
memory,
|
|
@@ -4678,50 +6433,87 @@ const summarize = (steps)=>{
|
|
|
4678
6433
|
explanation: causes.length === 0 ? counts : `${counts} ${causes}`
|
|
4679
6434
|
};
|
|
4680
6435
|
};
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
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)=>{
|
|
4693
6469
|
const steps = [];
|
|
4694
6470
|
let index = 0;
|
|
4695
6471
|
options.forEach((option)=>{
|
|
4696
6472
|
const explained = explainedOptionOf(option, index++);
|
|
4697
6473
|
const current = steps[steps.length - 1];
|
|
4698
|
-
|
|
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) {
|
|
4699
6478
|
current.options.push(explained);
|
|
4700
6479
|
return;
|
|
4701
6480
|
}
|
|
4702
|
-
steps.push({
|
|
4703
|
-
step:
|
|
6481
|
+
steps.push(outcome.reason == null ? {
|
|
6482
|
+
step: 0,
|
|
4704
6483
|
of: 0,
|
|
4705
|
-
executedIn:
|
|
4706
|
-
description: option.target === "database" ? DATABASE_STEP_DESCRIPTION : MEMORY_STEP_DESCRIPTION,
|
|
6484
|
+
executedIn: ranIn,
|
|
4707
6485
|
options: [
|
|
4708
6486
|
explained
|
|
4709
6487
|
],
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
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
|
|
4714
6500
|
});
|
|
4715
6501
|
});
|
|
4716
|
-
|
|
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") {
|
|
4717
6506
|
steps.unshift({
|
|
4718
6507
|
step: 0,
|
|
4719
6508
|
of: 0,
|
|
4720
|
-
executedIn:
|
|
4721
|
-
|
|
4722
|
-
|
|
6509
|
+
executedIn: ranIn,
|
|
6510
|
+
options: [],
|
|
6511
|
+
executedQueries: []
|
|
4723
6512
|
});
|
|
4724
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)=>{
|
|
4725
6517
|
for(let i = 0; i < steps.length; i++){
|
|
4726
6518
|
steps[i].step = i + 1;
|
|
4727
6519
|
steps[i].of = steps.length;
|
|
@@ -4736,10 +6528,11 @@ const summarize = (steps)=>{
|
|
|
4736
6528
|
* post-join filter alone in the memory half derives back to `"database"`, and the document
|
|
4737
6529
|
* would report memory work as having run in the database.
|
|
4738
6530
|
*/ const explainQuery = (options, context)=>{
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
6531
|
+
const executionSteps = numbered(toExecutionSteps(options, {
|
|
6532
|
+
kind: "database",
|
|
6533
|
+
database: context.database,
|
|
6534
|
+
plugin: context.pluginKind
|
|
6535
|
+
}));
|
|
4743
6536
|
return {
|
|
4744
6537
|
collection: context.collection,
|
|
4745
6538
|
database: context.database,
|
|
@@ -4766,7 +6559,7 @@ const summarize = (steps)=>{
|
|
|
4766
6559
|
// Only the first database step: a plugin reports what IT ran, and everything it ran
|
|
4767
6560
|
// was sent as one dispatch. Stamping the same statements onto a second database step
|
|
4768
6561
|
// would claim they ran twice.
|
|
4769
|
-
if (step
|
|
6562
|
+
if (isDatabaseStep(step) === false || attached === true) {
|
|
4770
6563
|
return {
|
|
4771
6564
|
...step,
|
|
4772
6565
|
options: [
|
|
@@ -4799,8 +6592,47 @@ const summarize = (steps)=>{
|
|
|
4799
6592
|
executionSteps
|
|
4800
6593
|
};
|
|
4801
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
|
+
};
|
|
4802
6632
|
|
|
4803
6633
|
;// CONCATENATED MODULE: ./src/plugins/query/formatExplanation.ts
|
|
6634
|
+
|
|
6635
|
+
|
|
4804
6636
|
const OPTION_LABEL_WIDTH = 8;
|
|
4805
6637
|
const WRAP_WIDTH = 68;
|
|
4806
6638
|
/** Wraps `text` to `WRAP_WIDTH`, prefixing every line with `indent`. */ const wrap = (text, indent)=>{
|
|
@@ -4826,29 +6658,41 @@ const COMPARATOR_SYMBOLS = {
|
|
|
4826
6658
|
"less-than": "<",
|
|
4827
6659
|
"less-than-equals": "<="
|
|
4828
6660
|
};
|
|
4829
|
-
const describeValue = (value)=>{
|
|
4830
|
-
if (value
|
|
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) {
|
|
4831
6666
|
return "?";
|
|
4832
6667
|
}
|
|
4833
|
-
if (value
|
|
4834
|
-
return
|
|
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;
|
|
6676
|
+
}
|
|
6677
|
+
if ("undefined" in value) {
|
|
6678
|
+
return "undefined";
|
|
4835
6679
|
}
|
|
4836
|
-
if (
|
|
4837
|
-
return value.
|
|
6680
|
+
if ("regex" in value) {
|
|
6681
|
+
return `/${value.regex.source}/${value.regex.flags}`;
|
|
4838
6682
|
}
|
|
4839
|
-
if (
|
|
4840
|
-
return
|
|
6683
|
+
if ("bigint" in value) {
|
|
6684
|
+
return `${value.bigint}n`;
|
|
4841
6685
|
}
|
|
4842
|
-
return value.
|
|
6686
|
+
return value.number;
|
|
4843
6687
|
};
|
|
4844
6688
|
/** Renders a serialized expression back to something close to the source predicate. */ const describeExpression = (expression)=>{
|
|
4845
6689
|
if (expression == null) {
|
|
4846
6690
|
return "?";
|
|
4847
6691
|
}
|
|
4848
|
-
if (expression.
|
|
6692
|
+
if (expression.type === "operator") {
|
|
4849
6693
|
return `${describeExpression(expression.left)} ${expression.operator} ${describeExpression(expression.right)}`;
|
|
4850
6694
|
}
|
|
4851
|
-
if (expression.
|
|
6695
|
+
if (expression.type === "comparator") {
|
|
4852
6696
|
const left = describeExpression(expression.left);
|
|
4853
6697
|
const right = describeExpression(expression.right);
|
|
4854
6698
|
const symbol = COMPARATOR_SYMBOLS[expression.comparator];
|
|
@@ -4857,13 +6701,24 @@ const describeValue = (value)=>{
|
|
|
4857
6701
|
}
|
|
4858
6702
|
return `${left} ${expression.negated === true ? "!==" : symbol} ${right}`;
|
|
4859
6703
|
}
|
|
4860
|
-
if (expression.
|
|
6704
|
+
if (expression.type === "property") {
|
|
4861
6705
|
return expression.path;
|
|
4862
6706
|
}
|
|
4863
|
-
if (expression.
|
|
6707
|
+
if (expression.type === "value") {
|
|
4864
6708
|
return describeValue(expression.value);
|
|
4865
6709
|
}
|
|
4866
|
-
|
|
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})`;
|
|
4867
6722
|
};
|
|
4868
6723
|
const describeOption = (option)=>{
|
|
4869
6724
|
const detail = option.detail;
|
|
@@ -4891,18 +6746,35 @@ const describeOption = (option)=>{
|
|
|
4891
6746
|
}
|
|
4892
6747
|
return "";
|
|
4893
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";
|
|
4894
6758
|
const formatStep = (step, lines)=>{
|
|
4895
|
-
const reason = step.reason == null ? "" : ` [${step.reason}]`;
|
|
4896
|
-
lines.push(` STEP ${step.step} of ${step.of} — ${step
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
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
|
+
}
|
|
4900
6768
|
}
|
|
4901
6769
|
lines.push("");
|
|
4902
6770
|
for (const option of step.options){
|
|
4903
6771
|
lines.push(` ${option.name.padEnd(OPTION_LABEL_WIDTH)} ${describeOption(option)}`.trimEnd());
|
|
4904
6772
|
}
|
|
4905
|
-
|
|
6773
|
+
if (isDatabaseStep(step) === false) {
|
|
6774
|
+
lines.push("");
|
|
6775
|
+
return;
|
|
6776
|
+
}
|
|
6777
|
+
for (const executed of step.executedQueries){
|
|
4906
6778
|
lines.push("");
|
|
4907
6779
|
lines.push(...executed.text.split("\n").map((line)=>` ${line}`));
|
|
4908
6780
|
if (executed.parameters != null && executed.parameters.length > 0) {
|
|
@@ -4951,8 +6823,11 @@ var types_QueryOrdering = /*#__PURE__*/ function(QueryOrdering) {
|
|
|
4951
6823
|
|
|
4952
6824
|
|
|
4953
6825
|
|
|
6826
|
+
|
|
4954
6827
|
// EXTERNAL MODULE: ./src/expressions/evaluate.ts
|
|
4955
6828
|
var evaluate = __webpack_require__(379);
|
|
6829
|
+
// EXTERNAL MODULE: ./src/expressions/fold.ts
|
|
6830
|
+
var fold = __webpack_require__(43);
|
|
4956
6831
|
;// CONCATENATED MODULE: ./src/plugins/wire/query.ts
|
|
4957
6832
|
|
|
4958
6833
|
|
|
@@ -5153,7 +7028,7 @@ const serializeQueryOptions = (options)=>{
|
|
|
5153
7028
|
}
|
|
5154
7029
|
case "filter":
|
|
5155
7030
|
{
|
|
5156
|
-
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));
|
|
5157
7032
|
options.add("filter", {
|
|
5158
7033
|
filter: (0,evaluate/* .toStrictPredicate */.wS)(expression),
|
|
5159
7034
|
expression,
|
|
@@ -5837,7 +7712,8 @@ var TrampolinePipeline = __webpack_require__(416);
|
|
|
5837
7712
|
if (!(0,assertions.isPropertyExpression)(left) || !(0,assertions.isValueExpression)(right)) {
|
|
5838
7713
|
return null;
|
|
5839
7714
|
}
|
|
5840
|
-
|
|
7715
|
+
// A called property is a CallExpression, so it fails the isPropertyExpression check above
|
|
7716
|
+
if (left.property.isKey !== true || right.value == null) {
|
|
5841
7717
|
return null;
|
|
5842
7718
|
}
|
|
5843
7719
|
return {
|
|
@@ -6210,11 +8086,17 @@ class EphemeralDataPlugin {
|
|
|
6210
8086
|
* collection to pair it with three rows.
|
|
6211
8087
|
*
|
|
6212
8088
|
* `cloned` is in storage shape, so the keys are read by resolved column name.
|
|
6213
|
-
*/
|
|
6214
|
-
|
|
6215
|
-
|
|
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));
|
|
6216
8097
|
event.executedQueries.push({
|
|
6217
|
-
text: `${operation.schema.collectionName}: scanned ${cloned.length} in-memory
|
|
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
|
|
6218
8100
|
});
|
|
6219
8101
|
const joinOption = operation.options.getLast("join");
|
|
6220
8102
|
const outerKeys = joinOption == null ? null : distinctJoinKeys(cloned, joinOption.value.outerKey, joinOption.value.semiJoinKeyThreshold, {
|
|
@@ -6389,6 +8271,29 @@ class TelemetryDbPlugin {
|
|
|
6389
8271
|
return JSON.stringify(option.value ?? null);
|
|
6390
8272
|
}
|
|
6391
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
|
+
};
|
|
6392
8297
|
class CacheDbPlugin {
|
|
6393
8298
|
plugin;
|
|
6394
8299
|
max;
|
|
@@ -6427,7 +8332,7 @@ class CacheDbPlugin {
|
|
|
6427
8332
|
* the next update would be written UNCHECKED with no error anywhere.
|
|
6428
8333
|
* Pinned by `datastore/src/collections/wrapperStacking.test.ts`.
|
|
6429
8334
|
*/ rebuild(entry) {
|
|
6430
|
-
return new entry.construct(structuredClone(entry.value), entry.isTransformed);
|
|
8335
|
+
return new entry.construct(reviveDates(structuredClone(entry.value)), entry.isTransformed);
|
|
6431
8336
|
}
|
|
6432
8337
|
query(event, done) {
|
|
6433
8338
|
const key = this.keyFor(event);
|
|
@@ -6450,6 +8355,14 @@ class CacheDbPlugin {
|
|
|
6450
8355
|
done(result);
|
|
6451
8356
|
return;
|
|
6452
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
|
+
}
|
|
6453
8366
|
this.store(key, result.data);
|
|
6454
8367
|
// The caller gets a rebuilt value too, not the one just stored, so that mutating
|
|
6455
8368
|
// the result of a MISS cannot corrupt what the next hit returns.
|
|
@@ -6835,32 +8748,62 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
6835
8748
|
H: () => (QueryOptionsCollection)
|
|
6836
8749
|
});
|
|
6837
8750
|
/* import */ var _assertions__rspack_import_1 = __webpack_require__(126);
|
|
6838
|
-
/* import */ var
|
|
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
|
+
|
|
6839
8755
|
|
|
6840
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
|
+
};
|
|
6841
8793
|
class QueryOptionsCollection {
|
|
6842
8794
|
options = new Map();
|
|
6843
8795
|
nextExecutionTarget = "database";
|
|
6844
8796
|
nextExecutionReason = null;
|
|
6845
8797
|
nextIndex = 0;
|
|
6846
8798
|
enumeratedItems = [];
|
|
8799
|
+
dirty = true;
|
|
8800
|
+
/** The collection a `splitAt`/`split` half came from. A capability report belongs to it. */ origin = null;
|
|
6847
8801
|
/** Cuts over to memory execution, keeping the first cause. See `MemoryExecutionReason`. */ cutOverToMemory(reason) {
|
|
6848
8802
|
this.nextExecutionTarget = "memory";
|
|
6849
8803
|
if (this.nextExecutionReason == null) {
|
|
6850
8804
|
this.nextExecutionReason = reason;
|
|
6851
8805
|
}
|
|
6852
8806
|
}
|
|
6853
|
-
/**
|
|
6854
|
-
* True when `split()` or `splitAt()` produced this collection.
|
|
6855
|
-
*
|
|
6856
|
-
* Those rebuild each half by re-adding its options, which re-derives execution targets
|
|
6857
|
-
* without the options that caused them — a post-join filter alone in the memory half
|
|
6858
|
-
* derives back to `"database"`. Anything reading `target` as a report of where work runs
|
|
6859
|
-
* has to reject a derived collection; see `explainQuery`.
|
|
6860
|
-
*/ derived = false;
|
|
6861
|
-
get isDerived() {
|
|
6862
|
-
return this.derived;
|
|
6863
|
-
}
|
|
6864
8807
|
get items() {
|
|
6865
8808
|
return this.options;
|
|
6866
8809
|
}
|
|
@@ -6895,7 +8838,7 @@ class QueryOptionsCollection {
|
|
|
6895
8838
|
if (filterValue.expression.type === "not-parsable") {
|
|
6896
8839
|
this.cutOverToMemory("not-parsable");
|
|
6897
8840
|
} else {
|
|
6898
|
-
(0,
|
|
8841
|
+
(0,_expressions_utils__rspack_import_2/* .forEach */.jJ)(filterValue.expression, (expression)=>{
|
|
6899
8842
|
if ((0,_assertions__rspack_import_1.isPropertyExpression)(expression) && expression.property.isUnmapped) {
|
|
6900
8843
|
// Cut over to memory execution, unmapped properties are not in the database and
|
|
6901
8844
|
// cannot be queried
|
|
@@ -6910,6 +8853,11 @@ class QueryOptionsCollection {
|
|
|
6910
8853
|
this.cutOverToMemory("renamed-property");
|
|
6911
8854
|
return false;
|
|
6912
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
|
+
}
|
|
6913
8861
|
return true;
|
|
6914
8862
|
});
|
|
6915
8863
|
}
|
|
@@ -6938,6 +8886,11 @@ class QueryOptionsCollection {
|
|
|
6938
8886
|
this.cutOverToMemory("renamed-property");
|
|
6939
8887
|
}
|
|
6940
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
|
+
}
|
|
6941
8894
|
if (name === "join") {
|
|
6942
8895
|
const joinValue = value;
|
|
6943
8896
|
// A join whose two sides live on different plugins cannot be sent to EITHER of
|
|
@@ -6950,18 +8903,24 @@ class QueryOptionsCollection {
|
|
|
6950
8903
|
this.cutOverToMemory("cross-plugin-join");
|
|
6951
8904
|
}
|
|
6952
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.
|
|
6953
8908
|
const item = {
|
|
6954
8909
|
index: this.nextIndex,
|
|
6955
|
-
option: {
|
|
8910
|
+
option: this.nextExecutionTarget === "database" ? {
|
|
6956
8911
|
name,
|
|
6957
|
-
target: this.nextExecutionTarget,
|
|
6958
8912
|
value,
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
8913
|
+
target: "database",
|
|
8914
|
+
reason: "executed"
|
|
8915
|
+
} : {
|
|
8916
|
+
name,
|
|
8917
|
+
value,
|
|
8918
|
+
target: "memory",
|
|
8919
|
+
reason: this.nextExecutionReason ?? "not-parsable"
|
|
6962
8920
|
}
|
|
6963
8921
|
};
|
|
6964
8922
|
this.nextIndex++;
|
|
8923
|
+
this.dirty = true;
|
|
6965
8924
|
const found = this.options.get(name);
|
|
6966
8925
|
this.options.set(name, [
|
|
6967
8926
|
...found ?? [],
|
|
@@ -7006,8 +8965,6 @@ class QueryOptionsCollection {
|
|
|
7006
8965
|
const sortedItems = this.enumeratedItems.toSorted((a, b)=>a.index - b.index);
|
|
7007
8966
|
const before = new QueryOptionsCollection();
|
|
7008
8967
|
const after = new QueryOptionsCollection();
|
|
7009
|
-
before.derived = true;
|
|
7010
|
-
after.derived = true;
|
|
7011
8968
|
let at = null;
|
|
7012
8969
|
for(let i = 0, length = sortedItems.length; i < length; i++){
|
|
7013
8970
|
const { option } = sortedItems[i];
|
|
@@ -7016,8 +8973,10 @@ class QueryOptionsCollection {
|
|
|
7016
8973
|
continue;
|
|
7017
8974
|
}
|
|
7018
8975
|
const destination = at == null ? before : after;
|
|
7019
|
-
destination.
|
|
8976
|
+
destination.adopt(sortedItems[i]);
|
|
7020
8977
|
}
|
|
8978
|
+
before.origin = this.origin ?? this;
|
|
8979
|
+
after.origin = this.origin ?? this;
|
|
7021
8980
|
return {
|
|
7022
8981
|
before,
|
|
7023
8982
|
at,
|
|
@@ -7049,23 +9008,99 @@ class QueryOptionsCollection {
|
|
|
7049
9008
|
this.nextExecutionReason = nextExecutionReason;
|
|
7050
9009
|
this.nextIndex = nextIndex;
|
|
7051
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;
|
|
7052
9015
|
};
|
|
7053
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
|
+
}
|
|
7054
9088
|
split() {
|
|
7055
9089
|
this.resolveEnumeration();
|
|
7056
9090
|
const sortedItems = this.enumeratedItems.toSorted((a, b)=>a.index - b.index);
|
|
7057
9091
|
const memoryQueryOptionsCollection = new QueryOptionsCollection();
|
|
7058
9092
|
const databaseQueryOptionsCollection = new QueryOptionsCollection();
|
|
7059
|
-
memoryQueryOptionsCollection.derived = true;
|
|
7060
|
-
databaseQueryOptionsCollection.derived = true;
|
|
7061
9093
|
for(let i = 0, length = sortedItems.length; i < length; i++){
|
|
7062
9094
|
const sortedItem = sortedItems[i];
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
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;
|
|
7069
9104
|
return {
|
|
7070
9105
|
memory: memoryQueryOptionsCollection,
|
|
7071
9106
|
database: databaseQueryOptionsCollection
|
|
@@ -7111,8 +9146,11 @@ class QueryOptionsCollection {
|
|
|
7111
9146
|
].flat().toSorted((a, b)=>a.index - b.index);
|
|
7112
9147
|
}
|
|
7113
9148
|
resolveEnumeration() {
|
|
7114
|
-
|
|
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) {
|
|
7115
9152
|
this.enumeratedItems = this.getEnumeration();
|
|
9153
|
+
this.dirty = false;
|
|
7116
9154
|
}
|
|
7117
9155
|
}
|
|
7118
9156
|
forEach(iterator) {
|
|
@@ -8090,12 +10128,6 @@ class SchemaComputed extends SchemaBase {
|
|
|
8090
10128
|
;// CONCATENATED MODULE: ./src/schema/PropertyInfo.ts
|
|
8091
10129
|
|
|
8092
10130
|
|
|
8093
|
-
const SUPPORTED_DESERIALIZATION_TYPES = new Set([
|
|
8094
|
-
types/* .SchemaTypes.Boolean */.L.Boolean,
|
|
8095
|
-
types/* .SchemaTypes.Date */.L.Date,
|
|
8096
|
-
types/* .SchemaTypes.Number */.L.Number,
|
|
8097
|
-
types/* .SchemaTypes.String */.L.String
|
|
8098
|
-
]);
|
|
8099
10131
|
/**
|
|
8100
10132
|
* Represents metadata and utilities for a property in a schema, including its type, name, parent, children, and serialization details.
|
|
8101
10133
|
*/ class PropertyInfo {
|
|
@@ -8215,9 +10247,6 @@ const SUPPORTED_DESERIALIZATION_TYPES = new Set([
|
|
|
8215
10247
|
get isRenamed() {
|
|
8216
10248
|
return !!this.from;
|
|
8217
10249
|
}
|
|
8218
|
-
get supportsDeserialization() {
|
|
8219
|
-
return this.valueDeserializer != null || SUPPORTED_DESERIALIZATION_TYPES.has(this.type);
|
|
8220
|
-
}
|
|
8221
10250
|
_getPropertyChain() {
|
|
8222
10251
|
if (this._propertyChainCache) {
|
|
8223
10252
|
return this._propertyChainCache;
|
|
@@ -8470,7 +10499,7 @@ const SUPPORTED_DESERIALIZATION_TYPES = new Set([
|
|
|
8470
10499
|
if (this.type === types/* .SchemaTypes.Boolean */.L.Boolean) {
|
|
8471
10500
|
return Boolean(value);
|
|
8472
10501
|
}
|
|
8473
|
-
|
|
10502
|
+
return value;
|
|
8474
10503
|
}
|
|
8475
10504
|
}
|
|
8476
10505
|
|
|
@@ -13513,12 +15542,14 @@ const isLogLevel = (value)=>typeof value === 'string' && LOG_LEVELS.includes(val
|
|
|
13513
15542
|
const debug = process.env.DEBUG;
|
|
13514
15543
|
if (debug === 'routier' || debug === '*') return 'debug';
|
|
13515
15544
|
const env = "production"?.toLowerCase();
|
|
13516
|
-
// `test` is deliberately absent. It used to be here, which meant no test suite anywhere
|
|
13517
|
-
// could run Routier quietly. Opt in with DEBUG=routier or ROUTIER_LOG_LEVEL when a test
|
|
13518
|
-
// needs the output.
|
|
13519
15545
|
if (env === 'dev' || env === 'development') return 'debug';
|
|
13520
15546
|
}
|
|
13521
|
-
|
|
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';
|
|
13522
15553
|
};
|
|
13523
15554
|
let level = resolveLevel();
|
|
13524
15555
|
let rank = RANK[level];
|
|
@@ -13833,11 +15864,14 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
13833
15864
|
Block: () => (/* reexport safe */ _codegen__rspack_import_1.Block),
|
|
13834
15865
|
BulkPersistChanges: () => (/* reexport safe */ _collections__rspack_import_2.BulkPersistChanges),
|
|
13835
15866
|
BulkPersistResult: () => (/* reexport safe */ _collections__rspack_import_2.BulkPersistResult),
|
|
15867
|
+
CALL_SOURCE: () => (/* reexport safe */ _expressions__rspack_import_4.CALL_SOURCE),
|
|
13836
15868
|
CacheDbPlugin: () => (/* reexport safe */ _plugins__rspack_import_7.CacheDbPlugin),
|
|
15869
|
+
CallExpression: () => (/* reexport safe */ _expressions__rspack_import_4.CallExpression),
|
|
13837
15870
|
CodeBuilder: () => (/* reexport safe */ _codegen__rspack_import_1.CodeBuilder),
|
|
13838
15871
|
ComparatorExpression: () => (/* reexport safe */ _expressions__rspack_import_4.ComparatorExpression),
|
|
13839
15872
|
ConcurrencyDbPlugin: () => (/* reexport safe */ _plugins__rspack_import_7.ConcurrencyDbPlugin),
|
|
13840
15873
|
ContainerBlock: () => (/* reexport safe */ _codegen__rspack_import_1.ContainerBlock),
|
|
15874
|
+
DATABASE_EXECUTION_EXPLANATIONS: () => (/* reexport safe */ _plugins__rspack_import_7.DATABASE_EXECUTION_EXPLANATIONS),
|
|
13841
15875
|
DEFAULT_SEMI_JOIN_KEY_THRESHOLD: () => (/* reexport safe */ _plugins__rspack_import_7.DEFAULT_SEMI_JOIN_KEY_THRESHOLD),
|
|
13842
15876
|
DataTranslator: () => (/* reexport safe */ _plugins__rspack_import_7.DataTranslator),
|
|
13843
15877
|
EXECUTED_QUERIES_UNSUPPORTED: () => (/* reexport safe */ _plugins__rspack_import_7.EXECUTED_QUERIES_UNSUPPORTED),
|
|
@@ -13845,6 +15879,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
13845
15879
|
EmptyExpression: () => (/* reexport safe */ _expressions__rspack_import_4.EmptyExpression),
|
|
13846
15880
|
EphemeralDataPlugin: () => (/* reexport safe */ _plugins__rspack_import_7.EphemeralDataPlugin),
|
|
13847
15881
|
Expression: () => (/* reexport safe */ _expressions__rspack_import_4.Expression),
|
|
15882
|
+
FOLDABLE: () => (/* reexport safe */ _expressions__rspack_import_4.FOLDABLE),
|
|
13848
15883
|
FunctionBuilder: () => (/* reexport safe */ _codegen__rspack_import_1.FunctionBuilder),
|
|
13849
15884
|
FunctionFactoryBuilder: () => (/* reexport safe */ _codegen__rspack_import_1.FunctionFactoryBuilder),
|
|
13850
15885
|
HashType: () => (/* reexport safe */ _schema__rspack_import_9.HashType),
|
|
@@ -13913,6 +15948,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
13913
15948
|
TranslatedGroupValue: () => (/* reexport safe */ _plugins__rspack_import_7.TranslatedGroupValue),
|
|
13914
15949
|
TranslatedSingleValue: () => (/* reexport safe */ _plugins__rspack_import_7.TranslatedSingleValue),
|
|
13915
15950
|
TupleTranslator: () => (/* reexport safe */ _plugins__rspack_import_7.TupleTranslator),
|
|
15951
|
+
UNRESOLVED: () => (/* reexport safe */ _expressions__rspack_import_4.UNRESOLVED),
|
|
13916
15952
|
ValueExpression: () => (/* reexport safe */ _expressions__rspack_import_4.ValueExpression),
|
|
13917
15953
|
VariableBuilder: () => (/* reexport safe */ _codegen__rspack_import_1.VariableBuilder),
|
|
13918
15954
|
WorkPipeline: () => (/* reexport safe */ _pipeline__rspack_import_6.WorkPipeline),
|
|
@@ -13924,6 +15960,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
13924
15960
|
assertIsNumber: () => (/* reexport safe */ _assertions__rspack_import_0.assertIsNumber),
|
|
13925
15961
|
assertString: () => (/* reexport safe */ _assertions__rspack_import_0.assertString),
|
|
13926
15962
|
cast: () => (/* reexport safe */ _utilities__rspack_import_10.cast),
|
|
15963
|
+
childrenOf: () => (/* reexport safe */ _expressions__rspack_import_4.childrenOf),
|
|
13927
15964
|
clone: () => (/* reexport safe */ _utilities__rspack_import_10.clone),
|
|
13928
15965
|
collectingSink: () => (/* reexport safe */ _plugins__rspack_import_7.collectingSink),
|
|
13929
15966
|
combineExpressions: () => (/* reexport safe */ _expressions__rspack_import_4.combineExpressions),
|
|
@@ -13932,15 +15969,21 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
13932
15969
|
cosineDistance: () => (/* reexport safe */ _plugins__rspack_import_7.cosineDistance),
|
|
13933
15970
|
createRequestHandler: () => (/* reexport safe */ _plugins__rspack_import_7.createRequestHandler),
|
|
13934
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),
|
|
13935
15975
|
deserializeBulkPersist: () => (/* reexport safe */ _plugins__rspack_import_7.deserializeBulkPersist),
|
|
13936
15976
|
deserializePersistResult: () => (/* reexport safe */ _plugins__rspack_import_7.deserializePersistResult),
|
|
13937
15977
|
deserializeQueryOptions: () => (/* reexport safe */ _plugins__rspack_import_7.deserializeQueryOptions),
|
|
13938
15978
|
distinctJoinKeys: () => (/* reexport safe */ _plugins__rspack_import_7.distinctJoinKeys),
|
|
13939
15979
|
evaluate: () => (/* reexport safe */ _expressions__rspack_import_4.evaluate),
|
|
13940
15980
|
executeJoin: () => (/* reexport safe */ _plugins__rspack_import_7.executeJoin),
|
|
15981
|
+
executedQueriesOf: () => (/* reexport safe */ _plugins__rspack_import_7.executedQueriesOf),
|
|
13941
15982
|
explainQuery: () => (/* reexport safe */ _plugins__rspack_import_7.explainQuery),
|
|
13942
15983
|
extractTypeInfo: () => (/* reexport safe */ _schema__rspack_import_9.extractTypeInfo),
|
|
13943
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),
|
|
13944
15987
|
forEach: () => (/* reexport safe */ _expressions__rspack_import_4.forEach),
|
|
13945
15988
|
formatExplanation: () => (/* reexport safe */ _plugins__rspack_import_7.formatExplanation),
|
|
13946
15989
|
getLogLevel: () => (/* reexport safe */ _utilities__rspack_import_10.getLogLevel),
|
|
@@ -13949,7 +15992,9 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
13949
15992
|
hash: () => (/* reexport safe */ _utilities__rspack_import_10.hash),
|
|
13950
15993
|
hashJoin: () => (/* reexport safe */ _plugins__rspack_import_7.hashJoin),
|
|
13951
15994
|
isArrayValued: () => (/* reexport safe */ _schema__rspack_import_9.isArrayValued),
|
|
15995
|
+
isCallExpression: () => (/* reexport safe */ _assertions__rspack_import_0.isCallExpression),
|
|
13952
15996
|
isComparatorExpression: () => (/* reexport safe */ _assertions__rspack_import_0.isComparatorExpression),
|
|
15997
|
+
isDatabaseStep: () => (/* reexport safe */ _plugins__rspack_import_7.isDatabaseStep),
|
|
13953
15998
|
isDate: () => (/* reexport safe */ _utilities__rspack_import_10.isDate),
|
|
13954
15999
|
isEmptyExpression: () => (/* reexport safe */ _assertions__rspack_import_0.isEmptyExpression),
|
|
13955
16000
|
isExpression: () => (/* reexport safe */ _assertions__rspack_import_0.isExpression),
|
|
@@ -13968,11 +16013,16 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
13968
16013
|
nearestBy: () => (/* reexport safe */ _plugins__rspack_import_7.nearestBy),
|
|
13969
16014
|
noop: () => (/* reexport safe */ _utilities__rspack_import_10.noop),
|
|
13970
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),
|
|
13971
16019
|
parseFragment: () => (/* reexport safe */ _expressions__rspack_import_4.parseFragment),
|
|
16020
|
+
peelCalls: () => (/* reexport safe */ _expressions__rspack_import_4.peelCalls),
|
|
13972
16021
|
propertyInfoToJsonSchema: () => (/* reexport safe */ _schema__rspack_import_9.propertyInfoToJsonSchema),
|
|
13973
16022
|
readJoinKey: () => (/* reexport safe */ _plugins__rspack_import_7.readJoinKey),
|
|
13974
16023
|
rehydrateSchemaFromJsonSchema: () => (/* reexport safe */ _schema__rspack_import_9.rehydrateSchemaFromJsonSchema),
|
|
13975
16024
|
rehydrateSchemaFromJsonString: () => (/* reexport safe */ _schema__rspack_import_9.rehydrateSchemaFromJsonString),
|
|
16025
|
+
renderCallAsJs: () => (/* reexport safe */ _expressions__rspack_import_4.renderCallAsJs),
|
|
13976
16026
|
resetLogLevel: () => (/* reexport safe */ _utilities__rspack_import_10.resetLogLevel),
|
|
13977
16027
|
resolveBulkPersistChanges: () => (/* reexport safe */ _utilities__rspack_import_10.resolveBulkPersistChanges),
|
|
13978
16028
|
s: () => (/* reexport safe */ _schema__rspack_import_9.s),
|
|
@@ -13993,7 +16043,8 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
13993
16043
|
unsafeCast: () => (/* reexport safe */ _utilities__rspack_import_10.unsafeCast),
|
|
13994
16044
|
uuid: () => (/* reexport safe */ _utilities__rspack_import_10.uuid),
|
|
13995
16045
|
uuidv4: () => (/* reexport safe */ _utilities__rspack_import_10.uuidv4),
|
|
13996
|
-
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)
|
|
13997
16048
|
});
|
|
13998
16049
|
/* import */ var _assertions__rspack_import_0 = __webpack_require__(126);
|
|
13999
16050
|
/* import */ var _codegen__rspack_import_1 = __webpack_require__(80);
|
|
@@ -14002,7 +16053,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
14002
16053
|
/* import */ var _expressions__rspack_import_4 = __webpack_require__(138);
|
|
14003
16054
|
/* import */ var _performance__rspack_import_5 = __webpack_require__(971);
|
|
14004
16055
|
/* import */ var _pipeline__rspack_import_6 = __webpack_require__(314);
|
|
14005
|
-
/* import */ var _plugins__rspack_import_7 = __webpack_require__(
|
|
16056
|
+
/* import */ var _plugins__rspack_import_7 = __webpack_require__(640);
|
|
14006
16057
|
/* import */ var _results__rspack_import_8 = __webpack_require__(264);
|
|
14007
16058
|
/* import */ var _schema__rspack_import_9 = __webpack_require__(755);
|
|
14008
16059
|
/* import */ var _utilities__rspack_import_10 = __webpack_require__(222);
|