@tanstack/db 0.4.7 → 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/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/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/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/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/state.ts +52 -48
- package/src/collection/sync.ts +7 -6
- package/src/indexes/auto-index.ts +0 -8
- 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
package/src/collection/state.ts
CHANGED
|
@@ -12,11 +12,18 @@ import type { CollectionLifecycleManager } from "./lifecycle"
|
|
|
12
12
|
import type { CollectionChangesManager } from "./changes"
|
|
13
13
|
import type { CollectionIndexesManager } from "./indexes"
|
|
14
14
|
|
|
15
|
-
interface PendingSyncedTransaction<
|
|
15
|
+
interface PendingSyncedTransaction<
|
|
16
|
+
T extends object = Record<string, unknown>,
|
|
17
|
+
TKey extends string | number = string | number,
|
|
18
|
+
> {
|
|
16
19
|
committed: boolean
|
|
17
20
|
operations: Array<OptimisticChangeMessage<T>>
|
|
18
21
|
truncate?: boolean
|
|
19
22
|
deletedKeys: Set<string | number>
|
|
23
|
+
optimisticSnapshot?: {
|
|
24
|
+
upserts: Map<TKey, T>
|
|
25
|
+
deletes: Set<TKey>
|
|
26
|
+
}
|
|
20
27
|
}
|
|
21
28
|
|
|
22
29
|
export class CollectionStateManager<
|
|
@@ -33,8 +40,9 @@ export class CollectionStateManager<
|
|
|
33
40
|
|
|
34
41
|
// Core state - make public for testing
|
|
35
42
|
public transactions: SortedMap<string, Transaction<any>>
|
|
36
|
-
public pendingSyncedTransactions: Array<
|
|
37
|
-
|
|
43
|
+
public pendingSyncedTransactions: Array<
|
|
44
|
+
PendingSyncedTransaction<TOutput, TKey>
|
|
45
|
+
> = []
|
|
38
46
|
public syncedData: Map<TKey, TOutput> | SortedMap<TKey, TOutput>
|
|
39
47
|
public syncedMetadata = new Map<TKey, unknown>()
|
|
40
48
|
|
|
@@ -442,10 +450,10 @@ export class CollectionStateManager<
|
|
|
442
450
|
},
|
|
443
451
|
{
|
|
444
452
|
committedSyncedTransactions: [] as Array<
|
|
445
|
-
PendingSyncedTransaction<TOutput>
|
|
453
|
+
PendingSyncedTransaction<TOutput, TKey>
|
|
446
454
|
>,
|
|
447
455
|
uncommittedSyncedTransactions: [] as Array<
|
|
448
|
-
PendingSyncedTransaction<TOutput>
|
|
456
|
+
PendingSyncedTransaction<TOutput, TKey>
|
|
449
457
|
>,
|
|
450
458
|
hasTruncateSync: false,
|
|
451
459
|
}
|
|
@@ -455,6 +463,12 @@ export class CollectionStateManager<
|
|
|
455
463
|
// Set flag to prevent redundant optimistic state recalculations
|
|
456
464
|
this.isCommittingSyncTransactions = true
|
|
457
465
|
|
|
466
|
+
// Get the optimistic snapshot from the truncate transaction (captured when truncate() was called)
|
|
467
|
+
const truncateOptimisticSnapshot = hasTruncateSync
|
|
468
|
+
? committedSyncedTransactions.find((t) => t.truncate)
|
|
469
|
+
?.optimisticSnapshot
|
|
470
|
+
: null
|
|
471
|
+
|
|
458
472
|
// First collect all keys that will be affected by sync operations
|
|
459
473
|
const changedKeys = new Set<TKey>()
|
|
460
474
|
for (const transaction of committedSyncedTransactions) {
|
|
@@ -484,13 +498,19 @@ export class CollectionStateManager<
|
|
|
484
498
|
// Handle truncate operations first
|
|
485
499
|
if (transaction.truncate) {
|
|
486
500
|
// TRUNCATE PHASE
|
|
487
|
-
// 1) Emit a delete for every
|
|
501
|
+
// 1) Emit a delete for every visible key (synced + optimistic) so downstream listeners/indexes
|
|
488
502
|
// observe a clear-before-rebuild. We intentionally skip keys already in
|
|
489
503
|
// optimisticDeletes because their delete was previously emitted by the user.
|
|
490
|
-
for
|
|
491
|
-
|
|
504
|
+
// Use the snapshot to ensure we emit deletes for all items that existed at truncate start.
|
|
505
|
+
const visibleKeys = new Set([
|
|
506
|
+
...this.syncedData.keys(),
|
|
507
|
+
...(truncateOptimisticSnapshot?.upserts.keys() || []),
|
|
508
|
+
])
|
|
509
|
+
for (const key of visibleKeys) {
|
|
510
|
+
if (truncateOptimisticSnapshot?.deletes.has(key)) continue
|
|
492
511
|
const previousValue =
|
|
493
|
-
|
|
512
|
+
truncateOptimisticSnapshot?.upserts.get(key) ||
|
|
513
|
+
this.syncedData.get(key)
|
|
494
514
|
if (previousValue !== undefined) {
|
|
495
515
|
events.push({ type: `delete`, key, value: previousValue })
|
|
496
516
|
}
|
|
@@ -574,41 +594,14 @@ export class CollectionStateManager<
|
|
|
574
594
|
}
|
|
575
595
|
}
|
|
576
596
|
|
|
577
|
-
// Build re-apply sets from
|
|
578
|
-
//
|
|
579
|
-
const reapplyUpserts = new Map<TKey, TOutput>(
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
if (
|
|
586
|
-
!this.isThisCollection(mutation.collection) ||
|
|
587
|
-
!mutation.optimistic
|
|
588
|
-
)
|
|
589
|
-
continue
|
|
590
|
-
const key = mutation.key as TKey
|
|
591
|
-
switch (mutation.type) {
|
|
592
|
-
case `insert`:
|
|
593
|
-
reapplyUpserts.set(key, mutation.modified as TOutput)
|
|
594
|
-
reapplyDeletes.delete(key)
|
|
595
|
-
break
|
|
596
|
-
case `update`: {
|
|
597
|
-
const base = this.syncedData.get(key)
|
|
598
|
-
const next = base
|
|
599
|
-
? (Object.assign({}, base, mutation.changes) as TOutput)
|
|
600
|
-
: (mutation.modified as TOutput)
|
|
601
|
-
reapplyUpserts.set(key, next)
|
|
602
|
-
reapplyDeletes.delete(key)
|
|
603
|
-
break
|
|
604
|
-
}
|
|
605
|
-
case `delete`:
|
|
606
|
-
reapplyUpserts.delete(key)
|
|
607
|
-
reapplyDeletes.add(key)
|
|
608
|
-
break
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
}
|
|
597
|
+
// Build re-apply sets from the snapshot taken at the start of this function.
|
|
598
|
+
// This prevents losing optimistic state if transactions complete during truncate processing.
|
|
599
|
+
const reapplyUpserts = new Map<TKey, TOutput>(
|
|
600
|
+
truncateOptimisticSnapshot!.upserts
|
|
601
|
+
)
|
|
602
|
+
const reapplyDeletes = new Set<TKey>(
|
|
603
|
+
truncateOptimisticSnapshot!.deletes
|
|
604
|
+
)
|
|
612
605
|
|
|
613
606
|
// Emit inserts for re-applied upserts, skipping any keys that have an optimistic delete.
|
|
614
607
|
// If the server also inserted/updated the same key in this batch, override that value
|
|
@@ -660,6 +653,20 @@ export class CollectionStateManager<
|
|
|
660
653
|
|
|
661
654
|
// Reset flag and recompute optimistic state for any remaining active transactions
|
|
662
655
|
this.isCommittingSyncTransactions = false
|
|
656
|
+
|
|
657
|
+
// If we had a truncate, restore the preserved optimistic state from the snapshot
|
|
658
|
+
// This includes items from transactions that may have completed during processing
|
|
659
|
+
if (hasTruncateSync && truncateOptimisticSnapshot) {
|
|
660
|
+
for (const [key, value] of truncateOptimisticSnapshot.upserts) {
|
|
661
|
+
this.optimisticUpserts.set(key, value)
|
|
662
|
+
}
|
|
663
|
+
for (const key of truncateOptimisticSnapshot.deletes) {
|
|
664
|
+
this.optimisticDeletes.add(key)
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Always overlay any still-active optimistic transactions so mutations that started
|
|
669
|
+
// after the truncate snapshot are preserved.
|
|
663
670
|
for (const transaction of this.transactions.values()) {
|
|
664
671
|
if (![`completed`, `failed`].includes(transaction.state)) {
|
|
665
672
|
for (const mutation of transaction.mutations) {
|
|
@@ -785,12 +792,9 @@ export class CollectionStateManager<
|
|
|
785
792
|
this.recentlySyncedKeys.clear()
|
|
786
793
|
})
|
|
787
794
|
|
|
788
|
-
//
|
|
795
|
+
// Mark that we've received the first commit (for tracking purposes)
|
|
789
796
|
if (!this.hasReceivedFirstCommit) {
|
|
790
797
|
this.hasReceivedFirstCommit = true
|
|
791
|
-
const callbacks = [...this.lifecycle.onFirstReadyCallbacks]
|
|
792
|
-
this.lifecycle.onFirstReadyCallbacks = []
|
|
793
|
-
callbacks.forEach((callback) => callback())
|
|
794
798
|
}
|
|
795
799
|
}
|
|
796
800
|
}
|
package/src/collection/sync.ts
CHANGED
|
@@ -148,12 +148,6 @@ export class CollectionSyncManager<
|
|
|
148
148
|
|
|
149
149
|
pendingTransaction.committed = true
|
|
150
150
|
|
|
151
|
-
// Update status to initialCommit when transitioning from loading
|
|
152
|
-
// This indicates we're in the process of committing the first transaction
|
|
153
|
-
if (this.lifecycle.status === `loading`) {
|
|
154
|
-
this.lifecycle.setStatus(`initialCommit`)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
151
|
this.state.commitPendingTransactions()
|
|
158
152
|
},
|
|
159
153
|
markReady: () => {
|
|
@@ -181,6 +175,13 @@ export class CollectionSyncManager<
|
|
|
181
175
|
// - Subsequent synced ops applied on the fresh base
|
|
182
176
|
// - Finally, optimistic mutations re-applied on top (single batch)
|
|
183
177
|
pendingTransaction.truncate = true
|
|
178
|
+
|
|
179
|
+
// Capture optimistic state NOW to preserve it even if transactions complete
|
|
180
|
+
// before this truncate transaction is committed
|
|
181
|
+
pendingTransaction.optimisticSnapshot = {
|
|
182
|
+
upserts: new Map(this.state.optimisticUpserts),
|
|
183
|
+
deletes: new Set(this.state.optimisticDeletes),
|
|
184
|
+
}
|
|
184
185
|
},
|
|
185
186
|
})
|
|
186
187
|
)
|
|
@@ -14,14 +14,6 @@ function shouldAutoIndex(collection: CollectionImpl<any, any, any, any, any>) {
|
|
|
14
14
|
return false
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
// Don't auto-index during sync operations
|
|
18
|
-
if (
|
|
19
|
-
collection.status === `loading` ||
|
|
20
|
-
collection.status === `initialCommit`
|
|
21
|
-
) {
|
|
22
|
-
return false
|
|
23
|
-
}
|
|
24
|
-
|
|
25
17
|
return true
|
|
26
18
|
}
|
|
27
19
|
|
|
@@ -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`
|