@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,73 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * WasRemoteStore (delegated-only): the relying-party view of the user's WAS
6
+ * Space, stripped to the RP model -- this app NEVER provisions the Space, never
7
+ * derives the spaceId, and never touches the `id` collection or DID publishing.
8
+ * It receives a set of wallet-delegated zcaps, reads the server URL + space id
9
+ * straight out of their `invocationTarget`s ({@link parseGrants}), and holds:
10
+ *
11
+ * - a `WasClient` wrapping the app's own `ZcapClient` (its invocation signer is
12
+ * the seed-derived controller the grants were delegated to);
13
+ * - per-collection capability routing, so each sync request invokes the exact
14
+ * collection grant;
15
+ * - a best-effort encryption-marker PUT (whether a delegated collection-scoped
16
+ * RW zcap authorizes writing the collection description). It is non-fatal
17
+ * either way -- envelopes replicate into an unmarked (plaintext) collection
18
+ * just the same.
19
+ */
20
+ import type { ZcapClient } from '@interop/ezcap';
21
+ import type { IZcap } from '@interop/data-integrity-core';
22
+ import { WasClient } from '@interop/was-client';
23
+ import type { ParsedGrants } from '../grants.js';
24
+ /** The outcome of a best-effort encryption-marker PUT, for diagnostics. */
25
+ export interface MarkerResult {
26
+ collectionId: string;
27
+ ok: boolean;
28
+ status?: number;
29
+ error?: string;
30
+ }
31
+ export declare class WasRemoteStore {
32
+ readonly was: WasClient;
33
+ readonly serverUrl: string;
34
+ readonly spaceId: string;
35
+ private readonly _byCollectionId;
36
+ private constructor();
37
+ /**
38
+ * Builds a delegated remote store from a parsed grant set and the app's
39
+ * ZcapClient (whose invocation signer is the controller the grants target).
40
+ * The EDV encryption provider is a no-op keystore: replication moves opaque
41
+ * envelopes verbatim, and encrypt/decrypt is a local read/write concern.
42
+ *
43
+ * @param options {object}
44
+ * @param options.parsed {ParsedGrants}
45
+ * @param options.zcapClient {ZcapClient}
46
+ * @returns {WasRemoteStore}
47
+ */
48
+ static fromGrants({ parsed, zcapClient }: {
49
+ parsed: ParsedGrants;
50
+ zcapClient: ZcapClient;
51
+ }): WasRemoteStore;
52
+ /**
53
+ * The delegated capability for one WAS collection, or `undefined` when no
54
+ * grant covers it (the sync port then invokes without a capability and the
55
+ * server denies it -- the intended fail-closed mode).
56
+ *
57
+ * @param collectionId {string} the WAS collection id
58
+ * @returns {IZcap | undefined}
59
+ */
60
+ collectionCapability(collectionId: string): IZcap | undefined;
61
+ /**
62
+ * Best-effort declaration of the `{ encryption: { scheme: 'edv' } }` marker on
63
+ * one collection, invoked with that collection's delegated RW zcap. Non-fatal:
64
+ * returns the outcome rather than throwing, so a server that does not authorize
65
+ * a delegated description write leaves replication untouched (the collection
66
+ * simply stays unmarked / plaintext, which still stores envelopes).
67
+ *
68
+ * @param collectionId {string} the WAS collection id
69
+ * @returns {Promise<MarkerResult>}
70
+ */
71
+ markCollectionEncrypted(collectionId: string): Promise<MarkerResult>;
72
+ }
73
+ //# sourceMappingURL=wasRemoteStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wasRemoteStore.d.ts","sourceRoot":"","sources":["../../src/storage/wasRemoteStore.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAA;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAE/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAEhD,2EAA2E;AAC3E,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,EAAE,EAAE,OAAO,CAAA;IACX,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,qBAAa,cAAc;IACzB,SAAgB,GAAG,EAAE,SAAS,CAAA;IAC9B,SAAgB,SAAS,EAAE,MAAM,CAAA;IACjC,SAAgB,OAAO,EAAE,MAAM,CAAA;IAC/B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAuB;IAEvD,OAAO;IAaP;;;;;;;;;;OAUG;IACH,MAAM,CAAC,UAAU,CAAC,EAChB,MAAM,EACN,UAAU,EACX,EAAE;QACD,MAAM,EAAE,YAAY,CAAA;QACpB,UAAU,EAAE,UAAU,CAAA;KACvB,GAAG,cAAc;IASlB;;;;;;;OAOG;IACH,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS;IAI7D;;;;;;;;;OASG;IACG,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;CAqB3E"}
@@ -0,0 +1,76 @@
1
+ import { WasClient } from '@interop/was-client';
2
+ import { createEdvEncryption } from '@interop/was-client/edv';
3
+ export class WasRemoteStore {
4
+ was;
5
+ serverUrl;
6
+ spaceId;
7
+ _byCollectionId;
8
+ constructor({ was, parsed }) {
9
+ this.was = was;
10
+ this.serverUrl = parsed.serverUrl;
11
+ this.spaceId = parsed.spaceId;
12
+ this._byCollectionId = parsed.byCollectionId;
13
+ }
14
+ /**
15
+ * Builds a delegated remote store from a parsed grant set and the app's
16
+ * ZcapClient (whose invocation signer is the controller the grants target).
17
+ * The EDV encryption provider is a no-op keystore: replication moves opaque
18
+ * envelopes verbatim, and encrypt/decrypt is a local read/write concern.
19
+ *
20
+ * @param options {object}
21
+ * @param options.parsed {ParsedGrants}
22
+ * @param options.zcapClient {ZcapClient}
23
+ * @returns {WasRemoteStore}
24
+ */
25
+ static fromGrants({ parsed, zcapClient }) {
26
+ const was = new WasClient({
27
+ serverUrl: parsed.serverUrl,
28
+ zcapClient,
29
+ encryption: createEdvEncryption({ resolveKeys: async () => null })
30
+ });
31
+ return new WasRemoteStore({ was, parsed });
32
+ }
33
+ /**
34
+ * The delegated capability for one WAS collection, or `undefined` when no
35
+ * grant covers it (the sync port then invokes without a capability and the
36
+ * server denies it -- the intended fail-closed mode).
37
+ *
38
+ * @param collectionId {string} the WAS collection id
39
+ * @returns {IZcap | undefined}
40
+ */
41
+ collectionCapability(collectionId) {
42
+ return this._byCollectionId[collectionId];
43
+ }
44
+ /**
45
+ * Best-effort declaration of the `{ encryption: { scheme: 'edv' } }` marker on
46
+ * one collection, invoked with that collection's delegated RW zcap. Non-fatal:
47
+ * returns the outcome rather than throwing, so a server that does not authorize
48
+ * a delegated description write leaves replication untouched (the collection
49
+ * simply stays unmarked / plaintext, which still stores envelopes).
50
+ *
51
+ * @param collectionId {string} the WAS collection id
52
+ * @returns {Promise<MarkerResult>}
53
+ */
54
+ async markCollectionEncrypted(collectionId) {
55
+ const capability = this.collectionCapability(collectionId);
56
+ if (!capability) {
57
+ return { collectionId, ok: false, error: 'no capability' };
58
+ }
59
+ try {
60
+ const response = await this.was.request({
61
+ capability,
62
+ path: `/space/${this.spaceId}/${collectionId}`,
63
+ method: 'PUT',
64
+ json: { id: collectionId, encryption: { scheme: 'edv' } }
65
+ });
66
+ return { collectionId, ok: true, status: response.status };
67
+ }
68
+ catch (err) {
69
+ const status = err.status ??
70
+ err.response?.status;
71
+ const message = err instanceof Error ? err.message : String(err);
72
+ return { collectionId, ok: false, status, error: message };
73
+ }
74
+ }
75
+ }
76
+ //# sourceMappingURL=wasRemoteStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wasRemoteStore.js","sourceRoot":"","sources":["../../src/storage/wasRemoteStore.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAW7D,MAAM,OAAO,cAAc;IACT,GAAG,CAAW;IACd,SAAS,CAAQ;IACjB,OAAO,CAAQ;IACd,eAAe,CAAuB;IAEvD,YAAoB,EAClB,GAAG,EACH,MAAM,EAIP;QACC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAA;QACd,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,CAAA;QACjC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAA;QAC7B,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC,cAAc,CAAA;IAC9C,CAAC;IAED;;;;;;;;;;OAUG;IACH,MAAM,CAAC,UAAU,CAAC,EAChB,MAAM,EACN,UAAU,EAIX;QACC,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC;YACxB,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,UAAU;YACV,UAAU,EAAE,mBAAmB,CAAC,EAAE,WAAW,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;SACnE,CAAC,CAAA;QACF,OAAO,IAAI,cAAc,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAA;IAC5C,CAAC;IAED;;;;;;;OAOG;IACH,oBAAoB,CAAC,YAAoB;QACvC,OAAO,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,CAAA;IAC3C,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,uBAAuB,CAAC,YAAoB;QAChD,MAAM,UAAU,GAAG,IAAI,CAAC,oBAAoB,CAAC,YAAY,CAAC,CAAA;QAC1D,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,eAAe,EAAE,CAAA;QAC5D,CAAC;QACD,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;gBACtC,UAAU;gBACV,IAAI,EAAE,UAAU,IAAI,CAAC,OAAO,IAAI,YAAY,EAAE;gBAC9C,MAAM,EAAE,KAAK;gBACb,IAAI,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE;aAC1D,CAAC,CAAA;YACF,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAA;QAC5D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,GACT,GAA2B,CAAC,MAAM;gBAClC,GAA0C,CAAC,QAAQ,EAAE,MAAM,CAAA;YAC9D,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YAChE,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;QAC5D,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,42 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * Shared WAS replication bootstrap: given a parsed grant set and the invoking
6
+ * ZcapClient, builds the delegated {@link WasRemoteStore}, best-effort marks
7
+ * each collection encrypted, and starts the supplied {@link SyncController} with
8
+ * reactive store patching. The caller injects the opened localStore, the
9
+ * controller, and the per-doc `onRemoteChange` patcher (typically wired to the
10
+ * rehydrate mechanism over the app's store registry) rather than this module
11
+ * reaching for app-side globals.
12
+ */
13
+ import type { ZcapClient } from '@interop/ezcap';
14
+ import type { RxChangeEvent } from 'rxdb/plugins/core';
15
+ import type { SyncedDoc } from '../sync/index.js';
16
+ import type { ParsedGrants } from '../grants.js';
17
+ import { WasRemoteStore } from './wasRemoteStore.js';
18
+ import type { LocalStore } from './localStore.js';
19
+ import type { SyncController } from './syncController.js';
20
+ /**
21
+ * Builds the remote store and starts background replication.
22
+ *
23
+ * @param options {object}
24
+ * @param options.parsed {ParsedGrants}
25
+ * @param options.zcapClient {ZcapClient} invocation signer = grants' controller
26
+ * @param options.localStore {LocalStore} the opened local encrypted replica
27
+ * @param options.syncController {SyncController} a fresh per-session controller
28
+ * @param options.onRemoteChange {(collectionKey, event) => void} per-doc
29
+ * reactive patcher for pulled/conflict-resolved remote changes
30
+ * @param [options.onAuthError] {() => void} fired when replication hits a
31
+ * 401/403 (expired/revoked access) -- wired to the reconnect banner
32
+ * @returns {Promise<WasRemoteStore>}
33
+ */
34
+ export declare function startWasSync({ parsed, zcapClient, localStore, syncController, onRemoteChange, onAuthError }: {
35
+ parsed: ParsedGrants;
36
+ zcapClient: ZcapClient;
37
+ localStore: LocalStore;
38
+ syncController: SyncController;
39
+ onRemoteChange: (collectionKey: string, event: RxChangeEvent<SyncedDoc>) => void;
40
+ onAuthError?: () => void;
41
+ }): Promise<WasRemoteStore>;
42
+ //# sourceMappingURL=wasSync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wasSync.d.ts","sourceRoot":"","sources":["../../src/storage/wasSync.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAEzD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,YAAY,CAAC,EACjC,MAAM,EACN,UAAU,EACV,UAAU,EACV,cAAc,EACd,cAAc,EACd,WAAW,EACZ,EAAE;IACD,MAAM,EAAE,YAAY,CAAA;IACpB,UAAU,EAAE,UAAU,CAAA;IACtB,UAAU,EAAE,UAAU,CAAA;IACtB,cAAc,EAAE,cAAc,CAAA;IAC9B,cAAc,EAAE,CACd,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,KAC5B,IAAI,CAAA;IACT,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;CACzB,GAAG,OAAO,CAAC,cAAc,CAAC,CAuB1B"}
@@ -0,0 +1,34 @@
1
+ import { WasRemoteStore } from './wasRemoteStore.js';
2
+ /**
3
+ * Builds the remote store and starts background replication.
4
+ *
5
+ * @param options {object}
6
+ * @param options.parsed {ParsedGrants}
7
+ * @param options.zcapClient {ZcapClient} invocation signer = grants' controller
8
+ * @param options.localStore {LocalStore} the opened local encrypted replica
9
+ * @param options.syncController {SyncController} a fresh per-session controller
10
+ * @param options.onRemoteChange {(collectionKey, event) => void} per-doc
11
+ * reactive patcher for pulled/conflict-resolved remote changes
12
+ * @param [options.onAuthError] {() => void} fired when replication hits a
13
+ * 401/403 (expired/revoked access) -- wired to the reconnect banner
14
+ * @returns {Promise<WasRemoteStore>}
15
+ */
16
+ export async function startWasSync({ parsed, zcapClient, localStore, syncController, onRemoteChange, onAuthError }) {
17
+ const remoteStore = WasRemoteStore.fromGrants({ parsed, zcapClient });
18
+ // Best-effort encryption marker; non-fatal either way (envelopes replicate
19
+ // into an unmarked collection just the same).
20
+ await Promise.all(Object.keys(parsed.byCollectionId).map(async (collectionId) => {
21
+ const result = await remoteStore.markCollectionEncrypted(collectionId);
22
+ if (!result.ok) {
23
+ console.warn(`Encryption marker PUT not authorized for "${collectionId}" (status ${result.status ?? 'n/a'}).`);
24
+ }
25
+ }));
26
+ await syncController.start({
27
+ remoteStore,
28
+ localStore,
29
+ onRemoteChange,
30
+ ...(onAuthError && { onAuthError })
31
+ });
32
+ return remoteStore;
33
+ }
34
+ //# sourceMappingURL=wasSync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wasSync.js","sourceRoot":"","sources":["../../src/storage/wasSync.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAIpD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,EACjC,MAAM,EACN,UAAU,EACV,UAAU,EACV,cAAc,EACd,cAAc,EACd,WAAW,EAWZ;IACC,MAAM,WAAW,GAAG,cAAc,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAA;IAErE,2EAA2E;IAC3E,8CAA8C;IAC9C,MAAM,OAAO,CAAC,GAAG,CACf,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,GAAG,CAAC,KAAK,EAAC,YAAY,EAAC,EAAE;QAC1D,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,uBAAuB,CAAC,YAAY,CAAC,CAAA;QACtE,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CACV,6CAA6C,YAAY,aAAa,MAAM,CAAC,MAAM,IAAI,KAAK,IAAI,CACjG,CAAA;QACH,CAAC;IACH,CAAC,CAAC,CACH,CAAA;IAED,MAAM,cAAc,CAAC,KAAK,CAAC;QACzB,WAAW;QACX,UAAU;QACV,cAAc;QACd,GAAG,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,CAAC;KACpC,CAAC,CAAA;IACF,OAAO,WAAW,CAAA;AACpB,CAAC"}
@@ -0,0 +1,44 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * The pull side of the WAS replication adapter: the `changes`-feed request /
6
+ * response mapping and the RxDB pull handler built from it. Maps each wire
7
+ * document (`{ id, _deleted, updatedAt, version, metaVersion?, data?, custom? }`)
8
+ * into an RxDB `WithDeleted<SyncedDoc>`, and applies the empty-page
9
+ * `checkpoint: null` rule.
10
+ */
11
+ import type { WithDeleted } from 'rxdb/plugins/core';
12
+ import type { SyncCheckpoint, SyncedDoc, WasSyncPort, WireDoc } from './types.js';
13
+ /**
14
+ * Maps one `changes`-feed wire document into an RxDB document. The envelope
15
+ * fields map straight across; the content body stays under `data` (omitted for
16
+ * tombstones, which carry no `data`) and the metadata body under `custom`.
17
+ * `metaVersion` / `custom` are present only once metadata has been written for
18
+ * the resource, and are simply absent otherwise (forward-compatible with a
19
+ * server that does not yet surface them on the feed). `_deleted` becomes RxDB's
20
+ * native deleted flag.
21
+ *
22
+ * @param doc {WireDoc}
23
+ * @returns {WithDeleted<SyncedDoc>}
24
+ */
25
+ export declare function wireDocToRxDoc(doc: WireDoc): WithDeleted<SyncedDoc>;
26
+ /**
27
+ * Builds the RxDB pull handler that fetches one `changes` page per call and
28
+ * resumes from the previous checkpoint. RxDB passes the last stored checkpoint
29
+ * (`undefined` on the first pull -- which the port omits from the request) and
30
+ * the batch size.
31
+ *
32
+ * The empty-page rule: when the server returns `checkpoint: null` (no change),
33
+ * keep the checkpoint RxDB gave us rather than persisting `null`, so the next
34
+ * pull resumes from the same position instead of restarting the feed.
35
+ *
36
+ * @param port {WasSyncPort}
37
+ * @returns {(lastCheckpoint: SyncCheckpoint | undefined, batchSize: number) =>
38
+ * Promise<{ documents: WithDeleted<SyncedDoc>[], checkpoint: SyncCheckpoint | undefined }>}
39
+ */
40
+ export declare function createPullHandler(port: WasSyncPort): (lastCheckpoint: SyncCheckpoint | undefined, batchSize: number) => Promise<{
41
+ documents: WithDeleted<SyncedDoc>[];
42
+ checkpoint: SyncCheckpoint | undefined;
43
+ }>;
44
+ //# sourceMappingURL=changesQuery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changesQuery.d.ts","sourceRoot":"","sources":["../../src/sync/changesQuery.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EACV,cAAc,EACd,SAAS,EACT,WAAW,EACX,OAAO,EACR,MAAM,YAAY,CAAA;AAEnB;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,WAAW,CAAC,SAAS,CAAC,CAiBnE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,IAE/C,gBAAgB,cAAc,GAAG,SAAS,EAC1C,WAAW,MAAM,KAChB,OAAO,CAAC;IACT,SAAS,EAAE,WAAW,CAAC,SAAS,CAAC,EAAE,CAAA;IACnC,UAAU,EAAE,cAAc,GAAG,SAAS,CAAA;CACvC,CAAC,CAcH"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Maps one `changes`-feed wire document into an RxDB document. The envelope
3
+ * fields map straight across; the content body stays under `data` (omitted for
4
+ * tombstones, which carry no `data`) and the metadata body under `custom`.
5
+ * `metaVersion` / `custom` are present only once metadata has been written for
6
+ * the resource, and are simply absent otherwise (forward-compatible with a
7
+ * server that does not yet surface them on the feed). `_deleted` becomes RxDB's
8
+ * native deleted flag.
9
+ *
10
+ * @param doc {WireDoc}
11
+ * @returns {WithDeleted<SyncedDoc>}
12
+ */
13
+ export function wireDocToRxDoc(doc) {
14
+ const rxDoc = {
15
+ id: doc.id,
16
+ updatedAt: doc.updatedAt,
17
+ version: doc.version,
18
+ _deleted: doc._deleted
19
+ };
20
+ if (doc.data !== undefined) {
21
+ rxDoc.data = doc.data;
22
+ }
23
+ if (doc.metaVersion !== undefined) {
24
+ rxDoc.metaVersion = doc.metaVersion;
25
+ }
26
+ if (doc.custom !== undefined) {
27
+ rxDoc.custom = doc.custom;
28
+ }
29
+ return rxDoc;
30
+ }
31
+ /**
32
+ * Builds the RxDB pull handler that fetches one `changes` page per call and
33
+ * resumes from the previous checkpoint. RxDB passes the last stored checkpoint
34
+ * (`undefined` on the first pull -- which the port omits from the request) and
35
+ * the batch size.
36
+ *
37
+ * The empty-page rule: when the server returns `checkpoint: null` (no change),
38
+ * keep the checkpoint RxDB gave us rather than persisting `null`, so the next
39
+ * pull resumes from the same position instead of restarting the feed.
40
+ *
41
+ * @param port {WasSyncPort}
42
+ * @returns {(lastCheckpoint: SyncCheckpoint | undefined, batchSize: number) =>
43
+ * Promise<{ documents: WithDeleted<SyncedDoc>[], checkpoint: SyncCheckpoint | undefined }>}
44
+ */
45
+ export function createPullHandler(port) {
46
+ return async function pull(lastCheckpoint, batchSize) {
47
+ const response = await port.query({
48
+ // Omit `checkpoint` entirely on the first pull (the port sends no
49
+ // checkpoint field, not `null`).
50
+ ...(lastCheckpoint !== undefined && { checkpoint: lastCheckpoint }),
51
+ limit: batchSize
52
+ });
53
+ return {
54
+ documents: response.documents.map(wireDocToRxDoc),
55
+ // Empty page (`checkpoint: null`) means "no change": keep the prior
56
+ // checkpoint so the feed does not restart from the beginning.
57
+ checkpoint: response.checkpoint ?? lastCheckpoint
58
+ };
59
+ };
60
+ }
61
+ //# sourceMappingURL=changesQuery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changesQuery.js","sourceRoot":"","sources":["../../src/sync/changesQuery.ts"],"names":[],"mappings":"AAkBA;;;;;;;;;;;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,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC3B,KAAK,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,CAAA;IACvB,CAAC;IACD,IAAI,GAAG,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QAClC,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC,WAAW,CAAA;IACrC,CAAC;IACD,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC7B,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAA;IAC3B,CAAC;IACD,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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=changesQuery.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changesQuery.test.d.ts","sourceRoot":"","sources":["../../src/sync/changesQuery.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,145 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * Unit tests for the pull side of the sync adapter (the `changes`-feed mapping
6
+ * and pull handler), driven by a fake WAS port -- no server, no RxDB engine.
7
+ */
8
+ import { describe, it, expect, vi } from 'vitest';
9
+ import { createPullHandler, wireDocToRxDoc } from './changesQuery.js';
10
+ /**
11
+ * A minimal fake port whose `query` replays a scripted list of pages. Only the
12
+ * methods the pull path uses are implemented; the write methods throw.
13
+ */
14
+ function fakePullPort(pages) {
15
+ const calls = [];
16
+ let index = 0;
17
+ return {
18
+ calls,
19
+ async query(options) {
20
+ calls.push(options);
21
+ const page = pages[index] ?? { documents: [], checkpoint: null };
22
+ index += 1;
23
+ return page;
24
+ },
25
+ putContent: vi.fn(),
26
+ deleteContent: vi.fn(),
27
+ putMeta: vi.fn(),
28
+ get: vi.fn()
29
+ };
30
+ }
31
+ describe('wireDocToRxDoc', () => {
32
+ it('maps a live content document, nesting the body under data', () => {
33
+ const doc = {
34
+ id: 'abc',
35
+ _deleted: false,
36
+ updatedAt: '2026-01-01T00:00:00Z',
37
+ version: 3,
38
+ data: { hello: 'world' }
39
+ };
40
+ expect(wireDocToRxDoc(doc)).toEqual({
41
+ id: 'abc',
42
+ updatedAt: '2026-01-01T00:00:00Z',
43
+ version: 3,
44
+ data: { hello: 'world' },
45
+ _deleted: false
46
+ });
47
+ });
48
+ it('carries metaVersion and the custom envelope when present', () => {
49
+ const doc = {
50
+ id: 'abc',
51
+ _deleted: false,
52
+ updatedAt: '2026-01-01T00:00:00Z',
53
+ version: 3,
54
+ metaVersion: 2,
55
+ data: { hello: 'world' },
56
+ custom: { jwe: { ciphertext: '...' } }
57
+ };
58
+ expect(wireDocToRxDoc(doc)).toEqual({
59
+ id: 'abc',
60
+ updatedAt: '2026-01-01T00:00:00Z',
61
+ version: 3,
62
+ metaVersion: 2,
63
+ data: { hello: 'world' },
64
+ custom: { jwe: { ciphertext: '...' } },
65
+ _deleted: false
66
+ });
67
+ });
68
+ it('projects a tombstone with no data and no metadata', () => {
69
+ const doc = {
70
+ id: 'gone',
71
+ _deleted: true,
72
+ updatedAt: '2026-01-02T00:00:00Z',
73
+ version: 4
74
+ };
75
+ const rx = wireDocToRxDoc(doc);
76
+ expect(rx).toEqual({
77
+ id: 'gone',
78
+ updatedAt: '2026-01-02T00:00:00Z',
79
+ version: 4,
80
+ _deleted: true
81
+ });
82
+ expect('data' in rx).toBe(false);
83
+ expect('custom' in rx).toBe(false);
84
+ });
85
+ });
86
+ describe('createPullHandler', () => {
87
+ const cpA = { id: 'a', updatedAt: '2026-01-01T00:00:01Z' };
88
+ const cpB = { id: 'b', updatedAt: '2026-01-01T00:00:02Z' };
89
+ it('omits the checkpoint on the first pull and forwards it on resume', async () => {
90
+ const port = fakePullPort([
91
+ {
92
+ documents: [
93
+ { id: 'a', _deleted: false, updatedAt: cpA.updatedAt, version: 1 }
94
+ ],
95
+ checkpoint: cpA
96
+ }
97
+ ]);
98
+ const pull = createPullHandler(port);
99
+ const first = await pull(undefined, 100);
100
+ expect(port.calls[0]).toEqual({ limit: 100 });
101
+ expect('checkpoint' in port.calls[0]).toBe(false);
102
+ expect(first.checkpoint).toEqual(cpA);
103
+ expect(first.documents).toHaveLength(1);
104
+ await pull(cpA, 50);
105
+ expect(port.calls[1]).toEqual({ checkpoint: cpA, limit: 50 });
106
+ });
107
+ it('iterates: each page returns its own checkpoint to resume from', async () => {
108
+ const port = fakePullPort([
109
+ {
110
+ documents: [
111
+ { id: 'a', _deleted: false, updatedAt: cpA.updatedAt, version: 1 }
112
+ ],
113
+ checkpoint: cpA
114
+ },
115
+ {
116
+ documents: [
117
+ { id: 'b', _deleted: false, updatedAt: cpB.updatedAt, version: 1 }
118
+ ],
119
+ checkpoint: cpB
120
+ }
121
+ ]);
122
+ const pull = createPullHandler(port);
123
+ const page1 = await pull(undefined, 100);
124
+ const page2 = await pull(page1.checkpoint, 100);
125
+ expect(page1.checkpoint).toEqual(cpA);
126
+ expect(page2.checkpoint).toEqual(cpB);
127
+ expect(page2.documents[0].id).toBe('b');
128
+ });
129
+ it('keeps the prior checkpoint on an empty page (checkpoint: null)', async () => {
130
+ const port = fakePullPort([{ documents: [], checkpoint: null }]);
131
+ const pull = createPullHandler(port);
132
+ const result = await pull(cpA, 100);
133
+ // The empty-page rule: do NOT persist null -- resume from the same position.
134
+ expect(result.documents).toEqual([]);
135
+ expect(result.checkpoint).toEqual(cpA);
136
+ });
137
+ it('returns undefined checkpoint on a first, empty pull', async () => {
138
+ const port = fakePullPort([{ documents: [], checkpoint: null }]);
139
+ const pull = createPullHandler(port);
140
+ const result = await pull(undefined, 100);
141
+ expect(result.documents).toEqual([]);
142
+ expect(result.checkpoint).toBeUndefined();
143
+ });
144
+ });
145
+ //# sourceMappingURL=changesQuery.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changesQuery.test.js","sourceRoot":"","sources":["../../src/sync/changesQuery.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * The per-collection EDV document cipher: the local encrypt/decrypt seam. It
6
+ * wraps the same `@interop/was-client` EDV codec the remote WAS handles use,
7
+ * pointed at the local replica: `encrypt` turns a JSON document into its stored
8
+ * EDV envelope (`{ id, sequence, jwe }`) minting a stable random resource id
9
+ * (`idDerivation: 'random'`), and `encryptUpdate` re-encrypts a mutable head
10
+ * document under its EXISTING id, advancing the envelope `sequence` from the
11
+ * prior envelope. `decrypt` reverses it. The sync layer moves the envelope
12
+ * verbatim; it never touches these keys.
13
+ */
14
+ import type { IKeyAgreementKey, IKeyResolver } from '@interop/data-integrity-core';
15
+ import type { Json } from './types.js';
16
+ /**
17
+ * A per-collection document cipher. `encrypt` is the create path (mints a random
18
+ * envelope id); `encryptUpdate` is the in-place update path (re-encrypts under
19
+ * an existing id, advancing `sequence` from the prior envelope); `decrypt`
20
+ * reverses either.
21
+ */
22
+ export interface DocCipher {
23
+ encrypt(options: {
24
+ data: Json;
25
+ }): Promise<{
26
+ id: string;
27
+ envelope: Json;
28
+ }>;
29
+ encryptUpdate(options: {
30
+ id: string;
31
+ data: Json;
32
+ current: Json;
33
+ }): Promise<{
34
+ id: string;
35
+ envelope: Json;
36
+ }>;
37
+ decrypt(options: {
38
+ envelope: Json;
39
+ }): Promise<Json>;
40
+ }
41
+ /**
42
+ * Whether a stored body is an EDV encryption envelope (carries an object `jwe`)
43
+ * rather than plaintext. Lets read paths stay tolerant of any legacy plaintext
44
+ * row.
45
+ *
46
+ * @param data {Json | undefined}
47
+ * @returns {boolean}
48
+ */
49
+ export declare function isEncryptedEnvelope(data: Json | undefined): boolean;
50
+ /**
51
+ * Builds a {@link DocCipher} for one collection from its derived key material
52
+ * (the per-collection X25519 key agreement key). Keys are supplied directly (no
53
+ * keystore lookup). `idDerivation: 'random'` mints a stable random id updated in
54
+ * place via `sequence` -- the mutable head-document model every entity here uses
55
+ * (constant bump / toggle / re-categorize edits).
56
+ *
57
+ * @param options {object}
58
+ * @param options.keyAgreementKey {IKeyAgreementKey}
59
+ * @param options.keyResolver {IKeyResolver}
60
+ * @param options.collectionId {string} labels errors; the codec is agnostic
61
+ * @returns {Promise<DocCipher>}
62
+ */
63
+ export declare function createDocCipher({ keyAgreementKey, keyResolver, collectionId }: {
64
+ keyAgreementKey: IKeyAgreementKey;
65
+ keyResolver: IKeyResolver;
66
+ collectionId: string;
67
+ }): Promise<DocCipher>;
68
+ //# sourceMappingURL=docCipher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docCipher.d.ts","sourceRoot":"","sources":["../../src/sync/docCipher.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;GASG;AACH,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EACb,MAAM,8BAA8B,CAAA;AAErC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAEtC;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,OAAO,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,IAAI,CAAA;KAAE,CAAC,CAAA;IACzE,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,CAAA;KAAE,CAAC,CAAA;IAC3C,OAAO,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACpD;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,GAAG,OAAO,CAMnE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,eAAe,CAAC,EACpC,eAAe,EACf,WAAW,EACX,YAAY,EACb,EAAE;IACD,eAAe,EAAE,gBAAgB,CAAA;IACjC,WAAW,EAAE,YAAY,CAAA;IACzB,YAAY,EAAE,MAAM,CAAA;CACrB,GAAG,OAAO,CAAC,SAAS,CAAC,CA+ErB"}