cry-synced-db-client 0.1.76 → 0.1.79
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 +106 -33
- package/dist/src/db/DexieDb.d.ts +2 -0
- package/dist/src/db/SyncedDb.d.ts +27 -0
- package/dist/src/db/types/managers.d.ts +6 -0
- package/dist/src/types/I_SyncedDb.d.ts +12 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -705,7 +705,6 @@ var CrossTabSyncManager = class {
|
|
|
705
705
|
// Private Methods
|
|
706
706
|
// ============================================================
|
|
707
707
|
async handleCrossTabMetaUpdate(payload) {
|
|
708
|
-
var _a;
|
|
709
708
|
if (!this.deps.isInitialized()) {
|
|
710
709
|
return;
|
|
711
710
|
}
|
|
@@ -717,7 +716,7 @@ var CrossTabSyncManager = class {
|
|
|
717
716
|
if (!collections.has(collection)) {
|
|
718
717
|
continue;
|
|
719
718
|
}
|
|
720
|
-
if (
|
|
719
|
+
if (!this.deps.isSyncAllowed(collection)) {
|
|
721
720
|
continue;
|
|
722
721
|
}
|
|
723
722
|
if (ids.length === 0) {
|
|
@@ -1870,7 +1869,7 @@ dist_default.registerCustom(
|
|
|
1870
1869
|
{
|
|
1871
1870
|
isApplicable: (v) => v instanceof ObjectId,
|
|
1872
1871
|
serialize: (v) => v.toHexString(),
|
|
1873
|
-
deserialize: (v) =>
|
|
1872
|
+
deserialize: (v) => v
|
|
1874
1873
|
},
|
|
1875
1874
|
"ObjectId"
|
|
1876
1875
|
);
|
|
@@ -2062,9 +2061,16 @@ var PendingChangesManager = class {
|
|
|
2062
2061
|
});
|
|
2063
2062
|
if (existing) {
|
|
2064
2063
|
Object.assign(existing, write.data);
|
|
2064
|
+
if (existing._id && typeof existing._id === "object") {
|
|
2065
|
+
existing._id = String(existing._id);
|
|
2066
|
+
}
|
|
2065
2067
|
saveBatch.push(existing);
|
|
2066
2068
|
} else {
|
|
2067
|
-
|
|
2069
|
+
const insertData = write.data;
|
|
2070
|
+
if (insertData._id && typeof insertData._id === "object") {
|
|
2071
|
+
insertData._id = String(insertData._id);
|
|
2072
|
+
}
|
|
2073
|
+
insertBatch.push(insertData);
|
|
2068
2074
|
}
|
|
2069
2075
|
}
|
|
2070
2076
|
if (dirtyChangesBatch.length > 0) {
|
|
@@ -2344,7 +2350,7 @@ var _SyncEngine = class _SyncEngine {
|
|
|
2344
2350
|
const syncSpecs = [];
|
|
2345
2351
|
const configMap = /* @__PURE__ */ new Map();
|
|
2346
2352
|
for (const [collectionName, config] of this.collections) {
|
|
2347
|
-
if (
|
|
2353
|
+
if (!this.deps.isSyncAllowed(collectionName)) continue;
|
|
2348
2354
|
const syncEnabled = (_a = config.syncConfig) == null ? void 0 : _a.enabled;
|
|
2349
2355
|
const shouldSync = typeof syncEnabled === "function" ? syncEnabled() : syncEnabled;
|
|
2350
2356
|
if (shouldSync === false) {
|
|
@@ -2873,13 +2879,12 @@ var ServerUpdateHandler = class {
|
|
|
2873
2879
|
* Handle incoming server update (WebSocket notification).
|
|
2874
2880
|
*/
|
|
2875
2881
|
async handleServerUpdate(payload) {
|
|
2876
|
-
var _a;
|
|
2877
2882
|
this.callOnWsNotification(payload);
|
|
2878
2883
|
if (!this.deps.isLeader()) return;
|
|
2879
2884
|
if (!this.deps.canReceiveServerUpdates()) return;
|
|
2880
2885
|
const collectionName = payload.collection;
|
|
2881
2886
|
if (!this.collections.has(collectionName)) return;
|
|
2882
|
-
if (
|
|
2887
|
+
if (!this.deps.isSyncAllowed(collectionName)) return;
|
|
2883
2888
|
const updatedIds = [];
|
|
2884
2889
|
switch (payload.operation) {
|
|
2885
2890
|
case "insert": {
|
|
@@ -3293,6 +3298,8 @@ var SyncedDb = class {
|
|
|
3293
3298
|
this.syncing = false;
|
|
3294
3299
|
this.syncLock = false;
|
|
3295
3300
|
this.wsUpdateQueue = [];
|
|
3301
|
+
/** When non-null, only these collections participate in sync/in-mem loading. */
|
|
3302
|
+
this.syncOnlyCollections = null;
|
|
3296
3303
|
// Sync metadata cache
|
|
3297
3304
|
this.syncMetaCache = /* @__PURE__ */ new Map();
|
|
3298
3305
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
@@ -3358,7 +3365,8 @@ var SyncedDb = class {
|
|
|
3358
3365
|
dexieDb: this.dexieDb,
|
|
3359
3366
|
writeToInMemBatch: (collection, items, operation) => {
|
|
3360
3367
|
this.inMemManager.writeBatch(collection, items, operation);
|
|
3361
|
-
}
|
|
3368
|
+
},
|
|
3369
|
+
isSyncAllowed: (collection) => this.isSyncAllowed(collection)
|
|
3362
3370
|
}
|
|
3363
3371
|
});
|
|
3364
3372
|
this.connectionManager = new ConnectionManager({
|
|
@@ -3438,7 +3446,8 @@ var SyncedDb = class {
|
|
|
3438
3446
|
flushAllPendingChanges: () => this.pendingChanges.flushAll(),
|
|
3439
3447
|
cancelRestUploadTimer: () => this.pendingChanges.cancelRestUploadTimer(),
|
|
3440
3448
|
awaitRestUpload: () => this.pendingChanges.awaitRestUpload(),
|
|
3441
|
-
broadcastUpdates: (updates) => this.crossTabSync.broadcastMetaUpdate(updates)
|
|
3449
|
+
broadcastUpdates: (updates) => this.crossTabSync.broadcastMetaUpdate(updates),
|
|
3450
|
+
isSyncAllowed: (collection) => this.isSyncAllowed(collection)
|
|
3442
3451
|
}
|
|
3443
3452
|
});
|
|
3444
3453
|
this.serverUpdateHandler = new ServerUpdateHandler({
|
|
@@ -3457,7 +3466,8 @@ var SyncedDb = class {
|
|
|
3457
3466
|
this.inMemManager.writeBatch(collection, items, operation);
|
|
3458
3467
|
},
|
|
3459
3468
|
broadcastUpdates: (updates) => this.crossTabSync.broadcastMetaUpdate(updates),
|
|
3460
|
-
getPendingChange: (collection, id) => this.pendingChanges.getPendingChange(collection, id)
|
|
3469
|
+
getPendingChange: (collection, id) => this.pendingChanges.getPendingChange(collection, id),
|
|
3470
|
+
isSyncAllowed: (collection) => this.isSyncAllowed(collection)
|
|
3461
3471
|
}
|
|
3462
3472
|
});
|
|
3463
3473
|
if (config.wakeSyncEnabled) {
|
|
@@ -3500,6 +3510,41 @@ var SyncedDb = class {
|
|
|
3500
3510
|
isLeaderTab() {
|
|
3501
3511
|
return this.leaderElection.isLeader();
|
|
3502
3512
|
}
|
|
3513
|
+
/**
|
|
3514
|
+
* Restrict sync to only these collections. When non-empty, only the listed
|
|
3515
|
+
* collections load from Dexie→in-mem and download from server. Pass an empty
|
|
3516
|
+
* array to sync all collections again.
|
|
3517
|
+
*
|
|
3518
|
+
* Newly-allowed collections are loaded from Dexie immediately and a sync is
|
|
3519
|
+
* triggered if online.
|
|
3520
|
+
*/
|
|
3521
|
+
async setSyncOnlyTheseCollections(collections) {
|
|
3522
|
+
const prevAllowed = /* @__PURE__ */ new Set();
|
|
3523
|
+
for (const [name] of this.collections) {
|
|
3524
|
+
if (this.isSyncAllowed(name)) prevAllowed.add(name);
|
|
3525
|
+
}
|
|
3526
|
+
this.syncOnlyCollections = collections.length > 0 ? new Set(collections) : null;
|
|
3527
|
+
const newlyAllowed = [];
|
|
3528
|
+
for (const [name] of this.collections) {
|
|
3529
|
+
if (this.isSyncAllowed(name) && !prevAllowed.has(name)) {
|
|
3530
|
+
newlyAllowed.push(name);
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
for (const name of newlyAllowed) {
|
|
3534
|
+
await this.loadCollectionToInMem(name);
|
|
3535
|
+
}
|
|
3536
|
+
if (newlyAllowed.length > 0 && this.connectionManager.canSync()) {
|
|
3537
|
+
this.sync("setSyncOnlyTheseCollections").catch(() => {
|
|
3538
|
+
});
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
/**
|
|
3542
|
+
* Get the current syncOnlyTheseCollections filter.
|
|
3543
|
+
* Returns null when all collections are synced.
|
|
3544
|
+
*/
|
|
3545
|
+
getSyncOnlyTheseCollections() {
|
|
3546
|
+
return this.syncOnlyCollections;
|
|
3547
|
+
}
|
|
3503
3548
|
/**
|
|
3504
3549
|
* Test helper: simulate receiving a cross-tab broadcast from another instance.
|
|
3505
3550
|
*/
|
|
@@ -3523,26 +3568,9 @@ var SyncedDb = class {
|
|
|
3523
3568
|
);
|
|
3524
3569
|
}
|
|
3525
3570
|
await this.pendingChanges.recoverPendingWrites();
|
|
3526
|
-
for (const [name
|
|
3527
|
-
if (
|
|
3528
|
-
this.
|
|
3529
|
-
await this.dexieDb.forEachBatch(name, 2e3, async (chunk) => {
|
|
3530
|
-
let writeIdx = 0;
|
|
3531
|
-
for (let i = 0; i < chunk.length; i++) {
|
|
3532
|
-
const item = chunk[i];
|
|
3533
|
-
if (!item._deleted && !item._archived) {
|
|
3534
|
-
chunk[writeIdx++] = item;
|
|
3535
|
-
}
|
|
3536
|
-
}
|
|
3537
|
-
chunk.length = writeIdx;
|
|
3538
|
-
if (chunk.length > 0) {
|
|
3539
|
-
this.inMemManager.writeBatch(name, chunk, "upsert");
|
|
3540
|
-
}
|
|
3541
|
-
});
|
|
3542
|
-
const meta = await this.dexieDb.getSyncMeta(name);
|
|
3543
|
-
if (meta) {
|
|
3544
|
-
this.syncMetaCache.set(name, meta);
|
|
3545
|
-
}
|
|
3571
|
+
for (const [name] of this.collections) {
|
|
3572
|
+
if (!this.isSyncAllowed(name)) continue;
|
|
3573
|
+
await this.loadCollectionToInMem(name);
|
|
3546
3574
|
}
|
|
3547
3575
|
this.leaderElection.init();
|
|
3548
3576
|
this.crossTabSync.init();
|
|
@@ -3904,7 +3932,7 @@ var SyncedDb = class {
|
|
|
3904
3932
|
if (existing) {
|
|
3905
3933
|
return this.save(collection, existing._id, update);
|
|
3906
3934
|
} else {
|
|
3907
|
-
const id = new ObjectId2();
|
|
3935
|
+
const id = new ObjectId2().toHexString();
|
|
3908
3936
|
const newDoc = __spreadValues({ _id: id }, update);
|
|
3909
3937
|
return this.insert(collection, newDoc);
|
|
3910
3938
|
}
|
|
@@ -3912,7 +3940,7 @@ var SyncedDb = class {
|
|
|
3912
3940
|
async insert(collection, data) {
|
|
3913
3941
|
var _a;
|
|
3914
3942
|
this.assertCollection(collection);
|
|
3915
|
-
const id = data._id
|
|
3943
|
+
const id = data._id ? String(data._id) : new ObjectId2().toHexString();
|
|
3916
3944
|
const existing = await this.dexieDb.getById(collection, id);
|
|
3917
3945
|
if (existing && !existing._deleted && !existing._archived) {
|
|
3918
3946
|
console.warn(
|
|
@@ -4263,6 +4291,42 @@ var SyncedDb = class {
|
|
|
4263
4291
|
returnArchived: (_b = opts == null ? void 0 : opts.returnArchived) != null ? _b : this.defaultReturnArchived
|
|
4264
4292
|
});
|
|
4265
4293
|
}
|
|
4294
|
+
/**
|
|
4295
|
+
* Whether a collection participates in sync (server download,
|
|
4296
|
+
* WS notifications, cross-tab sync, Dexie→in-mem loading).
|
|
4297
|
+
* False for writeOnly collections and collections excluded by syncOnlyCollections.
|
|
4298
|
+
*/
|
|
4299
|
+
isSyncAllowed(collection) {
|
|
4300
|
+
const config = this.collections.get(collection);
|
|
4301
|
+
if (!config) return false;
|
|
4302
|
+
if (config.writeOnly) return false;
|
|
4303
|
+
if (this.syncOnlyCollections && !this.syncOnlyCollections.has(collection)) return false;
|
|
4304
|
+
return true;
|
|
4305
|
+
}
|
|
4306
|
+
/**
|
|
4307
|
+
* Load a single collection from Dexie into in-memory cache.
|
|
4308
|
+
* Filters out deleted and archived items.
|
|
4309
|
+
*/
|
|
4310
|
+
async loadCollectionToInMem(name) {
|
|
4311
|
+
this.inMemManager.clearCollection(name);
|
|
4312
|
+
await this.dexieDb.forEachBatch(name, 2e3, async (chunk) => {
|
|
4313
|
+
let writeIdx = 0;
|
|
4314
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
4315
|
+
const item = chunk[i];
|
|
4316
|
+
if (!item._deleted && !item._archived) {
|
|
4317
|
+
chunk[writeIdx++] = item;
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
chunk.length = writeIdx;
|
|
4321
|
+
if (chunk.length > 0) {
|
|
4322
|
+
this.inMemManager.writeBatch(name, chunk, "upsert");
|
|
4323
|
+
}
|
|
4324
|
+
});
|
|
4325
|
+
const meta = await this.dexieDb.getSyncMeta(name);
|
|
4326
|
+
if (meta) {
|
|
4327
|
+
this.syncMetaCache.set(name, meta);
|
|
4328
|
+
}
|
|
4329
|
+
}
|
|
4266
4330
|
assertCollection(name) {
|
|
4267
4331
|
if (!this.collections.has(name)) {
|
|
4268
4332
|
throw new Error(`Collection "${name}" not configured`);
|
|
@@ -4345,6 +4409,12 @@ var DexieDb = class extends Dexie {
|
|
|
4345
4409
|
idToString(id) {
|
|
4346
4410
|
return String(id);
|
|
4347
4411
|
}
|
|
4412
|
+
/** Ensure _id is a primitive string. IndexedDB rejects ObjectId objects as keys. */
|
|
4413
|
+
ensureStringId(item) {
|
|
4414
|
+
if (item._id && typeof item._id === "object") {
|
|
4415
|
+
item._id = String(item._id);
|
|
4416
|
+
}
|
|
4417
|
+
}
|
|
4348
4418
|
async save(collection, id, data) {
|
|
4349
4419
|
const table = this.getTable(collection);
|
|
4350
4420
|
const key = this.idToString(id);
|
|
@@ -4353,17 +4423,19 @@ var DexieDb = class extends Dexie {
|
|
|
4353
4423
|
await table.update(key, data);
|
|
4354
4424
|
} else {
|
|
4355
4425
|
await table.put(__spreadValues({
|
|
4356
|
-
_id:
|
|
4426
|
+
_id: key
|
|
4357
4427
|
}, data));
|
|
4358
4428
|
}
|
|
4359
4429
|
}
|
|
4360
4430
|
async insert(collection, data) {
|
|
4361
4431
|
const table = this.getTable(collection);
|
|
4432
|
+
this.ensureStringId(data);
|
|
4362
4433
|
await table.put(data);
|
|
4363
4434
|
}
|
|
4364
4435
|
async saveMany(collection, items) {
|
|
4365
4436
|
if (items.length === 0) return;
|
|
4366
4437
|
const table = this.getTable(collection);
|
|
4438
|
+
for (const item of items) this.ensureStringId(item);
|
|
4367
4439
|
await table.bulkPut(items);
|
|
4368
4440
|
}
|
|
4369
4441
|
async deleteOne(collection, id) {
|
|
@@ -4382,6 +4454,7 @@ var DexieDb = class extends Dexie {
|
|
|
4382
4454
|
const table = this.getTable(collection);
|
|
4383
4455
|
await table.clear();
|
|
4384
4456
|
if (data.length > 0) {
|
|
4457
|
+
for (const item of data) this.ensureStringId(item);
|
|
4385
4458
|
await table.bulkPut(data);
|
|
4386
4459
|
}
|
|
4387
4460
|
}
|
package/dist/src/db/DexieDb.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export declare class DexieDb extends Dexie implements I_DexieDb {
|
|
|
15
15
|
constructor(tenant: string, collectionConfigs: CollectionConfig<any>[]);
|
|
16
16
|
private getTable;
|
|
17
17
|
private idToString;
|
|
18
|
+
/** Ensure _id is a primitive string. IndexedDB rejects ObjectId objects as keys. */
|
|
19
|
+
private ensureStringId;
|
|
18
20
|
save<T extends LocalDbEntity>(collection: string, id: Id, data: Partial<T>): Promise<void>;
|
|
19
21
|
insert<T extends LocalDbEntity>(collection: string, data: T): Promise<void>;
|
|
20
22
|
saveMany<T extends LocalDbEntity>(collection: string, items: T[]): Promise<void>;
|
|
@@ -27,6 +27,8 @@ export declare class SyncedDb implements I_SyncedDb {
|
|
|
27
27
|
private syncing;
|
|
28
28
|
private syncLock;
|
|
29
29
|
private wsUpdateQueue;
|
|
30
|
+
/** When non-null, only these collections participate in sync/in-mem loading. */
|
|
31
|
+
private syncOnlyCollections;
|
|
30
32
|
private readonly updaterId;
|
|
31
33
|
private readonly syncedDbInstanceId;
|
|
32
34
|
private syncMetaCache;
|
|
@@ -44,6 +46,20 @@ export declare class SyncedDb implements I_SyncedDb {
|
|
|
44
46
|
getInstanceId(): string;
|
|
45
47
|
getCrossTabSyncDebounceMs(): number;
|
|
46
48
|
isLeaderTab(): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Restrict sync to only these collections. When non-empty, only the listed
|
|
51
|
+
* collections load from Dexie→in-mem and download from server. Pass an empty
|
|
52
|
+
* array to sync all collections again.
|
|
53
|
+
*
|
|
54
|
+
* Newly-allowed collections are loaded from Dexie immediately and a sync is
|
|
55
|
+
* triggered if online.
|
|
56
|
+
*/
|
|
57
|
+
setSyncOnlyTheseCollections(collections: string[]): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Get the current syncOnlyTheseCollections filter.
|
|
60
|
+
* Returns null when all collections are synced.
|
|
61
|
+
*/
|
|
62
|
+
getSyncOnlyTheseCollections(): ReadonlySet<string> | null;
|
|
47
63
|
/**
|
|
48
64
|
* Test helper: simulate receiving a cross-tab broadcast from another instance.
|
|
49
65
|
*/
|
|
@@ -114,6 +130,17 @@ export declare class SyncedDb implements I_SyncedDb {
|
|
|
114
130
|
* Per-call values take precedence over global defaults.
|
|
115
131
|
*/
|
|
116
132
|
private resolveOpts;
|
|
133
|
+
/**
|
|
134
|
+
* Whether a collection participates in sync (server download,
|
|
135
|
+
* WS notifications, cross-tab sync, Dexie→in-mem loading).
|
|
136
|
+
* False for writeOnly collections and collections excluded by syncOnlyCollections.
|
|
137
|
+
*/
|
|
138
|
+
private isSyncAllowed;
|
|
139
|
+
/**
|
|
140
|
+
* Load a single collection from Dexie into in-memory cache.
|
|
141
|
+
* Filters out deleted and archived items.
|
|
142
|
+
*/
|
|
143
|
+
private loadCollectionToInMem;
|
|
117
144
|
private assertCollection;
|
|
118
145
|
/**
|
|
119
146
|
* Asserts write-only collection has online connectivity for reads.
|
|
@@ -50,6 +50,8 @@ export interface CrossTabSyncDeps {
|
|
|
50
50
|
dexieDb: I_DexieDb;
|
|
51
51
|
/** Write to in-mem batch. */
|
|
52
52
|
writeToInMemBatch: <T extends DbEntity>(collection: string, items: T[], operation: "upsert" | "delete") => void;
|
|
53
|
+
/** Whether a collection participates in sync (not writeOnly, not filtered out). */
|
|
54
|
+
isSyncAllowed: (collection: string) => boolean;
|
|
53
55
|
}
|
|
54
56
|
export interface CrossTabSyncConfig {
|
|
55
57
|
tenant: string;
|
|
@@ -225,6 +227,8 @@ export interface SyncEngineDeps {
|
|
|
225
227
|
awaitRestUpload: () => Promise<void>;
|
|
226
228
|
/** Broadcast updated IDs to other tabs */
|
|
227
229
|
broadcastUpdates: (updates: Record<string, string[]>) => void;
|
|
230
|
+
/** Whether a collection participates in sync (not writeOnly, not filtered out). */
|
|
231
|
+
isSyncAllowed: (collection: string) => boolean;
|
|
228
232
|
}
|
|
229
233
|
export interface SyncEngineConfig {
|
|
230
234
|
tenant: string;
|
|
@@ -256,6 +260,8 @@ export interface ServerUpdateHandlerDeps {
|
|
|
256
260
|
getPendingChange: (collection: string, id: Id) => PendingChange | undefined;
|
|
257
261
|
broadcastUpdates: (updates: Record<string, string[]>) => void;
|
|
258
262
|
writeToInMemBatch: <T extends DbEntity>(collection: string, items: T[], operation: "upsert" | "delete") => void;
|
|
263
|
+
/** Whether a collection participates in sync (not writeOnly, not filtered out). */
|
|
264
|
+
isSyncAllowed: (collection: string) => boolean;
|
|
259
265
|
}
|
|
260
266
|
export interface ServerUpdateHandlerConfig {
|
|
261
267
|
tenant: string;
|
|
@@ -518,6 +518,18 @@ export interface I_SyncedDb {
|
|
|
518
518
|
sync(calledFrom?: string): Promise<void>;
|
|
519
519
|
/** Ali je sinhronizacija v teku */
|
|
520
520
|
isSyncing(): boolean;
|
|
521
|
+
/**
|
|
522
|
+
* Restrict sync to only these collections. When non-empty, only listed
|
|
523
|
+
* collections load Dexie→in-mem and download from server. Pass empty
|
|
524
|
+
* array to sync all collections. Newly-allowed collections are loaded
|
|
525
|
+
* immediately and a sync is triggered if online.
|
|
526
|
+
*/
|
|
527
|
+
setSyncOnlyTheseCollections(collections: string[]): Promise<void>;
|
|
528
|
+
/**
|
|
529
|
+
* Get the current syncOnlyTheseCollections filter.
|
|
530
|
+
* Returns null when all collections are synced.
|
|
531
|
+
*/
|
|
532
|
+
getSyncOnlyTheseCollections(): ReadonlySet<string> | null;
|
|
521
533
|
/**
|
|
522
534
|
* Izvede batch upsert na serverju
|
|
523
535
|
*
|