@tanstack/db 0.5.2 → 0.5.3
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/index.d.cts +1 -1
- package/dist/cjs/collection/mutations.d.cts +2 -2
- package/dist/cjs/query/compiler/expressions.cjs +6 -44
- package/dist/cjs/query/compiler/expressions.cjs.map +1 -1
- package/dist/cjs/query/compiler/expressions.d.cts +15 -21
- package/dist/cjs/query/live/collection-subscriber.cjs +4 -14
- package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
- package/dist/cjs/query/optimizer.cjs +6 -9
- package/dist/cjs/query/optimizer.cjs.map +1 -1
- package/dist/cjs/types.d.cts +20 -1
- package/dist/esm/collection/index.d.ts +1 -1
- package/dist/esm/collection/mutations.d.ts +2 -2
- package/dist/esm/query/compiler/expressions.d.ts +15 -21
- package/dist/esm/query/compiler/expressions.js +6 -44
- package/dist/esm/query/compiler/expressions.js.map +1 -1
- package/dist/esm/query/live/collection-subscriber.js +5 -15
- package/dist/esm/query/live/collection-subscriber.js.map +1 -1
- package/dist/esm/query/optimizer.js +1 -4
- package/dist/esm/query/optimizer.js.map +1 -1
- package/dist/esm/types.d.ts +20 -1
- package/package.json +1 -1
- package/src/query/compiler/expressions.ts +18 -66
- package/src/query/live/collection-subscriber.ts +6 -18
- package/src/query/optimizer.ts +1 -6
- package/src/types.ts +20 -1
package/dist/esm/types.d.ts
CHANGED
|
@@ -89,7 +89,26 @@ export type NonEmptyArray<T> = [T, ...Array<T>];
|
|
|
89
89
|
* Utility type for a Transaction with at least one mutation
|
|
90
90
|
* This is used internally by the Transaction.commit method
|
|
91
91
|
*/
|
|
92
|
-
export type TransactionWithMutations<T extends object = Record<string, unknown>, TOperation extends OperationType = OperationType> = Transaction<T
|
|
92
|
+
export type TransactionWithMutations<T extends object = Record<string, unknown>, TOperation extends OperationType = OperationType> = Omit<Transaction<T>, `mutations`> & {
|
|
93
|
+
/**
|
|
94
|
+
* We must omit the `mutations` property from `Transaction<T>` before intersecting
|
|
95
|
+
* because TypeScript intersects property types when the same property appears on
|
|
96
|
+
* both sides of an intersection.
|
|
97
|
+
*
|
|
98
|
+
* Without `Omit`:
|
|
99
|
+
* - `Transaction<T>` has `mutations: Array<PendingMutation<T>>`
|
|
100
|
+
* - The intersection would create: `Array<PendingMutation<T>> & NonEmptyArray<PendingMutation<T, TOperation>>`
|
|
101
|
+
* - When mapping over this array, TypeScript widens `TOperation` from the specific literal
|
|
102
|
+
* (e.g., `"delete"`) to the union `OperationType` (`"insert" | "update" | "delete"`)
|
|
103
|
+
* - This causes `PendingMutation<T, OperationType>` to evaluate the conditional type
|
|
104
|
+
* `original: TOperation extends 'insert' ? {} : T` as `{} | T` instead of just `T`
|
|
105
|
+
*
|
|
106
|
+
* With `Omit`:
|
|
107
|
+
* - We remove `mutations` from `Transaction<T>` first
|
|
108
|
+
* - Then add back `mutations: NonEmptyArray<PendingMutation<T, TOperation>>`
|
|
109
|
+
* - TypeScript can properly narrow `TOperation` to the specific literal type
|
|
110
|
+
* - This ensures `mutation.original` is correctly typed as `T` (not `{} | T`) when mapping
|
|
111
|
+
*/
|
|
93
112
|
mutations: NonEmptyArray<PendingMutation<T, TOperation>>;
|
|
94
113
|
};
|
|
95
114
|
export interface TransactionConfig<T extends object = Record<string, unknown>> {
|
package/package.json
CHANGED
|
@@ -2,61 +2,26 @@ import { Func, PropRef, Value } from "../ir.js"
|
|
|
2
2
|
import type { BasicExpression, OrderBy } from "../ir.js"
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
* These are the only functions that can be used in WHERE clauses
|
|
7
|
-
* that are pushed down to collection subscriptions for index optimization.
|
|
8
|
-
*/
|
|
9
|
-
export const SUPPORTED_COLLECTION_FUNCS = new Set([
|
|
10
|
-
`eq`,
|
|
11
|
-
`gt`,
|
|
12
|
-
`lt`,
|
|
13
|
-
`gte`,
|
|
14
|
-
`lte`,
|
|
15
|
-
`and`,
|
|
16
|
-
`or`,
|
|
17
|
-
`in`,
|
|
18
|
-
`isNull`,
|
|
19
|
-
`isUndefined`,
|
|
20
|
-
`not`,
|
|
21
|
-
])
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Determines if a WHERE clause can be converted to collection-compatible BasicExpression format.
|
|
25
|
-
* This checks if the expression only uses functions supported by the collection index system.
|
|
5
|
+
* Normalizes a WHERE clause expression by removing table aliases from property references.
|
|
26
6
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
): boolean {
|
|
33
|
-
const tpe = whereClause.type
|
|
34
|
-
if (tpe === `func`) {
|
|
35
|
-
// Check if this function is supported
|
|
36
|
-
if (!SUPPORTED_COLLECTION_FUNCS.has(whereClause.name)) {
|
|
37
|
-
return false
|
|
38
|
-
}
|
|
39
|
-
// Recursively check all arguments
|
|
40
|
-
return whereClause.args.every((arg) =>
|
|
41
|
-
isConvertibleToCollectionFilter(arg as BasicExpression<boolean>)
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
|
-
return [`val`, `ref`].includes(tpe)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Converts a WHERE clause to BasicExpression format compatible with collection indexes.
|
|
49
|
-
* This function creates proper BasicExpression class instances that the collection
|
|
50
|
-
* index system can understand.
|
|
7
|
+
* This function recursively traverses an expression tree and creates new BasicExpression
|
|
8
|
+
* instances with normalized paths. The main transformation is removing the collection alias
|
|
9
|
+
* from property reference paths (e.g., `['user', 'id']` becomes `['id']` when `collectionAlias`
|
|
10
|
+
* is `'user'`), which is needed when converting query-level expressions to collection-level
|
|
11
|
+
* expressions for subscriptions.
|
|
51
12
|
*
|
|
52
|
-
* @param whereClause - The WHERE clause to
|
|
53
|
-
* @param collectionAlias - The alias of the collection being filtered
|
|
54
|
-
* @returns
|
|
13
|
+
* @param whereClause - The WHERE clause expression to normalize
|
|
14
|
+
* @param collectionAlias - The alias of the collection being filtered (to strip from paths)
|
|
15
|
+
* @returns A new BasicExpression with normalized paths
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // Input: ref with path ['user', 'id'] where collectionAlias is 'user'
|
|
19
|
+
* // Output: ref with path ['id']
|
|
55
20
|
*/
|
|
56
|
-
export function
|
|
21
|
+
export function normalizeExpressionPaths(
|
|
57
22
|
whereClause: BasicExpression<boolean>,
|
|
58
23
|
collectionAlias: string
|
|
59
|
-
): BasicExpression<boolean>
|
|
24
|
+
): BasicExpression<boolean> {
|
|
60
25
|
const tpe = whereClause.type
|
|
61
26
|
if (tpe === `val`) {
|
|
62
27
|
return new Value(whereClause.value)
|
|
@@ -74,42 +39,29 @@ export function convertToBasicExpression(
|
|
|
74
39
|
// Fallback for non-array paths
|
|
75
40
|
return new PropRef(Array.isArray(path) ? path : [String(path)])
|
|
76
41
|
} else {
|
|
77
|
-
// Check if this function is supported
|
|
78
|
-
if (!SUPPORTED_COLLECTION_FUNCS.has(whereClause.name)) {
|
|
79
|
-
return null
|
|
80
|
-
}
|
|
81
42
|
// Recursively convert all arguments
|
|
82
43
|
const args: Array<BasicExpression> = []
|
|
83
44
|
for (const arg of whereClause.args) {
|
|
84
|
-
const convertedArg =
|
|
45
|
+
const convertedArg = normalizeExpressionPaths(
|
|
85
46
|
arg as BasicExpression<boolean>,
|
|
86
47
|
collectionAlias
|
|
87
48
|
)
|
|
88
|
-
if (convertedArg == null) {
|
|
89
|
-
return null
|
|
90
|
-
}
|
|
91
49
|
args.push(convertedArg)
|
|
92
50
|
}
|
|
93
51
|
return new Func(whereClause.name, args)
|
|
94
52
|
}
|
|
95
53
|
}
|
|
96
54
|
|
|
97
|
-
export function
|
|
55
|
+
export function normalizeOrderByPaths(
|
|
98
56
|
orderBy: OrderBy,
|
|
99
57
|
collectionAlias: string
|
|
100
58
|
): OrderBy {
|
|
101
59
|
const normalizedOrderBy = orderBy.map((clause) => {
|
|
102
|
-
const basicExp =
|
|
60
|
+
const basicExp = normalizeExpressionPaths(
|
|
103
61
|
clause.expression,
|
|
104
62
|
collectionAlias
|
|
105
63
|
)
|
|
106
64
|
|
|
107
|
-
if (!basicExp) {
|
|
108
|
-
throw new Error(
|
|
109
|
-
`Failed to convert orderBy expression to a basic expression: ${clause.expression}`
|
|
110
|
-
)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
65
|
return {
|
|
114
66
|
...clause,
|
|
115
67
|
expression: basicExp,
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { MultiSet } from "@tanstack/db-ivm"
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
normalizeExpressionPaths,
|
|
4
|
+
normalizeOrderByPaths,
|
|
5
5
|
} from "../compiler/expressions.js"
|
|
6
|
-
import { WhereClauseConversionError } from "../../errors.js"
|
|
7
6
|
import type { MultiSetArray, RootStreamBuilder } from "@tanstack/db-ivm"
|
|
8
7
|
import type { Collection } from "../../collection/index.js"
|
|
9
8
|
import type { ChangeMessage } from "../../types.js"
|
|
@@ -41,13 +40,8 @@ export class CollectionSubscriber<
|
|
|
41
40
|
const whereClause = this.getWhereClauseForAlias()
|
|
42
41
|
|
|
43
42
|
if (whereClause) {
|
|
44
|
-
const whereExpression =
|
|
45
|
-
|
|
46
|
-
if (whereExpression) {
|
|
47
|
-
return this.subscribeToChanges(whereExpression)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
throw new WhereClauseConversionError(this.collectionId, this.alias)
|
|
43
|
+
const whereExpression = normalizeExpressionPaths(whereClause, this.alias)
|
|
44
|
+
return this.subscribeToChanges(whereExpression)
|
|
51
45
|
}
|
|
52
46
|
|
|
53
47
|
return this.subscribeToChanges()
|
|
@@ -199,10 +193,7 @@ export class CollectionSubscriber<
|
|
|
199
193
|
subscription.setOrderByIndex(index)
|
|
200
194
|
|
|
201
195
|
// Normalize the orderBy clauses such that the references are relative to the collection
|
|
202
|
-
const normalizedOrderBy =
|
|
203
|
-
orderBy,
|
|
204
|
-
this.alias
|
|
205
|
-
)
|
|
196
|
+
const normalizedOrderBy = normalizeOrderByPaths(orderBy, this.alias)
|
|
206
197
|
|
|
207
198
|
// Load the first `offset + limit` values from the index
|
|
208
199
|
// i.e. the K items from the collection that fall into the requested range: [offset, offset + limit[
|
|
@@ -289,10 +280,7 @@ export class CollectionSubscriber<
|
|
|
289
280
|
: biggestSentRow
|
|
290
281
|
|
|
291
282
|
// Normalize the orderBy clauses such that the references are relative to the collection
|
|
292
|
-
const normalizedOrderBy =
|
|
293
|
-
orderBy,
|
|
294
|
-
this.alias
|
|
295
|
-
)
|
|
283
|
+
const normalizedOrderBy = normalizeOrderByPaths(orderBy, this.alias)
|
|
296
284
|
|
|
297
285
|
// Take the `n` items after the biggest sent value
|
|
298
286
|
subscription.requestLimitedSnapshot({
|
package/src/query/optimizer.ts
CHANGED
|
@@ -131,7 +131,6 @@ import {
|
|
|
131
131
|
getWhereExpression,
|
|
132
132
|
isResidualWhere,
|
|
133
133
|
} from "./ir.js"
|
|
134
|
-
import { isConvertibleToCollectionFilter } from "./compiler/expressions.js"
|
|
135
134
|
import type { BasicExpression, From, QueryIR, Select, Where } from "./ir.js"
|
|
136
135
|
|
|
137
136
|
/**
|
|
@@ -248,14 +247,10 @@ function extractSourceWhereClauses(
|
|
|
248
247
|
const groupedClauses = groupWhereClauses(analyzedClauses)
|
|
249
248
|
|
|
250
249
|
// Only include single-source clauses that reference collections directly
|
|
251
|
-
// and can be converted to BasicExpression format for collection indexes
|
|
252
250
|
for (const [sourceAlias, whereClause] of groupedClauses.singleSource) {
|
|
253
251
|
// Check if this source alias corresponds to a collection reference
|
|
254
252
|
if (isCollectionReference(query, sourceAlias)) {
|
|
255
|
-
|
|
256
|
-
if (isConvertibleToCollectionFilter(whereClause)) {
|
|
257
|
-
sourceWhereClauses.set(sourceAlias, whereClause)
|
|
258
|
-
}
|
|
253
|
+
sourceWhereClauses.set(sourceAlias, whereClause)
|
|
259
254
|
}
|
|
260
255
|
}
|
|
261
256
|
|
package/src/types.ts
CHANGED
|
@@ -139,7 +139,26 @@ export type NonEmptyArray<T> = [T, ...Array<T>]
|
|
|
139
139
|
export type TransactionWithMutations<
|
|
140
140
|
T extends object = Record<string, unknown>,
|
|
141
141
|
TOperation extends OperationType = OperationType,
|
|
142
|
-
> = Transaction<T
|
|
142
|
+
> = Omit<Transaction<T>, `mutations`> & {
|
|
143
|
+
/**
|
|
144
|
+
* We must omit the `mutations` property from `Transaction<T>` before intersecting
|
|
145
|
+
* because TypeScript intersects property types when the same property appears on
|
|
146
|
+
* both sides of an intersection.
|
|
147
|
+
*
|
|
148
|
+
* Without `Omit`:
|
|
149
|
+
* - `Transaction<T>` has `mutations: Array<PendingMutation<T>>`
|
|
150
|
+
* - The intersection would create: `Array<PendingMutation<T>> & NonEmptyArray<PendingMutation<T, TOperation>>`
|
|
151
|
+
* - When mapping over this array, TypeScript widens `TOperation` from the specific literal
|
|
152
|
+
* (e.g., `"delete"`) to the union `OperationType` (`"insert" | "update" | "delete"`)
|
|
153
|
+
* - This causes `PendingMutation<T, OperationType>` to evaluate the conditional type
|
|
154
|
+
* `original: TOperation extends 'insert' ? {} : T` as `{} | T` instead of just `T`
|
|
155
|
+
*
|
|
156
|
+
* With `Omit`:
|
|
157
|
+
* - We remove `mutations` from `Transaction<T>` first
|
|
158
|
+
* - Then add back `mutations: NonEmptyArray<PendingMutation<T, TOperation>>`
|
|
159
|
+
* - TypeScript can properly narrow `TOperation` to the specific literal type
|
|
160
|
+
* - This ensures `mutation.original` is correctly typed as `T` (not `{} | T`) when mapping
|
|
161
|
+
*/
|
|
143
162
|
mutations: NonEmptyArray<PendingMutation<T, TOperation>>
|
|
144
163
|
}
|
|
145
164
|
|