@tanstack/db 0.4.6 → 0.4.8
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.cjs.map +1 -1
- package/dist/cjs/collection/index.d.cts +2 -1
- package/dist/cjs/collection/lifecycle.cjs +2 -3
- package/dist/cjs/collection/lifecycle.cjs.map +1 -1
- package/dist/cjs/collection/mutations.cjs +4 -4
- package/dist/cjs/collection/mutations.cjs.map +1 -1
- package/dist/cjs/collection/state.cjs +22 -33
- package/dist/cjs/collection/state.cjs.map +1 -1
- package/dist/cjs/collection/state.d.cts +6 -2
- package/dist/cjs/collection/sync.cjs +4 -3
- package/dist/cjs/collection/sync.cjs.map +1 -1
- package/dist/cjs/indexes/auto-index.cjs +0 -3
- package/dist/cjs/indexes/auto-index.cjs.map +1 -1
- package/dist/cjs/local-only.cjs +21 -2
- package/dist/cjs/local-only.cjs.map +1 -1
- package/dist/cjs/local-only.d.cts +64 -7
- package/dist/cjs/local-storage.cjs +71 -3
- package/dist/cjs/local-storage.cjs.map +1 -1
- package/dist/cjs/local-storage.d.cts +55 -2
- package/dist/cjs/query/live/collection-config-builder.cjs +54 -12
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.d.cts +17 -2
- package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
- package/dist/cjs/types.d.cts +3 -5
- package/dist/esm/collection/index.d.ts +2 -1
- package/dist/esm/collection/index.js.map +1 -1
- package/dist/esm/collection/lifecycle.js +2 -3
- package/dist/esm/collection/lifecycle.js.map +1 -1
- package/dist/esm/collection/mutations.js +4 -4
- package/dist/esm/collection/mutations.js.map +1 -1
- package/dist/esm/collection/state.d.ts +6 -2
- package/dist/esm/collection/state.js +22 -33
- package/dist/esm/collection/state.js.map +1 -1
- package/dist/esm/collection/sync.js +4 -3
- package/dist/esm/collection/sync.js.map +1 -1
- package/dist/esm/indexes/auto-index.js +0 -3
- package/dist/esm/indexes/auto-index.js.map +1 -1
- package/dist/esm/local-only.d.ts +64 -7
- package/dist/esm/local-only.js +21 -2
- package/dist/esm/local-only.js.map +1 -1
- package/dist/esm/local-storage.d.ts +55 -2
- package/dist/esm/local-storage.js +72 -4
- package/dist/esm/local-storage.js.map +1 -1
- package/dist/esm/query/live/collection-config-builder.d.ts +17 -2
- package/dist/esm/query/live/collection-config-builder.js +54 -12
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/collection-subscriber.js.map +1 -1
- package/dist/esm/types.d.ts +3 -5
- package/package.json +1 -1
- package/src/collection/index.ts +1 -1
- package/src/collection/lifecycle.ts +3 -4
- package/src/collection/mutations.ts +8 -4
- package/src/collection/state.ts +52 -48
- package/src/collection/sync.ts +7 -6
- package/src/indexes/auto-index.ts +0 -8
- package/src/local-only.ts +119 -30
- package/src/local-storage.ts +170 -5
- package/src/query/live/collection-config-builder.ts +103 -24
- package/src/query/live/collection-subscriber.ts +3 -3
- package/src/types.ts +3 -5
|
@@ -21,10 +21,15 @@ import type {
|
|
|
21
21
|
LiveQueryCollectionConfig,
|
|
22
22
|
SyncState,
|
|
23
23
|
} from "./types.js"
|
|
24
|
+
import type { AllCollectionEvents } from "../../collection/events.js"
|
|
24
25
|
|
|
25
26
|
// Global counter for auto-generated collection IDs
|
|
26
27
|
let liveQueryCollectionCounter = 0
|
|
27
28
|
|
|
29
|
+
type SyncMethods<TResult extends object> = Parameters<
|
|
30
|
+
SyncConfig<TResult>[`sync`]
|
|
31
|
+
>[0]
|
|
32
|
+
|
|
28
33
|
export class CollectionConfigBuilder<
|
|
29
34
|
TContext extends Context,
|
|
30
35
|
TResult extends object = GetResult<TContext>,
|
|
@@ -44,6 +49,12 @@ export class CollectionConfigBuilder<
|
|
|
44
49
|
|
|
45
50
|
private isGraphRunning = false
|
|
46
51
|
|
|
52
|
+
// Error state tracking
|
|
53
|
+
private isInErrorState = false
|
|
54
|
+
|
|
55
|
+
// Reference to the live query collection for error state transitions
|
|
56
|
+
private liveQueryCollection?: Collection<TResult, any, any>
|
|
57
|
+
|
|
47
58
|
private graphCache: D2 | undefined
|
|
48
59
|
private inputsCache: Record<string, RootStreamBuilder<unknown>> | undefined
|
|
49
60
|
private pipelineCache: ResultStream | undefined
|
|
@@ -101,12 +112,12 @@ export class CollectionConfigBuilder<
|
|
|
101
112
|
// This gives the callback a chance to load more data if needed,
|
|
102
113
|
// that's used to optimize orderBy operators that set a limit,
|
|
103
114
|
// in order to load some more data if we still don't have enough rows after the pipeline has run.
|
|
104
|
-
// That can
|
|
115
|
+
// That can happen because even though we load N rows, the pipeline might filter some of these rows out
|
|
105
116
|
// causing the orderBy operator to receive less than N rows or even no rows at all.
|
|
106
117
|
// So this callback would notice that it doesn't have enough rows and load some more.
|
|
107
|
-
// The callback returns a boolean, when it's true it's done loading data
|
|
118
|
+
// The callback returns a boolean, when it's true it's done loading data.
|
|
108
119
|
maybeRunGraph(
|
|
109
|
-
config:
|
|
120
|
+
config: SyncMethods<TResult>,
|
|
110
121
|
syncState: FullSyncState,
|
|
111
122
|
callback?: () => boolean
|
|
112
123
|
) {
|
|
@@ -120,13 +131,15 @@ export class CollectionConfigBuilder<
|
|
|
120
131
|
this.isGraphRunning = true
|
|
121
132
|
|
|
122
133
|
try {
|
|
123
|
-
const { begin, commit
|
|
134
|
+
const { begin, commit } = config
|
|
135
|
+
|
|
136
|
+
// Don't run if the live query is in an error state
|
|
137
|
+
if (this.isInErrorState) {
|
|
138
|
+
return
|
|
139
|
+
}
|
|
124
140
|
|
|
125
|
-
//
|
|
126
|
-
if (
|
|
127
|
-
this.allCollectionsReadyOrInitialCommit() &&
|
|
128
|
-
syncState.subscribedToAllCollections
|
|
129
|
-
) {
|
|
141
|
+
// Always run the graph if subscribed (eager execution)
|
|
142
|
+
if (syncState.subscribedToAllCollections) {
|
|
130
143
|
while (syncState.graph.pendingWork()) {
|
|
131
144
|
syncState.graph.run()
|
|
132
145
|
callback?.()
|
|
@@ -137,10 +150,9 @@ export class CollectionConfigBuilder<
|
|
|
137
150
|
if (syncState.messagesCount === 0) {
|
|
138
151
|
begin()
|
|
139
152
|
commit()
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
markReady()
|
|
153
|
+
// After initial commit, check if we should mark ready
|
|
154
|
+
// (in case all sources were already ready before we subscribed)
|
|
155
|
+
this.updateLiveQueryStatus(config)
|
|
144
156
|
}
|
|
145
157
|
}
|
|
146
158
|
} finally {
|
|
@@ -155,7 +167,10 @@ export class CollectionConfigBuilder<
|
|
|
155
167
|
}
|
|
156
168
|
}
|
|
157
169
|
|
|
158
|
-
private syncFn(config:
|
|
170
|
+
private syncFn(config: SyncMethods<TResult>) {
|
|
171
|
+
// Store reference to the live query collection for error state transitions
|
|
172
|
+
this.liveQueryCollection = config.collection
|
|
173
|
+
|
|
159
174
|
const syncState: SyncState = {
|
|
160
175
|
messagesCount: 0,
|
|
161
176
|
subscribedToAllCollections: false,
|
|
@@ -233,7 +248,7 @@ export class CollectionConfigBuilder<
|
|
|
233
248
|
}
|
|
234
249
|
|
|
235
250
|
private extendPipelineWithChangeProcessing(
|
|
236
|
-
config:
|
|
251
|
+
config: SyncMethods<TResult>,
|
|
237
252
|
syncState: SyncState
|
|
238
253
|
): FullSyncState {
|
|
239
254
|
const { begin, commit } = config
|
|
@@ -266,7 +281,7 @@ export class CollectionConfigBuilder<
|
|
|
266
281
|
}
|
|
267
282
|
|
|
268
283
|
private applyChanges(
|
|
269
|
-
config:
|
|
284
|
+
config: SyncMethods<TResult>,
|
|
270
285
|
changes: {
|
|
271
286
|
deletes: number
|
|
272
287
|
inserts: number
|
|
@@ -317,21 +332,76 @@ export class CollectionConfigBuilder<
|
|
|
317
332
|
}
|
|
318
333
|
}
|
|
319
334
|
|
|
335
|
+
/**
|
|
336
|
+
* Handle status changes from source collections
|
|
337
|
+
*/
|
|
338
|
+
private handleSourceStatusChange(
|
|
339
|
+
config: SyncMethods<TResult>,
|
|
340
|
+
collectionId: string,
|
|
341
|
+
event: AllCollectionEvents[`status:change`]
|
|
342
|
+
) {
|
|
343
|
+
const { status } = event
|
|
344
|
+
|
|
345
|
+
// Handle error state - any source collection in error puts live query in error
|
|
346
|
+
if (status === `error`) {
|
|
347
|
+
this.transitionToError(
|
|
348
|
+
`Source collection '${collectionId}' entered error state`
|
|
349
|
+
)
|
|
350
|
+
return
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Handle manual cleanup - this should not happen due to GC prevention,
|
|
354
|
+
// but could happen if user manually calls cleanup()
|
|
355
|
+
if (status === `cleaned-up`) {
|
|
356
|
+
this.transitionToError(
|
|
357
|
+
`Source collection '${collectionId}' was manually cleaned up while live query '${this.id}' depends on it. ` +
|
|
358
|
+
`Live queries prevent automatic GC, so this was likely a manual cleanup() call.`
|
|
359
|
+
)
|
|
360
|
+
return
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Update ready status based on all source collections
|
|
364
|
+
this.updateLiveQueryStatus(config)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Update the live query status based on source collection statuses
|
|
369
|
+
*/
|
|
370
|
+
private updateLiveQueryStatus(config: SyncMethods<TResult>) {
|
|
371
|
+
const { markReady } = config
|
|
372
|
+
|
|
373
|
+
// Don't update status if already in error
|
|
374
|
+
if (this.isInErrorState) {
|
|
375
|
+
return
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Mark ready when all source collections are ready
|
|
379
|
+
if (this.allCollectionsReady()) {
|
|
380
|
+
markReady()
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Transition the live query to error state
|
|
386
|
+
*/
|
|
387
|
+
private transitionToError(message: string) {
|
|
388
|
+
this.isInErrorState = true
|
|
389
|
+
|
|
390
|
+
// Log error to console for debugging
|
|
391
|
+
console.error(`[Live Query Error] ${message}`)
|
|
392
|
+
|
|
393
|
+
// Transition live query collection to error state
|
|
394
|
+
this.liveQueryCollection?._lifecycle.setStatus(`error`)
|
|
395
|
+
}
|
|
396
|
+
|
|
320
397
|
private allCollectionsReady() {
|
|
321
398
|
return Object.values(this.collections).every((collection) =>
|
|
322
399
|
collection.isReady()
|
|
323
400
|
)
|
|
324
401
|
}
|
|
325
402
|
|
|
326
|
-
private allCollectionsReadyOrInitialCommit() {
|
|
327
|
-
return Object.values(this.collections).every(
|
|
328
|
-
(collection) =>
|
|
329
|
-
collection.status === `ready` || collection.status === `initialCommit`
|
|
330
|
-
)
|
|
331
|
-
}
|
|
332
|
-
|
|
333
403
|
private subscribeToAllCollections(
|
|
334
|
-
config:
|
|
404
|
+
config: SyncMethods<TResult>,
|
|
335
405
|
syncState: FullSyncState
|
|
336
406
|
) {
|
|
337
407
|
const loaders = Object.entries(this.collections).map(
|
|
@@ -347,6 +417,12 @@ export class CollectionConfigBuilder<
|
|
|
347
417
|
const subscription = collectionSubscriber.subscribe()
|
|
348
418
|
this.subscriptions[collectionId] = subscription
|
|
349
419
|
|
|
420
|
+
// Subscribe to status changes for status flow
|
|
421
|
+
const statusUnsubscribe = collection.on(`status:change`, (event) => {
|
|
422
|
+
this.handleSourceStatusChange(config, collectionId, event)
|
|
423
|
+
})
|
|
424
|
+
syncState.unsubscribeCallbacks.add(statusUnsubscribe)
|
|
425
|
+
|
|
350
426
|
const loadMore = collectionSubscriber.loadMoreIfNeeded.bind(
|
|
351
427
|
collectionSubscriber,
|
|
352
428
|
subscription
|
|
@@ -364,6 +440,9 @@ export class CollectionConfigBuilder<
|
|
|
364
440
|
// Mark the collections as subscribed in the sync state
|
|
365
441
|
syncState.subscribedToAllCollections = true
|
|
366
442
|
|
|
443
|
+
// Initial status check after all subscriptions are set up
|
|
444
|
+
this.updateLiveQueryStatus(config)
|
|
445
|
+
|
|
367
446
|
return loadMoreDataCallback
|
|
368
447
|
}
|
|
369
448
|
}
|
|
@@ -103,9 +103,9 @@ export class CollectionSubscriber<
|
|
|
103
103
|
// otherwise we end up in an infinite loop trying to load more data
|
|
104
104
|
const dataLoader = sentChanges > 0 ? callback : undefined
|
|
105
105
|
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
106
|
+
// Always call maybeRunGraph to process changes eagerly.
|
|
107
|
+
// The graph will run unless the live query is in an error state.
|
|
108
|
+
// Status management is handled separately via status:change event listeners.
|
|
109
109
|
this.collectionConfigBuilder.maybeRunGraph(
|
|
110
110
|
this.config,
|
|
111
111
|
this.syncState,
|
package/src/types.ts
CHANGED
|
@@ -298,17 +298,15 @@ export type DeleteMutationFn<
|
|
|
298
298
|
*
|
|
299
299
|
* @example
|
|
300
300
|
* // Status transitions
|
|
301
|
-
* // idle → loading →
|
|
301
|
+
* // idle → loading → ready (when markReady() is called)
|
|
302
302
|
* // Any status can transition to → error or cleaned-up
|
|
303
303
|
*/
|
|
304
304
|
export type CollectionStatus =
|
|
305
305
|
/** Collection is created but sync hasn't started yet (when startSync config is false) */
|
|
306
306
|
| `idle`
|
|
307
|
-
/** Sync has started
|
|
307
|
+
/** Sync has started and is loading data */
|
|
308
308
|
| `loading`
|
|
309
|
-
/** Collection
|
|
310
|
-
| `initialCommit`
|
|
311
|
-
/** Collection has received at least one commit and is ready for use */
|
|
309
|
+
/** Collection has been explicitly marked ready via markReady() */
|
|
312
310
|
| `ready`
|
|
313
311
|
/** An error occurred during sync initialization */
|
|
314
312
|
| `error`
|