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 +159 -21
- package/dist/index.js +130 -84
- package/dist/src/db/SyncedDb.d.ts +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,91 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
-
##
|
|
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
|
-
##
|
|
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
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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}
|
|
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(
|
|
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(
|
|
2771
|
+
console.error(`[PendingChanges] onLocalstorageWriteResult callback failed: ${err}`, err);
|
|
2757
2772
|
}
|
|
2758
2773
|
}
|
|
2759
2774
|
this.scheduleRestUpload();
|
|
2760
2775
|
} catch (err) {
|
|
2761
|
-
console.error(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
3981
|
-
await
|
|
3982
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4011
|
-
await
|
|
4012
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
5565
|
+
console.error(`[SyncedDb] Failed to persist lastInitialSync: ${err}`, err);
|
|
5520
5566
|
});
|
|
5521
5567
|
}
|
|
5522
5568
|
this._setLastFullSync(now).catch((err) => {
|
|
5523
|
-
console.error(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
9990
|
+
console.error(`[Ebus2ProxyNotifier] WebSocket server error: ${message.error}`, message.error);
|
|
9945
9991
|
break;
|
|
9946
9992
|
}
|
|
9947
9993
|
} catch (err) {
|
|
9948
|
-
console.error(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
10052
|
+
console.error(`[Ebus2ProxyNotifier] Reconnection failed: ${err}`, err);
|
|
10007
10053
|
this.currentReconnectDelay = Math.min(
|
|
10008
10054
|
this.currentReconnectDelay * 2,
|
|
10009
10055
|
this.maxReconnectDelayMs
|