@tanstack/db 0.4.20 → 0.5.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 (127) hide show
  1. package/dist/cjs/collection/change-events.cjs +10 -12
  2. package/dist/cjs/collection/change-events.cjs.map +1 -1
  3. package/dist/cjs/collection/change-events.d.cts +1 -8
  4. package/dist/cjs/collection/index.cjs +18 -0
  5. package/dist/cjs/collection/index.cjs.map +1 -1
  6. package/dist/cjs/collection/index.d.cts +7 -5
  7. package/dist/cjs/index.cjs +21 -3
  8. package/dist/cjs/index.cjs.map +1 -1
  9. package/dist/cjs/index.d.cts +2 -0
  10. package/dist/cjs/indexes/auto-index.cjs +7 -3
  11. package/dist/cjs/indexes/auto-index.cjs.map +1 -1
  12. package/dist/cjs/local-storage.cjs.map +1 -1
  13. package/dist/cjs/local-storage.d.cts +2 -2
  14. package/dist/cjs/query/builder/functions.cjs +34 -0
  15. package/dist/cjs/query/builder/functions.cjs.map +1 -1
  16. package/dist/cjs/query/builder/functions.d.cts +5 -0
  17. package/dist/cjs/query/builder/index.cjs +2 -2
  18. package/dist/cjs/query/builder/index.cjs.map +1 -1
  19. package/dist/cjs/query/builder/types.d.cts +3 -22
  20. package/dist/cjs/query/compiler/evaluators.cjs +57 -4
  21. package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
  22. package/dist/cjs/query/compiler/evaluators.d.cts +13 -0
  23. package/dist/cjs/query/compiler/expressions.cjs +4 -1
  24. package/dist/cjs/query/compiler/expressions.cjs.map +1 -1
  25. package/dist/cjs/query/compiler/group-by.cjs +3 -3
  26. package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
  27. package/dist/cjs/query/compiler/index.cjs +2 -2
  28. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  29. package/dist/cjs/query/compiler/order-by.cjs +18 -6
  30. package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
  31. package/dist/cjs/query/compiler/order-by.d.cts +7 -1
  32. package/dist/cjs/query/expression-helpers.cjs +217 -0
  33. package/dist/cjs/query/expression-helpers.cjs.map +1 -0
  34. package/dist/cjs/query/expression-helpers.d.cts +216 -0
  35. package/dist/cjs/query/index.d.cts +2 -0
  36. package/dist/cjs/query/live/collection-config-builder.cjs +13 -0
  37. package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
  38. package/dist/cjs/query/live/collection-config-builder.d.cts +1 -0
  39. package/dist/cjs/query/live/types.d.cts +6 -1
  40. package/dist/cjs/query/predicate-utils.cjs +816 -0
  41. package/dist/cjs/query/predicate-utils.cjs.map +1 -0
  42. package/dist/cjs/query/predicate-utils.d.cts +116 -0
  43. package/dist/cjs/query/subset-dedupe.cjs +111 -0
  44. package/dist/cjs/query/subset-dedupe.cjs.map +1 -0
  45. package/dist/cjs/query/subset-dedupe.d.cts +66 -0
  46. package/dist/cjs/types.d.cts +29 -0
  47. package/dist/cjs/utils/comparison.cjs +30 -0
  48. package/dist/cjs/utils/comparison.cjs.map +1 -1
  49. package/dist/cjs/utils/comparison.d.cts +7 -1
  50. package/dist/cjs/utils/index-optimization.cjs +26 -22
  51. package/dist/cjs/utils/index-optimization.cjs.map +1 -1
  52. package/dist/cjs/utils/index-optimization.d.cts +5 -4
  53. package/dist/esm/collection/change-events.d.ts +1 -8
  54. package/dist/esm/collection/change-events.js +7 -9
  55. package/dist/esm/collection/change-events.js.map +1 -1
  56. package/dist/esm/collection/index.d.ts +7 -5
  57. package/dist/esm/collection/index.js +18 -0
  58. package/dist/esm/collection/index.js.map +1 -1
  59. package/dist/esm/index.d.ts +2 -0
  60. package/dist/esm/index.js +19 -1
  61. package/dist/esm/index.js.map +1 -1
  62. package/dist/esm/indexes/auto-index.js +7 -3
  63. package/dist/esm/indexes/auto-index.js.map +1 -1
  64. package/dist/esm/local-storage.d.ts +2 -2
  65. package/dist/esm/local-storage.js.map +1 -1
  66. package/dist/esm/query/builder/functions.d.ts +5 -0
  67. package/dist/esm/query/builder/functions.js +34 -0
  68. package/dist/esm/query/builder/functions.js.map +1 -1
  69. package/dist/esm/query/builder/index.js +2 -2
  70. package/dist/esm/query/builder/index.js.map +1 -1
  71. package/dist/esm/query/builder/types.d.ts +3 -22
  72. package/dist/esm/query/compiler/evaluators.d.ts +13 -0
  73. package/dist/esm/query/compiler/evaluators.js +59 -6
  74. package/dist/esm/query/compiler/evaluators.js.map +1 -1
  75. package/dist/esm/query/compiler/expressions.js +4 -1
  76. package/dist/esm/query/compiler/expressions.js.map +1 -1
  77. package/dist/esm/query/compiler/group-by.js +4 -4
  78. package/dist/esm/query/compiler/group-by.js.map +1 -1
  79. package/dist/esm/query/compiler/index.js +3 -3
  80. package/dist/esm/query/compiler/index.js.map +1 -1
  81. package/dist/esm/query/compiler/order-by.d.ts +7 -1
  82. package/dist/esm/query/compiler/order-by.js +18 -6
  83. package/dist/esm/query/compiler/order-by.js.map +1 -1
  84. package/dist/esm/query/expression-helpers.d.ts +216 -0
  85. package/dist/esm/query/expression-helpers.js +217 -0
  86. package/dist/esm/query/expression-helpers.js.map +1 -0
  87. package/dist/esm/query/index.d.ts +2 -0
  88. package/dist/esm/query/live/collection-config-builder.d.ts +1 -0
  89. package/dist/esm/query/live/collection-config-builder.js +13 -0
  90. package/dist/esm/query/live/collection-config-builder.js.map +1 -1
  91. package/dist/esm/query/live/types.d.ts +6 -1
  92. package/dist/esm/query/predicate-utils.d.ts +116 -0
  93. package/dist/esm/query/predicate-utils.js +816 -0
  94. package/dist/esm/query/predicate-utils.js.map +1 -0
  95. package/dist/esm/query/subset-dedupe.d.ts +66 -0
  96. package/dist/esm/query/subset-dedupe.js +111 -0
  97. package/dist/esm/query/subset-dedupe.js.map +1 -0
  98. package/dist/esm/types.d.ts +29 -0
  99. package/dist/esm/utils/comparison.d.ts +7 -1
  100. package/dist/esm/utils/comparison.js +30 -0
  101. package/dist/esm/utils/comparison.js.map +1 -1
  102. package/dist/esm/utils/index-optimization.d.ts +5 -4
  103. package/dist/esm/utils/index-optimization.js +26 -22
  104. package/dist/esm/utils/index-optimization.js.map +1 -1
  105. package/package.json +2 -2
  106. package/src/collection/change-events.ts +14 -24
  107. package/src/collection/index.ts +32 -4
  108. package/src/index.ts +4 -0
  109. package/src/indexes/auto-index.ts +8 -4
  110. package/src/local-storage.ts +11 -3
  111. package/src/query/builder/functions.ts +39 -0
  112. package/src/query/builder/index.ts +2 -2
  113. package/src/query/builder/types.ts +3 -25
  114. package/src/query/compiler/evaluators.ts +103 -5
  115. package/src/query/compiler/expressions.ts +3 -0
  116. package/src/query/compiler/group-by.ts +4 -4
  117. package/src/query/compiler/index.ts +3 -3
  118. package/src/query/compiler/order-by.ts +33 -7
  119. package/src/query/expression-helpers.ts +522 -0
  120. package/src/query/index.ts +12 -0
  121. package/src/query/live/collection-config-builder.ts +27 -0
  122. package/src/query/live/types.ts +11 -1
  123. package/src/query/predicate-utils.ts +1415 -0
  124. package/src/query/subset-dedupe.ts +243 -0
  125. package/src/types.ts +39 -0
  126. package/src/utils/comparison.ts +70 -1
  127. package/src/utils/index-optimization.ts +77 -63
@@ -1,9 +1,13 @@
1
1
  import { DEFAULT_COMPARE_OPTIONS } from "../utils.js";
2
2
  import { ReverseIndex } from "../indexes/reverse-index.js";
3
- function findIndexForField(indexes, fieldPath, compareOptions = DEFAULT_COMPARE_OPTIONS) {
4
- for (const index of indexes.values()) {
5
- if (index.matchesField(fieldPath) && index.matchesCompareOptions(compareOptions)) {
6
- if (!index.matchesDirection(compareOptions.direction)) {
3
+ function findIndexForField(collection, fieldPath, compareOptions) {
4
+ const compareOpts = compareOptions ?? {
5
+ ...DEFAULT_COMPARE_OPTIONS,
6
+ ...collection.compareOptions
7
+ };
8
+ for (const index of collection.indexes.values()) {
9
+ if (index.matchesField(fieldPath) && index.matchesCompareOptions(compareOpts)) {
10
+ if (!index.matchesDirection(compareOpts.direction)) {
7
11
  return new ReverseIndex(index);
8
12
  }
9
13
  return index;
@@ -35,10 +39,10 @@ function unionSets(sets) {
35
39
  }
36
40
  return result;
37
41
  }
38
- function optimizeExpressionWithIndexes(expression, indexes) {
39
- return optimizeQueryRecursive(expression, indexes);
42
+ function optimizeExpressionWithIndexes(expression, collection) {
43
+ return optimizeQueryRecursive(expression, collection);
40
44
  }
41
- function optimizeQueryRecursive(expression, indexes) {
45
+ function optimizeQueryRecursive(expression, collection) {
42
46
  if (expression.type === `func`) {
43
47
  switch (expression.name) {
44
48
  case `eq`:
@@ -46,18 +50,18 @@ function optimizeQueryRecursive(expression, indexes) {
46
50
  case `gte`:
47
51
  case `lt`:
48
52
  case `lte`:
49
- return optimizeSimpleComparison(expression, indexes);
53
+ return optimizeSimpleComparison(expression, collection);
50
54
  case `and`:
51
- return optimizeAndExpression(expression, indexes);
55
+ return optimizeAndExpression(expression, collection);
52
56
  case `or`:
53
- return optimizeOrExpression(expression, indexes);
57
+ return optimizeOrExpression(expression, collection);
54
58
  case `in`:
55
- return optimizeInArrayExpression(expression, indexes);
59
+ return optimizeInArrayExpression(expression, collection);
56
60
  }
57
61
  }
58
62
  return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set() };
59
63
  }
60
- function optimizeCompoundRangeQuery(expression, indexes) {
64
+ function optimizeCompoundRangeQuery(expression, collection) {
61
65
  if (expression.type !== `func` || expression.args.length < 2) {
62
66
  return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set() };
63
67
  }
@@ -107,7 +111,7 @@ function optimizeCompoundRangeQuery(expression, indexes) {
107
111
  for (const [fieldKey, operations] of fieldOperations) {
108
112
  if (operations.length >= 2) {
109
113
  const fieldPath = fieldKey.split(`.`);
110
- const index = findIndexForField(indexes, fieldPath);
114
+ const index = findIndexForField(collection, fieldPath);
111
115
  if (index && index.supports(`gt`) && index.supports(`lt`)) {
112
116
  let from = void 0;
113
117
  let to = void 0;
@@ -153,7 +157,7 @@ function optimizeCompoundRangeQuery(expression, indexes) {
153
157
  }
154
158
  return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set() };
155
159
  }
156
- function optimizeSimpleComparison(expression, indexes) {
160
+ function optimizeSimpleComparison(expression, collection) {
157
161
  if (expression.type !== `func` || expression.args.length !== 2) {
158
162
  return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set() };
159
163
  }
@@ -185,7 +189,7 @@ function optimizeSimpleComparison(expression, indexes) {
185
189
  }
186
190
  if (fieldArg && valueArg) {
187
191
  const fieldPath = fieldArg.path;
188
- const index = findIndexForField(indexes, fieldPath);
192
+ const index = findIndexForField(collection, fieldPath);
189
193
  if (index) {
190
194
  const queryValue = valueArg.value;
191
195
  const indexOperation = operation;
@@ -198,17 +202,17 @@ function optimizeSimpleComparison(expression, indexes) {
198
202
  }
199
203
  return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set() };
200
204
  }
201
- function optimizeAndExpression(expression, indexes) {
205
+ function optimizeAndExpression(expression, collection) {
202
206
  if (expression.type !== `func` || expression.args.length < 2) {
203
207
  return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set() };
204
208
  }
205
- const compoundRangeResult = optimizeCompoundRangeQuery(expression, indexes);
209
+ const compoundRangeResult = optimizeCompoundRangeQuery(expression, collection);
206
210
  if (compoundRangeResult.canOptimize) {
207
211
  return compoundRangeResult;
208
212
  }
209
213
  const results = [];
210
214
  for (const arg of expression.args) {
211
- const result = optimizeQueryRecursive(arg, indexes);
215
+ const result = optimizeQueryRecursive(arg, collection);
212
216
  if (result.canOptimize) {
213
217
  results.push(result);
214
218
  }
@@ -220,13 +224,13 @@ function optimizeAndExpression(expression, indexes) {
220
224
  }
221
225
  return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set() };
222
226
  }
223
- function optimizeOrExpression(expression, indexes) {
227
+ function optimizeOrExpression(expression, collection) {
224
228
  if (expression.type !== `func` || expression.args.length < 2) {
225
229
  return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set() };
226
230
  }
227
231
  const results = [];
228
232
  for (const arg of expression.args) {
229
- const result = optimizeQueryRecursive(arg, indexes);
233
+ const result = optimizeQueryRecursive(arg, collection);
230
234
  if (result.canOptimize) {
231
235
  results.push(result);
232
236
  }
@@ -238,7 +242,7 @@ function optimizeOrExpression(expression, indexes) {
238
242
  }
239
243
  return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set() };
240
244
  }
241
- function optimizeInArrayExpression(expression, indexes) {
245
+ function optimizeInArrayExpression(expression, collection) {
242
246
  if (expression.type !== `func` || expression.args.length !== 2) {
243
247
  return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set() };
244
248
  }
@@ -247,7 +251,7 @@ function optimizeInArrayExpression(expression, indexes) {
247
251
  if (fieldArg.type === `ref` && arrayArg.type === `val` && Array.isArray(arrayArg.value)) {
248
252
  const fieldPath = fieldArg.path;
249
253
  const values = arrayArg.value;
250
- const index = findIndexForField(indexes, fieldPath);
254
+ const index = findIndexForField(collection, fieldPath);
251
255
  if (index) {
252
256
  if (index.supports(`in`)) {
253
257
  const matchingKeys = index.lookup(`in`, values);
@@ -1 +1 @@
1
- {"version":3,"file":"index-optimization.js","sources":["../../../src/utils/index-optimization.ts"],"sourcesContent":["/**\n * # Index-Based Query Optimization\n *\n * This module provides utilities for optimizing query expressions by leveraging\n * available indexes to quickly find matching keys instead of scanning all data.\n *\n * This is different from the query structure optimizer in `query/optimizer.ts`\n * which rewrites query IR structure. This module focuses on using indexes during\n * query execution to speed up data filtering.\n *\n * ## Key Features:\n * - Uses indexes to find matching keys for WHERE conditions\n * - Supports AND/OR logic with set operations\n * - Handles range queries (eq, gt, gte, lt, lte)\n * - Optimizes IN array expressions\n */\n\nimport { DEFAULT_COMPARE_OPTIONS } from \"../utils.js\"\nimport { ReverseIndex } from \"../indexes/reverse-index.js\"\nimport type { CompareOptions } from \"../query/builder/types.js\"\nimport type {\n BaseIndex,\n IndexInterface,\n IndexOperation,\n} from \"../indexes/base-index.js\"\nimport type { BasicExpression } from \"../query/ir.js\"\n\n/**\n * Result of index-based query optimization\n */\nexport interface OptimizationResult<TKey> {\n canOptimize: boolean\n matchingKeys: Set<TKey>\n}\n\n/**\n * Finds an index that matches a given field path\n */\nexport function findIndexForField<TKey extends string | number>(\n indexes: Map<number, IndexInterface<TKey>>,\n fieldPath: Array<string>,\n compareOptions: CompareOptions = DEFAULT_COMPARE_OPTIONS\n): IndexInterface<TKey> | undefined {\n for (const index of indexes.values()) {\n if (\n index.matchesField(fieldPath) &&\n index.matchesCompareOptions(compareOptions)\n ) {\n if (!index.matchesDirection(compareOptions.direction)) {\n return new ReverseIndex(index)\n }\n return index\n }\n }\n return undefined\n}\n\n/**\n * Intersects multiple sets (AND logic)\n */\nexport function intersectSets<T>(sets: Array<Set<T>>): Set<T> {\n if (sets.length === 0) return new Set()\n if (sets.length === 1) return new Set(sets[0])\n\n let result = new Set(sets[0])\n for (let i = 1; i < sets.length; i++) {\n const newResult = new Set<T>()\n for (const item of result) {\n if (sets[i]!.has(item)) {\n newResult.add(item)\n }\n }\n result = newResult\n }\n return result\n}\n\n/**\n * Unions multiple sets (OR logic)\n */\nexport function unionSets<T>(sets: Array<Set<T>>): Set<T> {\n const result = new Set<T>()\n for (const set of sets) {\n for (const item of set) {\n result.add(item)\n }\n }\n return result\n}\n\n/**\n * Optimizes a query expression using available indexes to find matching keys\n */\nexport function optimizeExpressionWithIndexes<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): OptimizationResult<TKey> {\n return optimizeQueryRecursive(expression, indexes)\n}\n\n/**\n * Recursively optimizes query expressions\n */\nfunction optimizeQueryRecursive<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): OptimizationResult<TKey> {\n if (expression.type === `func`) {\n switch (expression.name) {\n case `eq`:\n case `gt`:\n case `gte`:\n case `lt`:\n case `lte`:\n return optimizeSimpleComparison(expression, indexes)\n\n case `and`:\n return optimizeAndExpression(expression, indexes)\n\n case `or`:\n return optimizeOrExpression(expression, indexes)\n\n case `in`:\n return optimizeInArrayExpression(expression, indexes)\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an expression can be optimized\n */\nexport function canOptimizeExpression<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): boolean {\n if (expression.type === `func`) {\n switch (expression.name) {\n case `eq`:\n case `gt`:\n case `gte`:\n case `lt`:\n case `lte`:\n return canOptimizeSimpleComparison(expression, indexes)\n\n case `and`:\n return canOptimizeAndExpression(expression, indexes)\n\n case `or`:\n return canOptimizeOrExpression(expression, indexes)\n\n case `in`:\n return canOptimizeInArrayExpression(expression, indexes)\n }\n }\n\n return false\n}\n\n/**\n * Optimizes compound range queries on the same field\n * Example: WHERE age > 5 AND age < 10\n */\nfunction optimizeCompoundRangeQuery<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length < 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n // Group range operations by field\n const fieldOperations = new Map<\n string,\n Array<{\n operation: `gt` | `gte` | `lt` | `lte`\n value: any\n }>\n >()\n\n // Collect all range operations from AND arguments\n for (const arg of expression.args) {\n if (arg.type === `func` && [`gt`, `gte`, `lt`, `lte`].includes(arg.name)) {\n const rangeOp = arg as any\n if (rangeOp.args.length === 2) {\n const leftArg = rangeOp.args[0]!\n const rightArg = rangeOp.args[1]!\n\n // Check both directions: field op value AND value op field\n let fieldArg: BasicExpression | null = null\n let valueArg: BasicExpression | null = null\n let operation = rangeOp.name as `gt` | `gte` | `lt` | `lte`\n\n if (leftArg.type === `ref` && rightArg.type === `val`) {\n // field op value\n fieldArg = leftArg\n valueArg = rightArg\n } else if (leftArg.type === `val` && rightArg.type === `ref`) {\n // value op field - need to flip the operation\n fieldArg = rightArg\n valueArg = leftArg\n\n // Flip the operation for reverse comparison\n switch (operation) {\n case `gt`:\n operation = `lt`\n break\n case `gte`:\n operation = `lte`\n break\n case `lt`:\n operation = `gt`\n break\n case `lte`:\n operation = `gte`\n break\n }\n }\n\n if (fieldArg && valueArg) {\n const fieldPath = (fieldArg as any).path\n const fieldKey = fieldPath.join(`.`)\n const value = (valueArg as any).value\n\n if (!fieldOperations.has(fieldKey)) {\n fieldOperations.set(fieldKey, [])\n }\n fieldOperations.get(fieldKey)!.push({ operation, value })\n }\n }\n }\n }\n\n // Check if we have multiple operations on the same field\n for (const [fieldKey, operations] of fieldOperations) {\n if (operations.length >= 2) {\n const fieldPath = fieldKey.split(`.`)\n const index = findIndexForField(indexes, fieldPath)\n\n if (index && index.supports(`gt`) && index.supports(`lt`)) {\n // Build range query options\n let from: any = undefined\n let to: any = undefined\n let fromInclusive = true\n let toInclusive = true\n\n for (const { operation, value } of operations) {\n switch (operation) {\n case `gt`:\n if (from === undefined || value > from) {\n from = value\n fromInclusive = false\n }\n break\n case `gte`:\n if (from === undefined || value > from) {\n from = value\n fromInclusive = true\n }\n break\n case `lt`:\n if (to === undefined || value < to) {\n to = value\n toInclusive = false\n }\n break\n case `lte`:\n if (to === undefined || value < to) {\n to = value\n toInclusive = true\n }\n break\n }\n }\n\n const matchingKeys = (index as any).rangeQuery({\n from,\n to,\n fromInclusive,\n toInclusive,\n })\n\n return { canOptimize: true, matchingKeys }\n }\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Optimizes simple comparison expressions (eq, gt, gte, lt, lte)\n */\nfunction optimizeSimpleComparison<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const leftArg = expression.args[0]!\n const rightArg = expression.args[1]!\n\n // Check both directions: field op value AND value op field\n let fieldArg: BasicExpression | null = null\n let valueArg: BasicExpression | null = null\n let operation = expression.name as `eq` | `gt` | `gte` | `lt` | `lte`\n\n if (leftArg.type === `ref` && rightArg.type === `val`) {\n // field op value\n fieldArg = leftArg\n valueArg = rightArg\n } else if (leftArg.type === `val` && rightArg.type === `ref`) {\n // value op field - need to flip the operation\n fieldArg = rightArg\n valueArg = leftArg\n\n // Flip the operation for reverse comparison\n switch (operation) {\n case `gt`:\n operation = `lt`\n break\n case `gte`:\n operation = `lte`\n break\n case `lt`:\n operation = `gt`\n break\n case `lte`:\n operation = `gte`\n break\n // eq stays the same\n }\n }\n\n if (fieldArg && valueArg) {\n const fieldPath = (fieldArg as any).path\n const index = findIndexForField(indexes, fieldPath)\n\n if (index) {\n const queryValue = (valueArg as any).value\n\n // Map operation to IndexOperation enum\n const indexOperation = operation as IndexOperation\n\n // Check if the index supports this operation\n if (!index.supports(indexOperation)) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const matchingKeys = index.lookup(indexOperation, queryValue)\n return { canOptimize: true, matchingKeys }\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if a simple comparison can be optimized\n */\nfunction canOptimizeSimpleComparison<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): boolean {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return false\n }\n\n const leftArg = expression.args[0]!\n const rightArg = expression.args[1]!\n\n // Check both directions: field op value AND value op field\n let fieldPath: Array<string> | null = null\n\n if (leftArg.type === `ref` && rightArg.type === `val`) {\n fieldPath = (leftArg as any).path\n } else if (leftArg.type === `val` && rightArg.type === `ref`) {\n fieldPath = (rightArg as any).path\n }\n\n if (fieldPath) {\n const index = findIndexForField(indexes, fieldPath)\n return index !== undefined\n }\n\n return false\n}\n\n/**\n * Optimizes AND expressions\n */\nfunction optimizeAndExpression<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length < 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n // First, try to optimize compound range queries on the same field\n const compoundRangeResult = optimizeCompoundRangeQuery(expression, indexes)\n if (compoundRangeResult.canOptimize) {\n return compoundRangeResult\n }\n\n const results: Array<OptimizationResult<TKey>> = []\n\n // Try to optimize each part, keep the optimizable ones\n for (const arg of expression.args) {\n const result = optimizeQueryRecursive(arg, indexes)\n if (result.canOptimize) {\n results.push(result)\n }\n }\n\n if (results.length > 0) {\n // Use intersectSets utility for AND logic\n const allMatchingSets = results.map((r) => r.matchingKeys)\n const intersectedKeys = intersectSets(allMatchingSets)\n return { canOptimize: true, matchingKeys: intersectedKeys }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an AND expression can be optimized\n */\nfunction canOptimizeAndExpression<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): boolean {\n if (expression.type !== `func` || expression.args.length < 2) {\n return false\n }\n\n // If any argument can be optimized, we can gain some speedup\n return expression.args.some((arg) => canOptimizeExpression(arg, indexes))\n}\n\n/**\n * Optimizes OR expressions\n */\nfunction optimizeOrExpression<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length < 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const results: Array<OptimizationResult<TKey>> = []\n\n // Try to optimize each part, keep the optimizable ones\n for (const arg of expression.args) {\n const result = optimizeQueryRecursive(arg, indexes)\n if (result.canOptimize) {\n results.push(result)\n }\n }\n\n if (results.length > 0) {\n // Use unionSets utility for OR logic\n const allMatchingSets = results.map((r) => r.matchingKeys)\n const unionedKeys = unionSets(allMatchingSets)\n return { canOptimize: true, matchingKeys: unionedKeys }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an OR expression can be optimized\n */\nfunction canOptimizeOrExpression<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): boolean {\n if (expression.type !== `func` || expression.args.length < 2) {\n return false\n }\n\n // If any argument can be optimized, we can gain some speedup\n return expression.args.some((arg) => canOptimizeExpression(arg, indexes))\n}\n\n/**\n * Optimizes IN array expressions\n */\nfunction optimizeInArrayExpression<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const fieldArg = expression.args[0]!\n const arrayArg = expression.args[1]!\n\n if (\n fieldArg.type === `ref` &&\n arrayArg.type === `val` &&\n Array.isArray((arrayArg as any).value)\n ) {\n const fieldPath = (fieldArg as any).path\n const values = (arrayArg as any).value\n const index = findIndexForField(indexes, fieldPath)\n\n if (index) {\n // Check if the index supports IN operation\n if (index.supports(`in`)) {\n const matchingKeys = index.lookup(`in`, values)\n return { canOptimize: true, matchingKeys }\n } else if (index.supports(`eq`)) {\n // Fallback to multiple equality lookups\n const matchingKeys = new Set<TKey>()\n for (const value of values) {\n const keysForValue = index.lookup(`eq`, value)\n for (const key of keysForValue) {\n matchingKeys.add(key)\n }\n }\n return { canOptimize: true, matchingKeys }\n }\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an IN array expression can be optimized\n */\nfunction canOptimizeInArrayExpression<TKey extends string | number>(\n expression: BasicExpression,\n indexes: Map<number, BaseIndex<TKey>>\n): boolean {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return false\n }\n\n const fieldArg = expression.args[0]!\n const arrayArg = expression.args[1]!\n\n if (\n fieldArg.type === `ref` &&\n arrayArg.type === `val` &&\n Array.isArray((arrayArg as any).value)\n ) {\n const fieldPath = (fieldArg as any).path\n const index = findIndexForField(indexes, fieldPath)\n return index !== undefined\n }\n\n return false\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index-optimization.js","sources":["../../../src/utils/index-optimization.ts"],"sourcesContent":["/**\n * # Index-Based Query Optimization\n *\n * This module provides utilities for optimizing query expressions by leveraging\n * available indexes to quickly find matching keys instead of scanning all data.\n *\n * This is different from the query structure optimizer in `query/optimizer.ts`\n * which rewrites query IR structure. This module focuses on using indexes during\n * query execution to speed up data filtering.\n *\n * ## Key Features:\n * - Uses indexes to find matching keys for WHERE conditions\n * - Supports AND/OR logic with set operations\n * - Handles range queries (eq, gt, gte, lt, lte)\n * - Optimizes IN array expressions\n */\n\nimport { DEFAULT_COMPARE_OPTIONS } from \"../utils.js\"\nimport { ReverseIndex } from \"../indexes/reverse-index.js\"\nimport type { CompareOptions } from \"../query/builder/types.js\"\nimport type { IndexInterface, IndexOperation } from \"../indexes/base-index.js\"\nimport type { BasicExpression } from \"../query/ir.js\"\nimport type { CollectionLike } from \"../types.js\"\n\n/**\n * Result of index-based query optimization\n */\nexport interface OptimizationResult<TKey> {\n canOptimize: boolean\n matchingKeys: Set<TKey>\n}\n\n/**\n * Finds an index that matches a given field path\n */\nexport function findIndexForField<TKey extends string | number>(\n collection: CollectionLike<any, TKey>,\n fieldPath: Array<string>,\n compareOptions?: CompareOptions\n): IndexInterface<TKey> | undefined {\n const compareOpts = compareOptions ?? {\n ...DEFAULT_COMPARE_OPTIONS,\n ...collection.compareOptions,\n }\n\n for (const index of collection.indexes.values()) {\n if (\n index.matchesField(fieldPath) &&\n index.matchesCompareOptions(compareOpts)\n ) {\n if (!index.matchesDirection(compareOpts.direction)) {\n return new ReverseIndex(index)\n }\n return index\n }\n }\n return undefined\n}\n\n/**\n * Intersects multiple sets (AND logic)\n */\nexport function intersectSets<T>(sets: Array<Set<T>>): Set<T> {\n if (sets.length === 0) return new Set()\n if (sets.length === 1) return new Set(sets[0])\n\n let result = new Set(sets[0])\n for (let i = 1; i < sets.length; i++) {\n const newResult = new Set<T>()\n for (const item of result) {\n if (sets[i]!.has(item)) {\n newResult.add(item)\n }\n }\n result = newResult\n }\n return result\n}\n\n/**\n * Unions multiple sets (OR logic)\n */\nexport function unionSets<T>(sets: Array<Set<T>>): Set<T> {\n const result = new Set<T>()\n for (const set of sets) {\n for (const item of set) {\n result.add(item)\n }\n }\n return result\n}\n\n/**\n * Optimizes a query expression using available indexes to find matching keys\n */\nexport function optimizeExpressionWithIndexes<\n T extends object,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>\n): OptimizationResult<TKey> {\n return optimizeQueryRecursive(expression, collection)\n}\n\n/**\n * Recursively optimizes query expressions\n */\nfunction optimizeQueryRecursive<T extends object, TKey extends string | number>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>\n): OptimizationResult<TKey> {\n if (expression.type === `func`) {\n switch (expression.name) {\n case `eq`:\n case `gt`:\n case `gte`:\n case `lt`:\n case `lte`:\n return optimizeSimpleComparison(expression, collection)\n\n case `and`:\n return optimizeAndExpression(expression, collection)\n\n case `or`:\n return optimizeOrExpression(expression, collection)\n\n case `in`:\n return optimizeInArrayExpression(expression, collection)\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an expression can be optimized\n */\nexport function canOptimizeExpression<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type === `func`) {\n switch (expression.name) {\n case `eq`:\n case `gt`:\n case `gte`:\n case `lt`:\n case `lte`:\n return canOptimizeSimpleComparison(expression, collection)\n\n case `and`:\n return canOptimizeAndExpression(expression, collection)\n\n case `or`:\n return canOptimizeOrExpression(expression, collection)\n\n case `in`:\n return canOptimizeInArrayExpression(expression, collection)\n }\n }\n\n return false\n}\n\n/**\n * Optimizes compound range queries on the same field\n * Example: WHERE age > 5 AND age < 10\n */\nfunction optimizeCompoundRangeQuery<\n T extends object,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length < 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n // Group range operations by field\n const fieldOperations = new Map<\n string,\n Array<{\n operation: `gt` | `gte` | `lt` | `lte`\n value: any\n }>\n >()\n\n // Collect all range operations from AND arguments\n for (const arg of expression.args) {\n if (arg.type === `func` && [`gt`, `gte`, `lt`, `lte`].includes(arg.name)) {\n const rangeOp = arg as any\n if (rangeOp.args.length === 2) {\n const leftArg = rangeOp.args[0]!\n const rightArg = rangeOp.args[1]!\n\n // Check both directions: field op value AND value op field\n let fieldArg: BasicExpression | null = null\n let valueArg: BasicExpression | null = null\n let operation = rangeOp.name as `gt` | `gte` | `lt` | `lte`\n\n if (leftArg.type === `ref` && rightArg.type === `val`) {\n // field op value\n fieldArg = leftArg\n valueArg = rightArg\n } else if (leftArg.type === `val` && rightArg.type === `ref`) {\n // value op field - need to flip the operation\n fieldArg = rightArg\n valueArg = leftArg\n\n // Flip the operation for reverse comparison\n switch (operation) {\n case `gt`:\n operation = `lt`\n break\n case `gte`:\n operation = `lte`\n break\n case `lt`:\n operation = `gt`\n break\n case `lte`:\n operation = `gte`\n break\n }\n }\n\n if (fieldArg && valueArg) {\n const fieldPath = (fieldArg as any).path\n const fieldKey = fieldPath.join(`.`)\n const value = (valueArg as any).value\n\n if (!fieldOperations.has(fieldKey)) {\n fieldOperations.set(fieldKey, [])\n }\n fieldOperations.get(fieldKey)!.push({ operation, value })\n }\n }\n }\n }\n\n // Check if we have multiple operations on the same field\n for (const [fieldKey, operations] of fieldOperations) {\n if (operations.length >= 2) {\n const fieldPath = fieldKey.split(`.`)\n const index = findIndexForField(collection, fieldPath)\n\n if (index && index.supports(`gt`) && index.supports(`lt`)) {\n // Build range query options\n let from: any = undefined\n let to: any = undefined\n let fromInclusive = true\n let toInclusive = true\n\n for (const { operation, value } of operations) {\n switch (operation) {\n case `gt`:\n if (from === undefined || value > from) {\n from = value\n fromInclusive = false\n }\n break\n case `gte`:\n if (from === undefined || value > from) {\n from = value\n fromInclusive = true\n }\n break\n case `lt`:\n if (to === undefined || value < to) {\n to = value\n toInclusive = false\n }\n break\n case `lte`:\n if (to === undefined || value < to) {\n to = value\n toInclusive = true\n }\n break\n }\n }\n\n const matchingKeys = (index as any).rangeQuery({\n from,\n to,\n fromInclusive,\n toInclusive,\n })\n\n return { canOptimize: true, matchingKeys }\n }\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Optimizes simple comparison expressions (eq, gt, gte, lt, lte)\n */\nfunction optimizeSimpleComparison<\n T extends object,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const leftArg = expression.args[0]!\n const rightArg = expression.args[1]!\n\n // Check both directions: field op value AND value op field\n let fieldArg: BasicExpression | null = null\n let valueArg: BasicExpression | null = null\n let operation = expression.name as `eq` | `gt` | `gte` | `lt` | `lte`\n\n if (leftArg.type === `ref` && rightArg.type === `val`) {\n // field op value\n fieldArg = leftArg\n valueArg = rightArg\n } else if (leftArg.type === `val` && rightArg.type === `ref`) {\n // value op field - need to flip the operation\n fieldArg = rightArg\n valueArg = leftArg\n\n // Flip the operation for reverse comparison\n switch (operation) {\n case `gt`:\n operation = `lt`\n break\n case `gte`:\n operation = `lte`\n break\n case `lt`:\n operation = `gt`\n break\n case `lte`:\n operation = `gte`\n break\n // eq stays the same\n }\n }\n\n if (fieldArg && valueArg) {\n const fieldPath = (fieldArg as any).path\n const index = findIndexForField(collection, fieldPath)\n\n if (index) {\n const queryValue = (valueArg as any).value\n\n // Map operation to IndexOperation enum\n const indexOperation = operation as IndexOperation\n\n // Check if the index supports this operation\n if (!index.supports(indexOperation)) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const matchingKeys = index.lookup(indexOperation, queryValue)\n return { canOptimize: true, matchingKeys }\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if a simple comparison can be optimized\n */\nfunction canOptimizeSimpleComparison<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return false\n }\n\n const leftArg = expression.args[0]!\n const rightArg = expression.args[1]!\n\n // Check both directions: field op value AND value op field\n let fieldPath: Array<string> | null = null\n\n if (leftArg.type === `ref` && rightArg.type === `val`) {\n fieldPath = (leftArg as any).path\n } else if (leftArg.type === `val` && rightArg.type === `ref`) {\n fieldPath = (rightArg as any).path\n }\n\n if (fieldPath) {\n const index = findIndexForField(collection, fieldPath)\n return index !== undefined\n }\n\n return false\n}\n\n/**\n * Optimizes AND expressions\n */\nfunction optimizeAndExpression<T extends object, TKey extends string | number>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length < 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n // First, try to optimize compound range queries on the same field\n const compoundRangeResult = optimizeCompoundRangeQuery(expression, collection)\n if (compoundRangeResult.canOptimize) {\n return compoundRangeResult\n }\n\n const results: Array<OptimizationResult<TKey>> = []\n\n // Try to optimize each part, keep the optimizable ones\n for (const arg of expression.args) {\n const result = optimizeQueryRecursive(arg, collection)\n if (result.canOptimize) {\n results.push(result)\n }\n }\n\n if (results.length > 0) {\n // Use intersectSets utility for AND logic\n const allMatchingSets = results.map((r) => r.matchingKeys)\n const intersectedKeys = intersectSets(allMatchingSets)\n return { canOptimize: true, matchingKeys: intersectedKeys }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an AND expression can be optimized\n */\nfunction canOptimizeAndExpression<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type !== `func` || expression.args.length < 2) {\n return false\n }\n\n // If any argument can be optimized, we can gain some speedup\n return expression.args.some((arg) => canOptimizeExpression(arg, collection))\n}\n\n/**\n * Optimizes OR expressions\n */\nfunction optimizeOrExpression<T extends object, TKey extends string | number>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length < 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const results: Array<OptimizationResult<TKey>> = []\n\n // Try to optimize each part, keep the optimizable ones\n for (const arg of expression.args) {\n const result = optimizeQueryRecursive(arg, collection)\n if (result.canOptimize) {\n results.push(result)\n }\n }\n\n if (results.length > 0) {\n // Use unionSets utility for OR logic\n const allMatchingSets = results.map((r) => r.matchingKeys)\n const unionedKeys = unionSets(allMatchingSets)\n return { canOptimize: true, matchingKeys: unionedKeys }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an OR expression can be optimized\n */\nfunction canOptimizeOrExpression<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type !== `func` || expression.args.length < 2) {\n return false\n }\n\n // If any argument can be optimized, we can gain some speedup\n return expression.args.some((arg) => canOptimizeExpression(arg, collection))\n}\n\n/**\n * Optimizes IN array expressions\n */\nfunction optimizeInArrayExpression<\n T extends object,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const fieldArg = expression.args[0]!\n const arrayArg = expression.args[1]!\n\n if (\n fieldArg.type === `ref` &&\n arrayArg.type === `val` &&\n Array.isArray((arrayArg as any).value)\n ) {\n const fieldPath = (fieldArg as any).path\n const values = (arrayArg as any).value\n const index = findIndexForField(collection, fieldPath)\n\n if (index) {\n // Check if the index supports IN operation\n if (index.supports(`in`)) {\n const matchingKeys = index.lookup(`in`, values)\n return { canOptimize: true, matchingKeys }\n } else if (index.supports(`eq`)) {\n // Fallback to multiple equality lookups\n const matchingKeys = new Set<TKey>()\n for (const value of values) {\n const keysForValue = index.lookup(`eq`, value)\n for (const key of keysForValue) {\n matchingKeys.add(key)\n }\n }\n return { canOptimize: true, matchingKeys }\n }\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an IN array expression can be optimized\n */\nfunction canOptimizeInArrayExpression<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return false\n }\n\n const fieldArg = expression.args[0]!\n const arrayArg = expression.args[1]!\n\n if (\n fieldArg.type === `ref` &&\n arrayArg.type === `val` &&\n Array.isArray((arrayArg as any).value)\n ) {\n const fieldPath = (fieldArg as any).path\n const index = findIndexForField(collection, fieldPath)\n return index !== undefined\n }\n\n return false\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@tanstack/db",
3
3
  "description": "A reactive client store for building super fast apps on sync",
4
- "version": "0.4.20",
4
+ "version": "0.5.0",
5
5
  "dependencies": {
6
6
  "@standard-schema/spec": "^1.0.0",
7
7
  "@tanstack/pacer": "^0.1.0",
8
- "@tanstack/db-ivm": "0.1.12"
8
+ "@tanstack/db-ivm": "0.1.13"
9
9
  },
10
10
  "devDependencies": {
11
11
  "@vitest/coverage-istanbul": "^3.2.4",
@@ -2,34 +2,27 @@ import {
2
2
  createSingleRowRefProxy,
3
3
  toExpression,
4
4
  } from "../query/builder/ref-proxy"
5
- import { compileSingleRowExpression } from "../query/compiler/evaluators.js"
5
+ import {
6
+ compileSingleRowExpression,
7
+ toBooleanPredicate,
8
+ } from "../query/compiler/evaluators.js"
6
9
  import {
7
10
  findIndexForField,
8
11
  optimizeExpressionWithIndexes,
9
12
  } from "../utils/index-optimization.js"
10
13
  import { ensureIndexForField } from "../indexes/auto-index.js"
11
14
  import { makeComparator } from "../utils/comparison.js"
15
+ import { buildCompareOptions } from "../query/compiler/order-by"
12
16
  import type {
13
17
  ChangeMessage,
18
+ CollectionLike,
14
19
  CurrentStateAsChangesOptions,
15
20
  SubscribeChangesOptions,
16
21
  } from "../types"
17
- import type { Collection, CollectionImpl } from "./index.js"
22
+ import type { CollectionImpl } from "./index.js"
18
23
  import type { SingleRowRefProxy } from "../query/builder/ref-proxy"
19
24
  import type { BasicExpression, OrderBy } from "../query/ir.js"
20
25
 
21
- /**
22
- * Interface for a collection-like object that provides the necessary methods
23
- * for the change events system to work
24
- */
25
- export interface CollectionLike<
26
- T extends object = Record<string, unknown>,
27
- TKey extends string | number = string | number,
28
- > extends Pick<
29
- Collection<T, TKey>,
30
- `get` | `has` | `entries` | `indexes` | `id`
31
- > {}
32
-
33
26
  /**
34
27
  * Returns the current state of the collection as an array of changes
35
28
  * @param collection - The collection to get changes from
@@ -140,7 +133,7 @@ export function currentStateAsChanges<
140
133
  // Try to optimize the query using indexes
141
134
  const optimizationResult = optimizeExpressionWithIndexes(
142
135
  expression,
143
- collection.indexes
136
+ collection
144
137
  )
145
138
 
146
139
  if (optimizationResult.canOptimize) {
@@ -199,7 +192,7 @@ export function createFilterFunction<T extends object>(
199
192
  const evaluator = compileSingleRowExpression(expression)
200
193
  const result = evaluator(item as Record<string, unknown>)
201
194
  // WHERE clauses should always evaluate to boolean predicates (Kevin's feedback)
202
- return result
195
+ return toBooleanPredicate(result)
203
196
  } catch {
204
197
  // If RefProxy approach fails (e.g., arithmetic operations), fall back to direct evaluation
205
198
  try {
@@ -211,7 +204,7 @@ export function createFilterFunction<T extends object>(
211
204
  }) as SingleRowRefProxy<T>
212
205
 
213
206
  const result = whereCallback(simpleProxy)
214
- return result
207
+ return toBooleanPredicate(result)
215
208
  } catch {
216
209
  // If both approaches fail, exclude the item
217
210
  return false
@@ -232,7 +225,7 @@ export function createFilterFunctionFromExpression<T extends object>(
232
225
  try {
233
226
  const evaluator = compileSingleRowExpression(expression)
234
227
  const result = evaluator(item as Record<string, unknown>)
235
- return Boolean(result)
228
+ return toBooleanPredicate(result)
236
229
  } catch {
237
230
  // If evaluation fails, exclude the item
238
231
  return false
@@ -326,21 +319,18 @@ function getOrderedKeys<T extends object, TKey extends string | number>(
326
319
  if (orderByExpression.type === `ref`) {
327
320
  const propRef = orderByExpression
328
321
  const fieldPath = propRef.path
322
+ const compareOpts = buildCompareOptions(clause, collection)
329
323
 
330
324
  // Ensure index exists for this field
331
325
  ensureIndexForField(
332
326
  fieldPath[0]!,
333
327
  fieldPath,
334
328
  collection as CollectionImpl<T, TKey>,
335
- clause.compareOptions
329
+ compareOpts
336
330
  )
337
331
 
338
332
  // Find the index
339
- const index = findIndexForField(
340
- collection.indexes,
341
- fieldPath,
342
- clause.compareOptions
343
- )
333
+ const index = findIndexForField(collection, fieldPath, compareOpts)
344
334
 
345
335
  if (index && index.supports(`gt`)) {
346
336
  // Use index optimization
@@ -27,6 +27,7 @@ import type {
27
27
  NonSingleResult,
28
28
  OperationConfig,
29
29
  SingleResult,
30
+ StringCollationConfig,
30
31
  SubscribeChangesOptions,
31
32
  Transaction as TransactionType,
32
33
  UtilsRecord,
@@ -132,7 +133,7 @@ export function createCollection<
132
133
  TKey extends string | number = string | number,
133
134
  TUtils extends UtilsRecord = UtilsRecord,
134
135
  >(
135
- options: CollectionConfig<InferSchemaOutput<T>, TKey, T> & {
136
+ options: CollectionConfig<InferSchemaOutput<T>, TKey, T, TUtils> & {
136
137
  schema: T
137
138
  utils?: TUtils
138
139
  } & NonSingleResult
@@ -145,7 +146,7 @@ export function createCollection<
145
146
  TKey extends string | number = string | number,
146
147
  TUtils extends UtilsRecord = UtilsRecord,
147
148
  >(
148
- options: CollectionConfig<InferSchemaOutput<T>, TKey, T> & {
149
+ options: CollectionConfig<InferSchemaOutput<T>, TKey, T, TUtils> & {
149
150
  schema: T
150
151
  utils?: TUtils
151
152
  } & SingleResult
@@ -159,7 +160,7 @@ export function createCollection<
159
160
  TKey extends string | number = string | number,
160
161
  TUtils extends UtilsRecord = UtilsRecord,
161
162
  >(
162
- options: CollectionConfig<T, TKey, never> & {
163
+ options: CollectionConfig<T, TKey, never, TUtils> & {
163
164
  schema?: never // prohibit schema if an explicit type is provided
164
165
  utils?: TUtils
165
166
  } & NonSingleResult
@@ -172,7 +173,7 @@ export function createCollection<
172
173
  TKey extends string | number = string | number,
173
174
  TUtils extends UtilsRecord = UtilsRecord,
174
175
  >(
175
- options: CollectionConfig<T, TKey, never> & {
176
+ options: CollectionConfig<T, TKey, never, TUtils> & {
176
177
  schema?: never // prohibit schema if an explicit type is provided
177
178
  utils?: TUtils
178
179
  } & SingleResult
@@ -230,6 +231,8 @@ export class CollectionImpl<
230
231
  // and for debugging
231
232
  public _state: CollectionStateManager<TOutput, TKey, TSchema, TInput>
232
233
 
234
+ private comparisonOpts: StringCollationConfig
235
+
233
236
  /**
234
237
  * Creates a new Collection instance
235
238
  *
@@ -267,6 +270,8 @@ export class CollectionImpl<
267
270
  this._state = new CollectionStateManager(config)
268
271
  this._sync = new CollectionSyncManager(config, this.id)
269
272
 
273
+ this.comparisonOpts = buildCompareOptionsFromConfig(config)
274
+
270
275
  this._changes.setDeps({
271
276
  collection: this, // Required for passing to CollectionSubscription
272
277
  lifecycle: this._lifecycle,
@@ -508,6 +513,11 @@ export class CollectionImpl<
508
513
  return this._mutations.validateData(data, type, key)
509
514
  }
510
515
 
516
+ get compareOptions(): StringCollationConfig {
517
+ // return a copy such that no one can mutate the internal comparison object
518
+ return { ...this.comparisonOpts }
519
+ }
520
+
511
521
  /**
512
522
  * Inserts one or more items into the collection
513
523
  * @param items - Single item or array of items to insert
@@ -848,3 +858,21 @@ export class CollectionImpl<
848
858
  return Promise.resolve()
849
859
  }
850
860
  }
861
+
862
+ function buildCompareOptionsFromConfig(
863
+ config: CollectionConfig<any, any, any>
864
+ ): StringCollationConfig {
865
+ if (config.defaultStringCollation) {
866
+ const options = config.defaultStringCollation
867
+ return {
868
+ stringSort: options.stringSort ?? `locale`,
869
+ locale: options.stringSort === `locale` ? options.locale : undefined,
870
+ localeOptions:
871
+ options.stringSort === `locale` ? options.localeOptions : undefined,
872
+ }
873
+ } else {
874
+ return {
875
+ stringSort: `locale`,
876
+ }
877
+ }
878
+ }
package/src/index.ts CHANGED
@@ -22,6 +22,10 @@ export * from "./indexes/btree-index.js"
22
22
  export * from "./indexes/lazy-index.js"
23
23
  export { type IndexOptions } from "./indexes/index-options.js"
24
24
 
25
+ // Expression helpers
26
+ export * from "./query/expression-helpers.js"
27
+
25
28
  // Re-export some stuff explicitly to ensure the type & value is exported
26
29
  export type { Collection } from "./collection/index.js"
27
30
  export { IR }
31
+ export { operators, type OperatorName } from "./query/builder/functions.js"
@@ -24,18 +24,22 @@ export function ensureIndexForField<
24
24
  fieldName: string,
25
25
  fieldPath: Array<string>,
26
26
  collection: CollectionImpl<T, TKey, any, any, any>,
27
- compareOptions: CompareOptions = DEFAULT_COMPARE_OPTIONS,
27
+ compareOptions?: CompareOptions,
28
28
  compareFn?: (a: any, b: any) => number
29
29
  ) {
30
30
  if (!shouldAutoIndex(collection)) {
31
31
  return
32
32
  }
33
33
 
34
+ const compareOpts = compareOptions ?? {
35
+ ...DEFAULT_COMPARE_OPTIONS,
36
+ ...collection.compareOptions,
37
+ }
38
+
34
39
  // Check if we already have an index for this field
35
40
  const existingIndex = Array.from(collection.indexes.values()).find(
36
41
  (index) =>
37
- index.matchesField(fieldPath) &&
38
- index.matchesCompareOptions(compareOptions)
42
+ index.matchesField(fieldPath) && index.matchesCompareOptions(compareOpts)
39
43
  )
40
44
 
41
45
  if (existingIndex) {
@@ -57,7 +61,7 @@ export function ensureIndexForField<
57
61
  {
58
62
  name: `auto:${fieldPath.join(`.`)}`,
59
63
  indexType: BTreeIndex,
60
- options: compareFn ? { compareFn, compareOptions } : {},
64
+ options: compareFn ? { compareFn, compareOptions: compareOpts } : {},
61
65
  }
62
66
  )
63
67
  } catch (error) {
@@ -283,7 +283,12 @@ export function localStorageCollectionOptions<
283
283
  config: LocalStorageCollectionConfig<InferSchemaOutput<T>, T, TKey> & {
284
284
  schema: T
285
285
  }
286
- ): CollectionConfig<InferSchemaOutput<T>, TKey, T> & {
286
+ ): CollectionConfig<
287
+ InferSchemaOutput<T>,
288
+ TKey,
289
+ T,
290
+ LocalStorageCollectionUtils
291
+ > & {
287
292
  id: string
288
293
  utils: LocalStorageCollectionUtils
289
294
  schema: T
@@ -298,7 +303,7 @@ export function localStorageCollectionOptions<
298
303
  config: LocalStorageCollectionConfig<T, never, TKey> & {
299
304
  schema?: never // prohibit schema
300
305
  }
301
- ): CollectionConfig<T, TKey> & {
306
+ ): CollectionConfig<T, TKey, never, LocalStorageCollectionUtils> & {
302
307
  id: string
303
308
  utils: LocalStorageCollectionUtils
304
309
  schema?: never // no schema in the result
@@ -306,7 +311,10 @@ export function localStorageCollectionOptions<
306
311
 
307
312
  export function localStorageCollectionOptions(
308
313
  config: LocalStorageCollectionConfig<any, any, string | number>
309
- ): Omit<CollectionConfig<any, string | number, any>, `id`> & {
314
+ ): Omit<
315
+ CollectionConfig<any, string | number, any, LocalStorageCollectionUtils>,
316
+ `id`
317
+ > & {
310
318
  id: string
311
319
  utils: LocalStorageCollectionUtils
312
320
  schema?: StandardSchemaV1
@@ -337,3 +337,42 @@ export const comparisonFunctions = [
337
337
  `like`,
338
338
  `ilike`,
339
339
  ] as const
340
+
341
+ /**
342
+ * All supported operator names in TanStack DB expressions
343
+ */
344
+ export const operators = [
345
+ // Comparison operators
346
+ `eq`,
347
+ `gt`,
348
+ `gte`,
349
+ `lt`,
350
+ `lte`,
351
+ `in`,
352
+ `like`,
353
+ `ilike`,
354
+ // Logical operators
355
+ `and`,
356
+ `or`,
357
+ `not`,
358
+ // Null checking
359
+ `isNull`,
360
+ `isUndefined`,
361
+ // String functions
362
+ `upper`,
363
+ `lower`,
364
+ `length`,
365
+ `concat`,
366
+ // Numeric functions
367
+ `add`,
368
+ // Utility functions
369
+ `coalesce`,
370
+ // Aggregate functions
371
+ `count`,
372
+ `avg`,
373
+ `sum`,
374
+ `min`,
375
+ `max`,
376
+ ] as const
377
+
378
+ export type OperatorName = (typeof operators)[number]
@@ -469,11 +469,11 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
469
469
 
470
470
  const opts: CompareOptions =
471
471
  typeof options === `string`
472
- ? { direction: options, nulls: `first`, stringSort: `locale` }
472
+ ? { direction: options, nulls: `first` }
473
473
  : {
474
474
  direction: options.direction ?? `asc`,
475
475
  nulls: options.nulls ?? `first`,
476
- stringSort: options.stringSort ?? `locale`,
476
+ stringSort: options.stringSort,
477
477
  locale:
478
478
  options.stringSort === `locale` ? options.locale : undefined,
479
479
  localeOptions:
@@ -1,5 +1,5 @@
1
1
  import type { CollectionImpl } from "../../collection/index.js"
2
- import type { SingleResult } from "../../types.js"
2
+ import type { SingleResult, StringCollationConfig } from "../../types.js"
3
3
  import type {
4
4
  Aggregate,
5
5
  BasicExpression,
@@ -303,26 +303,7 @@ export type OrderByCallback<TContext extends Context> = (
303
303
  export type OrderByOptions = {
304
304
  direction?: OrderByDirection
305
305
  nulls?: `first` | `last`
306
- } & StringSortOpts
307
-
308
- /**
309
- * StringSortOpts - Options for string sorting behavior
310
- *
311
- * This discriminated union allows for two types of string sorting:
312
- * - **Lexical**: Simple character-by-character comparison (default)
313
- * - **Locale**: Locale-aware sorting with optional customization
314
- *
315
- * The union ensures that locale options are only available when locale sorting is selected.
316
- */
317
- export type StringSortOpts =
318
- | {
319
- stringSort?: `lexical`
320
- }
321
- | {
322
- stringSort?: `locale`
323
- locale?: string
324
- localeOptions?: object
325
- }
306
+ } & StringCollationConfig
326
307
 
327
308
  /**
328
309
  * CompareOptions - Final resolved options for comparison operations
@@ -331,12 +312,9 @@ export type StringSortOpts =
331
312
  * to their concrete values. Unlike OrderByOptions, all fields are required
332
313
  * since defaults have been applied.
333
314
  */
334
- export type CompareOptions = {
315
+ export type CompareOptions = StringCollationConfig & {
335
316
  direction: OrderByDirection
336
317
  nulls: `first` | `last`
337
- stringSort: `lexical` | `locale`
338
- locale?: string
339
- localeOptions?: object
340
318
  }
341
319
 
342
320
  /**