cry-synced-db-client 0.1.111 → 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
|
-
|
|
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
|
-
|
|
712
|
-
|
|
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
|
-
|
|
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
|
|
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({
|
|
@@ -3615,7 +3678,7 @@ var SyncedDb = class _SyncedDb {
|
|
|
3615
3678
|
}
|
|
3616
3679
|
this.safeCallback(this.onDexieSyncEnd, { calledFrom: "setSyncOnlyTheseCollections", collectionCount: newlyAllowed.length, totalItems, durationMs: Date.now() - dexieStart });
|
|
3617
3680
|
}
|
|
3618
|
-
if (newlyAllowed.length > 0 && this.connectionManager.canSync()
|
|
3681
|
+
if (newlyAllowed.length > 0 && this.connectionManager.canSync()) {
|
|
3619
3682
|
this.sync("setSyncOnlyTheseCollections").catch(() => {
|
|
3620
3683
|
});
|
|
3621
3684
|
}
|
|
@@ -4261,12 +4324,10 @@ var SyncedDb = class _SyncedDb {
|
|
|
4261
4324
|
}
|
|
4262
4325
|
return;
|
|
4263
4326
|
}
|
|
4264
|
-
if (!this.leaderElection.isLeader()) {
|
|
4265
|
-
return;
|
|
4266
|
-
}
|
|
4267
4327
|
if (this.syncLock) return;
|
|
4268
4328
|
this.syncLock = true;
|
|
4269
4329
|
this.syncing = true;
|
|
4330
|
+
this.crossTabSync.startServerSync();
|
|
4270
4331
|
try {
|
|
4271
4332
|
await this.syncEngine.sync(calledFrom);
|
|
4272
4333
|
if (!this.syncOnlyCollections) {
|
|
@@ -4283,6 +4344,7 @@ var SyncedDb = class _SyncedDb {
|
|
|
4283
4344
|
} finally {
|
|
4284
4345
|
this.syncing = false;
|
|
4285
4346
|
this.syncLock = false;
|
|
4347
|
+
this.crossTabSync.endServerSync();
|
|
4286
4348
|
await this.processQueuedWsUpdates();
|
|
4287
4349
|
}
|
|
4288
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.1.113",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"cry-db": "^2.4.24",
|
|
39
39
|
"cry-helpers": "^2.1.193",
|
|
40
|
+
"cry-synced-db-client": "^0.1.111",
|
|
40
41
|
"msgpackr": "^1.11.9",
|
|
41
42
|
"notepack": "^0.0.2",
|
|
42
43
|
"notepack.io": "^3.0.1",
|