cry-synced-db-client 0.1.176 → 0.1.178

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,6 +1,91 @@
1
1
  # Versions
2
2
 
3
- ## Unreleased
3
+ ## 0.1.178 (2026-05-13)
4
+
5
+ ### Error reason interpolated into every `console.*` tag line
6
+
7
+ Audited all 111 `console.*` calls in `src/` against the
8
+ ts-coding/console-reporting skill (first-arg string + tag prefix + critical
9
+ info in the tag line + template literals + correct severity).
10
+
11
+ The previous pattern across the codebase was
12
+ `console.error("[Tag] X failed:", err)` — the reason lived in the second
13
+ arg, which is grep-unfriendly: a search for the failure message in logs
14
+ required expanding object payloads.
15
+
16
+ Converted ~60 sites to
17
+ `` console.error(`[Tag] X failed: ${err}`, err) `` so the reason appears
18
+ in the same scannable line as the tag while the full error object remains
19
+ attached for devtools inspection.
20
+
21
+ Also fixed:
22
+ - `SyncedDb.findById`: bare `console.error(err)` → tag line first, Error
23
+ object as second arg.
24
+ - `Ebus2ProxyNotifier`: WebSocket error event and server-error payload now
25
+ interpolate `event.type` / `message.error` into the tag line.
26
+ - `PendingChangesManager`: `_id` object-type debug log now puts the
27
+ `typeof` value in the tag line, not as a separate arg.
28
+
29
+ Pure log-message reshaping — no behavior change. Test suite 728 bun + 18
30
+ vitest still passing.
31
+
32
+ ## 0.1.177 (2026-05-13)
33
+
34
+ Hot-path micro-optimizations from a ts-coding skill audit. Five small wins,
35
+ each verified independently against the actual control flow before applying.
36
+
37
+ ### `$regex` operand cached per pattern
38
+
39
+ `matchesOperator` for `$regex` used to call `new RegExp(operand)` on every
40
+ record × operator invocation. For in-mem `find` over a large collection
41
+ this recompiled the same pattern thousands of times. Now a module-level
42
+ `Map<string, RegExp>` caches compiled regexes by operand string with a
43
+ bounded FIFO eviction (128 entries) so dynamic patterns don't leak.
44
+ Implementation in `src/utils/localQuery.ts:compileRegex`.
45
+
46
+ ### `SyncedDb.close()` is idempotent
47
+
48
+ Added a `this.closed` guard that early-returns on the second call. Most
49
+ disposal calls inside `close()` were already idempotent (timer clears,
50
+ listener removes), but `crossTabSync.dispose()`, `wakeSync.dispose()`,
51
+ `networkStatus.dispose()`, and `serverUpdateNotifier.dispose?.()` are not
52
+ internally guarded — calling them twice could throw. Now safe.
53
+
54
+ ### Orphan dirty reconstruction batches into `saveMany`
55
+
56
+ `SyncEngine.uploadDirtyItems` reconstructs missing main rows when a dirty
57
+ change exists but the main entry was lost (e.g. debounced write didn't
58
+ flush before reload). Previously called `await dexieDb.save(...)` per
59
+ orphan inside the per-item loop. Now collects reconstructed entities and
60
+ issues one `saveMany` at the end. Rare path — minor improvement, no
61
+ behavior change.
62
+
63
+ ### `sentIds` warning path drops intermediate spreads
64
+
65
+ `SyncEngine.uploadDirtyItems` builds a `sentIds` Set for the
66
+ "unacknowledged items" warning. Previously used
67
+ `new Set([...batches.flat().filter(...).flatMap(...)])` (multiple array
68
+ allocations) and `[...sentIds].filter(...)` (one more spread). Now a
69
+ single for-of pass into a Set, plus a direct iteration to build `unacked`.
70
+
71
+ ### `ServerUpdateHandler` batch case now concurrent
72
+
73
+ WebSocket batch server-update notifications previously processed each
74
+ insert / update / delete with sequential `await` calls. Each per-item
75
+ handler operates on an independent `_id`; Dexie reads/writes on different
76
+ keys don't conflict, so replaced the loops with `Promise.all` over the
77
+ per-item handlers. On a batch of N items this turns N sequential IDB
78
+ round-trips into N concurrent ones — the dominant latency win for any
79
+ tab subscribed to a busy collection.
80
+
81
+ The deeper "single `getByIds` + `saveMany` per batch" refactor was
82
+ deferred: the per-item update handler has 5+ semantically distinct
83
+ branches (self-echo A/B/C, pending change merge, dirty merge, clean
84
+ meta-only) and duplicating that logic for the batch path carries
85
+ re-implementation risk that isn't justified by the additional latency
86
+ savings over `Promise.all`.
87
+
88
+ ## 0.1.176 (2026-05-13)
4
89
 
5
90
  ### Passive transport metrics on `findNewerManyStream` round-trip (rdb2)
6
91
 
@@ -104,6 +189,8 @@ Live test (localhost, warm, 20 samples): WS p50 0.30 ms, E2E p50
104
189
  4.11 ms. Mock-only unit tests in `test/measureRtt.test.ts` (7 cases —
105
190
  delegation, propagated rejection, no-notifier error, shape sanity).
106
191
 
192
+ ## 0.1.175 (2026-05-13)
193
+
107
194
  ### `save(coll, id, {field: {}})` clears existing nested children
108
195
 
109
196
  `computeDiffInto` for plain objects iterated only `Object.keys(update)`,
@@ -176,6 +263,37 @@ await syncedDb.replaceSyncCollection({
176
263
  });
177
264
  ```
178
265
 
266
+ ## 0.1.174 (2026-05-12)
267
+
268
+ ### Fix: overlay `_dirty_changes` onto in-mem on init
269
+
270
+ After Ctrl+R while a debounced Dexie main write was pending, the in-mem
271
+ cache showed stale Dexie main state instead of the merged dirty diff —
272
+ UI readers saw old field values until the next sync round-trip.
273
+ Production incident, klikvet 2026-05-12 with the server offline.
274
+
275
+ Root cause: `loadCollectionToInMem` (called from `init()`) read only the
276
+ Dexie main table; it never overlaid `_dirty_changes`.
277
+ `recoverPendingWrites` recovers from `localStorage`, but `localStorage`
278
+ may have been cleared by an earlier partial-debounce success — the
279
+ Dexie-only `_dirty_changes` table is the durable source for those
280
+ writes.
281
+
282
+ Fix: after loading Dexie main, walk `_dirty_changes` and apply each diff
283
+ to the matching main row via `applyDiffLocally`. Orphan dirty (no
284
+ matching main row) is included in in-mem with a `console.warn`. Dirty
285
+ entries marking `_deleted` / `_archived` remove the record from in-mem.
286
+
287
+ Scope: in-mem cache only. `findById` uses the in-mem fast path, so its
288
+ results now reflect dirty. `find()` reads Dexie main directly — that's
289
+ a separate Dexie-overlay gap, not addressed here.
290
+
291
+ Regression test: `test/dirtyOverlayOnInit.test.ts` (5 cases — production
292
+ scenario, orphan, soft-delete via dirty, no-dirty fast path, multiple
293
+ records). 708 pass / 0 fail.
294
+
295
+ ## 0.1.173 (2026-05-12)
296
+
179
297
  ### `preprocessDirtyItem` callback — per-item filter / transform before upload
180
298
 
181
299
  New optional config callback fired for **every** dirty item just before it
@@ -212,6 +330,8 @@ new SyncedDb({
212
330
  Useful for: per-tenant data sanitization, conditional upload gating,
213
331
  audit-trail injection.
214
332
 
333
+ ## 0.1.172 (2026-05-12)
334
+
215
335
  ### Nested-bracket terminal layering in `mergeDirtyPath` Case 2
216
336
 
217
337
  When a new terminal-bracket whole-element write arrives AFTER pending
@@ -250,6 +370,8 @@ alongside actual upload errors in observability pipelines.
250
370
  `SUPRESS_DB_WARNINGS` constant in `SyncEngine.ts` silences them when
251
371
  needed (e.g. during noisy migrations).
252
372
 
373
+ ## 0.1.171 (2026-05-11)
374
+
253
375
  ### Runtime collection registration (`addCollectionToSync`, `replaceSyncCollection`)
254
376
 
255
377
  Two methods to install / replace collection configs at runtime; both load the
@@ -346,6 +468,8 @@ const sinceMs = isLeader
346
468
  : Date.now() - syncedDb.followerSince()!.getTime();
347
469
  ```
348
470
 
471
+ ## 0.1.163 (2026-05-10)
472
+
349
473
  ### `onServerSyncWrite` callback
350
474
 
351
475
  Single-shot callback that fires once per `restInterface.updateCollections`
@@ -383,7 +507,7 @@ parity across **mongo + Dexie + in-mem** simultaneously after a partial
383
507
  `save({ postavke: [{_id: "P1", kolicina: 2}] })` over an existing
384
508
  `postavke[0] = {_id: "P1", opis: "postavka 1", kolicina: 1}`.
385
509
 
386
- ## 0.1.162
510
+ ## 0.1.162 (2026-05-10)
387
511
 
388
512
  ### Bracket-by-_id paths flow through server unchanged
389
513
 
@@ -438,7 +562,7 @@ Replaced with `applyDiffLocally(base, diff, id)`:
438
562
 
439
563
  `deleteByPath` is now a sibling export of `setByPath` in `computeDiff.ts`.
440
564
 
441
- ## 0.1.161
565
+ ## 0.1.161 (2026-05-10)
442
566
 
443
567
  ### Don't auto-stamp `_id` on bracket-array elements
444
568
 
@@ -449,7 +573,7 @@ preserved. This allows callers to mix:
449
573
  - Bracket-by-_id sub-field path: `update["postavke[<id>].field"] = value`
450
574
  in the same payload without the client mutating element identity.
451
575
 
452
- ## 0.1.160
576
+ ## 0.1.160 (2026-05-09)
453
577
 
454
578
  ### Composition changes emit precise paths (not full-array replace)
455
579
 
@@ -471,7 +595,7 @@ Pre-fix, composition change emitted full-array replace at `basePath`,
471
595
  which `mergeDirtyPath` Case 2 then dropped pending sub-field paths on
472
596
  the same parent — race-y data-loss strip pattern visible in production.
473
597
 
474
- ## 0.1.159
598
+ ## 0.1.159 (2026-05-09)
475
599
 
476
600
  ### Self-echo WS suppression for `_rev <= local._rev`
477
601
 
@@ -490,11 +614,11 @@ older snapshot because a self-echo WS arrived after writeback and
490
614
  overwrote in-mem with the server's `$set`-iterated copy of postavke
491
615
  (missing freshly-set `pop` and `navodilo` fields). Now in-mem is preserved.
492
616
 
493
- ## 0.1.158
617
+ ## 0.1.158 (2026-05-09)
494
618
 
495
619
  Internal version bump consolidating 0.1.157 fixes for production publish.
496
620
 
497
- ## 0.1.157
621
+ ## 0.1.157 (2026-05-08)
498
622
 
499
623
  ### Recursive server-managed metadata strip at upload boundary
500
624
 
@@ -531,7 +655,7 @@ stuck-dirty payload (mixing top-level full arrays with bracket paths)
531
655
  into Dexie's `_dirty_changes` and asserts upload succeeds without
532
656
  mongo path-conflict errors via a `MongoFaithfulRestInterface` mock.
533
657
 
534
- ## 0.1.156
658
+ ## 0.1.156 (2026-05-08)
535
659
 
536
660
  Three related fixes targeting **dirty-payload metadata leak** and
537
661
  **concurrent array merge corruption** observed in production
@@ -607,7 +731,7 @@ Temporary upload-time scrubber dropping legacy position-based array
607
731
  paths (`field.<digit>(.…)?`) when `serverRev > baseRev`. Marked for
608
732
  removal after ~2026-05-15 once all clients have re-synced.
609
733
 
610
- ## 0.1.155
734
+ ## 0.1.155 (2026-05-08)
611
735
 
612
736
  Two new `SyncedDbConfig` fields targeting **cross-device scope-exit**
613
737
  detection: situations where one device modifies a record so it no longer
@@ -664,7 +788,7 @@ wake.
664
788
  - `_collectScopeExitPlan` and single-collection `evictOutOfScopeRecords`
665
789
  both route through the new helper.
666
790
 
667
- ## Unreleased
791
+ ## 0.1.149 (2026-04-27)
668
792
 
669
793
  ### `SyncSource` flag in `I_InMemDb.saveMany` / `deleteManyByIds`
670
794
 
@@ -702,6 +826,8 @@ Tests: `test/syncSource.test.ts` (9 cases) covers initial / incremental
702
826
  / refresh propagation across all public write paths. `MockInMemDb`
703
827
  exposes `recordedCalls: RecordedInMemCall[]` for assertion.
704
828
 
829
+ ## 0.1.148 (2026-04-26)
830
+
705
831
  ### `uploadDirtyItems` follow-up pass — drain in-sync writes immediately
706
832
 
707
833
  Writes that land **during** a sync iteration had their
@@ -732,6 +858,8 @@ first pass — a follow-up failure does not roll back the first pass's
732
858
  already-cleared dirty entries; affected items are caught at the next
733
859
  sync tick (same retry semantics as before).
734
860
 
861
+ ## 0.1.147 (2026-04-25)
862
+
735
863
  ### Auto-eviction co-located with sync — one round-trip total
736
864
 
737
865
  When `evictStaleRecordsEveryHrs > 0` and the interval has elapsed, the
@@ -803,6 +931,8 @@ request carry multiple specs against the same collection without
803
931
  library always populates them. Downstream code that constructs mock
804
932
  literals (e.g. tests) needs the new fields.
805
933
 
934
+ ## 0.1.144 (2026-04-24)
935
+
806
936
  ### Fix: filtered-sync tombstone (scope-exit from other writers)
807
937
 
808
938
  When a collection has `syncConfig.query` (e.g. `{ status: { $ne: "obsolete" } }`)
@@ -837,6 +967,8 @@ predicate (no implicit server policy).
837
967
  `$nor` support, required for the negated query to evaluate against the test
838
968
  mock and for any client-side filtering that uses logical operators.
839
969
 
970
+ ## 0.1.142 (2026-04-21)
971
+
840
972
  ### `getDirtyMeta()` for lightweight dirty-state inspection
841
973
 
842
974
  - New `SyncedDb.getDirtyMeta()` returns dirty-entry meta (everything except the
@@ -867,6 +999,8 @@ Two contributing causes, both fixed:
867
999
  cache learns of them via the existing shared-Dexie reload path. Reload
868
1000
  broadcasts (post-full-sync) remain leader-only.
869
1001
 
1002
+ ## 0.1.141 (2026-04-21)
1003
+
870
1004
  ### BREAKING: Self-healing sync/reconnect lifecycle
871
1005
 
872
1006
  Fixes a class of bugs where the 60s auto-sync scheduler silently died after a
@@ -897,17 +1031,21 @@ tenants, 62–296 min of dead scheduler with dirty items accumulating.
897
1031
  `onForcedOffline: (reason) => log(reason)` → `onSyncFailed: (reason) => log(reason)`.
898
1032
  Signature is identical. No other callback changes.
899
1033
 
900
- - Add `refreshInBackground` `QueryOpts` option for `findById` / `findByIds`
901
- - Stale-while-revalidate: cache-hit returns local result immediately and
902
- triggers a background fetch that updates Dexie + in-mem through conflict
903
- resolution (`processCollectionServerData`).
904
- - Orthogonal to `referToServer` does not change miss behaviour. With
905
- defaults (`referToServer: true`) misses are still awaited; with
906
- `referToServer: false` misses return `null` and bg fetch loads them async.
907
- - Dedupes against `referToServer`: IDs fetched blockingly are NOT re-fetched
908
- in the background (no double-round-trip).
909
- - Noop when offline or on writeOnly collections.
910
- - Ignored on `find` / `findOne` (use `referToServer` there).
1034
+ ## 0.1.139 (2026-04-21)
1035
+
1036
+ ### `refreshInBackground` `QueryOpts` option for `findById` / `findByIds`
1037
+
1038
+ Stale-while-revalidate: cache-hit returns the local result immediately and
1039
+ triggers a background fetch that updates Dexie + in-mem through conflict
1040
+ resolution (`processCollectionServerData`).
1041
+
1042
+ - Orthogonal to `referToServer` — does not change miss behaviour. With
1043
+ defaults (`referToServer: true`) misses are still awaited; with
1044
+ `referToServer: false` misses return `null` and bg fetch loads them async.
1045
+ - Dedupes against `referToServer`: IDs fetched blockingly are NOT re-fetched
1046
+ in the background (no double-round-trip).
1047
+ - Noop when offline or on writeOnly collections.
1048
+ - Ignored on `find` / `findOne` (use `referToServer` there).
911
1049
 
912
1050
  ## 0.1.146 (2026-04-25)
913
1051
 
package/dist/index.js CHANGED
@@ -37,6 +37,21 @@ import Dexie2 from "dexie";
37
37
  import { ObjectId as ObjectId2 } from "bson";
38
38
 
39
39
  // src/utils/localQuery.ts
40
+ var regexCache = /* @__PURE__ */ new Map();
41
+ var REGEX_CACHE_MAX = 128;
42
+ function compileRegex(operand) {
43
+ if (operand instanceof RegExp) return operand;
44
+ const key = String(operand);
45
+ let r = regexCache.get(key);
46
+ if (r) return r;
47
+ r = new RegExp(key);
48
+ if (regexCache.size >= REGEX_CACHE_MAX) {
49
+ const oldest = regexCache.keys().next().value;
50
+ if (oldest !== void 0) regexCache.delete(oldest);
51
+ }
52
+ regexCache.set(key, r);
53
+ return r;
54
+ }
40
55
  function matchesQuery(item, query) {
41
56
  for (const [key, condition] of Object.entries(query)) {
42
57
  if (key === "$and") {
@@ -107,7 +122,7 @@ function matchesOperator(value, operator, operand) {
107
122
  case "$exists":
108
123
  return operand ? value !== void 0 : value === void 0;
109
124
  case "$regex": {
110
- const regex = operand instanceof RegExp ? operand : new RegExp(operand);
125
+ const regex = compileRegex(operand);
111
126
  if (typeof value === "string") return regex.test(value);
112
127
  if (Array.isArray(value)) {
113
128
  return value.some((v) => typeof v === "string" && regex.test(v));
@@ -750,14 +765,14 @@ var InMemManager = class {
750
765
  try {
751
766
  metadatas = config.onObjectsUpdated(items);
752
767
  } catch (err) {
753
- console.error("[InMem] onObjectsUpdated callback failed:", err);
768
+ console.error(`[InMem] onObjectsUpdated callback failed: ${err}`, err);
754
769
  return;
755
770
  }
756
771
  } else if (config.onObjectUpdated) {
757
772
  try {
758
773
  metadatas = items.map((item) => config.onObjectUpdated(item));
759
774
  } catch (err) {
760
- console.error("[InMem] onObjectUpdated callback failed:", err);
775
+ console.error(`[InMem] onObjectUpdated callback failed: ${err}`, err);
761
776
  return;
762
777
  }
763
778
  } else {
@@ -790,14 +805,14 @@ var InMemManager = class {
790
805
  try {
791
806
  metadatas = config.onObjectsUpdated(items);
792
807
  } catch (err) {
793
- console.error("[InMem] onObjectsUpdated callback failed:", err);
808
+ console.error(`[InMem] onObjectsUpdated callback failed: ${err}`, err);
794
809
  return;
795
810
  }
796
811
  } else if (config.onObjectUpdated) {
797
812
  try {
798
813
  metadatas = items.map((item) => config.onObjectUpdated(item));
799
814
  } catch (err) {
800
- console.error("[InMem] onObjectUpdated callback failed:", err);
815
+ console.error(`[InMem] onObjectUpdated callback failed: ${err}`, err);
801
816
  return;
802
817
  }
803
818
  } else {
@@ -1063,7 +1078,7 @@ var LeaderElectionManager = class {
1063
1078
  try {
1064
1079
  this.leaderReelectionChannel.postMessage({ type: "reelect-leader" });
1065
1080
  } catch (err) {
1066
- console.error("[LeaderElection] Failed to broadcast leader reelection:", err);
1081
+ console.error(`[LeaderElection] Failed to broadcast leader reelection: ${err}`, err);
1067
1082
  }
1068
1083
  }
1069
1084
  }
@@ -1074,7 +1089,7 @@ var LeaderElectionManager = class {
1074
1089
  try {
1075
1090
  this.callbacks.onBecameLeader();
1076
1091
  } catch (err) {
1077
- console.error("[LeaderElection] onBecameLeader callback failed:", err);
1092
+ console.error(`[LeaderElection] onBecameLeader callback failed: ${err}`, err);
1078
1093
  }
1079
1094
  }
1080
1095
  }
@@ -1085,7 +1100,7 @@ var LeaderElectionManager = class {
1085
1100
  try {
1086
1101
  this.callbacks.onLostLeadership();
1087
1102
  } catch (err) {
1088
- console.error("[LeaderElection] onLostLeadership callback failed:", err);
1103
+ console.error(`[LeaderElection] onLostLeadership callback failed: ${err}`, err);
1089
1104
  }
1090
1105
  }
1091
1106
  }
@@ -1094,7 +1109,7 @@ var LeaderElectionManager = class {
1094
1109
  try {
1095
1110
  this.callbacks.onInfrastructureError(type, message, error);
1096
1111
  } catch (err) {
1097
- console.error("[LeaderElection] onInfrastructureError callback failed:", err);
1112
+ console.error(`[LeaderElection] onInfrastructureError callback failed: ${err}`, err);
1098
1113
  }
1099
1114
  }
1100
1115
  }
@@ -1219,7 +1234,7 @@ var CrossTabSyncManager = class {
1219
1234
  try {
1220
1235
  this.metaUpdateChannel.postMessage(payload);
1221
1236
  } catch (err) {
1222
- console.error("[CrossTabSync] Failed to broadcast meta update:", err);
1237
+ console.error(`[CrossTabSync] Failed to broadcast meta update: ${err}`, err);
1223
1238
  }
1224
1239
  }
1225
1240
  this.pendingBroadcasts.clear();
@@ -1313,7 +1328,7 @@ var CrossTabSyncManager = class {
1313
1328
  };
1314
1329
  this.callbacks.onCrossTabSync(info);
1315
1330
  } catch (err) {
1316
- console.error("[CrossTabSync] onCrossTabSync callback failed:", err);
1331
+ console.error(`[CrossTabSync] onCrossTabSync callback failed: ${err}`, err);
1317
1332
  }
1318
1333
  }
1319
1334
  } catch (err) {
@@ -1333,7 +1348,7 @@ var CrossTabSyncManager = class {
1333
1348
  try {
1334
1349
  this.metaUpdateChannel.postMessage(payload);
1335
1350
  } catch (err) {
1336
- console.error("[CrossTabSync] Failed to broadcast reload:", err);
1351
+ console.error(`[CrossTabSync] Failed to broadcast reload: ${err}`, err);
1337
1352
  }
1338
1353
  }
1339
1354
  callOnInfrastructureError(type, message, error) {
@@ -1341,7 +1356,7 @@ var CrossTabSyncManager = class {
1341
1356
  try {
1342
1357
  this.callbacks.onInfrastructureError(type, message, error);
1343
1358
  } catch (err) {
1344
- console.error("[CrossTabSync] onInfrastructureError callback failed:", err);
1359
+ console.error(`[CrossTabSync] onInfrastructureError callback failed: ${err}`, err);
1345
1360
  }
1346
1361
  }
1347
1362
  }
@@ -1407,7 +1422,7 @@ var ConnectionManager = class {
1407
1422
  } else {
1408
1423
  this.deps.tryBecomeLeader();
1409
1424
  this.tryGoOnline().catch((err) => {
1410
- console.error("[Connection] Failed to go online after forceOffline release:", err);
1425
+ console.error(`[Connection] Failed to go online after forceOffline release: ${err}`, err);
1411
1426
  });
1412
1427
  }
1413
1428
  }
@@ -1431,7 +1446,7 @@ var ConnectionManager = class {
1431
1446
  "ping"
1432
1447
  );
1433
1448
  } catch (err) {
1434
- console.warn("[Connection] tryGoOnline: ping failed:", err);
1449
+ console.warn(`[Connection] tryGoOnline: ping failed: ${err}`, err);
1435
1450
  this.online = false;
1436
1451
  return;
1437
1452
  }
@@ -1449,7 +1464,7 @@ var ConnectionManager = class {
1449
1464
  try {
1450
1465
  await this.deps.sync("INITIAL SYNC");
1451
1466
  } catch (err) {
1452
- console.warn("[Connection] INITIAL SYNC after tryGoOnline failed (stays online):", err);
1467
+ console.warn(`[Connection] INITIAL SYNC after tryGoOnline failed (stays online): ${err}`, err);
1453
1468
  }
1454
1469
  } finally {
1455
1470
  this.tryGoOnlineInFlight = false;
@@ -1465,7 +1480,7 @@ var ConnectionManager = class {
1465
1480
  this.autoSyncTimer = setInterval(() => {
1466
1481
  if (this.forcedOffline || !this.online) return;
1467
1482
  this.deps.sync(`interval ${intervalMs}ms`).catch((err) => {
1468
- console.error("[Connection] Auto-sync failed:", err);
1483
+ console.error(`[Connection] Auto-sync failed: ${err}`, err);
1469
1484
  });
1470
1485
  }, intervalMs);
1471
1486
  }
@@ -1474,7 +1489,7 @@ var ConnectionManager = class {
1474
1489
  this.reconnectTimer = setInterval(() => {
1475
1490
  if (this.forcedOffline || this.online || this.tryGoOnlineInFlight) return;
1476
1491
  this.tryGoOnline().catch((err) => {
1477
- console.error("[Connection] Reconnect tryGoOnline failed:", err);
1492
+ console.error(`[Connection] Reconnect tryGoOnline failed: ${err}`, err);
1478
1493
  });
1479
1494
  }, retryMs);
1480
1495
  }
@@ -1558,7 +1573,7 @@ var ConnectionManager = class {
1558
1573
  try {
1559
1574
  this.callbacks.onInfrastructureError(type, message, error);
1560
1575
  } catch (err) {
1561
- console.error("[Connection] onInfrastructureError callback failed:", err);
1576
+ console.error(`[Connection] onInfrastructureError callback failed: ${err}`, err);
1562
1577
  }
1563
1578
  }
1564
1579
  }
@@ -1571,7 +1586,7 @@ var ConnectionManager = class {
1571
1586
  try {
1572
1587
  this.callbacks.onSyncFailed(reason);
1573
1588
  } catch (err) {
1574
- console.error("[Connection] onSyncFailed callback failed:", err);
1589
+ console.error(`[Connection] onSyncFailed callback failed: ${err}`, err);
1575
1590
  }
1576
1591
  }
1577
1592
  }
@@ -1583,7 +1598,7 @@ var ConnectionManager = class {
1583
1598
  try {
1584
1599
  this.callbacks.onWsConnect();
1585
1600
  } catch (err) {
1586
- console.error("[Connection] onWsConnect callback failed:", err);
1601
+ console.error(`[Connection] onWsConnect callback failed: ${err}`, err);
1587
1602
  }
1588
1603
  }
1589
1604
  }
@@ -1595,7 +1610,7 @@ var ConnectionManager = class {
1595
1610
  try {
1596
1611
  this.callbacks.onWsDisconnect(reason);
1597
1612
  } catch (err) {
1598
- console.error("[Connection] onWsDisconnect callback failed:", err);
1613
+ console.error(`[Connection] onWsDisconnect callback failed: ${err}`, err);
1599
1614
  }
1600
1615
  }
1601
1616
  this.reportInfrastructureError(
@@ -1611,7 +1626,7 @@ var ConnectionManager = class {
1611
1626
  try {
1612
1627
  this.callbacks.onWsReconnect(attempt);
1613
1628
  } catch (err) {
1614
- console.error("[Connection] onWsReconnect callback failed:", err);
1629
+ console.error(`[Connection] onWsReconnect callback failed: ${err}`, err);
1615
1630
  }
1616
1631
  }
1617
1632
  }
@@ -2710,7 +2725,7 @@ var PendingChangesManager = class {
2710
2725
  calledFrom: pending.calledFrom
2711
2726
  });
2712
2727
  } catch (err) {
2713
- console.error("[PendingChanges] onDexieWriteRequest callback failed:", err);
2728
+ console.error(`[PendingChanges] onDexieWriteRequest callback failed: ${err}`, err);
2714
2729
  }
2715
2730
  }
2716
2731
  if (existing) {
@@ -2725,7 +2740,7 @@ var PendingChangesManager = class {
2725
2740
  // ensure _id is after spread
2726
2741
  });
2727
2742
  if (typeof insertData._id === "object") {
2728
- console.error(`[PendingChanges] Dexie: _id is object type in ${pending.collection}:`, typeof insertData._id, insertData._id);
2743
+ console.error(`[PendingChanges] Dexie: _id is object type (${typeof insertData._id}) in ${pending.collection}`, insertData._id);
2729
2744
  }
2730
2745
  await this.deps.dexieDb.insert(pending.collection, insertData);
2731
2746
  }
@@ -2739,7 +2754,7 @@ var PendingChangesManager = class {
2739
2754
  calledFrom: pending.calledFrom
2740
2755
  });
2741
2756
  } catch (err) {
2742
- console.error("[PendingChanges] onDexieWriteResult callback failed:", err);
2757
+ console.error(`[PendingChanges] onDexieWriteResult callback failed: ${err}`, err);
2743
2758
  }
2744
2759
  }
2745
2760
  clearPendingWrite(this.tenant, pending.collection, pending.id);
@@ -2753,12 +2768,12 @@ var PendingChangesManager = class {
2753
2768
  calledFrom: pending.calledFrom
2754
2769
  });
2755
2770
  } catch (err) {
2756
- console.error("[PendingChanges] onLocalstorageWriteResult callback failed:", err);
2771
+ console.error(`[PendingChanges] onLocalstorageWriteResult callback failed: ${err}`, err);
2757
2772
  }
2758
2773
  }
2759
2774
  this.scheduleRestUpload();
2760
2775
  } catch (err) {
2761
- console.error("[PendingChanges] Failed to write to Dexie:", err);
2776
+ console.error(`[PendingChanges] Failed to write to Dexie: ${err}`, err);
2762
2777
  if (this.callbacks.onDexieWriteResult) {
2763
2778
  try {
2764
2779
  this.callbacks.onDexieWriteResult({
@@ -2770,7 +2785,7 @@ var PendingChangesManager = class {
2770
2785
  calledFrom: pending.calledFrom
2771
2786
  });
2772
2787
  } catch (callbackErr) {
2773
- console.error("[PendingChanges] onDexieWriteResult callback failed:", callbackErr);
2788
+ console.error(`[PendingChanges] onDexieWriteResult callback failed: ${callbackErr}`, callbackErr);
2774
2789
  }
2775
2790
  }
2776
2791
  const newRetryCount = pending.retryCount + 1;
@@ -2812,7 +2827,7 @@ var PendingChangesManager = class {
2812
2827
  try {
2813
2828
  await this.deps.uploadDirtyItems();
2814
2829
  } catch (err) {
2815
- console.error("[PendingChanges] REST upload failed:", err);
2830
+ console.error(`[PendingChanges] REST upload failed: ${err}`, err);
2816
2831
  } finally {
2817
2832
  this.isUploadingToRest = false;
2818
2833
  resolveUpload();
@@ -3221,7 +3236,7 @@ var _SyncEngine = class _SyncEngine {
3221
3236
  });
3222
3237
  } catch (err) {
3223
3238
  const reason = err instanceof Error ? err.message : String(err);
3224
- console.error("[SyncEngine] Sync failed:", err);
3239
+ console.error(`[SyncEngine] Sync failed: ${err}`, err);
3225
3240
  this.deps.onSyncFailed(`Sync failed: ${reason}`);
3226
3241
  this.callOnSyncEnd({
3227
3242
  durationMs: Date.now() - startTime,
@@ -3253,6 +3268,7 @@ var _SyncEngine = class _SyncEngine {
3253
3268
  const skipped = [];
3254
3269
  const ids = dirtyChanges.map((dc) => dc._id);
3255
3270
  const fullItems = await this.dexieDb.getByIds(collectionName, ids);
3271
+ const orphanReconstructed = [];
3256
3272
  for (let i = 0; i < fullItems.length; i++) {
3257
3273
  const fullItem = fullItems[i];
3258
3274
  const id = ids[i];
@@ -3268,7 +3284,7 @@ var _SyncEngine = class _SyncEngine {
3268
3284
  const delta = dirtyChangesMap.get(String(id));
3269
3285
  if (delta) {
3270
3286
  const reconstructed = __spreadProps(__spreadValues({}, delta), { _id: id });
3271
- await this.dexieDb.save(collectionName, id, reconstructed);
3287
+ orphanReconstructed.push(reconstructed);
3272
3288
  updates.push({ _id: id, delta });
3273
3289
  } else {
3274
3290
  skipped.push({ _id: String(id), reason: "no-delta-for-orphan" });
@@ -3277,6 +3293,9 @@ var _SyncEngine = class _SyncEngine {
3277
3293
  skipped.push({ _id: "<null>", reason: "no-fullitem-no-id" });
3278
3294
  }
3279
3295
  }
3296
+ if (orphanReconstructed.length > 0) {
3297
+ await this.dexieDb.saveMany(collectionName, orphanReconstructed);
3298
+ }
3280
3299
  if (updates.length === 0) {
3281
3300
  console.warn(
3282
3301
  `[SyncEngine] uploadDirtyItems: ${collectionName} has ${dirtyChanges.length} dirty entries but 0 resolvable items`,
@@ -3294,7 +3313,7 @@ var _SyncEngine = class _SyncEngine {
3294
3313
  timestamp: /* @__PURE__ */ new Date()
3295
3314
  });
3296
3315
  } catch (err) {
3297
- console.error("[SyncEngine] onUploadSkip callback failed:", err);
3316
+ console.error(`[SyncEngine] onUploadSkip callback failed: ${err}`, err);
3298
3317
  }
3299
3318
  }
3300
3319
  continue;
@@ -3311,7 +3330,7 @@ var _SyncEngine = class _SyncEngine {
3311
3330
  timestamp: /* @__PURE__ */ new Date()
3312
3331
  });
3313
3332
  } catch (err) {
3314
- console.error("[SyncEngine] onUploadSkip callback failed:", err);
3333
+ console.error(`[SyncEngine] onUploadSkip callback failed: ${err}`, err);
3315
3334
  }
3316
3335
  }
3317
3336
  const mappedUpdates = [];
@@ -3536,14 +3555,20 @@ var _SyncEngine = class _SyncEngine {
3536
3555
  );
3537
3556
  }
3538
3557
  }
3539
- const sentIds = /* @__PURE__ */ new Set([
3540
- ...collectionBatches.flat().filter((b) => b.collection === collection).flatMap((b) => [
3541
- ...b.batch.updates.map((u) => String(u._id)),
3542
- ...b.batch.deletes.map((d) => String(d._id))
3543
- ])
3544
- ]);
3545
- const ackIds = new Set(allSuccessIds.map(String));
3546
- const unacked = [...sentIds].filter((id) => !ackIds.has(id));
3558
+ const sentIds = /* @__PURE__ */ new Set();
3559
+ for (const batch of collectionBatches) {
3560
+ for (const b of batch) {
3561
+ if (b.collection !== collection) continue;
3562
+ for (const u of b.batch.updates) sentIds.add(String(u._id));
3563
+ for (const d of b.batch.deletes) sentIds.add(String(d._id));
3564
+ }
3565
+ }
3566
+ const ackIds = /* @__PURE__ */ new Set();
3567
+ for (const id of allSuccessIds) ackIds.add(String(id));
3568
+ const unacked = [];
3569
+ for (const id of sentIds) {
3570
+ if (!ackIds.has(id)) unacked.push(id);
3571
+ }
3547
3572
  if (unacked.length > 0) {
3548
3573
  console.warn(
3549
3574
  `[SyncEngine] uploadDirtyItems: ${collection}: ${unacked.length} items sent but not acknowledged:`,
@@ -3780,7 +3805,7 @@ var _SyncEngine = class _SyncEngine {
3780
3805
  timestamp: /* @__PURE__ */ new Date()
3781
3806
  });
3782
3807
  } catch (err) {
3783
- console.error("[SyncEngine] onConflictResolved callback failed:", err);
3808
+ console.error(`[SyncEngine] onConflictResolved callback failed: ${err}`, err);
3784
3809
  }
3785
3810
  }
3786
3811
  return resolved;
@@ -3794,7 +3819,7 @@ var _SyncEngine = class _SyncEngine {
3794
3819
  try {
3795
3820
  fn(info);
3796
3821
  } catch (err) {
3797
- console.error("[SyncEngine] Callback failed:", err);
3822
+ console.error(`[SyncEngine] Callback failed: ${err}`, err);
3798
3823
  }
3799
3824
  }
3800
3825
  }
@@ -3803,7 +3828,7 @@ var _SyncEngine = class _SyncEngine {
3803
3828
  try {
3804
3829
  this.callbacks.onSyncStart(info);
3805
3830
  } catch (err) {
3806
- console.error("[SyncEngine] onSyncStart callback failed:", err);
3831
+ console.error(`[SyncEngine] onSyncStart callback failed: ${err}`, err);
3807
3832
  }
3808
3833
  }
3809
3834
  }
@@ -3812,7 +3837,7 @@ var _SyncEngine = class _SyncEngine {
3812
3837
  try {
3813
3838
  this.callbacks.onSyncEnd(info);
3814
3839
  } catch (err) {
3815
- console.error("[SyncEngine] onSyncEnd callback failed:", err);
3840
+ console.error(`[SyncEngine] onSyncEnd callback failed: ${err}`, err);
3816
3841
  }
3817
3842
  }
3818
3843
  }
@@ -3825,7 +3850,7 @@ var _SyncEngine = class _SyncEngine {
3825
3850
  calledFrom
3826
3851
  });
3827
3852
  } catch (err) {
3828
- console.error("[SyncEngine] onFindNewerManyCall callback failed:", err);
3853
+ console.error(`[SyncEngine] onFindNewerManyCall callback failed: ${err}`, err);
3829
3854
  }
3830
3855
  }
3831
3856
  }
@@ -3844,7 +3869,7 @@ var _SyncEngine = class _SyncEngine {
3844
3869
  ttfbMs: metrics == null ? void 0 : metrics.ttfbMs
3845
3870
  });
3846
3871
  } catch (err) {
3847
- console.error("[SyncEngine] onFindNewerManyResult callback failed:", err);
3872
+ console.error(`[SyncEngine] onFindNewerManyResult callback failed: ${err}`, err);
3848
3873
  }
3849
3874
  }
3850
3875
  }
@@ -3857,7 +3882,7 @@ var _SyncEngine = class _SyncEngine {
3857
3882
  calledFrom
3858
3883
  });
3859
3884
  } catch (err) {
3860
- console.error("[SyncEngine] onServerWriteRequest callback failed:", err);
3885
+ console.error(`[SyncEngine] onServerWriteRequest callback failed: ${err}`, err);
3861
3886
  }
3862
3887
  }
3863
3888
  }
@@ -3872,7 +3897,7 @@ var _SyncEngine = class _SyncEngine {
3872
3897
  calledFrom
3873
3898
  });
3874
3899
  } catch (err) {
3875
- console.error("[SyncEngine] onServerWriteResult callback failed:", err);
3900
+ console.error(`[SyncEngine] onServerWriteResult callback failed: ${err}`, err);
3876
3901
  }
3877
3902
  }
3878
3903
  }
@@ -3893,7 +3918,7 @@ var _SyncEngine = class _SyncEngine {
3893
3918
  timestamp
3894
3919
  });
3895
3920
  } catch (err) {
3896
- console.error("[SyncEngine] onServerSyncWrite callback failed:", err);
3921
+ console.error(`[SyncEngine] onServerSyncWrite callback failed: ${err}`, err);
3897
3922
  }
3898
3923
  }
3899
3924
  };
@@ -3977,39 +4002,57 @@ var ServerUpdateHandler = class {
3977
4002
  deletes.push(item.data);
3978
4003
  }
3979
4004
  }
3980
- for (const serverItem of inserts) {
3981
- await this.handleServerItemInsert(collectionName, serverItem);
3982
- updatedIds.push(String(serverItem._id));
4005
+ if (inserts.length > 0) {
4006
+ await Promise.all(
4007
+ inserts.map(
4008
+ (serverItem) => this.handleServerItemInsert(collectionName, serverItem)
4009
+ )
4010
+ );
4011
+ for (const serverItem of inserts) {
4012
+ updatedIds.push(String(serverItem._id));
4013
+ }
3983
4014
  }
3984
4015
  if (updates.length > 0) {
3985
4016
  const updateIds = updates.map((u) => u._id);
3986
4017
  const localItems = await this.dexieDb.getByIds(collectionName, updateIds);
3987
4018
  const missingIds = [];
4019
+ const updatePromises = [];
3988
4020
  for (let i = 0; i < updates.length; i++) {
3989
4021
  const deltaData = updates[i];
3990
4022
  const localItem = localItems[i];
3991
4023
  if (localItem) {
3992
- await this.handleServerItemUpdate(collectionName, localItem, deltaData);
4024
+ updatePromises.push(
4025
+ this.handleServerItemUpdate(collectionName, localItem, deltaData)
4026
+ );
3993
4027
  updatedIds.push(String(deltaData._id));
3994
4028
  } else {
3995
4029
  missingIds.push(deltaData._id);
3996
4030
  }
3997
4031
  }
4032
+ if (updatePromises.length > 0) await Promise.all(updatePromises);
3998
4033
  if (missingIds.length > 0) {
3999
4034
  const fullItems = await this.restInterface.findByIds(
4000
4035
  collectionName,
4001
4036
  missingIds
4002
4037
  );
4038
+ const insertPromises = [];
4003
4039
  for (const fullItem of fullItems) {
4004
4040
  if (!fullItem) continue;
4005
- await this.handleServerItemInsert(collectionName, fullItem);
4041
+ insertPromises.push(
4042
+ this.handleServerItemInsert(collectionName, fullItem)
4043
+ );
4006
4044
  updatedIds.push(String(fullItem._id));
4007
4045
  }
4046
+ if (insertPromises.length > 0) await Promise.all(insertPromises);
4008
4047
  }
4009
4048
  }
4010
- for (const deleteData of deletes) {
4011
- await this.handleServerItemDelete(collectionName, deleteData._id);
4012
- updatedIds.push(String(deleteData._id));
4049
+ if (deletes.length > 0) {
4050
+ await Promise.all(
4051
+ deletes.map((d) => this.handleServerItemDelete(collectionName, d._id))
4052
+ );
4053
+ for (const deleteData of deletes) {
4054
+ updatedIds.push(String(deleteData._id));
4055
+ }
4013
4056
  }
4014
4057
  break;
4015
4058
  }
@@ -4158,7 +4201,7 @@ var ServerUpdateHandler = class {
4158
4201
  timestamp: /* @__PURE__ */ new Date()
4159
4202
  });
4160
4203
  } catch (err) {
4161
- console.error("[ServerUpdateHandler] onWsNotification callback failed:", err);
4204
+ console.error(`[ServerUpdateHandler] onWsNotification callback failed: ${err}`, err);
4162
4205
  }
4163
4206
  }
4164
4207
  }
@@ -4240,7 +4283,7 @@ var WakeSyncManager = class {
4240
4283
  timestamp: /* @__PURE__ */ new Date()
4241
4284
  });
4242
4285
  } catch (err) {
4243
- console.error("[WakeSync] onWakeSync callback failed:", err);
4286
+ console.error(`[WakeSync] onWakeSync callback failed: ${err}`, err);
4244
4287
  }
4245
4288
  }
4246
4289
  this.deps.sync(`wake-sync:${trigger}`).catch((err) => {
@@ -4313,7 +4356,7 @@ var NetworkStatusManager = class {
4313
4356
  try {
4314
4357
  this.callbacks.onBrowserNetworkChange(info);
4315
4358
  } catch (err) {
4316
- console.error("[NetworkStatus] onBrowserNetworkChange callback failed:", err);
4359
+ console.error(`[NetworkStatus] onBrowserNetworkChange callback failed: ${err}`, err);
4317
4360
  }
4318
4361
  }
4319
4362
  if (finalOnlineState) {
@@ -4321,7 +4364,7 @@ var NetworkStatusManager = class {
4321
4364
  try {
4322
4365
  this.callbacks.onBrowserOnline();
4323
4366
  } catch (err) {
4324
- console.error("[NetworkStatus] onBrowserOnline callback failed:", err);
4367
+ console.error(`[NetworkStatus] onBrowserOnline callback failed: ${err}`, err);
4325
4368
  }
4326
4369
  }
4327
4370
  } else {
@@ -4329,12 +4372,12 @@ var NetworkStatusManager = class {
4329
4372
  try {
4330
4373
  this.callbacks.onBrowserOffline();
4331
4374
  } catch (err) {
4332
- console.error("[NetworkStatus] onBrowserOffline callback failed:", err);
4375
+ console.error(`[NetworkStatus] onBrowserOffline callback failed: ${err}`, err);
4333
4376
  }
4334
4377
  }
4335
4378
  }
4336
4379
  this.deps.setOnline(finalOnlineState).catch((err) => {
4337
- console.error("[NetworkStatus] Failed to set online status:", err);
4380
+ console.error(`[NetworkStatus] Failed to set online status: ${err}`, err);
4338
4381
  });
4339
4382
  }, this.debounceMs);
4340
4383
  }
@@ -4346,6 +4389,7 @@ var _SyncedDb = class _SyncedDb {
4346
4389
  this.collections = /* @__PURE__ */ new Map();
4347
4390
  // State
4348
4391
  this.initialized = false;
4392
+ this.closed = false;
4349
4393
  this.syncing = false;
4350
4394
  this.syncLock = false;
4351
4395
  this.wsUpdateQueue = [];
@@ -4399,14 +4443,14 @@ var _SyncedDb = class _SyncedDb {
4399
4443
  onBecameLeader: () => {
4400
4444
  if (this.initialized && !this.connectionManager.isOnline() && !this.connectionManager.isForcedOffline()) {
4401
4445
  this.connectionManager.tryGoOnline().catch((err) => {
4402
- console.error("[SyncedDb] tryGoOnline on becameLeader failed:", err);
4446
+ console.error(`[SyncedDb] tryGoOnline on becameLeader failed: ${err}`, err);
4403
4447
  });
4404
4448
  }
4405
4449
  if (config.onBecameLeader) {
4406
4450
  try {
4407
4451
  config.onBecameLeader();
4408
4452
  } catch (err) {
4409
- console.error("[SyncedDb] onBecameLeader callback failed:", err);
4453
+ console.error(`[SyncedDb] onBecameLeader callback failed: ${err}`, err);
4410
4454
  }
4411
4455
  }
4412
4456
  },
@@ -4740,7 +4784,7 @@ var _SyncedDb = class _SyncedDb {
4740
4784
  try {
4741
4785
  this.onDatabaseCreated();
4742
4786
  } catch (err) {
4743
- console.error("[SyncedDb] onDatabaseCreated callback failed:", err);
4787
+ console.error(`[SyncedDb] onDatabaseCreated callback failed: ${err}`, err);
4744
4788
  }
4745
4789
  }
4746
4790
  await this.pendingChanges.recoverPendingWrites();
@@ -4798,7 +4842,7 @@ var _SyncedDb = class _SyncedDb {
4798
4842
  this.visibilityFlushHandler = () => {
4799
4843
  if (document.visibilityState !== "hidden") return;
4800
4844
  this.flushToServer("visibility-hidden").catch((err) => {
4801
- console.warn("[SyncedDb] flushToServer on visibility-hidden failed:", err == null ? void 0 : err.message);
4845
+ console.warn(`[SyncedDb] flushToServer on visibility-hidden failed: ${err == null ? void 0 : err.message}`, err);
4802
4846
  });
4803
4847
  };
4804
4848
  document.addEventListener("visibilitychange", this.visibilityFlushHandler);
@@ -4884,6 +4928,8 @@ var _SyncedDb = class _SyncedDb {
4884
4928
  }
4885
4929
  async close() {
4886
4930
  var _a, _b;
4931
+ if (this.closed) return;
4932
+ this.closed = true;
4887
4933
  this.leaderElection.setClosing(true);
4888
4934
  this.pendingChanges.cancelRestUploadTimer();
4889
4935
  this.connectionManager.stopTimers();
@@ -4942,7 +4988,7 @@ var _SyncedDb = class _SyncedDb {
4942
4988
  this.assertCollection(collection);
4943
4989
  if (!id) {
4944
4990
  const err = new Error(`[SyncedDb] findById ${collection} no id ${id}`);
4945
- console.error(err);
4991
+ console.error(`[SyncedDb] findById ${collection}: no id provided`, err);
4946
4992
  return null;
4947
4993
  }
4948
4994
  id = this.normalizeId(id, "findById", collection);
@@ -5516,11 +5562,11 @@ var _SyncedDb = class _SyncedDb {
5516
5562
  const now = /* @__PURE__ */ new Date();
5517
5563
  if (!this._lastFullSyncDate) {
5518
5564
  this._setLastInitialSync(now).catch((err) => {
5519
- console.error("[SyncedDb] Failed to persist lastInitialSync:", err);
5565
+ console.error(`[SyncedDb] Failed to persist lastInitialSync: ${err}`, err);
5520
5566
  });
5521
5567
  }
5522
5568
  this._setLastFullSync(now).catch((err) => {
5523
- console.error("[SyncedDb] Failed to persist lastFullSync:", err);
5569
+ console.error(`[SyncedDb] Failed to persist lastFullSync: ${err}`, err);
5524
5570
  });
5525
5571
  }
5526
5572
  } catch (err) {
@@ -5539,7 +5585,7 @@ var _SyncedDb = class _SyncedDb {
5539
5585
  );
5540
5586
  await this._persistEvictionTimestamp();
5541
5587
  } catch (err) {
5542
- console.error("[SyncedDb] [evict] phase 3 failed:", err);
5588
+ console.error(`[SyncedDb] [evict] phase 3 failed: ${err}`, err);
5543
5589
  }
5544
5590
  }
5545
5591
  }
@@ -6275,7 +6321,7 @@ var _SyncedDb = class _SyncedDb {
6275
6321
  try {
6276
6322
  fn(info);
6277
6323
  } catch (err) {
6278
- console.error("[SyncedDb] Callback failed:", err);
6324
+ console.error(`[SyncedDb] Callback failed: ${err}`, err);
6279
6325
  }
6280
6326
  }
6281
6327
  }
@@ -9888,7 +9934,7 @@ var Ebus2ProxyServerUpdateNotifier = class {
9888
9934
  try {
9889
9935
  callback();
9890
9936
  } catch (err) {
9891
- console.error("[Ebus2ProxyNotifier] onWsConnect callback failed:", err);
9937
+ console.error(`[Ebus2ProxyNotifier] onWsConnect callback failed: ${err}`, err);
9892
9938
  }
9893
9939
  }
9894
9940
  }
@@ -9903,7 +9949,7 @@ var Ebus2ProxyServerUpdateNotifier = class {
9903
9949
  try {
9904
9950
  callback(reason);
9905
9951
  } catch (err) {
9906
- console.error("[Ebus2ProxyNotifier] onWsDisconnect callback failed:", err);
9952
+ console.error(`[Ebus2ProxyNotifier] onWsDisconnect callback failed: ${err}`, err);
9907
9953
  }
9908
9954
  }
9909
9955
  }
@@ -9912,7 +9958,7 @@ var Ebus2ProxyServerUpdateNotifier = class {
9912
9958
  }
9913
9959
  }
9914
9960
  handleError(event) {
9915
- console.error("[Ebus2ProxyNotifier] WebSocket error:", event);
9961
+ console.error(`[Ebus2ProxyNotifier] WebSocket error: ${event.type}`, event);
9916
9962
  }
9917
9963
  handleMessage(event) {
9918
9964
  try {
@@ -9941,11 +9987,11 @@ var Ebus2ProxyServerUpdateNotifier = class {
9941
9987
  }
9942
9988
  break;
9943
9989
  case "error":
9944
- console.error("[Ebus2ProxyNotifier] WebSocket server error:", message.error);
9990
+ console.error(`[Ebus2ProxyNotifier] WebSocket server error: ${message.error}`, message.error);
9945
9991
  break;
9946
9992
  }
9947
9993
  } catch (err) {
9948
- console.error("[Ebus2ProxyNotifier] Failed to parse WebSocket message:", err);
9994
+ console.error(`[Ebus2ProxyNotifier] Failed to parse WebSocket message: ${err}`, err);
9949
9995
  }
9950
9996
  }
9951
9997
  handleChannelMessage(message) {
@@ -9953,7 +9999,7 @@ var Ebus2ProxyServerUpdateNotifier = class {
9953
9999
  try {
9954
10000
  this.onServicesChange(message.data);
9955
10001
  } catch (err) {
9956
- console.error("[Ebus2ProxyNotifier] onServicesChange callback failed:", err);
10002
+ console.error(`[Ebus2ProxyNotifier] onServicesChange callback failed: ${err}`, err);
9957
10003
  }
9958
10004
  return;
9959
10005
  }
@@ -9970,14 +10016,14 @@ var Ebus2ProxyServerUpdateNotifier = class {
9970
10016
  try {
9971
10017
  this.onWsNotification(payload);
9972
10018
  } catch (err) {
9973
- console.error("[Ebus2ProxyNotifier] onWsNotification callback failed:", err);
10019
+ console.error(`[Ebus2ProxyNotifier] onWsNotification callback failed: ${err}`, err);
9974
10020
  }
9975
10021
  }
9976
10022
  for (const callback of this.callbacks) {
9977
10023
  try {
9978
10024
  callback(payload);
9979
10025
  } catch (err) {
9980
- console.error("[Ebus2ProxyNotifier] ServerUpdateCallback failed:", err);
10026
+ console.error(`[Ebus2ProxyNotifier] ServerUpdateCallback failed: ${err}`, err);
9981
10027
  }
9982
10028
  }
9983
10029
  }
@@ -9996,14 +10042,14 @@ var Ebus2ProxyServerUpdateNotifier = class {
9996
10042
  try {
9997
10043
  callback(this.reconnectAttempt);
9998
10044
  } catch (err) {
9999
- console.error("[Ebus2ProxyNotifier] onWsReconnect callback failed:", err);
10045
+ console.error(`[Ebus2ProxyNotifier] onWsReconnect callback failed: ${err}`, err);
10000
10046
  }
10001
10047
  }
10002
10048
  this.reconnectTimer = setTimeout(() => {
10003
10049
  this.reconnectTimer = void 0;
10004
10050
  if (this.shouldReconnect && !this.forcedOffline) {
10005
10051
  this.createWebSocket().catch((err) => {
10006
- console.error("[Ebus2ProxyNotifier] Reconnection failed:", err);
10052
+ console.error(`[Ebus2ProxyNotifier] Reconnection failed: ${err}`, err);
10007
10053
  this.currentReconnectDelay = Math.min(
10008
10054
  this.currentReconnectDelay * 2,
10009
10055
  this.maxReconnectDelayMs
@@ -24,6 +24,7 @@ export declare class SyncedDb implements I_SyncedDb {
24
24
  private readonly wakeSync?;
25
25
  private readonly networkStatus?;
26
26
  private initialized;
27
+ private closed;
27
28
  private syncing;
28
29
  private syncLock;
29
30
  private wsUpdateQueue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cry-synced-db-client",
3
- "version": "0.1.176",
3
+ "version": "0.1.178",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",