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 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 ((_a = collections.get(collection)) == null ? void 0 : _a.writeOnly) {
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) => new ObjectId(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
- insertBatch.push(write.data);
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 (config.writeOnly) continue;
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 ((_a = this.collections.get(collectionName)) == null ? void 0 : _a.writeOnly) return;
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, config] of this.collections) {
3527
- if (config.writeOnly) continue;
3528
- this.inMemManager.clearCollection(name);
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 || new ObjectId2();
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: 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
  }
@@ -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
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cry-synced-db-client",
3
- "version": "0.1.76",
3
+ "version": "0.1.79",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",