@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.
- package/dist/cjs/collection/change-events.cjs +10 -12
- package/dist/cjs/collection/change-events.cjs.map +1 -1
- package/dist/cjs/collection/change-events.d.cts +1 -8
- package/dist/cjs/collection/index.cjs +18 -0
- package/dist/cjs/collection/index.cjs.map +1 -1
- package/dist/cjs/collection/index.d.cts +7 -5
- package/dist/cjs/index.cjs +21 -3
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +2 -0
- package/dist/cjs/indexes/auto-index.cjs +7 -3
- package/dist/cjs/indexes/auto-index.cjs.map +1 -1
- package/dist/cjs/local-storage.cjs.map +1 -1
- package/dist/cjs/local-storage.d.cts +2 -2
- package/dist/cjs/query/builder/functions.cjs +34 -0
- package/dist/cjs/query/builder/functions.cjs.map +1 -1
- package/dist/cjs/query/builder/functions.d.cts +5 -0
- package/dist/cjs/query/builder/index.cjs +2 -2
- package/dist/cjs/query/builder/index.cjs.map +1 -1
- package/dist/cjs/query/builder/types.d.cts +3 -22
- package/dist/cjs/query/compiler/evaluators.cjs +57 -4
- package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
- package/dist/cjs/query/compiler/evaluators.d.cts +13 -0
- package/dist/cjs/query/compiler/expressions.cjs +4 -1
- package/dist/cjs/query/compiler/expressions.cjs.map +1 -1
- package/dist/cjs/query/compiler/group-by.cjs +3 -3
- package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.cjs +2 -2
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.cjs +18 -6
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.d.cts +7 -1
- package/dist/cjs/query/expression-helpers.cjs +217 -0
- package/dist/cjs/query/expression-helpers.cjs.map +1 -0
- package/dist/cjs/query/expression-helpers.d.cts +216 -0
- package/dist/cjs/query/index.d.cts +2 -0
- package/dist/cjs/query/live/collection-config-builder.cjs +13 -0
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.d.cts +1 -0
- package/dist/cjs/query/live/types.d.cts +6 -1
- package/dist/cjs/query/predicate-utils.cjs +816 -0
- package/dist/cjs/query/predicate-utils.cjs.map +1 -0
- package/dist/cjs/query/predicate-utils.d.cts +116 -0
- package/dist/cjs/query/subset-dedupe.cjs +111 -0
- package/dist/cjs/query/subset-dedupe.cjs.map +1 -0
- package/dist/cjs/query/subset-dedupe.d.cts +66 -0
- package/dist/cjs/types.d.cts +29 -0
- package/dist/cjs/utils/comparison.cjs +30 -0
- package/dist/cjs/utils/comparison.cjs.map +1 -1
- package/dist/cjs/utils/comparison.d.cts +7 -1
- package/dist/cjs/utils/index-optimization.cjs +26 -22
- package/dist/cjs/utils/index-optimization.cjs.map +1 -1
- package/dist/cjs/utils/index-optimization.d.cts +5 -4
- package/dist/esm/collection/change-events.d.ts +1 -8
- package/dist/esm/collection/change-events.js +7 -9
- package/dist/esm/collection/change-events.js.map +1 -1
- package/dist/esm/collection/index.d.ts +7 -5
- package/dist/esm/collection/index.js +18 -0
- package/dist/esm/collection/index.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +19 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/indexes/auto-index.js +7 -3
- package/dist/esm/indexes/auto-index.js.map +1 -1
- package/dist/esm/local-storage.d.ts +2 -2
- package/dist/esm/local-storage.js.map +1 -1
- package/dist/esm/query/builder/functions.d.ts +5 -0
- package/dist/esm/query/builder/functions.js +34 -0
- package/dist/esm/query/builder/functions.js.map +1 -1
- package/dist/esm/query/builder/index.js +2 -2
- package/dist/esm/query/builder/index.js.map +1 -1
- package/dist/esm/query/builder/types.d.ts +3 -22
- package/dist/esm/query/compiler/evaluators.d.ts +13 -0
- package/dist/esm/query/compiler/evaluators.js +59 -6
- package/dist/esm/query/compiler/evaluators.js.map +1 -1
- package/dist/esm/query/compiler/expressions.js +4 -1
- package/dist/esm/query/compiler/expressions.js.map +1 -1
- package/dist/esm/query/compiler/group-by.js +4 -4
- package/dist/esm/query/compiler/group-by.js.map +1 -1
- package/dist/esm/query/compiler/index.js +3 -3
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/compiler/order-by.d.ts +7 -1
- package/dist/esm/query/compiler/order-by.js +18 -6
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/expression-helpers.d.ts +216 -0
- package/dist/esm/query/expression-helpers.js +217 -0
- package/dist/esm/query/expression-helpers.js.map +1 -0
- package/dist/esm/query/index.d.ts +2 -0
- package/dist/esm/query/live/collection-config-builder.d.ts +1 -0
- package/dist/esm/query/live/collection-config-builder.js +13 -0
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/types.d.ts +6 -1
- package/dist/esm/query/predicate-utils.d.ts +116 -0
- package/dist/esm/query/predicate-utils.js +816 -0
- package/dist/esm/query/predicate-utils.js.map +1 -0
- package/dist/esm/query/subset-dedupe.d.ts +66 -0
- package/dist/esm/query/subset-dedupe.js +111 -0
- package/dist/esm/query/subset-dedupe.js.map +1 -0
- package/dist/esm/types.d.ts +29 -0
- package/dist/esm/utils/comparison.d.ts +7 -1
- package/dist/esm/utils/comparison.js +30 -0
- package/dist/esm/utils/comparison.js.map +1 -1
- package/dist/esm/utils/index-optimization.d.ts +5 -4
- package/dist/esm/utils/index-optimization.js +26 -22
- package/dist/esm/utils/index-optimization.js.map +1 -1
- package/package.json +2 -2
- package/src/collection/change-events.ts +14 -24
- package/src/collection/index.ts +32 -4
- package/src/index.ts +4 -0
- package/src/indexes/auto-index.ts +8 -4
- package/src/local-storage.ts +11 -3
- package/src/query/builder/functions.ts +39 -0
- package/src/query/builder/index.ts +2 -2
- package/src/query/builder/types.ts +3 -25
- package/src/query/compiler/evaluators.ts +103 -5
- package/src/query/compiler/expressions.ts +3 -0
- package/src/query/compiler/group-by.ts +4 -4
- package/src/query/compiler/index.ts +3 -3
- package/src/query/compiler/order-by.ts +33 -7
- package/src/query/expression-helpers.ts +522 -0
- package/src/query/index.ts +12 -0
- package/src/query/live/collection-config-builder.ts +27 -0
- package/src/query/live/types.ts +11 -1
- package/src/query/predicate-utils.ts +1415 -0
- package/src/query/subset-dedupe.ts +243 -0
- package/src/types.ts +39 -0
- package/src/utils/comparison.ts +70 -1
- 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(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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,
|
|
39
|
-
return optimizeQueryRecursive(expression,
|
|
42
|
+
function optimizeExpressionWithIndexes(expression, collection) {
|
|
43
|
+
return optimizeQueryRecursive(expression, collection);
|
|
40
44
|
}
|
|
41
|
-
function optimizeQueryRecursive(expression,
|
|
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,
|
|
53
|
+
return optimizeSimpleComparison(expression, collection);
|
|
50
54
|
case `and`:
|
|
51
|
-
return optimizeAndExpression(expression,
|
|
55
|
+
return optimizeAndExpression(expression, collection);
|
|
52
56
|
case `or`:
|
|
53
|
-
return optimizeOrExpression(expression,
|
|
57
|
+
return optimizeOrExpression(expression, collection);
|
|
54
58
|
case `in`:
|
|
55
|
-
return optimizeInArrayExpression(expression,
|
|
59
|
+
return optimizeInArrayExpression(expression, collection);
|
|
56
60
|
}
|
|
57
61
|
}
|
|
58
62
|
return { canOptimize: false, matchingKeys: /* @__PURE__ */ new Set() };
|
|
59
63
|
}
|
|
60
|
-
function optimizeCompoundRangeQuery(expression,
|
|
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(
|
|
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,
|
|
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(
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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(
|
|
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
|
+
"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.
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
package/src/collection/index.ts
CHANGED
|
@@ -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
|
|
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) {
|
package/src/local-storage.ts
CHANGED
|
@@ -283,7 +283,12 @@ export function localStorageCollectionOptions<
|
|
|
283
283
|
config: LocalStorageCollectionConfig<InferSchemaOutput<T>, T, TKey> & {
|
|
284
284
|
schema: T
|
|
285
285
|
}
|
|
286
|
-
): CollectionConfig<
|
|
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<
|
|
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
|
|
472
|
+
? { direction: options, nulls: `first` }
|
|
473
473
|
: {
|
|
474
474
|
direction: options.direction ?? `asc`,
|
|
475
475
|
nulls: options.nulls ?? `first`,
|
|
476
|
-
stringSort: options.stringSort
|
|
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
|
-
} &
|
|
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
|
/**
|