@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.
Files changed (70) hide show
  1. package/dist/assertions/index.cjs +19 -8
  2. package/dist/assertions/index.cjs.map +1 -1
  3. package/dist/assertions/index.d.ts +5 -1
  4. package/dist/assertions/index.js +21 -9
  5. package/dist/assertions/index.js.map +1 -1
  6. package/dist/codegen/blocks.d.ts +17 -1
  7. package/dist/codegen/handlers/types.d.ts +13 -5
  8. package/dist/codegen/index.cjs +28 -0
  9. package/dist/codegen/index.cjs.map +1 -1
  10. package/dist/codegen/index.js +28 -0
  11. package/dist/codegen/index.js.map +1 -1
  12. package/dist/collections/MemoryDataCollection.d.ts +8 -0
  13. package/dist/collections/index.cjs +141 -4
  14. package/dist/collections/index.cjs.map +1 -1
  15. package/dist/collections/index.js +141 -4
  16. package/dist/collections/index.js.map +1 -1
  17. package/dist/expressions/callSource.d.ts +41 -0
  18. package/dist/expressions/evaluate.d.ts +3 -0
  19. package/dist/expressions/fold.d.ts +7 -0
  20. package/dist/expressions/index.cjs +1985 -242
  21. package/dist/expressions/index.cjs.map +1 -1
  22. package/dist/expressions/index.d.ts +2 -0
  23. package/dist/expressions/index.js +1997 -243
  24. package/dist/expressions/index.js.map +1 -1
  25. package/dist/expressions/parser.d.ts +42 -1
  26. package/dist/expressions/types.d.ts +45 -26
  27. package/dist/expressions/utils.d.ts +19 -1
  28. package/dist/index.cjs +3139 -680
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.js +3479 -997
  31. package/dist/index.js.map +1 -1
  32. package/dist/performance/index.cjs +6 -4
  33. package/dist/performance/index.cjs.map +1 -1
  34. package/dist/performance/index.js +6 -4
  35. package/dist/performance/index.js.map +1 -1
  36. package/dist/pipeline/index.cjs +6 -4
  37. package/dist/pipeline/index.cjs.map +1 -1
  38. package/dist/pipeline/index.js +6 -4
  39. package/dist/pipeline/index.js.map +1 -1
  40. package/dist/plugins/EphemeralDataPlugin.d.ts +8 -0
  41. package/dist/plugins/index.cjs +2921 -411
  42. package/dist/plugins/index.cjs.map +1 -1
  43. package/dist/plugins/index.js +2863 -343
  44. package/dist/plugins/index.js.map +1 -1
  45. package/dist/plugins/query/QueryOptionsCollection.d.ts +48 -10
  46. package/dist/plugins/query/describeFilter.d.ts +83 -0
  47. package/dist/plugins/query/explain.d.ts +71 -9
  48. package/dist/plugins/query/index.d.ts +2 -0
  49. package/dist/plugins/query/join.d.ts +4 -1
  50. package/dist/plugins/query/renames.d.ts +27 -0
  51. package/dist/plugins/query/types.d.ts +50 -4
  52. package/dist/plugins/translators/SqlTranslator.d.ts +15 -0
  53. package/dist/schema/PropertyInfo.d.ts +0 -1
  54. package/dist/schema/SchemaDefinition.d.ts +8 -0
  55. package/dist/schema/changeTracker.d.ts +10 -0
  56. package/dist/schema/index.cjs +298 -288
  57. package/dist/schema/index.cjs.map +1 -1
  58. package/dist/schema/index.d.ts +1 -0
  59. package/dist/schema/index.js +301 -290
  60. package/dist/schema/index.js.map +1 -1
  61. package/dist/schema/types.d.ts +8 -7
  62. package/dist/schema/utils/storageDates.d.ts +25 -0
  63. package/dist/transfer/index.cjs.map +1 -1
  64. package/dist/transfer/index.js.map +1 -1
  65. package/dist/utilities/index.cjs +306 -68
  66. package/dist/utilities/index.cjs.map +1 -1
  67. package/dist/utilities/index.js +306 -68
  68. package/dist/utilities/index.js.map +1 -1
  69. package/package.json +2 -2
  70. package/dist/codegen/utils.d.ts +0 -22
@@ -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
- isPropertyExpression: () => (isPropertyExpression)
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
- j: () => (forEach)
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
- // Traverse left and right expressions if they exist
107
- if (expr.left) {
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
- // Traverse left and right expressions if they exist
125
- if (expr.left) {
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 _expressions_utils__rspack_import_0 = __webpack_require__(63);
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 and renamed properties
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,_expressions_utils__rspack_import_0/* .forEach */.j)(filterValue.expression, (expression)=>{
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
- if ((0,_assertions__rspack_import_1.isPropertyExpression)(expression) && expression.property.hasRenamedSegments) {
215
- // Cut over to memory execution: the plugin stores data under the
216
- // `from` (storage) names, but filter selectors reference the
217
- // in-memory names. Memory execution runs after deserialization,
218
- // where the in-memory names exist
219
- this.cutOverToMemory("renamed-property");
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: sort selectors reference in-memory names, which
229
- // only exist after deserialization when the property is renamed or unmapped
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: the plugin stores the vector under
239
- // the `from` name, and an unmapped property is not stored at all. Both are only
240
- // readable after deserialization, which is where memory execution runs.
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
- ...this.nextExecutionReason == null ? {} : {
269
- reason: this.nextExecutionReason
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.add(option.name, option.value);
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
- if (sortedItem.option.target === "database") {
373
- databaseQueryOptionsCollection.add(sortedItem.option.name, sortedItem.option.value);
374
- continue;
375
- }
376
- memoryQueryOptionsCollection.add(sortedItem.option.name, sortedItem.option.value);
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
- if (this.enumeratedItems.length != this.nextIndex) {
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
- return 'silent';
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];