cry-synced-db-client 0.1.147 → 0.1.150

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,72 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ### `SyncSource` flag in `I_InMemDb.saveMany` / `deleteManyByIds`
6
+
7
+ The in-mem write API now accepts an opcijski `opts: { source?: SyncSource }`
8
+ parameter that labels the **origin** of each batch:
9
+
10
+ - `'initial'` — first full sync of a collection (no prior `lastSyncTs`)
11
+ - `'refresh'` — `referToServerSync` background reload (timestamp = 0
12
+ for already-synced collection, e.g. when the app needs records outside
13
+ the current sync window)
14
+ - `'incremental'` — everything else: delta sync, server WS push,
15
+ optimistic local writes (`save` / `insert` / `deleteOne` / `deleteMany`
16
+ / `hardDelete`), `findById` / `findByIds` `returnDeleted` server fetch,
17
+ `ensureItemsAreLoaded`, `refreshByIdsInBackground`, eviction deletes
18
+
19
+ **Default**: when no `opts` is passed, `InMemManager.writeBatch`
20
+ normalizes to `'incremental'`. Backward compatible — existing
21
+ `I_InMemDb` implementations that ignore `opts` keep working unchanged.
22
+
23
+ The signal is **advisory**: it does not change sync semantics or
24
+ conflict resolution. Consumers can use it to apply per-source policies
25
+ (e.g. selective Vue reactivity in the in-mem store: items first seen
26
+ via `'incremental'` upgrade to reactive proxies; `'initial'` /
27
+ `'refresh'` floods stay as plain JS objects).
28
+
29
+ Threading: `SyncEngine` resolves `'initial'` per collection from
30
+ `syncMetaCache` lookup at the start of each `sync()` cycle (no prior
31
+ `lastSyncTs` ⇒ `'initial'`), and propagates the source via the
32
+ `writeToInMemBatch` deps callback through `InMemManager` to
33
+ `I_InMemDb.saveMany` / `deleteManyByIds`. `SyncedDb.referToServerSync`
34
+ hardcodes `'refresh'`; `syncCollectionForFind` derives source from the
35
+ local `timestamp === 0` check.
36
+
37
+ Tests: `test/syncSource.test.ts` (9 cases) covers initial / incremental
38
+ / refresh propagation across all public write paths. `MockInMemDb`
39
+ exposes `recordedCalls: RecordedInMemCall[]` for assertion.
40
+
41
+ ### `uploadDirtyItems` follow-up pass — drain in-sync writes immediately
42
+
43
+ Writes that land **during** a sync iteration had their
44
+ `scheduleRestUpload()` guarded out by `isSyncing()` (silent drop, no
45
+ re-schedule), so they sat in `_dirty_changes` until the next 60s
46
+ auto-sync tick. Particularly visible for high-frequency writeOnly
47
+ collections (e.g. `prehodi`, written on every route change) on tablets
48
+ with intensive navigation: dirty items piled up until the next tick.
49
+
50
+ `SyncEngine.sync()` now performs a single follow-up pass right after the
51
+ primary `uploadDirtyItems()` call:
52
+
53
+ 1. `flushAllPendingChanges()` — forces any in-flight 500ms Dexie
54
+ debounces to land in `_dirty_changes` before the second snapshot.
55
+ 2. `uploadDirtyItems(calledFrom + ":followUp")` — drains entries that
56
+ accumulated during the first pass's server roundtrip.
57
+
58
+ Single pass (not a loop) — bounded work; later writes after sync
59
+ completes will trigger their own `scheduleRestUpload()` once the
60
+ `isSyncing` flag clears. Sequential `await` ordering means no concurrent
61
+ server roundtrips and no new race conditions vs. the existing
62
+ snapshot-then-clear pattern in `_dirty_changes`. Stats from the
63
+ follow-up pass are merged into `uploadStats` so `onSyncEnd` /
64
+ `collectionStats.sentCount` reflect both passes.
65
+
66
+ Errors in the follow-up are caught by the same outer `try/catch` as the
67
+ first pass — a follow-up failure does not roll back the first pass's
68
+ already-cleared dirty entries; affected items are caught at the next
69
+ sync tick (same retry semantics as before).
70
+
5
71
  ### Auto-eviction co-located with sync — one round-trip total
6
72
 
7
73
  When `evictStaleRecordsEveryHrs > 0` and the interval has elapsed, the
package/dist/index.js CHANGED
@@ -272,12 +272,19 @@ var InMemManager = class {
272
272
  /**
273
273
  * Write batch of items to in-mem.
274
274
  * Handles metadata callbacks automatically when useObjectMetadata is enabled.
275
+ *
276
+ * @param opts.source — origin of the write ('initial' | 'refresh' | 'incremental').
277
+ * Implementacija I_InMemDb lahko ta signal uporabi za optimizacije
278
+ * (npr. selektivna reactivity); ne vpliva na sync semantiko.
279
+ * Default: 'incremental'.
275
280
  */
276
- writeBatch(collection, items, operation) {
281
+ writeBatch(collection, items, operation, opts) {
282
+ var _a;
277
283
  if (items.length === 0) return;
278
284
  const config = this.collections.get(collection);
285
+ const source = (_a = opts == null ? void 0 : opts.source) != null ? _a : "incremental";
279
286
  if (operation === "upsert") {
280
- this.inMemDb.saveMany(collection, items);
287
+ this.inMemDb.saveMany(collection, items, { source });
281
288
  if (this.useObjectMetadata && (config == null ? void 0 : config.hasMetadata)) {
282
289
  const ids = [];
283
290
  for (const item of items) ids.push(item._id);
@@ -304,7 +311,7 @@ var InMemManager = class {
304
311
  } else if (operation === "delete") {
305
312
  const ids = [];
306
313
  for (const item of items) ids.push(item._id);
307
- this.inMemDb.deleteManyByIds(collection, ids);
314
+ this.inMemDb.deleteManyByIds(collection, ids, { source });
308
315
  if (this.useObjectMetadata && (config == null ? void 0 : config.hasMetadata)) {
309
316
  this.deleteObjectsMetadataInternal(collection, ids);
310
317
  }
@@ -2478,10 +2485,13 @@ var _SyncEngine = class _SyncEngine {
2478
2485
  const findNewerManyStartTime = Date.now();
2479
2486
  const collectionState = /* @__PURE__ */ new Map();
2480
2487
  for (const [name] of configMap) {
2488
+ const prior = this.deps.getSyncMetaCache().get(name);
2489
+ const isInitial = !(prior == null ? void 0 : prior.lastSyncTs);
2481
2490
  collectionState.set(name, {
2482
2491
  maxTs: void 0,
2483
2492
  conflicts: 0,
2484
- receivedCount: 0
2493
+ receivedCount: 0,
2494
+ source: isInitial ? "initial" : "incremental"
2485
2495
  });
2486
2496
  }
2487
2497
  try {
@@ -2499,7 +2509,7 @@ var _SyncEngine = class _SyncEngine {
2499
2509
  if (!config) return;
2500
2510
  const state = collectionState.get(collection);
2501
2511
  state.receivedCount += items.length;
2502
- const stats = await this.processIncomingServerData(collection, config, items);
2512
+ const stats = await this.processIncomingServerData(collection, config, items, state.source);
2503
2513
  state.conflicts += stats.conflictsResolved;
2504
2514
  if (stats.maxTs) {
2505
2515
  if (!state.maxTs || this.compareTimestamps(stats.maxTs, state.maxTs) > 0) {
@@ -2553,6 +2563,17 @@ var _SyncEngine = class _SyncEngine {
2553
2563
  let uploadStats = { sentCount: 0 };
2554
2564
  try {
2555
2565
  uploadStats = await this.uploadDirtyItems(calledFrom);
2566
+ await this.deps.flushAllPendingChanges();
2567
+ const followUp = await this.uploadDirtyItems(`${calledFrom != null ? calledFrom : "sync"}:followUp`);
2568
+ if (followUp.sentCount > 0) {
2569
+ uploadStats.sentCount += followUp.sentCount;
2570
+ if (followUp.collectionSentCounts) {
2571
+ uploadStats.collectionSentCounts = uploadStats.collectionSentCounts || {};
2572
+ for (const [c, n] of Object.entries(followUp.collectionSentCounts)) {
2573
+ uploadStats.collectionSentCounts[c] = (uploadStats.collectionSentCounts[c] || 0) + n;
2574
+ }
2575
+ }
2576
+ }
2556
2577
  } catch (err) {
2557
2578
  console.error(
2558
2579
  "uploadDirtyItems failed (download succeeded, staying online):",
@@ -2712,13 +2733,14 @@ var _SyncEngine = class _SyncEngine {
2712
2733
  await this.dexieDb.deleteMany(collection, dexieDeleteIds);
2713
2734
  }
2714
2735
  if (inMemUpdateBatch.length > 0) {
2715
- this.deps.writeToInMemBatch(collection, inMemUpdateBatch, "upsert");
2736
+ this.deps.writeToInMemBatch(collection, inMemUpdateBatch, "upsert", { source: "incremental" });
2716
2737
  }
2717
2738
  if (inMemDeleteIds.length > 0) {
2718
2739
  this.deps.writeToInMemBatch(
2719
2740
  collection,
2720
2741
  inMemDeleteIds.map((id) => ({ _id: id })),
2721
- "delete"
2742
+ "delete",
2743
+ { source: "incremental" }
2722
2744
  );
2723
2745
  }
2724
2746
  }
@@ -2734,7 +2756,7 @@ var _SyncEngine = class _SyncEngine {
2734
2756
  }
2735
2757
  await this.dexieDb.deleteMany(collection, deleteIds);
2736
2758
  if (!isWriteOnly) {
2737
- this.deps.writeToInMemBatch(collection, deleteDbEntities, "delete");
2759
+ this.deps.writeToInMemBatch(collection, deleteDbEntities, "delete", { source: "incremental" });
2738
2760
  }
2739
2761
  sentCount += deleted.length;
2740
2762
  collectionSentCount += deleted.length;
@@ -2844,17 +2866,23 @@ var _SyncEngine = class _SyncEngine {
2844
2866
  /**
2845
2867
  * Process incoming server data for a single collection.
2846
2868
  * Used by referToServer to process findNewer results.
2869
+ *
2870
+ * @param opts.source — origin label posredovan v writeBatch → inMemDb.
2871
+ * Default `'incremental'`. Caller naj uporabi `'refresh'` za množični
2872
+ * reload (referToServerSync) in `'initial'` za first-time fill.
2847
2873
  */
2848
- async processCollectionServerData(collectionName, serverData) {
2874
+ async processCollectionServerData(collectionName, serverData, opts) {
2875
+ var _a;
2849
2876
  const config = this.collections.get(collectionName);
2850
2877
  if (!config) return { updatedIds: [] };
2851
- const result = await this.processIncomingServerData(collectionName, config, serverData);
2878
+ const source = (_a = opts == null ? void 0 : opts.source) != null ? _a : "incremental";
2879
+ const result = await this.processIncomingServerData(collectionName, config, serverData, source);
2852
2880
  if (result.updatedIds.length > 0) {
2853
2881
  this.deps.broadcastUpdates({ [collectionName]: result.updatedIds });
2854
2882
  }
2855
2883
  return { updatedIds: result.updatedIds };
2856
2884
  }
2857
- async processIncomingServerData(collectionName, config, serverData) {
2885
+ async processIncomingServerData(collectionName, config, serverData, source = "incremental") {
2858
2886
  if (serverData.length === 0) {
2859
2887
  return { conflictsResolved: 0, maxTs: void 0, updatedIds: [] };
2860
2888
  }
@@ -2916,13 +2944,14 @@ var _SyncEngine = class _SyncEngine {
2916
2944
  await this.dexieDb.saveMany(collectionName, dexieBatch);
2917
2945
  }
2918
2946
  if (inMemSaveBatch.length > 0) {
2919
- this.deps.writeToInMemBatch(collectionName, inMemSaveBatch, "upsert");
2947
+ this.deps.writeToInMemBatch(collectionName, inMemSaveBatch, "upsert", { source });
2920
2948
  }
2921
2949
  if (inMemDeleteIds.length > 0) {
2922
2950
  this.deps.writeToInMemBatch(
2923
2951
  collectionName,
2924
2952
  inMemDeleteIds.map((id) => ({ _id: id })),
2925
- "delete"
2953
+ "delete",
2954
+ { source }
2926
2955
  );
2927
2956
  }
2928
2957
  }
@@ -3196,12 +3225,12 @@ var ServerUpdateHandler = class {
3196
3225
  await this.dexieDb.clearDirtyChange(collection, serverItem._id);
3197
3226
  }
3198
3227
  if (!serverItem._deleted && !serverItem._archived) {
3199
- this.deps.writeToInMemBatch(collection, [this.stripLocalFields(serverItem)], "upsert");
3228
+ this.deps.writeToInMemBatch(collection, [this.stripLocalFields(serverItem)], "upsert", { source: "incremental" });
3200
3229
  }
3201
3230
  } else {
3202
3231
  await this.dexieDb.insert(collection, serverItem);
3203
3232
  if (!serverItem._deleted && !serverItem._archived) {
3204
- this.deps.writeToInMemBatch(collection, [this.stripLocalFields(serverItem)], "upsert");
3233
+ this.deps.writeToInMemBatch(collection, [this.stripLocalFields(serverItem)], "upsert", { source: "incremental" });
3205
3234
  }
3206
3235
  }
3207
3236
  }
@@ -3230,7 +3259,7 @@ var ServerUpdateHandler = class {
3230
3259
  const currentInMemState = Object.assign({}, localItem, pendingChange.data);
3231
3260
  const merged = this.mergeLocalWithDelta(currentInMemState, serverDelta);
3232
3261
  if (!merged._deleted && !merged._archived) {
3233
- this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert");
3262
+ this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert", { source: "incremental" });
3234
3263
  }
3235
3264
  return;
3236
3265
  }
@@ -3242,16 +3271,16 @@ var ServerUpdateHandler = class {
3242
3271
  await this.dexieDb.save(collection, serverDelta._id, merged);
3243
3272
  }
3244
3273
  if (!merged._deleted && !merged._archived) {
3245
- this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert");
3274
+ this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert", { source: "incremental" });
3246
3275
  }
3247
3276
  } else {
3248
3277
  if (!metaChanged) return;
3249
3278
  const merged = this.mergeLocalWithDelta(localItem, serverDelta);
3250
3279
  await this.dexieDb.save(collection, serverDelta._id, merged);
3251
3280
  if (!merged._deleted && !merged._archived) {
3252
- this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert");
3281
+ this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert", { source: "incremental" });
3253
3282
  } else {
3254
- this.deps.writeToInMemBatch(collection, [{ _id: serverDelta._id }], "delete");
3283
+ this.deps.writeToInMemBatch(collection, [{ _id: serverDelta._id }], "delete", { source: "incremental" });
3255
3284
  }
3256
3285
  }
3257
3286
  }
@@ -3267,7 +3296,7 @@ var ServerUpdateHandler = class {
3267
3296
  } else {
3268
3297
  await this.dexieDb.deleteOne(collection, id);
3269
3298
  }
3270
- this.deps.writeToInMemBatch(collection, [{ _id: id }], "delete");
3299
+ this.deps.writeToInMemBatch(collection, [{ _id: id }], "delete", { source: "incremental" });
3271
3300
  }
3272
3301
  // ============================================================
3273
3302
  // Private Helpers
@@ -3592,8 +3621,8 @@ var _SyncedDb = class _SyncedDb {
3592
3621
  isLeader: () => this.leaderElection.isLeader(),
3593
3622
  getCollections: () => this.collections,
3594
3623
  dexieDb: this.dexieDb,
3595
- writeToInMemBatch: (collection, items, operation) => {
3596
- this.inMemManager.writeBatch(collection, items, operation);
3624
+ writeToInMemBatch: (collection, items, operation, opts) => {
3625
+ this.inMemManager.writeBatch(collection, items, operation, opts);
3597
3626
  },
3598
3627
  isSyncAllowed: (collection) => this.isSyncAllowed(collection),
3599
3628
  reloadCollectionFromDexie: (collection) => this.loadCollectionToInMem(collection).then(() => void 0)
@@ -3672,8 +3701,8 @@ var _SyncedDb = class _SyncedDb {
3672
3701
  deps: {
3673
3702
  getSyncMetaCache: () => this.syncMetaCache,
3674
3703
  setSyncMetaCache: (collection, meta) => this.syncMetaCache.set(collection, meta),
3675
- writeToInMemBatch: (collection, items, operation) => {
3676
- this.inMemManager.writeBatch(collection, items, operation);
3704
+ writeToInMemBatch: (collection, items, operation, opts) => {
3705
+ this.inMemManager.writeBatch(collection, items, operation, opts);
3677
3706
  },
3678
3707
  getInMemById: (collection, id) => this.inMemDb.getById(collection, id),
3679
3708
  withSyncTimeout: (promise, operation) => this.connectionManager.withSyncTimeout(promise, operation),
@@ -3697,8 +3726,8 @@ var _SyncedDb = class _SyncedDb {
3697
3726
  deps: {
3698
3727
  isLeader: () => this.leaderElection.isLeader(),
3699
3728
  canReceiveServerUpdates: () => this.connectionManager.canReceiveServerUpdates(),
3700
- writeToInMemBatch: (collection, items, operation) => {
3701
- this.inMemManager.writeBatch(collection, items, operation);
3729
+ writeToInMemBatch: (collection, items, operation, opts) => {
3730
+ this.inMemManager.writeBatch(collection, items, operation, opts);
3702
3731
  },
3703
3732
  broadcastUpdates: (updates) => this.crossTabSync.broadcastMetaUpdate(updates),
3704
3733
  getPendingChange: (collection, id) => this.pendingChanges.getPendingChange(collection, id),
@@ -4007,7 +4036,7 @@ var _SyncedDb = class _SyncedDb {
4007
4036
  if (serverItem) {
4008
4037
  await this.dexieDb.saveMany(collection, [serverItem]);
4009
4038
  if (!serverItem._deleted && !serverItem._archived) {
4010
- this.inMemManager.writeBatch(collection, [serverItem], "upsert");
4039
+ this.inMemManager.writeBatch(collection, [serverItem], "upsert", { source: "incremental" });
4011
4040
  }
4012
4041
  }
4013
4042
  } catch (e) {
@@ -4066,7 +4095,7 @@ var _SyncedDb = class _SyncedDb {
4066
4095
  await this.dexieDb.saveMany(collection, serverItems);
4067
4096
  const toInMem = serverItems.filter((s) => !s._deleted && !s._archived);
4068
4097
  if (toInMem.length > 0) {
4069
- this.inMemManager.writeBatch(collection, toInMem, "upsert");
4098
+ this.inMemManager.writeBatch(collection, toInMem, "upsert", { source: "incremental" });
4070
4099
  }
4071
4100
  }
4072
4101
  } catch (e) {
@@ -4185,7 +4214,8 @@ var _SyncedDb = class _SyncedDb {
4185
4214
  "syncCollectionForFind"
4186
4215
  );
4187
4216
  if (serverData.length > 0) {
4188
- await this.syncEngine.processCollectionServerData(collection, serverData);
4217
+ const source = timestamp === 0 ? "initial" : "incremental";
4218
+ await this.syncEngine.processCollectionServerData(collection, serverData, { source });
4189
4219
  }
4190
4220
  } catch (e) {
4191
4221
  }
@@ -4203,7 +4233,7 @@ var _SyncedDb = class _SyncedDb {
4203
4233
  "referToServer"
4204
4234
  ).then(async (serverData) => {
4205
4235
  if (serverData.length > 0) {
4206
- await this.syncEngine.processCollectionServerData(collection, serverData);
4236
+ await this.syncEngine.processCollectionServerData(collection, serverData, { source: "refresh" });
4207
4237
  }
4208
4238
  }).catch((err) => {
4209
4239
  console.error(`referToServer failed for ${collection}:`, err);
@@ -4225,7 +4255,7 @@ var _SyncedDb = class _SyncedDb {
4225
4255
  "refreshInBackground"
4226
4256
  ).then(async (serverItems) => {
4227
4257
  if (!serverItems || serverItems.length === 0) return;
4228
- await this.syncEngine.processCollectionServerData(collection, serverItems);
4258
+ await this.syncEngine.processCollectionServerData(collection, serverItems, { source: "incremental" });
4229
4259
  }).catch((err) => {
4230
4260
  console.error(`refreshInBackground failed for ${collection}:`, err);
4231
4261
  });
@@ -4263,7 +4293,7 @@ var _SyncedDb = class _SyncedDb {
4263
4293
  await this.dexieDb.saveMany(collection, toSaveDexie);
4264
4294
  }
4265
4295
  if (toSaveInMem.length > 0) {
4266
- this.inMemManager.writeBatch(collection, toSaveInMem, "upsert");
4296
+ this.inMemManager.writeBatch(collection, toSaveInMem, "upsert", { source: "incremental" });
4267
4297
  }
4268
4298
  }
4269
4299
  // ==================== Write Operations ====================
@@ -4298,7 +4328,7 @@ var _SyncedDb = class _SyncedDb {
4298
4328
  const currentMem = isWriteOnly ? null : this.inMemDb.getById(collection, id);
4299
4329
  const merged = __spreadValues(__spreadValues({}, currentMem || existing || { _id: id }), update);
4300
4330
  if (!isWriteOnly && !(existing == null ? void 0 : existing._deleted) && !(existing == null ? void 0 : existing._archived)) {
4301
- this.inMemManager.writeBatch(collection, [merged], "upsert");
4331
+ this.inMemManager.writeBatch(collection, [merged], "upsert", { source: "incremental" });
4302
4332
  }
4303
4333
  return merged;
4304
4334
  }
@@ -4338,7 +4368,7 @@ var _SyncedDb = class _SyncedDb {
4338
4368
  });
4339
4369
  this.pendingChanges.schedule(collection, id, newData, 0, "insert");
4340
4370
  if (!((_a = this.collections.get(collection)) == null ? void 0 : _a.writeOnly)) {
4341
- this.inMemManager.writeBatch(collection, [newData], "upsert");
4371
+ this.inMemManager.writeBatch(collection, [newData], "upsert", { source: "incremental" });
4342
4372
  }
4343
4373
  return newData;
4344
4374
  }
@@ -4362,7 +4392,7 @@ var _SyncedDb = class _SyncedDb {
4362
4392
  };
4363
4393
  this.pendingChanges.schedule(collection, id, deleteUpdate, 0, "deleteOne");
4364
4394
  if (!((_a = this.collections.get(collection)) == null ? void 0 : _a.writeOnly)) {
4365
- this.inMemManager.writeBatch(collection, [{ _id: id }], "delete");
4395
+ this.inMemManager.writeBatch(collection, [{ _id: id }], "delete", { source: "incremental" });
4366
4396
  }
4367
4397
  return existing;
4368
4398
  }
@@ -4399,7 +4429,8 @@ var _SyncedDb = class _SyncedDb {
4399
4429
  this.inMemManager.writeBatch(
4400
4430
  collection,
4401
4431
  idsToDelete.map((id) => ({ _id: id })),
4402
- "delete"
4432
+ "delete",
4433
+ { source: "incremental" }
4403
4434
  );
4404
4435
  }
4405
4436
  return idsToDelete.length;
@@ -4420,7 +4451,7 @@ var _SyncedDb = class _SyncedDb {
4420
4451
  "hardDeleteOne"
4421
4452
  );
4422
4453
  await this.dexieDb.deleteOne(collection, id);
4423
- this.inMemManager.writeBatch(collection, [{ _id: id }], "delete");
4454
+ this.inMemManager.writeBatch(collection, [{ _id: id }], "delete", { source: "incremental" });
4424
4455
  return existing;
4425
4456
  }
4426
4457
  async hardDelete(collection, query) {
@@ -4457,7 +4488,7 @@ var _SyncedDb = class _SyncedDb {
4457
4488
  "hardDelete"
4458
4489
  );
4459
4490
  await this.dexieDb.deleteOne(collection, item.id);
4460
- this.inMemManager.writeBatch(collection, [{ _id: item.id }], "delete");
4491
+ this.inMemManager.writeBatch(collection, [{ _id: item.id }], "delete", { source: "incremental" });
4461
4492
  results.push(true);
4462
4493
  } catch (err) {
4463
4494
  console.error(`Failed to hard delete ${String(item.id)}:`, err);
@@ -4837,7 +4868,8 @@ var _SyncedDb = class _SyncedDb {
4837
4868
  this.inMemManager.writeBatch(
4838
4869
  collection,
4839
4870
  evictIds.map((id) => ({ _id: id })),
4840
- "delete"
4871
+ "delete",
4872
+ { source: "incremental" }
4841
4873
  );
4842
4874
  }
4843
4875
  this.crossTabSync.broadcastReload([collection]);
@@ -5090,7 +5122,8 @@ var _SyncedDb = class _SyncedDb {
5090
5122
  this.inMemManager.writeBatch(
5091
5123
  p.collection,
5092
5124
  uniqueEvictIds.map((id) => ({ _id: id })),
5093
- "delete"
5125
+ "delete",
5126
+ { source: "incremental" }
5094
5127
  );
5095
5128
  }
5096
5129
  this.crossTabSync.broadcastReload([p.collection]);
@@ -7,6 +7,7 @@
7
7
  * - Encapsulation: Hides metadata storage implementation
8
8
  */
9
9
  import type { Id, DbEntity } from "../../types/DbEntity";
10
+ import type { SyncSource } from "../../types/I_InMemDb";
10
11
  import type { I_InMemManager, InMemManagerConfig } from "../types/managers";
11
12
  export declare class InMemManager implements I_InMemManager {
12
13
  private readonly inMemDb;
@@ -18,8 +19,15 @@ export declare class InMemManager implements I_InMemManager {
18
19
  /**
19
20
  * Write batch of items to in-mem.
20
21
  * Handles metadata callbacks automatically when useObjectMetadata is enabled.
22
+ *
23
+ * @param opts.source — origin of the write ('initial' | 'refresh' | 'incremental').
24
+ * Implementacija I_InMemDb lahko ta signal uporabi za optimizacije
25
+ * (npr. selektivna reactivity); ne vpliva na sync semantiko.
26
+ * Default: 'incremental'.
21
27
  */
22
- writeBatch<T extends DbEntity>(collection: string, items: T[], operation: "upsert" | "delete"): void;
28
+ writeBatch<T extends DbEntity>(collection: string, items: T[], operation: "upsert" | "delete", opts?: {
29
+ source?: SyncSource;
30
+ }): void;
23
31
  /**
24
32
  * Initialize collection from Dexie data.
25
33
  * Called during SyncedDb.init().
@@ -7,6 +7,7 @@
7
7
  * - Uploading dirty items to server
8
8
  */
9
9
  import type { LocalDbEntity } from "../../types/DbEntity";
10
+ import type { SyncSource } from "../../types/I_InMemDb";
10
11
  import type { I_SyncEngine, SyncEngineConfig, SyncExtras } from "../types/managers";
11
12
  import type { UploadResult } from "../types/internal";
12
13
  export declare class SyncEngine implements I_SyncEngine {
@@ -39,8 +40,14 @@ export declare class SyncEngine implements I_SyncEngine {
39
40
  /**
40
41
  * Process incoming server data for a single collection.
41
42
  * Used by referToServer to process findNewer results.
43
+ *
44
+ * @param opts.source — origin label posredovan v writeBatch → inMemDb.
45
+ * Default `'incremental'`. Caller naj uporabi `'refresh'` za množični
46
+ * reload (referToServerSync) in `'initial'` za first-time fill.
42
47
  */
43
- processCollectionServerData(collectionName: string, serverData: LocalDbEntity[]): Promise<{
48
+ processCollectionServerData(collectionName: string, serverData: LocalDbEntity[], opts?: {
49
+ source?: SyncSource;
50
+ }): Promise<{
44
51
  updatedIds: string[];
45
52
  }>;
46
53
  /** Max items to process per batch in processIncomingServerData */
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import type { Id, DbEntity, LocalDbEntity } from "../../types/DbEntity";
6
6
  import type { I_DexieDb, SyncMeta, MetaUpdateBroadcast } from "../../types/I_DexieDb";
7
- import type { I_InMemDb } from "../../types/I_InMemDb";
7
+ import type { I_InMemDb, SyncSource } from "../../types/I_InMemDb";
8
8
  import type { I_RestInterface } from "../../types/I_RestInterface";
9
9
  import type { PublishDataPayload } from "../../types/PublishRevsPayload";
10
10
  import type { CollectionConfig, SyncInfo, ConflictResolutionReport, ServerWriteRequestInfo, ServerWriteResultInfo, FindNewerManyCallInfo, FindNewerManyResultInfo, DexieWriteRequestInfo, DexieWriteResultInfo, LocalstorageWriteResultInfo, WsNotificationInfo, CrossTabSyncInfo } from "../../types/I_SyncedDb";
@@ -49,7 +49,9 @@ export interface CrossTabSyncDeps {
49
49
  /** DexieDb instance. */
50
50
  dexieDb: I_DexieDb;
51
51
  /** Write to in-mem batch. */
52
- writeToInMemBatch: <T extends DbEntity>(collection: string, items: T[], operation: "upsert" | "delete") => void;
52
+ writeToInMemBatch: <T extends DbEntity>(collection: string, items: T[], operation: "upsert" | "delete", opts?: {
53
+ source?: SyncSource;
54
+ }) => void;
53
55
  /** Whether a collection participates in sync (not writeOnly, not filtered out). */
54
56
  isSyncAllowed: (collection: string) => boolean;
55
57
  /** Reload a collection fully from Dexie into in-mem (called on reload broadcast). */
@@ -212,7 +214,9 @@ export interface InMemManagerConfig {
212
214
  }
213
215
  export interface I_InMemManager {
214
216
  /** Write batch of items to in-mem. */
215
- writeBatch<T extends DbEntity>(collection: string, items: T[], operation: "upsert" | "delete"): void;
217
+ writeBatch<T extends DbEntity>(collection: string, items: T[], operation: "upsert" | "delete", opts?: {
218
+ source?: import("../../types/I_InMemDb").SyncSource;
219
+ }): void;
216
220
  /** Initialize collection from Dexie data. */
217
221
  initCollection<T extends DbEntity>(collection: string, items: T[]): void;
218
222
  /** Clear collection data and metadata. */
@@ -261,7 +265,9 @@ export interface SyncEngineCallbacks {
261
265
  export interface SyncEngineDeps {
262
266
  getSyncMetaCache: () => Map<string, SyncMeta>;
263
267
  setSyncMetaCache: (collection: string, meta: SyncMeta) => void;
264
- writeToInMemBatch: <T extends DbEntity>(collection: string, items: T[], operation: "upsert" | "delete") => void;
268
+ writeToInMemBatch: <T extends DbEntity>(collection: string, items: T[], operation: "upsert" | "delete", opts?: {
269
+ source?: SyncSource;
270
+ }) => void;
265
271
  getInMemById: <T extends DbEntity>(collection: string, id: Id) => T | undefined;
266
272
  withSyncTimeout: <T>(promise: Promise<T>, operation: string) => Promise<T>;
267
273
  /** Notify consumers that a sync cycle failed. Does not mutate online state. */
@@ -311,7 +317,9 @@ export interface I_SyncEngine {
311
317
  /** Upload dirty items for a specific collection. */
312
318
  uploadDirtyItemsForCollection(collection: string): Promise<UploadResult>;
313
319
  /** Process incoming server data for a single collection (used by referToServer). */
314
- processCollectionServerData(collectionName: string, serverData: import("../../types/DbEntity").LocalDbEntity[]): Promise<{
320
+ processCollectionServerData(collectionName: string, serverData: import("../../types/DbEntity").LocalDbEntity[], opts?: {
321
+ source?: SyncSource;
322
+ }): Promise<{
315
323
  updatedIds: string[];
316
324
  }>;
317
325
  }
@@ -323,7 +331,9 @@ export interface ServerUpdateHandlerDeps {
323
331
  canReceiveServerUpdates: () => boolean;
324
332
  getPendingChange: (collection: string, id: Id) => PendingChange | undefined;
325
333
  broadcastUpdates: (updates: Record<string, string[]>) => void;
326
- writeToInMemBatch: <T extends DbEntity>(collection: string, items: T[], operation: "upsert" | "delete") => void;
334
+ writeToInMemBatch: <T extends DbEntity>(collection: string, items: T[], operation: "upsert" | "delete", opts?: {
335
+ source?: SyncSource;
336
+ }) => void;
327
337
  /** Whether a collection participates in sync (not writeOnly, not filtered out). */
328
338
  isSyncAllowed: (collection: string) => boolean;
329
339
  }
@@ -1,13 +1,34 @@
1
1
  import type { Id, DbEntity } from "./DbEntity";
2
+ /**
3
+ * Izvor sync zapisa. Implementacija I_InMemDb lahko ta signal
4
+ * uporabi za optimizacije (npr. selektivna reactivity v klijentu),
5
+ * ne vpliva pa na sync semantiko.
6
+ *
7
+ * - 'initial' — full snapshot pri prvem polnjenju kolekcije
8
+ * - 'refresh' — refreshLocal pull (množični reload obstoječe kolekcije)
9
+ * - 'incremental' — delta s servera (vključno z lokalnim optimistic write
10
+ * pred server confirmom)
11
+ */
12
+ export type SyncSource = "initial" | "refresh" | "incremental";
13
+ /**
14
+ * Opcije za zapisovalne operacije.
15
+ * Field-i so opcijski — implementacije, ki jih ne uporabljajo, jih lahko ignorirajo.
16
+ */
17
+ export interface SaveManyOpts {
18
+ source?: SyncSource;
19
+ }
20
+ export interface DeleteManyByIdsOpts {
21
+ source?: SyncSource;
22
+ }
2
23
  /**
3
24
  * Interface za in-memory bazo podatkov
4
25
  * UI samo bere iz te baze, posodablja jo samo sync-db
5
26
  */
6
27
  export interface I_InMemDb {
7
28
  /** Shrani/posodobi več objektov naenkrat (bulk upsert) */
8
- saveMany<T extends DbEntity>(collection: string, items: T[]): void;
29
+ saveMany<T extends DbEntity>(collection: string, items: T[], opts?: SaveManyOpts): void;
9
30
  /** Izbriše več objektov iz kolekcije po ID-jih */
10
- deleteManyByIds(collection: string, ids: Id[]): void;
31
+ deleteManyByIds(collection: string, ids: Id[], opts?: DeleteManyByIdsOpts): void;
11
32
  /** Shrani celotno kolekcijo (nadomesti obstoječo) */
12
33
  saveCollection<T extends DbEntity>(collection: string, data: T[]): void;
13
34
  /** Izbriše celotno kolekcijo */
@@ -1,7 +1,7 @@
1
1
  export type { Id, Entity, IdOrEntity, DbEntity, LocalDbEntity } from "./DbEntity";
2
2
  export type { PublishableOperation, PublishRevsPayloadInsert, PublishRevsPayloadUpdate, PublishRevsPayloadDelete, PublishRevsPayloadUpdateMany, PublishRevsPayloadDeleteMany, PublishRevsPayloadBatchItem, PublishRevsPayloadBatch, PublishRevsPayload, PublishRevsSpec, PublishDataPayloadBase, PublishDataPayloadInsert, PublishDataPayloadUpdate, PublishDataPayloadDelete, PublishDataPayloadBatch, PublishDataPayload, PublishDataSpec, PublishSpec, } from "./PublishRevsPayload";
3
3
  export type { Obj, QuerySpec, Projection, QueryOpts, KeyOf, InsertKeyOf, InsertSpec, UpdateSpec, BatchSpec, UpsertOptions, GetNewerSpec, I_RestInterface as RestInterface, } from "./I_RestInterface";
4
- export type { I_InMemDb as InMemDb } from "./I_InMemDb";
4
+ export type { I_InMemDb as InMemDb, SyncSource, SaveManyOpts, DeleteManyByIdsOpts } from "./I_InMemDb";
5
5
  export type { I_DexieDb as DexieDb, SyncMeta, DirtyChange, DirtyMeta } from "./I_DexieDb";
6
6
  export type { I_ServerUpdateNotifier as ServerUpdateNotifier, ServerUpdateCallback, ServerUpdateNotifierCallbacks } from "./I_ServerUpdateNotifier";
7
7
  export type { I_SyncedDb as SyncedDb, SyncedDbConfig, CollectionConfig, CollectionSyncConfig, SyncInfo, ServerWriteRequestInfo, ServerWriteResultInfo, FindNewerManyCallInfo, FindNewerManyResultInfo, DexieWriteRequestInfo, DexieWriteResultInfo, LocalstorageWriteResultInfo, WsNotificationInfo, InfrastructureErrorType, InfrastructureErrorInfo, ConflictSource, ConflictResolutionReport, CrossTabSyncInfo, EvictionInfo, EvictionCollectionInfo, } from "./I_SyncedDb";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cry-synced-db-client",
3
- "version": "0.1.147",
3
+ "version": "0.1.150",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",