cry-synced-db-client 0.1.112 → 0.1.113

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/index.js CHANGED
@@ -599,6 +599,10 @@ var LeaderElectionManager = class {
599
599
  var CrossTabSyncManager = class {
600
600
  constructor(config) {
601
601
  this.pendingBroadcasts = /* @__PURE__ */ new Map();
602
+ /** True while a full server sync is in progress — suppresses delta broadcasts. */
603
+ this.serverSyncInProgress = false;
604
+ /** Collections that received updates while serverSyncInProgress was true. */
605
+ this.syncAffectedCollections = /* @__PURE__ */ new Set();
602
606
  this.tenant = config.tenant;
603
607
  this.instanceId = config.instanceId;
604
608
  this.windowId = config.windowId;
@@ -631,10 +635,18 @@ var CrossTabSyncManager = class {
631
635
  /**
632
636
  * Broadcast updated IDs to other tabs (debounced).
633
637
  * Only the leader should broadcast.
638
+ * While a server sync is in progress, suppresses delta broadcasts and only
639
+ * records which collections were affected (for the post-sync reload broadcast).
634
640
  */
635
641
  broadcastMetaUpdate(updates) {
636
642
  if (!this.metaUpdateChannel) return;
637
643
  if (!this.deps.isLeader()) return;
644
+ if (this.serverSyncInProgress) {
645
+ for (const collection of Object.keys(updates)) {
646
+ this.syncAffectedCollections.add(collection);
647
+ }
648
+ return;
649
+ }
638
650
  for (const [collection, ids] of Object.entries(updates)) {
639
651
  let existing = this.pendingBroadcasts.get(collection);
640
652
  if (!existing) {
@@ -652,6 +664,31 @@ var CrossTabSyncManager = class {
652
664
  this.flushBroadcasts();
653
665
  }, this.debounceMs);
654
666
  }
667
+ /**
668
+ * Called when a full server sync starts.
669
+ * Cancels any pending incremental broadcasts and enables suppression mode.
670
+ */
671
+ startServerSync() {
672
+ this.serverSyncInProgress = true;
673
+ this.syncAffectedCollections.clear();
674
+ if (this.broadcastDebounceTimer) {
675
+ clearTimeout(this.broadcastDebounceTimer);
676
+ this.broadcastDebounceTimer = void 0;
677
+ }
678
+ this.pendingBroadcasts.clear();
679
+ }
680
+ /**
681
+ * Called when a full server sync ends.
682
+ * Emits a single reload broadcast for all collections touched during the sync,
683
+ * replacing the many incremental delta broadcasts that were suppressed.
684
+ */
685
+ endServerSync() {
686
+ this.serverSyncInProgress = false;
687
+ if (this.syncAffectedCollections.size === 0) return;
688
+ const collections = Array.from(this.syncAffectedCollections);
689
+ this.syncAffectedCollections.clear();
690
+ this.broadcastReload(collections);
691
+ }
655
692
  /**
656
693
  * Flush pending broadcasts immediately.
657
694
  */
@@ -667,6 +704,7 @@ var CrossTabSyncManager = class {
667
704
  }
668
705
  if (Object.keys(updates).length > 0) {
669
706
  const payload = {
707
+ type: "delta",
670
708
  updates,
671
709
  instanceId: this.instanceId,
672
710
  isLeader: this.deps.isLeader(),
@@ -705,23 +743,32 @@ var CrossTabSyncManager = class {
705
743
  // Private Methods
706
744
  // ============================================================
707
745
  async handleCrossTabMetaUpdate(payload) {
708
- if (!this.deps.isInitialized()) {
709
- return;
746
+ if (!this.deps.isInitialized()) return;
747
+ if (payload.instanceId === this.instanceId) return;
748
+ if (payload.type === "reload") {
749
+ await this.handleReloadBroadcast(payload);
750
+ } else {
751
+ await this.handleDeltaBroadcast(payload);
710
752
  }
711
- if (payload.instanceId === this.instanceId) {
712
- return;
753
+ }
754
+ async handleReloadBroadcast(payload) {
755
+ const collections = this.deps.getCollections();
756
+ for (const collection of payload.collections) {
757
+ if (!collections.has(collection)) continue;
758
+ if (!this.deps.isSyncAllowed(collection)) continue;
759
+ try {
760
+ await this.deps.reloadCollectionFromDexie(collection);
761
+ } catch (err) {
762
+ console.error(`Error reloading collection ${collection} from Dexie:`, err);
763
+ }
713
764
  }
765
+ }
766
+ async handleDeltaBroadcast(payload) {
714
767
  const collections = this.deps.getCollections();
715
768
  for (const [collection, ids] of Object.entries(payload.updates)) {
716
- if (!collections.has(collection)) {
717
- continue;
718
- }
719
- if (!this.deps.isSyncAllowed(collection)) {
720
- continue;
721
- }
722
- if (ids.length === 0) {
723
- continue;
724
- }
769
+ if (!collections.has(collection)) continue;
770
+ if (!this.deps.isSyncAllowed(collection)) continue;
771
+ if (ids.length === 0) continue;
725
772
  try {
726
773
  const items = await this.deps.dexieDb.getByIds(collection, ids);
727
774
  const upsertBatch = [];
@@ -764,10 +811,25 @@ var CrossTabSyncManager = class {
764
811
  }
765
812
  }
766
813
  } catch (err) {
767
- console.error(`Error handling cross-tab meta update for ${collection}:`, err);
814
+ console.error(`Error handling cross-tab delta update for ${collection}:`, err);
768
815
  }
769
816
  }
770
817
  }
818
+ broadcastReload(collections) {
819
+ if (!this.metaUpdateChannel) return;
820
+ const payload = {
821
+ type: "reload",
822
+ collections,
823
+ instanceId: this.instanceId,
824
+ isLeader: this.deps.isLeader(),
825
+ windowId: this.windowId
826
+ };
827
+ try {
828
+ this.metaUpdateChannel.postMessage(payload);
829
+ } catch (err) {
830
+ console.error("Failed to broadcast reload:", err);
831
+ }
832
+ }
771
833
  callOnInfrastructureError(type, message, error) {
772
834
  if (this.callbacks.onInfrastructureError) {
773
835
  try {
@@ -3435,7 +3497,8 @@ var SyncedDb = class _SyncedDb {
3435
3497
  writeToInMemBatch: (collection, items, operation) => {
3436
3498
  this.inMemManager.writeBatch(collection, items, operation);
3437
3499
  },
3438
- isSyncAllowed: (collection) => this.isSyncAllowed(collection)
3500
+ isSyncAllowed: (collection) => this.isSyncAllowed(collection),
3501
+ reloadCollectionFromDexie: (collection) => this.loadCollectionToInMem(collection).then(() => void 0)
3439
3502
  }
3440
3503
  });
3441
3504
  this.connectionManager = new ConnectionManager({
@@ -4264,6 +4327,7 @@ var SyncedDb = class _SyncedDb {
4264
4327
  if (this.syncLock) return;
4265
4328
  this.syncLock = true;
4266
4329
  this.syncing = true;
4330
+ this.crossTabSync.startServerSync();
4267
4331
  try {
4268
4332
  await this.syncEngine.sync(calledFrom);
4269
4333
  if (!this.syncOnlyCollections) {
@@ -4280,6 +4344,7 @@ var SyncedDb = class _SyncedDb {
4280
4344
  } finally {
4281
4345
  this.syncing = false;
4282
4346
  this.syncLock = false;
4347
+ this.crossTabSync.endServerSync();
4283
4348
  await this.processQueuedWsUpdates();
4284
4349
  }
4285
4350
  }
@@ -16,6 +16,10 @@ export declare class CrossTabSyncManager implements I_CrossTabSyncManager {
16
16
  private metaUpdateChannel?;
17
17
  private pendingBroadcasts;
18
18
  private broadcastDebounceTimer?;
19
+ /** True while a full server sync is in progress — suppresses delta broadcasts. */
20
+ private serverSyncInProgress;
21
+ /** Collections that received updates while serverSyncInProgress was true. */
22
+ private syncAffectedCollections;
19
23
  constructor(config: CrossTabSyncConfig);
20
24
  /**
21
25
  * Get debounce time in ms.
@@ -28,8 +32,21 @@ export declare class CrossTabSyncManager implements I_CrossTabSyncManager {
28
32
  /**
29
33
  * Broadcast updated IDs to other tabs (debounced).
30
34
  * Only the leader should broadcast.
35
+ * While a server sync is in progress, suppresses delta broadcasts and only
36
+ * records which collections were affected (for the post-sync reload broadcast).
31
37
  */
32
38
  broadcastMetaUpdate(updates: Record<string, string[]>): void;
39
+ /**
40
+ * Called when a full server sync starts.
41
+ * Cancels any pending incremental broadcasts and enables suppression mode.
42
+ */
43
+ startServerSync(): void;
44
+ /**
45
+ * Called when a full server sync ends.
46
+ * Emits a single reload broadcast for all collections touched during the sync,
47
+ * replacing the many incremental delta broadcasts that were suppressed.
48
+ */
49
+ endServerSync(): void;
33
50
  /**
34
51
  * Flush pending broadcasts immediately.
35
52
  */
@@ -43,5 +60,8 @@ export declare class CrossTabSyncManager implements I_CrossTabSyncManager {
43
60
  */
44
61
  handleExternalBroadcast(payload: MetaUpdateBroadcast): void;
45
62
  private handleCrossTabMetaUpdate;
63
+ private handleReloadBroadcast;
64
+ private handleDeltaBroadcast;
65
+ private broadcastReload;
46
66
  private callOnInfrastructureError;
47
67
  }
@@ -52,6 +52,8 @@ export interface CrossTabSyncDeps {
52
52
  writeToInMemBatch: <T extends DbEntity>(collection: string, items: T[], operation: "upsert" | "delete") => void;
53
53
  /** Whether a collection participates in sync (not writeOnly, not filtered out). */
54
54
  isSyncAllowed: (collection: string) => boolean;
55
+ /** Reload a collection fully from Dexie into in-mem (called on reload broadcast). */
56
+ reloadCollectionFromDexie: (collection: string) => Promise<void>;
55
57
  }
56
58
  export interface CrossTabSyncConfig {
57
59
  tenant: string;
@@ -70,6 +72,16 @@ export interface I_CrossTabSyncManager {
70
72
  broadcastMetaUpdate(updates: Record<string, string[]>): void;
71
73
  /** Flush pending broadcasts immediately. */
72
74
  flushBroadcasts(): void;
75
+ /**
76
+ * Called when a full server sync starts.
77
+ * Suppresses incremental delta broadcasts and accumulates affected collections.
78
+ */
79
+ startServerSync(): void;
80
+ /**
81
+ * Called when a full server sync ends.
82
+ * Emits a single reload broadcast for all collections touched during the sync.
83
+ */
84
+ endServerSync(): void;
73
85
  /** Cleanup resources. */
74
86
  dispose(): void;
75
87
  /** Handle external broadcast (for testing). */
@@ -27,13 +27,8 @@ export interface DirtyChange {
27
27
  /** When last change was accumulated */
28
28
  updatedAt: number;
29
29
  }
30
- /**
31
- * Payload for cross-tab meta update broadcasts
32
- * Maps collection names to arrays of _id strings that were updated
33
- */
34
- export interface MetaUpdateBroadcast {
35
- /** Map of collection name -> array of _id strings that were updated */
36
- updates: Record<string, string[]>;
30
+ /** Shared fields for all cross-tab broadcast messages */
31
+ interface BroadcastBase {
37
32
  /** Unique ID of the SyncedDb instance that sent this broadcast */
38
33
  instanceId: string;
39
34
  /** Whether the sender is the leader tab */
@@ -41,6 +36,27 @@ export interface MetaUpdateBroadcast {
41
36
  /** Window ID of the sender (for debugging) */
42
37
  windowId: string;
43
38
  }
39
+ /**
40
+ * Incremental cross-tab broadcast: specific IDs updated per collection.
41
+ * Sent after individual writes (user edits, WebSocket updates).
42
+ */
43
+ export interface DeltaBroadcast extends BroadcastBase {
44
+ type: "delta";
45
+ /** Map of collection name -> array of _id strings that were updated */
46
+ updates: Record<string, string[]>;
47
+ }
48
+ /**
49
+ * Full-reload cross-tab broadcast: receiving tab must reload listed collections
50
+ * from Dexie into in-mem. Sent once after a full server sync completes instead
51
+ * of emitting many incremental delta broadcasts during the sync.
52
+ */
53
+ export interface ReloadBroadcast extends BroadcastBase {
54
+ type: "reload";
55
+ /** Collections that were affected by the sync and need a full reload */
56
+ collections: string[];
57
+ }
58
+ /** Discriminated union of all cross-tab broadcast message types */
59
+ export type MetaUpdateBroadcast = DeltaBroadcast | ReloadBroadcast;
44
60
  /**
45
61
  * Interface za Dexie (IndexedDB) bazo podatkov
46
62
  * Vse metode so async ker IndexedDB je asinhrona
@@ -101,3 +117,4 @@ export interface I_DexieDb {
101
117
  /** Vrne tenant */
102
118
  getTenant(): string;
103
119
  }
120
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cry-synced-db-client",
3
- "version": "0.1.112",
3
+ "version": "0.1.113",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",