@tanstack/db 0.0.26 → 0.0.29

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 (164) hide show
  1. package/dist/cjs/change-events.cjs +141 -0
  2. package/dist/cjs/change-events.cjs.map +1 -0
  3. package/dist/cjs/change-events.d.cts +49 -0
  4. package/dist/cjs/collection.cjs +236 -90
  5. package/dist/cjs/collection.cjs.map +1 -1
  6. package/dist/cjs/collection.d.cts +95 -20
  7. package/dist/cjs/errors.cjs +509 -1
  8. package/dist/cjs/errors.cjs.map +1 -1
  9. package/dist/cjs/errors.d.cts +225 -1
  10. package/dist/cjs/index.cjs +82 -3
  11. package/dist/cjs/index.cjs.map +1 -1
  12. package/dist/cjs/index.d.cts +5 -1
  13. package/dist/cjs/indexes/auto-index.cjs +64 -0
  14. package/dist/cjs/indexes/auto-index.cjs.map +1 -0
  15. package/dist/cjs/indexes/auto-index.d.cts +9 -0
  16. package/dist/cjs/indexes/base-index.cjs +46 -0
  17. package/dist/cjs/indexes/base-index.cjs.map +1 -0
  18. package/dist/cjs/indexes/base-index.d.cts +54 -0
  19. package/dist/cjs/indexes/index-options.d.cts +13 -0
  20. package/dist/cjs/indexes/lazy-index.cjs +193 -0
  21. package/dist/cjs/indexes/lazy-index.cjs.map +1 -0
  22. package/dist/cjs/indexes/lazy-index.d.cts +96 -0
  23. package/dist/cjs/indexes/ordered-index.cjs +227 -0
  24. package/dist/cjs/indexes/ordered-index.cjs.map +1 -0
  25. package/dist/cjs/indexes/ordered-index.d.cts +72 -0
  26. package/dist/cjs/local-storage.cjs +9 -15
  27. package/dist/cjs/local-storage.cjs.map +1 -1
  28. package/dist/cjs/query/builder/functions.cjs +11 -0
  29. package/dist/cjs/query/builder/functions.cjs.map +1 -1
  30. package/dist/cjs/query/builder/functions.d.cts +4 -0
  31. package/dist/cjs/query/builder/index.cjs +6 -7
  32. package/dist/cjs/query/builder/index.cjs.map +1 -1
  33. package/dist/cjs/query/builder/ref-proxy.cjs +37 -0
  34. package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
  35. package/dist/cjs/query/builder/ref-proxy.d.cts +12 -0
  36. package/dist/cjs/query/compiler/evaluators.cjs +83 -58
  37. package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
  38. package/dist/cjs/query/compiler/evaluators.d.cts +8 -0
  39. package/dist/cjs/query/compiler/expressions.cjs +61 -0
  40. package/dist/cjs/query/compiler/expressions.cjs.map +1 -0
  41. package/dist/cjs/query/compiler/expressions.d.cts +25 -0
  42. package/dist/cjs/query/compiler/group-by.cjs +5 -10
  43. package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
  44. package/dist/cjs/query/compiler/index.cjs +23 -17
  45. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  46. package/dist/cjs/query/compiler/index.d.cts +12 -3
  47. package/dist/cjs/query/compiler/joins.cjs +61 -12
  48. package/dist/cjs/query/compiler/joins.cjs.map +1 -1
  49. package/dist/cjs/query/compiler/order-by.cjs +4 -34
  50. package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
  51. package/dist/cjs/query/compiler/types.d.cts +2 -2
  52. package/dist/cjs/query/live-query-collection.cjs +54 -12
  53. package/dist/cjs/query/live-query-collection.cjs.map +1 -1
  54. package/dist/cjs/query/optimizer.cjs +45 -7
  55. package/dist/cjs/query/optimizer.cjs.map +1 -1
  56. package/dist/cjs/query/optimizer.d.cts +13 -3
  57. package/dist/cjs/transactions.cjs +5 -4
  58. package/dist/cjs/transactions.cjs.map +1 -1
  59. package/dist/cjs/types.d.cts +31 -0
  60. package/dist/cjs/utils/array-utils.cjs +18 -0
  61. package/dist/cjs/utils/array-utils.cjs.map +1 -0
  62. package/dist/cjs/utils/array-utils.d.cts +8 -0
  63. package/dist/cjs/utils/comparison.cjs +52 -0
  64. package/dist/cjs/utils/comparison.cjs.map +1 -0
  65. package/dist/cjs/utils/comparison.d.cts +11 -0
  66. package/dist/cjs/utils/index-optimization.cjs +270 -0
  67. package/dist/cjs/utils/index-optimization.cjs.map +1 -0
  68. package/dist/cjs/utils/index-optimization.d.cts +29 -0
  69. package/dist/esm/change-events.d.ts +49 -0
  70. package/dist/esm/change-events.js +141 -0
  71. package/dist/esm/change-events.js.map +1 -0
  72. package/dist/esm/collection.d.ts +95 -20
  73. package/dist/esm/collection.js +234 -88
  74. package/dist/esm/collection.js.map +1 -1
  75. package/dist/esm/errors.d.ts +225 -1
  76. package/dist/esm/errors.js +510 -2
  77. package/dist/esm/errors.js.map +1 -1
  78. package/dist/esm/index.d.ts +5 -1
  79. package/dist/esm/index.js +81 -2
  80. package/dist/esm/index.js.map +1 -1
  81. package/dist/esm/indexes/auto-index.d.ts +9 -0
  82. package/dist/esm/indexes/auto-index.js +64 -0
  83. package/dist/esm/indexes/auto-index.js.map +1 -0
  84. package/dist/esm/indexes/base-index.d.ts +54 -0
  85. package/dist/esm/indexes/base-index.js +46 -0
  86. package/dist/esm/indexes/base-index.js.map +1 -0
  87. package/dist/esm/indexes/index-options.d.ts +13 -0
  88. package/dist/esm/indexes/lazy-index.d.ts +96 -0
  89. package/dist/esm/indexes/lazy-index.js +193 -0
  90. package/dist/esm/indexes/lazy-index.js.map +1 -0
  91. package/dist/esm/indexes/ordered-index.d.ts +72 -0
  92. package/dist/esm/indexes/ordered-index.js +227 -0
  93. package/dist/esm/indexes/ordered-index.js.map +1 -0
  94. package/dist/esm/local-storage.js +9 -15
  95. package/dist/esm/local-storage.js.map +1 -1
  96. package/dist/esm/query/builder/functions.d.ts +4 -0
  97. package/dist/esm/query/builder/functions.js +11 -0
  98. package/dist/esm/query/builder/functions.js.map +1 -1
  99. package/dist/esm/query/builder/index.js +6 -7
  100. package/dist/esm/query/builder/index.js.map +1 -1
  101. package/dist/esm/query/builder/ref-proxy.d.ts +12 -0
  102. package/dist/esm/query/builder/ref-proxy.js +37 -0
  103. package/dist/esm/query/builder/ref-proxy.js.map +1 -1
  104. package/dist/esm/query/compiler/evaluators.d.ts +8 -0
  105. package/dist/esm/query/compiler/evaluators.js +84 -59
  106. package/dist/esm/query/compiler/evaluators.js.map +1 -1
  107. package/dist/esm/query/compiler/expressions.d.ts +25 -0
  108. package/dist/esm/query/compiler/expressions.js +61 -0
  109. package/dist/esm/query/compiler/expressions.js.map +1 -0
  110. package/dist/esm/query/compiler/group-by.js +5 -10
  111. package/dist/esm/query/compiler/group-by.js.map +1 -1
  112. package/dist/esm/query/compiler/index.d.ts +12 -3
  113. package/dist/esm/query/compiler/index.js +23 -17
  114. package/dist/esm/query/compiler/index.js.map +1 -1
  115. package/dist/esm/query/compiler/joins.js +61 -12
  116. package/dist/esm/query/compiler/joins.js.map +1 -1
  117. package/dist/esm/query/compiler/order-by.js +1 -31
  118. package/dist/esm/query/compiler/order-by.js.map +1 -1
  119. package/dist/esm/query/compiler/types.d.ts +2 -2
  120. package/dist/esm/query/live-query-collection.js +54 -12
  121. package/dist/esm/query/live-query-collection.js.map +1 -1
  122. package/dist/esm/query/optimizer.d.ts +13 -3
  123. package/dist/esm/query/optimizer.js +40 -2
  124. package/dist/esm/query/optimizer.js.map +1 -1
  125. package/dist/esm/transactions.js +5 -4
  126. package/dist/esm/transactions.js.map +1 -1
  127. package/dist/esm/types.d.ts +31 -0
  128. package/dist/esm/utils/array-utils.d.ts +8 -0
  129. package/dist/esm/utils/array-utils.js +18 -0
  130. package/dist/esm/utils/array-utils.js.map +1 -0
  131. package/dist/esm/utils/comparison.d.ts +11 -0
  132. package/dist/esm/utils/comparison.js +52 -0
  133. package/dist/esm/utils/comparison.js.map +1 -0
  134. package/dist/esm/utils/index-optimization.d.ts +29 -0
  135. package/dist/esm/utils/index-optimization.js +270 -0
  136. package/dist/esm/utils/index-optimization.js.map +1 -0
  137. package/package.json +3 -2
  138. package/src/change-events.ts +257 -0
  139. package/src/collection.ts +321 -110
  140. package/src/errors.ts +545 -1
  141. package/src/index.ts +7 -1
  142. package/src/indexes/auto-index.ts +108 -0
  143. package/src/indexes/base-index.ts +119 -0
  144. package/src/indexes/index-options.ts +42 -0
  145. package/src/indexes/lazy-index.ts +251 -0
  146. package/src/indexes/ordered-index.ts +305 -0
  147. package/src/local-storage.ts +16 -17
  148. package/src/query/builder/functions.ts +14 -0
  149. package/src/query/builder/index.ts +12 -7
  150. package/src/query/builder/ref-proxy.ts +65 -0
  151. package/src/query/compiler/evaluators.ts +114 -62
  152. package/src/query/compiler/expressions.ts +92 -0
  153. package/src/query/compiler/group-by.ts +10 -10
  154. package/src/query/compiler/index.ts +52 -22
  155. package/src/query/compiler/joins.ts +114 -15
  156. package/src/query/compiler/order-by.ts +1 -45
  157. package/src/query/compiler/types.ts +2 -2
  158. package/src/query/live-query-collection.ts +95 -15
  159. package/src/query/optimizer.ts +94 -5
  160. package/src/transactions.ts +10 -4
  161. package/src/types.ts +38 -0
  162. package/src/utils/array-utils.ts +28 -0
  163. package/src/utils/comparison.ts +79 -0
  164. package/src/utils/index-optimization.ts +546 -0
@@ -0,0 +1,270 @@
1
+ function findIndexForField(indexes, fieldPath) {
2
+ for (const index of indexes.values()) {
3
+ if (index.matchesField(fieldPath)) {
4
+ return index;
5
+ }
6
+ }
7
+ return void 0;
8
+ }
9
+ function intersectSets(sets) {
10
+ if (sets.length === 0) return /* @__PURE__ */ new Set();
11
+ if (sets.length === 1) return new Set(sets[0]);
12
+ let result = new Set(sets[0]);
13
+ for (let i = 1; i < sets.length; i++) {
14
+ const newResult = /* @__PURE__ */ new Set();
15
+ for (const item of result) {
16
+ if (sets[i].has(item)) {
17
+ newResult.add(item);
18
+ }
19
+ }
20
+ result = newResult;
21
+ }
22
+ return result;
23
+ }
24
+ function unionSets(sets) {
25
+ const result = /* @__PURE__ */ new Set();
26
+ for (const set of sets) {
27
+ for (const item of set) {
28
+ result.add(item);
29
+ }
30
+ }
31
+ return result;
32
+ }
33
+ function optimizeExpressionWithIndexes(expression, indexes) {
34
+ return optimizeQueryRecursive(expression, indexes);
35
+ }
36
+ function optimizeQueryRecursive(expression, indexes) {
37
+ if (expression.type === `func`) {
38
+ switch (expression.name) {
39
+ case `eq`:
40
+ case `gt`:
41
+ case `gte`:
42
+ case `lt`:
43
+ case `lte`:
44
+ return optimizeSimpleComparison(expression, indexes);
45
+ case `and`:
46
+ return optimizeAndExpression(expression, indexes);
47
+ case `or`:
48
+ return optimizeOrExpression(expression, indexes);
49
+ case `in`:
50
+ return optimizeInArrayExpression(expression, indexes);
51
+ }
52
+ }
53
+ return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set() };
54
+ }
55
+ function optimizeCompoundRangeQuery(expression, indexes) {
56
+ if (expression.type !== `func` || expression.args.length < 2) {
57
+ return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set() };
58
+ }
59
+ const fieldOperations = /* @__PURE__ */ new Map();
60
+ for (const arg of expression.args) {
61
+ if (arg.type === `func` && [`gt`, `gte`, `lt`, `lte`].includes(arg.name)) {
62
+ const rangeOp = arg;
63
+ if (rangeOp.args.length === 2) {
64
+ const leftArg = rangeOp.args[0];
65
+ const rightArg = rangeOp.args[1];
66
+ let fieldArg = null;
67
+ let valueArg = null;
68
+ let operation = rangeOp.name;
69
+ if (leftArg.type === `ref` && rightArg.type === `val`) {
70
+ fieldArg = leftArg;
71
+ valueArg = rightArg;
72
+ } else if (leftArg.type === `val` && rightArg.type === `ref`) {
73
+ fieldArg = rightArg;
74
+ valueArg = leftArg;
75
+ switch (operation) {
76
+ case `gt`:
77
+ operation = `lt`;
78
+ break;
79
+ case `gte`:
80
+ operation = `lte`;
81
+ break;
82
+ case `lt`:
83
+ operation = `gt`;
84
+ break;
85
+ case `lte`:
86
+ operation = `gte`;
87
+ break;
88
+ }
89
+ }
90
+ if (fieldArg && valueArg) {
91
+ const fieldPath = fieldArg.path;
92
+ const fieldKey = fieldPath.join(`.`);
93
+ const value = valueArg.value;
94
+ if (!fieldOperations.has(fieldKey)) {
95
+ fieldOperations.set(fieldKey, []);
96
+ }
97
+ fieldOperations.get(fieldKey).push({ operation, value });
98
+ }
99
+ }
100
+ }
101
+ }
102
+ for (const [fieldKey, operations] of fieldOperations) {
103
+ if (operations.length >= 2) {
104
+ const fieldPath = fieldKey.split(`.`);
105
+ const index = findIndexForField(indexes, fieldPath);
106
+ if (index && index.supports(`gt`) && index.supports(`lt`)) {
107
+ let from = void 0;
108
+ let to = void 0;
109
+ let fromInclusive = true;
110
+ let toInclusive = true;
111
+ for (const { operation, value } of operations) {
112
+ switch (operation) {
113
+ case `gt`:
114
+ if (from === void 0 || value > from) {
115
+ from = value;
116
+ fromInclusive = false;
117
+ }
118
+ break;
119
+ case `gte`:
120
+ if (from === void 0 || value > from) {
121
+ from = value;
122
+ fromInclusive = true;
123
+ }
124
+ break;
125
+ case `lt`:
126
+ if (to === void 0 || value < to) {
127
+ to = value;
128
+ toInclusive = false;
129
+ }
130
+ break;
131
+ case `lte`:
132
+ if (to === void 0 || value < to) {
133
+ to = value;
134
+ toInclusive = true;
135
+ }
136
+ break;
137
+ }
138
+ }
139
+ const matchingKeys = index.rangeQuery({
140
+ from,
141
+ to,
142
+ fromInclusive,
143
+ toInclusive
144
+ });
145
+ return { canOptimize: true, matchingKeys };
146
+ }
147
+ }
148
+ }
149
+ return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set() };
150
+ }
151
+ function optimizeSimpleComparison(expression, indexes) {
152
+ if (expression.type !== `func` || expression.args.length !== 2) {
153
+ return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set() };
154
+ }
155
+ const leftArg = expression.args[0];
156
+ const rightArg = expression.args[1];
157
+ let fieldArg = null;
158
+ let valueArg = null;
159
+ let operation = expression.name;
160
+ if (leftArg.type === `ref` && rightArg.type === `val`) {
161
+ fieldArg = leftArg;
162
+ valueArg = rightArg;
163
+ } else if (leftArg.type === `val` && rightArg.type === `ref`) {
164
+ fieldArg = rightArg;
165
+ valueArg = leftArg;
166
+ switch (operation) {
167
+ case `gt`:
168
+ operation = `lt`;
169
+ break;
170
+ case `gte`:
171
+ operation = `lte`;
172
+ break;
173
+ case `lt`:
174
+ operation = `gt`;
175
+ break;
176
+ case `lte`:
177
+ operation = `gte`;
178
+ break;
179
+ }
180
+ }
181
+ if (fieldArg && valueArg) {
182
+ const fieldPath = fieldArg.path;
183
+ const index = findIndexForField(indexes, fieldPath);
184
+ if (index) {
185
+ const queryValue = valueArg.value;
186
+ const indexOperation = operation;
187
+ if (!index.supports(indexOperation)) {
188
+ return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set() };
189
+ }
190
+ const matchingKeys = index.lookup(indexOperation, queryValue);
191
+ return { canOptimize: true, matchingKeys };
192
+ }
193
+ }
194
+ return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set() };
195
+ }
196
+ function optimizeAndExpression(expression, indexes) {
197
+ if (expression.type !== `func` || expression.args.length < 2) {
198
+ return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set() };
199
+ }
200
+ const compoundRangeResult = optimizeCompoundRangeQuery(expression, indexes);
201
+ if (compoundRangeResult.canOptimize) {
202
+ return compoundRangeResult;
203
+ }
204
+ const results = [];
205
+ for (const arg of expression.args) {
206
+ const result = optimizeQueryRecursive(arg, indexes);
207
+ if (result.canOptimize) {
208
+ results.push(result);
209
+ }
210
+ }
211
+ if (results.length > 0) {
212
+ const allMatchingSets = results.map((r) => r.matchingKeys);
213
+ const intersectedKeys = intersectSets(allMatchingSets);
214
+ return { canOptimize: true, matchingKeys: intersectedKeys };
215
+ }
216
+ return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set() };
217
+ }
218
+ function optimizeOrExpression(expression, indexes) {
219
+ if (expression.type !== `func` || expression.args.length < 2) {
220
+ return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set() };
221
+ }
222
+ const results = [];
223
+ for (const arg of expression.args) {
224
+ const result = optimizeQueryRecursive(arg, indexes);
225
+ if (result.canOptimize) {
226
+ results.push(result);
227
+ }
228
+ }
229
+ if (results.length > 0) {
230
+ const allMatchingSets = results.map((r) => r.matchingKeys);
231
+ const unionedKeys = unionSets(allMatchingSets);
232
+ return { canOptimize: true, matchingKeys: unionedKeys };
233
+ }
234
+ return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set() };
235
+ }
236
+ function optimizeInArrayExpression(expression, indexes) {
237
+ if (expression.type !== `func` || expression.args.length !== 2) {
238
+ return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set() };
239
+ }
240
+ const fieldArg = expression.args[0];
241
+ const arrayArg = expression.args[1];
242
+ if (fieldArg.type === `ref` && arrayArg.type === `val` && Array.isArray(arrayArg.value)) {
243
+ const fieldPath = fieldArg.path;
244
+ const values = arrayArg.value;
245
+ const index = findIndexForField(indexes, fieldPath);
246
+ if (index) {
247
+ if (index.supports(`in`)) {
248
+ const matchingKeys = index.lookup(`in`, values);
249
+ return { canOptimize: true, matchingKeys };
250
+ } else if (index.supports(`eq`)) {
251
+ const matchingKeys = /* @__PURE__ */ new Set();
252
+ for (const value of values) {
253
+ const keysForValue = index.lookup(`eq`, value);
254
+ for (const key of keysForValue) {
255
+ matchingKeys.add(key);
256
+ }
257
+ }
258
+ return { canOptimize: true, matchingKeys };
259
+ }
260
+ }
261
+ }
262
+ return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set() };
263
+ }
264
+ export {
265
+ findIndexForField,
266
+ intersectSets,
267
+ optimizeExpressionWithIndexes,
268
+ unionSets
269
+ };
270
+ //# sourceMappingURL=index-optimization.js.map
@@ -0,0 +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 type { BaseIndex, IndexOperation } 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, BaseIndex<TKey>>,\n fieldPath: Array<string>\n): BaseIndex<TKey> | undefined {\n for (const index of indexes.values()) {\n if (index.matchesField(fieldPath)) {\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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"}
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@tanstack/db",
3
3
  "description": "A reactive client store for building super fast apps on sync",
4
- "version": "0.0.26",
4
+ "version": "0.0.29",
5
5
  "dependencies": {
6
6
  "@electric-sql/d2mini": "^0.1.7",
7
7
  "@standard-schema/spec": "^1.0.0"
8
8
  },
9
9
  "devDependencies": {
10
- "@vitest/coverage-istanbul": "^3.0.9"
10
+ "@vitest/coverage-istanbul": "^3.0.9",
11
+ "arktype": "^2.1.20"
11
12
  },
12
13
  "exports": {
13
14
  ".": {
@@ -0,0 +1,257 @@
1
+ import {
2
+ createSingleRowRefProxy,
3
+ toExpression,
4
+ } from "./query/builder/ref-proxy"
5
+ import { compileSingleRowExpression } from "./query/compiler/evaluators.js"
6
+ import { optimizeExpressionWithIndexes } from "./utils/index-optimization.js"
7
+ import type {
8
+ ChangeMessage,
9
+ CurrentStateAsChangesOptions,
10
+ SubscribeChangesOptions,
11
+ } from "./types"
12
+ import type { Collection } from "./collection"
13
+ import type { SingleRowRefProxy } from "./query/builder/ref-proxy"
14
+ import type { BasicExpression } from "./query/ir.js"
15
+
16
+ /**
17
+ * Interface for a collection-like object that provides the necessary methods
18
+ * for the change events system to work
19
+ */
20
+ export interface CollectionLike<
21
+ T extends object = Record<string, unknown>,
22
+ TKey extends string | number = string | number,
23
+ > extends Pick<Collection<T, TKey>, `get` | `has` | `entries` | `indexes`> {}
24
+
25
+ /**
26
+ * Returns the current state of the collection as an array of changes
27
+ * @param collection - The collection to get changes from
28
+ * @param options - Options including optional where filter
29
+ * @returns An array of changes
30
+ * @example
31
+ * // Get all items as changes
32
+ * const allChanges = currentStateAsChanges(collection)
33
+ *
34
+ * // Get only items matching a condition
35
+ * const activeChanges = currentStateAsChanges(collection, {
36
+ * where: (row) => row.status === 'active'
37
+ * })
38
+ *
39
+ * // Get only items using a pre-compiled expression
40
+ * const activeChanges = currentStateAsChanges(collection, {
41
+ * whereExpression: eq(row.status, 'active')
42
+ * })
43
+ */
44
+ export function currentStateAsChanges<
45
+ T extends object,
46
+ TKey extends string | number,
47
+ >(
48
+ collection: CollectionLike<T, TKey>,
49
+ options: CurrentStateAsChangesOptions<T> = {}
50
+ ): Array<ChangeMessage<T>> {
51
+ // Helper function to collect filtered results
52
+ const collectFilteredResults = (
53
+ filterFn?: (value: T) => boolean
54
+ ): Array<ChangeMessage<T>> => {
55
+ const result: Array<ChangeMessage<T>> = []
56
+ for (const [key, value] of collection.entries()) {
57
+ // If no filter function is provided, include all items
58
+ if (filterFn?.(value) ?? true) {
59
+ result.push({
60
+ type: `insert`,
61
+ key,
62
+ value,
63
+ })
64
+ }
65
+ }
66
+ return result
67
+ }
68
+
69
+ if (!options.where && !options.whereExpression) {
70
+ // No filtering, return all items
71
+ return collectFilteredResults()
72
+ }
73
+
74
+ // There's a where clause, let's see if we can use an index
75
+ try {
76
+ let expression: BasicExpression<boolean>
77
+
78
+ if (options.whereExpression) {
79
+ // Use the pre-compiled expression directly
80
+ expression = options.whereExpression
81
+ } else if (options.where) {
82
+ // Create the single-row refProxy for the callback
83
+ const singleRowRefProxy = createSingleRowRefProxy<T>()
84
+
85
+ // Execute the callback to get the expression
86
+ const whereExpression = options.where(singleRowRefProxy)
87
+
88
+ // Convert the result to a BasicExpression
89
+ expression = toExpression(whereExpression)
90
+ } else {
91
+ // This should never happen due to the check above, but TypeScript needs it
92
+ return []
93
+ }
94
+
95
+ // Try to optimize the query using indexes
96
+ const optimizationResult = optimizeExpressionWithIndexes(
97
+ expression,
98
+ collection.indexes
99
+ )
100
+
101
+ if (optimizationResult.canOptimize) {
102
+ // Use index optimization
103
+ const result: Array<ChangeMessage<T>> = []
104
+ for (const key of optimizationResult.matchingKeys) {
105
+ const value = collection.get(key)
106
+ if (value !== undefined) {
107
+ result.push({
108
+ type: `insert`,
109
+ key,
110
+ value,
111
+ })
112
+ }
113
+ }
114
+ return result
115
+ } else {
116
+ // No index found or complex expression, fall back to full scan with filter
117
+ const filterFn = options.where
118
+ ? createFilterFunction(options.where)
119
+ : createFilterFunctionFromExpression(expression)
120
+
121
+ return collectFilteredResults(filterFn)
122
+ }
123
+ } catch (error) {
124
+ // If anything goes wrong with the where clause, fall back to full scan
125
+ console.warn(
126
+ `Error processing where clause, falling back to full scan:`,
127
+ error
128
+ )
129
+
130
+ const filterFn = options.where
131
+ ? createFilterFunction(options.where)
132
+ : createFilterFunctionFromExpression(options.whereExpression!)
133
+
134
+ return collectFilteredResults(filterFn)
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Creates a filter function from a where callback
140
+ * @param whereCallback - The callback function that defines the filter condition
141
+ * @returns A function that takes an item and returns true if it matches the filter
142
+ */
143
+ export function createFilterFunction<T extends object>(
144
+ whereCallback: (row: SingleRowRefProxy<T>) => any
145
+ ): (item: T) => boolean {
146
+ return (item: T): boolean => {
147
+ try {
148
+ // First try the RefProxy approach for query builder functions
149
+ const singleRowRefProxy = createSingleRowRefProxy<T>()
150
+ const whereExpression = whereCallback(singleRowRefProxy)
151
+ const expression = toExpression(whereExpression)
152
+ const evaluator = compileSingleRowExpression(expression)
153
+ const result = evaluator(item as Record<string, unknown>)
154
+ // WHERE clauses should always evaluate to boolean predicates (Kevin's feedback)
155
+ return result
156
+ } catch {
157
+ // If RefProxy approach fails (e.g., arithmetic operations), fall back to direct evaluation
158
+ try {
159
+ // Create a simple proxy that returns actual values for arithmetic operations
160
+ const simpleProxy = new Proxy(item as any, {
161
+ get(target, prop) {
162
+ return target[prop]
163
+ },
164
+ }) as SingleRowRefProxy<T>
165
+
166
+ const result = whereCallback(simpleProxy)
167
+ return result
168
+ } catch {
169
+ // If both approaches fail, exclude the item
170
+ return false
171
+ }
172
+ }
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Creates a filter function from a pre-compiled expression
178
+ * @param expression - The pre-compiled expression to evaluate
179
+ * @returns A function that takes an item and returns true if it matches the filter
180
+ */
181
+ export function createFilterFunctionFromExpression<T extends object>(
182
+ expression: BasicExpression<boolean>
183
+ ): (item: T) => boolean {
184
+ return (item: T): boolean => {
185
+ try {
186
+ const evaluator = compileSingleRowExpression(expression)
187
+ const result = evaluator(item as Record<string, unknown>)
188
+ return Boolean(result)
189
+ } catch {
190
+ // If evaluation fails, exclude the item
191
+ return false
192
+ }
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Creates a filtered callback that only calls the original callback with changes that match the where clause
198
+ * @param originalCallback - The original callback to filter
199
+ * @param options - The subscription options containing the where clause
200
+ * @returns A filtered callback function
201
+ */
202
+ export function createFilteredCallback<T extends object>(
203
+ originalCallback: (changes: Array<ChangeMessage<T>>) => void,
204
+ options: SubscribeChangesOptions<T>
205
+ ): (changes: Array<ChangeMessage<T>>) => void {
206
+ const filterFn = options.whereExpression
207
+ ? createFilterFunctionFromExpression(options.whereExpression)
208
+ : createFilterFunction(options.where!)
209
+
210
+ return (changes: Array<ChangeMessage<T>>) => {
211
+ const filteredChanges: Array<ChangeMessage<T>> = []
212
+
213
+ for (const change of changes) {
214
+ if (change.type === `insert`) {
215
+ // For inserts, check if the new value matches the filter
216
+ if (filterFn(change.value)) {
217
+ filteredChanges.push(change)
218
+ }
219
+ } else if (change.type === `update`) {
220
+ // For updates, we need to check both old and new values
221
+ const newValueMatches = filterFn(change.value)
222
+ const oldValueMatches = change.previousValue
223
+ ? filterFn(change.previousValue)
224
+ : false
225
+
226
+ if (newValueMatches && oldValueMatches) {
227
+ // Both old and new match: emit update
228
+ filteredChanges.push(change)
229
+ } else if (newValueMatches && !oldValueMatches) {
230
+ // New matches but old didn't: emit insert
231
+ filteredChanges.push({
232
+ ...change,
233
+ type: `insert`,
234
+ })
235
+ } else if (!newValueMatches && oldValueMatches) {
236
+ // Old matched but new doesn't: emit delete
237
+ filteredChanges.push({
238
+ ...change,
239
+ type: `delete`,
240
+ value: change.previousValue!, // Use the previous value for the delete
241
+ })
242
+ }
243
+ // If neither matches, don't emit anything
244
+ } else {
245
+ // For deletes, include if the previous value would have matched
246
+ // (so subscribers know something they were tracking was deleted)
247
+ if (filterFn(change.value)) {
248
+ filteredChanges.push(change)
249
+ }
250
+ }
251
+ }
252
+
253
+ if (filteredChanges.length > 0) {
254
+ originalCallback(filteredChanges)
255
+ }
256
+ }
257
+ }