@interop/was-react 0.20.1 → 0.22.0
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/README.md +28 -2
- package/dist/auth/loginFlow.d.ts.map +1 -1
- package/dist/auth/loginFlow.js +5 -5
- package/dist/auth/loginFlow.js.map +1 -1
- package/dist/auth/verifyResponse.d.ts.map +1 -1
- package/dist/auth/verifyResponse.js +7 -4
- package/dist/auth/verifyResponse.js.map +1 -1
- package/dist/dev/provisionDevGrants.js +1 -1
- package/dist/dev/provisionDevGrants.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -2
- package/dist/index.js.map +1 -1
- package/dist/log.d.ts +48 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +52 -0
- package/dist/log.js.map +1 -0
- package/dist/session/authStore.d.ts.map +1 -1
- package/dist/session/authStore.js +12 -10
- package/dist/session/authStore.js.map +1 -1
- package/dist/session/localWipe.d.ts.map +1 -1
- package/dist/session/localWipe.js +5 -4
- package/dist/session/localWipe.js.map +1 -1
- package/dist/storage/adopt.d.ts.map +1 -1
- package/dist/storage/adopt.js +2 -1
- package/dist/storage/adopt.js.map +1 -1
- package/dist/storage/descriptorManager.d.ts.map +1 -1
- package/dist/storage/descriptorManager.js +5 -4
- package/dist/storage/descriptorManager.js.map +1 -1
- package/dist/{sync → storage}/docCipher.d.ts +3 -29
- package/dist/storage/docCipher.d.ts.map +1 -0
- package/dist/{sync → storage}/docCipher.js +0 -20
- package/dist/storage/docCipher.js.map +1 -0
- package/dist/storage/entityStore.d.ts.map +1 -1
- package/dist/storage/entityStore.js +2 -1
- package/dist/storage/entityStore.js.map +1 -1
- package/dist/storage/localStore.d.ts +1 -1
- package/dist/storage/localStore.d.ts.map +1 -1
- package/dist/storage/localStore.js +15 -2
- package/dist/storage/localStore.js.map +1 -1
- package/dist/storage/sharedCollectionReader.d.ts +1 -1
- package/dist/storage/sharedCollectionReader.d.ts.map +1 -1
- package/dist/storage/sharedCollectionReader.js +11 -11
- package/dist/storage/sharedCollectionReader.js.map +1 -1
- package/dist/storage/storageContext.d.ts +1 -2
- package/dist/storage/storageContext.d.ts.map +1 -1
- package/dist/storage/storageContext.js.map +1 -1
- package/dist/storage/storageManager.d.ts +1 -1
- package/dist/storage/storageManager.d.ts.map +1 -1
- package/dist/storage/syncController.d.ts +30 -19
- package/dist/storage/syncController.d.ts.map +1 -1
- package/dist/storage/syncController.js +86 -157
- package/dist/storage/syncController.js.map +1 -1
- package/dist/storage/syncStatusStore.d.ts +8 -7
- package/dist/storage/syncStatusStore.d.ts.map +1 -1
- package/dist/storage/syncStatusStore.js.map +1 -1
- package/dist/storage/wasRemoteStore.d.ts.map +1 -1
- package/dist/storage/wasRemoteStore.js +6 -2
- package/dist/storage/wasRemoteStore.js.map +1 -1
- package/dist/storage/wasSync.d.ts +1 -1
- package/dist/storage/wasSync.d.ts.map +1 -1
- package/dist/storage/wasSync.js +28 -15
- package/dist/storage/wasSync.js.map +1 -1
- package/dist/{sync → storage}/wasSyncPort.d.ts +14 -8
- package/dist/storage/wasSyncPort.d.ts.map +1 -0
- package/dist/storage/wasSyncPort.js.map +1 -0
- package/dist/storage/writerId.d.ts +7 -13
- package/dist/storage/writerId.d.ts.map +1 -1
- package/dist/storage/writerId.js +30 -46
- package/dist/storage/writerId.js.map +1 -1
- package/package.json +16 -11
- package/dist/sync/changesQuery.d.ts +0 -44
- package/dist/sync/changesQuery.d.ts.map +0 -1
- package/dist/sync/changesQuery.js +0 -54
- package/dist/sync/changesQuery.js.map +0 -1
- package/dist/sync/docCipher.d.ts.map +0 -1
- package/dist/sync/docCipher.js.map +0 -1
- package/dist/sync/feedMasterPort.d.ts +0 -38
- package/dist/sync/feedMasterPort.d.ts.map +0 -1
- package/dist/sync/feedMasterPort.js +0 -116
- package/dist/sync/feedMasterPort.js.map +0 -1
- package/dist/sync/index.d.ts +0 -23
- package/dist/sync/index.d.ts.map +0 -1
- package/dist/sync/index.js +0 -27
- package/dist/sync/index.js.map +0 -1
- package/dist/sync/lww.d.ts +0 -50
- package/dist/sync/lww.d.ts.map +0 -1
- package/dist/sync/lww.js +0 -69
- package/dist/sync/lww.js.map +0 -1
- package/dist/sync/lwwConflictHandler.d.ts +0 -83
- package/dist/sync/lwwConflictHandler.d.ts.map +0 -1
- package/dist/sync/lwwConflictHandler.js +0 -114
- package/dist/sync/lwwConflictHandler.js.map +0 -1
- package/dist/sync/pushWrites.d.ts +0 -76
- package/dist/sync/pushWrites.d.ts.map +0 -1
- package/dist/sync/pushWrites.js +0 -221
- package/dist/sync/pushWrites.js.map +0 -1
- package/dist/sync/syncedDocSchema.d.ts +0 -22
- package/dist/sync/syncedDocSchema.d.ts.map +0 -1
- package/dist/sync/syncedDocSchema.js +0 -29
- package/dist/sync/syncedDocSchema.js.map +0 -1
- package/dist/sync/types.d.ts +0 -319
- package/dist/sync/types.d.ts.map +0 -1
- package/dist/sync/types.js +0 -58
- package/dist/sync/types.js.map +0 -1
- package/dist/sync/wasReplication.d.ts +0 -47
- package/dist/sync/wasReplication.d.ts.map +0 -1
- package/dist/sync/wasReplication.js +0 -92
- package/dist/sync/wasReplication.js.map +0 -1
- package/dist/sync/wasSyncPort.d.ts.map +0 -1
- package/dist/sync/wasSyncPort.js.map +0 -1
- /package/dist/{sync → storage}/wasSyncPort.js +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"changesQuery.js","sourceRoot":"","sources":["../../src/sync/changesQuery.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAEnD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAAC,GAAY;IACzC,MAAM,KAAK,GAA2B;QACpC,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,QAAQ,EAAE,GAAG,CAAC,QAAQ;KACvB,CAAA;IACD,sBAAsB,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAA;IACtD,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAiB;IACjD,OAAO,KAAK,UAAU,IAAI,CACxB,cAA0C,EAC1C,SAAiB;QAKjB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC;YAChC,kEAAkE;YAClE,iCAAiC;YACjC,GAAG,CAAC,cAAc,KAAK,SAAS,IAAI,EAAE,UAAU,EAAE,cAAc,EAAE,CAAC;YACnE,KAAK,EAAE,SAAS;SACjB,CAAC,CAAA;QACF,OAAO;YACL,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,cAAc,CAAC;YACjD,oEAAoE;YACpE,8DAA8D;YAC9D,UAAU,EAAE,QAAQ,CAAC,UAAU,IAAI,cAAc;SAClD,CAAA;IACH,CAAC,CAAA;AACH,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"docCipher.d.ts","sourceRoot":"","sources":["../../src/sync/docCipher.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EACb,MAAM,8BAA8B,CAAA;AACrC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAG/D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAEtC;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAKzD;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,SAAS;IACxB,OAAO,CAAC,OAAO,EAAE;QACf,IAAI,EAAE,IAAI,CAAA;KACX,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,IAAI,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC3D,aAAa,CAAC,OAAO,EAAE;QACrB,EAAE,EAAE,MAAM,CAAA;QACV,IAAI,EAAE,IAAI,CAAA;QACV,OAAO,EAAE,IAAI,CAAA;KACd,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,IAAI,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC3D,OAAO,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnD,SAAS,CAAC,CAAC,OAAO,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CAC5D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,YAAY,EACb,EAAE;IACD,YAAY,EAAE,MAAM,CAAA;CACrB,GAAG,SAAS,CAiCZ;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,eAAe,CAAC,EACpC,eAAe,EACf,WAAW,EACX,YAAY,EACZ,UAAU,EACV,IAAI,EACL,EAAE;IACD,eAAe,EAAE,gBAAgB,CAAA;IACjC,WAAW,EAAE,YAAY,CAAA;IACzB,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,oBAAoB,CAAA;IAChC,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAC5B,GAAG,OAAO,CAAC,SAAS,CAAC,CAUrB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,4BAA4B,CAAC,EAC3C,YAAY,EACb,EAAE;IACD,YAAY,EAAE,MAAM,CAAA;CACrB,GAAG,SAAS,CAkBZ"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"docCipher.js","sourceRoot":"","sources":["../../src/sync/docCipher.ts"],"names":[],"mappings":"AA6BA,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAC/E,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAG9D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAY;IAC9C,OAAO,CACL,GAAG,YAAY,iBAAiB;QAChC,CAAC,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,IAAI,KAAK,mBAAmB,CAAC,CAC3D,CAAA;AACH,CAAC;AA6BD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,uBAAuB,CAAC,EACtC,YAAY,EAGb;IACC,MAAM,SAAS,GAAG,CAAC,IAAU,EAAU,EAAE;QACvC,MAAM,EAAE,GAAI,IAAgC,EAAE,EAAE,CAAA;QAChD,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9C,MAAM,IAAI,KAAK,CACb,kCAAkC,YAAY,2BAA2B,CAC1E,CAAA;QACH,CAAC;QACD,OAAO,EAAE,CAAA;IACX,CAAC,CAAA;IACD,MAAM,eAAe,GAAG,CAAC,IAAU,EAAQ,EAAE;QAC3C,IAAI,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CACb,eAAe,YAAY,yCAAyC;gBAClE,yBAAyB,CAC5B,CAAA;QACH,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC,CAAA;IAED,OAAO;QACL,KAAK,CAAC,OAAO,CAAC,EAAE,IAAI,EAAkB;YACpC,OAAO,EAAE,EAAE,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;QAChD,CAAC;QAED,KAAK,CAAC,aAAa,CAAC,EAAE,EAAE,EAAE,IAAI,EAA8B;YAC1D,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;QAC/B,CAAC;QAED,KAAK,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAsB;YAC5C,OAAO,eAAe,CAAC,QAAQ,CAAC,CAAA;QAClC,CAAC;KACF,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,EACpC,eAAe,EACf,WAAW,EACX,YAAY,EACZ,UAAU,EACV,IAAI,EAOL;IACC,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC;QACtC,eAAe;QACf,WAAW;QACX,YAAY;QACZ,YAAY,EAAE,QAAQ;QACtB,UAAU;QACV,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,CAAC;KACpC,CAAC,CAAA;IACF,OAAO,MAA8B,CAAA;AACvC,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,4BAA4B,CAAC,EAC3C,YAAY,EAGb;IACC,MAAM,WAAW,GAAG,GAAU,EAAE;QAC9B,MAAM,IAAI,KAAK,CACb,eAAe,YAAY,2CAA2C;YACpE,4DAA4D,CAC/D,CAAA;IACH,CAAC,CAAA;IACD,OAAO;QACL,KAAK,CAAC,OAAO;YACX,OAAO,WAAW,EAAE,CAAA;QACtB,CAAC;QACD,KAAK,CAAC,aAAa;YACjB,OAAO,WAAW,EAAE,CAAA;QACtB,CAAC;QACD,KAAK,CAAC,OAAO;YACX,MAAM,IAAI,iBAAiB,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAA;QACzD,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* The `get` half of a {@link WasSyncPort}: resolves a resource's current master
|
|
6
|
-
* state (for the 412 conflict re-read) from the CHANGES-FEED BODY. Wraps a
|
|
7
|
-
* {@link WasSyncBasePort} (query + conditional writes) and adds the `get` the
|
|
8
|
-
* conflict assembler needs, producing a full {@link WasSyncPort}.
|
|
9
|
-
*
|
|
10
|
-
* Why the feed body rather than a `GET` ETag header: the mutable-head model
|
|
11
|
-
* settles a 412 by re-reading the resource's current `version` and comparing
|
|
12
|
-
* payloads (LWW). A raw `GET` reads that `version` from the response's `etag`
|
|
13
|
-
* header -- which a browser hides on a CROSS-ORIGIN response unless the server
|
|
14
|
-
* sends `Access-Control-Expose-Headers: etag`. A server that does not expose it
|
|
15
|
-
* would make a cross-origin re-read report `version: 0`, so the loser of a push
|
|
16
|
-
* race would re-push forever with a stale `If-Match` and never converge. The
|
|
17
|
-
* `changes` feed carries `version` (and `data`, `_deleted`, ...) in the JSON
|
|
18
|
-
* BODY, which CORS never strips, so resolving the master from the feed is both
|
|
19
|
-
* correct and origin-independent. Normal pull/push are unaffected (they already
|
|
20
|
-
* read `version` from the feed body).
|
|
21
|
-
*/
|
|
22
|
-
import type { WasSyncBasePort, WasSyncPort } from './types.js';
|
|
23
|
-
/**
|
|
24
|
-
* Wraps a base port with a `get` that resolves the master state from the changes
|
|
25
|
-
* feed. `query`, `putContent`, `deleteContent`, and `putMeta` pass straight
|
|
26
|
-
* through.
|
|
27
|
-
*
|
|
28
|
-
* A walk starts at the feed's origin, so resolving one resource pages past many
|
|
29
|
-
* others. With a per-push-batch `cache` the walk memoizes every one of them, and
|
|
30
|
-
* a row that finds a walk already running waits for it rather than starting a
|
|
31
|
-
* second -- so a batch of k conflicting rows costs one feed scan instead of k
|
|
32
|
-
* concurrent ones. Without a `cache` the behavior is exactly as before.
|
|
33
|
-
*
|
|
34
|
-
* @param base {WasSyncBasePort}
|
|
35
|
-
* @returns {WasSyncPort}
|
|
36
|
-
*/
|
|
37
|
-
export declare function withFeedMasterRead(base: WasSyncBasePort): WasSyncPort;
|
|
38
|
-
//# sourceMappingURL=feedMasterPort.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"feedMasterPort.d.ts","sourceRoot":"","sources":["../../src/sync/feedMasterPort.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAIV,eAAe,EACf,WAAW,EAEZ,MAAM,YAAY,CAAA;AAmFnB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,eAAe,GAAG,WAAW,CAgCrE"}
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { copyOptionalBodyFields } from './types.js';
|
|
2
|
-
// A generous page cap so a pathological feed cannot spin forever; conflicts are
|
|
3
|
-
// rare and the dev/e2e feed is small, so a full scan is acceptable.
|
|
4
|
-
const MAX_PAGES = 50;
|
|
5
|
-
const PAGE_SIZE = 500;
|
|
6
|
-
/**
|
|
7
|
-
* Builds a `MasterState` from a `changes`-feed document (all fields in-body).
|
|
8
|
-
*/
|
|
9
|
-
function toMasterState(doc) {
|
|
10
|
-
const master = {
|
|
11
|
-
version: doc.version,
|
|
12
|
-
updatedAt: doc.updatedAt,
|
|
13
|
-
deleted: doc._deleted
|
|
14
|
-
};
|
|
15
|
-
copyOptionalBodyFields({ source: doc, target: master });
|
|
16
|
-
return master;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Walks the changes feed from its origin looking for one resource, memoizing
|
|
20
|
-
* every document it pages past when the caller supplied a batch `cache`.
|
|
21
|
-
*
|
|
22
|
-
* @param options {object}
|
|
23
|
-
* @param options.base {WasSyncBasePort}
|
|
24
|
-
* @param options.id {string}
|
|
25
|
-
* @param [options.cache] {MasterReadCache}
|
|
26
|
-
* @returns {Promise<MasterState | null>}
|
|
27
|
-
*/
|
|
28
|
-
async function walkFeedFor({ base, id, cache }) {
|
|
29
|
-
let checkpoint;
|
|
30
|
-
for (let page = 0; page < MAX_PAGES; page++) {
|
|
31
|
-
const { documents, checkpoint: next } = await base.query({
|
|
32
|
-
...(checkpoint !== undefined && { checkpoint }),
|
|
33
|
-
limit: PAGE_SIZE
|
|
34
|
-
});
|
|
35
|
-
const byId = new Map(documents.map(doc => [doc.id, doc]));
|
|
36
|
-
if (cache !== undefined) {
|
|
37
|
-
for (const [docId, doc] of byId) {
|
|
38
|
-
cache.byId.set(docId, toMasterState(doc));
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
const found = byId.get(id);
|
|
42
|
-
if (found) {
|
|
43
|
-
return toMasterState(found);
|
|
44
|
-
}
|
|
45
|
-
// Reached the end of the feed without finding it: the resource is
|
|
46
|
-
// genuinely absent (a delete/delete race), so report `null` and let the
|
|
47
|
-
// conflict assembler synthesize a tombstone. Only THIS id is memoized as
|
|
48
|
-
// absent, never "every id this completed walk did not see": a row of the
|
|
49
|
-
// same batch may have created its resource after this walk read the feed,
|
|
50
|
-
// and answering `null` for it would fabricate a tombstone and drop a live
|
|
51
|
-
// payload -- exactly what the scan-budget throw below guards against.
|
|
52
|
-
if (next === null || documents.length === 0) {
|
|
53
|
-
cache?.byId.set(id, null);
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
checkpoint = next;
|
|
57
|
-
}
|
|
58
|
-
// The page budget ran out before the feed's end. The feed is keyset-ordered
|
|
59
|
-
// ascending on `updatedAt`, so a just-conflicted resource sits at the tail --
|
|
60
|
-
// the least-reachable position -- and may well be past the cap. Reporting
|
|
61
|
-
// `null` here would fabricate a false tombstone in the conflict assembler and
|
|
62
|
-
// drop the winner's payload. Throw instead: RxDB treats a thrown push-handler
|
|
63
|
-
// error as retryable, so the whole replication cycle retries rather than
|
|
64
|
-
// diverging.
|
|
65
|
-
throw new Error(`Feed master re-read for resource "${id}" exhausted its ` +
|
|
66
|
-
`${MAX_PAGES}-page scan budget without reaching the end of the ` +
|
|
67
|
-
`changes feed; retrying.`);
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Wraps a base port with a `get` that resolves the master state from the changes
|
|
71
|
-
* feed. `query`, `putContent`, `deleteContent`, and `putMeta` pass straight
|
|
72
|
-
* through.
|
|
73
|
-
*
|
|
74
|
-
* A walk starts at the feed's origin, so resolving one resource pages past many
|
|
75
|
-
* others. With a per-push-batch `cache` the walk memoizes every one of them, and
|
|
76
|
-
* a row that finds a walk already running waits for it rather than starting a
|
|
77
|
-
* second -- so a batch of k conflicting rows costs one feed scan instead of k
|
|
78
|
-
* concurrent ones. Without a `cache` the behavior is exactly as before.
|
|
79
|
-
*
|
|
80
|
-
* @param base {WasSyncBasePort}
|
|
81
|
-
* @returns {WasSyncPort}
|
|
82
|
-
*/
|
|
83
|
-
export function withFeedMasterRead(base) {
|
|
84
|
-
return {
|
|
85
|
-
query: base.query.bind(base),
|
|
86
|
-
putContent: base.putContent.bind(base),
|
|
87
|
-
deleteContent: base.deleteContent.bind(base),
|
|
88
|
-
putMeta: base.putMeta.bind(base),
|
|
89
|
-
async get({ id, cache }) {
|
|
90
|
-
if (cache === undefined) {
|
|
91
|
-
return walkFeedFor({ base, id });
|
|
92
|
-
}
|
|
93
|
-
// Wait out whatever walk this batch already has running -- it pages past
|
|
94
|
-
// every resource, so it may well answer this read -- then re-check the
|
|
95
|
-
// memo. Looped because the walk we waited for can be followed by another.
|
|
96
|
-
while (cache.inFlight !== null) {
|
|
97
|
-
await cache.inFlight;
|
|
98
|
-
}
|
|
99
|
-
const memoized = cache.byId.get(id);
|
|
100
|
-
if (memoized !== undefined) {
|
|
101
|
-
return memoized;
|
|
102
|
-
}
|
|
103
|
-
// Publish this walk for the batch's other rows. Nothing above awaits
|
|
104
|
-
// between the memo miss and here, so two rows can never both get past it.
|
|
105
|
-
// The published promise SETTLES either way: a walk that threw is this
|
|
106
|
-
// row's error to propagate, not a waiting row's.
|
|
107
|
-
const walk = walkFeedFor({ base, id, cache });
|
|
108
|
-
const clear = () => {
|
|
109
|
-
cache.inFlight = null;
|
|
110
|
-
};
|
|
111
|
-
cache.inFlight = walk.then(clear, clear);
|
|
112
|
-
return walk;
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
//# sourceMappingURL=feedMasterPort.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"feedMasterPort.js","sourceRoot":"","sources":["../../src/sync/feedMasterPort.ts"],"names":[],"mappings":"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"}
|
package/dist/sync/index.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* Public surface of the collection-agnostic WAS replication layer. Framework-
|
|
6
|
-
* agnostic: consumers supply an RxDB collection and a {@link WasSyncPort};
|
|
7
|
-
* nothing in the core adapter imports React. The port adapter
|
|
8
|
-
* (`createWasSyncPort`) and the document cipher (`createDocCipher`) bridge to
|
|
9
|
-
* `@interop/was-client`.
|
|
10
|
-
*/
|
|
11
|
-
export { createWasReplication } from './wasReplication.js';
|
|
12
|
-
export { syncedDocSchema } from './syncedDocSchema.js';
|
|
13
|
-
export { createPullHandler, wireDocToRxDoc } from './changesQuery.js';
|
|
14
|
-
export { createPushHandler, type PushWriteAck } from './pushWrites.js';
|
|
15
|
-
export { WasSyncConflictError, WasSyncAuthError, type Json, type SyncCheckpoint, type WireDoc, type SyncedDoc, type MasterState, type MasterReadCache, type WasSyncBasePort, type WasSyncPort } from './types.js';
|
|
16
|
-
export { createWasSyncPort } from './wasSyncPort.js';
|
|
17
|
-
export { withFeedMasterRead } from './feedMasterPort.js';
|
|
18
|
-
export { createDocCipher, createPlaintextDocCodec, createUnprovisionedDocCipher, isUnknownEpochError, type DocCipher } from './docCipher.js';
|
|
19
|
-
export { makeLwwConflictHandler } from './lwwConflictHandler.js';
|
|
20
|
-
export { lwwFields, remotePayloadWins, type LwwFields } from './lww.js';
|
|
21
|
-
export { errorStatus, errorMessage, formatEtag, isEncryptedEnvelope } from '@interop/was-client/sync';
|
|
22
|
-
export { hasKeyEpochs, epochRostersEqual } from '@interop/was-client/edv';
|
|
23
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/sync/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;GAMG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACrE,OAAO,EAAE,iBAAiB,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACtE,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,KAAK,IAAI,EACT,KAAK,cAAc,EACnB,KAAK,OAAO,EACZ,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,WAAW,EACjB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,4BAA4B,EAC5B,mBAAmB,EACnB,KAAK,SAAS,EACf,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AAChE,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,KAAK,SAAS,EAAE,MAAM,UAAU,CAAA;AAGvE,OAAO,EACL,WAAW,EACX,YAAY,EACZ,UAAU,EACV,mBAAmB,EACpB,MAAM,0BAA0B,CAAA;AAGjC,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA"}
|
package/dist/sync/index.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* Public surface of the collection-agnostic WAS replication layer. Framework-
|
|
6
|
-
* agnostic: consumers supply an RxDB collection and a {@link WasSyncPort};
|
|
7
|
-
* nothing in the core adapter imports React. The port adapter
|
|
8
|
-
* (`createWasSyncPort`) and the document cipher (`createDocCipher`) bridge to
|
|
9
|
-
* `@interop/was-client`.
|
|
10
|
-
*/
|
|
11
|
-
export { createWasReplication } from './wasReplication.js';
|
|
12
|
-
export { syncedDocSchema } from './syncedDocSchema.js';
|
|
13
|
-
export { createPullHandler, wireDocToRxDoc } from './changesQuery.js';
|
|
14
|
-
export { createPushHandler } from './pushWrites.js';
|
|
15
|
-
export { WasSyncConflictError, WasSyncAuthError } from './types.js';
|
|
16
|
-
export { createWasSyncPort } from './wasSyncPort.js';
|
|
17
|
-
export { withFeedMasterRead } from './feedMasterPort.js';
|
|
18
|
-
export { createDocCipher, createPlaintextDocCodec, createUnprovisionedDocCipher, isUnknownEpochError } from './docCipher.js';
|
|
19
|
-
export { makeLwwConflictHandler } from './lwwConflictHandler.js';
|
|
20
|
-
export { lwwFields, remotePayloadWins } from './lww.js';
|
|
21
|
-
// Owned by the client's sync subpath and re-exported verbatim, so the names this
|
|
22
|
-
// package's consumers already import stay put.
|
|
23
|
-
export { errorStatus, errorMessage, formatEtag, isEncryptedEnvelope } from '@interop/was-client/sync';
|
|
24
|
-
// The crypto-free predicates over a collection's encryption descriptor, owned by
|
|
25
|
-
// the client's EDV subpath and re-exported verbatim for the same reason.
|
|
26
|
-
export { hasKeyEpochs, epochRostersEqual } from '@interop/was-client/edv';
|
|
27
|
-
//# sourceMappingURL=index.js.map
|
package/dist/sync/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;GAMG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACrE,OAAO,EAAE,iBAAiB,EAAqB,MAAM,iBAAiB,CAAA;AACtE,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EASjB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,4BAA4B,EAC5B,mBAAmB,EAEpB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AAChE,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAkB,MAAM,UAAU,CAAA;AACvE,iFAAiF;AACjF,+CAA+C;AAC/C,OAAO,EACL,WAAW,EACX,YAAY,EACZ,UAAU,EACV,mBAAmB,EACpB,MAAM,0BAA0B,CAAA;AACjC,iFAAiF;AACjF,yEAAyE;AACzE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA"}
|
package/dist/sync/lww.d.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* The last-write-wins tiebreak for a mutable head document. Pure -- no React, no
|
|
6
|
-
* storage imports -- so both replicas run this identical rule against the same
|
|
7
|
-
* two payloads and converge on the same winner with no coordination.
|
|
8
|
-
*
|
|
9
|
-
* `updatedAt` (an ISO-8601 string) decides, compared CHRONOLOGICALLY (parsed to
|
|
10
|
-
* epoch ms, never lexically -- `updatedAt` is app-owned, so mixed precision like
|
|
11
|
-
* `...05Z` vs `...05.400Z`, or a `+00:00` offset, must still order by instant);
|
|
12
|
-
* `writerId` breaks an exact-instant tie deterministically. Both fields live in
|
|
13
|
-
* the payload (never the envelope-level checkpoint `updatedAt`).
|
|
14
|
-
*/
|
|
15
|
-
/**
|
|
16
|
-
* The two fields the last-write-wins rule reads off a payload: the app-owned
|
|
17
|
-
* ISO-8601 edit stamp and the writing client's id (the exact-instant
|
|
18
|
-
* tiebreaker). Read off a doc with {@link lwwFields}; compared with
|
|
19
|
-
* {@link remotePayloadWins}.
|
|
20
|
-
*/
|
|
21
|
-
export interface LwwFields {
|
|
22
|
-
updatedAt: string;
|
|
23
|
-
writerId: string;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Whether the remote payload wins over the local one under last-write-wins.
|
|
27
|
-
* The chronologically later `updatedAt` wins; on an exact-instant tie the
|
|
28
|
-
* lexically greater `writerId` wins (an arbitrary but deterministic,
|
|
29
|
-
* replica-independent choice).
|
|
30
|
-
*
|
|
31
|
-
* Unparseable `updatedAt` values are rejected as losers: a side whose stamp
|
|
32
|
-
* does not parse loses to one whose stamp does (a replica-independent rule, so
|
|
33
|
-
* every device converges on the same winner). When neither side parses, the
|
|
34
|
-
* lexical compare of the raw strings is the deterministic fallback.
|
|
35
|
-
*
|
|
36
|
-
* @param remote {LwwFields}
|
|
37
|
-
* @param local {LwwFields}
|
|
38
|
-
* @returns {boolean} true if the remote payload should replace the local one
|
|
39
|
-
*/
|
|
40
|
-
export declare function remotePayloadWins(remote: LwwFields, local: LwwFields): boolean;
|
|
41
|
-
/**
|
|
42
|
-
* Reads the LWW fields off a doc when it carries them. Storage payloads are
|
|
43
|
-
* generic over `{ id: string }`, so docs without `updatedAt`/`writerId` are
|
|
44
|
-
* legal; callers fall back to their own rule for those.
|
|
45
|
-
*
|
|
46
|
-
* @param doc {unknown}
|
|
47
|
-
* @returns {LwwFields | null}
|
|
48
|
-
*/
|
|
49
|
-
export declare function lwwFields(doc: unknown): LwwFields | null;
|
|
50
|
-
//# sourceMappingURL=lww.d.ts.map
|
package/dist/sync/lww.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"lww.d.ts","sourceRoot":"","sources":["../../src/sync/lww.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;GAUG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAUD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,SAAS,GACf,OAAO,CAeT;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,SAAS,GAAG,IAAI,CAQxD"}
|
package/dist/sync/lww.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* The last-write-wins tiebreak for a mutable head document. Pure -- no React, no
|
|
6
|
-
* storage imports -- so both replicas run this identical rule against the same
|
|
7
|
-
* two payloads and converge on the same winner with no coordination.
|
|
8
|
-
*
|
|
9
|
-
* `updatedAt` (an ISO-8601 string) decides, compared CHRONOLOGICALLY (parsed to
|
|
10
|
-
* epoch ms, never lexically -- `updatedAt` is app-owned, so mixed precision like
|
|
11
|
-
* `...05Z` vs `...05.400Z`, or a `+00:00` offset, must still order by instant);
|
|
12
|
-
* `writerId` breaks an exact-instant tie deterministically. Both fields live in
|
|
13
|
-
* the payload (never the envelope-level checkpoint `updatedAt`).
|
|
14
|
-
*/
|
|
15
|
-
/**
|
|
16
|
-
* Parses an ISO-8601 `updatedAt` into epoch ms, or `null` when unparseable.
|
|
17
|
-
*/
|
|
18
|
-
function parseInstant(updatedAt) {
|
|
19
|
-
const ms = Date.parse(updatedAt);
|
|
20
|
-
return Number.isNaN(ms) ? null : ms;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Whether the remote payload wins over the local one under last-write-wins.
|
|
24
|
-
* The chronologically later `updatedAt` wins; on an exact-instant tie the
|
|
25
|
-
* lexically greater `writerId` wins (an arbitrary but deterministic,
|
|
26
|
-
* replica-independent choice).
|
|
27
|
-
*
|
|
28
|
-
* Unparseable `updatedAt` values are rejected as losers: a side whose stamp
|
|
29
|
-
* does not parse loses to one whose stamp does (a replica-independent rule, so
|
|
30
|
-
* every device converges on the same winner). When neither side parses, the
|
|
31
|
-
* lexical compare of the raw strings is the deterministic fallback.
|
|
32
|
-
*
|
|
33
|
-
* @param remote {LwwFields}
|
|
34
|
-
* @param local {LwwFields}
|
|
35
|
-
* @returns {boolean} true if the remote payload should replace the local one
|
|
36
|
-
*/
|
|
37
|
-
export function remotePayloadWins(remote, local) {
|
|
38
|
-
const remoteMs = parseInstant(remote.updatedAt);
|
|
39
|
-
const localMs = parseInstant(local.updatedAt);
|
|
40
|
-
if (remoteMs !== null && localMs !== null) {
|
|
41
|
-
if (remoteMs !== localMs) {
|
|
42
|
-
return remoteMs > localMs;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
else if (remoteMs !== null || localMs !== null) {
|
|
46
|
-
// Exactly one side parses: the parseable stamp wins.
|
|
47
|
-
return remoteMs !== null;
|
|
48
|
-
}
|
|
49
|
-
else if (remote.updatedAt !== local.updatedAt) {
|
|
50
|
-
// Neither parses: fall back to the deterministic lexical compare.
|
|
51
|
-
return remote.updatedAt > local.updatedAt;
|
|
52
|
-
}
|
|
53
|
-
return remote.writerId > local.writerId;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Reads the LWW fields off a doc when it carries them. Storage payloads are
|
|
57
|
-
* generic over `{ id: string }`, so docs without `updatedAt`/`writerId` are
|
|
58
|
-
* legal; callers fall back to their own rule for those.
|
|
59
|
-
*
|
|
60
|
-
* @param doc {unknown}
|
|
61
|
-
* @returns {LwwFields | null}
|
|
62
|
-
*/
|
|
63
|
-
export function lwwFields(doc) {
|
|
64
|
-
const { updatedAt, writerId } = doc;
|
|
65
|
-
return typeof updatedAt === 'string' && typeof writerId === 'string'
|
|
66
|
-
? { updatedAt, writerId }
|
|
67
|
-
: null;
|
|
68
|
-
}
|
|
69
|
-
//# sourceMappingURL=lww.js.map
|
package/dist/sync/lww.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"lww.js","sourceRoot":"","sources":["../../src/sync/lww.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;GAUG;AAaH;;GAEG;AACH,SAAS,YAAY,CAAC,SAAiB;IACrC,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;IAChC,OAAO,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAA;AACrC,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAiB,EACjB,KAAgB;IAEhB,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IAC/C,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;IAC7C,IAAI,QAAQ,KAAK,IAAI,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QAC1C,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;YACzB,OAAO,QAAQ,GAAG,OAAO,CAAA;QAC3B,CAAC;IACH,CAAC;SAAM,IAAI,QAAQ,KAAK,IAAI,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACjD,qDAAqD;QACrD,OAAO,QAAQ,KAAK,IAAI,CAAA;IAC1B,CAAC;SAAM,IAAI,MAAM,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS,EAAE,CAAC;QAChD,kEAAkE;QAClE,OAAO,MAAM,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,CAAA;IAC3C,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAA;AACzC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,GAAY;IACpC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,GAG/B,CAAA;IACD,OAAO,OAAO,SAAS,KAAK,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ;QAClE,CAAC,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE;QACzB,CAAC,CAAC,IAAI,CAAA;AACV,CAAC"}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* The RxDB conflict handler for a mutable-head (LWW) collection. RxDB's default
|
|
6
|
-
* handler always drops the local fork and keeps the remote master, which is
|
|
7
|
-
* correct for content-addressed (immutable-per-id) collections but wrong here:
|
|
8
|
-
* every entity is a mutable head that two devices can edit concurrently, so a
|
|
9
|
-
* genuine content conflict must be settled by last-write-wins on the payload's
|
|
10
|
-
* own `updatedAt` (writerId tiebreak) -- exactly the rule two offline replicas
|
|
11
|
-
* apply independently to converge.
|
|
12
|
-
*
|
|
13
|
-
* The wrinkle is that the conflicting bodies are EDV envelopes (ciphertext), so
|
|
14
|
-
* `resolve` must decrypt both sides through this collection's cipher before it
|
|
15
|
-
* can compare the plaintext `updatedAt` / `writerId`. `isEqual` stays cheap and
|
|
16
|
-
* synchronous (a structural compare of the opaque bodies), as RxDB requires.
|
|
17
|
-
*
|
|
18
|
-
* Convergence: the server holds ONE winner of the push race as `realMasterState`;
|
|
19
|
-
* every replica compares that same master against its own local edit, and the
|
|
20
|
-
* `payloadWins` comparator is a total order over `(updatedAt, writerId)`, so the
|
|
21
|
-
* globally-latest payload wins on every replica with no coordination.
|
|
22
|
-
*
|
|
23
|
-
* The resolution rules, in order:
|
|
24
|
-
*
|
|
25
|
-
* 1. Version-only conflict: when the real master's whole content (`data` +
|
|
26
|
-
* `custom` + `_deleted`) still equals the assumed master's, the server holds
|
|
27
|
-
* nothing newer than what this replica last synced -- the 412 came from a
|
|
28
|
-
* stale `If-Match` (typically our own earlier write racing its feed echo).
|
|
29
|
-
* The local state (edit or tombstone) is re-asserted and re-pushed against
|
|
30
|
-
* the corrected version. Without this rule a local delete would be dropped by
|
|
31
|
-
* rule 5 and the entity would silently resurrect. `custom` MUST be part of
|
|
32
|
-
* this comparison, or a concurrent metadata-only edit committed on the server
|
|
33
|
-
* would be misclassified here and clobbered (rule 2 is what settles it).
|
|
34
|
-
* 2. Metadata conflict: `data` and `_deleted` are unchanged from the assumed
|
|
35
|
-
* master but `custom` differs -- a metadata-only edit committed on the server
|
|
36
|
-
* since this replica last synced (another device won the `/meta` race).
|
|
37
|
-
* Metadata carries no LWW timestamp of its own (the payload `updatedAt` lives
|
|
38
|
-
* in the encrypted `data`, which is equal on both sides here), so there is no
|
|
39
|
-
* payload to compare; the sound, replica-independent default is that the
|
|
40
|
-
* server-committed state wins -- the real master is adopted for `custom`.
|
|
41
|
-
* Without this rule the equal-`data` case would fall through to rule 3, where
|
|
42
|
-
* the two payloads compare equal and the tie keeps the local (stale) `custom`,
|
|
43
|
-
* silently clobbering the committed metadata.
|
|
44
|
-
* 3. An UNDECRYPTABLE side (the decrypt threw -- e.g. an envelope written under
|
|
45
|
-
* a key epoch this device has not seen) is never scored as the loser: it is
|
|
46
|
-
* presumed newer, not absent. An undecryptable master is adopted (never
|
|
47
|
-
* re-pushed over with the possibly-older local payload); an undecryptable
|
|
48
|
-
* local row is re-asserted (the user's edit is not silently dropped); both
|
|
49
|
-
* undecryptable adopts the master (deterministic and convergent). Each case
|
|
50
|
-
* is logged -- distinguishable from the intended tombstone/absent-body
|
|
51
|
-
* `null` the remaining rules were written for.
|
|
52
|
-
* 4. Both sides carry an LWW payload: pure payload LWW via `payloadWins`.
|
|
53
|
-
* 5. A live local edit vs an incomparable remote (e.g. a remote tombstone):
|
|
54
|
-
* the edit wins and is re-pushed (resurrection).
|
|
55
|
-
* 6. Everything else -- a local tombstone vs a REAL remote content change, or
|
|
56
|
-
* both sides incomparable: the master wins. Together with rule 5 this makes
|
|
57
|
-
* the delete-vs-concurrent-edit rule "the edit wins" on every replica: a
|
|
58
|
-
* tombstone carries no LWW payload of its own, so a genuine racing edit
|
|
59
|
-
* deterministically survives, whichever write reached the server first.
|
|
60
|
-
*/
|
|
61
|
-
import type { WithDeleted } from 'rxdb/plugins/core';
|
|
62
|
-
import type { Json, SyncedDoc } from './types.js';
|
|
63
|
-
import { type LwwFields } from './lww.js';
|
|
64
|
-
/**
|
|
65
|
-
* Builds an RxDB conflict handler that settles content conflicts by payload LWW,
|
|
66
|
-
* decrypting through the supplied per-collection `decrypt`.
|
|
67
|
-
*
|
|
68
|
-
* @param decrypt {(envelope: Json) => Promise<Json>} this collection's decrypt
|
|
69
|
-
* @param [payloadWins] {(remote: LwwFields, local: LwwFields) => boolean}
|
|
70
|
-
* the total-order comparator deciding whether the remote payload replaces the
|
|
71
|
-
* local one; defaults to {@link remotePayloadWins} (later `updatedAt` wins,
|
|
72
|
-
* `writerId` breaks a tie)
|
|
73
|
-
* @returns {import('rxdb/plugins/core').RxConflictHandler<SyncedDoc>}
|
|
74
|
-
*/
|
|
75
|
-
export declare function makeLwwConflictHandler(decrypt: (envelope: Json) => Promise<Json>, payloadWins?: (remote: LwwFields, local: LwwFields) => boolean): {
|
|
76
|
-
isEqual(a: WithDeleted<SyncedDoc>, b: WithDeleted<SyncedDoc>): boolean;
|
|
77
|
-
resolve({ realMasterState, newDocumentState, assumedMasterState }: {
|
|
78
|
-
realMasterState: WithDeleted<SyncedDoc>;
|
|
79
|
-
newDocumentState: WithDeleted<SyncedDoc>;
|
|
80
|
-
assumedMasterState?: WithDeleted<SyncedDoc>;
|
|
81
|
-
}): Promise<WithDeleted<SyncedDoc>>;
|
|
82
|
-
};
|
|
83
|
-
//# sourceMappingURL=lwwConflictHandler.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"lwwConflictHandler.d.ts","sourceRoot":"","sources":["../../src/sync/lwwConflictHandler.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAEjD,OAAO,EAAgC,KAAK,SAAS,EAAE,MAAM,UAAU,CAAA;AAoCvE;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,CAAC,QAAQ,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,EAC1C,WAAW,GAAE,CACX,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,SAAS,KACb,OAA2B;eAQnB,WAAW,CAAC,SAAS,CAAC,KAAK,WAAW,CAAC,SAAS,CAAC,GAAG,OAAO;uEAcnE;QACD,eAAe,EAAE,WAAW,CAAC,SAAS,CAAC,CAAA;QACvC,gBAAgB,EAAE,WAAW,CAAC,SAAS,CAAC,CAAA;QACxC,kBAAkB,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAA;KAC5C,GAAG,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;EAiFtC"}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { bodiesEqual } from './types.js';
|
|
2
|
-
import { lwwFields, remotePayloadWins } from './lww.js';
|
|
3
|
-
/**
|
|
4
|
-
* Decrypts one side's envelope into its LWW comparability (see {@link LwwSide}).
|
|
5
|
-
* A payload carrying no LWW stamp reads as `none`, the same as a tombstone or an
|
|
6
|
-
* absent body: nothing there to compare.
|
|
7
|
-
*/
|
|
8
|
-
async function lwwFieldsOf(doc, decrypt) {
|
|
9
|
-
if (doc._deleted || doc.data === undefined) {
|
|
10
|
-
return { kind: 'none' };
|
|
11
|
-
}
|
|
12
|
-
try {
|
|
13
|
-
const payload = lwwFields(await decrypt(doc.data));
|
|
14
|
-
return payload === null ? { kind: 'none' } : { kind: 'payload', payload };
|
|
15
|
-
}
|
|
16
|
-
catch (err) {
|
|
17
|
-
return { kind: 'undecryptable', err };
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Builds an RxDB conflict handler that settles content conflicts by payload LWW,
|
|
22
|
-
* decrypting through the supplied per-collection `decrypt`.
|
|
23
|
-
*
|
|
24
|
-
* @param decrypt {(envelope: Json) => Promise<Json>} this collection's decrypt
|
|
25
|
-
* @param [payloadWins] {(remote: LwwFields, local: LwwFields) => boolean}
|
|
26
|
-
* the total-order comparator deciding whether the remote payload replaces the
|
|
27
|
-
* local one; defaults to {@link remotePayloadWins} (later `updatedAt` wins,
|
|
28
|
-
* `writerId` breaks a tie)
|
|
29
|
-
* @returns {import('rxdb/plugins/core').RxConflictHandler<SyncedDoc>}
|
|
30
|
-
*/
|
|
31
|
-
export function makeLwwConflictHandler(decrypt, payloadWins = remotePayloadWins) {
|
|
32
|
-
return {
|
|
33
|
-
// Non-async and fast, as RxDB requires. The server revisions (`version` /
|
|
34
|
-
// `metaVersion`) participate deliberately: our own write's feed echo comes
|
|
35
|
-
// back byte-identical but one revision ahead, and it must NOT compare
|
|
36
|
-
// equal, or the higher version is never adopted and every later
|
|
37
|
-
// conditional write sends a stale `If-Match` (a guaranteed 412).
|
|
38
|
-
isEqual(a, b) {
|
|
39
|
-
return (a._deleted === b._deleted &&
|
|
40
|
-
a.version === b.version &&
|
|
41
|
-
a.metaVersion === b.metaVersion &&
|
|
42
|
-
bodiesEqual(a.data, b.data) &&
|
|
43
|
-
bodiesEqual(a.custom, b.custom));
|
|
44
|
-
},
|
|
45
|
-
async resolve({ realMasterState, newDocumentState, assumedMasterState }) {
|
|
46
|
-
// Rule 1 -- version-only conflict: the master's whole content (`data` +
|
|
47
|
-
// `custom`) is exactly what this replica last synced (only the revision
|
|
48
|
-
// moved, e.g. our own write racing its feed echo), so nothing remote is
|
|
49
|
-
// actually newer. Re-assert the local state -- crucially including a local
|
|
50
|
-
// TOMBSTONE, which rule 5 would otherwise drop (the silent-resurrection
|
|
51
|
-
// bug). `custom` is part of this equality so a concurrent metadata-only
|
|
52
|
-
// edit is NOT misclassified here (rule 2 handles it).
|
|
53
|
-
if (assumedMasterState !== undefined &&
|
|
54
|
-
realMasterState._deleted === assumedMasterState._deleted &&
|
|
55
|
-
bodiesEqual(realMasterState.data, assumedMasterState.data) &&
|
|
56
|
-
bodiesEqual(realMasterState.custom, assumedMasterState.custom)) {
|
|
57
|
-
return newDocumentState;
|
|
58
|
-
}
|
|
59
|
-
// Rule 2 -- metadata conflict: `data` and `_deleted` are unchanged from
|
|
60
|
-
// the assumed master, but `custom` moved on the server (a metadata-only
|
|
61
|
-
// edit that won the `/meta` race on another device). Metadata has no LWW
|
|
62
|
-
// timestamp of its own -- the payload `updatedAt` lives in `data`, equal
|
|
63
|
-
// on both sides here -- so the sound, replica-independent default is that
|
|
64
|
-
// the server-committed state wins: adopt the real master for `custom`.
|
|
65
|
-
// Without this rule the equal-`data` case would reach rule 3 with two
|
|
66
|
-
// equal payloads and the tie would keep the local (stale) metadata.
|
|
67
|
-
if (assumedMasterState !== undefined &&
|
|
68
|
-
realMasterState._deleted === assumedMasterState._deleted &&
|
|
69
|
-
bodiesEqual(realMasterState.data, assumedMasterState.data) &&
|
|
70
|
-
!bodiesEqual(realMasterState.custom, assumedMasterState.custom)) {
|
|
71
|
-
return realMasterState;
|
|
72
|
-
}
|
|
73
|
-
const [remote, local] = await Promise.all([
|
|
74
|
-
lwwFieldsOf(realMasterState, decrypt),
|
|
75
|
-
lwwFieldsOf(newDocumentState, decrypt)
|
|
76
|
-
]);
|
|
77
|
-
// Rule 3 -- an undecryptable side is never scored as the loser: it is
|
|
78
|
-
// presumed newer (typically an envelope under an unseen key epoch), not
|
|
79
|
-
// absent. Logged so it is distinguishable from a genuine tombstone.
|
|
80
|
-
if (remote.kind === 'undecryptable' || local.kind === 'undecryptable') {
|
|
81
|
-
if (remote.kind === 'undecryptable' && local.kind !== 'undecryptable') {
|
|
82
|
-
console.warn('LWW conflict: the remote master did not decrypt; adopting it ' +
|
|
83
|
-
'rather than re-pushing the local payload over it.', remote.err);
|
|
84
|
-
return realMasterState;
|
|
85
|
-
}
|
|
86
|
-
if (local.kind === 'undecryptable' && remote.kind !== 'undecryptable') {
|
|
87
|
-
console.warn('LWW conflict: the local row did not decrypt; re-asserting it ' +
|
|
88
|
-
'rather than dropping the local edit for the master.', local.err);
|
|
89
|
-
return newDocumentState;
|
|
90
|
-
}
|
|
91
|
-
console.warn('LWW conflict: neither side decrypted; adopting the master ' +
|
|
92
|
-
'(deterministic and convergent).', remote.kind === 'undecryptable' ? remote.err : undefined);
|
|
93
|
-
return realMasterState;
|
|
94
|
-
}
|
|
95
|
-
// Rule 4 -- both sides comparable: pure payload LWW.
|
|
96
|
-
if (remote.kind === 'payload' && local.kind === 'payload') {
|
|
97
|
-
return payloadWins(remote.payload, local.payload)
|
|
98
|
-
? realMasterState
|
|
99
|
-
: newDocumentState;
|
|
100
|
-
}
|
|
101
|
-
// Rule 5 -- a live local edit against a remote tombstone (or otherwise
|
|
102
|
-
// incomparable remote): keep the edit (resurrect / re-assert the write).
|
|
103
|
-
if (local.kind === 'payload' && remote.kind === 'none') {
|
|
104
|
-
return newDocumentState;
|
|
105
|
-
}
|
|
106
|
-
// Rule 6 -- everything else (a local tombstone racing a REAL remote
|
|
107
|
-
// content change, or both incomparable): the master wins, which is
|
|
108
|
-
// deterministic and convergent across replicas (the edit survives the
|
|
109
|
-
// delete on every device).
|
|
110
|
-
return realMasterState;
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
//# sourceMappingURL=lwwConflictHandler.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"lwwConflictHandler.js","sourceRoot":"","sources":["../../src/sync/lwwConflictHandler.ts"],"names":[],"mappings":"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"}
|