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