@routier/core 0.6.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/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/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 +8 -0
- package/dist/collections/index.cjs +141 -4
- package/dist/collections/index.cjs.map +1 -1
- package/dist/collections/index.js +141 -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 +1985 -242
- package/dist/expressions/index.cjs.map +1 -1
- package/dist/expressions/index.d.ts +2 -0
- package/dist/expressions/index.js +1997 -243
- package/dist/expressions/index.js.map +1 -1
- package/dist/expressions/parser.d.ts +42 -1
- package/dist/expressions/types.d.ts +45 -26
- package/dist/expressions/utils.d.ts +19 -1
- package/dist/index.cjs +3139 -680
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3479 -997
- 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/EphemeralDataPlugin.d.ts +8 -0
- package/dist/plugins/index.cjs +2921 -411
- package/dist/plugins/index.cjs.map +1 -1
- package/dist/plugins/index.js +2863 -343
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/query/QueryOptionsCollection.d.ts +48 -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 +2 -0
- package/dist/plugins/query/join.d.ts +4 -1
- package/dist/plugins/query/renames.d.ts +27 -0
- package/dist/plugins/query/types.d.ts +50 -4
- package/dist/plugins/translators/SqlTranslator.d.ts +15 -0
- package/dist/schema/PropertyInfo.d.ts +0 -1
- package/dist/schema/SchemaDefinition.d.ts +8 -0
- package/dist/schema/changeTracker.d.ts +10 -0
- package/dist/schema/index.cjs +298 -288
- package/dist/schema/index.cjs.map +1 -1
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +301 -290
- 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 +306 -68
- package/dist/utilities/index.cjs.map +1 -1
- package/dist/utilities/index.js +306 -68
- package/dist/utilities/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/codegen/utils.d.ts +0 -22
package/dist/utilities/index.cjs
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
var __webpack_modules__ = ({
|
|
4
4
|
126(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
5
5
|
__webpack_require__.d(__webpack_exports__, {
|
|
6
|
-
|
|
6
|
+
isComparatorExpression: () => (isComparatorExpression),
|
|
7
|
+
isPropertyExpression: () => (isPropertyExpression),
|
|
8
|
+
isValueExpression: () => (isValueExpression)
|
|
7
9
|
});
|
|
8
10
|
|
|
9
11
|
|
|
@@ -75,6 +77,11 @@ function isObjectWithType(value) {
|
|
|
75
77
|
*/ function isValueExpression(value) {
|
|
76
78
|
return isObjectWithType(value) && value.type === "value";
|
|
77
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Type guard: narrows `value` to `CallExpression` when it is an object with `type === "call"`.
|
|
82
|
+
*/ function isCallExpression(value) {
|
|
83
|
+
return isObjectWithType(value) && value.type === "call";
|
|
84
|
+
}
|
|
78
85
|
/**
|
|
79
86
|
* Type guard: narrows `value` to `EmptyExpression` when it is an object with `type === "empty"`.
|
|
80
87
|
*/ function isEmptyExpression(value) {
|
|
@@ -90,8 +97,42 @@ function isObjectWithType(value) {
|
|
|
90
97
|
},
|
|
91
98
|
63(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
92
99
|
__webpack_require__.d(__webpack_exports__, {
|
|
93
|
-
|
|
100
|
+
jJ: () => (forEach)
|
|
94
101
|
});
|
|
102
|
+
/**
|
|
103
|
+
* Separates an operand from the calls applied to it.
|
|
104
|
+
*
|
|
105
|
+
* `null` when there is no operand beneath the calls. Every consumer needs this to decide whether a
|
|
106
|
+
* comparator side is a property or a value, so it lives here rather than in each translator.
|
|
107
|
+
*/ function peelCalls(expression) {
|
|
108
|
+
const calls = [];
|
|
109
|
+
let current = expression;
|
|
110
|
+
while(current != null && current.type === "call"){
|
|
111
|
+
calls.unshift(current);
|
|
112
|
+
current = current.expression;
|
|
113
|
+
}
|
|
114
|
+
return current == null ? null : {
|
|
115
|
+
operand: current,
|
|
116
|
+
calls
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function childrenOf(expression) {
|
|
120
|
+
if (expression.type === "call") {
|
|
121
|
+
const call = expression;
|
|
122
|
+
return [
|
|
123
|
+
call.expression,
|
|
124
|
+
...call.arguments ?? []
|
|
125
|
+
].filter((child)=>child != null);
|
|
126
|
+
}
|
|
127
|
+
const children = [];
|
|
128
|
+
if (expression.left != null) {
|
|
129
|
+
children.push(expression.left);
|
|
130
|
+
}
|
|
131
|
+
if (expression.right != null) {
|
|
132
|
+
children.push(expression.right);
|
|
133
|
+
}
|
|
134
|
+
return children;
|
|
135
|
+
}
|
|
95
136
|
/**
|
|
96
137
|
* Extracts all properties referenced in an expression
|
|
97
138
|
* @param expression The expression to analyze
|
|
@@ -103,12 +144,8 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
103
144
|
if (expr.type === "property") {
|
|
104
145
|
properties.push(expr.property);
|
|
105
146
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
traverse(expr.left);
|
|
109
|
-
}
|
|
110
|
-
if (expr.right) {
|
|
111
|
-
traverse(expr.right);
|
|
147
|
+
for (const child of childrenOf(expr)){
|
|
148
|
+
traverse(child);
|
|
112
149
|
}
|
|
113
150
|
}
|
|
114
151
|
traverse(expression);
|
|
@@ -121,14 +158,8 @@ function forEach(expression, callback) {
|
|
|
121
158
|
if (!callback(expr)) {
|
|
122
159
|
return false;
|
|
123
160
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (!traverse(expr.left)) {
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
if (expr.right) {
|
|
131
|
-
if (!traverse(expr.right)) {
|
|
161
|
+
for (const child of childrenOf(expr)){
|
|
162
|
+
if (!traverse(child)) {
|
|
132
163
|
return false;
|
|
133
164
|
}
|
|
134
165
|
}
|
|
@@ -144,32 +175,86 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
144
175
|
H: () => (QueryOptionsCollection)
|
|
145
176
|
});
|
|
146
177
|
/* import */ var _assertions__rspack_import_1 = __webpack_require__(126);
|
|
147
|
-
/* import */ var
|
|
178
|
+
/* import */ var _expressions_utils__rspack_import_2 = __webpack_require__(63);
|
|
179
|
+
/* import */ var _schema_types__rspack_import_0 = __webpack_require__(537);
|
|
180
|
+
/* import */ var _utilities__rspack_import_3 = __webpack_require__(581);
|
|
181
|
+
|
|
182
|
+
|
|
148
183
|
|
|
149
184
|
|
|
185
|
+
/** What a schema type is called in JavaScript, where one exists. A value of any other type cannot equal it. */ const JAVASCRIPT_TYPE_OF = {
|
|
186
|
+
[_schema_types__rspack_import_0/* .SchemaTypes.Number */.L.Number]: "number",
|
|
187
|
+
[_schema_types__rspack_import_0/* .SchemaTypes.String */.L.String]: "string",
|
|
188
|
+
[_schema_types__rspack_import_0/* .SchemaTypes.Boolean */.L.Boolean]: "boolean",
|
|
189
|
+
[_schema_types__rspack_import_0/* .SchemaTypes.Date */.L.Date]: "object"
|
|
190
|
+
};
|
|
191
|
+
const mismatchedSide = (property, value)=>{
|
|
192
|
+
if (property == null || value == null || !(0,_assertions__rspack_import_1.isPropertyExpression)(property) || !(0,_assertions__rspack_import_1.isValueExpression)(value)) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
const expected = JAVASCRIPT_TYPE_OF[property.property.type];
|
|
196
|
+
if (expected == null || value.value == null || typeof value.value === expected) {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
property,
|
|
201
|
+
value,
|
|
202
|
+
expected
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
/** A strict comparison whose answer is the same for every row, because the types cannot be equal. */ const comparesTypesThatCannotMatch = (expression)=>{
|
|
206
|
+
if (!(0,_assertions__rspack_import_1.isComparatorExpression)(expression) || expression.strict !== true) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
if (expression.comparator !== "equals") {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
return mismatchedSide(expression.left, expression.right) != null || mismatchedSide(expression.right, expression.left) != null;
|
|
213
|
+
};
|
|
214
|
+
/** `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);
|
|
215
|
+
const mismatchWarning = (expression)=>{
|
|
216
|
+
const side = mismatchedSide(expression.left, expression.right) ?? mismatchedSide(expression.right, expression.left);
|
|
217
|
+
const outcome = expression.negated ? "every row matches" : "no row matches";
|
|
218
|
+
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`;
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* An item as one dispatch receives it: a new object, so a report written on it stays with that dispatch.
|
|
222
|
+
*
|
|
223
|
+
* A database option starts `executed` again, because a report is only an answer from the plugin that
|
|
224
|
+
* made it. A memory option keeps the reason core planned it with. A join's inner options are copied the
|
|
225
|
+
* same way, since a plugin can report on them too.
|
|
226
|
+
*/ const toDispatchItem = (item)=>{
|
|
227
|
+
const option = item.option;
|
|
228
|
+
const value = option.name === "join" ? {
|
|
229
|
+
...option.value,
|
|
230
|
+
innerOptions: option.value.innerOptions.forDispatch()
|
|
231
|
+
} : option.value;
|
|
232
|
+
return {
|
|
233
|
+
index: item.index,
|
|
234
|
+
option: option.target === "database" ? {
|
|
235
|
+
...option,
|
|
236
|
+
value,
|
|
237
|
+
reason: "executed"
|
|
238
|
+
} : {
|
|
239
|
+
...option,
|
|
240
|
+
value
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
};
|
|
150
244
|
class QueryOptionsCollection {
|
|
151
245
|
options = new Map();
|
|
152
246
|
nextExecutionTarget = "database";
|
|
153
247
|
nextExecutionReason = null;
|
|
154
248
|
nextIndex = 0;
|
|
155
249
|
enumeratedItems = [];
|
|
250
|
+
dirty = true;
|
|
251
|
+
/** The collection a `splitAt`/`split` half came from. A capability report belongs to it. */ origin = null;
|
|
156
252
|
/** Cuts over to memory execution, keeping the first cause. See `MemoryExecutionReason`. */ cutOverToMemory(reason) {
|
|
157
253
|
this.nextExecutionTarget = "memory";
|
|
158
254
|
if (this.nextExecutionReason == null) {
|
|
159
255
|
this.nextExecutionReason = reason;
|
|
160
256
|
}
|
|
161
257
|
}
|
|
162
|
-
/**
|
|
163
|
-
* True when `split()` or `splitAt()` produced this collection.
|
|
164
|
-
*
|
|
165
|
-
* Those rebuild each half by re-adding its options, which re-derives execution targets
|
|
166
|
-
* without the options that caused them — a post-join filter alone in the memory half
|
|
167
|
-
* derives back to `"database"`. Anything reading `target` as a report of where work runs
|
|
168
|
-
* has to reject a derived collection; see `explainQuery`.
|
|
169
|
-
*/ derived = false;
|
|
170
|
-
get isDerived() {
|
|
171
|
-
return this.derived;
|
|
172
|
-
}
|
|
173
258
|
get items() {
|
|
174
259
|
return this.options;
|
|
175
260
|
}
|
|
@@ -194,7 +279,7 @@ class QueryOptionsCollection {
|
|
|
194
279
|
}
|
|
195
280
|
}
|
|
196
281
|
if (name === "filter") {
|
|
197
|
-
// Need to check for unmapped
|
|
282
|
+
// Need to check for unmapped properties
|
|
198
283
|
const filterValue = value;
|
|
199
284
|
// A tautology (`x => true`) filters nothing — skip it entirely so
|
|
200
285
|
// plugins never see it
|
|
@@ -204,19 +289,20 @@ class QueryOptionsCollection {
|
|
|
204
289
|
if (filterValue.expression.type === "not-parsable") {
|
|
205
290
|
this.cutOverToMemory("not-parsable");
|
|
206
291
|
} else {
|
|
207
|
-
(0,
|
|
292
|
+
(0,_expressions_utils__rspack_import_2/* .forEach */.jJ)(filterValue.expression, (expression)=>{
|
|
208
293
|
if ((0,_assertions__rspack_import_1.isPropertyExpression)(expression) && expression.property.isUnmapped) {
|
|
209
294
|
// Cut over to memory execution, unmapped properties are not in the database and
|
|
210
295
|
// cannot be queried
|
|
211
296
|
this.cutOverToMemory("unmapped-property");
|
|
212
297
|
return false;
|
|
213
298
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
299
|
+
// A renamed property stays with the database. Whether the backend can read a
|
|
300
|
+
// `from` name is the plugin's to know, not this collection's: the property
|
|
301
|
+
// travels with the option, and a plugin that cannot resolve it reports it
|
|
302
|
+
// back — see `reportRenamedProperties`
|
|
303
|
+
if (comparesTypesThatCannotMatch(expression)) {
|
|
304
|
+
_utilities__rspack_import_3/* .logger.warn */.vF.warn(mismatchWarning(expression));
|
|
305
|
+
this.cutOverToMemory("predicate-error");
|
|
220
306
|
return false;
|
|
221
307
|
}
|
|
222
308
|
return true;
|
|
@@ -225,28 +311,26 @@ class QueryOptionsCollection {
|
|
|
225
311
|
}
|
|
226
312
|
if (name === "sort") {
|
|
227
313
|
const sortValue = value;
|
|
228
|
-
// Same rule as filters:
|
|
229
|
-
//
|
|
314
|
+
// Same rule as filters: an unmapped property only exists after deserialization. A
|
|
315
|
+
// renamed one stays with the database, for the plugin to resolve or report
|
|
230
316
|
if (sortValue.property != null && sortValue.property.isUnmapped) {
|
|
231
317
|
this.cutOverToMemory("unmapped-property");
|
|
232
|
-
} else if (sortValue.property != null && sortValue.property.hasRenamedSegments) {
|
|
233
|
-
this.cutOverToMemory("renamed-property");
|
|
234
318
|
}
|
|
235
319
|
}
|
|
236
320
|
if (name === "nearest") {
|
|
237
321
|
const nearestValue = value;
|
|
238
|
-
// Same rule as sort, and for the same reason:
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
//
|
|
242
|
-
// This is also what lets every translator's in-memory fallback read the column by
|
|
243
|
-
// its resolved name — anything whose storage name differs never reaches them.
|
|
322
|
+
// Same rule as sort, and for the same reason: an unmapped property is not stored at
|
|
323
|
+
// all, so it is only readable after deserialization, which is where memory execution
|
|
324
|
+
// runs. A vector stored under a `from` name is the plugin's to resolve or report.
|
|
244
325
|
if (nearestValue.property != null && nearestValue.property.isUnmapped) {
|
|
245
326
|
this.cutOverToMemory("unmapped-property");
|
|
246
|
-
} else if (nearestValue.property != null && nearestValue.property.hasRenamedSegments) {
|
|
247
|
-
this.cutOverToMemory("renamed-property");
|
|
248
327
|
}
|
|
249
328
|
}
|
|
329
|
+
if ((name === "filter" || name === "sort") && (this.options.has("skip") || this.options.has("take"))) {
|
|
330
|
+
// SQL emits WHERE before LIMIT and Mongo's find() filters before skipping, so an option
|
|
331
|
+
// written after a window can only see the windowed rows if it runs after it.
|
|
332
|
+
this.cutOverToMemory("after-window");
|
|
333
|
+
}
|
|
250
334
|
if (name === "join") {
|
|
251
335
|
const joinValue = value;
|
|
252
336
|
// A join whose two sides live on different plugins cannot be sent to EITHER of
|
|
@@ -259,18 +343,24 @@ class QueryOptionsCollection {
|
|
|
259
343
|
this.cutOverToMemory("cross-plugin-join");
|
|
260
344
|
}
|
|
261
345
|
}
|
|
346
|
+
// `executed` is the plan, not a record: nothing has run when an option is added. Every
|
|
347
|
+
// consumer reads it after the plugin returned, so the optimistic window is never observed.
|
|
262
348
|
const item = {
|
|
263
349
|
index: this.nextIndex,
|
|
264
|
-
option: {
|
|
350
|
+
option: this.nextExecutionTarget === "database" ? {
|
|
265
351
|
name,
|
|
266
|
-
target: this.nextExecutionTarget,
|
|
267
352
|
value,
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
353
|
+
target: "database",
|
|
354
|
+
reason: "executed"
|
|
355
|
+
} : {
|
|
356
|
+
name,
|
|
357
|
+
value,
|
|
358
|
+
target: "memory",
|
|
359
|
+
reason: this.nextExecutionReason ?? "not-parsable"
|
|
271
360
|
}
|
|
272
361
|
};
|
|
273
362
|
this.nextIndex++;
|
|
363
|
+
this.dirty = true;
|
|
274
364
|
const found = this.options.get(name);
|
|
275
365
|
this.options.set(name, [
|
|
276
366
|
...found ?? [],
|
|
@@ -315,8 +405,6 @@ class QueryOptionsCollection {
|
|
|
315
405
|
const sortedItems = this.enumeratedItems.toSorted((a, b)=>a.index - b.index);
|
|
316
406
|
const before = new QueryOptionsCollection();
|
|
317
407
|
const after = new QueryOptionsCollection();
|
|
318
|
-
before.derived = true;
|
|
319
|
-
after.derived = true;
|
|
320
408
|
let at = null;
|
|
321
409
|
for(let i = 0, length = sortedItems.length; i < length; i++){
|
|
322
410
|
const { option } = sortedItems[i];
|
|
@@ -325,8 +413,10 @@ class QueryOptionsCollection {
|
|
|
325
413
|
continue;
|
|
326
414
|
}
|
|
327
415
|
const destination = at == null ? before : after;
|
|
328
|
-
destination.
|
|
416
|
+
destination.adopt(sortedItems[i]);
|
|
329
417
|
}
|
|
418
|
+
before.origin = this.origin ?? this;
|
|
419
|
+
after.origin = this.origin ?? this;
|
|
330
420
|
return {
|
|
331
421
|
before,
|
|
332
422
|
at,
|
|
@@ -340,6 +430,9 @@ class QueryOptionsCollection {
|
|
|
340
430
|
* the shared collection before executing. Without restoring, a re-executed terminal —
|
|
341
431
|
* the whole point of a subscribed queryable — stacks its option a second time and
|
|
342
432
|
* runs it over the first execution's scalar result.
|
|
433
|
+
*
|
|
434
|
+
* The item objects are shared with the snapshot. Nothing reports on them, because every
|
|
435
|
+
* dispatch sends a `forDispatch` copy, so a restore brings back no reports.
|
|
343
436
|
*/ snapshot() {
|
|
344
437
|
const options = new Map([
|
|
345
438
|
...this.options.entries()
|
|
@@ -358,23 +451,128 @@ class QueryOptionsCollection {
|
|
|
358
451
|
this.nextExecutionReason = nextExecutionReason;
|
|
359
452
|
this.nextIndex = nextIndex;
|
|
360
453
|
this.enumeratedItems = [];
|
|
454
|
+
// Clearing the list is not enough now that staleness is a flag rather than a count:
|
|
455
|
+
// without this, `resolveEnumeration` believes the empty list is current and every read
|
|
456
|
+
// of the collection sees no options at all.
|
|
457
|
+
this.dirty = true;
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
/** Takes an item as it stands — same object, same index, same target and reason. */ adopt(item) {
|
|
461
|
+
const found = this.options.get(item.option.name);
|
|
462
|
+
this.options.set(item.option.name, [
|
|
463
|
+
...found ?? [],
|
|
464
|
+
item
|
|
465
|
+
]);
|
|
466
|
+
this.nextIndex = Math.max(this.nextIndex, item.index + 1);
|
|
467
|
+
this.dirty = true;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* A plugin reporting that its engine cannot express one option.
|
|
471
|
+
*
|
|
472
|
+
* Core marks the rest of the database phase `not-reached`, because the database has to stop
|
|
473
|
+
* there — a window applied in front of a filter that was not applied returns the wrong rows.
|
|
474
|
+
* Passing the cascade through core is what makes it impossible for a plugin to mark a
|
|
475
|
+
* non-contiguous cut.
|
|
476
|
+
*
|
|
477
|
+
* A report names a culprit and never un-names one, so reports commute.
|
|
478
|
+
*
|
|
479
|
+
* The option is not moved to the memory arm. It stays where it was planned, which is what keeps
|
|
480
|
+
* a redirect distinguishable from something core sent to memory in the first place.
|
|
481
|
+
*/ reportMissingCapability(item) {
|
|
482
|
+
this.report(item, "missing-capability");
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* A plugin reporting that its engine would answer one option differently from JavaScript.
|
|
486
|
+
*
|
|
487
|
+
* Same cascade as `reportMissingCapability`, and a separate reason because the caller can act on
|
|
488
|
+
* one and not the other. See `DatabaseExecutionReason`.
|
|
489
|
+
*/ reportEngineDivergence(item) {
|
|
490
|
+
this.report(item, "engine-divergence");
|
|
491
|
+
}
|
|
492
|
+
report(item, reason) {
|
|
493
|
+
// A half can only see its own slice, and the database has to stop for the whole dispatch.
|
|
494
|
+
if (this.origin != null) {
|
|
495
|
+
this.origin.report(item, reason);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
this.resolveEnumeration();
|
|
499
|
+
for (const candidate of this.enumeratedItems){
|
|
500
|
+
if (candidate.option.target !== "database" || candidate.index < item.index) {
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
if (candidate.index === item.index) {
|
|
504
|
+
candidate.option.reason = reason;
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
if (candidate.option.reason === "executed") {
|
|
508
|
+
candidate.option.reason = "not-reached";
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* A copy of the collection for one dispatch to a plugin, with nothing reported on it.
|
|
514
|
+
*
|
|
515
|
+
* Capability is answered per dispatch, so a report is only an answer for the execution that
|
|
516
|
+
* produced it. Reports are written onto items, and the items of a queryable's collection
|
|
517
|
+
* outlive any one execution: a snapshot shares them, and a subscription dispatches the same
|
|
518
|
+
* query on every change. A report left on them replays options the plugin did run on the
|
|
519
|
+
* next execution, such as a `skip` applied twice over rows already windowed, or hands a
|
|
520
|
+
* renamed filter to memory that the engine could have run.
|
|
521
|
+
*
|
|
522
|
+
* Each item keeps its index, name, value and target. A half from `split`/`splitAt` is copied
|
|
523
|
+
* with a copy of its origin, and its items are that copy's items, so a report on the half still
|
|
524
|
+
* cascades over the whole dispatch without reaching the collection it was copied from.
|
|
525
|
+
*/ forDispatch() {
|
|
526
|
+
if (this.origin == null) {
|
|
527
|
+
return this.copyForDispatch().copy;
|
|
528
|
+
}
|
|
529
|
+
const { copy: root, copies } = this.origin.copyForDispatch();
|
|
530
|
+
const half = new QueryOptionsCollection();
|
|
531
|
+
this.resolveEnumeration();
|
|
532
|
+
for (const item of this.enumeratedItems){
|
|
533
|
+
// An item added to the half after it was split has no counterpart in the origin
|
|
534
|
+
half.adopt(copies.get(item) ?? toDispatchItem(item));
|
|
535
|
+
}
|
|
536
|
+
half.origin = root;
|
|
537
|
+
return half;
|
|
538
|
+
}
|
|
539
|
+
copyForDispatch() {
|
|
540
|
+
const copy = new QueryOptionsCollection();
|
|
541
|
+
const copies = new Map();
|
|
542
|
+
this.resolveEnumeration();
|
|
543
|
+
for (const item of this.enumeratedItems){
|
|
544
|
+
const copied = toDispatchItem(item);
|
|
545
|
+
copies.set(item, copied);
|
|
546
|
+
copy.adopt(copied);
|
|
547
|
+
}
|
|
548
|
+
copy.nextExecutionTarget = this.nextExecutionTarget;
|
|
549
|
+
copy.nextExecutionReason = this.nextExecutionReason;
|
|
550
|
+
copy.nextIndex = this.nextIndex;
|
|
551
|
+
return {
|
|
552
|
+
copy,
|
|
553
|
+
copies
|
|
361
554
|
};
|
|
362
555
|
}
|
|
556
|
+
/** The options the database did not run, in the order they were written. */ notExecuted() {
|
|
557
|
+
this.resolveEnumeration();
|
|
558
|
+
return this.enumeratedItems.filter((item)=>item.option.target === "database" && item.option.reason !== "executed").toSorted((a, b)=>a.index - b.index);
|
|
559
|
+
}
|
|
363
560
|
split() {
|
|
364
561
|
this.resolveEnumeration();
|
|
365
562
|
const sortedItems = this.enumeratedItems.toSorted((a, b)=>a.index - b.index);
|
|
366
563
|
const memoryQueryOptionsCollection = new QueryOptionsCollection();
|
|
367
564
|
const databaseQueryOptionsCollection = new QueryOptionsCollection();
|
|
368
|
-
memoryQueryOptionsCollection.derived = true;
|
|
369
|
-
databaseQueryOptionsCollection.derived = true;
|
|
370
565
|
for(let i = 0, length = sortedItems.length; i < length; i++){
|
|
371
566
|
const sortedItem = sortedItems[i];
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
567
|
+
const half = sortedItem.option.target === "database" ? databaseQueryOptionsCollection : memoryQueryOptionsCollection;
|
|
568
|
+
// The ITEM, not its name and value. Re-adding would re-derive target and reason from a
|
|
569
|
+
// fresh cascade, and a memory option re-added alone comes back out as `database` with no
|
|
570
|
+
// reason at all. Sharing it also means a plugin's report on the database half is the
|
|
571
|
+
// same object the explanation reads.
|
|
572
|
+
half.adopt(sortedItem);
|
|
377
573
|
}
|
|
574
|
+
memoryQueryOptionsCollection.origin = this.origin ?? this;
|
|
575
|
+
databaseQueryOptionsCollection.origin = this.origin ?? this;
|
|
378
576
|
return {
|
|
379
577
|
memory: memoryQueryOptionsCollection,
|
|
380
578
|
database: databaseQueryOptionsCollection
|
|
@@ -420,8 +618,11 @@ class QueryOptionsCollection {
|
|
|
420
618
|
].flat().toSorted((a, b)=>a.index - b.index);
|
|
421
619
|
}
|
|
422
620
|
resolveEnumeration() {
|
|
423
|
-
|
|
621
|
+
// A flag, not a count: adopting leaves gaps in the indexes, so `length !== nextIndex` is
|
|
622
|
+
// true forever on a half and the enumeration rebuilds on every read.
|
|
623
|
+
if (this.dirty === true) {
|
|
424
624
|
this.enumeratedItems = this.getEnumeration();
|
|
625
|
+
this.dirty = false;
|
|
425
626
|
}
|
|
426
627
|
}
|
|
427
628
|
forEach(iterator) {
|
|
@@ -433,6 +634,41 @@ class QueryOptionsCollection {
|
|
|
433
634
|
}
|
|
434
635
|
|
|
435
636
|
|
|
637
|
+
},
|
|
638
|
+
537(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
639
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
640
|
+
L: () => (SchemaTypes)
|
|
641
|
+
});
|
|
642
|
+
var SchemaTypes = /*#__PURE__*/ function(SchemaTypes) {
|
|
643
|
+
SchemaTypes["Array"] = "Array";
|
|
644
|
+
SchemaTypes["Boolean"] = "Boolean";
|
|
645
|
+
SchemaTypes["Date"] = "Date";
|
|
646
|
+
SchemaTypes["Number"] = "Number";
|
|
647
|
+
SchemaTypes["Object"] = "Object";
|
|
648
|
+
SchemaTypes["String"] = "String";
|
|
649
|
+
SchemaTypes["Definition"] = "Definition";
|
|
650
|
+
SchemaTypes["Function"] = "Function";
|
|
651
|
+
SchemaTypes["Computed"] = "Computed";
|
|
652
|
+
/**
|
|
653
|
+
* Content in, reference out. The only type whose write shape differs from its stored
|
|
654
|
+
* shape, and a leaf on purpose — see `SchemaFile`.
|
|
655
|
+
*/ SchemaTypes["File"] = "File";
|
|
656
|
+
/**
|
|
657
|
+
* A fixed-length list of numbers, carrying its dimension count — see `SchemaVector`.
|
|
658
|
+
*
|
|
659
|
+
* Value-shaped exactly like `s.array(s.number())`, which is why every array codegen
|
|
660
|
+
* handler accepts it. It is a distinct type only so a backend can recognise it and store
|
|
661
|
+
* it natively; nothing else needs to tell the two apart.
|
|
662
|
+
*/ SchemaTypes["Vector"] = "Vector";
|
|
663
|
+
return SchemaTypes;
|
|
664
|
+
}({});
|
|
665
|
+
var HashType = /*#__PURE__*/ (/* unused pure expression or super */ null && (function(HashType) {
|
|
666
|
+
HashType["Ids"] = "Ids";
|
|
667
|
+
HashType["Object"] = "Object";
|
|
668
|
+
return HashType;
|
|
669
|
+
}({})));
|
|
670
|
+
|
|
671
|
+
|
|
436
672
|
},
|
|
437
673
|
76(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
438
674
|
__webpack_require__.d(__webpack_exports__, {
|
|
@@ -529,12 +765,14 @@ const isLogLevel = (value)=>typeof value === 'string' && LOG_LEVELS.includes(val
|
|
|
529
765
|
const debug = process.env.DEBUG;
|
|
530
766
|
if (debug === 'routier' || debug === '*') return 'debug';
|
|
531
767
|
const env = "production"?.toLowerCase();
|
|
532
|
-
// `test` is deliberately absent. It used to be here, which meant no test suite anywhere
|
|
533
|
-
// could run Routier quietly. Opt in with DEBUG=routier or ROUTIER_LOG_LEVEL when a test
|
|
534
|
-
// needs the output.
|
|
535
768
|
if (env === 'dev' || env === 'development') return 'debug';
|
|
536
769
|
}
|
|
537
|
-
|
|
770
|
+
// Warnings are on unless something turns them off.
|
|
771
|
+
//
|
|
772
|
+
// Routier warns when a query returns correct rows a slower way than it could, or when a filter
|
|
773
|
+
// compares types that can never match. Both are the caller's to act on, and a default of
|
|
774
|
+
// `silent` meant the only people who ever saw them were the ones who already knew to look.
|
|
775
|
+
return 'warn';
|
|
538
776
|
};
|
|
539
777
|
let level = resolveLevel();
|
|
540
778
|
let rank = RANK[level];
|