@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.
Files changed (112) hide show
  1. package/README.md +28 -2
  2. package/dist/auth/loginFlow.d.ts.map +1 -1
  3. package/dist/auth/loginFlow.js +5 -5
  4. package/dist/auth/loginFlow.js.map +1 -1
  5. package/dist/auth/verifyResponse.d.ts.map +1 -1
  6. package/dist/auth/verifyResponse.js +7 -4
  7. package/dist/auth/verifyResponse.js.map +1 -1
  8. package/dist/dev/provisionDevGrants.js +1 -1
  9. package/dist/dev/provisionDevGrants.js.map +1 -1
  10. package/dist/index.d.ts +6 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +15 -2
  13. package/dist/index.js.map +1 -1
  14. package/dist/log.d.ts +48 -0
  15. package/dist/log.d.ts.map +1 -0
  16. package/dist/log.js +52 -0
  17. package/dist/log.js.map +1 -0
  18. package/dist/session/authStore.d.ts.map +1 -1
  19. package/dist/session/authStore.js +12 -10
  20. package/dist/session/authStore.js.map +1 -1
  21. package/dist/session/localWipe.d.ts.map +1 -1
  22. package/dist/session/localWipe.js +5 -4
  23. package/dist/session/localWipe.js.map +1 -1
  24. package/dist/storage/adopt.d.ts.map +1 -1
  25. package/dist/storage/adopt.js +2 -1
  26. package/dist/storage/adopt.js.map +1 -1
  27. package/dist/storage/descriptorManager.d.ts.map +1 -1
  28. package/dist/storage/descriptorManager.js +5 -4
  29. package/dist/storage/descriptorManager.js.map +1 -1
  30. package/dist/{sync → storage}/docCipher.d.ts +3 -29
  31. package/dist/storage/docCipher.d.ts.map +1 -0
  32. package/dist/{sync → storage}/docCipher.js +0 -20
  33. package/dist/storage/docCipher.js.map +1 -0
  34. package/dist/storage/entityStore.d.ts.map +1 -1
  35. package/dist/storage/entityStore.js +2 -1
  36. package/dist/storage/entityStore.js.map +1 -1
  37. package/dist/storage/localStore.d.ts +1 -1
  38. package/dist/storage/localStore.d.ts.map +1 -1
  39. package/dist/storage/localStore.js +15 -2
  40. package/dist/storage/localStore.js.map +1 -1
  41. package/dist/storage/sharedCollectionReader.d.ts +1 -1
  42. package/dist/storage/sharedCollectionReader.d.ts.map +1 -1
  43. package/dist/storage/sharedCollectionReader.js +11 -11
  44. package/dist/storage/sharedCollectionReader.js.map +1 -1
  45. package/dist/storage/storageContext.d.ts +1 -2
  46. package/dist/storage/storageContext.d.ts.map +1 -1
  47. package/dist/storage/storageContext.js.map +1 -1
  48. package/dist/storage/storageManager.d.ts +1 -1
  49. package/dist/storage/storageManager.d.ts.map +1 -1
  50. package/dist/storage/syncController.d.ts +30 -19
  51. package/dist/storage/syncController.d.ts.map +1 -1
  52. package/dist/storage/syncController.js +86 -157
  53. package/dist/storage/syncController.js.map +1 -1
  54. package/dist/storage/syncStatusStore.d.ts +8 -7
  55. package/dist/storage/syncStatusStore.d.ts.map +1 -1
  56. package/dist/storage/syncStatusStore.js.map +1 -1
  57. package/dist/storage/wasRemoteStore.d.ts.map +1 -1
  58. package/dist/storage/wasRemoteStore.js +6 -2
  59. package/dist/storage/wasRemoteStore.js.map +1 -1
  60. package/dist/storage/wasSync.d.ts +1 -1
  61. package/dist/storage/wasSync.d.ts.map +1 -1
  62. package/dist/storage/wasSync.js +28 -15
  63. package/dist/storage/wasSync.js.map +1 -1
  64. package/dist/{sync → storage}/wasSyncPort.d.ts +14 -8
  65. package/dist/storage/wasSyncPort.d.ts.map +1 -0
  66. package/dist/storage/wasSyncPort.js.map +1 -0
  67. package/dist/storage/writerId.d.ts +7 -13
  68. package/dist/storage/writerId.d.ts.map +1 -1
  69. package/dist/storage/writerId.js +30 -46
  70. package/dist/storage/writerId.js.map +1 -1
  71. package/package.json +16 -11
  72. package/dist/sync/changesQuery.d.ts +0 -44
  73. package/dist/sync/changesQuery.d.ts.map +0 -1
  74. package/dist/sync/changesQuery.js +0 -54
  75. package/dist/sync/changesQuery.js.map +0 -1
  76. package/dist/sync/docCipher.d.ts.map +0 -1
  77. package/dist/sync/docCipher.js.map +0 -1
  78. package/dist/sync/feedMasterPort.d.ts +0 -38
  79. package/dist/sync/feedMasterPort.d.ts.map +0 -1
  80. package/dist/sync/feedMasterPort.js +0 -116
  81. package/dist/sync/feedMasterPort.js.map +0 -1
  82. package/dist/sync/index.d.ts +0 -23
  83. package/dist/sync/index.d.ts.map +0 -1
  84. package/dist/sync/index.js +0 -27
  85. package/dist/sync/index.js.map +0 -1
  86. package/dist/sync/lww.d.ts +0 -50
  87. package/dist/sync/lww.d.ts.map +0 -1
  88. package/dist/sync/lww.js +0 -69
  89. package/dist/sync/lww.js.map +0 -1
  90. package/dist/sync/lwwConflictHandler.d.ts +0 -83
  91. package/dist/sync/lwwConflictHandler.d.ts.map +0 -1
  92. package/dist/sync/lwwConflictHandler.js +0 -114
  93. package/dist/sync/lwwConflictHandler.js.map +0 -1
  94. package/dist/sync/pushWrites.d.ts +0 -76
  95. package/dist/sync/pushWrites.d.ts.map +0 -1
  96. package/dist/sync/pushWrites.js +0 -221
  97. package/dist/sync/pushWrites.js.map +0 -1
  98. package/dist/sync/syncedDocSchema.d.ts +0 -22
  99. package/dist/sync/syncedDocSchema.d.ts.map +0 -1
  100. package/dist/sync/syncedDocSchema.js +0 -29
  101. package/dist/sync/syncedDocSchema.js.map +0 -1
  102. package/dist/sync/types.d.ts +0 -319
  103. package/dist/sync/types.d.ts.map +0 -1
  104. package/dist/sync/types.js +0 -58
  105. package/dist/sync/types.js.map +0 -1
  106. package/dist/sync/wasReplication.d.ts +0 -47
  107. package/dist/sync/wasReplication.d.ts.map +0 -1
  108. package/dist/sync/wasReplication.js +0 -92
  109. package/dist/sync/wasReplication.js.map +0 -1
  110. package/dist/sync/wasSyncPort.d.ts.map +0 -1
  111. package/dist/sync/wasSyncPort.js.map +0 -1
  112. /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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"}
@@ -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
@@ -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"}
@@ -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
@@ -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"}
@@ -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
@@ -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
@@ -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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"}