cry-synced-db-client 2.0.1 → 2.0.4

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
@@ -1,5 +1,42 @@
1
1
  # Versions
2
2
 
3
+ ## 2.0.2 (2026-07-13)
4
+
5
+ ### Fix: `PendingChangesManager.schedule` — deleted fields resurrected from prior pending save
6
+
7
+ When two rapid `save()` calls arrived before the first Dexie debounce
8
+ flushed, `PendingChangesManager.schedule` merged them via
9
+ `Object.assign(existing.data, data)`. The second call's `data` was the
10
+ result of `applyObjDiff` — which **physically removes** keys set to
11
+ `undefined` (delete-by-path). `Object.assign` doesn't delete keys
12
+ absent from the source, so the first save's value silently survived in
13
+ the pending payload, resurrecting fields the caller had explicitly
14
+ cleared.
15
+
16
+ **Real-world trigger (klikvet):** `mounted()` calls
17
+ `save({ sedajObdeluje: "primoz" })` → `beforeRouteLeave` calls
18
+ `save({ sedajObdeluje: undefined })`. Both land in `PendingChanges`
19
+ before Dexie debounce fires. The merged row kept `sedajObdeluje: "primoz"`
20
+ in Dexie indefinitely — `razveljaviSamoVpogledSpremembe` never saw the
21
+ field cleared in the `objDiff` between `racunObVstopu` and
22
+ `this.racun`.
23
+
24
+ **Fix in `PendingChangesManager.schedule`:** replace shallow
25
+ `Object.assign(existing.data, data)` with key-aware merge — copy all
26
+ keys from `existing.data`, overwrite with keys in `data`, and
27
+ **delete** any key that exists in the existing row but is absent from
28
+ `data` (excluding `_id`, `_rev`, `_ts`).
29
+
30
+ **Fix in `SyncedDb.save()`:** diff against **in-mem** state (when
31
+ available) instead of stale Dexie. If PendingChanges holds writes that
32
+ haven't reached Dexie, deletions made against the live in-mem value
33
+ are invisible when diffing against stale Dexie (existing field is
34
+ missing from Dexie → no diff), and the field silently resurrects
35
+ from the in-mem base during `applyObjDiff`.
36
+
37
+ **New test:** `test/node-only/pendingChangesDeleteField.test.ts` —
38
+ 2 cases covering the two-rapid-save-delete scenario.
39
+
3
40
  ## 2.0.0 (2026-07-10)
4
41
 
5
42
  ### Dual-channel ebus support: `db/` + `dbbracketed/`
package/dist/index.js CHANGED
@@ -509,6 +509,9 @@ function computeObjDiff(existing, update) {
509
509
  continue;
510
510
  computeDiffInto(existing[key], update[key], key, diff);
511
511
  }
512
+ if (Object.prototype.hasOwnProperty.call(update, "_lastUpdaterId")) {
513
+ diff._lastUpdaterId = update._lastUpdaterId;
514
+ }
512
515
  return diff;
513
516
  }
514
517
  function tokenizePath(path) {
@@ -2723,7 +2726,17 @@ var PendingChangesManager = class {
2723
2726
  }
2724
2727
  const deltaWithId = existing ? data : __spreadProps(__spreadValues({}, data), { _id: id });
2725
2728
  savePendingWrite(this.tenant, collection, id, deltaWithId);
2726
- const fullData = existing ? Object.assign(existing.data, data, { _id: id }) : __spreadProps(__spreadValues({}, data), { _id: id });
2729
+ let fullData;
2730
+ if (existing) {
2731
+ fullData = __spreadProps(__spreadValues(__spreadValues({}, existing.data), data), { _id: id });
2732
+ for (const key2 of Object.keys(existing.data)) {
2733
+ if (key2 !== "_id" && !(key2 in data)) {
2734
+ delete fullData[key2];
2735
+ }
2736
+ }
2737
+ } else {
2738
+ fullData = __spreadProps(__spreadValues({}, data), { _id: id });
2739
+ }
2727
2740
  const timer = setTimeout(() => {
2728
2741
  this.executePendingChange(key);
2729
2742
  }, this.debounceDexieWritesMs);
@@ -4784,8 +4797,7 @@ var ServerUpdateHandler = class {
4784
4797
  return "ignore";
4785
4798
  }
4786
4799
  if (incomingRev === localRev + 1) {
4787
- const updaterId = incomingUpdaterId != null ? incomingUpdaterId : localItem._lastUpdaterId;
4788
- if (updaterId === this.updaterId) {
4800
+ if (incomingUpdaterId !== void 0 && incomingUpdaterId === this.updaterId) {
4789
4801
  return "bump-meta";
4790
4802
  }
4791
4803
  return "apply";
@@ -6515,26 +6527,22 @@ var _SyncedDb = class _SyncedDb {
6515
6527
  delete update._id;
6516
6528
  }
6517
6529
  update = _SyncedDb.stringifyObjectIds(update);
6530
+ const isWriteOnly = (_b = (_a = this.collections.get(collection)) == null ? void 0 : _a.writeOnly) != null ? _b : false;
6518
6531
  const existing = await this.dexieDb.getById(collection, id);
6519
- if (!existing && !((_a = this.collections.get(collection)) == null ? void 0 : _a.writeOnly)) {
6532
+ if (!existing && !isWriteOnly) {
6520
6533
  console.warn(
6521
6534
  `[SyncedDb] SyncedDb.save: Object ${String(id)} not found in ${collection}, creating new`
6522
6535
  );
6523
6536
  }
6524
6537
  const fullChanges = __spreadProps(__spreadValues({}, update), { _lastUpdaterId: this.updaterId });
6525
- const diff = computeObjDiff(existing, fullChanges);
6538
+ const currentMem = isWriteOnly ? null : this.inMemDb.getById(collection, id);
6539
+ const base = currentMem != null ? currentMem : existing;
6540
+ const diff = computeObjDiff(base, fullChanges);
6526
6541
  await this.dexieDb.addDirtyChange(collection, id, diff, {
6527
6542
  _ts: existing == null ? void 0 : existing._ts,
6528
6543
  _rev: existing == null ? void 0 : existing._rev
6529
6544
  });
6530
- const isWriteOnly = (_b = this.collections.get(collection)) == null ? void 0 : _b.writeOnly;
6531
- const currentMem = isWriteOnly ? null : this.inMemDb.getById(collection, id);
6532
- const merged = applyObjDiff(
6533
- currentMem != null ? currentMem : existing,
6534
- diff,
6535
- id,
6536
- collection
6537
- );
6545
+ const merged = applyObjDiff(base, diff, id, collection);
6538
6546
  this.pendingChanges.schedule(
6539
6547
  collection,
6540
6548
  id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cry-synced-db-client",
3
- "version": "2.0.1",
3
+ "version": "2.0.4",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -37,7 +37,7 @@
37
37
  "vitest": "^4.1.10"
38
38
  },
39
39
  "dependencies": {
40
- "cry-db": "^2.6.2",
40
+ "cry-db": "^2.6.3",
41
41
  "cry-helpers": "^2.1.207",
42
42
  "msgpackr": "^2.0.4",
43
43
  "superjson": "^2.2.6"