cry-synced-db-client 0.1.202 → 0.1.204

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
+ ## 0.1.204 (2026-06-17)
4
+
5
+ ### Per-collection error isolation in `uploadDirtyItems`
6
+
7
+ Celoten per-collection cikel (`getDirty`, `getByIds`, `saveMany`,
8
+ `incrementDirtyUploadAttempts`, ...) je zdaj zavit v `try/catch`. Če Dexie
9
+ operacija za eno kolekcijo vrže (npr. corrupted transaction, IndexedDB error),
10
+ odpade samo tista kolekcija — ostale se normalno pošljejo naprej.
11
+
12
+ **Before:** ena kolekcija pade → celoten upload crasne, vse ostale collection
13
+ dirty entry-ji ostanejo neposlani do naslednjega sync cycle-a.
14
+ **After:** napaka se logira (`console.error`), zanka gre na naslednjo kolekcijo.
15
+
16
+ 876 pass, 0 fail.
17
+
18
+ ## 0.1.203 (2026-06-15)
19
+
20
+ ### `onDirtyItemStuck` callback zdaj prejme tudi `itemsContent: DirtyChange[]`
21
+
22
+ `DirtyItemStuckInfo` je dobil novo polje `itemsContent` — polni `DirtyChange[]`
23
+ entry-ji (vključno s `changes` payload-om), ne samo `DirtyMeta[]` z metapodatki.
24
+
25
+ **Affected paths:**
26
+
27
+ - `callOnDirtyItemStuck` v `SyncEngine.ts`: async, fetcha polne entry-je prek
28
+ `dexieDb.getDirtyChangesBatch()` in jih posreduje kot `itemsContent`
29
+
30
+ ```typescript
31
+ onDirtyItemStuck: (info) => {
32
+ // info.items — DirtyMeta[] (isto kot prej)
33
+ // info.itemsContent — DirtyChange[] (polni entry-ji s `changes` payload-om)
34
+ console.log('Stuck changes:', info.itemsContent[0]!.changes);
35
+ }
36
+ ```
37
+
38
+ 899 pass, 0 fail.
39
+
3
40
  ## 0.1.201 (2026-06-14)
4
41
 
5
42
  ### `onBeforeDirtyClearAll` callback zdaj prejme `DirtyChange[]` (ne samo `DirtyMeta[]`)
@@ -9,11 +46,13 @@ callback dobi poleg metapodatkov (`id`, `collection`, `stuckSince`, ...) tudi
9
46
  `changes` payload z dejanskimi field-level spremembami.
10
47
 
11
48
  **Affected paths:**
49
+
12
50
  - `clearDirty()` clear-all path: fetcha `DirtyChange[]` prek `getDirtyChangesBatch`
13
51
  - `discardStuckItems()`: zamenja `getDirty` + `find` (ki je vračal
14
52
  `Partial<LocalDbEntity>[]`) z `getDirtyChangesBatch` za pravilne `DirtyChange[]`
15
53
 
16
54
  **Type change (backward-compatible):**
55
+
17
56
  - `items` v `BeforeDirtyClearAllInfo`: `DirtyMeta[]` → `DirtyChange[]`
18
57
  - `DirtyChange` je superset of `DirtyMeta`; vsa obstoječa polja (`id`,
19
58
  `collection`, `createdAt`, `updatedAt`, `stuckSince`, ...) so še vedno na voljo
@@ -33,11 +72,13 @@ bi postali stuck — zdaj se po 3 skipih (vsak sync da 2 incrementa = main + fol
33
72
  dobijo `stuckSince`.
34
73
 
35
74
  **Internal:**
75
+
36
76
  - V `uploadDirtyItems` zbira `preprocessSkippedIds` v obeh poteh, batch-write v
37
77
  `incrementDirtyUploadAttempts` takoj za `for` zanko (en DB write za vse skipane
38
78
  iteme namesto N write-ov)
39
79
 
40
80
  **Tests:** 5 novih testov v `test/preprocessDirtyItem.test.ts`:
81
+
41
82
  - `undefined/throw: numUploadAttempts increments across sync cycles, eventually stuck`
42
83
  - `undefined/throw: onDirtyItemStuck fires when item becomes stuck`
43
84
  - `mixed: stuck item via getStuckItems while non-stuck dirty still active`
@@ -51,17 +92,21 @@ dobijo `stuckSince`.
51
92
  Nov mehanizem za detekcijo dirty itemov, ki jih server vztrajno zavrača.
52
93
 
53
94
  **Fields na `DirtyChange` / `DirtyMeta`:**
95
+
54
96
  - `firstUploadAttempt?`, `lastUploadAttempt?`, `numUploadAttempts?`, `stuckSince?`
55
97
  - `stuckSince` se nastavi ko `numUploadAttempts > DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS` (2)
56
98
 
57
99
  **Nove metode na `SyncedDb`:**
100
+
58
101
  - `getStuckItems()` — vrne samo stuck dirty iteme po kolekcijah (`Record<collection, DirtyMeta[]>`)
59
102
  - `discardStuckItems(calledFrom?)` — zbriše vse stuck dirty iteme, sproži `onBeforeDirtyClearAll` z `reason: "discard-stuck"`
60
103
 
61
104
  **Nov callback:**
105
+
62
106
  - `onDirtyItemStuck(info: DirtyItemStuckInfo)` — sproži se ko item prvič postane stuck (po 3. neuspelem uploadu). Vsebuje `collection`, `items: DirtyMeta[]`, `calledFrom`, `timestamp`.
63
107
 
64
108
  **Internal:**
109
+
65
110
  - `I_DexieDb.incrementDirtyUploadAttempts(collection, ids)` — nova metoda, vrača `DirtyMeta[]` na novo stuck itemov
66
111
  - Vgrajena v `SyncEngine.uploadDirtyItems` in `uploadDirtyItemsForCollection` — per-collection result (errored ids) in catch block (network/timeout)
67
112
  - `DexieDb.getDirtyMeta` sedaj vključuje nova polja v izhod
@@ -84,6 +129,7 @@ Razlika od `refreshInBackground`: `refreshImmediately` je blokiren — počaka
84
129
  na odgovor serverja in vrne ažuriran podatek.
85
130
 
86
131
  Internal:
132
+
87
133
  - `_refreshImmediately()` — nova zasebna metoda: fetča s serverja, primerja
88
134
  `_rev`, posodobi Dexie samo če server novejši; za iteme z dirty spremembami
89
135
  delegira `processCollectionServerData`
@@ -108,7 +154,6 @@ in-mem. `referToServer` zdaj resnično awaita sveže podatke s serverja.
108
154
  Internal: `ensureItemsAreLoaded` — odstranjena Dexie pre-check (`getByIds`
109
155
  → `missingIds`).
110
156
 
111
-
112
157
  ## 0.1.194 (2026-06-09)
113
158
 
114
159
  ### Preload status `ready` fix
@@ -118,7 +163,6 @@ je `state === "hydrated"`, ne glede na `itemCount` ali `everDownloaded`.
118
163
  Prazen (še nikoli sinhroniziran) direktorij je še vedno ready — svežina
119
164
  podatkov je globalni koncept (`lastSuccessfulServerSync`).
120
165
 
121
-
122
166
  ## 0.1.193 (2026-05-25)
123
167
 
124
168
  Adds per-collection preload status reporting (non-breaking, additive).
@@ -256,6 +300,7 @@ batched download leaves collections registered; next auto-sync tick
256
300
  retries via the normal sync flow.
257
301
 
258
302
  Compared to looping `addCollectionToSync(spec)` per item:
303
+
259
304
  - N collections × per-collection RTT (sequential or `Promise.all`)
260
305
  → 1 RTT for the whole batch.
261
306
  - Hydration also parallelized.
@@ -647,6 +692,7 @@ paths are preserved. After the prune, `entry.baseTs` / `entry.baseRev`
647
692
  advance to the new floor.
648
693
 
649
694
  Implemented in:
695
+
650
696
  - `src/db/DexieDb.ts` — new module-level `rebaseDirtyOnServerAdvance`
651
697
  called from both `addDirtyChange` and `addDirtyChangesBatch` before
652
698
  `mergeDirtyChanges`.
@@ -716,6 +762,7 @@ drop silently. These fields are server-mirrored — local materialization
716
762
  with partial data could diverge from the canonical state.
717
763
 
718
764
  Tests:
765
+
719
766
  - `test/applyDiffLocallyObiskShape.test.ts` — 5 new cases covering
720
767
  multi-segment prefix shapes plus the `_`-prefix silent-drop contract.
721
768
  - `test/applyDiffLocallyMaterialize.test.ts` — flipped the
@@ -815,6 +862,7 @@ sentry/log dashboards.
815
862
 
816
863
  What's NOT routed through `networkError` (intentional — these are real
817
864
  bugs regardless of network state):
865
+
818
866
  - Caller bugs (falsy `_id`, missing `_id`, id mismatch, no id provided)
819
867
  - Dexie failures (`bulkPut failed`, `Failed to write to Dexie`, etc.)
820
868
  - Consumer-supplied callback throws (`onSyncEnd callback failed`, etc.)
@@ -994,6 +1042,7 @@ in the same scannable line as the tag while the full error object remains
994
1042
  attached for devtools inspection.
995
1043
 
996
1044
  Also fixed:
1045
+
997
1046
  - `SyncedDb.findById`: bare `console.error(err)` → tag line first, Error
998
1047
  object as second arg.
999
1048
  - `Ebus2ProxyNotifier`: WebSocket error event and server-error payload now
@@ -1215,6 +1264,7 @@ Behavior on subsequent calls (inspected against the existing config's
1215
1264
  spec; the new config (whatever shape) drives future syncs alone.
1216
1265
 
1217
1266
  Constraints:
1267
+
1218
1268
  - Dexie schema must already declare the table (Dexie does not support
1219
1269
  adding tables to an open database). The auto-register handles only the
1220
1270
  runtime SyncedDb-level config.
@@ -1528,6 +1578,7 @@ spread that would replace top-level array/object fields wholesale and
1528
1578
  drop nested fields the caller's `update` didn't mention.
1529
1579
 
1530
1580
  Replaced with `applyDiffLocally(base, diff, id)`:
1581
+
1531
1582
  1. Deep-clone `base` (currentMem ?? existing) via `safeDeepClone`
1532
1583
  (handles Date and `ObjectId`-like values; avoids `structuredClone`
1533
1584
  throwing on bson class instances)
@@ -1544,6 +1595,7 @@ Replaced with `applyDiffLocally(base, diff, id)`:
1544
1595
  Reverted automatic `_id` stamping for objects appearing as array elements.
1545
1596
  If an array of objects lacks `_id`, the caller's element shape is now
1546
1597
  preserved. This allows callers to mix:
1598
+
1547
1599
  - Whole-element bracket replace: `update.postavke = [{...}]`
1548
1600
  - Bracket-by-_id sub-field path: `update["postavke[<id>].field"] = value`
1549
1601
  in the same payload without the client mutating element identity.
@@ -1562,6 +1614,7 @@ in the same payload without the client mutating element identity.
1562
1614
  | Different `_id` set | mixed: `$pull` + `$push` + sub-field |
1563
1615
 
1564
1616
  For composition changes, `computeDiff` now emits:
1617
+
1565
1618
  - **Removed `_id`**: `arr[<id>] = undefined` (server: `$pull`)
1566
1619
  - **Added `_id`**: `arr[<id>] = [element]` (server: `$concatArrays + $filter`)
1567
1620
  - **Retained `_id`**: element-wise sub-field via `arr[<id>].field`
@@ -2069,6 +2122,7 @@ streams separately. **Non-breaking**: consumers that destructure
2069
2122
  `{ collection, loaded, total }` and ignore `phase` keep working unchanged.
2070
2123
 
2071
2124
  Type change in `I_SyncedDb.SyncedDbConfig` and internal `SyncEngineCallbacks`:
2125
+
2072
2126
  ```ts
2073
2127
  onSyncProgress?: (info: {
2074
2128
  phase: 'dexie' | 'server';
package/dist/index.js CHANGED
@@ -3195,7 +3195,6 @@ var SUPRESS_DB_WARNINGS = true;
3195
3195
  var _SyncEngine = class _SyncEngine {
3196
3196
  constructor(config) {
3197
3197
  this.tenant = config.tenant;
3198
- this.updaterId = config.updaterId;
3199
3198
  this.collections = config.collections;
3200
3199
  this.dexieDb = config.dexieDb;
3201
3200
  this.restInterface = config.restInterface;
@@ -3529,59 +3528,80 @@ var _SyncEngine = class _SyncEngine {
3529
3528
  async uploadDirtyItems(calledFrom) {
3530
3529
  var _a, _b;
3531
3530
  const collectionBatches = [];
3532
- for (const [collectionName] of this.collections) {
3533
- const dirtyChanges = await this.dexieDb.getDirty(collectionName);
3534
- if (dirtyChanges.length === 0) continue;
3535
- const dirtyChangesMap = /* @__PURE__ */ new Map();
3536
- for (const dirtyItem of dirtyChanges) {
3537
- dirtyChangesMap.set(String(dirtyItem._id), dirtyItem);
3538
- }
3539
- const updates = [];
3540
- const skipped = [];
3541
- const ids = dirtyChanges.map((dc) => dc._id);
3542
- const fullItems = await this.dexieDb.getByIds(
3543
- collectionName,
3544
- ids
3545
- );
3546
- const orphanReconstructed = [];
3547
- for (let i = 0; i < fullItems.length; i++) {
3548
- const fullItem = fullItems[i];
3549
- const id = ids[i];
3550
- if (fullItem) {
3551
- const delta = dirtyChangesMap.get(String(fullItem._id));
3552
- if (delta) {
3553
- const currentServerRev = typeof fullItem._rev === "number" ? fullItem._rev : void 0;
3554
- updates.push({ _id: fullItem._id, delta, currentServerRev });
3531
+ for (const [collectionName] of this.collections)
3532
+ try {
3533
+ const dirtyChanges = await this.dexieDb.getDirty(collectionName);
3534
+ if (dirtyChanges.length === 0) continue;
3535
+ const dirtyChangesMap = /* @__PURE__ */ new Map();
3536
+ for (const dirtyItem of dirtyChanges) {
3537
+ dirtyChangesMap.set(String(dirtyItem._id), dirtyItem);
3538
+ }
3539
+ const updates = [];
3540
+ const skipped = [];
3541
+ const ids = dirtyChanges.map((dc) => dc._id);
3542
+ const fullItems = await this.dexieDb.getByIds(
3543
+ collectionName,
3544
+ ids
3545
+ );
3546
+ const orphanReconstructed = [];
3547
+ for (let i = 0; i < fullItems.length; i++) {
3548
+ const fullItem = fullItems[i];
3549
+ const id = ids[i];
3550
+ if (fullItem) {
3551
+ const delta = dirtyChangesMap.get(String(fullItem._id));
3552
+ if (delta) {
3553
+ const currentServerRev = typeof fullItem._rev === "number" ? fullItem._rev : void 0;
3554
+ updates.push({ _id: fullItem._id, delta, currentServerRev });
3555
+ } else {
3556
+ skipped.push({
3557
+ _id: String(fullItem._id),
3558
+ reason: "no-delta-for-fullitem"
3559
+ });
3560
+ }
3561
+ } else if (id != null) {
3562
+ const delta = dirtyChangesMap.get(String(id));
3563
+ if (delta) {
3564
+ const reconstructed = __spreadProps(__spreadValues({}, delta), { _id: id });
3565
+ orphanReconstructed.push(reconstructed);
3566
+ updates.push({ _id: id, delta });
3567
+ } else {
3568
+ skipped.push({ _id: String(id), reason: "no-delta-for-orphan" });
3569
+ }
3555
3570
  } else {
3556
- skipped.push({
3557
- _id: String(fullItem._id),
3558
- reason: "no-delta-for-fullitem"
3559
- });
3571
+ skipped.push({ _id: "<null>", reason: "no-fullitem-no-id" });
3560
3572
  }
3561
- } else if (id != null) {
3562
- const delta = dirtyChangesMap.get(String(id));
3563
- if (delta) {
3564
- const reconstructed = __spreadProps(__spreadValues({}, delta), { _id: id });
3565
- orphanReconstructed.push(reconstructed);
3566
- updates.push({ _id: id, delta });
3567
- } else {
3568
- skipped.push({ _id: String(id), reason: "no-delta-for-orphan" });
3573
+ }
3574
+ if (orphanReconstructed.length > 0) {
3575
+ await this.dexieDb.saveMany(collectionName, orphanReconstructed);
3576
+ }
3577
+ if (updates.length === 0) {
3578
+ console.warn(
3579
+ `[SyncEngine] uploadDirtyItems: ${collectionName} has`,
3580
+ dirtyChanges.length,
3581
+ "dirty entries but 0 resolvable items",
3582
+ skipped
3583
+ );
3584
+ if (this.callbacks.onUploadSkip) {
3585
+ try {
3586
+ this.callbacks.onUploadSkip({
3587
+ collection: collectionName,
3588
+ reason: "no-resolvable-items",
3589
+ dirtyCount: dirtyChanges.length,
3590
+ skippedIds: skipped.slice(0, 20).map((s) => s._id),
3591
+ skipReasons: skipped.slice(0, 20),
3592
+ calledFrom,
3593
+ timestamp: /* @__PURE__ */ new Date()
3594
+ });
3595
+ } catch (err) {
3596
+ console.error(
3597
+ `[SyncEngine] onUploadSkip callback failed: ${err}`,
3598
+ err
3599
+ );
3600
+ }
3569
3601
  }
3570
- } else {
3571
- skipped.push({ _id: "<null>", reason: "no-fullitem-no-id" });
3602
+ continue;
3572
3603
  }
3573
- }
3574
- if (orphanReconstructed.length > 0) {
3575
- await this.dexieDb.saveMany(collectionName, orphanReconstructed);
3576
- }
3577
- if (updates.length === 0) {
3578
- console.warn(
3579
- `[SyncEngine] uploadDirtyItems: ${collectionName} has`,
3580
- dirtyChanges.length,
3581
- "dirty entries but 0 resolvable items",
3582
- skipped
3583
- );
3584
- if (this.callbacks.onUploadSkip) {
3604
+ if (skipped.length > 0 && this.callbacks.onUploadSkip) {
3585
3605
  try {
3586
3606
  this.callbacks.onUploadSkip({
3587
3607
  collection: collectionName,
@@ -3599,86 +3619,75 @@ var _SyncEngine = class _SyncEngine {
3599
3619
  );
3600
3620
  }
3601
3621
  }
3602
- continue;
3603
- }
3604
- if (skipped.length > 0 && this.callbacks.onUploadSkip) {
3605
- try {
3606
- this.callbacks.onUploadSkip({
3607
- collection: collectionName,
3608
- reason: "no-resolvable-items",
3609
- dirtyCount: dirtyChanges.length,
3610
- skippedIds: skipped.slice(0, 20).map((s) => s._id),
3611
- skipReasons: skipped.slice(0, 20),
3612
- calledFrom,
3613
- timestamp: /* @__PURE__ */ new Date()
3614
- });
3615
- } catch (err) {
3616
- console.error(
3617
- `[SyncEngine] onUploadSkip callback failed: ${err}`,
3618
- err
3622
+ const mappedUpdates = [];
3623
+ const preprocessSkippedIds = [];
3624
+ for (const item of updates) {
3625
+ const dirtyBaseRev = typeof item.delta._rev === "number" ? item.delta._rev : void 0;
3626
+ const stripped = stripServerManagedFromChanges(
3627
+ item.delta
3619
3628
  );
3620
- }
3621
- }
3622
- const mappedUpdates = [];
3623
- const preprocessSkippedIds = [];
3624
- for (const item of updates) {
3625
- const dirtyBaseRev = typeof item.delta._rev === "number" ? item.delta._rev : void 0;
3626
- const stripped = stripServerManagedFromChanges(
3627
- item.delta
3628
- );
3629
- const fixed = fixDotnetArrays(
3630
- stripped,
3631
- item.currentServerRev,
3632
- dirtyBaseRev
3633
- );
3634
- let candidate = {
3635
- _id: item._id,
3636
- update: fixed
3637
- };
3638
- if (this.preprocessDirtyItem) {
3639
- try {
3640
- const processed = this.preprocessDirtyItem(
3641
- candidate,
3642
- collectionName
3643
- );
3644
- if (processed === void 0) {
3629
+ const fixed = fixDotnetArrays(
3630
+ stripped,
3631
+ item.currentServerRev,
3632
+ dirtyBaseRev
3633
+ );
3634
+ let candidate = {
3635
+ _id: item._id,
3636
+ update: fixed
3637
+ };
3638
+ if (this.preprocessDirtyItem) {
3639
+ try {
3640
+ const processed = this.preprocessDirtyItem(
3641
+ candidate,
3642
+ collectionName
3643
+ );
3644
+ if (processed === void 0) {
3645
+ preprocessSkippedIds.push(item._id);
3646
+ continue;
3647
+ }
3648
+ candidate = processed;
3649
+ } catch (err) {
3650
+ console.error(
3651
+ `[SyncEngine] preprocessDirtyItem(${collectionName}) failed for _id=${String(item._id)}; keeping dirty for retry:`,
3652
+ err
3653
+ );
3645
3654
  preprocessSkippedIds.push(item._id);
3646
3655
  continue;
3647
3656
  }
3648
- candidate = processed;
3649
- } catch (err) {
3650
- console.error(
3651
- `[SyncEngine] preprocessDirtyItem(${collectionName}) failed for _id=${String(item._id)}; keeping dirty for retry:`,
3652
- err
3653
- );
3654
- preprocessSkippedIds.push(item._id);
3655
- continue;
3656
3657
  }
3658
+ mappedUpdates.push({
3659
+ _id: candidate._id,
3660
+ _rev: dirtyBaseRev != null ? dirtyBaseRev : 0,
3661
+ update: candidate.update
3662
+ });
3657
3663
  }
3658
- mappedUpdates.push({
3659
- _id: candidate._id,
3660
- _rev: dirtyBaseRev != null ? dirtyBaseRev : 0,
3661
- update: candidate.update
3662
- });
3663
- }
3664
- if (preprocessSkippedIds.length > 0) {
3665
- const newlyStuck = await this.dexieDb.incrementDirtyUploadAttempts(
3666
- collectionName,
3667
- preprocessSkippedIds
3664
+ if (preprocessSkippedIds.length > 0) {
3665
+ const newlyStuck = await this.dexieDb.incrementDirtyUploadAttempts(
3666
+ collectionName,
3667
+ preprocessSkippedIds
3668
+ );
3669
+ await this.callOnDirtyItemStuck(
3670
+ collectionName,
3671
+ newlyStuck,
3672
+ calledFrom
3673
+ );
3674
+ }
3675
+ if (mappedUpdates.length === 0) continue;
3676
+ collectionBatches.push([
3677
+ {
3678
+ collection: collectionName,
3679
+ batch: {
3680
+ updates: mappedUpdates,
3681
+ deletes: []
3682
+ }
3683
+ }
3684
+ ]);
3685
+ } catch (err) {
3686
+ console.error(
3687
+ `[SyncEngine] uploadDirtyItems: failed for collection "${collectionName}":`,
3688
+ err
3668
3689
  );
3669
- this.callOnDirtyItemStuck(collectionName, newlyStuck, calledFrom);
3670
3690
  }
3671
- if (mappedUpdates.length === 0) continue;
3672
- collectionBatches.push([
3673
- {
3674
- collection: collectionName,
3675
- batch: {
3676
- updates: mappedUpdates,
3677
- deletes: []
3678
- }
3679
- }
3680
- ]);
3681
- }
3682
3691
  if (collectionBatches.length === 0) {
3683
3692
  return { sentCount: 0 };
3684
3693
  }
@@ -3719,7 +3728,7 @@ var _SyncEngine = class _SyncEngine {
3719
3728
  b.collection,
3720
3729
  allIds
3721
3730
  );
3722
- this.callOnDirtyItemStuck(b.collection, newlyStuck, calledFrom);
3731
+ await this.callOnDirtyItemStuck(b.collection, newlyStuck, calledFrom);
3723
3732
  }
3724
3733
  }
3725
3734
  throw err;
@@ -3798,7 +3807,7 @@ var _SyncEngine = class _SyncEngine {
3798
3807
  collection,
3799
3808
  retainedIds
3800
3809
  );
3801
- this.callOnDirtyItemStuck(collection, newlyStuck, calledFrom);
3810
+ await this.callOnDirtyItemStuck(collection, newlyStuck, calledFrom);
3802
3811
  }
3803
3812
  let collectionSentCount = 0;
3804
3813
  const isWriteOnly = (_b = this.collections.get(collection)) == null ? void 0 : _b.writeOnly;
@@ -4343,12 +4352,18 @@ var _SyncEngine = class _SyncEngine {
4343
4352
  }
4344
4353
  }
4345
4354
  }
4346
- callOnDirtyItemStuck(collection, stuckMetas, calledFrom) {
4355
+ async callOnDirtyItemStuck(collection, stuckMetas, calledFrom) {
4347
4356
  if (!this.callbacks.onDirtyItemStuck || stuckMetas.length === 0) return;
4348
4357
  try {
4358
+ const ids = stuckMetas.map(
4359
+ (m) => m.id
4360
+ );
4361
+ const fullMap = await this.dexieDb.getDirtyChangesBatch(collection, ids);
4362
+ const itemsContent = stuckMetas.map((m) => fullMap.get(String(m.id))).filter(Boolean);
4349
4363
  this.callbacks.onDirtyItemStuck({
4350
4364
  collection,
4351
4365
  items: stuckMetas,
4366
+ itemsContent,
4352
4367
  calledFrom,
4353
4368
  timestamp: /* @__PURE__ */ new Date()
4354
4369
  });
@@ -12,7 +12,6 @@ import type { I_SyncEngine, SyncEngineConfig, SyncExtras } from "../types/manage
12
12
  import type { UploadResult } from "../types/internal";
13
13
  export declare class SyncEngine implements I_SyncEngine {
14
14
  private readonly tenant;
15
- private readonly updaterId;
16
15
  private readonly collections;
17
16
  private readonly dexieDb;
18
17
  private readonly restInterface;
@@ -1,7 +1,7 @@
1
1
  import type { AggregateOptions } from "mongodb";
2
2
  import type { Id, DbEntity, LocalDbEntity } from "./DbEntity";
3
3
  import type { QuerySpec, QueryOpts, UpdateSpec, InsertSpec, BatchSpec, I_RestInterface, CollectionUpdateRequest, CollectionUpdateResult, GetNewerSpec } from "./I_RestInterface";
4
- import type { DirtyMeta, I_DexieDb } from "./I_DexieDb";
4
+ import type { DirtyMeta, DirtyChange, I_DexieDb } from "./I_DexieDb";
5
5
  import type { I_InMemDb } from "./I_InMemDb";
6
6
  import type { I_ServerUpdateNotifier } from "./I_ServerUpdateNotifier";
7
7
  import type { WakeSyncInfo, NetworkStatusChangeInfo } from "../db/types/managers";
@@ -101,6 +101,11 @@ export interface DirtyItemStuckInfo {
101
101
  collection: string;
102
102
  /** Meta podatki stuck itemov (vsebujejo `stuckSince`, `numUploadAttempts`, itd.) */
103
103
  items: import("./I_DexieDb").DirtyMeta[];
104
+ /**
105
+ * Polni DirtyChange entry-ji (vključno s `changes` payload-om) za stuck iteme.
106
+ * Vsebuje dejanske spremenjene field-e, ne samo metapodatke.
107
+ */
108
+ itemsContent: DirtyChange[];
104
109
  /** Optional caller tag */
105
110
  calledFrom?: string;
106
111
  /** Timestamp when stuck was detected */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cry-synced-db-client",
3
- "version": "0.1.202",
3
+ "version": "0.1.204",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",