cry-synced-db-client 0.1.197 → 0.1.200

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,53 @@
1
1
  # Versions
2
2
 
3
+ ## 0.1.200 (2026-06-14)
4
+
5
+ ### `preprocessDirtyItem` skip/throw zdaj incrementa `numUploadAttempts`
6
+
7
+ `SyncEngine.uploadDirtyItems`: ko `preprocessDirtyItem` vrne `undefined` ali vrže
8
+ exception, se item ne samo preskoči — zdaj dobi tudi `incrementDirtyUploadAttempts`,
9
+ tako da po 2+ skipih postane stuck in viden prek `getStuckItems()` / `onDirtyItemStuck`.
10
+
11
+ Prej so skipani itemi tiho ostali v `_dirty_changes` brez metapodatkov in nikoli ne
12
+ bi postali stuck — zdaj se po 3 skipih (vsak sync da 2 incrementa = main + followUp)
13
+ dobijo `stuckSince`.
14
+
15
+ **Internal:**
16
+ - V `uploadDirtyItems` zbira `preprocessSkippedIds` v obeh poteh, batch-write v
17
+ `incrementDirtyUploadAttempts` takoj za `for` zanko (en DB write za vse skipane
18
+ iteme namesto N write-ov)
19
+
20
+ **Tests:** 5 novih testov v `test/preprocessDirtyItem.test.ts`:
21
+ - `undefined/throw: numUploadAttempts increments across sync cycles, eventually stuck`
22
+ - `undefined/throw: onDirtyItemStuck fires when item becomes stuck`
23
+ - `mixed: stuck item via getStuckItems while non-stuck dirty still active`
24
+
25
+ 876 pass, 0 fail.
26
+
27
+ ## 0.1.198 (2026-06-13)
28
+
29
+ ### Stuck-item tracking (`getStuckItems` / `discardStuckItems` / `onDirtyItemStuck`)
30
+
31
+ Nov mehanizem za detekcijo dirty itemov, ki jih server vztrajno zavrača.
32
+
33
+ **Fields na `DirtyChange` / `DirtyMeta`:**
34
+ - `firstUploadAttempt?`, `lastUploadAttempt?`, `numUploadAttempts?`, `stuckSince?`
35
+ - `stuckSince` se nastavi ko `numUploadAttempts > DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS` (2)
36
+
37
+ **Nove metode na `SyncedDb`:**
38
+ - `getStuckItems()` — vrne samo stuck dirty iteme po kolekcijah (`Record<collection, DirtyMeta[]>`)
39
+ - `discardStuckItems(calledFrom?)` — zbriše vse stuck dirty iteme, sproži `onBeforeDirtyClearAll` z `reason: "discard-stuck"`
40
+
41
+ **Nov callback:**
42
+ - `onDirtyItemStuck(info: DirtyItemStuckInfo)` — sproži se ko item prvič postane stuck (po 3. neuspelem uploadu). Vsebuje `collection`, `items: DirtyMeta[]`, `calledFrom`, `timestamp`.
43
+
44
+ **Internal:**
45
+ - `I_DexieDb.incrementDirtyUploadAttempts(collection, ids)` — nova metoda, vrača `DirtyMeta[]` na novo stuck itemov
46
+ - Vgrajena v `SyncEngine.uploadDirtyItems` in `uploadDirtyItemsForCollection` — per-collection result (errored ids) in catch block (network/timeout)
47
+ - `DexieDb.getDirtyMeta` sedaj vključuje nova polja v izhod
48
+
49
+ **Tests:** 17 testov v `test/stuckItems.test.ts` — unit testi za increment, integracijski za get/discard, e2e za callback skozi upload pipeline.
50
+
3
51
  ## 0.1.196 (2026-06-10)
4
52
 
5
53
  ### `refreshImmediately` — blokirni server-fetch z verzijsko primerjavo
package/dist/index.js CHANGED
@@ -3245,7 +3245,10 @@ var _SyncEngine = class _SyncEngine {
3245
3245
  configMap.set(collectionName, config);
3246
3246
  }
3247
3247
  this.callOnFindNewerManyCall(syncSpecs, calledFrom);
3248
- this.callbackSafe(this.callbacks.onServerSyncStart, { calledFrom, collectionCount: syncSpecs.length });
3248
+ this.callbackSafe(this.callbacks.onServerSyncStart, {
3249
+ calledFrom,
3250
+ collectionCount: syncSpecs.length
3251
+ });
3249
3252
  const findNewerManyStartTime = Date.now();
3250
3253
  const collectionState = /* @__PURE__ */ new Map();
3251
3254
  for (const [name] of configMap) {
@@ -3276,7 +3279,12 @@ var _SyncEngine = class _SyncEngine {
3276
3279
  if (!config) return;
3277
3280
  const state = collectionState.get(collection);
3278
3281
  state.receivedCount += items.length;
3279
- const stats = await this.processIncomingServerData(collection, config, items, state.source);
3282
+ const stats = await this.processIncomingServerData(
3283
+ collection,
3284
+ config,
3285
+ items,
3286
+ state.source
3287
+ );
3280
3288
  state.conflicts += stats.conflictsResolved;
3281
3289
  if (stats.maxTs) {
3282
3290
  if (!state.maxTs || this.compareTimestamps(stats.maxTs, state.maxTs) > 0) {
@@ -3358,12 +3366,16 @@ var _SyncEngine = class _SyncEngine {
3358
3366
  try {
3359
3367
  uploadStats = await this.uploadDirtyItems(calledFrom);
3360
3368
  await this.deps.flushAllPendingChanges();
3361
- const followUp = await this.uploadDirtyItems(`${calledFrom != null ? calledFrom : "sync"}:followUp`);
3369
+ const followUp = await this.uploadDirtyItems(
3370
+ `${calledFrom != null ? calledFrom : "sync"}:followUp`
3371
+ );
3362
3372
  if (followUp.sentCount > 0) {
3363
3373
  uploadStats.sentCount += followUp.sentCount;
3364
3374
  if (followUp.collectionSentCounts) {
3365
3375
  uploadStats.collectionSentCounts = uploadStats.collectionSentCounts || {};
3366
- for (const [c, n] of Object.entries(followUp.collectionSentCounts)) {
3376
+ for (const [c, n] of Object.entries(
3377
+ followUp.collectionSentCounts
3378
+ )) {
3367
3379
  uploadStats.collectionSentCounts[c] = (uploadStats.collectionSentCounts[c] || 0) + n;
3368
3380
  }
3369
3381
  }
@@ -3375,7 +3387,9 @@ var _SyncEngine = class _SyncEngine {
3375
3387
  );
3376
3388
  }
3377
3389
  sentCount = uploadStats.sentCount;
3378
- for (const [collectionName, stats] of Object.entries(uploadStats.collectionSentCounts || {})) {
3390
+ for (const [collectionName, stats] of Object.entries(
3391
+ uploadStats.collectionSentCounts || {}
3392
+ )) {
3379
3393
  if (collectionStats[collectionName]) {
3380
3394
  collectionStats[collectionName].sentCount = stats;
3381
3395
  } else {
@@ -3473,15 +3487,20 @@ var _SyncEngine = class _SyncEngine {
3473
3487
  });
3474
3488
  }
3475
3489
  }
3476
- if (dexieSave.length > 0) await this.dexieDb.saveMany(collection, dexieSave);
3490
+ if (dexieSave.length > 0)
3491
+ await this.dexieDb.saveMany(collection, dexieSave);
3477
3492
  if (dexieDeleteIds.length > 0) {
3478
3493
  await this.dexieDb.deleteMany(collection, dexieDeleteIds);
3479
3494
  }
3480
3495
  if (memUpsert.length > 0) {
3481
- this.deps.writeToInMemBatch(collection, memUpsert, "upsert", { source: "incremental" });
3496
+ this.deps.writeToInMemBatch(collection, memUpsert, "upsert", {
3497
+ source: "incremental"
3498
+ });
3482
3499
  }
3483
3500
  if (memDelete.length > 0) {
3484
- this.deps.writeToInMemBatch(collection, memDelete, "delete", { source: "incremental" });
3501
+ this.deps.writeToInMemBatch(collection, memDelete, "delete", {
3502
+ source: "incremental"
3503
+ });
3485
3504
  }
3486
3505
  if (reAddDirty.length > 0) {
3487
3506
  await this.dexieDb.addDirtyChangesBatch(collection, reAddDirty);
@@ -3508,7 +3527,7 @@ var _SyncEngine = class _SyncEngine {
3508
3527
  * Upload dirty items for all collections.
3509
3528
  */
3510
3529
  async uploadDirtyItems(calledFrom) {
3511
- var _a;
3530
+ var _a, _b;
3512
3531
  const collectionBatches = [];
3513
3532
  for (const [collectionName] of this.collections) {
3514
3533
  const dirtyChanges = await this.dexieDb.getDirty(collectionName);
@@ -3520,7 +3539,10 @@ var _SyncEngine = class _SyncEngine {
3520
3539
  const updates = [];
3521
3540
  const skipped = [];
3522
3541
  const ids = dirtyChanges.map((dc) => dc._id);
3523
- const fullItems = await this.dexieDb.getByIds(collectionName, ids);
3542
+ const fullItems = await this.dexieDb.getByIds(
3543
+ collectionName,
3544
+ ids
3545
+ );
3524
3546
  const orphanReconstructed = [];
3525
3547
  for (let i = 0; i < fullItems.length; i++) {
3526
3548
  const fullItem = fullItems[i];
@@ -3531,7 +3553,10 @@ var _SyncEngine = class _SyncEngine {
3531
3553
  const currentServerRev = typeof fullItem._rev === "number" ? fullItem._rev : void 0;
3532
3554
  updates.push({ _id: fullItem._id, delta, currentServerRev });
3533
3555
  } else {
3534
- skipped.push({ _id: String(fullItem._id), reason: "no-delta-for-fullitem" });
3556
+ skipped.push({
3557
+ _id: String(fullItem._id),
3558
+ reason: "no-delta-for-fullitem"
3559
+ });
3535
3560
  }
3536
3561
  } else if (id != null) {
3537
3562
  const delta = dirtyChangesMap.get(String(id));
@@ -3568,7 +3593,10 @@ var _SyncEngine = class _SyncEngine {
3568
3593
  timestamp: /* @__PURE__ */ new Date()
3569
3594
  });
3570
3595
  } catch (err) {
3571
- console.error(`[SyncEngine] onUploadSkip callback failed: ${err}`, err);
3596
+ console.error(
3597
+ `[SyncEngine] onUploadSkip callback failed: ${err}`,
3598
+ err
3599
+ );
3572
3600
  }
3573
3601
  }
3574
3602
  continue;
@@ -3585,10 +3613,14 @@ var _SyncEngine = class _SyncEngine {
3585
3613
  timestamp: /* @__PURE__ */ new Date()
3586
3614
  });
3587
3615
  } catch (err) {
3588
- console.error(`[SyncEngine] onUploadSkip callback failed: ${err}`, err);
3616
+ console.error(
3617
+ `[SyncEngine] onUploadSkip callback failed: ${err}`,
3618
+ err
3619
+ );
3589
3620
  }
3590
3621
  }
3591
3622
  const mappedUpdates = [];
3623
+ const preprocessSkippedIds = [];
3592
3624
  for (const item of updates) {
3593
3625
  const dirtyBaseRev = typeof item.delta._rev === "number" ? item.delta._rev : void 0;
3594
3626
  const stripped = stripServerManagedFromChanges(
@@ -3605,8 +3637,12 @@ var _SyncEngine = class _SyncEngine {
3605
3637
  };
3606
3638
  if (this.preprocessDirtyItem) {
3607
3639
  try {
3608
- const processed = this.preprocessDirtyItem(candidate, collectionName);
3640
+ const processed = this.preprocessDirtyItem(
3641
+ candidate,
3642
+ collectionName
3643
+ );
3609
3644
  if (processed === void 0) {
3645
+ preprocessSkippedIds.push(item._id);
3610
3646
  continue;
3611
3647
  }
3612
3648
  candidate = processed;
@@ -3615,6 +3651,7 @@ var _SyncEngine = class _SyncEngine {
3615
3651
  `[SyncEngine] preprocessDirtyItem(${collectionName}) failed for _id=${String(item._id)}; keeping dirty for retry:`,
3616
3652
  err
3617
3653
  );
3654
+ preprocessSkippedIds.push(item._id);
3618
3655
  continue;
3619
3656
  }
3620
3657
  }
@@ -3624,14 +3661,23 @@ var _SyncEngine = class _SyncEngine {
3624
3661
  update: candidate.update
3625
3662
  });
3626
3663
  }
3664
+ if (preprocessSkippedIds.length > 0) {
3665
+ const newlyStuck = await this.dexieDb.incrementDirtyUploadAttempts(
3666
+ collectionName,
3667
+ preprocessSkippedIds
3668
+ );
3669
+ this.callOnDirtyItemStuck(collectionName, newlyStuck, calledFrom);
3670
+ }
3627
3671
  if (mappedUpdates.length === 0) continue;
3628
- collectionBatches.push([{
3629
- collection: collectionName,
3630
- batch: {
3631
- updates: mappedUpdates,
3632
- deletes: []
3672
+ collectionBatches.push([
3673
+ {
3674
+ collection: collectionName,
3675
+ batch: {
3676
+ updates: mappedUpdates,
3677
+ deletes: []
3678
+ }
3633
3679
  }
3634
- }]);
3680
+ ]);
3635
3681
  }
3636
3682
  if (collectionBatches.length === 0) {
3637
3683
  return { sentCount: 0 };
@@ -3664,6 +3710,18 @@ var _SyncEngine = class _SyncEngine {
3664
3710
  writeStartedAt,
3665
3711
  calledFrom
3666
3712
  );
3713
+ for (const batch of collectionBatches) {
3714
+ for (const b of batch) {
3715
+ const allIds = [];
3716
+ for (const u of b.batch.updates) allIds.push(u._id);
3717
+ for (const d of b.batch.deletes) allIds.push((_a = d._id) != null ? _a : d);
3718
+ const newlyStuck = await this.dexieDb.incrementDirtyUploadAttempts(
3719
+ b.collection,
3720
+ allIds
3721
+ );
3722
+ this.callOnDirtyItemStuck(b.collection, newlyStuck, calledFrom);
3723
+ }
3724
+ }
3667
3725
  throw err;
3668
3726
  }
3669
3727
  let sentCount = 0;
@@ -3712,13 +3770,38 @@ var _SyncEngine = class _SyncEngine {
3712
3770
  `[SyncEngine] Sync upload [${collection}]: ${ambiguous.length} id(s) appeared in BOTH inserted/updated/deleted AND errors[] \u2014 keeping dirty for safety. _ids: ${ambiguous.join(", ")}`
3713
3771
  );
3714
3772
  }
3715
- const dirtyBeforeRefresh = mustRefreshIds.size > 0 ? await this.dexieDb.getDirtyChangesBatch(collection, [...mustRefreshIds]) : /* @__PURE__ */ new Map();
3773
+ const dirtyBeforeRefresh = mustRefreshIds.size > 0 ? await this.dexieDb.getDirtyChangesBatch(collection, [
3774
+ ...mustRefreshIds
3775
+ ]) : /* @__PURE__ */ new Map();
3716
3776
  const uploadedByIdRefresh = mustRefreshIds.size > 0 ? this.uploadedSnapshotFor(collectionBatches, collection) : /* @__PURE__ */ new Map();
3717
3777
  if (allSuccessIds.length > 0) {
3718
3778
  await this.dexieDb.clearDirtyChangesBatch(collection, allSuccessIds);
3719
3779
  }
3780
+ const sentIdsForCollection = /* @__PURE__ */ new Set();
3781
+ for (const batch of collectionBatches) {
3782
+ for (const b of batch) {
3783
+ if (b.collection !== collection) continue;
3784
+ for (const u of b.batch.updates)
3785
+ sentIdsForCollection.add(String(u._id));
3786
+ for (const d of b.batch.deletes)
3787
+ sentIdsForCollection.add(String(d._id));
3788
+ }
3789
+ }
3790
+ const retainedIds = [];
3791
+ for (const sid of sentIdsForCollection) {
3792
+ if (!allSuccessIds.find((aid) => String(aid) === sid)) {
3793
+ retainedIds.push(sid);
3794
+ }
3795
+ }
3796
+ if (retainedIds.length > 0) {
3797
+ const newlyStuck = await this.dexieDb.incrementDirtyUploadAttempts(
3798
+ collection,
3799
+ retainedIds
3800
+ );
3801
+ this.callOnDirtyItemStuck(collection, newlyStuck, calledFrom);
3802
+ }
3720
3803
  let collectionSentCount = 0;
3721
- const isWriteOnly = (_a = this.collections.get(collection)) == null ? void 0 : _a.writeOnly;
3804
+ const isWriteOnly = (_b = this.collections.get(collection)) == null ? void 0 : _b.writeOnly;
3722
3805
  const insertedAndUpdated = inserted.concat(updated);
3723
3806
  if (insertedAndUpdated.length > 0) {
3724
3807
  const idsToCheck = [];
@@ -3726,7 +3809,10 @@ var _SyncEngine = class _SyncEngine {
3726
3809
  if (isWriteOnly) {
3727
3810
  await this.dexieDb.deleteMany(collection, idsToCheck);
3728
3811
  } else {
3729
- const dexieItems = await this.dexieDb.getByIds(collection, idsToCheck);
3812
+ const dexieItems = await this.dexieDb.getByIds(
3813
+ collection,
3814
+ idsToCheck
3815
+ );
3730
3816
  const dexieSaveBatch = [];
3731
3817
  const inMemUpdateBatch = [];
3732
3818
  const inMemDeleteIds = [];
@@ -3754,7 +3840,12 @@ var _SyncEngine = class _SyncEngine {
3754
3840
  await this.dexieDb.deleteMany(collection, dexieDeleteIds);
3755
3841
  }
3756
3842
  if (inMemUpdateBatch.length > 0) {
3757
- this.deps.writeToInMemBatch(collection, inMemUpdateBatch, "upsert", { source: "incremental" });
3843
+ this.deps.writeToInMemBatch(
3844
+ collection,
3845
+ inMemUpdateBatch,
3846
+ "upsert",
3847
+ { source: "incremental" }
3848
+ );
3758
3849
  }
3759
3850
  if (inMemDeleteIds.length > 0) {
3760
3851
  this.deps.writeToInMemBatch(
@@ -3777,7 +3868,9 @@ var _SyncEngine = class _SyncEngine {
3777
3868
  }
3778
3869
  await this.dexieDb.deleteMany(collection, deleteIds);
3779
3870
  if (!isWriteOnly) {
3780
- this.deps.writeToInMemBatch(collection, deleteDbEntities, "delete", { source: "incremental" });
3871
+ this.deps.writeToInMemBatch(collection, deleteDbEntities, "delete", {
3872
+ source: "incremental"
3873
+ });
3781
3874
  }
3782
3875
  sentCount += deleted.length;
3783
3876
  collectionSentCount += deleted.length;
@@ -3793,7 +3886,7 @@ var _SyncEngine = class _SyncEngine {
3793
3886
  if (collectionSentCount > 0) {
3794
3887
  collectionSentCounts[collection] = collectionSentCount;
3795
3888
  }
3796
- let maxTs = void 0;
3889
+ let maxTs;
3797
3890
  for (const arr of [inserted, updated, deleted]) {
3798
3891
  for (const item of arr) {
3799
3892
  if (item._ts) {
@@ -3857,13 +3950,16 @@ var _SyncEngine = class _SyncEngine {
3857
3950
  /**
3858
3951
  * Upload dirty items for a specific collection.
3859
3952
  */
3860
- async uploadDirtyItemsForCollection(collection) {
3953
+ async uploadDirtyItemsForCollection(collection, calledFrom) {
3861
3954
  const dirtyItems = await this.dexieDb.getDirty(collection);
3862
3955
  if (dirtyItems.length === 0) {
3863
3956
  return { sentCount: 0 };
3864
3957
  }
3865
3958
  const ids = dirtyItems.map((item) => item._id);
3866
- const fullItems = await this.dexieDb.getByIds(collection, ids);
3959
+ const fullItems = await this.dexieDb.getByIds(
3960
+ collection,
3961
+ ids
3962
+ );
3867
3963
  const dirtyChangesMap = /* @__PURE__ */ new Map();
3868
3964
  for (const dirtyItem of dirtyItems) {
3869
3965
  dirtyChangesMap.set(String(dirtyItem._id), dirtyItem);
@@ -3879,20 +3975,24 @@ var _SyncEngine = class _SyncEngine {
3879
3975
  if (updates.length === 0) {
3880
3976
  return { sentCount: 0 };
3881
3977
  }
3882
- const collectionBatches = [[{
3883
- collection,
3884
- batch: {
3885
- updates: updates.map((item) => {
3886
- const _a = item.delta, { _ts, _rev } = _a, changes = __objRest(_a, ["_ts", "_rev"]);
3887
- return {
3888
- _id: item._id,
3889
- _rev: typeof _rev === "number" ? _rev : 0,
3890
- update: changes
3891
- };
3892
- }),
3893
- deletes: []
3894
- }
3895
- }]];
3978
+ const collectionBatches = [
3979
+ [
3980
+ {
3981
+ collection,
3982
+ batch: {
3983
+ updates: updates.map((item) => {
3984
+ const _a = item.delta, { _ts, _rev } = _a, changes = __objRest(_a, ["_ts", "_rev"]);
3985
+ return {
3986
+ _id: item._id,
3987
+ _rev: typeof _rev === "number" ? _rev : 0,
3988
+ update: changes
3989
+ };
3990
+ }),
3991
+ deletes: []
3992
+ }
3993
+ }
3994
+ ]
3995
+ ];
3896
3996
  const results = await this.deps.withSyncTimeout(
3897
3997
  this.restInterface.updateCollections(collectionBatches),
3898
3998
  "updateCollections"
@@ -3955,6 +4055,29 @@ var _SyncEngine = class _SyncEngine {
3955
4055
  if (allSuccessIds.length > 0) {
3956
4056
  await this.dexieDb.clearDirtyChangesBatch(collection, allSuccessIds);
3957
4057
  }
4058
+ const sentIdsForCollection = /* @__PURE__ */ new Set();
4059
+ for (const batch of collectionBatches) {
4060
+ for (const b of batch) {
4061
+ if (b.collection !== collection) continue;
4062
+ for (const u of b.batch.updates)
4063
+ sentIdsForCollection.add(String(u._id));
4064
+ for (const d of b.batch.deletes)
4065
+ sentIdsForCollection.add(String(d._id));
4066
+ }
4067
+ }
4068
+ const retainedIds = [];
4069
+ for (const sid of sentIdsForCollection) {
4070
+ if (!allSuccessIds.find((aid) => String(aid) === sid)) {
4071
+ retainedIds.push(sid);
4072
+ }
4073
+ }
4074
+ if (retainedIds.length > 0) {
4075
+ const newlyStuck = await this.dexieDb.incrementDirtyUploadAttempts(
4076
+ collection,
4077
+ retainedIds
4078
+ );
4079
+ this.callOnDirtyItemStuck(collection, newlyStuck, calledFrom);
4080
+ }
3958
4081
  if (mustRefresh && mustRefresh.length > 0) {
3959
4082
  await this.adoptMustRefresh(
3960
4083
  collection,
@@ -3980,7 +4103,12 @@ var _SyncEngine = class _SyncEngine {
3980
4103
  const config = this.collections.get(collectionName);
3981
4104
  if (!config) return { updatedIds: [] };
3982
4105
  const source = (_a = opts == null ? void 0 : opts.source) != null ? _a : "incremental";
3983
- const result = await this.processIncomingServerData(collectionName, config, serverData, source);
4106
+ const result = await this.processIncomingServerData(
4107
+ collectionName,
4108
+ config,
4109
+ serverData,
4110
+ source
4111
+ );
3984
4112
  if (result.updatedIds.length > 0) {
3985
4113
  this.deps.broadcastUpdates({ [collectionName]: result.updatedIds });
3986
4114
  }
@@ -3998,8 +4126,14 @@ var _SyncEngine = class _SyncEngine {
3998
4126
  const chunk = serverData.slice(offset, offset + BATCH);
3999
4127
  const chunkIds = [];
4000
4128
  for (const item of chunk) chunkIds.push(item._id);
4001
- const localItems = await this.dexieDb.getByIds(collectionName, chunkIds);
4002
- const dirtyChangesMap = await this.dexieDb.getDirtyChangesBatch(collectionName, chunkIds);
4129
+ const localItems = await this.dexieDb.getByIds(
4130
+ collectionName,
4131
+ chunkIds
4132
+ );
4133
+ const dirtyChangesMap = await this.dexieDb.getDirtyChangesBatch(
4134
+ collectionName,
4135
+ chunkIds
4136
+ );
4003
4137
  const dexieBatch = [];
4004
4138
  const inMemSaveBatch = [];
4005
4139
  const inMemDeleteIds = [];
@@ -4048,7 +4182,9 @@ var _SyncEngine = class _SyncEngine {
4048
4182
  await this.dexieDb.saveMany(collectionName, dexieBatch);
4049
4183
  }
4050
4184
  if (inMemSaveBatch.length > 0) {
4051
- this.deps.writeToInMemBatch(collectionName, inMemSaveBatch, "upsert", { source });
4185
+ this.deps.writeToInMemBatch(collectionName, inMemSaveBatch, "upsert", {
4186
+ source
4187
+ });
4052
4188
  }
4053
4189
  if (inMemDeleteIds.length > 0) {
4054
4190
  this.deps.writeToInMemBatch(
@@ -4096,7 +4232,10 @@ var _SyncEngine = class _SyncEngine {
4096
4232
  timestamp: /* @__PURE__ */ new Date()
4097
4233
  });
4098
4234
  } catch (err) {
4099
- console.error(`[SyncEngine] onConflictResolved callback failed: ${err}`, err);
4235
+ console.error(
4236
+ `[SyncEngine] onConflictResolved callback failed: ${err}`,
4237
+ err
4238
+ );
4100
4239
  }
4101
4240
  }
4102
4241
  return resolved;
@@ -4141,7 +4280,10 @@ var _SyncEngine = class _SyncEngine {
4141
4280
  calledFrom
4142
4281
  });
4143
4282
  } catch (err) {
4144
- console.error(`[SyncEngine] onFindNewerManyCall callback failed: ${err}`, err);
4283
+ console.error(
4284
+ `[SyncEngine] onFindNewerManyCall callback failed: ${err}`,
4285
+ err
4286
+ );
4145
4287
  }
4146
4288
  }
4147
4289
  }
@@ -4160,7 +4302,10 @@ var _SyncEngine = class _SyncEngine {
4160
4302
  ttfbMs: metrics == null ? void 0 : metrics.ttfbMs
4161
4303
  });
4162
4304
  } catch (err) {
4163
- console.error(`[SyncEngine] onFindNewerManyResult callback failed: ${err}`, err);
4305
+ console.error(
4306
+ `[SyncEngine] onFindNewerManyResult callback failed: ${err}`,
4307
+ err
4308
+ );
4164
4309
  }
4165
4310
  }
4166
4311
  }
@@ -4173,7 +4318,10 @@ var _SyncEngine = class _SyncEngine {
4173
4318
  calledFrom
4174
4319
  });
4175
4320
  } catch (err) {
4176
- console.error(`[SyncEngine] onServerWriteRequest callback failed: ${err}`, err);
4321
+ console.error(
4322
+ `[SyncEngine] onServerWriteRequest callback failed: ${err}`,
4323
+ err
4324
+ );
4177
4325
  }
4178
4326
  }
4179
4327
  }
@@ -4188,10 +4336,29 @@ var _SyncEngine = class _SyncEngine {
4188
4336
  calledFrom
4189
4337
  });
4190
4338
  } catch (err) {
4191
- console.error(`[SyncEngine] onServerWriteResult callback failed: ${err}`, err);
4339
+ console.error(
4340
+ `[SyncEngine] onServerWriteResult callback failed: ${err}`,
4341
+ err
4342
+ );
4192
4343
  }
4193
4344
  }
4194
4345
  }
4346
+ callOnDirtyItemStuck(collection, stuckMetas, calledFrom) {
4347
+ if (!this.callbacks.onDirtyItemStuck || stuckMetas.length === 0) return;
4348
+ try {
4349
+ this.callbacks.onDirtyItemStuck({
4350
+ collection,
4351
+ items: stuckMetas,
4352
+ calledFrom,
4353
+ timestamp: /* @__PURE__ */ new Date()
4354
+ });
4355
+ } catch (err) {
4356
+ console.error(
4357
+ `[SyncEngine] onDirtyItemStuck callback failed: ${err}`,
4358
+ err
4359
+ );
4360
+ }
4361
+ }
4195
4362
  callOnServerSyncWrite(request, response, error, startTime, timestamp, calledFrom) {
4196
4363
  if (!this.callbacks.onServerSyncWrite) return;
4197
4364
  try {
@@ -4209,7 +4376,10 @@ var _SyncEngine = class _SyncEngine {
4209
4376
  timestamp
4210
4377
  });
4211
4378
  } catch (err) {
4212
- console.error(`[SyncEngine] onServerSyncWrite callback failed: ${err}`, err);
4379
+ console.error(
4380
+ `[SyncEngine] onServerSyncWrite callback failed: ${err}`,
4381
+ err
4382
+ );
4213
4383
  }
4214
4384
  }
4215
4385
  };
@@ -4867,7 +5037,8 @@ var _SyncedDb = class _SyncedDb {
4867
5037
  onServerSyncWrite: config.onServerSyncWrite,
4868
5038
  onFindNewerManyCall: config.onFindNewerManyCall,
4869
5039
  onFindNewerManyResult: config.onFindNewerManyResult,
4870
- onUploadSkip: config.onUploadSkip
5040
+ onUploadSkip: config.onUploadSkip,
5041
+ onDirtyItemStuck: config.onDirtyItemStuck
4871
5042
  },
4872
5043
  deps: {
4873
5044
  getSyncMetaCache: () => this.syncMetaCache,
@@ -6330,6 +6501,59 @@ var _SyncedDb = class _SyncedDb {
6330
6501
  await this.dexieDb.clearDirtyChanges(collection);
6331
6502
  return metas;
6332
6503
  }
6504
+ /**
6505
+ * Return dirty entries whose `stuckSince` is set across all collections.
6506
+ * A stuck entry has exceeded `DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS` upload
6507
+ * attempts without success — typically a server-side error that prevents
6508
+ * the record from being accepted.
6509
+ *
6510
+ * Same shape as `getDirtyMeta()` but filtered to stuck items only.
6511
+ * Collections with no stuck items are omitted from the result.
6512
+ */
6513
+ async getStuckItems() {
6514
+ const result = {};
6515
+ for (const [collectionName] of this.collections) {
6516
+ const metas = await this.dexieDb.getDirtyMeta(collectionName);
6517
+ const stuck = metas.filter((m) => m.stuckSince !== void 0);
6518
+ if (stuck.length > 0) {
6519
+ result[collectionName] = stuck;
6520
+ }
6521
+ }
6522
+ return result;
6523
+ }
6524
+ /**
6525
+ * Discard all stuck dirty entries across ALL collections without touching
6526
+ * the main row data. A stuck entry is one where `stuckSince` is set
6527
+ * (exceeded `DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS` upload attempts).
6528
+ *
6529
+ * Fires `onBeforeDirtyClearAll` once per collection that has stuck items,
6530
+ * with `reason: "discard-stuck"`. Returns the `DirtyMeta[]` of every
6531
+ * entry that was removed.
6532
+ *
6533
+ * Does NOT await in-flight uploads — call `flushToServer()` first if a
6534
+ * last-chance upload is desired.
6535
+ *
6536
+ * @param calledFrom Diagnostic tag threaded through to the callback.
6537
+ */
6538
+ async discardStuckItems(calledFrom) {
6539
+ const cleared = [];
6540
+ for (const [collectionName] of this.collections) {
6541
+ const metas = await this.dexieDb.getDirtyMeta(collectionName);
6542
+ const stuck = metas.filter((m) => m.stuckSince !== void 0);
6543
+ if (stuck.length === 0) continue;
6544
+ this.safeCallback(this.onBeforeDirtyClearAll, {
6545
+ reason: "discard-stuck",
6546
+ collection: collectionName,
6547
+ items: stuck,
6548
+ calledFrom,
6549
+ timestamp: /* @__PURE__ */ new Date()
6550
+ });
6551
+ const ids = stuck.map((m) => m.id);
6552
+ await this.dexieDb.clearDirtyChangesBatch(collectionName, ids);
6553
+ for (const m of stuck) cleared.push(m);
6554
+ }
6555
+ return cleared;
6556
+ }
6333
6557
  // ==================== Data Deletion ====================
6334
6558
  async dropCollection(collection, force = false) {
6335
6559
  this.assertCollection(collection);
@@ -7359,6 +7583,9 @@ var SyncedDb = _SyncedDb;
7359
7583
  // src/db/DexieDb.ts
7360
7584
  import Dexie from "dexie";
7361
7585
 
7586
+ // src/types/I_DexieDb.ts
7587
+ var DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS = 2;
7588
+
7362
7589
  // src/utils/computeDiff.ts
7363
7590
  function isDescendantOrEqual(path, candidate) {
7364
7591
  if (path === candidate) return true;
@@ -7645,7 +7872,11 @@ var DexieDb = class extends Dexie {
7645
7872
  baseTs: entry.baseTs,
7646
7873
  baseRev: entry.baseRev,
7647
7874
  createdAt: entry.createdAt,
7648
- updatedAt: entry.updatedAt
7875
+ updatedAt: entry.updatedAt,
7876
+ firstUploadAttempt: entry.firstUploadAttempt,
7877
+ lastUploadAttempt: entry.lastUploadAttempt,
7878
+ numUploadAttempts: entry.numUploadAttempts,
7879
+ stuckSince: entry.stuckSince
7649
7880
  });
7650
7881
  }
7651
7882
  return result;
@@ -7723,6 +7954,45 @@ var DexieDb = class extends Dexie {
7723
7954
  }
7724
7955
  return result;
7725
7956
  }
7957
+ async incrementDirtyUploadAttempts(collection, ids) {
7958
+ var _a;
7959
+ if (ids.length === 0) return [];
7960
+ const keys = [];
7961
+ for (const id of ids) keys.push([collection, this.idToString(id)]);
7962
+ const entries = await this.dirtyChanges.bulkGet(keys);
7963
+ const now = Date.now();
7964
+ const toUpdate = [];
7965
+ const newlyStuck = [];
7966
+ for (let i = 0; i < entries.length; i++) {
7967
+ const entry = entries[i];
7968
+ if (!entry) continue;
7969
+ entry.numUploadAttempts = ((_a = entry.numUploadAttempts) != null ? _a : 0) + 1;
7970
+ if (entry.firstUploadAttempt === void 0) {
7971
+ entry.firstUploadAttempt = now;
7972
+ }
7973
+ entry.lastUploadAttempt = now;
7974
+ if (entry.numUploadAttempts > DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS && entry.stuckSince === void 0) {
7975
+ entry.stuckSince = now;
7976
+ newlyStuck.push({
7977
+ collection: entry.collection,
7978
+ id: entry.id,
7979
+ baseTs: entry.baseTs,
7980
+ baseRev: entry.baseRev,
7981
+ createdAt: entry.createdAt,
7982
+ updatedAt: entry.updatedAt,
7983
+ firstUploadAttempt: entry.firstUploadAttempt,
7984
+ lastUploadAttempt: entry.lastUploadAttempt,
7985
+ numUploadAttempts: entry.numUploadAttempts,
7986
+ stuckSince: entry.stuckSince
7987
+ });
7988
+ }
7989
+ toUpdate.push(entry);
7990
+ }
7991
+ if (toUpdate.length > 0) {
7992
+ await this.dirtyChanges.bulkPut(toUpdate);
7993
+ }
7994
+ return newlyStuck;
7995
+ }
7726
7996
  async clearDirtyChange(collection, id) {
7727
7997
  const stringId = this.idToString(id);
7728
7998
  await this.dirtyChanges.delete([collection, stringId]);
@@ -8231,6 +8501,7 @@ function createStructureReader(structure, firstId) {
8231
8501
  inlineObjectReadThreshold = Infinity;
8232
8502
  return readObject();
8233
8503
  }
8504
+ structure.read0 = optimizedReadObject;
8234
8505
  if (structure.highByte === 0)
8235
8506
  structure.read = createSecondByteReader(firstId, structure.read);
8236
8507
  return optimizedReadObject();
@@ -8247,6 +8518,7 @@ function createStructureReader(structure, firstId) {
8247
8518
  return object;
8248
8519
  }
8249
8520
  readObject.count = 0;
8521
+ structure.read0 = readObject;
8250
8522
  if (structure.highByte === 0) {
8251
8523
  return createSecondByteReader(firstId, readObject);
8252
8524
  }
@@ -8646,7 +8918,7 @@ var recordDefinition = (id, highByte) => {
8646
8918
  }
8647
8919
  currentStructures[id] = structure;
8648
8920
  structure.read = createStructureReader(structure, firstByte);
8649
- return structure.read();
8921
+ return (structure.read0 || structure.read)();
8650
8922
  };
8651
8923
  currentExtensions[0] = () => {
8652
8924
  };
@@ -9024,6 +9296,7 @@ var Packr = class extends Unpackr {
9024
9296
  let newSharedData = (packr3._prepareStructures || prepareStructures)(structures, packr3);
9025
9297
  if (!encodingError) {
9026
9298
  if (packr3.saveStructures(newSharedData, newSharedData.isCompatible) === false) {
9299
+ structures.uninitialized = true;
9027
9300
  return packr3.pack(value, encodeOptions);
9028
9301
  }
9029
9302
  packr3.lastNamedStructuresLength = sharedLength;
@@ -46,6 +46,7 @@ export declare class DexieDb extends Dexie implements I_DexieDb {
46
46
  }>): Promise<void>;
47
47
  getDirtyChange(collection: string, id: Id): Promise<DirtyChange | undefined>;
48
48
  getDirtyChangesBatch(collection: string, ids: Id[]): Promise<Map<string, DirtyChange>>;
49
+ incrementDirtyUploadAttempts(collection: string, ids: Id[]): Promise<DirtyMeta[]>;
49
50
  clearDirtyChange(collection: string, id: Id): Promise<void>;
50
51
  clearDirtyChangesBatch(collection: string, ids: Id[]): Promise<void>;
51
52
  clearDirtyChanges(collection: string): Promise<void>;
@@ -333,6 +333,31 @@ export declare class SyncedDb implements I_SyncedDb {
333
333
  * upload is desired.
334
334
  */
335
335
  clearDirty(collection?: string, ids?: Id[], calledFrom?: string): Promise<DirtyMeta[]>;
336
+ /**
337
+ * Return dirty entries whose `stuckSince` is set across all collections.
338
+ * A stuck entry has exceeded `DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS` upload
339
+ * attempts without success — typically a server-side error that prevents
340
+ * the record from being accepted.
341
+ *
342
+ * Same shape as `getDirtyMeta()` but filtered to stuck items only.
343
+ * Collections with no stuck items are omitted from the result.
344
+ */
345
+ getStuckItems(): Promise<Readonly<Record<string, readonly DirtyMeta[]>>>;
346
+ /**
347
+ * Discard all stuck dirty entries across ALL collections without touching
348
+ * the main row data. A stuck entry is one where `stuckSince` is set
349
+ * (exceeded `DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS` upload attempts).
350
+ *
351
+ * Fires `onBeforeDirtyClearAll` once per collection that has stuck items,
352
+ * with `reason: "discard-stuck"`. Returns the `DirtyMeta[]` of every
353
+ * entry that was removed.
354
+ *
355
+ * Does NOT await in-flight uploads — call `flushToServer()` first if a
356
+ * last-chance upload is desired.
357
+ *
358
+ * @param calledFrom Diagnostic tag threaded through to the callback.
359
+ */
360
+ discardStuckItems(calledFrom?: string): Promise<DirtyMeta[]>;
336
361
  dropCollection(collection: string, force?: boolean): Promise<void>;
337
362
  dropDatabase(force?: boolean): Promise<void>;
338
363
  /**
@@ -62,7 +62,7 @@ export declare class SyncEngine implements I_SyncEngine {
62
62
  /**
63
63
  * Upload dirty items for a specific collection.
64
64
  */
65
- uploadDirtyItemsForCollection(collection: string): Promise<UploadResult>;
65
+ uploadDirtyItemsForCollection(collection: string, calledFrom?: string): Promise<UploadResult>;
66
66
  /**
67
67
  * Process incoming server data for a single collection.
68
68
  * Used by referToServer to process findNewer results.
@@ -89,5 +89,6 @@ export declare class SyncEngine implements I_SyncEngine {
89
89
  private callOnFindNewerManyResult;
90
90
  private callOnServerWriteRequest;
91
91
  private callOnServerWriteResult;
92
+ private callOnDirtyItemStuck;
92
93
  private callOnServerSyncWrite;
93
94
  }
@@ -267,6 +267,7 @@ export interface SyncEngineCallbacks {
267
267
  onFindNewerManyCall?: (info: FindNewerManyCallInfo) => void;
268
268
  onFindNewerManyResult?: (info: FindNewerManyResultInfo) => void;
269
269
  onUploadSkip?: (info: import("../../types/I_SyncedDb").UploadSkipInfo) => void;
270
+ onDirtyItemStuck?: (info: import("../../types/I_SyncedDb").DirtyItemStuckInfo) => void;
270
271
  }
271
272
  export interface SyncEngineDeps {
272
273
  getSyncMetaCache: () => Map<string, SyncMeta>;
@@ -326,7 +327,7 @@ export interface I_SyncEngine {
326
327
  /** Upload dirty items for all collections. */
327
328
  uploadDirtyItems(calledFrom?: string): Promise<UploadResult>;
328
329
  /** Upload dirty items for a specific collection. */
329
- uploadDirtyItemsForCollection(collection: string): Promise<UploadResult>;
330
+ uploadDirtyItemsForCollection(collection: string, calledFrom?: string): Promise<UploadResult>;
330
331
  /** Process incoming server data for a single collection (used by referToServer). */
331
332
  processCollectionServerData(collectionName: string, serverData: import("../../types/DbEntity").LocalDbEntity[], opts?: {
332
333
  source?: SyncSource;
@@ -7,6 +7,11 @@ export interface SyncMeta {
7
7
  collection: string;
8
8
  lastSyncTs?: any;
9
9
  }
10
+ /**
11
+ * After this many upload attempts without success, the item is considered
12
+ * stuck and `stuckSince` is set on the DirtyChange entry.
13
+ */
14
+ export declare const DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS = 2;
10
15
  /**
11
16
  * Dirty change entry - tracks accumulated changes for a record
12
17
  * Stored in _dirty_changes table with composite key [collection, id]
@@ -26,6 +31,14 @@ export interface DirtyChange {
26
31
  createdAt: number;
27
32
  /** When last change was accumulated */
28
33
  updatedAt: number;
34
+ /** When this dirty entry was first attempted for upload (ms timestamp) */
35
+ firstUploadAttempt?: number;
36
+ /** When this dirty entry was last attempted for upload (ms timestamp) */
37
+ lastUploadAttempt?: number;
38
+ /** How many upload attempts have been made so far */
39
+ numUploadAttempts?: number;
40
+ /** Set when numUploadAttempts > DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS (ms timestamp) */
41
+ stuckSince?: number;
29
42
  }
30
43
  /**
31
44
  * Meta fields of a DirtyChange entry, without the `changes` payload.
@@ -110,6 +123,8 @@ export interface I_DexieDb {
110
123
  getDirtyChange(collection: string, id: Id): Promise<DirtyChange | undefined>;
111
124
  /** Get dirty change entries for multiple records (batch) */
112
125
  getDirtyChangesBatch(collection: string, ids: Id[]): Promise<Map<string, DirtyChange>>;
126
+ /** Increment upload attempt counters for retained dirty entries. Returns newly stuck DirtyMeta[]. */
127
+ incrementDirtyUploadAttempts(collection: string, ids: Id[]): Promise<DirtyMeta[]>;
113
128
  /** Clear dirty change for a record (after successful sync) */
114
129
  clearDirtyChange(collection: string, id: Id): Promise<void>;
115
130
  /** Clear dirty changes for multiple records (batch) */
@@ -91,6 +91,21 @@ export interface SaveIdMismatchInfo {
91
91
  /** Timestamp when mismatch detected */
92
92
  timestamp: Date;
93
93
  }
94
+ /**
95
+ * Payload za `onDirtyItemStuck` callback. Sproži se, ko `incrementDirtyUploadAttempts`
96
+ * nastavi `stuckSince` na DirtyChange entry-ju — pomeni, da je item presegel
97
+ * `DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS` neuspelih nalaganj.
98
+ */
99
+ export interface DirtyItemStuckInfo {
100
+ /** Collection name */
101
+ collection: string;
102
+ /** Meta podatki stuck itemov (vsebujejo `stuckSince`, `numUploadAttempts`, itd.) */
103
+ items: import("./I_DexieDb").DirtyMeta[];
104
+ /** Optional caller tag */
105
+ calledFrom?: string;
106
+ /** Timestamp when stuck was detected */
107
+ timestamp: Date;
108
+ }
94
109
  /**
95
110
  * Callback payload fired by `clearDirty()` when called WITHOUT a
96
111
  * `collection` argument (clear-all path). One invocation per
@@ -741,6 +756,19 @@ export interface SyncedDbConfig {
741
756
  * reveals which dirty entries hit which skip path.
742
757
  */
743
758
  onUploadSkip?: (info: UploadSkipInfo) => void;
759
+ /**
760
+ * Fired when a dirty item exceeds `DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS`
761
+ * upload attempts without server acknowledgement. The item's `stuckSince`
762
+ * is set and it will remain dirty until manually cleared via
763
+ * `discardStuckItems()` or `clearDirty()`, or until a future upload
764
+ * succeeds (which clears the dirty entry normally).
765
+ *
766
+ * Fires once per collection per `uploadDirtyItems` cycle, with ALL items
767
+ * that newly became stuck in that cycle (not previously stuck items).
768
+ *
769
+ * Use for alerting / syslog / automatic recovery flows.
770
+ */
771
+ onDirtyItemStuck?: (info: DirtyItemStuckInfo) => void;
744
772
  /**
745
773
  * Callback when `save(collection, id, update)` is called with `update._id`
746
774
  * that does NOT match `id`. Library auto-strips `_id` from update to prevent
@@ -1228,6 +1256,20 @@ export interface I_SyncedDb {
1228
1256
  * ki imajo vsaj en dirty zapis. Kolekcije brez dirty vnosov niso vključene.
1229
1257
  */
1230
1258
  getDirtyMeta(): Promise<Readonly<Record<string, readonly DirtyMeta[]>>>;
1259
+ /**
1260
+ * Return dirty entries whose `stuckSince` is set across all collections.
1261
+ * A stuck entry has exceeded `DIRTY_STUCK_AFTER_UPLOAD_ATTEMPTS` upload
1262
+ * attempts without success. Collections with no stuck items are omitted.
1263
+ */
1264
+ getStuckItems(): Promise<Readonly<Record<string, readonly DirtyMeta[]>>>;
1265
+ /**
1266
+ * Discard all stuck dirty entries across ALL collections without touching
1267
+ * the main row data. Fires `onBeforeDirtyClearAll` per collection with
1268
+ * `reason: "discard-stuck"`. Returns the `DirtyMeta[]` of removed entries.
1269
+ *
1270
+ * @param calledFrom Diagnostic tag threaded through to the callback.
1271
+ */
1272
+ discardStuckItems(calledFrom?: string): Promise<DirtyMeta[]>;
1231
1273
  /**
1232
1274
  * Drops a collection, ensuring no data loss.
1233
1275
  * - Throws if offline or forcedOffline (unless force=true)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cry-synced-db-client",
3
- "version": "0.1.197",
3
+ "version": "0.1.200",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -30,16 +30,16 @@
30
30
  "@types/bun": "latest",
31
31
  "bson": "^7.2.0",
32
32
  "cry-ebus-proxy": "^2.0.0",
33
- "dexie": "^4.4.2",
34
- "esbuild": "^0.28.0",
33
+ "dexie": "^4.4.3",
34
+ "esbuild": "^0.28.1",
35
35
  "fake-indexeddb": "^6.2.5",
36
36
  "typescript": "^6",
37
- "vitest": "^4.1.6"
37
+ "vitest": "^4.1.8"
38
38
  },
39
39
  "dependencies": {
40
40
  "cry-db": "^2.5.0",
41
- "cry-helpers": "^2.1.194",
42
- "msgpackr": "^2.0.1",
41
+ "cry-helpers": "^2.1.205",
42
+ "msgpackr": "^2.0.4",
43
43
  "superjson": "^2.2.6"
44
44
  },
45
45
  "peerDependencies": {