@tanstack/db 0.0.30 → 0.0.32

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/src/collection.ts CHANGED
@@ -62,9 +62,6 @@ import type {
62
62
  import type { IndexOptions } from "./indexes/index-options.js"
63
63
  import type { BaseIndex, IndexResolver } from "./indexes/base-index.js"
64
64
 
65
- // Store collections in memory
66
- export const collectionsStore = new Map<string, CollectionImpl<any, any, any>>()
67
-
68
65
  interface PendingSyncedTransaction<T extends object = Record<string, unknown>> {
69
66
  committed: boolean
70
67
  operations: Array<OptimisticChangeMessage<T>>
@@ -321,6 +318,12 @@ export class CollectionImpl<
321
318
  const callbacks = [...this.onFirstReadyCallbacks]
322
319
  this.onFirstReadyCallbacks = []
323
320
  callbacks.forEach((callback) => callback())
321
+
322
+ // If the collection is empty when it becomes ready, emit an empty change event
323
+ // to notify subscribers (like LiveQueryCollection) that the collection is ready
324
+ if (this.size === 0 && this.changeListeners.size > 0) {
325
+ this.emitEmptyReadyEvent()
326
+ }
324
327
  }
325
328
  }
326
329
  }
@@ -427,9 +430,6 @@ export class CollectionImpl<
427
430
  autoIndex: config.autoIndex ?? `eager`,
428
431
  }
429
432
 
430
- // Store in global collections store
431
- collectionsStore.set(this.id, this)
432
-
433
433
  // Set up data storage with optional comparison function
434
434
  if (this.config.compare) {
435
435
  this.syncedData = new SortedMap<TKey, T>(this.config.compare)
@@ -687,7 +687,9 @@ export class CollectionImpl<
687
687
  /**
688
688
  * Recompute optimistic state from active transactions
689
689
  */
690
- private recomputeOptimisticState(): void {
690
+ private recomputeOptimisticState(
691
+ triggeredByUserAction: boolean = false
692
+ ): void {
691
693
  // Skip redundant recalculations when we're in the middle of committing sync transactions
692
694
  if (this.isCommittingSyncTransactions) {
693
695
  return
@@ -738,13 +740,26 @@ export class CollectionImpl<
738
740
  this.collectOptimisticChanges(previousState, previousDeletes, events)
739
741
 
740
742
  // Filter out events for recently synced keys to prevent duplicates
741
- const filteredEventsBySyncStatus = events.filter(
742
- (event) => !this.recentlySyncedKeys.has(event.key)
743
- )
743
+ // BUT: Only filter out events that are actually from sync operations
744
+ // New user transactions should NOT be filtered even if the key was recently synced
745
+ const filteredEventsBySyncStatus = events.filter((event) => {
746
+ if (!this.recentlySyncedKeys.has(event.key)) {
747
+ return true // Key not recently synced, allow event through
748
+ }
749
+
750
+ // Key was recently synced - allow if this is a user-triggered action
751
+ if (triggeredByUserAction) {
752
+ return true
753
+ }
754
+
755
+ // Otherwise filter out duplicate sync events
756
+ return false
757
+ })
744
758
 
745
759
  // Filter out redundant delete events if there are pending sync transactions
746
760
  // that will immediately restore the same data, but only for completed transactions
747
- if (this.pendingSyncedTransactions.length > 0) {
761
+ // IMPORTANT: Skip complex filtering for user-triggered actions to prevent UI blocking
762
+ if (this.pendingSyncedTransactions.length > 0 && !triggeredByUserAction) {
748
763
  const pendingSyncKeys = new Set<TKey>()
749
764
  const completedTransactionMutations = new Set<string>()
750
765
 
@@ -788,14 +803,14 @@ export class CollectionImpl<
788
803
  if (filteredEvents.length > 0) {
789
804
  this.updateIndexes(filteredEvents)
790
805
  }
791
- this.emitEvents(filteredEvents)
806
+ this.emitEvents(filteredEvents, triggeredByUserAction)
792
807
  } else {
793
808
  // Update indexes for all events
794
809
  if (filteredEventsBySyncStatus.length > 0) {
795
810
  this.updateIndexes(filteredEventsBySyncStatus)
796
811
  }
797
812
  // Emit all events if no pending sync transactions
798
- this.emitEvents(filteredEventsBySyncStatus)
813
+ this.emitEvents(filteredEventsBySyncStatus, triggeredByUserAction)
799
814
  }
800
815
  }
801
816
 
@@ -873,27 +888,37 @@ export class CollectionImpl<
873
888
  return this.syncedData.get(key)
874
889
  }
875
890
 
891
+ /**
892
+ * Emit an empty ready event to notify subscribers that the collection is ready
893
+ * This bypasses the normal empty array check in emitEvents
894
+ */
895
+ private emitEmptyReadyEvent(): void {
896
+ // Emit empty array directly to all listeners
897
+ for (const listener of this.changeListeners) {
898
+ listener([])
899
+ }
900
+ }
901
+
876
902
  /**
877
903
  * Emit events either immediately or batch them for later emission
878
904
  */
879
905
  private emitEvents(
880
906
  changes: Array<ChangeMessage<T, TKey>>,
881
- endBatching = false
907
+ forceEmit = false
882
908
  ): void {
883
- if (this.shouldBatchEvents && !endBatching) {
909
+ // Skip batching for user actions (forceEmit=true) to keep UI responsive
910
+ if (this.shouldBatchEvents && !forceEmit) {
884
911
  // Add events to the batch
885
912
  this.batchedEvents.push(...changes)
886
913
  return
887
914
  }
888
915
 
889
- // Either we're not batching, or we're ending the batching cycle
916
+ // Either we're not batching, or we're forcing emission (user action or ending batch cycle)
890
917
  let eventsToEmit = changes
891
918
 
892
- if (endBatching) {
893
- // End batching: combine any batched events with new events and clean up state
894
- if (this.batchedEvents.length > 0) {
895
- eventsToEmit = [...this.batchedEvents, ...changes]
896
- }
919
+ // If we have batched events and this is a forced emit, combine them
920
+ if (this.batchedEvents.length > 0 && forceEmit) {
921
+ eventsToEmit = [...this.batchedEvents, ...changes]
897
922
  this.batchedEvents = []
898
923
  this.shouldBatchEvents = false
899
924
  }
@@ -1625,7 +1650,7 @@ export class CollectionImpl<
1625
1650
  ambientTransaction.applyMutations(mutations)
1626
1651
 
1627
1652
  this.transactions.set(ambientTransaction.id, ambientTransaction)
1628
- this.recomputeOptimisticState()
1653
+ this.recomputeOptimisticState(true)
1629
1654
 
1630
1655
  return ambientTransaction
1631
1656
  } else {
@@ -1650,7 +1675,7 @@ export class CollectionImpl<
1650
1675
 
1651
1676
  // Add the transaction to the collection's transactions store
1652
1677
  this.transactions.set(directOpTransaction.id, directOpTransaction)
1653
- this.recomputeOptimisticState()
1678
+ this.recomputeOptimisticState(true)
1654
1679
 
1655
1680
  return directOpTransaction
1656
1681
  }
@@ -1847,7 +1872,7 @@ export class CollectionImpl<
1847
1872
  ambientTransaction.applyMutations(mutations)
1848
1873
 
1849
1874
  this.transactions.set(ambientTransaction.id, ambientTransaction)
1850
- this.recomputeOptimisticState()
1875
+ this.recomputeOptimisticState(true)
1851
1876
 
1852
1877
  return ambientTransaction
1853
1878
  }
@@ -1876,7 +1901,7 @@ export class CollectionImpl<
1876
1901
  // Add the transaction to the collection's transactions store
1877
1902
 
1878
1903
  this.transactions.set(directOpTransaction.id, directOpTransaction)
1879
- this.recomputeOptimisticState()
1904
+ this.recomputeOptimisticState(true)
1880
1905
 
1881
1906
  return directOpTransaction
1882
1907
  }
@@ -1963,7 +1988,7 @@ export class CollectionImpl<
1963
1988
  ambientTransaction.applyMutations(mutations)
1964
1989
 
1965
1990
  this.transactions.set(ambientTransaction.id, ambientTransaction)
1966
- this.recomputeOptimisticState()
1991
+ this.recomputeOptimisticState(true)
1967
1992
 
1968
1993
  return ambientTransaction
1969
1994
  }
@@ -1989,7 +2014,7 @@ export class CollectionImpl<
1989
2014
  directOpTransaction.commit()
1990
2015
 
1991
2016
  this.transactions.set(directOpTransaction.id, directOpTransaction)
1992
- this.recomputeOptimisticState()
2017
+ this.recomputeOptimisticState(true)
1993
2018
 
1994
2019
  return directOpTransaction
1995
2020
  }
@@ -2251,6 +2276,6 @@ export class CollectionImpl<
2251
2276
  // CRITICAL: Capture visible state BEFORE clearing optimistic state
2252
2277
  this.capturePreSyncVisibleState()
2253
2278
 
2254
- this.recomputeOptimisticState()
2279
+ this.recomputeOptimisticState(false)
2255
2280
  }
2256
2281
  }