@interop/was-react 0.1.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 (223) hide show
  1. package/LICENSE.md +20 -0
  2. package/README.md +410 -0
  3. package/dist/auth/chapi.d.ts +43 -0
  4. package/dist/auth/chapi.d.ts.map +1 -0
  5. package/dist/auth/chapi.js +107 -0
  6. package/dist/auth/chapi.js.map +1 -0
  7. package/dist/auth/loginFlow.d.ts +93 -0
  8. package/dist/auth/loginFlow.d.ts.map +1 -0
  9. package/dist/auth/loginFlow.js +149 -0
  10. package/dist/auth/loginFlow.js.map +1 -0
  11. package/dist/auth/loginRequest.d.ts +66 -0
  12. package/dist/auth/loginRequest.d.ts.map +1 -0
  13. package/dist/auth/loginRequest.js +83 -0
  14. package/dist/auth/loginRequest.js.map +1 -0
  15. package/dist/auth/verifyResponse.d.ts +52 -0
  16. package/dist/auth/verifyResponse.d.ts.map +1 -0
  17. package/dist/auth/verifyResponse.js +138 -0
  18. package/dist/auth/verifyResponse.js.map +1 -0
  19. package/dist/auth/verifyResponse.test.d.ts +2 -0
  20. package/dist/auth/verifyResponse.test.d.ts.map +1 -0
  21. package/dist/auth/verifyResponse.test.js +282 -0
  22. package/dist/auth/verifyResponse.test.js.map +1 -0
  23. package/dist/auth/walletRequestTypes.d.ts +143 -0
  24. package/dist/auth/walletRequestTypes.d.ts.map +1 -0
  25. package/dist/auth/walletRequestTypes.js +2 -0
  26. package/dist/auth/walletRequestTypes.js.map +1 -0
  27. package/dist/config.d.ts +150 -0
  28. package/dist/config.d.ts.map +1 -0
  29. package/dist/config.js +27 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/dev/cli.d.ts +16 -0
  32. package/dist/dev/cli.d.ts.map +1 -0
  33. package/dist/dev/cli.js +122 -0
  34. package/dist/dev/cli.js.map +1 -0
  35. package/dist/dev/cli.test.d.ts +2 -0
  36. package/dist/dev/cli.test.d.ts.map +1 -0
  37. package/dist/dev/cli.test.js +39 -0
  38. package/dist/dev/cli.test.js.map +1 -0
  39. package/dist/dev/index.d.ts +10 -0
  40. package/dist/dev/index.d.ts.map +1 -0
  41. package/dist/dev/index.js +10 -0
  42. package/dist/dev/index.js.map +1 -0
  43. package/dist/dev/provisionDevGrants.d.ts +73 -0
  44. package/dist/dev/provisionDevGrants.d.ts.map +1 -0
  45. package/dist/dev/provisionDevGrants.js +176 -0
  46. package/dist/dev/provisionDevGrants.js.map +1 -0
  47. package/dist/grants.d.ts +59 -0
  48. package/dist/grants.d.ts.map +1 -0
  49. package/dist/grants.js +82 -0
  50. package/dist/grants.js.map +1 -0
  51. package/dist/grants.test.d.ts +2 -0
  52. package/dist/grants.test.d.ts.map +1 -0
  53. package/dist/grants.test.js +79 -0
  54. package/dist/grants.test.js.map +1 -0
  55. package/dist/identity/agents.d.ts +99 -0
  56. package/dist/identity/agents.d.ts.map +1 -0
  57. package/dist/identity/agents.js +132 -0
  58. package/dist/identity/agents.js.map +1 -0
  59. package/dist/identity/appSession.d.ts +78 -0
  60. package/dist/identity/appSession.d.ts.map +1 -0
  61. package/dist/identity/appSession.js +93 -0
  62. package/dist/identity/appSession.js.map +1 -0
  63. package/dist/identity/documentLoader.d.ts +20 -0
  64. package/dist/identity/documentLoader.d.ts.map +1 -0
  65. package/dist/identity/documentLoader.js +99 -0
  66. package/dist/identity/documentLoader.js.map +1 -0
  67. package/dist/identity/initAppSession.d.ts +27 -0
  68. package/dist/identity/initAppSession.d.ts.map +1 -0
  69. package/dist/identity/initAppSession.js +30 -0
  70. package/dist/identity/initAppSession.js.map +1 -0
  71. package/dist/identity/seedCredential.d.ts +74 -0
  72. package/dist/identity/seedCredential.d.ts.map +1 -0
  73. package/dist/identity/seedCredential.js +165 -0
  74. package/dist/identity/seedCredential.js.map +1 -0
  75. package/dist/identity/seedCredential.test.d.ts +2 -0
  76. package/dist/identity/seedCredential.test.d.ts.map +1 -0
  77. package/dist/identity/seedCredential.test.js +205 -0
  78. package/dist/identity/seedCredential.test.js.map +1 -0
  79. package/dist/identity/seedStore.d.ts +27 -0
  80. package/dist/identity/seedStore.d.ts.map +1 -0
  81. package/dist/identity/seedStore.js +74 -0
  82. package/dist/identity/seedStore.js.map +1 -0
  83. package/dist/index.d.ts +37 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +46 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/mui/ProtectedRoute.d.ts +10 -0
  88. package/dist/mui/ProtectedRoute.d.ts.map +1 -0
  89. package/dist/mui/ProtectedRoute.js +54 -0
  90. package/dist/mui/ProtectedRoute.js.map +1 -0
  91. package/dist/mui/ReconnectBanner.d.ts +2 -0
  92. package/dist/mui/ReconnectBanner.d.ts.map +1 -0
  93. package/dist/mui/ReconnectBanner.js +20 -0
  94. package/dist/mui/ReconnectBanner.js.map +1 -0
  95. package/dist/mui/SyncStatusChip.d.ts +2 -0
  96. package/dist/mui/SyncStatusChip.d.ts.map +1 -0
  97. package/dist/mui/SyncStatusChip.js +27 -0
  98. package/dist/mui/SyncStatusChip.js.map +1 -0
  99. package/dist/mui/index.d.ts +12 -0
  100. package/dist/mui/index.d.ts.map +1 -0
  101. package/dist/mui/index.js +12 -0
  102. package/dist/mui/index.js.map +1 -0
  103. package/dist/react/WasSessionProvider.d.ts +36 -0
  104. package/dist/react/WasSessionProvider.d.ts.map +1 -0
  105. package/dist/react/WasSessionProvider.js +35 -0
  106. package/dist/react/WasSessionProvider.js.map +1 -0
  107. package/dist/react/hooks.d.ts +66 -0
  108. package/dist/react/hooks.d.ts.map +1 -0
  109. package/dist/react/hooks.js +121 -0
  110. package/dist/react/hooks.js.map +1 -0
  111. package/dist/react/index.d.ts +11 -0
  112. package/dist/react/index.d.ts.map +1 -0
  113. package/dist/react/index.js +11 -0
  114. package/dist/react/index.js.map +1 -0
  115. package/dist/session/appReadyStore.d.ts +10 -0
  116. package/dist/session/appReadyStore.d.ts.map +1 -0
  117. package/dist/session/appReadyStore.js +22 -0
  118. package/dist/session/appReadyStore.js.map +1 -0
  119. package/dist/session/authStore.d.ts +71 -0
  120. package/dist/session/authStore.d.ts.map +1 -0
  121. package/dist/session/authStore.js +347 -0
  122. package/dist/session/authStore.js.map +1 -0
  123. package/dist/session/index.d.ts +10 -0
  124. package/dist/session/index.d.ts.map +1 -0
  125. package/dist/session/index.js +10 -0
  126. package/dist/session/index.js.map +1 -0
  127. package/dist/storage/entityStore.d.ts +51 -0
  128. package/dist/storage/entityStore.d.ts.map +1 -0
  129. package/dist/storage/entityStore.js +78 -0
  130. package/dist/storage/entityStore.js.map +1 -0
  131. package/dist/storage/localStore.d.ts +144 -0
  132. package/dist/storage/localStore.d.ts.map +1 -0
  133. package/dist/storage/localStore.js +289 -0
  134. package/dist/storage/localStore.js.map +1 -0
  135. package/dist/storage/rehydrate.d.ts +56 -0
  136. package/dist/storage/rehydrate.d.ts.map +1 -0
  137. package/dist/storage/rehydrate.js +87 -0
  138. package/dist/storage/rehydrate.js.map +1 -0
  139. package/dist/storage/rehydrate.test.d.ts +2 -0
  140. package/dist/storage/rehydrate.test.d.ts.map +1 -0
  141. package/dist/storage/rehydrate.test.js +172 -0
  142. package/dist/storage/rehydrate.test.js.map +1 -0
  143. package/dist/storage/storageManager.d.ts +39 -0
  144. package/dist/storage/storageManager.d.ts.map +1 -0
  145. package/dist/storage/storageManager.js +68 -0
  146. package/dist/storage/storageManager.js.map +1 -0
  147. package/dist/storage/syncController.d.ts +79 -0
  148. package/dist/storage/syncController.d.ts.map +1 -0
  149. package/dist/storage/syncController.js +189 -0
  150. package/dist/storage/syncController.js.map +1 -0
  151. package/dist/storage/syncStatusStore.d.ts +17 -0
  152. package/dist/storage/syncStatusStore.d.ts.map +1 -0
  153. package/dist/storage/syncStatusStore.js +19 -0
  154. package/dist/storage/syncStatusStore.js.map +1 -0
  155. package/dist/storage/wasRemoteStore.d.ts +73 -0
  156. package/dist/storage/wasRemoteStore.d.ts.map +1 -0
  157. package/dist/storage/wasRemoteStore.js +76 -0
  158. package/dist/storage/wasRemoteStore.js.map +1 -0
  159. package/dist/storage/wasSync.d.ts +42 -0
  160. package/dist/storage/wasSync.d.ts.map +1 -0
  161. package/dist/storage/wasSync.js +34 -0
  162. package/dist/storage/wasSync.js.map +1 -0
  163. package/dist/sync/changesQuery.d.ts +44 -0
  164. package/dist/sync/changesQuery.d.ts.map +1 -0
  165. package/dist/sync/changesQuery.js +61 -0
  166. package/dist/sync/changesQuery.js.map +1 -0
  167. package/dist/sync/changesQuery.test.d.ts +2 -0
  168. package/dist/sync/changesQuery.test.d.ts.map +1 -0
  169. package/dist/sync/changesQuery.test.js +145 -0
  170. package/dist/sync/changesQuery.test.js.map +1 -0
  171. package/dist/sync/docCipher.d.ts +68 -0
  172. package/dist/sync/docCipher.d.ts.map +1 -0
  173. package/dist/sync/docCipher.js +89 -0
  174. package/dist/sync/docCipher.js.map +1 -0
  175. package/dist/sync/feedMasterPort.d.ts +30 -0
  176. package/dist/sync/feedMasterPort.d.ts.map +1 -0
  177. package/dist/sync/feedMasterPort.js +58 -0
  178. package/dist/sync/feedMasterPort.js.map +1 -0
  179. package/dist/sync/index.d.ts +21 -0
  180. package/dist/sync/index.d.ts.map +1 -0
  181. package/dist/sync/index.js +21 -0
  182. package/dist/sync/index.js.map +1 -0
  183. package/dist/sync/lww.d.ts +29 -0
  184. package/dist/sync/lww.d.ts.map +1 -0
  185. package/dist/sync/lww.js +28 -0
  186. package/dist/sync/lww.js.map +1 -0
  187. package/dist/sync/lww.test.d.ts +2 -0
  188. package/dist/sync/lww.test.d.ts.map +1 -0
  189. package/dist/sync/lww.test.js +28 -0
  190. package/dist/sync/lww.test.js.map +1 -0
  191. package/dist/sync/lwwConflictHandler.d.ts +49 -0
  192. package/dist/sync/lwwConflictHandler.d.ts.map +1 -0
  193. package/dist/sync/lwwConflictHandler.js +65 -0
  194. package/dist/sync/lwwConflictHandler.js.map +1 -0
  195. package/dist/sync/lwwConflictHandler.test.d.ts +2 -0
  196. package/dist/sync/lwwConflictHandler.test.d.ts.map +1 -0
  197. package/dist/sync/lwwConflictHandler.test.js +78 -0
  198. package/dist/sync/lwwConflictHandler.test.js.map +1 -0
  199. package/dist/sync/pushWrites.d.ts +57 -0
  200. package/dist/sync/pushWrites.d.ts.map +1 -0
  201. package/dist/sync/pushWrites.js +159 -0
  202. package/dist/sync/pushWrites.js.map +1 -0
  203. package/dist/sync/pushWrites.test.d.ts +2 -0
  204. package/dist/sync/pushWrites.test.d.ts.map +1 -0
  205. package/dist/sync/pushWrites.test.js +287 -0
  206. package/dist/sync/pushWrites.test.js.map +1 -0
  207. package/dist/sync/syncedDocSchema.d.ts +22 -0
  208. package/dist/sync/syncedDocSchema.d.ts.map +1 -0
  209. package/dist/sync/syncedDocSchema.js +26 -0
  210. package/dist/sync/syncedDocSchema.js.map +1 -0
  211. package/dist/sync/types.d.ts +194 -0
  212. package/dist/sync/types.d.ts.map +1 -0
  213. package/dist/sync/types.js +36 -0
  214. package/dist/sync/types.js.map +1 -0
  215. package/dist/sync/wasReplication.d.ts +47 -0
  216. package/dist/sync/wasReplication.d.ts.map +1 -0
  217. package/dist/sync/wasReplication.js +55 -0
  218. package/dist/sync/wasReplication.js.map +1 -0
  219. package/dist/sync/wasSyncPort.d.ts +45 -0
  220. package/dist/sync/wasSyncPort.d.ts.map +1 -0
  221. package/dist/sync/wasSyncPort.js +170 -0
  222. package/dist/sync/wasSyncPort.js.map +1 -0
  223. package/package.json +142 -0
@@ -0,0 +1,89 @@
1
+ import { createEdvEncryption } from '@interop/was-client/edv';
2
+ /**
3
+ * Whether a stored body is an EDV encryption envelope (carries an object `jwe`)
4
+ * rather than plaintext. Lets read paths stay tolerant of any legacy plaintext
5
+ * row.
6
+ *
7
+ * @param data {Json | undefined}
8
+ * @returns {boolean}
9
+ */
10
+ export function isEncryptedEnvelope(data) {
11
+ if (data === undefined || data === null || typeof data !== 'object') {
12
+ return false;
13
+ }
14
+ const jwe = data.jwe;
15
+ return jwe !== null && typeof jwe === 'object';
16
+ }
17
+ /**
18
+ * Builds a {@link DocCipher} for one collection from its derived key material
19
+ * (the per-collection X25519 key agreement key). Keys are supplied directly (no
20
+ * keystore lookup). `idDerivation: 'random'` mints a stable random id updated in
21
+ * place via `sequence` -- the mutable head-document model every entity here uses
22
+ * (constant bump / toggle / re-categorize edits).
23
+ *
24
+ * @param options {object}
25
+ * @param options.keyAgreementKey {IKeyAgreementKey}
26
+ * @param options.keyResolver {IKeyResolver}
27
+ * @param options.collectionId {string} labels errors; the codec is agnostic
28
+ * @returns {Promise<DocCipher>}
29
+ */
30
+ export async function createDocCipher({ keyAgreementKey, keyResolver, collectionId }) {
31
+ const provider = createEdvEncryption({
32
+ resolveKeys: async () => null,
33
+ idDerivation: 'random'
34
+ });
35
+ const codec = await provider.codecFor({
36
+ spaceId: 'local',
37
+ collectionId,
38
+ scheme: 'edv',
39
+ keys: { keyAgreementKey, keyResolver }
40
+ });
41
+ if (!codec) {
42
+ throw new Error(`Could not build the EDV cipher for collection "${collectionId}".`);
43
+ }
44
+ // Parses the codec's `EncodedWrite` (id + envelope body bytes) into the
45
+ // stored `{ id, envelope }` shape. Shared by the create and update paths.
46
+ const readEncoded = (encoded) => {
47
+ if (typeof encoded.id !== 'string' ||
48
+ !(encoded.body instanceof Uint8Array)) {
49
+ throw new Error(`EDV encrypt for collection "${collectionId}" returned no id/envelope body.`);
50
+ }
51
+ const envelope = JSON.parse(new TextDecoder().decode(encoded.body));
52
+ return { id: encoded.id, envelope };
53
+ };
54
+ return {
55
+ async encrypt({ data }) {
56
+ // `encode` with no caller id is the create path: encrypt, then use the
57
+ // minted random id.
58
+ const encoded = await codec.encode({
59
+ data: data
60
+ });
61
+ return readEncoded(encoded);
62
+ },
63
+ async encryptUpdate({ id, data, current }) {
64
+ // The update path: hand the codec the prior stored envelope so it advances
65
+ // `sequence` from it and re-encrypts under the same id. The codec reads
66
+ // `current.data` (the prior envelope) and its `etag` header (unused here;
67
+ // the wire `If-Match` is derived from the synced-doc `version`, not this).
68
+ const priorResponse = {
69
+ data: current,
70
+ json: async () => current,
71
+ headers: { get: () => null }
72
+ };
73
+ const encoded = await codec.encode({
74
+ id,
75
+ data: data,
76
+ current: priorResponse
77
+ });
78
+ return readEncoded(encoded);
79
+ },
80
+ async decrypt({ envelope }) {
81
+ const response = {
82
+ data: envelope,
83
+ json: async () => envelope
84
+ };
85
+ return (await codec.decode(response));
86
+ }
87
+ };
88
+ }
89
+ //# sourceMappingURL=docCipher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docCipher.js","sourceRoot":"","sources":["../../src/sync/docCipher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * A thin adapter over a {@link WasSyncPort} that resolves a resource's current
6
+ * master state (for the 412 conflict re-read) from the CHANGES-FEED BODY rather
7
+ * than the base port's `GET` ETag header.
8
+ *
9
+ * Why this exists: the mutable-head model settles a 412 by re-reading the
10
+ * resource's current `version` and comparing payloads (LWW). The verbatim
11
+ * `wasSyncPort.get()` reads that `version` from the response's `etag` header --
12
+ * which a browser hides on a CROSS-ORIGIN response unless the server sends
13
+ * `Access-Control-Expose-Headers: etag`. A server that does not expose it makes
14
+ * cross-origin `get()` report `version: 0`, which makes the loser of a push race
15
+ * re-push forever with a stale `If-Match` and never converge. The `changes` feed
16
+ * carries `version` (and `data`, `_deleted`, ...) in the JSON BODY, which CORS
17
+ * never strips, so resolving the master from the feed is both correct and
18
+ * origin-independent. Normal pull/push are unaffected (they already read
19
+ * `version` from the feed body), so only `get` is overridden.
20
+ */
21
+ import type { WasSyncPort } from './types.js';
22
+ /**
23
+ * Wraps a base port so its `get` resolves the master state from the changes feed.
24
+ * `query`, `putContent`, `deleteContent`, and `putMeta` pass straight through.
25
+ *
26
+ * @param base {WasSyncPort}
27
+ * @returns {WasSyncPort}
28
+ */
29
+ export declare function withFeedMasterRead(base: WasSyncPort): WasSyncPort;
30
+ //# sourceMappingURL=feedMasterPort.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feedMasterPort.d.ts","sourceRoot":"","sources":["../../src/sync/feedMasterPort.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAIV,WAAW,EAEZ,MAAM,YAAY,CAAA;AA4BnB;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,GAAG,WAAW,CAyBjE"}
@@ -0,0 +1,58 @@
1
+ // A generous page cap so a pathological feed cannot spin forever; conflicts are
2
+ // rare and the dev/e2e feed is small, so a full scan is acceptable.
3
+ const MAX_PAGES = 50;
4
+ const PAGE_SIZE = 500;
5
+ /**
6
+ * Builds a `MasterState` from a `changes`-feed document (all fields in-body).
7
+ */
8
+ function toMasterState(doc) {
9
+ const master = {
10
+ version: doc.version,
11
+ updatedAt: doc.updatedAt,
12
+ deleted: doc._deleted
13
+ };
14
+ if (doc.data !== undefined) {
15
+ master.data = doc.data;
16
+ }
17
+ if (doc.metaVersion !== undefined) {
18
+ master.metaVersion = doc.metaVersion;
19
+ }
20
+ if (doc.custom !== undefined) {
21
+ master.custom = doc.custom;
22
+ }
23
+ return master;
24
+ }
25
+ /**
26
+ * Wraps a base port so its `get` resolves the master state from the changes feed.
27
+ * `query`, `putContent`, `deleteContent`, and `putMeta` pass straight through.
28
+ *
29
+ * @param base {WasSyncPort}
30
+ * @returns {WasSyncPort}
31
+ */
32
+ export function withFeedMasterRead(base) {
33
+ return {
34
+ query: base.query.bind(base),
35
+ putContent: base.putContent.bind(base),
36
+ deleteContent: base.deleteContent.bind(base),
37
+ putMeta: base.putMeta.bind(base),
38
+ async get({ id }) {
39
+ let checkpoint;
40
+ for (let page = 0; page < MAX_PAGES; page++) {
41
+ const { documents, checkpoint: next } = await base.query({
42
+ ...(checkpoint !== undefined && { checkpoint }),
43
+ limit: PAGE_SIZE
44
+ });
45
+ const found = documents.find(doc => doc.id === id);
46
+ if (found) {
47
+ return toMasterState(found);
48
+ }
49
+ if (next === null || documents.length === 0) {
50
+ break;
51
+ }
52
+ checkpoint = next;
53
+ }
54
+ return null;
55
+ }
56
+ };
57
+ }
58
+ //# sourceMappingURL=feedMasterPort.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feedMasterPort.js","sourceRoot":"","sources":["../../src/sync/feedMasterPort.ts"],"names":[],"mappings":"AA4BA,gFAAgF;AAChF,oEAAoE;AACpE,MAAM,SAAS,GAAG,EAAE,CAAA;AACpB,MAAM,SAAS,GAAG,GAAG,CAAA;AAErB;;GAEG;AACH,SAAS,aAAa,CAAC,GAAY;IACjC,MAAM,MAAM,GAAgB;QAC1B,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,OAAO,EAAE,GAAG,CAAC,QAAQ;KACtB,CAAA;IACD,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC3B,MAAM,CAAC,IAAI,GAAG,GAAG,CAAC,IAAY,CAAA;IAChC,CAAC;IACD,IAAI,GAAG,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QAClC,MAAM,CAAC,WAAW,GAAG,GAAG,CAAC,WAAW,CAAA;IACtC,CAAC;IACD,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,MAAc,CAAA;IACpC,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAiB;IAClD,OAAO;QACL,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QAC5B,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;QACtC,aAAa,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;QAC5C,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;QAChC,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE;YACd,IAAI,UAAsC,CAAA;YAC1C,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC;gBAC5C,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC;oBACvD,GAAG,CAAC,UAAU,KAAK,SAAS,IAAI,EAAE,UAAU,EAAE,CAAC;oBAC/C,KAAK,EAAE,SAAS;iBACjB,CAAC,CAAA;gBACF,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;gBAClD,IAAI,KAAK,EAAE,CAAC;oBACV,OAAO,aAAa,CAAC,KAAK,CAAC,CAAA;gBAC7B,CAAC;gBACD,IAAI,IAAI,KAAK,IAAI,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAC5C,MAAK;gBACP,CAAC;gBACD,UAAU,GAAG,IAAI,CAAA;YACnB,CAAC;YACD,OAAO,IAAI,CAAA;QACb,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,21 @@
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, formatEtag } from './pushWrites.js';
15
+ export { WasSyncConflictError, type Json, type SyncCheckpoint, type WireDoc, type SyncedDoc, type MasterState, type WasSyncPort } from './types.js';
16
+ export { createWasSyncPort } from './wasSyncPort.js';
17
+ export { withFeedMasterRead } from './feedMasterPort.js';
18
+ export { createDocCipher, isEncryptedEnvelope, type DocCipher } from './docCipher.js';
19
+ export { makeLwwConflictHandler } from './lwwConflictHandler.js';
20
+ export { remotePayloadWins } from './lww.js';
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
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,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC/D,OAAO,EACL,oBAAoB,EACpB,KAAK,IAAI,EACT,KAAK,cAAc,EACnB,KAAK,OAAO,EACZ,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,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,mBAAmB,EACnB,KAAK,SAAS,EACf,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA"}
@@ -0,0 +1,21 @@
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, formatEtag } from './pushWrites.js';
15
+ export { WasSyncConflictError } from './types.js';
16
+ export { createWasSyncPort } from './wasSyncPort.js';
17
+ export { withFeedMasterRead } from './feedMasterPort.js';
18
+ export { createDocCipher, isEncryptedEnvelope } from './docCipher.js';
19
+ export { makeLwwConflictHandler } from './lwwConflictHandler.js';
20
+ export { remotePayloadWins } from './lww.js';
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
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,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC/D,OAAO,EACL,oBAAoB,EAOrB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EACL,eAAe,EACf,mBAAmB,EAEpB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA"}
@@ -0,0 +1,29 @@
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, so lexical compare == chronological compare)
10
+ * decides; `deviceId` breaks an exact tie deterministically. Both fields live in
11
+ * the payload (never the envelope-level checkpoint `updatedAt`).
12
+ */
13
+ /**
14
+ * Whether the remote payload wins over the local one under last-write-wins.
15
+ * Later `updatedAt` wins; on an exact `updatedAt` tie the lexically greater
16
+ * `deviceId` wins (an arbitrary but deterministic, replica-independent choice).
17
+ *
18
+ * @param remote {{ updatedAt: string; deviceId: string }}
19
+ * @param local {{ updatedAt: string; deviceId: string }}
20
+ * @returns {boolean} true if the remote payload should replace the local one
21
+ */
22
+ export declare function remotePayloadWins(remote: {
23
+ updatedAt: string;
24
+ deviceId: string;
25
+ }, local: {
26
+ updatedAt: string;
27
+ deviceId: string;
28
+ }): boolean;
29
+ //# sourceMappingURL=lww.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lww.d.ts","sourceRoot":"","sources":["../../src/sync/lww.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;GAQG;AAEH;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAC/C,KAAK,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC7C,OAAO,CAKT"}
@@ -0,0 +1,28 @@
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, so lexical compare == chronological compare)
10
+ * decides; `deviceId` breaks an exact tie deterministically. Both fields live in
11
+ * the payload (never the envelope-level checkpoint `updatedAt`).
12
+ */
13
+ /**
14
+ * Whether the remote payload wins over the local one under last-write-wins.
15
+ * Later `updatedAt` wins; on an exact `updatedAt` tie the lexically greater
16
+ * `deviceId` wins (an arbitrary but deterministic, replica-independent choice).
17
+ *
18
+ * @param remote {{ updatedAt: string; deviceId: string }}
19
+ * @param local {{ updatedAt: string; deviceId: string }}
20
+ * @returns {boolean} true if the remote payload should replace the local one
21
+ */
22
+ export function remotePayloadWins(remote, local) {
23
+ if (remote.updatedAt !== local.updatedAt) {
24
+ return remote.updatedAt > local.updatedAt;
25
+ }
26
+ return remote.deviceId > local.deviceId;
27
+ }
28
+ //# sourceMappingURL=lww.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lww.js","sourceRoot":"","sources":["../../src/sync/lww.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;GAQG;AAEH;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAA+C,EAC/C,KAA8C;IAE9C,IAAI,MAAM,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS,EAAE,CAAC;QACzC,OAAO,MAAM,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,CAAA;IAC3C,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAA;AACzC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=lww.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lww.test.d.ts","sourceRoot":"","sources":["../../src/sync/lww.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,28 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * Unit tests for the last-write-wins tiebreak.
6
+ *
7
+ * @vitest-environment node
8
+ */
9
+ import { describe, expect, it } from 'vitest';
10
+ import { remotePayloadWins } from './lww.js';
11
+ describe('remotePayloadWins', () => {
12
+ it('later updatedAt wins (remote newer)', () => {
13
+ expect(remotePayloadWins({ updatedAt: '2026-01-02T00:00:00.000Z', deviceId: 'a' }, { updatedAt: '2026-01-01T00:00:00.000Z', deviceId: 'z' })).toBe(true);
14
+ });
15
+ it('earlier updatedAt loses (local newer)', () => {
16
+ expect(remotePayloadWins({ updatedAt: '2026-01-01T00:00:00.000Z', deviceId: 'z' }, { updatedAt: '2026-01-02T00:00:00.000Z', deviceId: 'a' })).toBe(false);
17
+ });
18
+ it('breaks an exact updatedAt tie by greater deviceId', () => {
19
+ const at = '2026-01-01T00:00:00.000Z';
20
+ expect(remotePayloadWins({ updatedAt: at, deviceId: 'b' }, { updatedAt: at, deviceId: 'a' })).toBe(true);
21
+ expect(remotePayloadWins({ updatedAt: at, deviceId: 'a' }, { updatedAt: at, deviceId: 'b' })).toBe(false);
22
+ });
23
+ it('a fully identical payload does not let remote win', () => {
24
+ const same = { updatedAt: '2026-01-01T00:00:00.000Z', deviceId: 'a' };
25
+ expect(remotePayloadWins(same, same)).toBe(false);
26
+ });
27
+ });
28
+ //# sourceMappingURL=lww.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lww.test.js","sourceRoot":"","sources":["../../src/sync/lww.test.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;GAIG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAE5C,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,CACJ,iBAAiB,CACf,EAAE,SAAS,EAAE,0BAA0B,EAAE,QAAQ,EAAE,GAAG,EAAE,EACxD,EAAE,SAAS,EAAE,0BAA0B,EAAE,QAAQ,EAAE,GAAG,EAAE,CACzD,CACF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACd,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,CACJ,iBAAiB,CACf,EAAE,SAAS,EAAE,0BAA0B,EAAE,QAAQ,EAAE,GAAG,EAAE,EACxD,EAAE,SAAS,EAAE,0BAA0B,EAAE,QAAQ,EAAE,GAAG,EAAE,CACzD,CACF,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACf,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,EAAE,GAAG,0BAA0B,CAAA;QACrC,MAAM,CACJ,iBAAiB,CACf,EAAE,SAAS,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,EAChC,EAAE,SAAS,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,CACjC,CACF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACZ,MAAM,CACJ,iBAAiB,CACf,EAAE,SAAS,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,EAChC,EAAE,SAAS,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,CACjC,CACF,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACf,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,IAAI,GAAG,EAAE,SAAS,EAAE,0BAA0B,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAA;QACrE,MAAM,CAAC,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACnD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -0,0 +1,49 @@
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` (deviceId 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` / `deviceId`. `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, deviceId)`, so the
21
+ * globally-latest payload wins on every replica with no coordination.
22
+ */
23
+ import type { WithDeleted } from 'rxdb/plugins/core';
24
+ import type { Json, SyncedDoc } from './types.js';
25
+ /** The LWW fields read out of a decrypted entity payload. */
26
+ interface LwwPayload {
27
+ updatedAt: string;
28
+ deviceId: string;
29
+ }
30
+ /**
31
+ * Builds an RxDB conflict handler that settles content conflicts by payload LWW,
32
+ * decrypting through the supplied per-collection `decrypt`.
33
+ *
34
+ * @param decrypt {(envelope: Json) => Promise<Json>} this collection's decrypt
35
+ * @param [payloadWins] {(remote: LwwPayload, local: LwwPayload) => boolean}
36
+ * the total-order comparator deciding whether the remote payload replaces the
37
+ * local one; defaults to {@link remotePayloadWins} (later `updatedAt` wins,
38
+ * `deviceId` breaks a tie)
39
+ * @returns {import('rxdb/plugins/core').RxConflictHandler<SyncedDoc>}
40
+ */
41
+ export declare function makeLwwConflictHandler(decrypt: (envelope: Json) => Promise<Json>, payloadWins?: (remote: LwwPayload, local: LwwPayload) => boolean): {
42
+ isEqual(a: WithDeleted<SyncedDoc>, b: WithDeleted<SyncedDoc>): boolean;
43
+ resolve({ realMasterState, newDocumentState }: {
44
+ realMasterState: WithDeleted<SyncedDoc>;
45
+ newDocumentState: WithDeleted<SyncedDoc>;
46
+ }): Promise<WithDeleted<SyncedDoc>>;
47
+ };
48
+ export {};
49
+ //# sourceMappingURL=lwwConflictHandler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lwwConflictHandler.d.ts","sourceRoot":"","sources":["../../src/sync/lwwConflictHandler.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAGjD,6DAA6D;AAC7D,UAAU,UAAU;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAiCD;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,CAAC,QAAQ,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,EAC1C,WAAW,GAAE,CACX,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,UAAU,KACd,OAA2B;eAKnB,WAAW,CAAC,SAAS,CAAC,KAAK,WAAW,CAAC,SAAS,CAAC,GAAG,OAAO;mDAOnE;QACD,eAAe,EAAE,WAAW,CAAC,SAAS,CAAC,CAAA;QACvC,gBAAgB,EAAE,WAAW,CAAC,SAAS,CAAC,CAAA;KACzC,GAAG,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;EAmBtC"}
@@ -0,0 +1,65 @@
1
+ import { remotePayloadWins } from './lww.js';
2
+ /** Structural equality of two opaque bodies (used for the fast `isEqual`). */
3
+ function bodiesEqual(a, b) {
4
+ return JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
5
+ }
6
+ /**
7
+ * Decrypts one side's envelope into its `{ updatedAt, deviceId }`, or `null`
8
+ * when the side has no decryptable payload (a tombstone, or an absent/corrupt
9
+ * body). `null` means "no comparable timestamp", handled by the caller.
10
+ */
11
+ async function lwwFieldsOf(doc, decrypt) {
12
+ if (doc._deleted || doc.data === undefined) {
13
+ return null;
14
+ }
15
+ try {
16
+ const payload = (await decrypt(doc.data));
17
+ if (typeof payload.updatedAt === 'string' &&
18
+ typeof payload.deviceId === 'string') {
19
+ return { updatedAt: payload.updatedAt, deviceId: payload.deviceId };
20
+ }
21
+ return null;
22
+ }
23
+ catch {
24
+ return null;
25
+ }
26
+ }
27
+ /**
28
+ * Builds an RxDB conflict handler that settles content conflicts by payload LWW,
29
+ * decrypting through the supplied per-collection `decrypt`.
30
+ *
31
+ * @param decrypt {(envelope: Json) => Promise<Json>} this collection's decrypt
32
+ * @param [payloadWins] {(remote: LwwPayload, local: LwwPayload) => boolean}
33
+ * the total-order comparator deciding whether the remote payload replaces the
34
+ * local one; defaults to {@link remotePayloadWins} (later `updatedAt` wins,
35
+ * `deviceId` breaks a tie)
36
+ * @returns {import('rxdb/plugins/core').RxConflictHandler<SyncedDoc>}
37
+ */
38
+ export function makeLwwConflictHandler(decrypt, payloadWins = remotePayloadWins) {
39
+ return {
40
+ // Non-async and fast, as RxDB requires: no real conflict when the opaque
41
+ // content + deletion flag already agree (e.g. our own write echoing back).
42
+ isEqual(a, b) {
43
+ return a._deleted === b._deleted && bodiesEqual(a.data, b.data);
44
+ },
45
+ async resolve({ realMasterState, newDocumentState }) {
46
+ const [remote, local] = await Promise.all([
47
+ lwwFieldsOf(realMasterState, decrypt),
48
+ lwwFieldsOf(newDocumentState, decrypt)
49
+ ]);
50
+ // Both sides comparable: pure payload LWW.
51
+ if (remote && local) {
52
+ return payloadWins(remote, local) ? realMasterState : newDocumentState;
53
+ }
54
+ // A live local edit against a remote tombstone (or otherwise
55
+ // incomparable remote): keep the edit (resurrect / re-assert the write).
56
+ if (local && !remote) {
57
+ return newDocumentState;
58
+ }
59
+ // Everything else (local tombstone, both incomparable): the master wins,
60
+ // which is deterministic and convergent across replicas.
61
+ return realMasterState;
62
+ }
63
+ };
64
+ }
65
+ //# sourceMappingURL=lwwConflictHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lwwConflictHandler.js","sourceRoot":"","sources":["../../src/sync/lwwConflictHandler.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAQ5C,8EAA8E;AAC9E,SAAS,WAAW,CAAC,CAAmB,EAAE,CAAmB;IAC3D,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,IAAI,CAAC,CAAA;AAChE,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,WAAW,CACxB,GAA2B,EAC3B,OAA0C;IAE1C,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC3C,OAAO,IAAI,CAAA;IACb,CAAC;IACD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAwB,CAAA;QAChE,IACE,OAAO,OAAO,CAAC,SAAS,KAAK,QAAQ;YACrC,OAAO,OAAO,CAAC,QAAQ,KAAK,QAAQ,EACpC,CAAC;YACD,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAA;QACrE,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAA0C,EAC1C,cAGe,iBAAiB;IAEhC,OAAO;QACL,yEAAyE;QACzE,2EAA2E;QAC3E,OAAO,CAAC,CAAyB,EAAE,CAAyB;YAC1D,OAAO,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,QAAQ,IAAI,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAA;QACjE,CAAC;QAED,KAAK,CAAC,OAAO,CAAC,EACZ,eAAe,EACf,gBAAgB,EAIjB;YACC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gBACxC,WAAW,CAAC,eAAe,EAAE,OAAO,CAAC;gBACrC,WAAW,CAAC,gBAAgB,EAAE,OAAO,CAAC;aACvC,CAAC,CAAA;YACF,2CAA2C;YAC3C,IAAI,MAAM,IAAI,KAAK,EAAE,CAAC;gBACpB,OAAO,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,gBAAgB,CAAA;YACxE,CAAC;YACD,6DAA6D;YAC7D,yEAAyE;YACzE,IAAI,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;gBACrB,OAAO,gBAAgB,CAAA;YACzB,CAAC;YACD,yEAAyE;YACzE,yDAAyD;YACzD,OAAO,eAAe,CAAA;QACxB,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=lwwConflictHandler.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lwwConflictHandler.test.d.ts","sourceRoot":"","sources":["../../src/sync/lwwConflictHandler.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,78 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ import { describe, it, expect } from 'vitest';
5
+ import { makeLwwConflictHandler } from './lwwConflictHandler.js';
6
+ // A fake cipher whose "envelope" is just the plaintext payload wrapped in
7
+ // `{ jwe: payload }`; decrypt unwraps it. Lets us drive the handler's LWW
8
+ // decision without real crypto.
9
+ const decrypt = async (envelope) => envelope.jwe;
10
+ function row(payload, { deleted = false, version = 0 } = {}) {
11
+ const doc = {
12
+ id: 'r1',
13
+ updatedAt: '2026-01-01T00:00:00Z',
14
+ version,
15
+ _deleted: deleted
16
+ };
17
+ if (payload !== null) {
18
+ doc.data = { jwe: payload };
19
+ }
20
+ return doc;
21
+ }
22
+ const handler = makeLwwConflictHandler(decrypt);
23
+ describe('makeLwwConflictHandler', () => {
24
+ it('isEqual is true only when body + deletion agree', () => {
25
+ const a = row({ updatedAt: 't1', deviceId: 'd1' });
26
+ const b = row({ updatedAt: 't1', deviceId: 'd1' });
27
+ const c = row({ updatedAt: 't2', deviceId: 'd1' });
28
+ expect(handler.isEqual(a, b)).toBe(true);
29
+ expect(handler.isEqual(a, c)).toBe(false);
30
+ expect(handler.isEqual(a, { ...b, _deleted: true })).toBe(false);
31
+ });
32
+ it('resolves to the later payload (remote wins)', async () => {
33
+ const remote = row({ updatedAt: '2026-02-02T00:00:00Z', deviceId: 'dB' });
34
+ const local = row({ updatedAt: '2026-01-01T00:00:00Z', deviceId: 'dA' });
35
+ const winner = await handler.resolve({
36
+ realMasterState: remote,
37
+ newDocumentState: local
38
+ });
39
+ expect(winner).toBe(remote);
40
+ });
41
+ it('resolves to the later payload (local wins)', async () => {
42
+ const remote = row({ updatedAt: '2026-01-01T00:00:00Z', deviceId: 'dB' });
43
+ const local = row({ updatedAt: '2026-02-02T00:00:00Z', deviceId: 'dA' });
44
+ const winner = await handler.resolve({
45
+ realMasterState: remote,
46
+ newDocumentState: local
47
+ });
48
+ expect(winner).toBe(local);
49
+ });
50
+ it('breaks an exact updatedAt tie by greater deviceId', async () => {
51
+ const remote = row({ updatedAt: 'T', deviceId: 'dZ' });
52
+ const local = row({ updatedAt: 'T', deviceId: 'dA' });
53
+ const winner = await handler.resolve({
54
+ realMasterState: remote,
55
+ newDocumentState: local
56
+ });
57
+ expect(winner).toBe(remote);
58
+ });
59
+ it('keeps a live local edit over a remote tombstone', async () => {
60
+ const remote = row(null, { deleted: true, version: 3 });
61
+ const local = row({ updatedAt: 'T', deviceId: 'dA' });
62
+ const winner = await handler.resolve({
63
+ realMasterState: remote,
64
+ newDocumentState: local
65
+ });
66
+ expect(winner).toBe(local);
67
+ });
68
+ it('defaults to the master when incomparable (local tombstone)', async () => {
69
+ const remote = row({ updatedAt: 'T', deviceId: 'dB' }, { version: 2 });
70
+ const local = row(null, { deleted: true });
71
+ const winner = await handler.resolve({
72
+ realMasterState: remote,
73
+ newDocumentState: local
74
+ });
75
+ expect(winner).toBe(remote);
76
+ });
77
+ });
78
+ //# sourceMappingURL=lwwConflictHandler.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lwwConflictHandler.test.js","sourceRoot":"","sources":["../../src/sync/lwwConflictHandler.test.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAG7C,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AAEhE,0EAA0E;AAC1E,0EAA0E;AAC1E,gCAAgC;AAChC,MAAM,OAAO,GAAG,KAAK,EAAE,QAAc,EAAiB,EAAE,CACrD,QAA0B,CAAC,GAAG,CAAA;AAEjC,SAAS,GAAG,CACV,OAAuD,EACvD,EAAE,OAAO,GAAG,KAAK,EAAE,OAAO,GAAG,CAAC,KAA8C,EAAE;IAE9E,MAAM,GAAG,GAA2B;QAClC,EAAE,EAAE,IAAI;QACR,SAAS,EAAE,sBAAsB;QACjC,OAAO;QACP,QAAQ,EAAE,OAAO;KAClB,CAAA;IACD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACrB,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,OAAO,EAAqB,CAAA;IAChD,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,MAAM,OAAO,GAAG,sBAAsB,CAAC,OAAO,CAAC,CAAA;AAE/C,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACtC,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,CAAC,GAAG,GAAG,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;QAClD,MAAM,CAAC,GAAG,GAAG,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;QAClD,MAAM,CAAC,GAAG,GAAG,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;QAClD,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACxC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACzC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAClE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC3D,MAAM,MAAM,GAAG,GAAG,CAAC,EAAE,SAAS,EAAE,sBAAsB,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;QACzE,MAAM,KAAK,GAAG,GAAG,CAAC,EAAE,SAAS,EAAE,sBAAsB,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;QACxE,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC;YACnC,eAAe,EAAE,MAAM;YACvB,gBAAgB,EAAE,KAAK;SACxB,CAAC,CAAA;QACF,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC7B,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC1D,MAAM,MAAM,GAAG,GAAG,CAAC,EAAE,SAAS,EAAE,sBAAsB,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;QACzE,MAAM,KAAK,GAAG,GAAG,CAAC,EAAE,SAAS,EAAE,sBAAsB,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;QACxE,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC;YACnC,eAAe,EAAE,MAAM;YACvB,gBAAgB,EAAE,KAAK;SACxB,CAAC,CAAA;QACF,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC5B,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QACjE,MAAM,MAAM,GAAG,GAAG,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;QACtD,MAAM,KAAK,GAAG,GAAG,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;QACrD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC;YACnC,eAAe,EAAE,MAAM;YACvB,gBAAgB,EAAE,KAAK;SACxB,CAAC,CAAA;QACF,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC7B,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;QAC/D,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAA;QACvD,MAAM,KAAK,GAAG,GAAG,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;QACrD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC;YACnC,eAAe,EAAE,MAAM;YACvB,gBAAgB,EAAE,KAAK;SACxB,CAAC,CAAA;QACF,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC5B,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;QAC1E,MAAM,MAAM,GAAG,GAAG,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAA;QACtE,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;QAC1C,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC;YACnC,eAAe,EAAE,MAAM;YACvB,gBAAgB,EAAE,KAAK;SACxB,CAAC,CAAA;QACF,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC7B,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -0,0 +1,57 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * The push side of the WAS replication adapter: fans each local change out to
6
+ * conditional WAS writes and assembles the RxDB conflict entry when the server
7
+ * rejects a write with `412`.
8
+ *
9
+ * A single RxDB document spans two independently-versioned sub-resources: the
10
+ * content (`data` / `version`, at `PUT/DELETE /:id`) and the metadata (`custom`
11
+ * / `metaVersion`, at `PUT /:id/meta`). This handler diffs the new local state
12
+ * against the assumed master to route each half:
13
+ *
14
+ * - content changed -> `PUT /:id` (`If-Match: "<version>"`) or, on create,
15
+ * `PUT /:id` (`If-None-Match: *`); a delete -> `DELETE /:id`.
16
+ * - metadata changed -> `PUT /:id/meta` (`If-Match: "<metaVersion>"`, or
17
+ * `If-None-Match: *` when the resource has no metadata yet).
18
+ *
19
+ * Content is written before metadata on a create, because the server rejects a
20
+ * `/meta` write to a resource that does not yet exist.
21
+ *
22
+ * RxDB's push contract asks only for *conflicts* back (the current master state
23
+ * of each rejected row); a successful write's new `version` / `metaVersion` is
24
+ * not returned here -- it is picked up on the next pull, where our own write
25
+ * echoes back as a remote change (idempotent, since the bodies are
26
+ * byte-identical). We deliberately do not capture the `204` ETag to short-circuit
27
+ * that echo: doing so would require a side write to the local revision fields
28
+ * from inside the push handler, which risks a re-push loop, for no correctness
29
+ * gain on immutable content-addressed collections.
30
+ */
31
+ import type { WithDeleted } from 'rxdb/plugins/core';
32
+ import type { SyncedDoc, WasSyncPort } from './types.js';
33
+ /**
34
+ * Formats a master revision (`version` or `metaVersion`) as the quoted strong
35
+ * ETag the server compares `If-Match` against (revision `3` becomes `"3"`).
36
+ *
37
+ * @param revision {number}
38
+ * @returns {string}
39
+ */
40
+ export declare function formatEtag(revision: number): string;
41
+ /**
42
+ * Builds the RxDB push handler that fans a batch of local changes out to
43
+ * conditional WAS writes and returns the conflicting rows' master states.
44
+ *
45
+ * Rows are pushed concurrently; if any non-conflict error is thrown the whole
46
+ * batch rejects (RxDB re-sends it later), matching RxDB's all-or-nothing retry.
47
+ *
48
+ * @param port {WasSyncPort}
49
+ * @returns {(rows: Array<{ newDocumentState: WithDeleted<SyncedDoc>,
50
+ * assumedMasterState?: WithDeleted<SyncedDoc> }>) =>
51
+ * Promise<WithDeleted<SyncedDoc>[]>}
52
+ */
53
+ export declare function createPushHandler(port: WasSyncPort): (rows: Array<{
54
+ newDocumentState: WithDeleted<SyncedDoc>;
55
+ assumedMasterState?: WithDeleted<SyncedDoc>;
56
+ }>) => Promise<WithDeleted<SyncedDoc>[]>;
57
+ //# sourceMappingURL=pushWrites.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pushWrites.d.ts","sourceRoot":"","sources":["../../src/sync/pushWrites.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAqB,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAG3E;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEnD;AA0JD;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,IAE/C,MAAM,KAAK,CAAC;IACV,gBAAgB,EAAE,WAAW,CAAC,SAAS,CAAC,CAAA;IACxC,kBAAkB,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAA;CAC5C,CAAC,KACD,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,CAAC,CAcrC"}