cry-synced-db-client 0.1.170 → 0.1.171
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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,102 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
### Runtime collection registration (`addCollectionToSync`, `replaceSyncCollection`)
|
|
6
|
+
|
|
7
|
+
Two methods to install / replace collection configs at runtime; both load the
|
|
8
|
+
Dexie cursor into the sync-meta cache, hydrate in-mem from Dexie, extend an
|
|
9
|
+
active `syncOnlyTheseCollections` filter, and fire a one-shot **targeted**
|
|
10
|
+
download for just that collection (`syncCollectionForFind` →
|
|
11
|
+
`processCollectionServerData`, cursor advances inline). Other collections
|
|
12
|
+
are not re-fetched.
|
|
13
|
+
|
|
14
|
+
**`addCollectionToSync(spec)` — safe, idempotent** (Promise<void>)
|
|
15
|
+
|
|
16
|
+
| Existing config | Action |
|
|
17
|
+
|---|---|
|
|
18
|
+
| permanent (`temporaryConfig !== true`) | no-op |
|
|
19
|
+
| temporary (`temporaryConfig === true`) | replaced |
|
|
20
|
+
| none | added |
|
|
21
|
+
|
|
22
|
+
**`replaceSyncCollection(spec)` — explicit re-config** (Promise<boolean>)
|
|
23
|
+
|
|
24
|
+
| Existing | New | Result |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| none | any | install, returns `true` |
|
|
27
|
+
| temporary | any | replace, returns `true` |
|
|
28
|
+
| permanent | permanent | replace, returns `true` |
|
|
29
|
+
| permanent | temporary | **blocked, returns `false`** (no-op) |
|
|
30
|
+
|
|
31
|
+
The only blocked transition is permanent → temporary (cannot downgrade an
|
|
32
|
+
established config to provisional).
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// Boot with a provisional config so the Dexie schema includes the
|
|
36
|
+
// collection but the real query/conflict-resolver is deferred.
|
|
37
|
+
new SyncedDb({ collections: [{ name: "obravnave", temporaryConfig: true }] });
|
|
38
|
+
|
|
39
|
+
// Idempotent install after login:
|
|
40
|
+
await syncedDb.addCollectionToSync({
|
|
41
|
+
name: "obravnave",
|
|
42
|
+
syncConfig: { query: () => ({ lokacija_id: currentLokacijaId }) },
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Later, user switches lokacija — explicit re-config:
|
|
46
|
+
const ok = await syncedDb.replaceSyncCollection({
|
|
47
|
+
name: "obravnave",
|
|
48
|
+
syncConfig: { query: () => ({ lokacija_id: newLokacijaId }) },
|
|
49
|
+
});
|
|
50
|
+
// ok === true (replacement of permanent with permanent allowed)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### `flushToServer()` + automatic flush on `visibilitychange:hidden`
|
|
54
|
+
|
|
55
|
+
`SyncedDb.flushToServer(calledFrom?)` pushes dirty data to the server
|
|
56
|
+
immediately, bypassing `debounceRestWritesMs`. Flow: cancel REST upload
|
|
57
|
+
debounce → `flushAll()` (pending Dexie writes) → `awaitRestUpload()`
|
|
58
|
+
(in-flight upload) → `uploadDirtyItems(calledFrom)`. No-op when offline,
|
|
59
|
+
forced offline, or pre-`init`.
|
|
60
|
+
|
|
61
|
+
A `visibilitychange` listener fires `flushToServer("visibility-hidden")`
|
|
62
|
+
automatically when the tab becomes `hidden`. Browser may throttle or
|
|
63
|
+
suspend a hidden tab shortly after the event, so the debounced upload
|
|
64
|
+
could otherwise never run.
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// Manual flush (e.g. before navigation)
|
|
68
|
+
await syncedDb.flushToServer("pre-navigate");
|
|
69
|
+
|
|
70
|
+
// Automatic: no caller action required. Listener registered in init(),
|
|
71
|
+
// cleaned up in close().
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Upload itself is **not gated on `isLeader`** — `PendingChangesManager`
|
|
75
|
+
guards only on `canSync()` — so followers also drain their dirty queue
|
|
76
|
+
on hidden. Cancelling the timer cancels only the *current* pending burst:
|
|
77
|
+
subsequent writes schedule a new timer and the auto-sync interval still
|
|
78
|
+
fires. To permanently stop uploads, call `close()` or `forceOffline(true)`.
|
|
79
|
+
|
|
80
|
+
### Leadership transition timestamps (`leaderSince` / `followerSince`)
|
|
81
|
+
|
|
82
|
+
Two new getters on `SyncedDb` (and `I_LeaderElectionManager`):
|
|
83
|
+
|
|
84
|
+
- `leaderSince(): Date | undefined` — when this tab acquired the Web Locks
|
|
85
|
+
leader lock; `undefined` while a follower
|
|
86
|
+
- `followerSince(): Date | undefined` — when this tab transitioned to
|
|
87
|
+
follower (or, for tabs that never claimed leadership, the construction
|
|
88
|
+
timestamp); `undefined` while the leader
|
|
89
|
+
|
|
90
|
+
Exactly one of the two is defined at any time after construction. Useful
|
|
91
|
+
for heartbeat payloads / debug overlays (`Date.now() - leaderSince()` =
|
|
92
|
+
"time in current leadership state").
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
const isLeader = syncedDb.isLeaderTab();
|
|
96
|
+
const sinceMs = isLeader
|
|
97
|
+
? Date.now() - syncedDb.leaderSince()!.getTime()
|
|
98
|
+
: Date.now() - syncedDb.followerSince()!.getTime();
|
|
99
|
+
```
|
|
100
|
+
|
|
5
101
|
### `onServerSyncWrite` callback
|
|
6
102
|
|
|
7
103
|
Single-shot callback that fires once per `restInterface.updateCollections`
|
package/dist/index.js
CHANGED
|
@@ -876,6 +876,7 @@ var LeaderElectionManager = class {
|
|
|
876
876
|
this.isLeaderFlag = false;
|
|
877
877
|
this.closing = false;
|
|
878
878
|
this.releasingDueToVisibility = false;
|
|
879
|
+
this._followerSince = /* @__PURE__ */ new Date();
|
|
879
880
|
this.tenant = config.tenant;
|
|
880
881
|
this.windowId = config.windowId;
|
|
881
882
|
this.callbacks = config.callbacks;
|
|
@@ -973,6 +974,21 @@ var LeaderElectionManager = class {
|
|
|
973
974
|
isLeader() {
|
|
974
975
|
return this.isLeaderFlag;
|
|
975
976
|
}
|
|
977
|
+
/**
|
|
978
|
+
* Returns the timestamp when this tab became leader, or undefined if currently a follower.
|
|
979
|
+
* Cleared on transition to follower.
|
|
980
|
+
*/
|
|
981
|
+
leaderSince() {
|
|
982
|
+
return this._leaderSince;
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Returns the timestamp when this tab became follower, or undefined if currently a leader.
|
|
986
|
+
* Initialized at construction (every tab starts as a follower until it claims the lock).
|
|
987
|
+
* Cleared on transition to leader.
|
|
988
|
+
*/
|
|
989
|
+
followerSince() {
|
|
990
|
+
return this._followerSince;
|
|
991
|
+
}
|
|
976
992
|
/**
|
|
977
993
|
* Cleanup resources.
|
|
978
994
|
*/
|
|
@@ -1024,6 +1040,8 @@ var LeaderElectionManager = class {
|
|
|
1024
1040
|
}
|
|
1025
1041
|
}
|
|
1026
1042
|
callOnBecameLeader() {
|
|
1043
|
+
this._leaderSince = /* @__PURE__ */ new Date();
|
|
1044
|
+
this._followerSince = void 0;
|
|
1027
1045
|
if (this.callbacks.onBecameLeader) {
|
|
1028
1046
|
try {
|
|
1029
1047
|
this.callbacks.onBecameLeader();
|
|
@@ -1033,6 +1051,8 @@ var LeaderElectionManager = class {
|
|
|
1033
1051
|
}
|
|
1034
1052
|
}
|
|
1035
1053
|
callOnLostLeadership() {
|
|
1054
|
+
this._followerSince = /* @__PURE__ */ new Date();
|
|
1055
|
+
this._leaderSince = void 0;
|
|
1036
1056
|
if (this.callbacks.onLostLeadership) {
|
|
1037
1057
|
try {
|
|
1038
1058
|
this.callbacks.onLostLeadership();
|
|
@@ -4499,6 +4519,55 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4499
4519
|
isLeaderTab() {
|
|
4500
4520
|
return this.leaderElection.isLeader();
|
|
4501
4521
|
}
|
|
4522
|
+
leaderSince() {
|
|
4523
|
+
return this.leaderElection.leaderSince();
|
|
4524
|
+
}
|
|
4525
|
+
followerSince() {
|
|
4526
|
+
return this.leaderElection.followerSince();
|
|
4527
|
+
}
|
|
4528
|
+
/**
|
|
4529
|
+
* Register a collection for sync at runtime. See `I_SyncedDb.addCollectionToSync`.
|
|
4530
|
+
*/
|
|
4531
|
+
async addCollectionToSync(spec) {
|
|
4532
|
+
const existing = this.collections.get(spec.name);
|
|
4533
|
+
if (existing && !existing.temporaryConfig) {
|
|
4534
|
+
return;
|
|
4535
|
+
}
|
|
4536
|
+
await this._installCollectionConfig(spec);
|
|
4537
|
+
}
|
|
4538
|
+
/**
|
|
4539
|
+
* Replace the collection config and re-sync. See `I_SyncedDb.replaceSyncCollection`.
|
|
4540
|
+
*/
|
|
4541
|
+
async replaceSyncCollection(spec) {
|
|
4542
|
+
const existing = this.collections.get(spec.name);
|
|
4543
|
+
if (existing && !existing.temporaryConfig && spec.temporaryConfig) {
|
|
4544
|
+
return false;
|
|
4545
|
+
}
|
|
4546
|
+
await this._installCollectionConfig(spec);
|
|
4547
|
+
return true;
|
|
4548
|
+
}
|
|
4549
|
+
/**
|
|
4550
|
+
* Shared install path for addCollectionToSync / replaceSyncCollection:
|
|
4551
|
+
* write config, extend active sync filter, load Dexie cursor + in-mem,
|
|
4552
|
+
* fire a targeted one-shot download for just this collection.
|
|
4553
|
+
*/
|
|
4554
|
+
async _installCollectionConfig(spec) {
|
|
4555
|
+
var _a;
|
|
4556
|
+
this.collections.set(spec.name, spec);
|
|
4557
|
+
if (this.syncOnlyCollections) {
|
|
4558
|
+
this.syncOnlyCollections.add(spec.name);
|
|
4559
|
+
}
|
|
4560
|
+
const meta = await this.dexieDb.getSyncMeta(spec.name);
|
|
4561
|
+
if (meta) this.syncMetaCache.set(spec.name, meta);
|
|
4562
|
+
if (!spec.writeOnly) {
|
|
4563
|
+
await this.loadCollectionToInMem(spec.name);
|
|
4564
|
+
}
|
|
4565
|
+
if (!spec.writeOnly && this.connectionManager.canSync()) {
|
|
4566
|
+
const rawQuery = (_a = spec.syncConfig) == null ? void 0 : _a.query;
|
|
4567
|
+
const query = typeof rawQuery === "function" ? rawQuery() : rawQuery;
|
|
4568
|
+
await this.syncCollectionForFind(spec.name, query, { returnDeleted: true });
|
|
4569
|
+
}
|
|
4570
|
+
}
|
|
4502
4571
|
/**
|
|
4503
4572
|
* Restrict sync to only these collections. When non-empty, only the listed
|
|
4504
4573
|
* collections load from Dexie→in-mem and download from server. Pass an empty
|
|
@@ -4616,6 +4685,15 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4616
4685
|
};
|
|
4617
4686
|
window.addEventListener("beforeunload", this.beforeUnloadHandler);
|
|
4618
4687
|
}
|
|
4688
|
+
if (typeof document !== "undefined") {
|
|
4689
|
+
this.visibilityFlushHandler = () => {
|
|
4690
|
+
if (document.visibilityState !== "hidden") return;
|
|
4691
|
+
this.flushToServer("visibility-hidden").catch((err) => {
|
|
4692
|
+
console.warn("flushToServer on visibility-hidden failed:", err == null ? void 0 : err.message);
|
|
4693
|
+
});
|
|
4694
|
+
};
|
|
4695
|
+
document.addEventListener("visibilitychange", this.visibilityFlushHandler);
|
|
4696
|
+
}
|
|
4619
4697
|
this.initialized = true;
|
|
4620
4698
|
}
|
|
4621
4699
|
/**
|
|
@@ -4626,6 +4704,25 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4626
4704
|
async flush() {
|
|
4627
4705
|
await this.pendingChanges.flushAll();
|
|
4628
4706
|
}
|
|
4707
|
+
/**
|
|
4708
|
+
* Push dirty data to the server immediately, bypassing the upload debounce.
|
|
4709
|
+
*
|
|
4710
|
+
* Flushes pending Dexie writes, awaits any in-flight REST upload, then fires
|
|
4711
|
+
* a fresh `uploadDirtyItems()`. Called automatically when the tab becomes
|
|
4712
|
+
* hidden so data is not stranded by browser tab-throttling / suspension.
|
|
4713
|
+
*
|
|
4714
|
+
* No-op when offline, forced offline, or not yet initialized.
|
|
4715
|
+
*
|
|
4716
|
+
* @param calledFrom Diagnostic tag threaded through to upload callbacks
|
|
4717
|
+
*/
|
|
4718
|
+
async flushToServer(calledFrom) {
|
|
4719
|
+
if (!this.initialized) return;
|
|
4720
|
+
if (!this.connectionManager.canSync()) return;
|
|
4721
|
+
this.pendingChanges.cancelRestUploadTimer();
|
|
4722
|
+
await this.pendingChanges.flushAll();
|
|
4723
|
+
await this.pendingChanges.awaitRestUpload();
|
|
4724
|
+
await this.syncEngine.uploadDirtyItems(calledFrom);
|
|
4725
|
+
}
|
|
4629
4726
|
/**
|
|
4630
4727
|
* Returns when all collections were last successfully synced
|
|
4631
4728
|
* from the server, or undefined if never.
|
|
@@ -4706,6 +4803,10 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4706
4803
|
window.removeEventListener("beforeunload", this.beforeUnloadHandler);
|
|
4707
4804
|
this.beforeUnloadHandler = void 0;
|
|
4708
4805
|
}
|
|
4806
|
+
if (typeof document !== "undefined" && this.visibilityFlushHandler) {
|
|
4807
|
+
document.removeEventListener("visibilitychange", this.visibilityFlushHandler);
|
|
4808
|
+
this.visibilityFlushHandler = void 0;
|
|
4809
|
+
}
|
|
4709
4810
|
this.syncMetaCache.clear();
|
|
4710
4811
|
for (const collectionName of this.collections.keys()) {
|
|
4711
4812
|
this.inMemManager.clearCollection(collectionName);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AggregateOptions } from "mongodb";
|
|
2
|
-
import type { I_SyncedDb, SyncedDbConfig, WsNotificationInfo, EvictionInfo, EvictionCollectionInfo } from "../types/I_SyncedDb";
|
|
2
|
+
import type { I_SyncedDb, SyncedDbConfig, CollectionConfig, WsNotificationInfo, EvictionInfo, EvictionCollectionInfo } from "../types/I_SyncedDb";
|
|
3
3
|
import type { DirtyMeta, MetaUpdateBroadcast } from "../types/I_DexieDb";
|
|
4
4
|
import type { QuerySpec, QueryOpts, UpdateSpec, InsertSpec, BatchSpec } from "../types/I_RestInterface";
|
|
5
5
|
import type { Id, DbEntity } from "../types/DbEntity";
|
|
@@ -36,6 +36,7 @@ export declare class SyncedDb implements I_SyncedDb {
|
|
|
36
36
|
private unsubscribeServerUpdates?;
|
|
37
37
|
private cleanupNotifierCallbacks?;
|
|
38
38
|
private beforeUnloadHandler?;
|
|
39
|
+
private visibilityFlushHandler?;
|
|
39
40
|
private readonly defaultReturnDeleted;
|
|
40
41
|
private readonly defaultReturnArchived;
|
|
41
42
|
private readonly onDatabaseCreated?;
|
|
@@ -62,6 +63,22 @@ export declare class SyncedDb implements I_SyncedDb {
|
|
|
62
63
|
getInstanceId(): string;
|
|
63
64
|
getCrossTabSyncDebounceMs(): number;
|
|
64
65
|
isLeaderTab(): boolean;
|
|
66
|
+
leaderSince(): Date | undefined;
|
|
67
|
+
followerSince(): Date | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* Register a collection for sync at runtime. See `I_SyncedDb.addCollectionToSync`.
|
|
70
|
+
*/
|
|
71
|
+
addCollectionToSync(spec: CollectionConfig): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Replace the collection config and re-sync. See `I_SyncedDb.replaceSyncCollection`.
|
|
74
|
+
*/
|
|
75
|
+
replaceSyncCollection(spec: CollectionConfig): Promise<boolean>;
|
|
76
|
+
/**
|
|
77
|
+
* Shared install path for addCollectionToSync / replaceSyncCollection:
|
|
78
|
+
* write config, extend active sync filter, load Dexie cursor + in-mem,
|
|
79
|
+
* fire a targeted one-shot download for just this collection.
|
|
80
|
+
*/
|
|
81
|
+
private _installCollectionConfig;
|
|
65
82
|
/**
|
|
66
83
|
* Restrict sync to only these collections. When non-empty, only the listed
|
|
67
84
|
* collections load from Dexie→in-mem and download from server. Pass an empty
|
|
@@ -87,6 +104,18 @@ export declare class SyncedDb implements I_SyncedDb {
|
|
|
87
104
|
* Does NOT upload to server — call sync() for that.
|
|
88
105
|
*/
|
|
89
106
|
flush(): Promise<void>;
|
|
107
|
+
/**
|
|
108
|
+
* Push dirty data to the server immediately, bypassing the upload debounce.
|
|
109
|
+
*
|
|
110
|
+
* Flushes pending Dexie writes, awaits any in-flight REST upload, then fires
|
|
111
|
+
* a fresh `uploadDirtyItems()`. Called automatically when the tab becomes
|
|
112
|
+
* hidden so data is not stranded by browser tab-throttling / suspension.
|
|
113
|
+
*
|
|
114
|
+
* No-op when offline, forced offline, or not yet initialized.
|
|
115
|
+
*
|
|
116
|
+
* @param calledFrom Diagnostic tag threaded through to upload callbacks
|
|
117
|
+
*/
|
|
118
|
+
flushToServer(calledFrom?: string): Promise<void>;
|
|
90
119
|
private _lastFullSyncDate?;
|
|
91
120
|
private _lastInitialSyncDate?;
|
|
92
121
|
/**
|
|
@@ -12,6 +12,8 @@ export declare class LeaderElectionManager implements I_LeaderElectionManager {
|
|
|
12
12
|
private isLeaderFlag;
|
|
13
13
|
private closing;
|
|
14
14
|
private releasingDueToVisibility;
|
|
15
|
+
private _leaderSince?;
|
|
16
|
+
private _followerSince?;
|
|
15
17
|
private releaseLeaderLockResolve?;
|
|
16
18
|
private becameLeaderPromise?;
|
|
17
19
|
private becameLeaderResolve?;
|
|
@@ -36,6 +38,17 @@ export declare class LeaderElectionManager implements I_LeaderElectionManager {
|
|
|
36
38
|
* Check if this instance is currently the leader.
|
|
37
39
|
*/
|
|
38
40
|
isLeader(): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Returns the timestamp when this tab became leader, or undefined if currently a follower.
|
|
43
|
+
* Cleared on transition to follower.
|
|
44
|
+
*/
|
|
45
|
+
leaderSince(): Date | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Returns the timestamp when this tab became follower, or undefined if currently a leader.
|
|
48
|
+
* Initialized at construction (every tab starts as a follower until it claims the lock).
|
|
49
|
+
* Cleared on transition to leader.
|
|
50
|
+
*/
|
|
51
|
+
followerSince(): Date | undefined;
|
|
39
52
|
/**
|
|
40
53
|
* Cleanup resources.
|
|
41
54
|
*/
|
|
@@ -26,6 +26,10 @@ export interface I_LeaderElectionManager {
|
|
|
26
26
|
releaseLeaderLock(): void;
|
|
27
27
|
/** Check if this instance is currently the leader. */
|
|
28
28
|
isLeader(): boolean;
|
|
29
|
+
/** Timestamp when this tab became leader, or undefined if currently a follower. */
|
|
30
|
+
leaderSince(): Date | undefined;
|
|
31
|
+
/** Timestamp when this tab became follower, or undefined if currently a leader. */
|
|
32
|
+
followerSince(): Date | undefined;
|
|
29
33
|
/** Initialize (setup visibility listeners, BroadcastChannel). */
|
|
30
34
|
init(): void;
|
|
31
35
|
/** Cleanup resources. */
|
|
@@ -402,6 +402,13 @@ export interface CollectionConfig<T extends DbEntity = any, M = any> {
|
|
|
402
402
|
* Read operations throw. Server sync and WS notifications are skipped.
|
|
403
403
|
*/
|
|
404
404
|
writeOnly?: boolean;
|
|
405
|
+
/**
|
|
406
|
+
* Marks this config as provisional — a later `addCollectionToSync()` with
|
|
407
|
+
* a permanent (non-temporary) config will replace it. Permanent configs
|
|
408
|
+
* (the default) are immutable: subsequent `addCollectionToSync()` calls
|
|
409
|
+
* for the same collection are no-ops once a permanent config is in place.
|
|
410
|
+
*/
|
|
411
|
+
temporaryConfig?: boolean;
|
|
405
412
|
/** Whether this collection uses in-memory metadata */
|
|
406
413
|
hasMetadata?: boolean;
|
|
407
414
|
/** Callback called when a single object is written to in-mem. Returns metadata to store. */
|
|
@@ -737,6 +744,53 @@ export interface I_SyncedDb {
|
|
|
737
744
|
* Does NOT upload to server — call sync() for that.
|
|
738
745
|
*/
|
|
739
746
|
flush(): Promise<void>;
|
|
747
|
+
/**
|
|
748
|
+
* Push dirty data to the server immediately, bypassing the upload debounce.
|
|
749
|
+
* Flushes pending Dexie writes, awaits any in-flight REST upload, then fires
|
|
750
|
+
* a fresh upload. Called automatically on `visibilitychange` to `hidden` so
|
|
751
|
+
* data is not stranded by browser tab-throttling / suspension.
|
|
752
|
+
* No-op when offline, forced offline, or not yet initialized.
|
|
753
|
+
* @param calledFrom Diagnostic tag threaded through to upload callbacks
|
|
754
|
+
*/
|
|
755
|
+
flushToServer(calledFrom?: string): Promise<void>;
|
|
756
|
+
/**
|
|
757
|
+
* Register a collection for sync at runtime.
|
|
758
|
+
*
|
|
759
|
+
* Behavior depends on whether a config already exists for `spec.name`:
|
|
760
|
+
* - permanent (existing config has `temporaryConfig !== true`): no-op
|
|
761
|
+
* - temporary (existing config has `temporaryConfig === true`): replaced by `spec`
|
|
762
|
+
* - none: `spec` is added
|
|
763
|
+
*
|
|
764
|
+
* On add or replace: loads the Dexie cursor for this collection into the
|
|
765
|
+
* sync-meta cache, hydrates in-mem from Dexie, and (when online + not
|
|
766
|
+
* `writeOnly`) fires a one-shot download sync for just this collection.
|
|
767
|
+
* If a `syncOnlyTheseCollections` filter is active, the new collection is
|
|
768
|
+
* added to it so future syncs include it.
|
|
769
|
+
*
|
|
770
|
+
* @param spec Collection config to register
|
|
771
|
+
*/
|
|
772
|
+
addCollectionToSync(spec: CollectionConfig): Promise<void>;
|
|
773
|
+
/**
|
|
774
|
+
* Replace the collection config and re-sync.
|
|
775
|
+
*
|
|
776
|
+
* Like `addCollectionToSync` but more aggressive — overrides even a
|
|
777
|
+
* permanent existing config. The only blocked transition is
|
|
778
|
+
* **permanent → temporary** (cannot downgrade an already-established
|
|
779
|
+
* config to provisional):
|
|
780
|
+
*
|
|
781
|
+
* - no existing config: install `spec`, return `true`
|
|
782
|
+
* - existing temporary: replace with `spec` (any type), return `true`
|
|
783
|
+
* - existing permanent, `spec.temporaryConfig === true`: return `false`, no-op
|
|
784
|
+
* - existing permanent, `spec` permanent: replace with `spec`, return `true`
|
|
785
|
+
*
|
|
786
|
+
* On replace: extends active `syncOnlyTheseCollections` filter, reloads
|
|
787
|
+
* the Dexie cursor, re-hydrates in-mem, and (when online + not `writeOnly`)
|
|
788
|
+
* fires a one-shot targeted download.
|
|
789
|
+
*
|
|
790
|
+
* @param spec Collection config to install
|
|
791
|
+
* @returns `true` if installed, `false` if blocked (permanent → temporary)
|
|
792
|
+
*/
|
|
793
|
+
replaceSyncCollection(spec: CollectionConfig): Promise<boolean>;
|
|
740
794
|
/**
|
|
741
795
|
* Returns when all collections were last successfully synced
|
|
742
796
|
* from the server, or undefined if never synced.
|
|
@@ -954,6 +1008,19 @@ export interface I_SyncedDb {
|
|
|
954
1008
|
* @returns true if this instance holds the leader lock
|
|
955
1009
|
*/
|
|
956
1010
|
isLeaderTab(): boolean;
|
|
1011
|
+
/**
|
|
1012
|
+
* Timestamp when this tab became the leader.
|
|
1013
|
+
* Set on transition from follower to leader; cleared when leadership is lost.
|
|
1014
|
+
* @returns Date of leader transition, or undefined if currently a follower
|
|
1015
|
+
*/
|
|
1016
|
+
leaderSince(): Date | undefined;
|
|
1017
|
+
/**
|
|
1018
|
+
* Timestamp when this tab became a follower.
|
|
1019
|
+
* Initialized at construction (every tab starts as a follower until it claims
|
|
1020
|
+
* the lock); cleared when leadership is gained, set again on transition to follower.
|
|
1021
|
+
* @returns Date of follower transition, or undefined if currently the leader
|
|
1022
|
+
*/
|
|
1023
|
+
followerSince(): Date | undefined;
|
|
957
1024
|
/**
|
|
958
1025
|
* Get metadata for a single object.
|
|
959
1026
|
* @param collection Collection name
|