cry-synced-db-client 0.1.132 → 0.1.135

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,13 @@
1
1
  # Versions
2
2
 
3
+ ## 0.1.135 (2026-04-20)
4
+
5
+ - Conflict resolution: server wins on primitive field conflicts when `external._rev > local._rev`
6
+ - Previously local always won for overlapping primitive fields during conflict merge
7
+ - New rule applies recursively to object elements inside arrays (via `mergeObjectArrays` → `mergeObjects`)
8
+ - Arrays still unionize; nested objects without `_rev` keep local-wins behavior
9
+ - Affects dirty-conflict resolution path ([SyncEngine.resolveCollectionConflict](src/db/sync/SyncEngine.ts))
10
+
3
11
  ## 0.1.126 (2026-04-08)
4
12
 
5
13
  - Eviction: remove out-of-scope records from Dexie and in-mem cache
package/dist/index.js CHANGED
@@ -2319,6 +2319,7 @@ function resolveConflict(local, external) {
2319
2319
  }
2320
2320
  function mergeObjects(local, external) {
2321
2321
  const result = __spreadValues({}, local);
2322
+ const serverWinsOnConflict = typeof local._rev === "number" && typeof external._rev === "number" && external._rev > local._rev;
2322
2323
  for (const key of Object.keys(external)) {
2323
2324
  if (key === "_id" || key === "_dirty") {
2324
2325
  continue;
@@ -2336,6 +2337,8 @@ function mergeObjects(local, external) {
2336
2337
  result[key] = mergeArrays(localValue, externalValue);
2337
2338
  } else if (isPlainObject3(localValue) && isPlainObject3(externalValue)) {
2338
2339
  result[key] = mergeObjects(localValue, externalValue);
2340
+ } else if (serverWinsOnConflict) {
2341
+ result[key] = externalValue;
2339
2342
  }
2340
2343
  }
2341
2344
  return result;
@@ -2567,20 +2570,15 @@ var _SyncEngine = class _SyncEngine {
2567
2570
  dirtyChangesMap.set(String(dirtyItem._id), dirtyItem);
2568
2571
  }
2569
2572
  const updates = [];
2570
- const deletes = [];
2571
2573
  const ids = dirtyChanges.map((dc) => dc._id);
2572
2574
  const fullItems = await this.dexieDb.getByIds(collectionName, ids);
2573
2575
  for (let i = 0; i < fullItems.length; i++) {
2574
2576
  const fullItem = fullItems[i];
2575
2577
  const id = ids[i];
2576
2578
  if (fullItem) {
2577
- if (fullItem._deleted) {
2578
- deletes.push(fullItem);
2579
- } else {
2580
- const delta = dirtyChangesMap.get(String(fullItem._id));
2581
- if (delta) {
2582
- updates.push({ _id: fullItem._id, delta });
2583
- }
2579
+ const delta = dirtyChangesMap.get(String(fullItem._id));
2580
+ if (delta) {
2581
+ updates.push({ _id: fullItem._id, delta });
2584
2582
  }
2585
2583
  } else if (id != null) {
2586
2584
  const delta = dirtyChangesMap.get(String(id));
@@ -2591,9 +2589,9 @@ var _SyncEngine = class _SyncEngine {
2591
2589
  }
2592
2590
  }
2593
2591
  }
2594
- if (updates.length === 0 && deletes.length === 0) {
2592
+ if (updates.length === 0) {
2595
2593
  console.warn(
2596
- `uploadDirtyItems: ${collectionName} has ${dirtyChanges.length} dirty entries but 0 updates/deletes`
2594
+ `uploadDirtyItems: ${collectionName} has ${dirtyChanges.length} dirty entries but 0 resolvable items`
2597
2595
  );
2598
2596
  continue;
2599
2597
  }
@@ -2607,7 +2605,7 @@ var _SyncEngine = class _SyncEngine {
2607
2605
  update: changes
2608
2606
  };
2609
2607
  }),
2610
- deletes: deletes.map((item) => ({ _id: item._id }))
2608
+ deletes: []
2611
2609
  }
2612
2610
  }]);
2613
2611
  }
@@ -2650,14 +2648,19 @@ var _SyncEngine = class _SyncEngine {
2650
2648
  const dexieItems = await this.dexieDb.getByIds(collection, idsToCheck);
2651
2649
  const dexieSaveBatch = [];
2652
2650
  const inMemUpdateBatch = [];
2651
+ const inMemDeleteIds = [];
2652
+ const dexieDeleteIds = [];
2653
2653
  for (let i = 0; i < insertedAndUpdated.length; i++) {
2654
2654
  const entity = insertedAndUpdated[i];
2655
2655
  const dexieItem = dexieItems[i];
2656
2656
  if (dexieItem) {
2657
2657
  dexieItem._rev = entity._rev;
2658
2658
  dexieItem._ts = entity._ts;
2659
- dexieSaveBatch.push(dexieItem);
2660
- if (!dexieItem._deleted) {
2659
+ if (dexieItem._deleted) {
2660
+ inMemDeleteIds.push(entity._id);
2661
+ dexieDeleteIds.push(entity._id);
2662
+ } else {
2663
+ dexieSaveBatch.push(dexieItem);
2661
2664
  const inMemItem = this.deps.getInMemById(collection, entity._id);
2662
2665
  if (inMemItem) {
2663
2666
  inMemUpdateBatch.push(__spreadProps(__spreadValues({}, inMemItem), {
@@ -2671,9 +2674,19 @@ var _SyncEngine = class _SyncEngine {
2671
2674
  if (dexieSaveBatch.length > 0) {
2672
2675
  await this.dexieDb.saveMany(collection, dexieSaveBatch);
2673
2676
  }
2677
+ if (dexieDeleteIds.length > 0) {
2678
+ await this.dexieDb.deleteMany(collection, dexieDeleteIds);
2679
+ }
2674
2680
  if (inMemUpdateBatch.length > 0) {
2675
2681
  this.deps.writeToInMemBatch(collection, inMemUpdateBatch, "upsert");
2676
2682
  }
2683
+ if (inMemDeleteIds.length > 0) {
2684
+ this.deps.writeToInMemBatch(
2685
+ collection,
2686
+ inMemDeleteIds.map((id) => ({ _id: id })),
2687
+ "delete"
2688
+ );
2689
+ }
2677
2690
  }
2678
2691
  sentCount += insertedAndUpdated.length;
2679
2692
  collectionSentCount += insertedAndUpdated.length;
@@ -2756,19 +2769,14 @@ var _SyncEngine = class _SyncEngine {
2756
2769
  dirtyChangesMap.set(String(dirtyItem._id), dirtyItem);
2757
2770
  }
2758
2771
  const updates = [];
2759
- const deletes = [];
2760
2772
  for (const fullItem of fullItems) {
2761
2773
  if (!fullItem) continue;
2762
- if (fullItem._deleted) {
2763
- deletes.push(fullItem);
2764
- } else {
2765
- const delta = dirtyChangesMap.get(String(fullItem._id));
2766
- if (delta) {
2767
- updates.push({ _id: fullItem._id, delta });
2768
- }
2774
+ const delta = dirtyChangesMap.get(String(fullItem._id));
2775
+ if (delta) {
2776
+ updates.push({ _id: fullItem._id, delta });
2769
2777
  }
2770
2778
  }
2771
- if (updates.length === 0 && deletes.length === 0) {
2779
+ if (updates.length === 0) {
2772
2780
  return { sentCount: 0 };
2773
2781
  }
2774
2782
  const collectionBatches = [[{
@@ -2778,7 +2786,7 @@ var _SyncEngine = class _SyncEngine {
2778
2786
  const _a = item.delta, { _ts, _rev } = _a, changes = __objRest(_a, ["_ts", "_rev"]);
2779
2787
  return { _id: item._id, update: changes };
2780
2788
  }),
2781
- deletes: deletes.map((item) => ({ _id: item._id }))
2789
+ deletes: []
2782
2790
  }
2783
2791
  }]];
2784
2792
  const results = await this.deps.withSyncTimeout(
@@ -5,13 +5,15 @@ import type { DbEntity } from "../types/DbEntity";
5
5
  * 1. Če ima lokalni objekt _rev in ima objekt s serverja nižji ali isti _rev, ignoriramo server
6
6
  * 2. Polja, ki niso array in niso object:
7
7
  * - če lokalna verzija nima polja, zunanja pa ga ima, polje dodamo
8
- * - če imata oba objekta polje, vrednost s serverja zavržemo
8
+ * - če imata oba objekta polje:
9
+ * - če external._rev > local._rev (oba definirana), zmaga server
10
+ * - sicer ohranimo lokalno vrednost
9
11
  * 3. Polja tipa array:
10
12
  * - če lokalni objekt nima polja, ga dodamo
11
13
  * - če je tip podatkov string[], shranimo deduplicirano unijo
12
14
  * - če je tip podatkov Object[]:
13
15
  * - elemente brez _id dodamo v lokalni array
14
- * - objekte z _id primerjamo rekurzivno
16
+ * - objekte z _id primerjamo rekurzivno (rekurzija uporabi isto _rev pravilo)
15
17
  * 4. Če je tip podatkov Record<string, Object>:
16
18
  * - za vsak key, ki ga ni v lokalnem objektu, dodamo vrednost
17
19
  * - za vsak key, ki obstaja, primerjamo rekurzivno
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cry-synced-db-client",
3
- "version": "0.1.132",
3
+ "version": "0.1.135",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",