@tanstack/db 0.4.9 → 0.4.10
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/events.cjs +9 -51
- package/dist/cjs/collection/events.cjs.map +1 -1
- package/dist/cjs/collection/events.d.cts +18 -7
- package/dist/cjs/collection/index.cjs +9 -12
- package/dist/cjs/collection/index.cjs.map +1 -1
- package/dist/cjs/collection/index.d.cts +13 -14
- package/dist/cjs/collection/subscription.cjs +62 -6
- package/dist/cjs/collection/subscription.cjs.map +1 -1
- package/dist/cjs/collection/subscription.d.cts +16 -3
- package/dist/cjs/collection/sync.cjs +58 -6
- package/dist/cjs/collection/sync.cjs.map +1 -1
- package/dist/cjs/collection/sync.d.cts +18 -4
- package/dist/cjs/errors.cjs +8 -0
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/errors.d.cts +6 -0
- package/dist/cjs/event-emitter.cjs +94 -0
- package/dist/cjs/event-emitter.cjs.map +1 -0
- package/dist/cjs/event-emitter.d.cts +45 -0
- package/dist/cjs/index.cjs +1 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/local-only.cjs.map +1 -1
- package/dist/cjs/local-only.d.cts +2 -5
- package/dist/cjs/query/compiler/index.cjs +6 -2
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.d.cts +3 -2
- package/dist/cjs/query/compiler/joins.cjs +6 -3
- package/dist/cjs/query/compiler/joins.cjs.map +1 -1
- package/dist/cjs/query/compiler/joins.d.cts +2 -2
- package/dist/cjs/query/compiler/order-by.cjs +18 -4
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.d.cts +2 -1
- package/dist/cjs/query/compiler/types.d.cts +4 -0
- package/dist/cjs/query/index.d.cts +1 -0
- package/dist/cjs/query/live/collection-config-builder.cjs +32 -6
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.d.cts +13 -1
- package/dist/cjs/query/live/collection-subscriber.cjs +29 -0
- package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
- package/dist/cjs/query/live/collection-subscriber.d.cts +1 -0
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/query/live-query-collection.d.cts +2 -2
- package/dist/cjs/types.d.cts +82 -11
- package/dist/esm/collection/events.d.ts +18 -7
- package/dist/esm/collection/events.js +9 -51
- package/dist/esm/collection/events.js.map +1 -1
- package/dist/esm/collection/index.d.ts +13 -14
- package/dist/esm/collection/index.js +9 -12
- package/dist/esm/collection/index.js.map +1 -1
- package/dist/esm/collection/subscription.d.ts +16 -3
- package/dist/esm/collection/subscription.js +62 -6
- package/dist/esm/collection/subscription.js.map +1 -1
- package/dist/esm/collection/sync.d.ts +18 -4
- package/dist/esm/collection/sync.js +59 -7
- package/dist/esm/collection/sync.js.map +1 -1
- package/dist/esm/errors.d.ts +6 -0
- package/dist/esm/errors.js +8 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/event-emitter.d.ts +45 -0
- package/dist/esm/event-emitter.js +94 -0
- package/dist/esm/event-emitter.js.map +1 -0
- package/dist/esm/index.js +2 -1
- package/dist/esm/local-only.d.ts +2 -5
- package/dist/esm/local-only.js.map +1 -1
- package/dist/esm/query/compiler/index.d.ts +3 -2
- package/dist/esm/query/compiler/index.js +6 -2
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/compiler/joins.d.ts +2 -2
- package/dist/esm/query/compiler/joins.js +6 -3
- package/dist/esm/query/compiler/joins.js.map +1 -1
- package/dist/esm/query/compiler/order-by.d.ts +2 -1
- package/dist/esm/query/compiler/order-by.js +18 -4
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/compiler/types.d.ts +4 -0
- package/dist/esm/query/index.d.ts +1 -0
- package/dist/esm/query/live/collection-config-builder.d.ts +13 -1
- package/dist/esm/query/live/collection-config-builder.js +33 -7
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/collection-subscriber.d.ts +1 -0
- package/dist/esm/query/live/collection-subscriber.js +29 -0
- package/dist/esm/query/live/collection-subscriber.js.map +1 -1
- package/dist/esm/query/live-query-collection.d.ts +2 -2
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/types.d.ts +82 -11
- package/package.json +2 -2
- package/src/collection/events.ts +25 -74
- package/src/collection/index.ts +15 -19
- package/src/collection/subscription.ts +88 -6
- package/src/collection/sync.ts +81 -9
- package/src/errors.ts +12 -0
- package/src/event-emitter.ts +118 -0
- package/src/local-only.ts +5 -12
- package/src/query/compiler/index.ts +9 -1
- package/src/query/compiler/joins.ts +7 -1
- package/src/query/compiler/order-by.ts +23 -2
- package/src/query/compiler/types.ts +5 -0
- package/src/query/index.ts +1 -0
- package/src/query/live/collection-config-builder.ts +56 -7
- package/src/query/live/collection-subscriber.ts +50 -0
- package/src/query/live-query-collection.ts +8 -4
- package/src/types.ts +93 -11
package/src/local-only.ts
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
BaseCollectionConfig,
|
|
3
3
|
CollectionConfig,
|
|
4
|
-
DeleteMutationFn,
|
|
5
4
|
DeleteMutationFnParams,
|
|
6
5
|
InferSchemaOutput,
|
|
7
|
-
InsertMutationFn,
|
|
8
6
|
InsertMutationFnParams,
|
|
9
7
|
OperationType,
|
|
10
8
|
PendingMutation,
|
|
11
9
|
SyncConfig,
|
|
12
|
-
UpdateMutationFn,
|
|
13
10
|
UpdateMutationFnParams,
|
|
14
11
|
UtilsRecord,
|
|
15
12
|
} from "./types"
|
|
@@ -67,13 +64,7 @@ type LocalOnlyCollectionOptionsResult<
|
|
|
67
64
|
T extends object,
|
|
68
65
|
TKey extends string | number,
|
|
69
66
|
TSchema extends StandardSchemaV1 | never = never,
|
|
70
|
-
> =
|
|
71
|
-
CollectionConfig<T, TKey, TSchema>,
|
|
72
|
-
`onInsert` | `onUpdate` | `onDelete`
|
|
73
|
-
> & {
|
|
74
|
-
onInsert?: InsertMutationFn<T, TKey, LocalOnlyCollectionUtils>
|
|
75
|
-
onUpdate?: UpdateMutationFn<T, TKey, LocalOnlyCollectionUtils>
|
|
76
|
-
onDelete?: DeleteMutationFn<T, TKey, LocalOnlyCollectionUtils>
|
|
67
|
+
> = CollectionConfig<T, TKey, TSchema> & {
|
|
77
68
|
utils: LocalOnlyCollectionUtils
|
|
78
69
|
}
|
|
79
70
|
|
|
@@ -191,7 +182,7 @@ export function localOnlyCollectionOptions<
|
|
|
191
182
|
const { initialData, onInsert, onUpdate, onDelete, ...restConfig } = config
|
|
192
183
|
|
|
193
184
|
// Create the sync configuration with transaction confirmation capability
|
|
194
|
-
const syncResult = createLocalOnlySync(initialData)
|
|
185
|
+
const syncResult = createLocalOnlySync<T, TKey>(initialData)
|
|
195
186
|
|
|
196
187
|
/**
|
|
197
188
|
* Create wrapper handlers that call user handlers first, then confirm transactions
|
|
@@ -279,9 +270,11 @@ export function localOnlyCollectionOptions<
|
|
|
279
270
|
onDelete: wrappedOnDelete,
|
|
280
271
|
utils: {
|
|
281
272
|
acceptMutations,
|
|
282
|
-
}
|
|
273
|
+
},
|
|
283
274
|
startSync: true,
|
|
284
275
|
gcTime: 0,
|
|
276
|
+
} as LocalOnlyCollectionOptionsResult<T, TKey, TSchema> & {
|
|
277
|
+
schema?: StandardSchemaV1
|
|
285
278
|
}
|
|
286
279
|
}
|
|
287
280
|
|
|
@@ -28,7 +28,9 @@ import type {
|
|
|
28
28
|
NamespacedAndKeyedStream,
|
|
29
29
|
ResultStream,
|
|
30
30
|
} from "../../types.js"
|
|
31
|
-
import type { QueryCache, QueryMapping } from "./types.js"
|
|
31
|
+
import type { QueryCache, QueryMapping, WindowOptions } from "./types.js"
|
|
32
|
+
|
|
33
|
+
export type { WindowOptions } from "./types.js"
|
|
32
34
|
|
|
33
35
|
/**
|
|
34
36
|
* Result of query compilation including both the pipeline and source-specific WHERE clauses
|
|
@@ -87,6 +89,7 @@ export function compileQuery(
|
|
|
87
89
|
callbacks: Record<string, LazyCollectionCallbacks>,
|
|
88
90
|
lazySources: Set<string>,
|
|
89
91
|
optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,
|
|
92
|
+
setWindowFn: (windowFn: (options: WindowOptions) => void) => void,
|
|
90
93
|
cache: QueryCache = new WeakMap(),
|
|
91
94
|
queryMapping: QueryMapping = new WeakMap()
|
|
92
95
|
): CompilationResult {
|
|
@@ -134,6 +137,7 @@ export function compileQuery(
|
|
|
134
137
|
callbacks,
|
|
135
138
|
lazySources,
|
|
136
139
|
optimizableOrderByCollections,
|
|
140
|
+
setWindowFn,
|
|
137
141
|
cache,
|
|
138
142
|
queryMapping,
|
|
139
143
|
aliasToCollectionId,
|
|
@@ -169,6 +173,7 @@ export function compileQuery(
|
|
|
169
173
|
callbacks,
|
|
170
174
|
lazySources,
|
|
171
175
|
optimizableOrderByCollections,
|
|
176
|
+
setWindowFn,
|
|
172
177
|
rawQuery,
|
|
173
178
|
compileQuery,
|
|
174
179
|
aliasToCollectionId,
|
|
@@ -311,6 +316,7 @@ export function compileQuery(
|
|
|
311
316
|
query.select || {},
|
|
312
317
|
collections[mainCollectionId]!,
|
|
313
318
|
optimizableOrderByCollections,
|
|
319
|
+
setWindowFn,
|
|
314
320
|
query.limit,
|
|
315
321
|
query.offset
|
|
316
322
|
)
|
|
@@ -381,6 +387,7 @@ function processFrom(
|
|
|
381
387
|
callbacks: Record<string, LazyCollectionCallbacks>,
|
|
382
388
|
lazySources: Set<string>,
|
|
383
389
|
optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,
|
|
390
|
+
setWindowFn: (windowFn: (options: WindowOptions) => void) => void,
|
|
384
391
|
cache: QueryCache,
|
|
385
392
|
queryMapping: QueryMapping,
|
|
386
393
|
aliasToCollectionId: Record<string, string>,
|
|
@@ -412,6 +419,7 @@ function processFrom(
|
|
|
412
419
|
callbacks,
|
|
413
420
|
lazySources,
|
|
414
421
|
optimizableOrderByCollections,
|
|
422
|
+
setWindowFn,
|
|
415
423
|
cache,
|
|
416
424
|
queryMapping
|
|
417
425
|
)
|
|
@@ -31,7 +31,7 @@ import type {
|
|
|
31
31
|
NamespacedAndKeyedStream,
|
|
32
32
|
NamespacedRow,
|
|
33
33
|
} from "../../types.js"
|
|
34
|
-
import type { QueryCache, QueryMapping } from "./types.js"
|
|
34
|
+
import type { QueryCache, QueryMapping, WindowOptions } from "./types.js"
|
|
35
35
|
import type { CollectionSubscription } from "../../collection/subscription.js"
|
|
36
36
|
|
|
37
37
|
/** Function type for loading specific keys into a lazy collection */
|
|
@@ -61,6 +61,7 @@ export function processJoins(
|
|
|
61
61
|
callbacks: Record<string, LazyCollectionCallbacks>,
|
|
62
62
|
lazySources: Set<string>,
|
|
63
63
|
optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,
|
|
64
|
+
setWindowFn: (windowFn: (options: WindowOptions) => void) => void,
|
|
64
65
|
rawQuery: QueryIR,
|
|
65
66
|
onCompileSubquery: CompileQueryFn,
|
|
66
67
|
aliasToCollectionId: Record<string, string>,
|
|
@@ -83,6 +84,7 @@ export function processJoins(
|
|
|
83
84
|
callbacks,
|
|
84
85
|
lazySources,
|
|
85
86
|
optimizableOrderByCollections,
|
|
87
|
+
setWindowFn,
|
|
86
88
|
rawQuery,
|
|
87
89
|
onCompileSubquery,
|
|
88
90
|
aliasToCollectionId,
|
|
@@ -111,6 +113,7 @@ function processJoin(
|
|
|
111
113
|
callbacks: Record<string, LazyCollectionCallbacks>,
|
|
112
114
|
lazySources: Set<string>,
|
|
113
115
|
optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,
|
|
116
|
+
setWindowFn: (windowFn: (options: WindowOptions) => void) => void,
|
|
114
117
|
rawQuery: QueryIR,
|
|
115
118
|
onCompileSubquery: CompileQueryFn,
|
|
116
119
|
aliasToCollectionId: Record<string, string>,
|
|
@@ -131,6 +134,7 @@ function processJoin(
|
|
|
131
134
|
callbacks,
|
|
132
135
|
lazySources,
|
|
133
136
|
optimizableOrderByCollections,
|
|
137
|
+
setWindowFn,
|
|
134
138
|
cache,
|
|
135
139
|
queryMapping,
|
|
136
140
|
onCompileSubquery,
|
|
@@ -421,6 +425,7 @@ function processJoinSource(
|
|
|
421
425
|
callbacks: Record<string, LazyCollectionCallbacks>,
|
|
422
426
|
lazySources: Set<string>,
|
|
423
427
|
optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,
|
|
428
|
+
setWindowFn: (windowFn: (options: WindowOptions) => void) => void,
|
|
424
429
|
cache: QueryCache,
|
|
425
430
|
queryMapping: QueryMapping,
|
|
426
431
|
onCompileSubquery: CompileQueryFn,
|
|
@@ -453,6 +458,7 @@ function processJoinSource(
|
|
|
453
458
|
callbacks,
|
|
454
459
|
lazySources,
|
|
455
460
|
optimizableOrderByCollections,
|
|
461
|
+
setWindowFn,
|
|
456
462
|
cache,
|
|
457
463
|
queryMapping
|
|
458
464
|
)
|
|
@@ -5,6 +5,7 @@ import { ensureIndexForField } from "../../indexes/auto-index.js"
|
|
|
5
5
|
import { findIndexForField } from "../../utils/index-optimization.js"
|
|
6
6
|
import { compileExpression } from "./evaluators.js"
|
|
7
7
|
import { replaceAggregatesByRefs } from "./group-by.js"
|
|
8
|
+
import type { WindowOptions } from "./types.js"
|
|
8
9
|
import type { CompiledSingleRowExpression } from "./evaluators.js"
|
|
9
10
|
import type { OrderBy, OrderByClause, QueryIR, Select } from "../ir.js"
|
|
10
11
|
import type { NamespacedAndKeyedStream, NamespacedRow } from "../../types.js"
|
|
@@ -38,6 +39,7 @@ export function processOrderBy(
|
|
|
38
39
|
selectClause: Select,
|
|
39
40
|
collection: Collection,
|
|
40
41
|
optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,
|
|
42
|
+
setWindowFn: (windowFn: (options: WindowOptions) => void) => void,
|
|
41
43
|
limit?: number,
|
|
42
44
|
offset?: number
|
|
43
45
|
): IStreamBuilder<KeyValue<unknown, [NamespacedRow, string]>> {
|
|
@@ -107,6 +109,8 @@ export function processOrderBy(
|
|
|
107
109
|
|
|
108
110
|
let setSizeCallback: ((getSize: () => number) => void) | undefined
|
|
109
111
|
|
|
112
|
+
let orderByOptimizationInfo: OrderByOptimizationInfo | undefined
|
|
113
|
+
|
|
110
114
|
// Optimize the orderBy operator to lazily load elements
|
|
111
115
|
// by using the range index of the collection.
|
|
112
116
|
// Only for orderBy clause on a single column for now (no composite ordering)
|
|
@@ -161,7 +165,7 @@ export function processOrderBy(
|
|
|
161
165
|
? String(orderByExpression.path[0])
|
|
162
166
|
: rawQuery.from.alias
|
|
163
167
|
|
|
164
|
-
|
|
168
|
+
orderByOptimizationInfo = {
|
|
165
169
|
alias: orderByAlias,
|
|
166
170
|
offset: offset ?? 0,
|
|
167
171
|
limit,
|
|
@@ -179,7 +183,7 @@ export function processOrderBy(
|
|
|
179
183
|
...optimizableOrderByCollections[followRefCollection.id]!,
|
|
180
184
|
dataNeeded: () => {
|
|
181
185
|
const size = getSize()
|
|
182
|
-
return Math.max(0, limit - size)
|
|
186
|
+
return Math.max(0, orderByOptimizationInfo!.limit - size)
|
|
183
187
|
},
|
|
184
188
|
}
|
|
185
189
|
}
|
|
@@ -194,6 +198,23 @@ export function processOrderBy(
|
|
|
194
198
|
offset,
|
|
195
199
|
comparator: compare,
|
|
196
200
|
setSizeCallback,
|
|
201
|
+
setWindowFn: (
|
|
202
|
+
windowFn: (options: { offset?: number; limit?: number }) => void
|
|
203
|
+
) => {
|
|
204
|
+
setWindowFn(
|
|
205
|
+
// We wrap the move function such that we update the orderByOptimizationInfo
|
|
206
|
+
// because that is used by the `dataNeeded` callback to determine if we need to load more data
|
|
207
|
+
(options) => {
|
|
208
|
+
windowFn(options)
|
|
209
|
+
if (orderByOptimizationInfo) {
|
|
210
|
+
orderByOptimizationInfo.offset =
|
|
211
|
+
options.offset ?? orderByOptimizationInfo.offset
|
|
212
|
+
orderByOptimizationInfo.limit =
|
|
213
|
+
options.limit ?? orderByOptimizationInfo.limit
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
)
|
|
217
|
+
},
|
|
197
218
|
})
|
|
198
219
|
// orderByWithFractionalIndex returns [key, [value, index]] - we keep this format
|
|
199
220
|
)
|
|
@@ -10,3 +10,8 @@ export type QueryCache = WeakMap<QueryIR, CompilationResult>
|
|
|
10
10
|
* Mapping from optimized queries back to their original queries for caching
|
|
11
11
|
*/
|
|
12
12
|
export type QueryMapping = WeakMap<QueryIR, QueryIR>
|
|
13
|
+
|
|
14
|
+
export type WindowOptions = {
|
|
15
|
+
offset?: number
|
|
16
|
+
limit?: number
|
|
17
|
+
}
|
package/src/query/index.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { D2, output } from "@tanstack/db-ivm"
|
|
2
2
|
import { compileQuery } from "../compiler/index.js"
|
|
3
3
|
import { buildQuery, getQueryIR } from "../builder/index.js"
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
MissingAliasInputsError,
|
|
6
|
+
SetWindowRequiresOrderByError,
|
|
7
|
+
} from "../../errors.js"
|
|
5
8
|
import { transactionScopedScheduler } from "../../scheduler.js"
|
|
6
9
|
import { getActiveTransaction } from "../../transactions.js"
|
|
7
10
|
import { CollectionSubscriber } from "./collection-subscriber.js"
|
|
8
11
|
import { getCollectionBuilder } from "./collection-registry.js"
|
|
12
|
+
import type { WindowOptions } from "../compiler/index.js"
|
|
9
13
|
import type { SchedulerContextId } from "../../scheduler.js"
|
|
10
14
|
import type { CollectionSubscription } from "../../collection/subscription.js"
|
|
11
15
|
import type { RootStreamBuilder } from "@tanstack/db-ivm"
|
|
@@ -32,6 +36,13 @@ import type { AllCollectionEvents } from "../../collection/events.js"
|
|
|
32
36
|
export type LiveQueryCollectionUtils = UtilsRecord & {
|
|
33
37
|
getRunCount: () => number
|
|
34
38
|
getBuilder: () => CollectionConfigBuilder<any, any>
|
|
39
|
+
/**
|
|
40
|
+
* Sets the offset and limit of an ordered query.
|
|
41
|
+
* Is a no-op if the query is not ordered.
|
|
42
|
+
*
|
|
43
|
+
* @returns `true` if no subset loading was triggered, or `Promise<void>` that resolves when the subset has been loaded
|
|
44
|
+
*/
|
|
45
|
+
setWindow: (options: WindowOptions) => true | Promise<void>
|
|
35
46
|
}
|
|
36
47
|
|
|
37
48
|
type PendingGraphRun = {
|
|
@@ -79,7 +90,11 @@ export class CollectionConfigBuilder<
|
|
|
79
90
|
private isInErrorState = false
|
|
80
91
|
|
|
81
92
|
// Reference to the live query collection for error state transitions
|
|
82
|
-
|
|
93
|
+
public liveQueryCollection?: Collection<TResult, any, any>
|
|
94
|
+
|
|
95
|
+
private windowFn: ((options: WindowOptions) => void) | undefined
|
|
96
|
+
|
|
97
|
+
private maybeRunGraphFn: (() => void) | undefined
|
|
83
98
|
|
|
84
99
|
private readonly aliasDependencies: Record<
|
|
85
100
|
string,
|
|
@@ -171,10 +186,39 @@ export class CollectionConfigBuilder<
|
|
|
171
186
|
utils: {
|
|
172
187
|
getRunCount: this.getRunCount.bind(this),
|
|
173
188
|
getBuilder: () => this,
|
|
189
|
+
setWindow: this.setWindow.bind(this),
|
|
174
190
|
},
|
|
175
191
|
}
|
|
176
192
|
}
|
|
177
193
|
|
|
194
|
+
setWindow(options: WindowOptions): true | Promise<void> {
|
|
195
|
+
if (!this.windowFn) {
|
|
196
|
+
throw new SetWindowRequiresOrderByError()
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
this.windowFn(options)
|
|
200
|
+
this.maybeRunGraphFn?.()
|
|
201
|
+
|
|
202
|
+
// Check if loading a subset was triggered
|
|
203
|
+
if (this.liveQueryCollection?.isLoadingSubset) {
|
|
204
|
+
// Loading was triggered, return a promise that resolves when it completes
|
|
205
|
+
return new Promise<void>((resolve) => {
|
|
206
|
+
const unsubscribe = this.liveQueryCollection!.on(
|
|
207
|
+
`loadingSubset:change`,
|
|
208
|
+
(event) => {
|
|
209
|
+
if (!event.isLoadingSubset) {
|
|
210
|
+
unsubscribe()
|
|
211
|
+
resolve()
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// No loading was triggered
|
|
219
|
+
return true
|
|
220
|
+
}
|
|
221
|
+
|
|
178
222
|
/**
|
|
179
223
|
* Resolves a collection alias to its collection ID.
|
|
180
224
|
*
|
|
@@ -452,13 +496,15 @@ export class CollectionConfigBuilder<
|
|
|
452
496
|
}
|
|
453
497
|
)
|
|
454
498
|
|
|
455
|
-
const
|
|
499
|
+
const loadSubsetDataCallbacks = this.subscribeToAllCollections(
|
|
456
500
|
config,
|
|
457
501
|
fullSyncState
|
|
458
502
|
)
|
|
459
503
|
|
|
504
|
+
this.maybeRunGraphFn = () => this.scheduleGraphRun(loadSubsetDataCallbacks)
|
|
505
|
+
|
|
460
506
|
// Initial run with callback to load more data if needed
|
|
461
|
-
this.scheduleGraphRun(
|
|
507
|
+
this.scheduleGraphRun(loadSubsetDataCallbacks)
|
|
462
508
|
|
|
463
509
|
// Return the unsubscribe function
|
|
464
510
|
return () => {
|
|
@@ -517,7 +563,10 @@ export class CollectionConfigBuilder<
|
|
|
517
563
|
this.subscriptions,
|
|
518
564
|
this.lazySourcesCallbacks,
|
|
519
565
|
this.lazySources,
|
|
520
|
-
this.optimizableOrderByCollections
|
|
566
|
+
this.optimizableOrderByCollections,
|
|
567
|
+
(windowFn: (options: WindowOptions) => void) => {
|
|
568
|
+
this.windowFn = windowFn
|
|
569
|
+
}
|
|
521
570
|
)
|
|
522
571
|
|
|
523
572
|
this.pipelineCache = compilation.pipeline
|
|
@@ -764,7 +813,7 @@ export class CollectionConfigBuilder<
|
|
|
764
813
|
// Combine all loaders into a single callback that initiates loading more data
|
|
765
814
|
// from any source that needs it. Returns true once all loaders have been called,
|
|
766
815
|
// but the actual async loading may still be in progress.
|
|
767
|
-
const
|
|
816
|
+
const loadSubsetDataCallbacks = () => {
|
|
768
817
|
loaders.map((loader) => loader())
|
|
769
818
|
return true
|
|
770
819
|
}
|
|
@@ -776,7 +825,7 @@ export class CollectionConfigBuilder<
|
|
|
776
825
|
// Initial status check after all subscriptions are set up
|
|
777
826
|
this.updateLiveQueryStatus(config)
|
|
778
827
|
|
|
779
|
-
return
|
|
828
|
+
return loadSubsetDataCallbacks
|
|
780
829
|
}
|
|
781
830
|
}
|
|
782
831
|
|
|
@@ -24,6 +24,12 @@ export class CollectionSubscriber<
|
|
|
24
24
|
// Keep track of the biggest value we've sent so far (needed for orderBy optimization)
|
|
25
25
|
private biggest: any = undefined
|
|
26
26
|
|
|
27
|
+
// Track deferred promises for subscription loading states
|
|
28
|
+
private subscriptionLoadingPromises = new Map<
|
|
29
|
+
CollectionSubscription,
|
|
30
|
+
{ resolve: () => void }
|
|
31
|
+
>()
|
|
32
|
+
|
|
27
33
|
constructor(
|
|
28
34
|
private alias: string,
|
|
29
35
|
private collectionId: string,
|
|
@@ -66,7 +72,51 @@ export class CollectionSubscriber<
|
|
|
66
72
|
includeInitialState
|
|
67
73
|
)
|
|
68
74
|
}
|
|
75
|
+
|
|
76
|
+
// Subscribe to subscription status changes to propagate loading state
|
|
77
|
+
const statusUnsubscribe = subscription.on(`status:change`, (event) => {
|
|
78
|
+
// TODO: For now we are setting this loading state whenever the subscription
|
|
79
|
+
// status changes to 'loadingSubset'. But we have discussed it only happening
|
|
80
|
+
// when the the live query has it's offset/limit changed, and that triggers the
|
|
81
|
+
// subscription to request a snapshot. This will require more work to implement,
|
|
82
|
+
// and builds on https://github.com/TanStack/db/pull/663 which this PR
|
|
83
|
+
// does not yet depend on.
|
|
84
|
+
if (event.status === `loadingSubset`) {
|
|
85
|
+
// Guard against duplicate transitions
|
|
86
|
+
if (!this.subscriptionLoadingPromises.has(subscription)) {
|
|
87
|
+
let resolve: () => void
|
|
88
|
+
const promise = new Promise<void>((res) => {
|
|
89
|
+
resolve = res
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
this.subscriptionLoadingPromises.set(subscription, {
|
|
93
|
+
resolve: resolve!,
|
|
94
|
+
})
|
|
95
|
+
this.collectionConfigBuilder.liveQueryCollection!._sync.trackLoadPromise(
|
|
96
|
+
promise
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
// status is 'ready'
|
|
101
|
+
const deferred = this.subscriptionLoadingPromises.get(subscription)
|
|
102
|
+
if (deferred) {
|
|
103
|
+
// Clear the map entry FIRST (before resolving)
|
|
104
|
+
this.subscriptionLoadingPromises.delete(subscription)
|
|
105
|
+
deferred.resolve()
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
|
|
69
110
|
const unsubscribe = () => {
|
|
111
|
+
// If subscription has a pending promise, resolve it before unsubscribing
|
|
112
|
+
const deferred = this.subscriptionLoadingPromises.get(subscription)
|
|
113
|
+
if (deferred) {
|
|
114
|
+
// Clear the map entry FIRST (before resolving)
|
|
115
|
+
this.subscriptionLoadingPromises.delete(subscription)
|
|
116
|
+
deferred.resolve()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
statusUnsubscribe()
|
|
70
120
|
subscription.unsubscribe()
|
|
71
121
|
}
|
|
72
122
|
// currentSyncState is always defined when subscribe() is called
|
|
@@ -20,16 +20,20 @@ import type { Context, GetResult } from "./builder/types.js"
|
|
|
20
20
|
type CollectionConfigForContext<
|
|
21
21
|
TContext extends Context,
|
|
22
22
|
TResult extends object,
|
|
23
|
+
TUtils extends UtilsRecord = {},
|
|
23
24
|
> = TContext extends SingleResult
|
|
24
|
-
? CollectionConfigSingleRowOption<TResult> &
|
|
25
|
-
|
|
25
|
+
? CollectionConfigSingleRowOption<TResult, string | number, never, TUtils> &
|
|
26
|
+
SingleResult
|
|
27
|
+
: CollectionConfigSingleRowOption<TResult, string | number, never, TUtils> &
|
|
28
|
+
NonSingleResult
|
|
26
29
|
|
|
27
30
|
type CollectionForContext<
|
|
28
31
|
TContext extends Context,
|
|
29
32
|
TResult extends object,
|
|
33
|
+
TUtils extends UtilsRecord = {},
|
|
30
34
|
> = TContext extends SingleResult
|
|
31
|
-
? Collection<TResult> & SingleResult
|
|
32
|
-
: Collection<TResult> & NonSingleResult
|
|
35
|
+
? Collection<TResult, string | number, TUtils> & SingleResult
|
|
36
|
+
: Collection<TResult, string | number, TUtils> & NonSingleResult
|
|
33
37
|
|
|
34
38
|
/**
|
|
35
39
|
* Creates live query collection options for use with createCollection
|
package/src/types.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { Collection } from "./collection/index.js"
|
|
|
3
3
|
import type { StandardSchemaV1 } from "@standard-schema/spec"
|
|
4
4
|
import type { Transaction } from "./transactions"
|
|
5
5
|
import type { BasicExpression, OrderBy } from "./query/ir.js"
|
|
6
|
+
import type { EventEmitter } from "./event-emitter.js"
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Helper type to extract the output type from a standard schema
|
|
@@ -150,17 +151,83 @@ export type Row<TExtensions = never> = Record<string, Value<TExtensions>>
|
|
|
150
151
|
|
|
151
152
|
export type OperationType = `insert` | `update` | `delete`
|
|
152
153
|
|
|
153
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Subscription status values
|
|
156
|
+
*/
|
|
157
|
+
export type SubscriptionStatus = `ready` | `loadingSubset`
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Event emitted when subscription status changes
|
|
161
|
+
*/
|
|
162
|
+
export interface SubscriptionStatusChangeEvent {
|
|
163
|
+
type: `status:change`
|
|
164
|
+
subscription: Subscription
|
|
165
|
+
previousStatus: SubscriptionStatus
|
|
166
|
+
status: SubscriptionStatus
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Event emitted when subscription status changes to a specific status
|
|
171
|
+
*/
|
|
172
|
+
export interface SubscriptionStatusEvent<T extends SubscriptionStatus> {
|
|
173
|
+
type: `status:${T}`
|
|
174
|
+
subscription: Subscription
|
|
175
|
+
previousStatus: SubscriptionStatus
|
|
176
|
+
status: T
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Event emitted when subscription is unsubscribed
|
|
181
|
+
*/
|
|
182
|
+
export interface SubscriptionUnsubscribedEvent {
|
|
183
|
+
type: `unsubscribed`
|
|
184
|
+
subscription: Subscription
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* All subscription events
|
|
189
|
+
*/
|
|
190
|
+
export type SubscriptionEvents = {
|
|
191
|
+
"status:change": SubscriptionStatusChangeEvent
|
|
192
|
+
"status:ready": SubscriptionStatusEvent<`ready`>
|
|
193
|
+
"status:loadingSubset": SubscriptionStatusEvent<`loadingSubset`>
|
|
194
|
+
unsubscribed: SubscriptionUnsubscribedEvent
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Public interface for a collection subscription
|
|
199
|
+
* Used by sync implementations to track subscription lifecycle
|
|
200
|
+
*/
|
|
201
|
+
export interface Subscription extends EventEmitter<SubscriptionEvents> {
|
|
202
|
+
/** Current status of the subscription */
|
|
203
|
+
readonly status: SubscriptionStatus
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export type LoadSubsetOptions = {
|
|
207
|
+
/** The where expression to filter the data */
|
|
154
208
|
where?: BasicExpression<boolean>
|
|
209
|
+
/** The order by clause to sort the data */
|
|
155
210
|
orderBy?: OrderBy
|
|
211
|
+
/** The limit of the data to load */
|
|
156
212
|
limit?: number
|
|
213
|
+
/**
|
|
214
|
+
* The subscription that triggered the load.
|
|
215
|
+
* Advanced sync implementations can use this for:
|
|
216
|
+
* - LRU caching keyed by subscription
|
|
217
|
+
* - Reference counting to track active subscriptions
|
|
218
|
+
* - Subscribing to subscription events (e.g., finalization/unsubscribe)
|
|
219
|
+
* @optional Available when called from CollectionSubscription, may be undefined for direct calls
|
|
220
|
+
*/
|
|
221
|
+
subscription?: Subscription
|
|
157
222
|
}
|
|
158
223
|
|
|
224
|
+
export type LoadSubsetFn = (options: LoadSubsetOptions) => true | Promise<void>
|
|
225
|
+
|
|
159
226
|
export type CleanupFn = () => void
|
|
160
227
|
|
|
161
228
|
export type SyncConfigRes = {
|
|
162
229
|
cleanup?: CleanupFn
|
|
163
|
-
|
|
230
|
+
loadSubset?: LoadSubsetFn
|
|
164
231
|
}
|
|
165
232
|
export interface SyncConfig<
|
|
166
233
|
T extends object = Record<string, unknown>,
|
|
@@ -242,7 +309,7 @@ export interface InsertConfig {
|
|
|
242
309
|
export type UpdateMutationFnParams<
|
|
243
310
|
T extends object = Record<string, unknown>,
|
|
244
311
|
TKey extends string | number = string | number,
|
|
245
|
-
TUtils extends UtilsRecord =
|
|
312
|
+
TUtils extends UtilsRecord = UtilsRecord,
|
|
246
313
|
> = {
|
|
247
314
|
transaction: TransactionWithMutations<T, `update`>
|
|
248
315
|
collection: Collection<T, TKey, TUtils>
|
|
@@ -251,7 +318,7 @@ export type UpdateMutationFnParams<
|
|
|
251
318
|
export type InsertMutationFnParams<
|
|
252
319
|
T extends object = Record<string, unknown>,
|
|
253
320
|
TKey extends string | number = string | number,
|
|
254
|
-
TUtils extends UtilsRecord =
|
|
321
|
+
TUtils extends UtilsRecord = UtilsRecord,
|
|
255
322
|
> = {
|
|
256
323
|
transaction: TransactionWithMutations<T, `insert`>
|
|
257
324
|
collection: Collection<T, TKey, TUtils>
|
|
@@ -259,7 +326,7 @@ export type InsertMutationFnParams<
|
|
|
259
326
|
export type DeleteMutationFnParams<
|
|
260
327
|
T extends object = Record<string, unknown>,
|
|
261
328
|
TKey extends string | number = string | number,
|
|
262
|
-
TUtils extends UtilsRecord =
|
|
329
|
+
TUtils extends UtilsRecord = UtilsRecord,
|
|
263
330
|
> = {
|
|
264
331
|
transaction: TransactionWithMutations<T, `delete`>
|
|
265
332
|
collection: Collection<T, TKey, TUtils>
|
|
@@ -268,21 +335,21 @@ export type DeleteMutationFnParams<
|
|
|
268
335
|
export type InsertMutationFn<
|
|
269
336
|
T extends object = Record<string, unknown>,
|
|
270
337
|
TKey extends string | number = string | number,
|
|
271
|
-
TUtils extends UtilsRecord =
|
|
338
|
+
TUtils extends UtilsRecord = UtilsRecord,
|
|
272
339
|
TReturn = any,
|
|
273
340
|
> = (params: InsertMutationFnParams<T, TKey, TUtils>) => Promise<TReturn>
|
|
274
341
|
|
|
275
342
|
export type UpdateMutationFn<
|
|
276
343
|
T extends object = Record<string, unknown>,
|
|
277
344
|
TKey extends string | number = string | number,
|
|
278
|
-
TUtils extends UtilsRecord =
|
|
345
|
+
TUtils extends UtilsRecord = UtilsRecord,
|
|
279
346
|
TReturn = any,
|
|
280
347
|
> = (params: UpdateMutationFnParams<T, TKey, TUtils>) => Promise<TReturn>
|
|
281
348
|
|
|
282
349
|
export type DeleteMutationFn<
|
|
283
350
|
T extends object = Record<string, unknown>,
|
|
284
351
|
TKey extends string | number = string | number,
|
|
285
|
-
TUtils extends UtilsRecord =
|
|
352
|
+
TUtils extends UtilsRecord = UtilsRecord,
|
|
286
353
|
TReturn = any,
|
|
287
354
|
> = (params: DeleteMutationFnParams<T, TKey, TUtils>) => Promise<TReturn>
|
|
288
355
|
|
|
@@ -313,6 +380,8 @@ export type CollectionStatus =
|
|
|
313
380
|
/** Collection has been cleaned up and resources freed */
|
|
314
381
|
| `cleaned-up`
|
|
315
382
|
|
|
383
|
+
export type SyncMode = `eager` | `on-demand`
|
|
384
|
+
|
|
316
385
|
export interface BaseCollectionConfig<
|
|
317
386
|
T extends object = Record<string, unknown>,
|
|
318
387
|
TKey extends string | number = string | number,
|
|
@@ -321,7 +390,7 @@ export interface BaseCollectionConfig<
|
|
|
321
390
|
// then it would conflict with the overloads of createCollection which
|
|
322
391
|
// requires either T to be provided or a schema to be provided but not both!
|
|
323
392
|
TSchema extends StandardSchemaV1 = never,
|
|
324
|
-
TUtils extends UtilsRecord =
|
|
393
|
+
TUtils extends UtilsRecord = UtilsRecord,
|
|
325
394
|
TReturn = any,
|
|
326
395
|
> {
|
|
327
396
|
// If an id isn't passed in, a UUID will be
|
|
@@ -374,6 +443,15 @@ export interface BaseCollectionConfig<
|
|
|
374
443
|
* compare: (x, y) => x.createdAt.getTime() - y.createdAt.getTime()
|
|
375
444
|
*/
|
|
376
445
|
compare?: (x: T, y: T) => number
|
|
446
|
+
/**
|
|
447
|
+
* The mode of sync to use for the collection.
|
|
448
|
+
* @default `eager`
|
|
449
|
+
* @description
|
|
450
|
+
* - `eager`: syncs all data immediately on preload
|
|
451
|
+
* - `on-demand`: syncs data in incremental snapshots when the collection is queried
|
|
452
|
+
* The exact implementation of the sync mode is up to the sync implementation.
|
|
453
|
+
*/
|
|
454
|
+
syncMode?: SyncMode
|
|
377
455
|
/**
|
|
378
456
|
* Optional asynchronous handler function called before an insert operation
|
|
379
457
|
* @param params Object containing transaction and collection information
|
|
@@ -503,13 +581,16 @@ export interface BaseCollectionConfig<
|
|
|
503
581
|
* }
|
|
504
582
|
*/
|
|
505
583
|
onDelete?: DeleteMutationFn<T, TKey, TUtils, TReturn>
|
|
584
|
+
|
|
585
|
+
utils?: TUtils
|
|
506
586
|
}
|
|
507
587
|
|
|
508
588
|
export interface CollectionConfig<
|
|
509
589
|
T extends object = Record<string, unknown>,
|
|
510
590
|
TKey extends string | number = string | number,
|
|
511
591
|
TSchema extends StandardSchemaV1 = never,
|
|
512
|
-
|
|
592
|
+
TUtils extends UtilsRecord = UtilsRecord,
|
|
593
|
+
> extends BaseCollectionConfig<T, TKey, TSchema, TUtils> {
|
|
513
594
|
sync: SyncConfig<T, TKey>
|
|
514
595
|
}
|
|
515
596
|
|
|
@@ -533,7 +614,8 @@ export type CollectionConfigSingleRowOption<
|
|
|
533
614
|
T extends object = Record<string, unknown>,
|
|
534
615
|
TKey extends string | number = string | number,
|
|
535
616
|
TSchema extends StandardSchemaV1 = never,
|
|
536
|
-
|
|
617
|
+
TUtils extends UtilsRecord = {},
|
|
618
|
+
> = CollectionConfig<T, TKey, TSchema, TUtils> & MaybeSingleResult
|
|
537
619
|
|
|
538
620
|
export type ChangesPayload<T extends object = Record<string, unknown>> = Array<
|
|
539
621
|
ChangeMessage<T>
|