@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
@@ -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
- e3: () => (isPropertyExpression)
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
- j: () => (forEach)
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
- // Traverse left and right expressions if they exist
105
- if (expr.left) {
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
- // Traverse left and right expressions if they exist
123
- if (expr.left) {
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 _expressions_utils__rspack_import_0 = __webpack_require__(63);
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 and renamed properties
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,_expressions_utils__rspack_import_0/* .forEach */.j)(filterValue.expression, (expression)=>{
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
- if ((0,_assertions__rspack_import_1/* .isPropertyExpression */.e3)(expression) && expression.property.hasRenamedSegments) {
213
- // Cut over to memory execution: the plugin stores data under the
214
- // `from` (storage) names, but filter selectors reference the
215
- // in-memory names. Memory execution runs after deserialization,
216
- // where the in-memory names exist
217
- this.cutOverToMemory("renamed-property");
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: sort selectors reference in-memory names, which
227
- // only exist after deserialization when the property is renamed or unmapped
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: the plugin stores the vector under
237
- // the `from` name, and an unmapped property is not stored at all. Both are only
238
- // readable after deserialization, which is where memory execution runs.
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
- ...this.nextExecutionReason == null ? {} : {
267
- reason: this.nextExecutionReason
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.add(option.name, option.value);
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
- if (sortedItem.option.target === "database") {
371
- databaseQueryOptionsCollection.add(sortedItem.option.name, sortedItem.option.value);
372
- continue;
373
- }
374
- memoryQueryOptionsCollection.add(sortedItem.option.name, sortedItem.option.value);
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
- if (this.enumeratedItems.length != this.nextIndex) {
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
- return 'silent';
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];