@routier/core 0.7.0 → 0.8.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/codegen/blocks.d.ts +17 -1
- package/dist/codegen/handlers/types.d.ts +13 -5
- package/dist/codegen/index.cjs +28 -0
- package/dist/codegen/index.cjs.map +1 -1
- package/dist/codegen/index.js +28 -0
- package/dist/codegen/index.js.map +1 -1
- package/dist/collections/MemoryDataCollection.d.ts +2 -4
- package/dist/collections/index.cjs +127 -15
- package/dist/collections/index.cjs.map +1 -1
- package/dist/collections/index.js +127 -15
- package/dist/collections/index.js.map +1 -1
- package/dist/expressions/index.cjs +226 -4
- package/dist/expressions/index.cjs.map +1 -1
- package/dist/expressions/index.js +228 -5
- package/dist/expressions/index.js.map +1 -1
- package/dist/expressions/parser.d.ts +42 -1
- package/dist/index.cjs +753 -345
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +756 -345
- package/dist/index.js.map +1 -1
- package/dist/plugins/EphemeralDataPlugin.d.ts +8 -0
- package/dist/plugins/index.cjs +566 -49
- package/dist/plugins/index.cjs.map +1 -1
- package/dist/plugins/index.js +566 -48
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/query/QueryOptionsCollection.d.ts +15 -5
- package/dist/plugins/query/index.d.ts +1 -0
- package/dist/plugins/query/renames.d.ts +27 -0
- package/dist/plugins/query/types.d.ts +15 -1
- package/dist/plugins/translators/SqlTranslator.d.ts +15 -0
- package/dist/schema/SchemaDefinition.d.ts +8 -0
- package/dist/schema/changeTracker.d.ts +10 -0
- package/dist/schema/index.cjs +291 -274
- package/dist/schema/index.cjs.map +1 -1
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +294 -276
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/types.d.ts +8 -7
- package/dist/schema/utils/storageDates.d.ts +25 -0
- package/dist/transfer/index.cjs.map +1 -1
- package/dist/transfer/index.js.map +1 -1
- package/dist/utilities/index.cjs +74 -29
- package/dist/utilities/index.cjs.map +1 -1
- package/dist/utilities/index.js +74 -29
- package/dist/utilities/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/codegen/utils.d.ts +0 -22
package/dist/plugins/index.cjs
CHANGED
|
@@ -1021,6 +1021,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
1021
1021
|
|
|
1022
1022
|
|
|
1023
1023
|
|
|
1024
|
+
|
|
1024
1025
|
// Error message constants
|
|
1025
1026
|
const ERROR_MESSAGES = {
|
|
1026
1027
|
PROPERTY_NOT_FOUND: (path)=>`Error parsing query, could not find PropertyInfo for path: ${path}`,
|
|
@@ -1628,6 +1629,9 @@ const COALESCE_OPERATORS = sourceKeyed({
|
|
|
1628
1629
|
// A comparison always names a schema property, so the condition alone settles it
|
|
1629
1630
|
return true;
|
|
1630
1631
|
}
|
|
1632
|
+
if (operand.kind === "opaque") {
|
|
1633
|
+
return operand.reads.some(containsProperty);
|
|
1634
|
+
}
|
|
1631
1635
|
return operand.kind === "arithmetic" && (containsProperty(operand.left) || containsProperty(operand.right) || operand.extra != null && containsProperty(operand.extra));
|
|
1632
1636
|
};
|
|
1633
1637
|
const DECLARATION_KEYWORDS = new Set([
|
|
@@ -1805,13 +1809,18 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1805
1809
|
scope;
|
|
1806
1810
|
paramsName;
|
|
1807
1811
|
params;
|
|
1812
|
+
/**
|
|
1813
|
+
* Whether this parses a value selector rather than a filter, and so reads a call it has no node for
|
|
1814
|
+
* as an `OpaqueOperand` instead of refusing it.
|
|
1815
|
+
*/ readsValues;
|
|
1808
1816
|
/** Set when a param value shaped the tree itself (e.g. x[p.name]) — such templates cannot be cached. */ structurallyDependsOnParams = false;
|
|
1809
|
-
constructor(schema, stream, scope, paramsName, params){
|
|
1817
|
+
constructor(schema, stream, scope, paramsName, params, readsValues = false){
|
|
1810
1818
|
this.schema = schema;
|
|
1811
1819
|
this.stream = stream;
|
|
1812
1820
|
this.scope = scope;
|
|
1813
1821
|
this.paramsName = paramsName;
|
|
1814
1822
|
this.params = params;
|
|
1823
|
+
this.readsValues = readsValues;
|
|
1815
1824
|
}
|
|
1816
1825
|
parse() {
|
|
1817
1826
|
const expression = this.parseOr();
|
|
@@ -1833,6 +1842,71 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
1833
1842
|
}
|
|
1834
1843
|
return answer;
|
|
1835
1844
|
}
|
|
1845
|
+
/**
|
|
1846
|
+
* What a value selector returns: one value, or the fields of an object literal.
|
|
1847
|
+
*
|
|
1848
|
+
* A block body is read only when it does nothing but return, which is what a transpiler makes of an
|
|
1849
|
+
* arrow function. Anything more is refused, and the caller falls back to running the function.
|
|
1850
|
+
*/ parseSelector() {
|
|
1851
|
+
const block = this.stream.matchPunctuation("{");
|
|
1852
|
+
if (block) {
|
|
1853
|
+
const keyword = this.stream.next();
|
|
1854
|
+
if (keyword.kind !== "identifier" || keyword.value !== "return") {
|
|
1855
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("a selector block that does more than return"));
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
const selected = this.stream.isPunctuation("{") ? this.parseObjectLiteral() : this.stream.isPunctuation("(") && this.stream.isPunctuation("{", 1) ? this.parseBracketedObjectLiteral() : this.parseInterpolation();
|
|
1859
|
+
if (block) {
|
|
1860
|
+
this.stream.matchPunctuation(";");
|
|
1861
|
+
this.stream.expectPunctuation("}");
|
|
1862
|
+
}
|
|
1863
|
+
if (!this.stream.isAtEnd) {
|
|
1864
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`unexpected token '${this.stream.peek()?.value}'`));
|
|
1865
|
+
}
|
|
1866
|
+
return selected;
|
|
1867
|
+
}
|
|
1868
|
+
/** `({ … })`, the arrow body that returns an object. */ parseBracketedObjectLiteral() {
|
|
1869
|
+
this.stream.expectPunctuation("(");
|
|
1870
|
+
const fields = this.parseObjectLiteral();
|
|
1871
|
+
this.stream.expectPunctuation(")");
|
|
1872
|
+
return fields;
|
|
1873
|
+
}
|
|
1874
|
+
/**
|
|
1875
|
+
* The fields of an object literal, each one value.
|
|
1876
|
+
*
|
|
1877
|
+
* A field's value is read without a top-level conditional, which needs brackets here: the look-ahead
|
|
1878
|
+
* for a `?` does not stop at the comma that ends the field.
|
|
1879
|
+
*/ parseObjectLiteral() {
|
|
1880
|
+
this.stream.expectPunctuation("{");
|
|
1881
|
+
const fields = [];
|
|
1882
|
+
while(!this.stream.matchPunctuation("}")){
|
|
1883
|
+
const key = this.stream.next();
|
|
1884
|
+
if (key.kind !== "identifier" && key.kind !== "string") {
|
|
1885
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`the object key '${key.value}'`));
|
|
1886
|
+
}
|
|
1887
|
+
fields.push({
|
|
1888
|
+
name: key.value,
|
|
1889
|
+
operand: this.stream.matchPunctuation(":") ? this.parseValue() : this.parseShorthand(key)
|
|
1890
|
+
});
|
|
1891
|
+
if (!this.stream.matchPunctuation(",")) {
|
|
1892
|
+
this.stream.expectPunctuation("}");
|
|
1893
|
+
break;
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
return fields;
|
|
1897
|
+
}
|
|
1898
|
+
/** `{ name }`, which reads what the parameter list bound `name` to. */ parseShorthand(key) {
|
|
1899
|
+
const binding = key.kind === "identifier" ? this.scope.get(key.value) : undefined;
|
|
1900
|
+
if (binding == null || binding.kind === "inlined") {
|
|
1901
|
+
throw new Error(ERROR_MESSAGES.VARIABLE_VALUE(key.value));
|
|
1902
|
+
}
|
|
1903
|
+
return this.parseChain({
|
|
1904
|
+
kind: binding.kind,
|
|
1905
|
+
path: [
|
|
1906
|
+
...binding.path
|
|
1907
|
+
]
|
|
1908
|
+
});
|
|
1909
|
+
}
|
|
1836
1910
|
/** The expression a `{ … }` block answers with. */ parseBlock() {
|
|
1837
1911
|
this.stream.expectPunctuation("{");
|
|
1838
1912
|
const answer = this.parseStatements();
|
|
@@ -2096,7 +2170,7 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
2096
2170
|
* A structural dependence found inside propagates outward: the template it belongs to cannot be
|
|
2097
2171
|
* cached either.
|
|
2098
2172
|
*/ parseNested(source) {
|
|
2099
|
-
const nested = new ExpressionParser(this.schema, new TokenStream(tokenize(source)), this.scope, this.paramsName, this.params);
|
|
2173
|
+
const nested = new ExpressionParser(this.schema, new TokenStream(tokenize(source)), this.scope, this.paramsName, this.params, this.readsValues);
|
|
2100
2174
|
const operand = nested.parseInterpolation();
|
|
2101
2175
|
// Leftover tokens mean the interpolation held something this reads only part of. Silently
|
|
2102
2176
|
// keeping the part it understood is the worst outcome available: `${x.age > 5 ? "a" : "b"}`
|
|
@@ -2388,7 +2462,7 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
2388
2462
|
if (argument.kind === "method-call") {
|
|
2389
2463
|
throw new Error(ERROR_MESSAGES.UNSUPPORTED("nested method call inside .includes()"));
|
|
2390
2464
|
}
|
|
2391
|
-
if (argument.kind === "arithmetic" || argument.kind === "conditional") {
|
|
2465
|
+
if (argument.kind === "arithmetic" || argument.kind === "conditional" || argument.kind === "opaque") {
|
|
2392
2466
|
throw new Error(ERROR_MESSAGES.UNSUPPORTED("arithmetic inside .includes()"));
|
|
2393
2467
|
}
|
|
2394
2468
|
return {
|
|
@@ -2482,7 +2556,7 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
2482
2556
|
if (argument.kind === "method-call") {
|
|
2483
2557
|
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`nested method call inside .${method}()`));
|
|
2484
2558
|
}
|
|
2485
|
-
if (argument.kind === "arithmetic" || argument.kind === "conditional") {
|
|
2559
|
+
if (argument.kind === "arithmetic" || argument.kind === "conditional" || argument.kind === "opaque") {
|
|
2486
2560
|
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`arithmetic inside .${method}()`));
|
|
2487
2561
|
}
|
|
2488
2562
|
return {
|
|
@@ -2492,9 +2566,20 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
2492
2566
|
argument
|
|
2493
2567
|
};
|
|
2494
2568
|
}
|
|
2569
|
+
if (this.readsValues) {
|
|
2570
|
+
return this.withGroupCall(this.opaqueCall(this.resolveChain(options.kind, path, transformer, locale)));
|
|
2571
|
+
}
|
|
2495
2572
|
throw new Error(ERROR_MESSAGES.UNSUPPORTED(`method '.${method}()'`));
|
|
2496
2573
|
}
|
|
2497
2574
|
if (transformer != null) {
|
|
2575
|
+
if (this.readsValues) {
|
|
2576
|
+
return this.withGroupCall({
|
|
2577
|
+
kind: "opaque",
|
|
2578
|
+
reads: [
|
|
2579
|
+
this.resolveChain(options.kind, path, transformer, locale)
|
|
2580
|
+
]
|
|
2581
|
+
});
|
|
2582
|
+
}
|
|
2498
2583
|
throw new Error(ERROR_MESSAGES.UNSUPPORTED("property access after a transform method"));
|
|
2499
2584
|
}
|
|
2500
2585
|
path.push(segment.value);
|
|
@@ -2639,10 +2724,39 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
2639
2724
|
argument
|
|
2640
2725
|
};
|
|
2641
2726
|
}
|
|
2727
|
+
// Any other member or call of a value, which a selector reads through
|
|
2728
|
+
if (this.readsValues) {
|
|
2729
|
+
this.stream.next();
|
|
2730
|
+
this.stream.next();
|
|
2731
|
+
receiver = this.stream.isPunctuation("(") ? this.opaqueCall(receiver) : {
|
|
2732
|
+
kind: "opaque",
|
|
2733
|
+
reads: [
|
|
2734
|
+
receiver
|
|
2735
|
+
]
|
|
2736
|
+
};
|
|
2737
|
+
continue;
|
|
2738
|
+
}
|
|
2642
2739
|
break;
|
|
2643
2740
|
}
|
|
2644
2741
|
return receiver;
|
|
2645
2742
|
}
|
|
2743
|
+
/** A call with no node of its own, from its `(`, kept for what its receiver and arguments read. */ opaqueCall(receiver) {
|
|
2744
|
+
const reads = [
|
|
2745
|
+
receiver
|
|
2746
|
+
];
|
|
2747
|
+
this.stream.expectPunctuation("(");
|
|
2748
|
+
while(!this.stream.matchPunctuation(")")){
|
|
2749
|
+
reads.push(this.parseValue());
|
|
2750
|
+
if (!this.stream.matchPunctuation(",")) {
|
|
2751
|
+
this.stream.expectPunctuation(")");
|
|
2752
|
+
break;
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
return {
|
|
2756
|
+
kind: "opaque",
|
|
2757
|
+
reads
|
|
2758
|
+
};
|
|
2759
|
+
}
|
|
2646
2760
|
withValueTransformer(operand) {
|
|
2647
2761
|
if (this.stream.isPunctuation(".")) {
|
|
2648
2762
|
const method = this.stream.peek(1);
|
|
@@ -2676,6 +2790,10 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
2676
2790
|
if (right.kind === "method-call") {
|
|
2677
2791
|
throw new Error(ERROR_MESSAGES.UNSUPPORTED("method call on the right side of a comparison"));
|
|
2678
2792
|
}
|
|
2793
|
+
// A comparison is a tree a backend renders, and this operand has no node in one
|
|
2794
|
+
if (left.kind === "opaque" || right.kind === "opaque") {
|
|
2795
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("a call with no expression form inside a comparison"));
|
|
2796
|
+
}
|
|
2679
2797
|
if (needsBrackets(left) || needsBrackets(right)) {
|
|
2680
2798
|
throw new Error(ERROR_MESSAGES.UNSUPPORTED("a bitwise or nullish operator compared without brackets, which JavaScript reads the other way round"));
|
|
2681
2799
|
}
|
|
@@ -2889,6 +3007,9 @@ const resolveParamPath = (paramsName, path, data)=>{
|
|
|
2889
3007
|
if (operand.kind === "method-call") {
|
|
2890
3008
|
throw new Error(ERROR_MESSAGES.UNSUPPORTED("a method call inside arithmetic"));
|
|
2891
3009
|
}
|
|
3010
|
+
if (operand.kind === "opaque") {
|
|
3011
|
+
throw new Error(ERROR_MESSAGES.UNSUPPORTED("a call with no expression form inside arithmetic"));
|
|
3012
|
+
}
|
|
2892
3013
|
return this.createValueExpression(operand, null, /* applyConverter */ false);
|
|
2893
3014
|
}
|
|
2894
3015
|
createPropertyExpression(operand) {
|
|
@@ -3224,6 +3345,104 @@ const toExpression = (schema, fn, params)=>{
|
|
|
3224
3345
|
return _types__rspack_import_1/* .Expression.notParsable */.r4.notParsable(refusalOf(error));
|
|
3225
3346
|
}
|
|
3226
3347
|
};
|
|
3348
|
+
const collectReads = (operand, into)=>{
|
|
3349
|
+
switch(operand.kind){
|
|
3350
|
+
case "property":
|
|
3351
|
+
into.add(operand.property);
|
|
3352
|
+
return;
|
|
3353
|
+
case "method-call":
|
|
3354
|
+
collectReads(operand.target, into);
|
|
3355
|
+
collectReads(operand.argument, into);
|
|
3356
|
+
return;
|
|
3357
|
+
case "arithmetic":
|
|
3358
|
+
collectReads(operand.left, into);
|
|
3359
|
+
collectReads(operand.right, into);
|
|
3360
|
+
if (operand.extra != null) {
|
|
3361
|
+
collectReads(operand.extra, into);
|
|
3362
|
+
}
|
|
3363
|
+
return;
|
|
3364
|
+
case "conditional":
|
|
3365
|
+
for (const property of getProperties(operand.condition)){
|
|
3366
|
+
into.add(property);
|
|
3367
|
+
}
|
|
3368
|
+
collectReads(operand.whenTrue, into);
|
|
3369
|
+
collectReads(operand.whenFalse, into);
|
|
3370
|
+
return;
|
|
3371
|
+
case "opaque":
|
|
3372
|
+
for (const read of operand.reads){
|
|
3373
|
+
collectReads(read, into);
|
|
3374
|
+
}
|
|
3375
|
+
return;
|
|
3376
|
+
}
|
|
3377
|
+
};
|
|
3378
|
+
const selectedValue = (operand)=>{
|
|
3379
|
+
const found = new Set();
|
|
3380
|
+
collectReads(operand, found);
|
|
3381
|
+
const reads = [
|
|
3382
|
+
...found
|
|
3383
|
+
];
|
|
3384
|
+
return {
|
|
3385
|
+
property: reads.length === 1 ? reads[0] : null,
|
|
3386
|
+
reads,
|
|
3387
|
+
isDirectProperty: operand.kind === "property" && operand.transformer == null
|
|
3388
|
+
};
|
|
3389
|
+
};
|
|
3390
|
+
// Keyed like the template cache. A selector takes no params, so every result is cacheable
|
|
3391
|
+
const selectorCache = new WeakMap();
|
|
3392
|
+
/**
|
|
3393
|
+
* Reads a sort, map, group or `nearest` selector with the grammar filters use, for what its value is
|
|
3394
|
+
* read from.
|
|
3395
|
+
*
|
|
3396
|
+
* Not for evaluating it: the caller's function stays the value, and nothing here renders one. A plugin
|
|
3397
|
+
* decides from the result whether it can run the option. One that orders or projects by column cannot
|
|
3398
|
+
* run a value that is not the property itself, and one that runs the function over stored rows cannot
|
|
3399
|
+
* run it over a renamed property.
|
|
3400
|
+
*
|
|
3401
|
+
* So the grammar is wider here than for a filter. A call it has no node for, such as `getFullYear()`,
|
|
3402
|
+
* is kept for the operands it reads rather than refused, since the function it came from still runs.
|
|
3403
|
+
*
|
|
3404
|
+
* `not-parsable` is not logged. The option runs as it did before the selector was parsed.
|
|
3405
|
+
*
|
|
3406
|
+
* Cached by function source per schema, like `toExpression`. The result is shared, so it is read-only.
|
|
3407
|
+
*/ const parseSelector = (schema, selector)=>{
|
|
3408
|
+
const source = selector.toString();
|
|
3409
|
+
let bySource = selectorCache.get(schema);
|
|
3410
|
+
const cached = bySource?.get(source);
|
|
3411
|
+
if (cached != null) {
|
|
3412
|
+
return cached;
|
|
3413
|
+
}
|
|
3414
|
+
let parsed;
|
|
3415
|
+
try {
|
|
3416
|
+
const shape = resolveFunctionShape(source, false);
|
|
3417
|
+
const parser = new ExpressionParser(schema, new TokenStream(tokenize(shape.body)), shape.scope, null, undefined, true);
|
|
3418
|
+
const selected = parser.parseSelector();
|
|
3419
|
+
parsed = Array.isArray(selected) ? {
|
|
3420
|
+
kind: "object",
|
|
3421
|
+
fields: selected.map((field)=>({
|
|
3422
|
+
name: field.name,
|
|
3423
|
+
...selectedValue(field.operand)
|
|
3424
|
+
}))
|
|
3425
|
+
} : {
|
|
3426
|
+
kind: "value",
|
|
3427
|
+
value: selectedValue(selected)
|
|
3428
|
+
};
|
|
3429
|
+
} catch (error) {
|
|
3430
|
+
parsed = {
|
|
3431
|
+
kind: "not-parsable",
|
|
3432
|
+
reason: refusalOf(error)
|
|
3433
|
+
};
|
|
3434
|
+
}
|
|
3435
|
+
if (bySource == null) {
|
|
3436
|
+
bySource = new Map();
|
|
3437
|
+
selectorCache.set(schema, bySource);
|
|
3438
|
+
}
|
|
3439
|
+
// Stryker disable next-line all: the same resource bound as the template cache's
|
|
3440
|
+
if (bySource.size >= MAX_CACHED_TEMPLATES_PER_SCHEMA) {
|
|
3441
|
+
bySource.clear();
|
|
3442
|
+
}
|
|
3443
|
+
bySource.set(source, parsed);
|
|
3444
|
+
return parsed;
|
|
3445
|
+
}; // #endregion
|
|
3227
3446
|
|
|
3228
3447
|
|
|
3229
3448
|
},
|
|
@@ -3931,6 +4150,30 @@ const mismatchWarning = (expression)=>{
|
|
|
3931
4150
|
const outcome = expression.negated ? "every row matches" : "no row matches";
|
|
3932
4151
|
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`;
|
|
3933
4152
|
};
|
|
4153
|
+
/**
|
|
4154
|
+
* An item as one dispatch receives it: a new object, so a report written on it stays with that dispatch.
|
|
4155
|
+
*
|
|
4156
|
+
* A database option starts `executed` again, because a report is only an answer from the plugin that
|
|
4157
|
+
* made it. A memory option keeps the reason core planned it with. A join's inner options are copied the
|
|
4158
|
+
* same way, since a plugin can report on them too.
|
|
4159
|
+
*/ const toDispatchItem = (item)=>{
|
|
4160
|
+
const option = item.option;
|
|
4161
|
+
const value = option.name === "join" ? {
|
|
4162
|
+
...option.value,
|
|
4163
|
+
innerOptions: option.value.innerOptions.forDispatch()
|
|
4164
|
+
} : option.value;
|
|
4165
|
+
return {
|
|
4166
|
+
index: item.index,
|
|
4167
|
+
option: option.target === "database" ? {
|
|
4168
|
+
...option,
|
|
4169
|
+
value,
|
|
4170
|
+
reason: "executed"
|
|
4171
|
+
} : {
|
|
4172
|
+
...option,
|
|
4173
|
+
value
|
|
4174
|
+
}
|
|
4175
|
+
};
|
|
4176
|
+
};
|
|
3934
4177
|
class QueryOptionsCollection {
|
|
3935
4178
|
options = new Map();
|
|
3936
4179
|
nextExecutionTarget = "database";
|
|
@@ -3969,7 +4212,7 @@ class QueryOptionsCollection {
|
|
|
3969
4212
|
}
|
|
3970
4213
|
}
|
|
3971
4214
|
if (name === "filter") {
|
|
3972
|
-
// Need to check for unmapped
|
|
4215
|
+
// Need to check for unmapped properties
|
|
3973
4216
|
const filterValue = value;
|
|
3974
4217
|
// A tautology (`x => true`) filters nothing — skip it entirely so
|
|
3975
4218
|
// plugins never see it
|
|
@@ -3986,14 +4229,10 @@ class QueryOptionsCollection {
|
|
|
3986
4229
|
this.cutOverToMemory("unmapped-property");
|
|
3987
4230
|
return false;
|
|
3988
4231
|
}
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
// where the in-memory names exist
|
|
3994
|
-
this.cutOverToMemory("renamed-property");
|
|
3995
|
-
return false;
|
|
3996
|
-
}
|
|
4232
|
+
// A renamed property stays with the database. Whether the backend can read a
|
|
4233
|
+
// `from` name is the plugin's to know, not this collection's: the property
|
|
4234
|
+
// travels with the option, and a plugin that cannot resolve it reports it
|
|
4235
|
+
// back — see `reportRenamedProperties`
|
|
3997
4236
|
if (comparesTypesThatCannotMatch(expression)) {
|
|
3998
4237
|
_utilities__rspack_import_3/* .logger.warn */.vF.warn(mismatchWarning(expression));
|
|
3999
4238
|
this.cutOverToMemory("predicate-error");
|
|
@@ -4005,26 +4244,19 @@ class QueryOptionsCollection {
|
|
|
4005
4244
|
}
|
|
4006
4245
|
if (name === "sort") {
|
|
4007
4246
|
const sortValue = value;
|
|
4008
|
-
// Same rule as filters:
|
|
4009
|
-
//
|
|
4247
|
+
// Same rule as filters: an unmapped property only exists after deserialization. A
|
|
4248
|
+
// renamed one stays with the database, for the plugin to resolve or report
|
|
4010
4249
|
if (sortValue.property != null && sortValue.property.isUnmapped) {
|
|
4011
4250
|
this.cutOverToMemory("unmapped-property");
|
|
4012
|
-
} else if (sortValue.property != null && sortValue.property.hasRenamedSegments) {
|
|
4013
|
-
this.cutOverToMemory("renamed-property");
|
|
4014
4251
|
}
|
|
4015
4252
|
}
|
|
4016
4253
|
if (name === "nearest") {
|
|
4017
4254
|
const nearestValue = value;
|
|
4018
|
-
// Same rule as sort, and for the same reason:
|
|
4019
|
-
//
|
|
4020
|
-
//
|
|
4021
|
-
//
|
|
4022
|
-
// This is also what lets every translator's in-memory fallback read the column by
|
|
4023
|
-
// its resolved name — anything whose storage name differs never reaches them.
|
|
4255
|
+
// Same rule as sort, and for the same reason: an unmapped property is not stored at
|
|
4256
|
+
// all, so it is only readable after deserialization, which is where memory execution
|
|
4257
|
+
// runs. A vector stored under a `from` name is the plugin's to resolve or report.
|
|
4024
4258
|
if (nearestValue.property != null && nearestValue.property.isUnmapped) {
|
|
4025
4259
|
this.cutOverToMemory("unmapped-property");
|
|
4026
|
-
} else if (nearestValue.property != null && nearestValue.property.hasRenamedSegments) {
|
|
4027
|
-
this.cutOverToMemory("renamed-property");
|
|
4028
4260
|
}
|
|
4029
4261
|
}
|
|
4030
4262
|
if ((name === "filter" || name === "sort") && (this.options.has("skip") || this.options.has("take"))) {
|
|
@@ -4131,6 +4363,9 @@ class QueryOptionsCollection {
|
|
|
4131
4363
|
* the shared collection before executing. Without restoring, a re-executed terminal —
|
|
4132
4364
|
* the whole point of a subscribed queryable — stacks its option a second time and
|
|
4133
4365
|
* runs it over the first execution's scalar result.
|
|
4366
|
+
*
|
|
4367
|
+
* The item objects are shared with the snapshot. Nothing reports on them, because every
|
|
4368
|
+
* dispatch sends a `forDispatch` copy, so a restore brings back no reports.
|
|
4134
4369
|
*/ snapshot() {
|
|
4135
4370
|
const options = new Map([
|
|
4136
4371
|
...this.options.entries()
|
|
@@ -4208,19 +4443,48 @@ class QueryOptionsCollection {
|
|
|
4208
4443
|
}
|
|
4209
4444
|
}
|
|
4210
4445
|
/**
|
|
4211
|
-
*
|
|
4446
|
+
* A copy of the collection for one dispatch to a plugin, with nothing reported on it.
|
|
4212
4447
|
*
|
|
4213
4448
|
* Capability is answered per dispatch, so a report is only an answer for the execution that
|
|
4214
|
-
* produced it.
|
|
4215
|
-
*
|
|
4216
|
-
*
|
|
4217
|
-
|
|
4449
|
+
* produced it. Reports are written onto items, and the items of a queryable's collection
|
|
4450
|
+
* outlive any one execution: a snapshot shares them, and a subscription dispatches the same
|
|
4451
|
+
* query on every change. A report left on them replays options the plugin did run on the
|
|
4452
|
+
* next execution, such as a `skip` applied twice over rows already windowed, or hands a
|
|
4453
|
+
* renamed filter to memory that the engine could have run.
|
|
4454
|
+
*
|
|
4455
|
+
* Each item keeps its index, name, value and target. A half from `split`/`splitAt` is copied
|
|
4456
|
+
* with a copy of its origin, and its items are that copy's items, so a report on the half still
|
|
4457
|
+
* cascades over the whole dispatch without reaching the collection it was copied from.
|
|
4458
|
+
*/ forDispatch() {
|
|
4459
|
+
if (this.origin == null) {
|
|
4460
|
+
return this.copyForDispatch().copy;
|
|
4461
|
+
}
|
|
4462
|
+
const { copy: root, copies } = this.origin.copyForDispatch();
|
|
4463
|
+
const half = new QueryOptionsCollection();
|
|
4218
4464
|
this.resolveEnumeration();
|
|
4219
4465
|
for (const item of this.enumeratedItems){
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
}
|
|
4466
|
+
// An item added to the half after it was split has no counterpart in the origin
|
|
4467
|
+
half.adopt(copies.get(item) ?? toDispatchItem(item));
|
|
4223
4468
|
}
|
|
4469
|
+
half.origin = root;
|
|
4470
|
+
return half;
|
|
4471
|
+
}
|
|
4472
|
+
copyForDispatch() {
|
|
4473
|
+
const copy = new QueryOptionsCollection();
|
|
4474
|
+
const copies = new Map();
|
|
4475
|
+
this.resolveEnumeration();
|
|
4476
|
+
for (const item of this.enumeratedItems){
|
|
4477
|
+
const copied = toDispatchItem(item);
|
|
4478
|
+
copies.set(item, copied);
|
|
4479
|
+
copy.adopt(copied);
|
|
4480
|
+
}
|
|
4481
|
+
copy.nextExecutionTarget = this.nextExecutionTarget;
|
|
4482
|
+
copy.nextExecutionReason = this.nextExecutionReason;
|
|
4483
|
+
copy.nextIndex = this.nextIndex;
|
|
4484
|
+
return {
|
|
4485
|
+
copy,
|
|
4486
|
+
copies
|
|
4487
|
+
};
|
|
4224
4488
|
}
|
|
4225
4489
|
/** The options the database did not run, in the order they were written. */ notExecuted() {
|
|
4226
4490
|
this.resolveEnumeration();
|
|
@@ -4420,6 +4684,128 @@ var HashType = /*#__PURE__*/ (/* unused pure expression or super */ null && (fun
|
|
|
4420
4684
|
}({})));
|
|
4421
4685
|
|
|
4422
4686
|
|
|
4687
|
+
},
|
|
4688
|
+
575(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
4689
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
4690
|
+
l: () => (isArrayValued)
|
|
4691
|
+
});
|
|
4692
|
+
/* import */ var _types__rspack_import_0 = __webpack_require__(537);
|
|
4693
|
+
|
|
4694
|
+
/**
|
|
4695
|
+
* Types whose runtime value is a JS array.
|
|
4696
|
+
*
|
|
4697
|
+
* `Array` and `Vector` are the same thing to every layer that copies, compares, hashes or
|
|
4698
|
+
* freezes a value — a vector is a list of numbers and nothing more. They differ only where a
|
|
4699
|
+
* backend chooses storage, which is the one place that should ask for `SchemaTypes.Vector` by
|
|
4700
|
+
* name.
|
|
4701
|
+
*
|
|
4702
|
+
* This exists so adding a third array-shaped type is one edit rather than a hunt through
|
|
4703
|
+
* twelve handlers. Missing one of those is silent in the worst way: a vector that clones by
|
|
4704
|
+
* reference is shared with the change tracker's copy, so overwriting an embedding produces no
|
|
4705
|
+
* diff and the save reports nothing to do.
|
|
4706
|
+
*/ const ARRAY_VALUED_TYPES = new Set([
|
|
4707
|
+
_types__rspack_import_0/* .SchemaTypes.Array */.L.Array,
|
|
4708
|
+
_types__rspack_import_0/* .SchemaTypes.Vector */.L.Vector
|
|
4709
|
+
]);
|
|
4710
|
+
/** True when the property's value is a JS array and needs value rather than reference semantics. */ const isArrayValued = (type)=>ARRAY_VALUED_TYPES.has(type);
|
|
4711
|
+
/**
|
|
4712
|
+
* True when the property's elements are primitives, so a spread is a sufficient copy.
|
|
4713
|
+
*
|
|
4714
|
+
* A vector is always numbers, so it never needs the per-element deep copy an array of objects
|
|
4715
|
+
* or dates does.
|
|
4716
|
+
*/ const PRIMITIVE_ELEMENT_TYPES = new Set([
|
|
4717
|
+
_types__rspack_import_0/* .SchemaTypes.String */.L.String,
|
|
4718
|
+
_types__rspack_import_0/* .SchemaTypes.Number */.L.Number,
|
|
4719
|
+
_types__rspack_import_0/* .SchemaTypes.Boolean */.L.Boolean
|
|
4720
|
+
]);
|
|
4721
|
+
const hasPrimitiveElements = (type, elementType)=>type === SchemaTypes.Vector || PRIMITIVE_ELEMENT_TYPES.has(elementType);
|
|
4722
|
+
|
|
4723
|
+
|
|
4724
|
+
},
|
|
4725
|
+
894(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
4726
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
4727
|
+
T: () => (getStorageDateReviver)
|
|
4728
|
+
});
|
|
4729
|
+
/* import */ var _types__rspack_import_0 = __webpack_require__(537);
|
|
4730
|
+
/* import */ var _propertyKind__rspack_import_1 = __webpack_require__(575);
|
|
4731
|
+
|
|
4732
|
+
|
|
4733
|
+
const collectDatePaths = (properties, paths)=>{
|
|
4734
|
+
for (const property of properties){
|
|
4735
|
+
// The stored value belongs to whoever wrote it: a custom serializer, deserializer or
|
|
4736
|
+
// transform reads it back, and would be handed a Date it did not expect. Unmapped
|
|
4737
|
+
// properties are never stored.
|
|
4738
|
+
if (property.valueSerializer != null || property.valueDeserializer != null || property.transform != null || property.isUnmapped) {
|
|
4739
|
+
continue;
|
|
4740
|
+
}
|
|
4741
|
+
if (property.type === _types__rspack_import_0/* .SchemaTypes.Object */.L.Object) {
|
|
4742
|
+
collectDatePaths(property.children, paths);
|
|
4743
|
+
continue;
|
|
4744
|
+
}
|
|
4745
|
+
const isArray = (0,_propertyKind__rspack_import_1/* .isArrayValued */.l)(property.type) && property.innerSchema?.type === _types__rspack_import_0/* .SchemaTypes.Date */.L.Date;
|
|
4746
|
+
if (property.type !== _types__rspack_import_0/* .SchemaTypes.Date */.L.Date && isArray === false) {
|
|
4747
|
+
continue;
|
|
4748
|
+
}
|
|
4749
|
+
paths.push({
|
|
4750
|
+
segments: [
|
|
4751
|
+
...property.getParentPathArray({
|
|
4752
|
+
useFromPropertyName: true
|
|
4753
|
+
}),
|
|
4754
|
+
property.getResolvedName()
|
|
4755
|
+
],
|
|
4756
|
+
isArray
|
|
4757
|
+
});
|
|
4758
|
+
}
|
|
4759
|
+
};
|
|
4760
|
+
const reviveAt = (record, path)=>{
|
|
4761
|
+
const { segments } = path;
|
|
4762
|
+
let parent = record;
|
|
4763
|
+
for(let i = 0, length = segments.length - 1; i < length; i++){
|
|
4764
|
+
parent = parent[segments[i]];
|
|
4765
|
+
// An absent or null parent holds no date
|
|
4766
|
+
if (parent == null || typeof parent !== "object") {
|
|
4767
|
+
return;
|
|
4768
|
+
}
|
|
4769
|
+
}
|
|
4770
|
+
const key = segments[segments.length - 1];
|
|
4771
|
+
const value = parent[key];
|
|
4772
|
+
if (path.isArray === false) {
|
|
4773
|
+
if (typeof value === "string") {
|
|
4774
|
+
parent[key] = new Date(value);
|
|
4775
|
+
}
|
|
4776
|
+
return;
|
|
4777
|
+
}
|
|
4778
|
+
if (Array.isArray(value)) {
|
|
4779
|
+
for(let i = 0, length = value.length; i < length; i++){
|
|
4780
|
+
if (typeof value[i] === "string") {
|
|
4781
|
+
value[i] = new Date(value[i]);
|
|
4782
|
+
}
|
|
4783
|
+
}
|
|
4784
|
+
}
|
|
4785
|
+
};
|
|
4786
|
+
/** Per schema: `null` for a schema with no dates, so a caller can skip the pass. */ const revivers = new WeakMap();
|
|
4787
|
+
/**
|
|
4788
|
+
* The reviver for `schema`'s records, or `null` when it declares no dates.
|
|
4789
|
+
*
|
|
4790
|
+
* Built once per compiled schema. A read revives every row it returns, so the paths are resolved
|
|
4791
|
+
* here rather than per row.
|
|
4792
|
+
*/ const getStorageDateReviver = (schema)=>{
|
|
4793
|
+
const cached = revivers.get(schema);
|
|
4794
|
+
if (cached !== undefined) {
|
|
4795
|
+
return cached;
|
|
4796
|
+
}
|
|
4797
|
+
const paths = [];
|
|
4798
|
+
collectDatePaths(schema.properties, paths);
|
|
4799
|
+
const reviver = paths.length === 0 ? null : (record)=>{
|
|
4800
|
+
for(let i = 0, length = paths.length; i < length; i++){
|
|
4801
|
+
reviveAt(record, paths[i]);
|
|
4802
|
+
}
|
|
4803
|
+
};
|
|
4804
|
+
revivers.set(schema, reviver);
|
|
4805
|
+
return reviver;
|
|
4806
|
+
};
|
|
4807
|
+
|
|
4808
|
+
|
|
4423
4809
|
},
|
|
4424
4810
|
76(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
4425
4811
|
__webpack_require__.d(__webpack_exports__, {
|
|
@@ -4689,6 +5075,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
4689
5075
|
CacheDbPlugin: () => (/* reexport */ CacheDbPlugin),
|
|
4690
5076
|
Query: () => (/* reexport */ Query),
|
|
4691
5077
|
splitSendableOptions: () => (/* reexport */ splitSendableOptions),
|
|
5078
|
+
withInnerSide: () => (/* reexport */ withInnerSide),
|
|
4692
5079
|
executeJoin: () => (/* reexport */ executeJoin),
|
|
4693
5080
|
formatExplanation: () => (/* reexport */ formatExplanation),
|
|
4694
5081
|
parameteriseDocument: () => (/* reexport */ parameteriseDocument),
|
|
@@ -4719,13 +5106,13 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
4719
5106
|
readJoinKey: () => (/* reexport */ readJoinKey),
|
|
4720
5107
|
loadJoinInnerSide: () => (/* reexport */ loadJoinInnerSide),
|
|
4721
5108
|
DataTranslator: () => (/* reexport */ DataTranslator),
|
|
5109
|
+
reportRenamedProperties: () => (/* reexport */ reportRenamedProperties),
|
|
4722
5110
|
serializeBulkPersist: () => (/* reexport */ serializeBulkPersist),
|
|
4723
|
-
toEntityShape: () => (/* reexport */ toEntityShape),
|
|
4724
5111
|
TelemetryDbPlugin: () => (/* reexport */ TelemetryDbPlugin),
|
|
4725
|
-
|
|
5112
|
+
toEntityShape: () => (/* reexport */ toEntityShape),
|
|
4726
5113
|
createRequestHandler: () => (/* reexport */ createRequestHandler),
|
|
4727
5114
|
SqlTranslator: () => (/* reexport */ SqlTranslator),
|
|
4728
|
-
|
|
5115
|
+
withExecutedQueries: () => (/* reexport */ withExecutedQueries),
|
|
4729
5116
|
QueryOrdering: () => (/* reexport */ types_QueryOrdering),
|
|
4730
5117
|
describeFilterAsJs: () => (/* reexport */ describeFilterAsJs),
|
|
4731
5118
|
serializeQueryOptions: () => (/* reexport */ serializeQueryOptions),
|
|
@@ -5699,9 +6086,12 @@ class JsonTranslator extends DataTranslator {
|
|
|
5699
6086
|
}
|
|
5700
6087
|
}
|
|
5701
6088
|
|
|
6089
|
+
// EXTERNAL MODULE: ./src/schema/utils/storageDates.ts
|
|
6090
|
+
var storageDates = __webpack_require__(894);
|
|
5702
6091
|
;// CONCATENATED MODULE: ./src/plugins/translators/SqlTranslator.ts
|
|
5703
6092
|
|
|
5704
6093
|
|
|
6094
|
+
|
|
5705
6095
|
/**
|
|
5706
6096
|
* A stored vector as a list of numbers, whatever the driver handed back.
|
|
5707
6097
|
*
|
|
@@ -5730,6 +6120,31 @@ class SqlTranslator extends DataTranslator {
|
|
|
5730
6120
|
super(query);
|
|
5731
6121
|
this.pushedDown = pushedDown;
|
|
5732
6122
|
}
|
|
6123
|
+
/**
|
|
6124
|
+
* Dates back as Dates, before the caller's selectors run over the rows.
|
|
6125
|
+
*
|
|
6126
|
+
* A `group` key and a `map` are the caller's lambdas, run here over rows as the engine returned them.
|
|
6127
|
+
* SQLite, D1 and libSQL hand a date back as the TEXT it was stored as, which has no `getFullYear()`
|
|
6128
|
+
* and groups apart from the Date the entity holds. Revived at storage paths and in place, which the
|
|
6129
|
+
* datastore's deserialization still reads, and after `decodeJsonColumns`, so a date inside a JSON
|
|
6130
|
+
* column is revived too. Only a string is converted, so an engine that returns a Date (PostgreSQL,
|
|
6131
|
+
* PGlite, MySQL) is left alone, and so is a row already revived.
|
|
6132
|
+
*
|
|
6133
|
+
* Not a joined statement's rows, which are tuples, each half already deserialized. Nor rows whose
|
|
6134
|
+
* `group` or `map` was handed back, which the datastore runs after deserializing them.
|
|
6135
|
+
*/ translate(data) {
|
|
6136
|
+
const runsHere = (name)=>this.query.options.get(name).some((item)=>item.option.target === "database" && item.option.reason === "executed");
|
|
6137
|
+
if (this.pushedDown.join !== true && this.query.schema != null && Array.isArray(data) && (runsHere("group") || runsHere("map"))) {
|
|
6138
|
+
const reviveDates = (0,storageDates/* .getStorageDateReviver */.T)(this.query.schema);
|
|
6139
|
+
for(let i = 0, length = reviveDates == null ? 0 : data.length; i < length; i++){
|
|
6140
|
+
const row = data[i];
|
|
6141
|
+
if (row != null && typeof row === "object") {
|
|
6142
|
+
reviveDates(row);
|
|
6143
|
+
}
|
|
6144
|
+
}
|
|
6145
|
+
}
|
|
6146
|
+
return super.translate(data);
|
|
6147
|
+
}
|
|
5733
6148
|
count(data, _) {
|
|
5734
6149
|
if (Array.isArray(data) && data.length > 0) {
|
|
5735
6150
|
// Count is returned as the property alias on the query.
|
|
@@ -6181,7 +6596,6 @@ const isParameter = (value)=>typeof value === "object" && value !== null && PARA
|
|
|
6181
6596
|
*/ const MEMORY_EXECUTION_EXPLANATIONS = {
|
|
6182
6597
|
"not-parsable": "A filter could not be parsed into an expression tree, so it and every option after it run in memory.",
|
|
6183
6598
|
"unmapped-property": "The property is not stored in the database, so it can only be read after deserialization.",
|
|
6184
|
-
"renamed-property": "The property is stored under a different name, and selectors use the in-memory name, so it can only be read after deserialization.",
|
|
6185
6599
|
"map-rename": "A map renames or drops properties, so every option after it refers to names the database does not have.",
|
|
6186
6600
|
"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.",
|
|
6187
6601
|
"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.",
|
|
@@ -6683,6 +7097,80 @@ const formatStep = (step, lines)=>{
|
|
|
6683
7097
|
return lines.join("\n");
|
|
6684
7098
|
};
|
|
6685
7099
|
|
|
7100
|
+
// EXTERNAL MODULE: ./src/expressions/utils.ts
|
|
7101
|
+
var utils = __webpack_require__(63);
|
|
7102
|
+
;// CONCATENATED MODULE: ./src/plugins/query/renames.ts
|
|
7103
|
+
|
|
7104
|
+
|
|
7105
|
+
const PROPERTY_READING_OPTIONS = [
|
|
7106
|
+
"filter",
|
|
7107
|
+
"sort",
|
|
7108
|
+
"nearest",
|
|
7109
|
+
"map",
|
|
7110
|
+
"group"
|
|
7111
|
+
];
|
|
7112
|
+
const namesRenamedProperty = (expression)=>{
|
|
7113
|
+
let found = false;
|
|
7114
|
+
if (expression == null) {
|
|
7115
|
+
return found;
|
|
7116
|
+
}
|
|
7117
|
+
(0,utils/* .forEach */.jJ)(expression, (node)=>{
|
|
7118
|
+
if ((0,assertions.isPropertyExpression)(node) && node.property.hasRenamedSegments) {
|
|
7119
|
+
found = true;
|
|
7120
|
+
return false;
|
|
7121
|
+
}
|
|
7122
|
+
return true;
|
|
7123
|
+
});
|
|
7124
|
+
return found;
|
|
7125
|
+
};
|
|
7126
|
+
const isRenamed = (property)=>property != null && property.hasRenamedSegments;
|
|
7127
|
+
/**
|
|
7128
|
+
* Whether a selector's value is read from a renamed property, whether it is that property or computed
|
|
7129
|
+
* from it: `r => r.dueDate.getTime()` reads `dueDate` all the same. Every property it reads when the
|
|
7130
|
+
* selector was parsed, and otherwise the property recorded for it.
|
|
7131
|
+
*/ const readsRenamedValue = (value)=>value != null && (value.reads != null ? value.reads.some(isRenamed) : isRenamed(value.property));
|
|
7132
|
+
const readsRenamedField = (fields)=>fields != null && fields.some(readsRenamedValue);
|
|
7133
|
+
const readsRenamedProperty = (name, value)=>{
|
|
7134
|
+
switch(name){
|
|
7135
|
+
case "filter":
|
|
7136
|
+
return namesRenamedProperty(value.expression);
|
|
7137
|
+
case "map":
|
|
7138
|
+
// A projection reads each field it selects
|
|
7139
|
+
return readsRenamedField(value.fields);
|
|
7140
|
+
case "group":
|
|
7141
|
+
// A group reads its key, then copies every field of the row into its members: every schema
|
|
7142
|
+
// property, or what a `map` before it selected
|
|
7143
|
+
return readsRenamedValue(value.key) || readsRenamedField(value.fields);
|
|
7144
|
+
default:
|
|
7145
|
+
return readsRenamedValue(value);
|
|
7146
|
+
}
|
|
7147
|
+
};
|
|
7148
|
+
/**
|
|
7149
|
+
* Hands back every option over a property stored under a `.from()` name, for the datastore to run
|
|
7150
|
+
* in memory.
|
|
7151
|
+
*
|
|
7152
|
+
* Core keeps such an option with the database, because only the plugin knows whether its backend
|
|
7153
|
+
* reads storage names. One that translates the option — SQL renders the column from
|
|
7154
|
+
* `getResolvedName()` — needs nothing from here. One that runs the caller's lambda over rows as it
|
|
7155
|
+
* stores them reads a key the row does not have, and answers wrongly without an error: that plugin
|
|
7156
|
+
* calls this before it reads anything, and the datastore finishes the query after deserialization,
|
|
7157
|
+
* where the in-memory names exist.
|
|
7158
|
+
*
|
|
7159
|
+
* Reported as `missing-capability`: the backend cannot express the option as written, and like
|
|
7160
|
+
* every capability, that is only knowable by the plugin.
|
|
7161
|
+
*
|
|
7162
|
+
* @param names Which options to check, for a plugin that resolves some of them itself — Mongo renders
|
|
7163
|
+
* filters and sorts through the stored path, and runs `nearest`, `map` and `group` in JavaScript.
|
|
7164
|
+
*/ const reportRenamedProperties = (options, names = PROPERTY_READING_OPTIONS)=>{
|
|
7165
|
+
for (const name of names){
|
|
7166
|
+
for (const item of options.get(name)){
|
|
7167
|
+
if (readsRenamedProperty(name, item.option.value)) {
|
|
7168
|
+
options.reportMissingCapability(item);
|
|
7169
|
+
}
|
|
7170
|
+
}
|
|
7171
|
+
}
|
|
7172
|
+
};
|
|
7173
|
+
|
|
6686
7174
|
;// CONCATENATED MODULE: ./src/plugins/query/types.ts
|
|
6687
7175
|
var types_QueryOrdering = /*#__PURE__*/ function(QueryOrdering) {
|
|
6688
7176
|
QueryOrdering["Descending"] = "desc";
|
|
@@ -6700,6 +7188,7 @@ var types_QueryOrdering = /*#__PURE__*/ function(QueryOrdering) {
|
|
|
6700
7188
|
|
|
6701
7189
|
|
|
6702
7190
|
|
|
7191
|
+
|
|
6703
7192
|
// EXTERNAL MODULE: ./src/expressions/evaluate.ts
|
|
6704
7193
|
var evaluate = __webpack_require__(379);
|
|
6705
7194
|
// EXTERNAL MODULE: ./src/expressions/fold.ts
|
|
@@ -6713,6 +7202,9 @@ var fold = __webpack_require__(43);
|
|
|
6713
7202
|
* Everything except `map` and `group`. Those two are defined BY a closure — the projection is the
|
|
6714
7203
|
* option — and no data form of them exists to send. Nothing else needs its closure: a sort is a
|
|
6715
7204
|
* property and a direction, a filter is an expression tree, `nearest` is a vector and a count.
|
|
7205
|
+
*
|
|
7206
|
+
* Except a sort or `nearest` whose selector computes its value, such as `x => x.name.length`. It is sent
|
|
7207
|
+
* as the property it reads, and the receiver would order by that instead. See `isSendable`.
|
|
6716
7208
|
*/ const SENDABLE = new Set([
|
|
6717
7209
|
"skip",
|
|
6718
7210
|
"take",
|
|
@@ -6726,6 +7218,7 @@ var fold = __webpack_require__(43);
|
|
|
6726
7218
|
"sum",
|
|
6727
7219
|
"distinct"
|
|
6728
7220
|
]);
|
|
7221
|
+
const isSendable = (name, value)=>SENDABLE.has(name) && (name !== "sort" && name !== "nearest" || value.isDirectProperty !== false);
|
|
6729
7222
|
/**
|
|
6730
7223
|
* Splits options into the PREFIX that can be sent and the remainder that cannot.
|
|
6731
7224
|
*
|
|
@@ -6741,7 +7234,12 @@ var fold = __webpack_require__(43);
|
|
|
6741
7234
|
const local = new QueryOptionsCollection/* .QueryOptionsCollection */.H();
|
|
6742
7235
|
let stopped = false;
|
|
6743
7236
|
options.forEach((option)=>{
|
|
6744
|
-
|
|
7237
|
+
// Reported by the plugin, so it belongs to the datastore, and so does everything after it —
|
|
7238
|
+
// a report cascades to the end of the database phase, which keeps what is left a prefix
|
|
7239
|
+
if (option.target === "database" && option.reason !== "executed") {
|
|
7240
|
+
return;
|
|
7241
|
+
}
|
|
7242
|
+
if (stopped === false && isSendable(option.name, option.value) === false) {
|
|
6745
7243
|
stopped = true;
|
|
6746
7244
|
}
|
|
6747
7245
|
(stopped ? local : sendable).add(option.name, option.value);
|
|
@@ -7608,6 +8106,15 @@ class EphemeralDataPlugin {
|
|
|
7608
8106
|
*/ get databaseName() {
|
|
7609
8107
|
return this._databaseName;
|
|
7610
8108
|
}
|
|
8109
|
+
/**
|
|
8110
|
+
* Whether the records this plugin holds are in storage shape, keyed by `from` names.
|
|
8111
|
+
*
|
|
8112
|
+
* True for every store of what the datastore serialized, which is why a renamed property is
|
|
8113
|
+
* reported and records are cloned and keyed by their storage names. The datastore's change probe
|
|
8114
|
+
* holds rows the broadcast has already deserialized, so it reads them by in-memory names instead.
|
|
8115
|
+
*/ get holdsStorageShape() {
|
|
8116
|
+
return true;
|
|
8117
|
+
}
|
|
7611
8118
|
/**
|
|
7612
8119
|
* All-or-nothing across every collection in the save.
|
|
7613
8120
|
*
|
|
@@ -7815,7 +8322,9 @@ class EphemeralDataPlugin {
|
|
|
7815
8322
|
* join discards the surplus. Same pairs either way.
|
|
7816
8323
|
*/ resolveJoinInnerSide(event, outerKeys, done) {
|
|
7817
8324
|
const joinOption = event.operation.options.getLast("join");
|
|
7818
|
-
|
|
8325
|
+
// Not reached when an option before it was reported: the datastore's own join branch pairs
|
|
8326
|
+
// the rows this read returns.
|
|
8327
|
+
if (joinOption == null || joinOption.reason !== "executed") {
|
|
7819
8328
|
done({
|
|
7820
8329
|
ok: "success"
|
|
7821
8330
|
});
|
|
@@ -7842,7 +8351,7 @@ class EphemeralDataPlugin {
|
|
|
7842
8351
|
const innerRows = [];
|
|
7843
8352
|
// Records are held in STORAGE shape, so the key is read by its resolved column name.
|
|
7844
8353
|
const innerKey = joinOption.value.innerKey;
|
|
7845
|
-
const keyColumn = innerKey.property?.getResolvedName() ?? innerKey.propertyName;
|
|
8354
|
+
const keyColumn = this.holdsStorageShape ? innerKey.property?.getResolvedName() ?? innerKey.propertyName : innerKey.propertyName;
|
|
7846
8355
|
for (const record of innerCollection.values()){
|
|
7847
8356
|
if (outerKeys != null && outerKeys.has(record[keyColumn]) === false) {
|
|
7848
8357
|
continue;
|
|
@@ -7871,7 +8380,7 @@ class EphemeralDataPlugin {
|
|
|
7871
8380
|
* to fall back to `structuredClone`, which is roughly an order of magnitude slower and was paid
|
|
7872
8381
|
* on EVERY read of EVERY schema that renames a property.
|
|
7873
8382
|
*/ recordCloner(schema) {
|
|
7874
|
-
const hasRenamedProperties = schema.properties.some((property)=>property.from != null);
|
|
8383
|
+
const hasRenamedProperties = this.holdsStorageShape && schema.properties.some((property)=>property.from != null);
|
|
7875
8384
|
return hasRenamedProperties ? schema.cloneStorage : schema.clone;
|
|
7876
8385
|
}
|
|
7877
8386
|
query(event, done) {
|
|
@@ -7883,6 +8392,12 @@ class EphemeralDataPlugin {
|
|
|
7883
8392
|
const schema = operation.schema;
|
|
7884
8393
|
const collection = this.resolveCollection(schema);
|
|
7885
8394
|
const cloneRecord = this.recordCloner(schema);
|
|
8395
|
+
// Records are held in storage shape and every option below runs the caller's lambda
|
|
8396
|
+
// over them, so a `from` property is read by a name the record does not have. Handed
|
|
8397
|
+
// back, and the datastore runs it after deserialization.
|
|
8398
|
+
if (this.holdsStorageShape) {
|
|
8399
|
+
reportRenamedProperties(operation.options);
|
|
8400
|
+
}
|
|
7886
8401
|
collection.load((r)=>{
|
|
7887
8402
|
if (r.ok === Result/* .Result.ERROR */.Q.ERROR) {
|
|
7888
8403
|
done(Result/* .PluginEventResult.error */.D.error(event.id, r.error));
|
|
@@ -7891,7 +8406,9 @@ class EphemeralDataPlugin {
|
|
|
7891
8406
|
const orderedOptions = [];
|
|
7892
8407
|
operation.options.forEach((o)=>orderedOptions.push(o));
|
|
7893
8408
|
let leadingFilterCount = 0;
|
|
7894
|
-
|
|
8409
|
+
// Stops at a reported filter too: the database phase ends there, and the datastore
|
|
8410
|
+
// runs it and everything after it.
|
|
8411
|
+
while(leadingFilterCount < orderedOptions.length && orderedOptions[leadingFilterCount].name === "filter" && orderedOptions[leadingFilterCount].reason === "executed"){
|
|
7895
8412
|
leadingFilterCount++;
|
|
7896
8413
|
}
|
|
7897
8414
|
// Key-equality fast path: when a leading filter's parsed expression pins
|
|
@@ -7969,14 +8486,14 @@ class EphemeralDataPlugin {
|
|
|
7969
8486
|
* that was never applied.
|
|
7970
8487
|
*
|
|
7971
8488
|
* Before the inner side, to match execution order.
|
|
7972
|
-
*/ const described = describeFilters(operation.options.get("filter").map((entry)=>entry.option.value));
|
|
8489
|
+
*/ const described = describeFilters(operation.options.get("filter").filter((entry)=>entry.option.reason === "executed").map((entry)=>entry.option.value));
|
|
7973
8490
|
event.executedQueries.push({
|
|
7974
8491
|
text: `${operation.schema.collectionName}: scanned ${cloned.length} in-memory ` + `${cloned.length === 1 ? "record" : "records"}, filter ${described.text}`,
|
|
7975
8492
|
parameters: described.parameters.length > 0 ? described.parameters : undefined
|
|
7976
8493
|
});
|
|
7977
8494
|
const joinOption = operation.options.getLast("join");
|
|
7978
|
-
const outerKeys = joinOption == null ? null : distinctJoinKeys(cloned, joinOption.value.outerKey, joinOption.value.semiJoinKeyThreshold, {
|
|
7979
|
-
storageShape:
|
|
8495
|
+
const outerKeys = joinOption == null || joinOption.reason !== "executed" ? null : distinctJoinKeys(cloned, joinOption.value.outerKey, joinOption.value.semiJoinKeyThreshold, {
|
|
8496
|
+
storageShape: this.holdsStorageShape
|
|
7980
8497
|
});
|
|
7981
8498
|
this.resolveJoinInnerSide(event, outerKeys, (joinResult)=>{
|
|
7982
8499
|
if (joinResult.ok === "error") {
|
|
@@ -8152,7 +8669,7 @@ class TelemetryDbPlugin {
|
|
|
8152
8669
|
*
|
|
8153
8670
|
* The clone is a real date and fails `instanceof Date`, which is what a caller checks. Mutated in
|
|
8154
8671
|
* place because the clone is already private to this call.
|
|
8155
|
-
*/ const
|
|
8672
|
+
*/ const CacheDbPlugin_reviveDates = (value)=>{
|
|
8156
8673
|
if (value == null || typeof value !== "object") {
|
|
8157
8674
|
return value;
|
|
8158
8675
|
}
|
|
@@ -8161,12 +8678,12 @@ class TelemetryDbPlugin {
|
|
|
8161
8678
|
}
|
|
8162
8679
|
if (Array.isArray(value)) {
|
|
8163
8680
|
for(let i = 0, length = value.length; i < length; i++){
|
|
8164
|
-
value[i] =
|
|
8681
|
+
value[i] = CacheDbPlugin_reviveDates(value[i]);
|
|
8165
8682
|
}
|
|
8166
8683
|
return value;
|
|
8167
8684
|
}
|
|
8168
8685
|
for (const key of Object.keys(value)){
|
|
8169
|
-
value[key] =
|
|
8686
|
+
value[key] = CacheDbPlugin_reviveDates(value[key]);
|
|
8170
8687
|
}
|
|
8171
8688
|
return value;
|
|
8172
8689
|
};
|
|
@@ -8208,7 +8725,7 @@ class CacheDbPlugin {
|
|
|
8208
8725
|
* the next update would be written UNCHECKED with no error anywhere.
|
|
8209
8726
|
* Pinned by `datastore/src/collections/wrapperStacking.test.ts`.
|
|
8210
8727
|
*/ rebuild(entry) {
|
|
8211
|
-
return new entry.construct(
|
|
8728
|
+
return new entry.construct(CacheDbPlugin_reviveDates(structuredClone(entry.value)), entry.isTransformed);
|
|
8212
8729
|
}
|
|
8213
8730
|
query(event, done) {
|
|
8214
8731
|
const key = this.keyFor(event);
|