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