cry-synced-db-client 0.1.162 → 0.1.164
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 +187 -0
- package/dist/index.js +39 -49
- package/dist/src/db/SyncedDb.d.ts +0 -21
- package/dist/src/db/sync/SyncEngine.d.ts +1 -0
- package/dist/src/db/types/managers.d.ts +2 -1
- package/dist/src/types/I_SyncedDb.d.ts +43 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,192 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
### `onServerSyncWrite` callback
|
|
6
|
+
|
|
7
|
+
Single-shot callback that fires once per `restInterface.updateCollections`
|
|
8
|
+
round-trip with both the request payload and either the server response
|
|
9
|
+
or a thrown error in one payload. Convenience over correlating
|
|
10
|
+
`onServerWriteRequest` + `onServerWriteResult`:
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
const syncedDb = new SyncedDb({
|
|
14
|
+
// ...
|
|
15
|
+
onServerSyncWrite: (info) => {
|
|
16
|
+
if (info.error) {
|
|
17
|
+
console.error('upload failed', info.error.message, info.error.stack);
|
|
18
|
+
} else {
|
|
19
|
+
logToSyslog({ request: info.request, response: info.response });
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`ServerSyncWriteInfo`: `{ request, response?, error?, durationMs, calledFrom?, timestamp }`.
|
|
26
|
+
`response` is `undefined` on transport / timeout / network failure;
|
|
27
|
+
`error` is `{message, name?, stack?}` (serialized — safe for logging).
|
|
28
|
+
|
|
29
|
+
### Local Dexie write uses diff-applied merged row (parity fix)
|
|
30
|
+
|
|
31
|
+
`SyncedDb.save()` now schedules the **mongo-symmetric `merged` row** to
|
|
32
|
+
Dexie via `pendingChanges`, not the raw partial `update`. Without this,
|
|
33
|
+
`Dexie.table.update(key, partialUpdate)` would replace top-level array
|
|
34
|
+
fields wholesale (postavke, koraki, etc.) and drop element sub-fields
|
|
35
|
+
the caller didn't mention — same data-loss class the in-mem path
|
|
36
|
+
already protected against. New regression test
|
|
37
|
+
`test/node-only/insertUpdateRacunPostavke.test.ts` (real cry-db) verifies
|
|
38
|
+
parity across **mongo + Dexie + in-mem** simultaneously after a partial
|
|
39
|
+
`save({ postavke: [{_id: "P1", kolicina: 2}] })` over an existing
|
|
40
|
+
`postavke[0] = {_id: "P1", opis: "postavka 1", kolicina: 1}`.
|
|
41
|
+
|
|
42
|
+
## 0.1.162
|
|
43
|
+
|
|
44
|
+
### Bracket-by-_id paths flow through server unchanged
|
|
45
|
+
|
|
46
|
+
`SyncEngine.uploadDirtyItems` no longer calls `translateBracketPathsToIndex`.
|
|
47
|
+
Bracket paths (`arr[<_id>].field`) leave the client as-is; cry-db ≥ 2.4.33
|
|
48
|
+
translates them server-side to mongo `arr.$[<filterId>].field` +
|
|
49
|
+
`arrayFilters` atomically against the **live document**.
|
|
50
|
+
|
|
51
|
+
This eliminates the race window where another writer's
|
|
52
|
+
reorder/insert/delete shifted the targeted index between the client's
|
|
53
|
+
read and the server's apply. Translation against a stale Dexie snapshot
|
|
54
|
+
(client-side) is replaced by atomic resolution against mongo's current
|
|
55
|
+
state (server-side).
|
|
56
|
+
|
|
57
|
+
### `undefined` value = delete the field (cry-db `$unset` convention)
|
|
58
|
+
|
|
59
|
+
App code now signals "delete this field" by setting the value to
|
|
60
|
+
`undefined` in `update`. End-to-end behavior:
|
|
61
|
+
|
|
62
|
+
- `computeDiff` preserves `undefined` in the emitted diff (no longer
|
|
63
|
+
normalizes to `null`)
|
|
64
|
+
- Dirty change in Dexie carries `undefined` (Dexie's structured clone
|
|
65
|
+
preserves it)
|
|
66
|
+
- REST upload via `msgpackr` (`structuredClone: true`) preserves
|
|
67
|
+
`undefined` in the binary frame
|
|
68
|
+
- Server cry-db `_processUpdateObject` routes `key: undefined` to
|
|
69
|
+
`$unset[key] = true`; mongo physically removes the field
|
|
70
|
+
- Local in-mem and Dexie: `applyDiffLocally` detects `undefined`
|
|
71
|
+
values in the diff and uses `deleteByPath` (mongo-symmetric)
|
|
72
|
+
|
|
73
|
+
Caller convention: read with strict `racun.field === undefined` is
|
|
74
|
+
now safe (the field is genuinely absent, not stored as `null`).
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// remove `navodilo` from the racun
|
|
78
|
+
await syncedDb.save("racuni", id, { navodilo: undefined });
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Diff-aware local apply (`applyDiffLocally`)
|
|
82
|
+
|
|
83
|
+
`SyncedDb.save()` no longer uses shallow `{ ...currentMem, ...update }`
|
|
84
|
+
spread that would replace top-level array/object fields wholesale and
|
|
85
|
+
drop nested fields the caller's `update` didn't mention.
|
|
86
|
+
|
|
87
|
+
Replaced with `applyDiffLocally(base, diff, id)`:
|
|
88
|
+
1. Deep-clone `base` (currentMem ?? existing) via `safeDeepClone`
|
|
89
|
+
(handles Date and `ObjectId`-like values; avoids `structuredClone`
|
|
90
|
+
throwing on bson class instances)
|
|
91
|
+
2. For each `(path, value)` in `diff`: `setByPath` if value is
|
|
92
|
+
defined, `deleteByPath` if value is `undefined`
|
|
93
|
+
3. Result matches what server-side `$set` + `$unset` would produce
|
|
94
|
+
|
|
95
|
+
`deleteByPath` is now a sibling export of `setByPath` in `computeDiff.ts`.
|
|
96
|
+
|
|
97
|
+
## 0.1.161
|
|
98
|
+
|
|
99
|
+
### Don't auto-stamp `_id` on bracket-array elements
|
|
100
|
+
|
|
101
|
+
Reverted automatic `_id` stamping for objects appearing as array elements.
|
|
102
|
+
If an array of objects lacks `_id`, the caller's element shape is now
|
|
103
|
+
preserved. This allows callers to mix:
|
|
104
|
+
- Whole-element bracket replace: `update.postavke = [{...}]`
|
|
105
|
+
- Bracket-by-_id sub-field path: `update["postavke[<id>].field"] = value`
|
|
106
|
+
in the same payload without the client mutating element identity.
|
|
107
|
+
|
|
108
|
+
## 0.1.160
|
|
109
|
+
|
|
110
|
+
### Composition changes emit precise paths (not full-array replace)
|
|
111
|
+
|
|
112
|
+
`computeArrayDiff` strategy matrix is expanded:
|
|
113
|
+
|
|
114
|
+
| Composition | Strategy |
|
|
115
|
+
|---|---|
|
|
116
|
+
| Empty → empty | no diff |
|
|
117
|
+
| Same `_id` set, same order | element-wise: `arr[<id>].field` |
|
|
118
|
+
| Same `_id` set, different order | full replace `arr` |
|
|
119
|
+
| Different `_id` set | mixed: `$pull` + `$push` + sub-field |
|
|
120
|
+
|
|
121
|
+
For composition changes, `computeDiff` now emits:
|
|
122
|
+
- **Removed `_id`**: `arr[<id>] = undefined` (server: `$pull`)
|
|
123
|
+
- **Added `_id`**: `arr[<id>] = [element]` (server: `$concatArrays + $filter`)
|
|
124
|
+
- **Retained `_id`**: element-wise sub-field via `arr[<id>].field`
|
|
125
|
+
|
|
126
|
+
Pre-fix, composition change emitted full-array replace at `basePath`,
|
|
127
|
+
which `mergeDirtyPath` Case 2 then dropped pending sub-field paths on
|
|
128
|
+
the same parent — race-y data-loss strip pattern visible in production.
|
|
129
|
+
|
|
130
|
+
## 0.1.159
|
|
131
|
+
|
|
132
|
+
### Self-echo WS suppression for `_rev <= local._rev`
|
|
133
|
+
|
|
134
|
+
`ServerUpdateHandler` now ignores its own WS notifications when
|
|
135
|
+
`_lastUpdaterId === self.updaterId AND _rev <= local._rev`. Three sub-cases:
|
|
136
|
+
|
|
137
|
+
| `_rev` relation | Action |
|
|
138
|
+
|---|---|
|
|
139
|
+
| `=== local._rev + 1` | Bump meta to match server, clear dirty (existing loopback case) |
|
|
140
|
+
| `=== local._rev` | WS arrived AFTER post-upload writeback. Local is already at server's rev — drop the WS payload entirely. |
|
|
141
|
+
| `< local._rev` | Stale duplicate / out-of-order delivery. Never downgrade local. |
|
|
142
|
+
|
|
143
|
+
In B and C, `mergeLocalWithDelta` and `writeToInMemBatch` are skipped
|
|
144
|
+
entirely. Production data-loss case (2026-05-09): page re-mount loaded
|
|
145
|
+
older snapshot because a self-echo WS arrived after writeback and
|
|
146
|
+
overwrote in-mem with the server's `$set`-iterated copy of postavke
|
|
147
|
+
(missing freshly-set `pop` and `navodilo` fields). Now in-mem is preserved.
|
|
148
|
+
|
|
149
|
+
## 0.1.158
|
|
150
|
+
|
|
151
|
+
Internal version bump consolidating 0.1.157 fixes for production publish.
|
|
152
|
+
|
|
153
|
+
## 0.1.157
|
|
154
|
+
|
|
155
|
+
### Recursive server-managed metadata strip at upload boundary
|
|
156
|
+
|
|
157
|
+
`stripServerManagedFromChanges(changes)` walks dirty payload paths AND
|
|
158
|
+
values, removing every `_ts` / `_rev` / `_csq` at every depth (including
|
|
159
|
+
inside nested arrays and objects). Replaces SyncEngine's prefix-only
|
|
160
|
+
filter (`startsWith('_ts.')`) which missed deep paths like
|
|
161
|
+
`_redundanca.terapije[<id>]._rev` and silent-rejected uploads on the
|
|
162
|
+
server.
|
|
163
|
+
|
|
164
|
+
### `fixDotnetArrays` recognizes bracket-by-_id paths and nested top-level array keys
|
|
165
|
+
|
|
166
|
+
When a top-level full array key (e.g. `zaracunaj` or
|
|
167
|
+
`_redundanca.terapije`) coexists with element paths (`arr[<id>].field`
|
|
168
|
+
or `arr.0.field`), the element paths are dropped to prevent mongo
|
|
169
|
+
"path conflict" rejection (`Updating the path 'X' would create a
|
|
170
|
+
conflict at 'Y'`). Now also catches dot-key array names like
|
|
171
|
+
`_redundanca.terapije` and bracket-by-_id paths.
|
|
172
|
+
|
|
173
|
+
### Auto-stamp `_id` on every plain object inside arrays (`SyncedDb.ensureNestedIds`)
|
|
174
|
+
|
|
175
|
+
Write operations (save / upsert / insert) recursively walk the data
|
|
176
|
+
tree and stamp `_id = new ObjectId().toHexString()` on every plain
|
|
177
|
+
object that appears as an array element but lacks one. This keeps
|
|
178
|
+
every save on the element-wise bracket-by-_id path
|
|
179
|
+
(`computeDiff`'s `allElementsHaveId` check).
|
|
180
|
+
|
|
181
|
+
(Note: 0.1.161 reverted this — see entry above.)
|
|
182
|
+
|
|
183
|
+
### Production stuck-dirty regression test
|
|
184
|
+
|
|
185
|
+
`test/stuckDirtyDirectInject.test.ts` injects an exact production
|
|
186
|
+
stuck-dirty payload (mixing top-level full arrays with bracket paths)
|
|
187
|
+
into Dexie's `_dirty_changes` and asserts upload succeeds without
|
|
188
|
+
mongo path-conflict errors via a `MongoFaithfulRestInterface` mock.
|
|
189
|
+
|
|
3
190
|
## 0.1.156
|
|
4
191
|
|
|
5
192
|
Three related fixes targeting **dirty-payload metadata leak** and
|
package/dist/index.js
CHANGED
|
@@ -3218,6 +3218,7 @@ var _SyncEngine = class _SyncEngine {
|
|
|
3218
3218
|
}
|
|
3219
3219
|
this.callOnServerWriteRequest(collectionBatches, calledFrom);
|
|
3220
3220
|
const writeStartTime = Date.now();
|
|
3221
|
+
const writeStartedAt = /* @__PURE__ */ new Date();
|
|
3221
3222
|
let results;
|
|
3222
3223
|
try {
|
|
3223
3224
|
results = await this.deps.withSyncTimeout(
|
|
@@ -3225,8 +3226,24 @@ var _SyncEngine = class _SyncEngine {
|
|
|
3225
3226
|
"updateCollections"
|
|
3226
3227
|
);
|
|
3227
3228
|
this.callOnServerWriteResult(results, writeStartTime, true, calledFrom);
|
|
3229
|
+
this.callOnServerSyncWrite(
|
|
3230
|
+
collectionBatches,
|
|
3231
|
+
results,
|
|
3232
|
+
void 0,
|
|
3233
|
+
writeStartTime,
|
|
3234
|
+
writeStartedAt,
|
|
3235
|
+
calledFrom
|
|
3236
|
+
);
|
|
3228
3237
|
} catch (err) {
|
|
3229
3238
|
this.callOnServerWriteResult([], writeStartTime, false, calledFrom, err);
|
|
3239
|
+
this.callOnServerSyncWrite(
|
|
3240
|
+
collectionBatches,
|
|
3241
|
+
void 0,
|
|
3242
|
+
err,
|
|
3243
|
+
writeStartTime,
|
|
3244
|
+
writeStartedAt,
|
|
3245
|
+
calledFrom
|
|
3246
|
+
);
|
|
3230
3247
|
throw err;
|
|
3231
3248
|
}
|
|
3232
3249
|
let sentCount = 0;
|
|
@@ -3628,6 +3645,26 @@ var _SyncEngine = class _SyncEngine {
|
|
|
3628
3645
|
}
|
|
3629
3646
|
}
|
|
3630
3647
|
}
|
|
3648
|
+
callOnServerSyncWrite(request, response, error, startTime, timestamp, calledFrom) {
|
|
3649
|
+
if (!this.callbacks.onServerSyncWrite) return;
|
|
3650
|
+
try {
|
|
3651
|
+
const errInfo = error ? {
|
|
3652
|
+
message: error instanceof Error ? error.message : String(error),
|
|
3653
|
+
name: error instanceof Error ? error.name : void 0,
|
|
3654
|
+
stack: error instanceof Error ? error.stack : void 0
|
|
3655
|
+
} : void 0;
|
|
3656
|
+
this.callbacks.onServerSyncWrite({
|
|
3657
|
+
request,
|
|
3658
|
+
response,
|
|
3659
|
+
error: errInfo,
|
|
3660
|
+
durationMs: Date.now() - startTime,
|
|
3661
|
+
calledFrom,
|
|
3662
|
+
timestamp
|
|
3663
|
+
});
|
|
3664
|
+
} catch (err) {
|
|
3665
|
+
console.error("onServerSyncWrite callback failed:", err);
|
|
3666
|
+
}
|
|
3667
|
+
}
|
|
3631
3668
|
};
|
|
3632
3669
|
// ============================================================
|
|
3633
3670
|
// Private: Process Incoming Data
|
|
@@ -4248,6 +4285,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4248
4285
|
onConflictResolved: config.onConflictResolved,
|
|
4249
4286
|
onServerWriteRequest: config.onServerWriteRequest,
|
|
4250
4287
|
onServerWriteResult: config.onServerWriteResult,
|
|
4288
|
+
onServerSyncWrite: config.onServerSyncWrite,
|
|
4251
4289
|
onFindNewerManyCall: config.onFindNewerManyCall,
|
|
4252
4290
|
onFindNewerManyResult: config.onFindNewerManyResult,
|
|
4253
4291
|
onUploadSkip: config.onUploadSkip
|
|
@@ -4883,7 +4921,6 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4883
4921
|
});
|
|
4884
4922
|
delete update._id;
|
|
4885
4923
|
}
|
|
4886
|
-
_SyncedDb.ensureNestedIds(update);
|
|
4887
4924
|
update = _SyncedDb.stringifyObjectIds(update);
|
|
4888
4925
|
const existing = await this.dexieDb.getById(collection, id);
|
|
4889
4926
|
if (!existing && !((_a = this.collections.get(collection)) == null ? void 0 : _a.writeOnly)) {
|
|
@@ -4899,10 +4936,6 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4899
4936
|
diff,
|
|
4900
4937
|
{ _ts: existing == null ? void 0 : existing._ts, _rev: existing == null ? void 0 : existing._rev }
|
|
4901
4938
|
);
|
|
4902
|
-
const newData = __spreadProps(__spreadValues({}, update), {
|
|
4903
|
-
_lastUpdaterId: this.updaterId
|
|
4904
|
-
});
|
|
4905
|
-
this.pendingChanges.schedule(collection, id, newData, 0, "save");
|
|
4906
4939
|
const isWriteOnly = (_b = this.collections.get(collection)) == null ? void 0 : _b.writeOnly;
|
|
4907
4940
|
const currentMem = isWriteOnly ? null : this.inMemDb.getById(collection, id);
|
|
4908
4941
|
const merged = _SyncedDb.applyDiffLocally(
|
|
@@ -4910,6 +4943,7 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4910
4943
|
diff,
|
|
4911
4944
|
id
|
|
4912
4945
|
);
|
|
4946
|
+
this.pendingChanges.schedule(collection, id, merged, 0, "save");
|
|
4913
4947
|
if (!isWriteOnly && !(existing == null ? void 0 : existing._deleted) && !(existing == null ? void 0 : existing._archived)) {
|
|
4914
4948
|
this.inMemManager.writeBatch(collection, [merged], "upsert", { source: "incremental" });
|
|
4915
4949
|
}
|
|
@@ -4918,7 +4952,6 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4918
4952
|
async upsert(collection, query, update) {
|
|
4919
4953
|
this.assertCollection(collection);
|
|
4920
4954
|
this.ensureId(update, "upsert", collection);
|
|
4921
|
-
_SyncedDb.ensureNestedIds(update);
|
|
4922
4955
|
query = _SyncedDb.stringifyObjectIds(query);
|
|
4923
4956
|
update = _SyncedDb.stringifyObjectIds(update);
|
|
4924
4957
|
const existing = await this.findOne(collection, query);
|
|
@@ -4932,7 +4965,6 @@ var _SyncedDb = class _SyncedDb {
|
|
|
4932
4965
|
var _a;
|
|
4933
4966
|
this.assertCollection(collection);
|
|
4934
4967
|
this.ensureId(data, "insert", collection);
|
|
4935
|
-
_SyncedDb.ensureNestedIds(data);
|
|
4936
4968
|
data = _SyncedDb.stringifyObjectIds(data);
|
|
4937
4969
|
const unsetPaths = collectUnsetPaths(data);
|
|
4938
4970
|
const id = String(data._id);
|
|
@@ -6094,48 +6126,6 @@ var _SyncedDb = class _SyncedDb {
|
|
|
6094
6126
|
}
|
|
6095
6127
|
return out;
|
|
6096
6128
|
}
|
|
6097
|
-
/**
|
|
6098
|
-
* Recursively walk `value` and ensure every plain object that appears
|
|
6099
|
-
* as an element of an array carries an `_id`. Missing `_id`s are
|
|
6100
|
-
* generated as `new ObjectId().toHexString()` (string, not BSON instance).
|
|
6101
|
-
*
|
|
6102
|
-
* Mutates the input tree in place — caller sees the freshly-assigned ids
|
|
6103
|
-
* (matches the pattern of `ensureId`).
|
|
6104
|
-
*
|
|
6105
|
-
* Why: `computeDiff` falls back to a full-array replace whenever any
|
|
6106
|
-
* element of an array of objects lacks `_id` (see `allElementsHaveId`).
|
|
6107
|
-
* That defeats element-wise `arr[<_id>].field` paths and re-introduces
|
|
6108
|
-
* the stale-array-overwrite bug. Stamping ids upfront keeps every save
|
|
6109
|
-
* on the per-element bracket path.
|
|
6110
|
-
*
|
|
6111
|
-
* Skipped:
|
|
6112
|
-
* - primitives (numbers, strings, booleans, null, undefined)
|
|
6113
|
-
* - `Date`, `ObjectId`-like values
|
|
6114
|
-
* - top-level objects (only ARRAY ELEMENTS get an auto-id; the entity
|
|
6115
|
-
* itself is handled by `ensureId`)
|
|
6116
|
-
*/
|
|
6117
|
-
static ensureNestedIds(value) {
|
|
6118
|
-
if (value === null || value === void 0) return;
|
|
6119
|
-
if (typeof value !== "object") return;
|
|
6120
|
-
if (value instanceof Date) return;
|
|
6121
|
-
if (_SyncedDb.isObjectIdLike(value)) return;
|
|
6122
|
-
if (Array.isArray(value)) {
|
|
6123
|
-
for (const element of value) {
|
|
6124
|
-
if (element !== null && typeof element === "object" && !Array.isArray(element) && !(element instanceof Date) && !_SyncedDb.isObjectIdLike(element)) {
|
|
6125
|
-
if (element._id == null || element._id === "") {
|
|
6126
|
-
element._id = new ObjectId2().toHexString();
|
|
6127
|
-
} else if (_SyncedDb.isObjectIdLike(element._id)) {
|
|
6128
|
-
element._id = String(element._id);
|
|
6129
|
-
}
|
|
6130
|
-
}
|
|
6131
|
-
_SyncedDb.ensureNestedIds(element);
|
|
6132
|
-
}
|
|
6133
|
-
return;
|
|
6134
|
-
}
|
|
6135
|
-
for (const key of Object.keys(value)) {
|
|
6136
|
-
_SyncedDb.ensureNestedIds(value[key]);
|
|
6137
|
-
}
|
|
6138
|
-
}
|
|
6139
6129
|
/**
|
|
6140
6130
|
* Asserts write-only collection has online connectivity for reads.
|
|
6141
6131
|
* @throws Error if offline
|
|
@@ -427,27 +427,6 @@ export declare class SyncedDb implements I_SyncedDb {
|
|
|
427
427
|
* instances like bson `ObjectId`.
|
|
428
428
|
*/
|
|
429
429
|
private static safeDeepClone;
|
|
430
|
-
/**
|
|
431
|
-
* Recursively walk `value` and ensure every plain object that appears
|
|
432
|
-
* as an element of an array carries an `_id`. Missing `_id`s are
|
|
433
|
-
* generated as `new ObjectId().toHexString()` (string, not BSON instance).
|
|
434
|
-
*
|
|
435
|
-
* Mutates the input tree in place — caller sees the freshly-assigned ids
|
|
436
|
-
* (matches the pattern of `ensureId`).
|
|
437
|
-
*
|
|
438
|
-
* Why: `computeDiff` falls back to a full-array replace whenever any
|
|
439
|
-
* element of an array of objects lacks `_id` (see `allElementsHaveId`).
|
|
440
|
-
* That defeats element-wise `arr[<_id>].field` paths and re-introduces
|
|
441
|
-
* the stale-array-overwrite bug. Stamping ids upfront keeps every save
|
|
442
|
-
* on the per-element bracket path.
|
|
443
|
-
*
|
|
444
|
-
* Skipped:
|
|
445
|
-
* - primitives (numbers, strings, booleans, null, undefined)
|
|
446
|
-
* - `Date`, `ObjectId`-like values
|
|
447
|
-
* - top-level objects (only ARRAY ELEMENTS get an auto-id; the entity
|
|
448
|
-
* itself is handled by `ensureId`)
|
|
449
|
-
*/
|
|
450
|
-
private static ensureNestedIds;
|
|
451
430
|
/**
|
|
452
431
|
* Asserts write-only collection has online connectivity for reads.
|
|
453
432
|
* @throws Error if offline
|
|
@@ -7,7 +7,7 @@ import type { I_DexieDb, SyncMeta, MetaUpdateBroadcast } from "../../types/I_Dex
|
|
|
7
7
|
import type { I_InMemDb, SyncSource } from "../../types/I_InMemDb";
|
|
8
8
|
import type { I_RestInterface } from "../../types/I_RestInterface";
|
|
9
9
|
import type { PublishDataPayload } from "../../types/PublishRevsPayload";
|
|
10
|
-
import type { CollectionConfig, SyncInfo, ConflictResolutionReport, ServerWriteRequestInfo, ServerWriteResultInfo, FindNewerManyCallInfo, FindNewerManyResultInfo, DexieWriteRequestInfo, DexieWriteResultInfo, LocalstorageWriteResultInfo, WsNotificationInfo, CrossTabSyncInfo } from "../../types/I_SyncedDb";
|
|
10
|
+
import type { CollectionConfig, SyncInfo, ConflictResolutionReport, ServerWriteRequestInfo, ServerWriteResultInfo, ServerSyncWriteInfo, FindNewerManyCallInfo, FindNewerManyResultInfo, DexieWriteRequestInfo, DexieWriteResultInfo, LocalstorageWriteResultInfo, WsNotificationInfo, CrossTabSyncInfo } from "../../types/I_SyncedDb";
|
|
11
11
|
import type { PendingChange, UploadResult } from "./internal";
|
|
12
12
|
export interface LeaderElectionCallbacks {
|
|
13
13
|
onBecameLeader?: () => void;
|
|
@@ -259,6 +259,7 @@ export interface SyncEngineCallbacks {
|
|
|
259
259
|
onConflictResolved?: (report: ConflictResolutionReport) => void;
|
|
260
260
|
onServerWriteRequest?: (info: ServerWriteRequestInfo) => void;
|
|
261
261
|
onServerWriteResult?: (info: ServerWriteResultInfo) => void;
|
|
262
|
+
onServerSyncWrite?: (info: ServerSyncWriteInfo) => void;
|
|
262
263
|
onFindNewerManyCall?: (info: FindNewerManyCallInfo) => void;
|
|
263
264
|
onFindNewerManyResult?: (info: FindNewerManyResultInfo) => void;
|
|
264
265
|
onUploadSkip?: (info: import("../../types/I_SyncedDb").UploadSkipInfo) => void;
|
|
@@ -117,6 +117,41 @@ export interface ServerWriteResultInfo {
|
|
|
117
117
|
/** Where sync was called from (for debugging) */
|
|
118
118
|
calledFrom?: string;
|
|
119
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Callback payload for a single `updateCollections` round-trip — fires
|
|
122
|
+
* once per call with both the request and either the response OR the
|
|
123
|
+
* error description in a single payload.
|
|
124
|
+
*
|
|
125
|
+
* Convenience over `onServerWriteRequest` + `onServerWriteResult`: lets
|
|
126
|
+
* consumers route the entire request/response pair (or a thrown error)
|
|
127
|
+
* to syslog / observability with a single hook, without correlating
|
|
128
|
+
* across the two events.
|
|
129
|
+
*/
|
|
130
|
+
export interface ServerSyncWriteInfo {
|
|
131
|
+
/** Batches sent to the server (the request payload). */
|
|
132
|
+
request: CollectionUpdateRequest<any>[];
|
|
133
|
+
/**
|
|
134
|
+
* Per-collection results from the server — present when the call
|
|
135
|
+
* succeeded (no transport / timeout / network error). Individual
|
|
136
|
+
* items inside the result may still report per-record `errors`.
|
|
137
|
+
*/
|
|
138
|
+
response?: CollectionUpdateResult[];
|
|
139
|
+
/**
|
|
140
|
+
* Populated when `restInterface.updateCollections` threw or timed
|
|
141
|
+
* out. `response` is `undefined` in that case.
|
|
142
|
+
*/
|
|
143
|
+
error?: {
|
|
144
|
+
message: string;
|
|
145
|
+
name?: string;
|
|
146
|
+
stack?: string;
|
|
147
|
+
};
|
|
148
|
+
/** Round-trip duration in ms. */
|
|
149
|
+
durationMs: number;
|
|
150
|
+
/** Where sync was called from (for debugging). */
|
|
151
|
+
calledFrom?: string;
|
|
152
|
+
/** Wall-clock time when the round-trip started. */
|
|
153
|
+
timestamp: Date;
|
|
154
|
+
}
|
|
120
155
|
/**
|
|
121
156
|
* Callback payload for findNewerMany calls
|
|
122
157
|
*/
|
|
@@ -459,6 +494,14 @@ export interface SyncedDbConfig {
|
|
|
459
494
|
onServerWriteRequest?: (info: ServerWriteRequestInfo) => void;
|
|
460
495
|
/** Callback after receiving result from server (updateCollections) */
|
|
461
496
|
onServerWriteResult?: (info: ServerWriteResultInfo) => void;
|
|
497
|
+
/**
|
|
498
|
+
* Single-shot callback for each `updateCollections` round-trip — fires
|
|
499
|
+
* once with both the request payload AND either the server response or
|
|
500
|
+
* the thrown error (`error.message`/`name`/`stack`). Convenient when a
|
|
501
|
+
* consumer wants to log the full request/response pair (e.g. to syslog)
|
|
502
|
+
* without correlating across `onServerWriteRequest` + `onServerWriteResult`.
|
|
503
|
+
*/
|
|
504
|
+
onServerSyncWrite?: (info: ServerSyncWriteInfo) => void;
|
|
462
505
|
/** Callback when findNewerMany is called */
|
|
463
506
|
onFindNewerManyCall?: (info: FindNewerManyCallInfo) => void;
|
|
464
507
|
/** Callback when findNewerMany completes */
|