cry-synced-db-client 0.1.148 → 0.1.151

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,42 @@
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
+
5
41
  ### `uploadDirtyItems` follow-up pass — drain in-sync writes immediately
6
42
 
7
43
  Writes that land **during** a sync iteration had their
package/dist/index.js CHANGED
@@ -69,16 +69,13 @@ function matchesQuery(item, query) {
69
69
  function matchesCondition(item, key, condition) {
70
70
  const value = getNestedValue(item, key);
71
71
  if (condition === null || typeof condition !== "object") {
72
- return value === condition;
72
+ return scalarMatches(value, condition);
73
73
  }
74
74
  if (condition instanceof Date) {
75
- if (value instanceof Date) {
76
- return value.getTime() === condition.getTime();
77
- }
78
- return false;
75
+ return scalarMatches(value, condition);
79
76
  }
80
77
  if (Array.isArray(condition)) {
81
- return condition.includes(value);
78
+ return condition.some((c) => scalarMatches(value, c));
82
79
  }
83
80
  for (const [op, opValue] of Object.entries(condition)) {
84
81
  if (!matchesOperator(value, op, opValue)) {
@@ -90,29 +87,32 @@ function matchesCondition(item, key, condition) {
90
87
  function matchesOperator(value, operator, operand) {
91
88
  switch (operator) {
92
89
  case "$eq":
93
- return equals(value, operand);
90
+ return scalarMatches(value, operand);
94
91
  case "$ne":
95
- return !equals(value, operand);
92
+ return !scalarMatches(value, operand);
96
93
  case "$gt":
97
- return value > operand;
94
+ return compareScalarOrArray(value, (v) => v > operand);
98
95
  case "$gte":
99
- return value >= operand;
96
+ return compareScalarOrArray(value, (v) => v >= operand);
100
97
  case "$lt":
101
- return value < operand;
98
+ return compareScalarOrArray(value, (v) => v < operand);
102
99
  case "$lte":
103
- return value <= operand;
100
+ return compareScalarOrArray(value, (v) => v <= operand);
104
101
  case "$in":
105
102
  if (!Array.isArray(operand)) return false;
106
- return operand.some((item) => equals(value, item));
103
+ return operand.some((item) => scalarMatches(value, item));
107
104
  case "$nin":
108
105
  if (!Array.isArray(operand)) return true;
109
- return !operand.some((item) => equals(value, item));
106
+ return !operand.some((item) => scalarMatches(value, item));
110
107
  case "$exists":
111
108
  return operand ? value !== void 0 : value === void 0;
112
109
  case "$regex": {
113
- if (typeof value !== "string") return false;
114
110
  const regex = operand instanceof RegExp ? operand : new RegExp(operand);
115
- return regex.test(value);
111
+ if (typeof value === "string") return regex.test(value);
112
+ if (Array.isArray(value)) {
113
+ return value.some((v) => typeof v === "string" && regex.test(v));
114
+ }
115
+ return false;
116
116
  }
117
117
  case "$elemMatch":
118
118
  if (!Array.isArray(value)) return false;
@@ -152,6 +152,19 @@ function getNestedValue(obj, path) {
152
152
  }
153
153
  return current;
154
154
  }
155
+ function scalarMatches(value, scalar) {
156
+ if (equals(value, scalar)) return true;
157
+ if (Array.isArray(value)) {
158
+ return value.some((elem) => equals(elem, scalar));
159
+ }
160
+ return false;
161
+ }
162
+ function compareScalarOrArray(value, predicate) {
163
+ if (Array.isArray(value)) {
164
+ return value.some((v) => predicate(v));
165
+ }
166
+ return predicate(value);
167
+ }
155
168
  function equals(a, b) {
156
169
  var _a, _b;
157
170
  if (a === b) return true;
@@ -272,12 +285,19 @@ var InMemManager = class {
272
285
  /**
273
286
  * Write batch of items to in-mem.
274
287
  * Handles metadata callbacks automatically when useObjectMetadata is enabled.
288
+ *
289
+ * @param opts.source — origin of the write ('initial' | 'refresh' | 'incremental').
290
+ * Implementacija I_InMemDb lahko ta signal uporabi za optimizacije
291
+ * (npr. selektivna reactivity); ne vpliva na sync semantiko.
292
+ * Default: 'incremental'.
275
293
  */
276
- writeBatch(collection, items, operation) {
294
+ writeBatch(collection, items, operation, opts) {
295
+ var _a;
277
296
  if (items.length === 0) return;
278
297
  const config = this.collections.get(collection);
298
+ const source = (_a = opts == null ? void 0 : opts.source) != null ? _a : "incremental";
279
299
  if (operation === "upsert") {
280
- this.inMemDb.saveMany(collection, items);
300
+ this.inMemDb.saveMany(collection, items, { source });
281
301
  if (this.useObjectMetadata && (config == null ? void 0 : config.hasMetadata)) {
282
302
  const ids = [];
283
303
  for (const item of items) ids.push(item._id);
@@ -304,7 +324,7 @@ var InMemManager = class {
304
324
  } else if (operation === "delete") {
305
325
  const ids = [];
306
326
  for (const item of items) ids.push(item._id);
307
- this.inMemDb.deleteManyByIds(collection, ids);
327
+ this.inMemDb.deleteManyByIds(collection, ids, { source });
308
328
  if (this.useObjectMetadata && (config == null ? void 0 : config.hasMetadata)) {
309
329
  this.deleteObjectsMetadataInternal(collection, ids);
310
330
  }
@@ -2478,10 +2498,13 @@ var _SyncEngine = class _SyncEngine {
2478
2498
  const findNewerManyStartTime = Date.now();
2479
2499
  const collectionState = /* @__PURE__ */ new Map();
2480
2500
  for (const [name] of configMap) {
2501
+ const prior = this.deps.getSyncMetaCache().get(name);
2502
+ const isInitial = !(prior == null ? void 0 : prior.lastSyncTs);
2481
2503
  collectionState.set(name, {
2482
2504
  maxTs: void 0,
2483
2505
  conflicts: 0,
2484
- receivedCount: 0
2506
+ receivedCount: 0,
2507
+ source: isInitial ? "initial" : "incremental"
2485
2508
  });
2486
2509
  }
2487
2510
  try {
@@ -2499,7 +2522,7 @@ var _SyncEngine = class _SyncEngine {
2499
2522
  if (!config) return;
2500
2523
  const state = collectionState.get(collection);
2501
2524
  state.receivedCount += items.length;
2502
- const stats = await this.processIncomingServerData(collection, config, items);
2525
+ const stats = await this.processIncomingServerData(collection, config, items, state.source);
2503
2526
  state.conflicts += stats.conflictsResolved;
2504
2527
  if (stats.maxTs) {
2505
2528
  if (!state.maxTs || this.compareTimestamps(stats.maxTs, state.maxTs) > 0) {
@@ -2723,13 +2746,14 @@ var _SyncEngine = class _SyncEngine {
2723
2746
  await this.dexieDb.deleteMany(collection, dexieDeleteIds);
2724
2747
  }
2725
2748
  if (inMemUpdateBatch.length > 0) {
2726
- this.deps.writeToInMemBatch(collection, inMemUpdateBatch, "upsert");
2749
+ this.deps.writeToInMemBatch(collection, inMemUpdateBatch, "upsert", { source: "incremental" });
2727
2750
  }
2728
2751
  if (inMemDeleteIds.length > 0) {
2729
2752
  this.deps.writeToInMemBatch(
2730
2753
  collection,
2731
2754
  inMemDeleteIds.map((id) => ({ _id: id })),
2732
- "delete"
2755
+ "delete",
2756
+ { source: "incremental" }
2733
2757
  );
2734
2758
  }
2735
2759
  }
@@ -2745,7 +2769,7 @@ var _SyncEngine = class _SyncEngine {
2745
2769
  }
2746
2770
  await this.dexieDb.deleteMany(collection, deleteIds);
2747
2771
  if (!isWriteOnly) {
2748
- this.deps.writeToInMemBatch(collection, deleteDbEntities, "delete");
2772
+ this.deps.writeToInMemBatch(collection, deleteDbEntities, "delete", { source: "incremental" });
2749
2773
  }
2750
2774
  sentCount += deleted.length;
2751
2775
  collectionSentCount += deleted.length;
@@ -2855,17 +2879,23 @@ var _SyncEngine = class _SyncEngine {
2855
2879
  /**
2856
2880
  * Process incoming server data for a single collection.
2857
2881
  * Used by referToServer to process findNewer results.
2882
+ *
2883
+ * @param opts.source — origin label posredovan v writeBatch → inMemDb.
2884
+ * Default `'incremental'`. Caller naj uporabi `'refresh'` za množični
2885
+ * reload (referToServerSync) in `'initial'` za first-time fill.
2858
2886
  */
2859
- async processCollectionServerData(collectionName, serverData) {
2887
+ async processCollectionServerData(collectionName, serverData, opts) {
2888
+ var _a;
2860
2889
  const config = this.collections.get(collectionName);
2861
2890
  if (!config) return { updatedIds: [] };
2862
- const result = await this.processIncomingServerData(collectionName, config, serverData);
2891
+ const source = (_a = opts == null ? void 0 : opts.source) != null ? _a : "incremental";
2892
+ const result = await this.processIncomingServerData(collectionName, config, serverData, source);
2863
2893
  if (result.updatedIds.length > 0) {
2864
2894
  this.deps.broadcastUpdates({ [collectionName]: result.updatedIds });
2865
2895
  }
2866
2896
  return { updatedIds: result.updatedIds };
2867
2897
  }
2868
- async processIncomingServerData(collectionName, config, serverData) {
2898
+ async processIncomingServerData(collectionName, config, serverData, source = "incremental") {
2869
2899
  if (serverData.length === 0) {
2870
2900
  return { conflictsResolved: 0, maxTs: void 0, updatedIds: [] };
2871
2901
  }
@@ -2927,13 +2957,14 @@ var _SyncEngine = class _SyncEngine {
2927
2957
  await this.dexieDb.saveMany(collectionName, dexieBatch);
2928
2958
  }
2929
2959
  if (inMemSaveBatch.length > 0) {
2930
- this.deps.writeToInMemBatch(collectionName, inMemSaveBatch, "upsert");
2960
+ this.deps.writeToInMemBatch(collectionName, inMemSaveBatch, "upsert", { source });
2931
2961
  }
2932
2962
  if (inMemDeleteIds.length > 0) {
2933
2963
  this.deps.writeToInMemBatch(
2934
2964
  collectionName,
2935
2965
  inMemDeleteIds.map((id) => ({ _id: id })),
2936
- "delete"
2966
+ "delete",
2967
+ { source }
2937
2968
  );
2938
2969
  }
2939
2970
  }
@@ -3207,12 +3238,12 @@ var ServerUpdateHandler = class {
3207
3238
  await this.dexieDb.clearDirtyChange(collection, serverItem._id);
3208
3239
  }
3209
3240
  if (!serverItem._deleted && !serverItem._archived) {
3210
- this.deps.writeToInMemBatch(collection, [this.stripLocalFields(serverItem)], "upsert");
3241
+ this.deps.writeToInMemBatch(collection, [this.stripLocalFields(serverItem)], "upsert", { source: "incremental" });
3211
3242
  }
3212
3243
  } else {
3213
3244
  await this.dexieDb.insert(collection, serverItem);
3214
3245
  if (!serverItem._deleted && !serverItem._archived) {
3215
- this.deps.writeToInMemBatch(collection, [this.stripLocalFields(serverItem)], "upsert");
3246
+ this.deps.writeToInMemBatch(collection, [this.stripLocalFields(serverItem)], "upsert", { source: "incremental" });
3216
3247
  }
3217
3248
  }
3218
3249
  }
@@ -3241,7 +3272,7 @@ var ServerUpdateHandler = class {
3241
3272
  const currentInMemState = Object.assign({}, localItem, pendingChange.data);
3242
3273
  const merged = this.mergeLocalWithDelta(currentInMemState, serverDelta);
3243
3274
  if (!merged._deleted && !merged._archived) {
3244
- this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert");
3275
+ this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert", { source: "incremental" });
3245
3276
  }
3246
3277
  return;
3247
3278
  }
@@ -3253,16 +3284,16 @@ var ServerUpdateHandler = class {
3253
3284
  await this.dexieDb.save(collection, serverDelta._id, merged);
3254
3285
  }
3255
3286
  if (!merged._deleted && !merged._archived) {
3256
- this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert");
3287
+ this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert", { source: "incremental" });
3257
3288
  }
3258
3289
  } else {
3259
3290
  if (!metaChanged) return;
3260
3291
  const merged = this.mergeLocalWithDelta(localItem, serverDelta);
3261
3292
  await this.dexieDb.save(collection, serverDelta._id, merged);
3262
3293
  if (!merged._deleted && !merged._archived) {
3263
- this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert");
3294
+ this.deps.writeToInMemBatch(collection, [this.stripLocalFields(merged)], "upsert", { source: "incremental" });
3264
3295
  } else {
3265
- this.deps.writeToInMemBatch(collection, [{ _id: serverDelta._id }], "delete");
3296
+ this.deps.writeToInMemBatch(collection, [{ _id: serverDelta._id }], "delete", { source: "incremental" });
3266
3297
  }
3267
3298
  }
3268
3299
  }
@@ -3278,7 +3309,7 @@ var ServerUpdateHandler = class {
3278
3309
  } else {
3279
3310
  await this.dexieDb.deleteOne(collection, id);
3280
3311
  }
3281
- this.deps.writeToInMemBatch(collection, [{ _id: id }], "delete");
3312
+ this.deps.writeToInMemBatch(collection, [{ _id: id }], "delete", { source: "incremental" });
3282
3313
  }
3283
3314
  // ============================================================
3284
3315
  // Private Helpers
@@ -3603,8 +3634,8 @@ var _SyncedDb = class _SyncedDb {
3603
3634
  isLeader: () => this.leaderElection.isLeader(),
3604
3635
  getCollections: () => this.collections,
3605
3636
  dexieDb: this.dexieDb,
3606
- writeToInMemBatch: (collection, items, operation) => {
3607
- this.inMemManager.writeBatch(collection, items, operation);
3637
+ writeToInMemBatch: (collection, items, operation, opts) => {
3638
+ this.inMemManager.writeBatch(collection, items, operation, opts);
3608
3639
  },
3609
3640
  isSyncAllowed: (collection) => this.isSyncAllowed(collection),
3610
3641
  reloadCollectionFromDexie: (collection) => this.loadCollectionToInMem(collection).then(() => void 0)
@@ -3683,8 +3714,8 @@ var _SyncedDb = class _SyncedDb {
3683
3714
  deps: {
3684
3715
  getSyncMetaCache: () => this.syncMetaCache,
3685
3716
  setSyncMetaCache: (collection, meta) => this.syncMetaCache.set(collection, meta),
3686
- writeToInMemBatch: (collection, items, operation) => {
3687
- this.inMemManager.writeBatch(collection, items, operation);
3717
+ writeToInMemBatch: (collection, items, operation, opts) => {
3718
+ this.inMemManager.writeBatch(collection, items, operation, opts);
3688
3719
  },
3689
3720
  getInMemById: (collection, id) => this.inMemDb.getById(collection, id),
3690
3721
  withSyncTimeout: (promise, operation) => this.connectionManager.withSyncTimeout(promise, operation),
@@ -3708,8 +3739,8 @@ var _SyncedDb = class _SyncedDb {
3708
3739
  deps: {
3709
3740
  isLeader: () => this.leaderElection.isLeader(),
3710
3741
  canReceiveServerUpdates: () => this.connectionManager.canReceiveServerUpdates(),
3711
- writeToInMemBatch: (collection, items, operation) => {
3712
- this.inMemManager.writeBatch(collection, items, operation);
3742
+ writeToInMemBatch: (collection, items, operation, opts) => {
3743
+ this.inMemManager.writeBatch(collection, items, operation, opts);
3713
3744
  },
3714
3745
  broadcastUpdates: (updates) => this.crossTabSync.broadcastMetaUpdate(updates),
3715
3746
  getPendingChange: (collection, id) => this.pendingChanges.getPendingChange(collection, id),
@@ -4018,7 +4049,7 @@ var _SyncedDb = class _SyncedDb {
4018
4049
  if (serverItem) {
4019
4050
  await this.dexieDb.saveMany(collection, [serverItem]);
4020
4051
  if (!serverItem._deleted && !serverItem._archived) {
4021
- this.inMemManager.writeBatch(collection, [serverItem], "upsert");
4052
+ this.inMemManager.writeBatch(collection, [serverItem], "upsert", { source: "incremental" });
4022
4053
  }
4023
4054
  }
4024
4055
  } catch (e) {
@@ -4077,7 +4108,7 @@ var _SyncedDb = class _SyncedDb {
4077
4108
  await this.dexieDb.saveMany(collection, serverItems);
4078
4109
  const toInMem = serverItems.filter((s) => !s._deleted && !s._archived);
4079
4110
  if (toInMem.length > 0) {
4080
- this.inMemManager.writeBatch(collection, toInMem, "upsert");
4111
+ this.inMemManager.writeBatch(collection, toInMem, "upsert", { source: "incremental" });
4081
4112
  }
4082
4113
  }
4083
4114
  } catch (e) {
@@ -4196,7 +4227,8 @@ var _SyncedDb = class _SyncedDb {
4196
4227
  "syncCollectionForFind"
4197
4228
  );
4198
4229
  if (serverData.length > 0) {
4199
- await this.syncEngine.processCollectionServerData(collection, serverData);
4230
+ const source = timestamp === 0 ? "initial" : "incremental";
4231
+ await this.syncEngine.processCollectionServerData(collection, serverData, { source });
4200
4232
  }
4201
4233
  } catch (e) {
4202
4234
  }
@@ -4214,7 +4246,7 @@ var _SyncedDb = class _SyncedDb {
4214
4246
  "referToServer"
4215
4247
  ).then(async (serverData) => {
4216
4248
  if (serverData.length > 0) {
4217
- await this.syncEngine.processCollectionServerData(collection, serverData);
4249
+ await this.syncEngine.processCollectionServerData(collection, serverData, { source: "refresh" });
4218
4250
  }
4219
4251
  }).catch((err) => {
4220
4252
  console.error(`referToServer failed for ${collection}:`, err);
@@ -4236,7 +4268,7 @@ var _SyncedDb = class _SyncedDb {
4236
4268
  "refreshInBackground"
4237
4269
  ).then(async (serverItems) => {
4238
4270
  if (!serverItems || serverItems.length === 0) return;
4239
- await this.syncEngine.processCollectionServerData(collection, serverItems);
4271
+ await this.syncEngine.processCollectionServerData(collection, serverItems, { source: "incremental" });
4240
4272
  }).catch((err) => {
4241
4273
  console.error(`refreshInBackground failed for ${collection}:`, err);
4242
4274
  });
@@ -4274,7 +4306,7 @@ var _SyncedDb = class _SyncedDb {
4274
4306
  await this.dexieDb.saveMany(collection, toSaveDexie);
4275
4307
  }
4276
4308
  if (toSaveInMem.length > 0) {
4277
- this.inMemManager.writeBatch(collection, toSaveInMem, "upsert");
4309
+ this.inMemManager.writeBatch(collection, toSaveInMem, "upsert", { source: "incremental" });
4278
4310
  }
4279
4311
  }
4280
4312
  // ==================== Write Operations ====================
@@ -4309,7 +4341,7 @@ var _SyncedDb = class _SyncedDb {
4309
4341
  const currentMem = isWriteOnly ? null : this.inMemDb.getById(collection, id);
4310
4342
  const merged = __spreadValues(__spreadValues({}, currentMem || existing || { _id: id }), update);
4311
4343
  if (!isWriteOnly && !(existing == null ? void 0 : existing._deleted) && !(existing == null ? void 0 : existing._archived)) {
4312
- this.inMemManager.writeBatch(collection, [merged], "upsert");
4344
+ this.inMemManager.writeBatch(collection, [merged], "upsert", { source: "incremental" });
4313
4345
  }
4314
4346
  return merged;
4315
4347
  }
@@ -4349,7 +4381,7 @@ var _SyncedDb = class _SyncedDb {
4349
4381
  });
4350
4382
  this.pendingChanges.schedule(collection, id, newData, 0, "insert");
4351
4383
  if (!((_a = this.collections.get(collection)) == null ? void 0 : _a.writeOnly)) {
4352
- this.inMemManager.writeBatch(collection, [newData], "upsert");
4384
+ this.inMemManager.writeBatch(collection, [newData], "upsert", { source: "incremental" });
4353
4385
  }
4354
4386
  return newData;
4355
4387
  }
@@ -4373,7 +4405,7 @@ var _SyncedDb = class _SyncedDb {
4373
4405
  };
4374
4406
  this.pendingChanges.schedule(collection, id, deleteUpdate, 0, "deleteOne");
4375
4407
  if (!((_a = this.collections.get(collection)) == null ? void 0 : _a.writeOnly)) {
4376
- this.inMemManager.writeBatch(collection, [{ _id: id }], "delete");
4408
+ this.inMemManager.writeBatch(collection, [{ _id: id }], "delete", { source: "incremental" });
4377
4409
  }
4378
4410
  return existing;
4379
4411
  }
@@ -4410,7 +4442,8 @@ var _SyncedDb = class _SyncedDb {
4410
4442
  this.inMemManager.writeBatch(
4411
4443
  collection,
4412
4444
  idsToDelete.map((id) => ({ _id: id })),
4413
- "delete"
4445
+ "delete",
4446
+ { source: "incremental" }
4414
4447
  );
4415
4448
  }
4416
4449
  return idsToDelete.length;
@@ -4431,7 +4464,7 @@ var _SyncedDb = class _SyncedDb {
4431
4464
  "hardDeleteOne"
4432
4465
  );
4433
4466
  await this.dexieDb.deleteOne(collection, id);
4434
- this.inMemManager.writeBatch(collection, [{ _id: id }], "delete");
4467
+ this.inMemManager.writeBatch(collection, [{ _id: id }], "delete", { source: "incremental" });
4435
4468
  return existing;
4436
4469
  }
4437
4470
  async hardDelete(collection, query) {
@@ -4468,7 +4501,7 @@ var _SyncedDb = class _SyncedDb {
4468
4501
  "hardDelete"
4469
4502
  );
4470
4503
  await this.dexieDb.deleteOne(collection, item.id);
4471
- this.inMemManager.writeBatch(collection, [{ _id: item.id }], "delete");
4504
+ this.inMemManager.writeBatch(collection, [{ _id: item.id }], "delete", { source: "incremental" });
4472
4505
  results.push(true);
4473
4506
  } catch (err) {
4474
4507
  console.error(`Failed to hard delete ${String(item.id)}:`, err);
@@ -4848,7 +4881,8 @@ var _SyncedDb = class _SyncedDb {
4848
4881
  this.inMemManager.writeBatch(
4849
4882
  collection,
4850
4883
  evictIds.map((id) => ({ _id: id })),
4851
- "delete"
4884
+ "delete",
4885
+ { source: "incremental" }
4852
4886
  );
4853
4887
  }
4854
4888
  this.crossTabSync.broadcastReload([collection]);
@@ -5101,7 +5135,8 @@ var _SyncedDb = class _SyncedDb {
5101
5135
  this.inMemManager.writeBatch(
5102
5136
  p.collection,
5103
5137
  uniqueEvictIds.map((id) => ({ _id: id })),
5104
- "delete"
5138
+ "delete",
5139
+ { source: "incremental" }
5105
5140
  );
5106
5141
  }
5107
5142
  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";
@@ -5,6 +5,10 @@ import type { DbEntity } from "../types/DbEntity";
5
5
  * Podpira polje-operatorje: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin,
6
6
  * $exists, $regex, $elemMatch, $size, $all — in logične operatorje
7
7
  * $and, $or, $nor na top-levelu.
8
+ *
9
+ * Skalarni operandi se ujemajo tudi z array-polji (MongoDB semantika):
10
+ * `{ a: "ok" }` matcha tako `{ a: "ok" }` kot `{ a: ["ok", "more"] }`.
11
+ * Velja za neposredno enakost, $eq, $ne, $in, $nin, $gt/$gte/$lt/$lte in $regex.
8
12
  */
9
13
  export declare function matchesQuery<T extends DbEntity>(item: T, query: QuerySpec<T>): boolean;
10
14
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cry-synced-db-client",
3
- "version": "0.1.148",
3
+ "version": "0.1.151",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",