@tanstack/db 0.5.10 → 0.5.12
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/SortedMap.cjs +40 -26
- package/dist/cjs/SortedMap.cjs.map +1 -1
- package/dist/cjs/SortedMap.d.cts +10 -15
- package/dist/cjs/collection/change-events.cjs +1 -1
- package/dist/cjs/collection/change-events.cjs.map +1 -1
- package/dist/cjs/collection/changes.cjs.map +1 -1
- package/dist/cjs/collection/events.cjs.map +1 -1
- package/dist/cjs/collection/events.d.cts +12 -4
- package/dist/cjs/collection/index.cjs +2 -1
- package/dist/cjs/collection/index.cjs.map +1 -1
- package/dist/cjs/collection/indexes.cjs.map +1 -1
- package/dist/cjs/collection/lifecycle.cjs.map +1 -1
- package/dist/cjs/collection/mutations.cjs +5 -2
- package/dist/cjs/collection/mutations.cjs.map +1 -1
- package/dist/cjs/collection/state.cjs +6 -5
- package/dist/cjs/collection/state.cjs.map +1 -1
- package/dist/cjs/collection/state.d.cts +4 -1
- package/dist/cjs/collection/subscription.cjs +60 -53
- package/dist/cjs/collection/subscription.cjs.map +1 -1
- package/dist/cjs/collection/subscription.d.cts +18 -4
- package/dist/cjs/collection/sync.cjs.map +1 -1
- package/dist/cjs/errors.cjs +9 -0
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/errors.d.cts +3 -0
- package/dist/cjs/event-emitter.cjs.map +1 -1
- package/dist/cjs/index.cjs +4 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +2 -1
- package/dist/cjs/indexes/auto-index.cjs.map +1 -1
- package/dist/cjs/indexes/base-index.cjs.map +1 -1
- package/dist/cjs/indexes/btree-index.cjs +8 -6
- package/dist/cjs/indexes/btree-index.cjs.map +1 -1
- package/dist/cjs/indexes/lazy-index.cjs.map +1 -1
- package/dist/cjs/indexes/reverse-index.cjs.map +1 -1
- package/dist/cjs/local-only.cjs.map +1 -1
- package/dist/cjs/local-storage.cjs.map +1 -1
- package/dist/cjs/optimistic-action.cjs.map +1 -1
- package/dist/cjs/paced-mutations.cjs.map +1 -1
- package/dist/cjs/proxy.cjs.map +1 -1
- package/dist/cjs/query/builder/functions.cjs.map +1 -1
- package/dist/cjs/query/builder/index.cjs.map +1 -1
- package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
- package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
- package/dist/cjs/query/compiler/expressions.cjs.map +1 -1
- package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/compiler/joins.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.cjs +91 -38
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.d.cts +6 -2
- package/dist/cjs/query/compiler/select.cjs.map +1 -1
- package/dist/cjs/query/expression-helpers.cjs.map +1 -1
- package/dist/cjs/query/index.d.cts +1 -1
- package/dist/cjs/query/ir.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-registry.cjs.map +1 -1
- package/dist/cjs/query/live/collection-subscriber.cjs +30 -15
- package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
- package/dist/cjs/query/live/internal.cjs.map +1 -1
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/query/optimizer.cjs.map +1 -1
- package/dist/cjs/query/predicate-utils.cjs +19 -2
- package/dist/cjs/query/predicate-utils.cjs.map +1 -1
- package/dist/cjs/query/predicate-utils.d.cts +32 -1
- package/dist/cjs/query/subset-dedupe.cjs.map +1 -1
- package/dist/cjs/scheduler.cjs.map +1 -1
- package/dist/cjs/strategies/debounceStrategy.cjs.map +1 -1
- package/dist/cjs/strategies/queueStrategy.cjs.map +1 -1
- package/dist/cjs/strategies/throttleStrategy.cjs.map +1 -1
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/types.d.cts +43 -5
- package/dist/cjs/utils/browser-polyfills.cjs.map +1 -1
- package/dist/cjs/utils/btree.cjs.map +1 -1
- package/dist/cjs/utils/comparison.cjs.map +1 -1
- package/dist/cjs/utils/cursor.cjs +39 -0
- package/dist/cjs/utils/cursor.cjs.map +1 -0
- package/dist/cjs/utils/cursor.d.cts +18 -0
- package/dist/cjs/utils/index-optimization.cjs.map +1 -1
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/esm/SortedMap.d.ts +10 -15
- package/dist/esm/SortedMap.js +40 -26
- package/dist/esm/SortedMap.js.map +1 -1
- package/dist/esm/collection/change-events.js +1 -1
- package/dist/esm/collection/change-events.js.map +1 -1
- package/dist/esm/collection/changes.js.map +1 -1
- package/dist/esm/collection/events.d.ts +12 -4
- package/dist/esm/collection/events.js.map +1 -1
- package/dist/esm/collection/index.js +2 -1
- package/dist/esm/collection/index.js.map +1 -1
- package/dist/esm/collection/indexes.js.map +1 -1
- package/dist/esm/collection/lifecycle.js.map +1 -1
- package/dist/esm/collection/mutations.js +6 -3
- package/dist/esm/collection/mutations.js.map +1 -1
- package/dist/esm/collection/state.d.ts +4 -1
- package/dist/esm/collection/state.js +6 -5
- package/dist/esm/collection/state.js.map +1 -1
- package/dist/esm/collection/subscription.d.ts +18 -4
- package/dist/esm/collection/subscription.js +61 -54
- package/dist/esm/collection/subscription.js.map +1 -1
- package/dist/esm/collection/sync.js.map +1 -1
- package/dist/esm/errors.d.ts +3 -0
- package/dist/esm/errors.js +9 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/event-emitter.js.map +1 -1
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +6 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/indexes/auto-index.js.map +1 -1
- package/dist/esm/indexes/base-index.js.map +1 -1
- package/dist/esm/indexes/btree-index.js +8 -6
- package/dist/esm/indexes/btree-index.js.map +1 -1
- package/dist/esm/indexes/lazy-index.js.map +1 -1
- package/dist/esm/indexes/reverse-index.js.map +1 -1
- package/dist/esm/local-only.js.map +1 -1
- package/dist/esm/local-storage.js.map +1 -1
- package/dist/esm/optimistic-action.js.map +1 -1
- package/dist/esm/paced-mutations.js.map +1 -1
- package/dist/esm/proxy.js.map +1 -1
- package/dist/esm/query/builder/functions.js.map +1 -1
- package/dist/esm/query/builder/index.js.map +1 -1
- package/dist/esm/query/builder/ref-proxy.js.map +1 -1
- package/dist/esm/query/compiler/evaluators.js.map +1 -1
- package/dist/esm/query/compiler/expressions.js.map +1 -1
- package/dist/esm/query/compiler/group-by.js.map +1 -1
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/compiler/joins.js.map +1 -1
- package/dist/esm/query/compiler/order-by.d.ts +6 -2
- package/dist/esm/query/compiler/order-by.js +91 -38
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/compiler/select.js.map +1 -1
- package/dist/esm/query/expression-helpers.js.map +1 -1
- package/dist/esm/query/index.d.ts +1 -1
- package/dist/esm/query/ir.js.map +1 -1
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/collection-registry.js.map +1 -1
- package/dist/esm/query/live/collection-subscriber.js +30 -15
- package/dist/esm/query/live/collection-subscriber.js.map +1 -1
- package/dist/esm/query/live/internal.js.map +1 -1
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/query/optimizer.js.map +1 -1
- package/dist/esm/query/predicate-utils.d.ts +32 -1
- package/dist/esm/query/predicate-utils.js +19 -2
- package/dist/esm/query/predicate-utils.js.map +1 -1
- package/dist/esm/query/subset-dedupe.js.map +1 -1
- package/dist/esm/scheduler.js.map +1 -1
- package/dist/esm/strategies/debounceStrategy.js.map +1 -1
- package/dist/esm/strategies/queueStrategy.js.map +1 -1
- package/dist/esm/strategies/throttleStrategy.js.map +1 -1
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +43 -5
- package/dist/esm/utils/browser-polyfills.js.map +1 -1
- package/dist/esm/utils/btree.js.map +1 -1
- package/dist/esm/utils/comparison.js.map +1 -1
- package/dist/esm/utils/cursor.d.ts +18 -0
- package/dist/esm/utils/cursor.js +39 -0
- package/dist/esm/utils/cursor.js.map +1 -0
- package/dist/esm/utils/index-optimization.js.map +1 -1
- package/dist/esm/utils.js.map +1 -1
- package/package.json +30 -28
- package/src/SortedMap.ts +50 -31
- package/src/collection/change-events.ts +23 -21
- package/src/collection/changes.ts +12 -12
- package/src/collection/events.ts +20 -10
- package/src/collection/index.ts +47 -46
- package/src/collection/indexes.ts +14 -14
- package/src/collection/lifecycle.ts +16 -16
- package/src/collection/mutations.ts +25 -20
- package/src/collection/state.ts +43 -36
- package/src/collection/subscription.ts +114 -83
- package/src/collection/sync.ts +13 -13
- package/src/duplicate-instance-check.ts +1 -1
- package/src/errors.ts +49 -40
- package/src/event-emitter.ts +5 -5
- package/src/index.ts +21 -20
- package/src/indexes/auto-index.ts +11 -11
- package/src/indexes/base-index.ts +13 -13
- package/src/indexes/btree-index.ts +21 -17
- package/src/indexes/index-options.ts +3 -3
- package/src/indexes/lazy-index.ts +8 -8
- package/src/indexes/reverse-index.ts +5 -5
- package/src/local-only.ts +12 -12
- package/src/local-storage.ts +17 -17
- package/src/optimistic-action.ts +5 -5
- package/src/paced-mutations.ts +6 -6
- package/src/proxy.ts +43 -43
- package/src/query/builder/functions.ts +28 -28
- package/src/query/builder/index.ts +22 -22
- package/src/query/builder/ref-proxy.ts +4 -4
- package/src/query/builder/types.ts +8 -8
- package/src/query/compiler/evaluators.ts +9 -9
- package/src/query/compiler/expressions.ts +6 -6
- package/src/query/compiler/group-by.ts +24 -24
- package/src/query/compiler/index.ts +44 -44
- package/src/query/compiler/joins.ts +37 -37
- package/src/query/compiler/order-by.ts +170 -77
- package/src/query/compiler/select.ts +13 -13
- package/src/query/compiler/types.ts +2 -2
- package/src/query/expression-helpers.ts +16 -16
- package/src/query/index.ts +10 -9
- package/src/query/ir.ts +13 -13
- package/src/query/live/collection-config-builder.ts +53 -53
- package/src/query/live/collection-registry.ts +6 -6
- package/src/query/live/collection-subscriber.ts +87 -48
- package/src/query/live/internal.ts +1 -1
- package/src/query/live/types.ts +4 -4
- package/src/query/live-query-collection.ts +15 -15
- package/src/query/optimizer.ts +29 -29
- package/src/query/predicate-utils.ts +105 -50
- package/src/query/subset-dedupe.ts +6 -6
- package/src/scheduler.ts +3 -3
- package/src/strategies/debounceStrategy.ts +6 -6
- package/src/strategies/index.ts +4 -4
- package/src/strategies/queueStrategy.ts +5 -5
- package/src/strategies/throttleStrategy.ts +6 -6
- package/src/strategies/types.ts +2 -2
- package/src/transactions.ts +9 -9
- package/src/types.ts +51 -12
- package/src/utils/array-utils.ts +1 -1
- package/src/utils/browser-polyfills.ts +2 -2
- package/src/utils/btree.ts +22 -22
- package/src/utils/comparison.ts +3 -3
- package/src/utils/cursor.ts +78 -0
- package/src/utils/index-optimization.ts +14 -14
- package/src/utils.ts +4 -4
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { MultiSet } from
|
|
1
|
+
import { MultiSet } from '@tanstack/db-ivm'
|
|
2
2
|
import {
|
|
3
3
|
normalizeExpressionPaths,
|
|
4
4
|
normalizeOrderByPaths,
|
|
5
|
-
} from
|
|
6
|
-
import type { MultiSetArray, RootStreamBuilder } from
|
|
7
|
-
import type { Collection } from
|
|
8
|
-
import type { ChangeMessage } from
|
|
9
|
-
import type { Context, GetResult } from
|
|
10
|
-
import type { BasicExpression } from
|
|
11
|
-
import type { OrderByOptimizationInfo } from
|
|
12
|
-
import type { CollectionConfigBuilder } from
|
|
13
|
-
import type { CollectionSubscription } from
|
|
5
|
+
} from '../compiler/expressions.js'
|
|
6
|
+
import type { MultiSetArray, RootStreamBuilder } from '@tanstack/db-ivm'
|
|
7
|
+
import type { Collection } from '../../collection/index.js'
|
|
8
|
+
import type { ChangeMessage } from '../../types.js'
|
|
9
|
+
import type { Context, GetResult } from '../builder/types.js'
|
|
10
|
+
import type { BasicExpression } from '../ir.js'
|
|
11
|
+
import type { OrderByOptimizationInfo } from '../compiler/order-by.js'
|
|
12
|
+
import type { CollectionConfigBuilder } from './collection-config-builder.js'
|
|
13
|
+
import type { CollectionSubscription } from '../../collection/subscription.js'
|
|
14
14
|
|
|
15
15
|
const loadMoreCallbackSymbol = Symbol.for(
|
|
16
|
-
`@tanstack/db.collection-config-builder
|
|
16
|
+
`@tanstack/db.collection-config-builder`,
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
export class CollectionSubscriber<
|
|
@@ -33,7 +33,7 @@ export class CollectionSubscriber<
|
|
|
33
33
|
private alias: string,
|
|
34
34
|
private collectionId: string,
|
|
35
35
|
private collection: Collection,
|
|
36
|
-
private collectionConfigBuilder: CollectionConfigBuilder<TContext, TResult
|
|
36
|
+
private collectionConfigBuilder: CollectionConfigBuilder<TContext, TResult>,
|
|
37
37
|
) {}
|
|
38
38
|
|
|
39
39
|
subscribe(): CollectionSubscription {
|
|
@@ -53,17 +53,17 @@ export class CollectionSubscriber<
|
|
|
53
53
|
if (orderByInfo) {
|
|
54
54
|
subscription = this.subscribeToOrderedChanges(
|
|
55
55
|
whereExpression,
|
|
56
|
-
orderByInfo
|
|
56
|
+
orderByInfo,
|
|
57
57
|
)
|
|
58
58
|
} else {
|
|
59
59
|
// If the source alias is lazy then we should not include the initial state
|
|
60
60
|
const includeInitialState = !this.collectionConfigBuilder.isLazyAlias(
|
|
61
|
-
this.alias
|
|
61
|
+
this.alias,
|
|
62
62
|
)
|
|
63
63
|
|
|
64
64
|
subscription = this.subscribeToMatchingChanges(
|
|
65
65
|
whereExpression,
|
|
66
|
-
includeInitialState
|
|
66
|
+
includeInitialState,
|
|
67
67
|
)
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -79,7 +79,7 @@ export class CollectionSubscriber<
|
|
|
79
79
|
resolve: resolve!,
|
|
80
80
|
})
|
|
81
81
|
this.collectionConfigBuilder.liveQueryCollection!._sync.trackLoadPromise(
|
|
82
|
-
promise
|
|
82
|
+
promise,
|
|
83
83
|
)
|
|
84
84
|
}
|
|
85
85
|
}
|
|
@@ -120,14 +120,14 @@ export class CollectionSubscriber<
|
|
|
120
120
|
// currentSyncState is always defined when subscribe() is called
|
|
121
121
|
// (called during sync session setup)
|
|
122
122
|
this.collectionConfigBuilder.currentSyncState!.unsubscribeCallbacks.add(
|
|
123
|
-
unsubscribe
|
|
123
|
+
unsubscribe,
|
|
124
124
|
)
|
|
125
125
|
return subscription
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
private sendChangesToPipeline(
|
|
129
129
|
changes: Iterable<ChangeMessage<any, string | number>>,
|
|
130
|
-
callback?: () => boolean
|
|
130
|
+
callback?: () => boolean,
|
|
131
131
|
) {
|
|
132
132
|
// currentSyncState and input are always defined when this method is called
|
|
133
133
|
// (only called from active subscriptions during a sync session)
|
|
@@ -136,7 +136,7 @@ export class CollectionSubscriber<
|
|
|
136
136
|
const sentChanges = sendChangesToInput(
|
|
137
137
|
input,
|
|
138
138
|
changes,
|
|
139
|
-
this.collection.config.getKey
|
|
139
|
+
this.collection.config.getKey,
|
|
140
140
|
)
|
|
141
141
|
|
|
142
142
|
// Do not provide the callback that loads more data
|
|
@@ -154,10 +154,10 @@ export class CollectionSubscriber<
|
|
|
154
154
|
|
|
155
155
|
private subscribeToMatchingChanges(
|
|
156
156
|
whereExpression: BasicExpression<boolean> | undefined,
|
|
157
|
-
includeInitialState: boolean = false
|
|
157
|
+
includeInitialState: boolean = false,
|
|
158
158
|
) {
|
|
159
159
|
const sendChanges = (
|
|
160
|
-
changes: Array<ChangeMessage<any, string | number
|
|
160
|
+
changes: Array<ChangeMessage<any, string | number>>,
|
|
161
161
|
) => {
|
|
162
162
|
this.sendChangesToPipeline(changes)
|
|
163
163
|
}
|
|
@@ -172,12 +172,12 @@ export class CollectionSubscriber<
|
|
|
172
172
|
|
|
173
173
|
private subscribeToOrderedChanges(
|
|
174
174
|
whereExpression: BasicExpression<boolean> | undefined,
|
|
175
|
-
orderByInfo: OrderByOptimizationInfo
|
|
175
|
+
orderByInfo: OrderByOptimizationInfo,
|
|
176
176
|
) {
|
|
177
177
|
const { orderBy, offset, limit, index } = orderByInfo
|
|
178
178
|
|
|
179
179
|
const sendChangesInRange = (
|
|
180
|
-
changes: Iterable<ChangeMessage<any, string | number
|
|
180
|
+
changes: Iterable<ChangeMessage<any, string | number>>,
|
|
181
181
|
) => {
|
|
182
182
|
// Split live updates into a delete of the old value and an insert of the new value
|
|
183
183
|
const splittedChanges = splitUpdates(changes)
|
|
@@ -190,17 +190,41 @@ export class CollectionSubscriber<
|
|
|
190
190
|
whereExpression,
|
|
191
191
|
})
|
|
192
192
|
|
|
193
|
-
|
|
193
|
+
// Listen for truncate events to reset cursor tracking state
|
|
194
|
+
// This ensures that after a must-refetch/truncate, we don't use stale cursor data
|
|
195
|
+
const truncateUnsubscribe = this.collection.on(`truncate`, () => {
|
|
196
|
+
this.biggest = undefined
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
// Clean up truncate listener when subscription is unsubscribed
|
|
200
|
+
subscription.on(`unsubscribed`, () => {
|
|
201
|
+
truncateUnsubscribe()
|
|
202
|
+
})
|
|
194
203
|
|
|
195
204
|
// Normalize the orderBy clauses such that the references are relative to the collection
|
|
196
205
|
const normalizedOrderBy = normalizeOrderByPaths(orderBy, this.alias)
|
|
197
206
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
207
|
+
if (index) {
|
|
208
|
+
// We have an index on the first orderBy column - use lazy loading optimization
|
|
209
|
+
// This works for both single-column and multi-column orderBy:
|
|
210
|
+
// - Single-column: index provides exact ordering
|
|
211
|
+
// - Multi-column: index provides ordering on first column, secondary sort in memory
|
|
212
|
+
subscription.setOrderByIndex(index)
|
|
213
|
+
|
|
214
|
+
// Load the first `offset + limit` values from the index
|
|
215
|
+
// i.e. the K items from the collection that fall into the requested range: [offset, offset + limit[
|
|
216
|
+
subscription.requestLimitedSnapshot({
|
|
217
|
+
limit: offset + limit,
|
|
218
|
+
orderBy: normalizedOrderBy,
|
|
219
|
+
})
|
|
220
|
+
} else {
|
|
221
|
+
// No index available (e.g., non-ref expression): pass orderBy/limit to loadSubset
|
|
222
|
+
// so the sync layer can optimize if the backend supports it
|
|
223
|
+
subscription.requestSnapshot({
|
|
224
|
+
orderBy: normalizedOrderBy,
|
|
225
|
+
limit: offset + limit,
|
|
226
|
+
})
|
|
227
|
+
}
|
|
204
228
|
|
|
205
229
|
return subscription
|
|
206
230
|
}
|
|
@@ -220,11 +244,10 @@ export class CollectionSubscriber<
|
|
|
220
244
|
const { dataNeeded } = orderByInfo
|
|
221
245
|
|
|
222
246
|
if (!dataNeeded) {
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
)
|
|
247
|
+
// dataNeeded is not set when there's no index (e.g., non-ref expression).
|
|
248
|
+
// In this case, we've already loaded all data via requestSnapshot
|
|
249
|
+
// and don't need to lazily load more.
|
|
250
|
+
return true
|
|
228
251
|
}
|
|
229
252
|
|
|
230
253
|
// `dataNeeded` probes the orderBy operator to see if it needs more data
|
|
@@ -238,7 +261,7 @@ export class CollectionSubscriber<
|
|
|
238
261
|
|
|
239
262
|
private sendChangesToPipelineWithTracking(
|
|
240
263
|
changes: Iterable<ChangeMessage<any, string | number>>,
|
|
241
|
-
subscription: CollectionSubscription
|
|
264
|
+
subscription: CollectionSubscription,
|
|
242
265
|
) {
|
|
243
266
|
const orderByInfo = this.getOrderByInfo()
|
|
244
267
|
if (!orderByInfo) {
|
|
@@ -262,7 +285,7 @@ export class CollectionSubscriber<
|
|
|
262
285
|
|
|
263
286
|
this.sendChangesToPipeline(
|
|
264
287
|
trackedChanges,
|
|
265
|
-
subscriptionWithLoader[loadMoreCallbackSymbol]
|
|
288
|
+
subscriptionWithLoader[loadMoreCallbackSymbol],
|
|
266
289
|
)
|
|
267
290
|
}
|
|
268
291
|
|
|
@@ -273,20 +296,33 @@ export class CollectionSubscriber<
|
|
|
273
296
|
if (!orderByInfo) {
|
|
274
297
|
return
|
|
275
298
|
}
|
|
276
|
-
const { orderBy, valueExtractorForRawRow } = orderByInfo
|
|
299
|
+
const { orderBy, valueExtractorForRawRow, offset } = orderByInfo
|
|
277
300
|
const biggestSentRow = this.biggest
|
|
278
|
-
|
|
301
|
+
|
|
302
|
+
// Extract all orderBy column values from the biggest sent row
|
|
303
|
+
// For single-column: returns single value, for multi-column: returns array
|
|
304
|
+
const extractedValues = biggestSentRow
|
|
279
305
|
? valueExtractorForRawRow(biggestSentRow)
|
|
280
|
-
:
|
|
306
|
+
: undefined
|
|
307
|
+
|
|
308
|
+
// Normalize to array format for minValues
|
|
309
|
+
const minValues =
|
|
310
|
+
extractedValues !== undefined
|
|
311
|
+
? Array.isArray(extractedValues)
|
|
312
|
+
? extractedValues
|
|
313
|
+
: [extractedValues]
|
|
314
|
+
: undefined
|
|
281
315
|
|
|
282
316
|
// Normalize the orderBy clauses such that the references are relative to the collection
|
|
283
317
|
const normalizedOrderBy = normalizeOrderByPaths(orderBy, this.alias)
|
|
284
318
|
|
|
285
319
|
// Take the `n` items after the biggest sent value
|
|
320
|
+
// Pass the current window offset to ensure proper deduplication
|
|
286
321
|
subscription.requestLimitedSnapshot({
|
|
287
322
|
orderBy: normalizedOrderBy,
|
|
288
323
|
limit: n,
|
|
289
|
-
|
|
324
|
+
minValues,
|
|
325
|
+
offset,
|
|
290
326
|
})
|
|
291
327
|
}
|
|
292
328
|
|
|
@@ -312,13 +348,16 @@ export class CollectionSubscriber<
|
|
|
312
348
|
|
|
313
349
|
private *trackSentValues(
|
|
314
350
|
changes: Iterable<ChangeMessage<any, string | number>>,
|
|
315
|
-
comparator: (a: any, b: any) => number
|
|
351
|
+
comparator: (a: any, b: any) => number,
|
|
316
352
|
) {
|
|
317
353
|
for (const change of changes) {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
354
|
+
// Only track inserts/updates for cursor positioning, not deletes
|
|
355
|
+
if (change.type !== `delete`) {
|
|
356
|
+
if (!this.biggest) {
|
|
357
|
+
this.biggest = change.value
|
|
358
|
+
} else if (comparator(this.biggest, change.value) < 0) {
|
|
359
|
+
this.biggest = change.value
|
|
360
|
+
}
|
|
322
361
|
}
|
|
323
362
|
|
|
324
363
|
yield change
|
|
@@ -332,7 +371,7 @@ export class CollectionSubscriber<
|
|
|
332
371
|
function sendChangesToInput(
|
|
333
372
|
input: RootStreamBuilder<unknown>,
|
|
334
373
|
changes: Iterable<ChangeMessage>,
|
|
335
|
-
getKey: (item: ChangeMessage[`value`]) => any
|
|
374
|
+
getKey: (item: ChangeMessage[`value`]) => any,
|
|
336
375
|
): number {
|
|
337
376
|
const multiSetArray: MultiSetArray<unknown> = []
|
|
338
377
|
for (const change of changes) {
|
|
@@ -360,7 +399,7 @@ function* splitUpdates<
|
|
|
360
399
|
T extends object = Record<string, unknown>,
|
|
361
400
|
TKey extends string | number = string | number,
|
|
362
401
|
>(
|
|
363
|
-
changes: Iterable<ChangeMessage<T, TKey
|
|
402
|
+
changes: Iterable<ChangeMessage<T, TKey>>,
|
|
364
403
|
): Generator<ChangeMessage<T, TKey>> {
|
|
365
404
|
for (const change of changes) {
|
|
366
405
|
if (change.type === `update`) {
|
package/src/query/live/types.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { D2, RootStreamBuilder } from
|
|
1
|
+
import type { D2, RootStreamBuilder } from '@tanstack/db-ivm'
|
|
2
2
|
import type {
|
|
3
3
|
CollectionConfig,
|
|
4
4
|
ResultStream,
|
|
5
5
|
StringCollationConfig,
|
|
6
|
-
} from
|
|
7
|
-
import type { InitialQueryBuilder, QueryBuilder } from
|
|
8
|
-
import type { Context, GetResult } from
|
|
6
|
+
} from '../../types.js'
|
|
7
|
+
import type { InitialQueryBuilder, QueryBuilder } from '../builder/index.js'
|
|
8
|
+
import type { Context, GetResult } from '../builder/types.js'
|
|
9
9
|
|
|
10
10
|
export type Changes<T> = {
|
|
11
11
|
deletes: number
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { createCollection } from
|
|
2
|
-
import { CollectionConfigBuilder } from
|
|
1
|
+
import { createCollection } from '../collection/index.js'
|
|
2
|
+
import { CollectionConfigBuilder } from './live/collection-config-builder.js'
|
|
3
3
|
import {
|
|
4
4
|
getBuilderFromConfig,
|
|
5
5
|
registerCollectionBuilder,
|
|
6
|
-
} from
|
|
7
|
-
import type { LiveQueryCollectionUtils } from
|
|
8
|
-
import type { LiveQueryCollectionConfig } from
|
|
9
|
-
import type { InitialQueryBuilder, QueryBuilder } from
|
|
10
|
-
import type { Collection } from
|
|
6
|
+
} from './live/collection-registry.js'
|
|
7
|
+
import type { LiveQueryCollectionUtils } from './live/collection-config-builder.js'
|
|
8
|
+
import type { LiveQueryCollectionConfig } from './live/types.js'
|
|
9
|
+
import type { InitialQueryBuilder, QueryBuilder } from './builder/index.js'
|
|
10
|
+
import type { Collection } from '../collection/index.js'
|
|
11
11
|
import type {
|
|
12
12
|
CollectionConfig,
|
|
13
13
|
CollectionConfigSingleRowOption,
|
|
14
14
|
NonSingleResult,
|
|
15
15
|
SingleResult,
|
|
16
16
|
UtilsRecord,
|
|
17
|
-
} from
|
|
18
|
-
import type { Context, GetResult } from
|
|
17
|
+
} from '../types.js'
|
|
18
|
+
import type { Context, GetResult } from './builder/types.js'
|
|
19
19
|
|
|
20
20
|
type CollectionConfigForContext<
|
|
21
21
|
TContext extends Context,
|
|
@@ -63,7 +63,7 @@ export function liveQueryCollectionOptions<
|
|
|
63
63
|
TContext extends Context,
|
|
64
64
|
TResult extends object = GetResult<TContext>,
|
|
65
65
|
>(
|
|
66
|
-
config: LiveQueryCollectionConfig<TContext, TResult
|
|
66
|
+
config: LiveQueryCollectionConfig<TContext, TResult>,
|
|
67
67
|
): CollectionConfigForContext<TContext, TResult> & {
|
|
68
68
|
utils: LiveQueryCollectionUtils
|
|
69
69
|
} {
|
|
@@ -116,7 +116,7 @@ export function createLiveQueryCollection<
|
|
|
116
116
|
TContext extends Context,
|
|
117
117
|
TResult extends object = GetResult<TContext>,
|
|
118
118
|
>(
|
|
119
|
-
query: (q: InitialQueryBuilder) => QueryBuilder<TContext
|
|
119
|
+
query: (q: InitialQueryBuilder) => QueryBuilder<TContext>,
|
|
120
120
|
): CollectionForContext<TContext, TResult> & {
|
|
121
121
|
utils: LiveQueryCollectionUtils
|
|
122
122
|
}
|
|
@@ -127,7 +127,7 @@ export function createLiveQueryCollection<
|
|
|
127
127
|
TResult extends object = GetResult<TContext>,
|
|
128
128
|
TUtils extends UtilsRecord = {},
|
|
129
129
|
>(
|
|
130
|
-
config: LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils }
|
|
130
|
+
config: LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils },
|
|
131
131
|
): CollectionForContext<TContext, TResult> & {
|
|
132
132
|
utils: LiveQueryCollectionUtils & TUtils
|
|
133
133
|
}
|
|
@@ -140,7 +140,7 @@ export function createLiveQueryCollection<
|
|
|
140
140
|
>(
|
|
141
141
|
configOrQuery:
|
|
142
142
|
| (LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils })
|
|
143
|
-
| ((q: InitialQueryBuilder) => QueryBuilder<TContext>)
|
|
143
|
+
| ((q: InitialQueryBuilder) => QueryBuilder<TContext>),
|
|
144
144
|
): CollectionForContext<TContext, TResult> & {
|
|
145
145
|
utils: LiveQueryCollectionUtils & TUtils
|
|
146
146
|
} {
|
|
@@ -149,7 +149,7 @@ export function createLiveQueryCollection<
|
|
|
149
149
|
// Simple query function case
|
|
150
150
|
const config: LiveQueryCollectionConfig<TContext, TResult> = {
|
|
151
151
|
query: configOrQuery as (
|
|
152
|
-
q: InitialQueryBuilder
|
|
152
|
+
q: InitialQueryBuilder,
|
|
153
153
|
) => QueryBuilder<TContext>,
|
|
154
154
|
}
|
|
155
155
|
const options = liveQueryCollectionOptions<TContext, TResult>(config)
|
|
@@ -185,7 +185,7 @@ function bridgeToCreateCollection<
|
|
|
185
185
|
TResult extends object,
|
|
186
186
|
TUtils extends UtilsRecord = {},
|
|
187
187
|
>(
|
|
188
|
-
options: CollectionConfig<TResult> & { utils: TUtils }
|
|
188
|
+
options: CollectionConfig<TResult> & { utils: TUtils },
|
|
189
189
|
): Collection<TResult, string | number, TUtils> {
|
|
190
190
|
const collection = createCollection(options as any) as unknown as Collection<
|
|
191
191
|
TResult,
|
package/src/query/optimizer.ts
CHANGED
|
@@ -120,8 +120,8 @@
|
|
|
120
120
|
* transformed into a D2Mini pipeline.
|
|
121
121
|
*/
|
|
122
122
|
|
|
123
|
-
import { deepEquals } from
|
|
124
|
-
import { CannotCombineEmptyExpressionListError } from
|
|
123
|
+
import { deepEquals } from '../utils.js'
|
|
124
|
+
import { CannotCombineEmptyExpressionListError } from '../errors.js'
|
|
125
125
|
import {
|
|
126
126
|
CollectionRef as CollectionRefClass,
|
|
127
127
|
Func,
|
|
@@ -130,8 +130,8 @@ import {
|
|
|
130
130
|
createResidualWhere,
|
|
131
131
|
getWhereExpression,
|
|
132
132
|
isResidualWhere,
|
|
133
|
-
} from
|
|
134
|
-
import type { BasicExpression, From, QueryIR, Select, Where } from
|
|
133
|
+
} from './ir.js'
|
|
134
|
+
import type { BasicExpression, From, QueryIR, Select, Where } from './ir.js'
|
|
135
135
|
|
|
136
136
|
/**
|
|
137
137
|
* Represents a WHERE clause after source analysis
|
|
@@ -226,7 +226,7 @@ export function optimizeQuery(query: QueryIR): OptimizationResult {
|
|
|
226
226
|
* @returns Map of source aliases to their WHERE clauses
|
|
227
227
|
*/
|
|
228
228
|
function extractSourceWhereClauses(
|
|
229
|
-
query: QueryIR
|
|
229
|
+
query: QueryIR,
|
|
230
230
|
): Map<string, BasicExpression<boolean>> {
|
|
231
231
|
const sourceWhereClauses = new Map<string, BasicExpression<boolean>>()
|
|
232
232
|
|
|
@@ -240,7 +240,7 @@ function extractSourceWhereClauses(
|
|
|
240
240
|
|
|
241
241
|
// Analyze each WHERE clause to determine which sources it touches
|
|
242
242
|
const analyzedClauses = splitWhereClauses.map((clause) =>
|
|
243
|
-
analyzeWhereClause(clause)
|
|
243
|
+
analyzeWhereClause(clause),
|
|
244
244
|
)
|
|
245
245
|
|
|
246
246
|
// Group clauses by single-source vs multi-source
|
|
@@ -297,7 +297,7 @@ function applyRecursiveOptimization(query: QueryIR): QueryIR {
|
|
|
297
297
|
query.from.type === `queryRef`
|
|
298
298
|
? new QueryRefClass(
|
|
299
299
|
applyRecursiveOptimization(query.from.query),
|
|
300
|
-
query.from.alias
|
|
300
|
+
query.from.alias,
|
|
301
301
|
)
|
|
302
302
|
: query.from,
|
|
303
303
|
join: query.join?.map((joinClause) => ({
|
|
@@ -306,7 +306,7 @@ function applyRecursiveOptimization(query: QueryIR): QueryIR {
|
|
|
306
306
|
joinClause.from.type === `queryRef`
|
|
307
307
|
? new QueryRefClass(
|
|
308
308
|
applyRecursiveOptimization(joinClause.from.query),
|
|
309
|
-
joinClause.from.alias
|
|
309
|
+
joinClause.from.alias,
|
|
310
310
|
)
|
|
311
311
|
: joinClause.from,
|
|
312
312
|
})),
|
|
@@ -346,7 +346,7 @@ function applySingleLevelOptimization(query: QueryIR): QueryIR {
|
|
|
346
346
|
|
|
347
347
|
// Filter out residual WHERE clauses to prevent them from being optimized again
|
|
348
348
|
const nonResidualWhereClauses = query.where.filter(
|
|
349
|
-
(where) => !isResidualWhere(where)
|
|
349
|
+
(where) => !isResidualWhere(where),
|
|
350
350
|
)
|
|
351
351
|
|
|
352
352
|
// Step 1: Split all AND clauses at the root level for granular optimization
|
|
@@ -354,7 +354,7 @@ function applySingleLevelOptimization(query: QueryIR): QueryIR {
|
|
|
354
354
|
|
|
355
355
|
// Step 2: Analyze each WHERE clause to determine which sources it touches
|
|
356
356
|
const analyzedClauses = splitWhereClauses.map((clause) =>
|
|
357
|
-
analyzeWhereClause(clause)
|
|
357
|
+
analyzeWhereClause(clause),
|
|
358
358
|
)
|
|
359
359
|
|
|
360
360
|
// Step 3: Group clauses by single-source vs multi-source
|
|
@@ -365,7 +365,7 @@ function applySingleLevelOptimization(query: QueryIR): QueryIR {
|
|
|
365
365
|
|
|
366
366
|
// Add back any residual WHERE clauses that were filtered out
|
|
367
367
|
const residualWhereClauses = query.where.filter((where) =>
|
|
368
|
-
isResidualWhere(where)
|
|
368
|
+
isResidualWhere(where),
|
|
369
369
|
)
|
|
370
370
|
if (residualWhereClauses.length > 0) {
|
|
371
371
|
optimizedQuery.where = [
|
|
@@ -461,7 +461,7 @@ function isRedundantSubquery(query: QueryIR): boolean {
|
|
|
461
461
|
* ```
|
|
462
462
|
*/
|
|
463
463
|
function splitAndClauses(
|
|
464
|
-
whereClauses: Array<Where
|
|
464
|
+
whereClauses: Array<Where>,
|
|
465
465
|
): Array<BasicExpression<boolean>> {
|
|
466
466
|
const result: Array<BasicExpression<boolean>> = []
|
|
467
467
|
|
|
@@ -475,7 +475,7 @@ function splitAndClauses(
|
|
|
475
475
|
|
|
476
476
|
// Helper function for recursive splitting of BasicExpression arrays
|
|
477
477
|
function splitAndClausesRecursive(
|
|
478
|
-
clause: BasicExpression<boolean
|
|
478
|
+
clause: BasicExpression<boolean>,
|
|
479
479
|
): Array<BasicExpression<boolean>> {
|
|
480
480
|
if (clause.type === `func` && clause.name === `and`) {
|
|
481
481
|
// Recursively split nested AND clauses to handle complex expressions
|
|
@@ -515,7 +515,7 @@ function splitAndClausesRecursive(
|
|
|
515
515
|
* ```
|
|
516
516
|
*/
|
|
517
517
|
function analyzeWhereClause(
|
|
518
|
-
clause: BasicExpression<boolean
|
|
518
|
+
clause: BasicExpression<boolean>,
|
|
519
519
|
): AnalyzedWhereClause {
|
|
520
520
|
// Track which table aliases this WHERE clause touches
|
|
521
521
|
const touchedSources = new Set<string>()
|
|
@@ -580,7 +580,7 @@ function analyzeWhereClause(
|
|
|
580
580
|
* @returns Grouped clauses ready for optimization
|
|
581
581
|
*/
|
|
582
582
|
function groupWhereClauses(
|
|
583
|
-
analyzedClauses: Array<AnalyzedWhereClause
|
|
583
|
+
analyzedClauses: Array<AnalyzedWhereClause>,
|
|
584
584
|
): GroupedWhereClauses {
|
|
585
585
|
const singleSource = new Map<string, Array<BasicExpression<boolean>>>()
|
|
586
586
|
const multiSource: Array<BasicExpression<boolean>> = []
|
|
@@ -630,7 +630,7 @@ function groupWhereClauses(
|
|
|
630
630
|
*/
|
|
631
631
|
function applyOptimizations(
|
|
632
632
|
query: QueryIR,
|
|
633
|
-
groupedClauses: GroupedWhereClauses
|
|
633
|
+
groupedClauses: GroupedWhereClauses,
|
|
634
634
|
): QueryIR {
|
|
635
635
|
// Track which single-source clauses were actually optimized
|
|
636
636
|
const actuallyOptimized = new Set<string>()
|
|
@@ -639,7 +639,7 @@ function applyOptimizations(
|
|
|
639
639
|
const optimizedFrom = optimizeFromWithTracking(
|
|
640
640
|
query.from,
|
|
641
641
|
groupedClauses.singleSource,
|
|
642
|
-
actuallyOptimized
|
|
642
|
+
actuallyOptimized,
|
|
643
643
|
)
|
|
644
644
|
|
|
645
645
|
// Optimize JOIN clauses and track what was optimized
|
|
@@ -649,7 +649,7 @@ function applyOptimizations(
|
|
|
649
649
|
from: optimizeFromWithTracking(
|
|
650
650
|
joinClause.from,
|
|
651
651
|
groupedClauses.singleSource,
|
|
652
|
-
actuallyOptimized
|
|
652
|
+
actuallyOptimized,
|
|
653
653
|
),
|
|
654
654
|
}))
|
|
655
655
|
: undefined
|
|
@@ -667,7 +667,7 @@ function applyOptimizations(
|
|
|
667
667
|
query.join &&
|
|
668
668
|
query.join.some(
|
|
669
669
|
(join) =>
|
|
670
|
-
join.type === `left` || join.type === `right` || join.type === `full
|
|
670
|
+
join.type === `left` || join.type === `right` || join.type === `full`,
|
|
671
671
|
)
|
|
672
672
|
|
|
673
673
|
// Add single-source clauses
|
|
@@ -690,8 +690,8 @@ function applyOptimizations(
|
|
|
690
690
|
? [
|
|
691
691
|
combineWithAnd(
|
|
692
692
|
remainingWhereClauses.flatMap((clause) =>
|
|
693
|
-
splitAndClausesRecursive(getWhereExpression(clause))
|
|
694
|
-
)
|
|
693
|
+
splitAndClausesRecursive(getWhereExpression(clause)),
|
|
694
|
+
),
|
|
695
695
|
),
|
|
696
696
|
]
|
|
697
697
|
: remainingWhereClauses
|
|
@@ -749,11 +749,11 @@ function deepCopyQuery(query: QueryIR): QueryIR {
|
|
|
749
749
|
joinClause.from.type === `collectionRef`
|
|
750
750
|
? new CollectionRefClass(
|
|
751
751
|
joinClause.from.collection,
|
|
752
|
-
joinClause.from.alias
|
|
752
|
+
joinClause.from.alias,
|
|
753
753
|
)
|
|
754
754
|
: new QueryRefClass(
|
|
755
755
|
deepCopyQuery(joinClause.from.query),
|
|
756
|
-
joinClause.from.alias
|
|
756
|
+
joinClause.from.alias,
|
|
757
757
|
),
|
|
758
758
|
}))
|
|
759
759
|
: undefined,
|
|
@@ -780,7 +780,7 @@ function deepCopyQuery(query: QueryIR): QueryIR {
|
|
|
780
780
|
function optimizeFromWithTracking(
|
|
781
781
|
from: From,
|
|
782
782
|
singleSourceClauses: Map<string, BasicExpression<boolean>>,
|
|
783
|
-
actuallyOptimized: Set<string
|
|
783
|
+
actuallyOptimized: Set<string>,
|
|
784
784
|
): From {
|
|
785
785
|
const whereClause = singleSourceClauses.get(from.alias)
|
|
786
786
|
|
|
@@ -833,7 +833,7 @@ function optimizeFromWithTracking(
|
|
|
833
833
|
function unsafeSelect(
|
|
834
834
|
query: QueryIR,
|
|
835
835
|
whereClause: BasicExpression<boolean>,
|
|
836
|
-
outerAlias: string
|
|
836
|
+
outerAlias: string,
|
|
837
837
|
): boolean {
|
|
838
838
|
if (!query.select) return false
|
|
839
839
|
|
|
@@ -870,7 +870,7 @@ function unsafeFnSelect(query: QueryIR) {
|
|
|
870
870
|
function isSafeToPushIntoExistingSubquery(
|
|
871
871
|
query: QueryIR,
|
|
872
872
|
whereClause: BasicExpression<boolean>,
|
|
873
|
-
outerAlias: string
|
|
873
|
+
outerAlias: string,
|
|
874
874
|
): boolean {
|
|
875
875
|
return !(
|
|
876
876
|
unsafeSelect(query, whereClause, outerAlias) ||
|
|
@@ -945,7 +945,7 @@ function collectRefs(expr: any): Array<PropRef> {
|
|
|
945
945
|
function whereReferencesComputedSelectFields(
|
|
946
946
|
select: Select,
|
|
947
947
|
whereClause: BasicExpression<boolean>,
|
|
948
|
-
outerAlias: string
|
|
948
|
+
outerAlias: string,
|
|
949
949
|
): boolean {
|
|
950
950
|
// Build a set of computed field names at the top-level of the subquery output
|
|
951
951
|
const computed = new Set<string>()
|
|
@@ -979,7 +979,7 @@ function whereReferencesComputedSelectFields(
|
|
|
979
979
|
function referencesAliasWithRemappedSelect(
|
|
980
980
|
subquery: QueryIR,
|
|
981
981
|
whereClause: BasicExpression<boolean>,
|
|
982
|
-
outerAlias: string
|
|
982
|
+
outerAlias: string,
|
|
983
983
|
): boolean {
|
|
984
984
|
const refs = collectRefs(whereClause)
|
|
985
985
|
// Only care about clauses that actually reference the outer alias.
|
|
@@ -1046,7 +1046,7 @@ function referencesAliasWithRemappedSelect(
|
|
|
1046
1046
|
* @throws Error if the expressions array is empty
|
|
1047
1047
|
*/
|
|
1048
1048
|
function combineWithAnd(
|
|
1049
|
-
expressions: Array<BasicExpression<boolean
|
|
1049
|
+
expressions: Array<BasicExpression<boolean>>,
|
|
1050
1050
|
): BasicExpression<boolean> {
|
|
1051
1051
|
if (expressions.length === 0) {
|
|
1052
1052
|
throw new CannotCombineEmptyExpressionListError()
|