cry-synced-db-client 0.1.176 → 0.1.177
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 +130 -21
- package/dist/index.js +64 -18
- package/dist/src/db/SyncedDb.d.ts +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,62 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.1.177 (2026-05-13)
|
|
4
|
+
|
|
5
|
+
Hot-path micro-optimizations from a ts-coding skill audit. Five small wins,
|
|
6
|
+
each verified independently against the actual control flow before applying.
|
|
7
|
+
|
|
8
|
+
### `$regex` operand cached per pattern
|
|
9
|
+
|
|
10
|
+
`matchesOperator` for `$regex` used to call `new RegExp(operand)` on every
|
|
11
|
+
record × operator invocation. For in-mem `find` over a large collection
|
|
12
|
+
this recompiled the same pattern thousands of times. Now a module-level
|
|
13
|
+
`Map<string, RegExp>` caches compiled regexes by operand string with a
|
|
14
|
+
bounded FIFO eviction (128 entries) so dynamic patterns don't leak.
|
|
15
|
+
Implementation in `src/utils/localQuery.ts:compileRegex`.
|
|
16
|
+
|
|
17
|
+
### `SyncedDb.close()` is idempotent
|
|
18
|
+
|
|
19
|
+
Added a `this.closed` guard that early-returns on the second call. Most
|
|
20
|
+
disposal calls inside `close()` were already idempotent (timer clears,
|
|
21
|
+
listener removes), but `crossTabSync.dispose()`, `wakeSync.dispose()`,
|
|
22
|
+
`networkStatus.dispose()`, and `serverUpdateNotifier.dispose?.()` are not
|
|
23
|
+
internally guarded — calling them twice could throw. Now safe.
|
|
24
|
+
|
|
25
|
+
### Orphan dirty reconstruction batches into `saveMany`
|
|
26
|
+
|
|
27
|
+
`SyncEngine.uploadDirtyItems` reconstructs missing main rows when a dirty
|
|
28
|
+
change exists but the main entry was lost (e.g. debounced write didn't
|
|
29
|
+
flush before reload). Previously called `await dexieDb.save(...)` per
|
|
30
|
+
orphan inside the per-item loop. Now collects reconstructed entities and
|
|
31
|
+
issues one `saveMany` at the end. Rare path — minor improvement, no
|
|
32
|
+
behavior change.
|
|
33
|
+
|
|
34
|
+
### `sentIds` warning path drops intermediate spreads
|
|
35
|
+
|
|
36
|
+
`SyncEngine.uploadDirtyItems` builds a `sentIds` Set for the
|
|
37
|
+
"unacknowledged items" warning. Previously used
|
|
38
|
+
`new Set([...batches.flat().filter(...).flatMap(...)])` (multiple array
|
|
39
|
+
allocations) and `[...sentIds].filter(...)` (one more spread). Now a
|
|
40
|
+
single for-of pass into a Set, plus a direct iteration to build `unacked`.
|
|
41
|
+
|
|
42
|
+
### `ServerUpdateHandler` batch case now concurrent
|
|
43
|
+
|
|
44
|
+
WebSocket batch server-update notifications previously processed each
|
|
45
|
+
insert / update / delete with sequential `await` calls. Each per-item
|
|
46
|
+
handler operates on an independent `_id`; Dexie reads/writes on different
|
|
47
|
+
keys don't conflict, so replaced the loops with `Promise.all` over the
|
|
48
|
+
per-item handlers. On a batch of N items this turns N sequential IDB
|
|
49
|
+
round-trips into N concurrent ones — the dominant latency win for any
|
|
50
|
+
tab subscribed to a busy collection.
|
|
51
|
+
|
|
52
|
+
The deeper "single `getByIds` + `saveMany` per batch" refactor was
|
|
53
|
+
deferred: the per-item update handler has 5+ semantically distinct
|
|
54
|
+
branches (self-echo A/B/C, pending change merge, dirty merge, clean
|
|
55
|
+
meta-only) and duplicating that logic for the batch path carries
|
|
56
|
+
re-implementation risk that isn't justified by the additional latency
|
|
57
|
+
savings over `Promise.all`.
|
|
58
|
+
|
|
59
|
+
## 0.1.176 (2026-05-13)
|
|
4
60
|
|
|
5
61
|
### Passive transport metrics on `findNewerManyStream` round-trip (rdb2)
|
|
6
62
|
|
|
@@ -104,6 +160,8 @@ Live test (localhost, warm, 20 samples): WS p50 0.30 ms, E2E p50
|
|
|
104
160
|
4.11 ms. Mock-only unit tests in `test/measureRtt.test.ts` (7 cases —
|
|
105
161
|
delegation, propagated rejection, no-notifier error, shape sanity).
|
|
106
162
|
|
|
163
|
+
## 0.1.175 (2026-05-13)
|
|
164
|
+
|
|
107
165
|
### `save(coll, id, {field: {}})` clears existing nested children
|
|
108
166
|
|
|
109
167
|
`computeDiffInto` for plain objects iterated only `Object.keys(update)`,
|
|
@@ -176,6 +234,37 @@ await syncedDb.replaceSyncCollection({
|
|
|
176
234
|
});
|
|
177
235
|
```
|
|
178
236
|
|
|
237
|
+
## 0.1.174 (2026-05-12)
|
|
238
|
+
|
|
239
|
+
### Fix: overlay `_dirty_changes` onto in-mem on init
|
|
240
|
+
|
|
241
|
+
After Ctrl+R while a debounced Dexie main write was pending, the in-mem
|
|
242
|
+
cache showed stale Dexie main state instead of the merged dirty diff —
|
|
243
|
+
UI readers saw old field values until the next sync round-trip.
|
|
244
|
+
Production incident, klikvet 2026-05-12 with the server offline.
|
|
245
|
+
|
|
246
|
+
Root cause: `loadCollectionToInMem` (called from `init()`) read only the
|
|
247
|
+
Dexie main table; it never overlaid `_dirty_changes`.
|
|
248
|
+
`recoverPendingWrites` recovers from `localStorage`, but `localStorage`
|
|
249
|
+
may have been cleared by an earlier partial-debounce success — the
|
|
250
|
+
Dexie-only `_dirty_changes` table is the durable source for those
|
|
251
|
+
writes.
|
|
252
|
+
|
|
253
|
+
Fix: after loading Dexie main, walk `_dirty_changes` and apply each diff
|
|
254
|
+
to the matching main row via `applyDiffLocally`. Orphan dirty (no
|
|
255
|
+
matching main row) is included in in-mem with a `console.warn`. Dirty
|
|
256
|
+
entries marking `_deleted` / `_archived` remove the record from in-mem.
|
|
257
|
+
|
|
258
|
+
Scope: in-mem cache only. `findById` uses the in-mem fast path, so its
|
|
259
|
+
results now reflect dirty. `find()` reads Dexie main directly — that's
|
|
260
|
+
a separate Dexie-overlay gap, not addressed here.
|
|
261
|
+
|
|
262
|
+
Regression test: `test/dirtyOverlayOnInit.test.ts` (5 cases — production
|
|
263
|
+
scenario, orphan, soft-delete via dirty, no-dirty fast path, multiple
|
|
264
|
+
records). 708 pass / 0 fail.
|
|
265
|
+
|
|
266
|
+
## 0.1.173 (2026-05-12)
|
|
267
|
+
|
|
179
268
|
### `preprocessDirtyItem` callback — per-item filter / transform before upload
|
|
180
269
|
|
|
181
270
|
New optional config callback fired for **every** dirty item just before it
|
|
@@ -212,6 +301,8 @@ new SyncedDb({
|
|
|
212
301
|
Useful for: per-tenant data sanitization, conditional upload gating,
|
|
213
302
|
audit-trail injection.
|
|
214
303
|
|
|
304
|
+
## 0.1.172 (2026-05-12)
|
|
305
|
+
|
|
215
306
|
### Nested-bracket terminal layering in `mergeDirtyPath` Case 2
|
|
216
307
|
|
|
217
308
|
When a new terminal-bracket whole-element write arrives AFTER pending
|
|
@@ -250,6 +341,8 @@ alongside actual upload errors in observability pipelines.
|
|
|
250
341
|
`SUPRESS_DB_WARNINGS` constant in `SyncEngine.ts` silences them when
|
|
251
342
|
needed (e.g. during noisy migrations).
|
|
252
343
|
|
|
344
|
+
## 0.1.171 (2026-05-11)
|
|
345
|
+
|
|
253
346
|
### Runtime collection registration (`addCollectionToSync`, `replaceSyncCollection`)
|
|
254
347
|
|
|
255
348
|
Two methods to install / replace collection configs at runtime; both load the
|
|
@@ -346,6 +439,8 @@ const sinceMs = isLeader
|
|
|
346
439
|
: Date.now() - syncedDb.followerSince()!.getTime();
|
|
347
440
|
```
|
|
348
441
|
|
|
442
|
+
## 0.1.163 (2026-05-10)
|
|
443
|
+
|
|
349
444
|
### `onServerSyncWrite` callback
|
|
350
445
|
|
|
351
446
|
Single-shot callback that fires once per `restInterface.updateCollections`
|
|
@@ -383,7 +478,7 @@ parity across **mongo + Dexie + in-mem** simultaneously after a partial
|
|
|
383
478
|
`save({ postavke: [{_id: "P1", kolicina: 2}] })` over an existing
|
|
384
479
|
`postavke[0] = {_id: "P1", opis: "postavka 1", kolicina: 1}`.
|
|
385
480
|
|
|
386
|
-
## 0.1.162
|
|
481
|
+
## 0.1.162 (2026-05-10)
|
|
387
482
|
|
|
388
483
|
### Bracket-by-_id paths flow through server unchanged
|
|
389
484
|
|
|
@@ -438,7 +533,7 @@ Replaced with `applyDiffLocally(base, diff, id)`:
|
|
|
438
533
|
|
|
439
534
|
`deleteByPath` is now a sibling export of `setByPath` in `computeDiff.ts`.
|
|
440
535
|
|
|
441
|
-
## 0.1.161
|
|
536
|
+
## 0.1.161 (2026-05-10)
|
|
442
537
|
|
|
443
538
|
### Don't auto-stamp `_id` on bracket-array elements
|
|
444
539
|
|
|
@@ -449,7 +544,7 @@ preserved. This allows callers to mix:
|
|
|
449
544
|
- Bracket-by-_id sub-field path: `update["postavke[<id>].field"] = value`
|
|
450
545
|
in the same payload without the client mutating element identity.
|
|
451
546
|
|
|
452
|
-
## 0.1.160
|
|
547
|
+
## 0.1.160 (2026-05-09)
|
|
453
548
|
|
|
454
549
|
### Composition changes emit precise paths (not full-array replace)
|
|
455
550
|
|
|
@@ -471,7 +566,7 @@ Pre-fix, composition change emitted full-array replace at `basePath`,
|
|
|
471
566
|
which `mergeDirtyPath` Case 2 then dropped pending sub-field paths on
|
|
472
567
|
the same parent — race-y data-loss strip pattern visible in production.
|
|
473
568
|
|
|
474
|
-
## 0.1.159
|
|
569
|
+
## 0.1.159 (2026-05-09)
|
|
475
570
|
|
|
476
571
|
### Self-echo WS suppression for `_rev <= local._rev`
|
|
477
572
|
|
|
@@ -490,11 +585,11 @@ older snapshot because a self-echo WS arrived after writeback and
|
|
|
490
585
|
overwrote in-mem with the server's `$set`-iterated copy of postavke
|
|
491
586
|
(missing freshly-set `pop` and `navodilo` fields). Now in-mem is preserved.
|
|
492
587
|
|
|
493
|
-
## 0.1.158
|
|
588
|
+
## 0.1.158 (2026-05-09)
|
|
494
589
|
|
|
495
590
|
Internal version bump consolidating 0.1.157 fixes for production publish.
|
|
496
591
|
|
|
497
|
-
## 0.1.157
|
|
592
|
+
## 0.1.157 (2026-05-08)
|
|
498
593
|
|
|
499
594
|
### Recursive server-managed metadata strip at upload boundary
|
|
500
595
|
|
|
@@ -531,7 +626,7 @@ stuck-dirty payload (mixing top-level full arrays with bracket paths)
|
|
|
531
626
|
into Dexie's `_dirty_changes` and asserts upload succeeds without
|
|
532
627
|
mongo path-conflict errors via a `MongoFaithfulRestInterface` mock.
|
|
533
628
|
|
|
534
|
-
## 0.1.156
|
|
629
|
+
## 0.1.156 (2026-05-08)
|
|
535
630
|
|
|
536
631
|
Three related fixes targeting **dirty-payload metadata leak** and
|
|
537
632
|
**concurrent array merge corruption** observed in production
|
|
@@ -607,7 +702,7 @@ Temporary upload-time scrubber dropping legacy position-based array
|
|
|
607
702
|
paths (`field.<digit>(.…)?`) when `serverRev > baseRev`. Marked for
|
|
608
703
|
removal after ~2026-05-15 once all clients have re-synced.
|
|
609
704
|
|
|
610
|
-
## 0.1.155
|
|
705
|
+
## 0.1.155 (2026-05-08)
|
|
611
706
|
|
|
612
707
|
Two new `SyncedDbConfig` fields targeting **cross-device scope-exit**
|
|
613
708
|
detection: situations where one device modifies a record so it no longer
|
|
@@ -664,7 +759,7 @@ wake.
|
|
|
664
759
|
- `_collectScopeExitPlan` and single-collection `evictOutOfScopeRecords`
|
|
665
760
|
both route through the new helper.
|
|
666
761
|
|
|
667
|
-
##
|
|
762
|
+
## 0.1.149 (2026-04-27)
|
|
668
763
|
|
|
669
764
|
### `SyncSource` flag in `I_InMemDb.saveMany` / `deleteManyByIds`
|
|
670
765
|
|
|
@@ -702,6 +797,8 @@ Tests: `test/syncSource.test.ts` (9 cases) covers initial / incremental
|
|
|
702
797
|
/ refresh propagation across all public write paths. `MockInMemDb`
|
|
703
798
|
exposes `recordedCalls: RecordedInMemCall[]` for assertion.
|
|
704
799
|
|
|
800
|
+
## 0.1.148 (2026-04-26)
|
|
801
|
+
|
|
705
802
|
### `uploadDirtyItems` follow-up pass — drain in-sync writes immediately
|
|
706
803
|
|
|
707
804
|
Writes that land **during** a sync iteration had their
|
|
@@ -732,6 +829,8 @@ first pass — a follow-up failure does not roll back the first pass's
|
|
|
732
829
|
already-cleared dirty entries; affected items are caught at the next
|
|
733
830
|
sync tick (same retry semantics as before).
|
|
734
831
|
|
|
832
|
+
## 0.1.147 (2026-04-25)
|
|
833
|
+
|
|
735
834
|
### Auto-eviction co-located with sync — one round-trip total
|
|
736
835
|
|
|
737
836
|
When `evictStaleRecordsEveryHrs > 0` and the interval has elapsed, the
|
|
@@ -803,6 +902,8 @@ request carry multiple specs against the same collection without
|
|
|
803
902
|
library always populates them. Downstream code that constructs mock
|
|
804
903
|
literals (e.g. tests) needs the new fields.
|
|
805
904
|
|
|
905
|
+
## 0.1.144 (2026-04-24)
|
|
906
|
+
|
|
806
907
|
### Fix: filtered-sync tombstone (scope-exit from other writers)
|
|
807
908
|
|
|
808
909
|
When a collection has `syncConfig.query` (e.g. `{ status: { $ne: "obsolete" } }`)
|
|
@@ -837,6 +938,8 @@ predicate (no implicit server policy).
|
|
|
837
938
|
`$nor` support, required for the negated query to evaluate against the test
|
|
838
939
|
mock and for any client-side filtering that uses logical operators.
|
|
839
940
|
|
|
941
|
+
## 0.1.142 (2026-04-21)
|
|
942
|
+
|
|
840
943
|
### `getDirtyMeta()` for lightweight dirty-state inspection
|
|
841
944
|
|
|
842
945
|
- New `SyncedDb.getDirtyMeta()` returns dirty-entry meta (everything except the
|
|
@@ -867,6 +970,8 @@ Two contributing causes, both fixed:
|
|
|
867
970
|
cache learns of them via the existing shared-Dexie reload path. Reload
|
|
868
971
|
broadcasts (post-full-sync) remain leader-only.
|
|
869
972
|
|
|
973
|
+
## 0.1.141 (2026-04-21)
|
|
974
|
+
|
|
870
975
|
### BREAKING: Self-healing sync/reconnect lifecycle
|
|
871
976
|
|
|
872
977
|
Fixes a class of bugs where the 60s auto-sync scheduler silently died after a
|
|
@@ -897,17 +1002,21 @@ tenants, 62–296 min of dead scheduler with dirty items accumulating.
|
|
|
897
1002
|
`onForcedOffline: (reason) => log(reason)` → `onSyncFailed: (reason) => log(reason)`.
|
|
898
1003
|
Signature is identical. No other callback changes.
|
|
899
1004
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
1005
|
+
## 0.1.139 (2026-04-21)
|
|
1006
|
+
|
|
1007
|
+
### `refreshInBackground` `QueryOpts` option for `findById` / `findByIds`
|
|
1008
|
+
|
|
1009
|
+
Stale-while-revalidate: cache-hit returns the local result immediately and
|
|
1010
|
+
triggers a background fetch that updates Dexie + in-mem through conflict
|
|
1011
|
+
resolution (`processCollectionServerData`).
|
|
1012
|
+
|
|
1013
|
+
- Orthogonal to `referToServer` — does not change miss behaviour. With
|
|
1014
|
+
defaults (`referToServer: true`) misses are still awaited; with
|
|
1015
|
+
`referToServer: false` misses return `null` and bg fetch loads them async.
|
|
1016
|
+
- Dedupes against `referToServer`: IDs fetched blockingly are NOT re-fetched
|
|
1017
|
+
in the background (no double-round-trip).
|
|
1018
|
+
- Noop when offline or on writeOnly collections.
|
|
1019
|
+
- Ignored on `find` / `findOne` (use `referToServer` there).
|
|
911
1020
|
|
|
912
1021
|
## 0.1.146 (2026-04-25)
|
|
913
1022
|
|
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));
|
|
@@ -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`,
|
|
@@ -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:`,
|
|
@@ -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
|
}
|
|
@@ -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 = [];
|
|
@@ -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();
|