@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,144 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * LocalStore: the always-on local encrypted replica. A GENERIC per-entity
6
+ * envelope store over an app's registered collections. Owns one RxDB
7
+ * (Dexie/IndexedDB) database holding one collection per entity on the shared
8
+ * `syncedDocSchema()`; every at-rest row is `{ id, updatedAt, version, data }`
9
+ * where `data` is the EDV envelope `{ id, sequence, jwe }` -- the server never
10
+ * sees plaintext.
11
+ *
12
+ * Two id planes: the logical entity `uuid` lives INSIDE the encrypted payload;
13
+ * the RxDB primary key is the opaque random EDV envelope id. An in-memory
14
+ * `uuid -> envelopeId` index (built during hydration) routes updates/deletes.
15
+ * Two timestamp planes: the row-level `updatedAt` is only the sync checkpoint;
16
+ * the payload's own `createdAt` / `updatedAt` (inside the ciphertext) drive
17
+ * domain sorting and LWW.
18
+ *
19
+ * Writes: create mints a fresh random envelope; update re-encrypts under the
20
+ * SAME envelope id with `sequence`+1 (the mutable-head model); delete is an RxDB
21
+ * soft-delete tombstone.
22
+ */
23
+ import { type RxCollection, type RxStorage } from 'rxdb/plugins/core';
24
+ import { type WasCollectionConfig } from '../config.js';
25
+ import { type Json, type SyncedDoc } from '../sync/index.js';
26
+ type EntityPayload = {
27
+ id: string;
28
+ };
29
+ /**
30
+ * The local encrypted store. Construct via {@link LocalStore.init}, which
31
+ * derives the per-collection ciphers from the master seed and opens RxDB.
32
+ */
33
+ export declare class LocalStore {
34
+ private _db;
35
+ private _collections;
36
+ private _ciphers;
37
+ private _index;
38
+ private constructor();
39
+ /**
40
+ * Opens (or creates) the encrypted store: derives a per-collection KAK + cipher
41
+ * from the master seed and opens one RxDB collection per entity.
42
+ *
43
+ * @param options {object}
44
+ * @param options.seed {Uint8Array} the 32-byte master seed
45
+ * @param options.collections {WasCollectionConfig[]} the collection registry
46
+ * (logical key to WAS collection id)
47
+ * @param [options.storage] {RxStorage<unknown, unknown>} defaults to
48
+ * Dexie/IndexedDB; injectable for tests
49
+ * @param [options.dbName] {string} defaults to {@link DEFAULT_DB_NAME}
50
+ * @returns {Promise<LocalStore>}
51
+ */
52
+ static init({ seed, collections, storage, dbName }: {
53
+ seed: Uint8Array;
54
+ collections: WasCollectionConfig[];
55
+ storage?: RxStorage<unknown, unknown>;
56
+ dbName?: string;
57
+ }): Promise<LocalStore>;
58
+ private _collection;
59
+ private _cipher;
60
+ /**
61
+ * Builds (or rebuilds) the `uuid -> envelopeId` index for one collection by
62
+ * decrypting every live row. Returns the index map.
63
+ */
64
+ private _ensureIndex;
65
+ /**
66
+ * Encrypts `payload` into a fresh EDV envelope and inserts it as a new row.
67
+ *
68
+ * @param key {string} the collection logical key
69
+ * @param payload {EntityPayload} the plaintext entity (carries its own uuid)
70
+ * @returns {Promise<void>}
71
+ */
72
+ insertEntity(key: string, payload: EntityPayload): Promise<void>;
73
+ /**
74
+ * Re-encrypts `payload` in place under its existing envelope id, advancing the
75
+ * envelope `sequence` (the mutable-head update). The row keeps its primary key;
76
+ * only `data` and the checkpoint `updatedAt` change.
77
+ *
78
+ * @param key {string}
79
+ * @param payload {EntityPayload}
80
+ * @returns {Promise<void>}
81
+ */
82
+ updateEntity(key: string, payload: EntityPayload): Promise<void>;
83
+ /**
84
+ * Tombstones the entity's row (RxDB soft delete) so the deletion replicates.
85
+ *
86
+ * @param key {string}
87
+ * @param uuid {string} the logical entity uuid
88
+ * @returns {Promise<void>}
89
+ */
90
+ deleteEntity(key: string, uuid: string): Promise<void>;
91
+ /**
92
+ * Decrypts every live row of a collection into its plaintext payload, and
93
+ * (re)builds the `uuid -> envelopeId` index as a side effect of hydration.
94
+ *
95
+ * @param key {string}
96
+ * @returns {Promise<T[]>}
97
+ */
98
+ listEntities<T extends EntityPayload>(key: string): Promise<T[]>;
99
+ /**
100
+ * Decrypts a single EDV envelope into its plaintext payload, for per-doc
101
+ * reactive patching of a pulled remote change (without a whole-collection
102
+ * re-hydrate).
103
+ *
104
+ * @param key {string}
105
+ * @param envelope {Json} the `data` field of the at-rest row
106
+ * @returns {Promise<T>}
107
+ */
108
+ decryptEnvelope<T extends EntityPayload>(key: string, envelope: Json): Promise<T>;
109
+ /**
110
+ * Records a `uuid -> envelopeId` mapping for a remotely-pulled row so a
111
+ * subsequent LOCAL edit of that entity can find its envelope. A no-op until
112
+ * the collection has been hydrated once (hydration builds the full index).
113
+ *
114
+ * @param key {string}
115
+ * @param uuid {string}
116
+ * @param envelopeId {string}
117
+ * @returns {void}
118
+ */
119
+ rememberEnvelope(key: string, uuid: string, envelopeId: string): void;
120
+ /**
121
+ * Forgets a `uuid -> envelopeId` mapping (a remotely-pulled tombstone).
122
+ *
123
+ * @param key {string}
124
+ * @param uuid {string}
125
+ * @returns {void}
126
+ */
127
+ forgetEnvelope(key: string, uuid: string): void;
128
+ /**
129
+ * The live RxDB collection handle, for reactive subscriptions and the sync
130
+ * controller.
131
+ *
132
+ * @param key {string}
133
+ * @returns {RxCollection<SyncedDoc>}
134
+ */
135
+ rxCollection(key: string): RxCollection<SyncedDoc>;
136
+ /**
137
+ * Closes the database (without removing data).
138
+ *
139
+ * @returns {Promise<void>}
140
+ */
141
+ close(): Promise<void>;
142
+ }
143
+ export {};
144
+ //# sourceMappingURL=localStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localStore.d.ts","sourceRoot":"","sources":["../../src/storage/localStore.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAEL,KAAK,YAAY,EAEjB,KAAK,SAAS,EACf,MAAM,mBAAmB,CAAA;AAE1B,OAAO,EAAmB,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAA;AACxE,OAAO,EAIL,KAAK,IAAI,EACT,KAAK,SAAS,EAEf,MAAM,kBAAkB,CAAA;AAMzB,KAAK,aAAa,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAA;AAEnC;;;GAGG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,GAAG,CAAY;IACvB,OAAO,CAAC,YAAY,CAAyC;IAC7D,OAAO,CAAC,QAAQ,CAA2B;IAE3C,OAAO,CAAC,MAAM,CAAqC;IAEnD,OAAO;IAeP;;;;;;;;;;;;OAYG;WACU,IAAI,CAAC,EAChB,IAAI,EACJ,WAAW,EACX,OAAO,EACP,MAAwB,EACzB,EAAE;QACD,IAAI,EAAE,UAAU,CAAA;QAChB,WAAW,EAAE,mBAAmB,EAAE,CAAA;QAClC,OAAO,CAAC,EAAE,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QACrC,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,GAAG,OAAO,CAAC,UAAU,CAAC;IAkDvB,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,OAAO;IAQf;;;OAGG;YACW,YAAY;IAsB1B;;;;;;OAMG;IACG,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAetE;;;;;;;;OAQG;IACG,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BtE;;;;;;OAMG;IACG,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAa5D;;;;;;OAMG;IACG,YAAY,CAAC,CAAC,SAAS,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAkBtE;;;;;;;;OAQG;IACG,eAAe,CAAC,CAAC,SAAS,aAAa,EAC3C,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,IAAI,GACb,OAAO,CAAC,CAAC,CAAC;IAIb;;;;;;;;;OASG;IACH,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAIrE;;;;;;OAMG;IACH,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAI/C;;;;;;OAMG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAAC,SAAS,CAAC;IAIlD;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
@@ -0,0 +1,289 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * LocalStore: the always-on local encrypted replica. A GENERIC per-entity
6
+ * envelope store over an app's registered collections. Owns one RxDB
7
+ * (Dexie/IndexedDB) database holding one collection per entity on the shared
8
+ * `syncedDocSchema()`; every at-rest row is `{ id, updatedAt, version, data }`
9
+ * where `data` is the EDV envelope `{ id, sequence, jwe }` -- the server never
10
+ * sees plaintext.
11
+ *
12
+ * Two id planes: the logical entity `uuid` lives INSIDE the encrypted payload;
13
+ * the RxDB primary key is the opaque random EDV envelope id. An in-memory
14
+ * `uuid -> envelopeId` index (built during hydration) routes updates/deletes.
15
+ * Two timestamp planes: the row-level `updatedAt` is only the sync checkpoint;
16
+ * the payload's own `createdAt` / `updatedAt` (inside the ciphertext) drive
17
+ * domain sorting and LWW.
18
+ *
19
+ * Writes: create mints a fresh random envelope; update re-encrypts under the
20
+ * SAME envelope id with `sequence`+1 (the mutable-head model); delete is an RxDB
21
+ * soft-delete tombstone.
22
+ */
23
+ import { createRxDatabase } from 'rxdb/plugins/core';
24
+ import { getRxStorageDexie } from 'rxdb/plugins/storage-dexie';
25
+ import { DEFAULT_DB_NAME } from '../config.js';
26
+ import { syncedDocSchema, createDocCipher, makeLwwConflictHandler } from '../sync/index.js';
27
+ import { deriveCollectionKeys } from '../identity/agents.js';
28
+ /**
29
+ * The local encrypted store. Construct via {@link LocalStore.init}, which
30
+ * derives the per-collection ciphers from the master seed and opens RxDB.
31
+ */
32
+ export class LocalStore {
33
+ _db;
34
+ _collections;
35
+ _ciphers;
36
+ // Per-collection logical-uuid -> envelope (RxDB primary key) index.
37
+ _index;
38
+ constructor({ db, collections, ciphers }) {
39
+ this._db = db;
40
+ this._collections = collections;
41
+ this._ciphers = ciphers;
42
+ this._index = {};
43
+ }
44
+ /**
45
+ * Opens (or creates) the encrypted store: derives a per-collection KAK + cipher
46
+ * from the master seed and opens one RxDB collection per entity.
47
+ *
48
+ * @param options {object}
49
+ * @param options.seed {Uint8Array} the 32-byte master seed
50
+ * @param options.collections {WasCollectionConfig[]} the collection registry
51
+ * (logical key to WAS collection id)
52
+ * @param [options.storage] {RxStorage<unknown, unknown>} defaults to
53
+ * Dexie/IndexedDB; injectable for tests
54
+ * @param [options.dbName] {string} defaults to {@link DEFAULT_DB_NAME}
55
+ * @returns {Promise<LocalStore>}
56
+ */
57
+ static async init({ seed, collections, storage, dbName = DEFAULT_DB_NAME }) {
58
+ const ciphers = {};
59
+ for (const { key, id } of collections) {
60
+ const { keyAgreementKey, keyResolver } = await deriveCollectionKeys({
61
+ seed,
62
+ collectionId: id
63
+ });
64
+ ciphers[key] = await createDocCipher({
65
+ keyAgreementKey,
66
+ keyResolver,
67
+ collectionId: id
68
+ });
69
+ }
70
+ const db = await createRxDatabase({
71
+ name: dbName,
72
+ storage: storage ?? getRxStorageDexie(),
73
+ closeDuplicates: true,
74
+ // Single-tab: RxDB gates replication on leadership under multiInstance;
75
+ // multi-tab is deferred, so replicate directly in this tab.
76
+ multiInstance: false
77
+ });
78
+ // Each collection gets an LWW conflict handler bound to its own cipher, so
79
+ // a 412 push conflict (concurrent multi-device edit of the same mutable
80
+ // head) is settled by decrypting both sides and comparing payload
81
+ // `updatedAt` (deviceId tiebreak) rather than RxDB's default master-wins.
82
+ const collectionsConfig = Object.fromEntries(collections.map(({ key }) => {
83
+ const cipher = ciphers[key];
84
+ if (!cipher) {
85
+ throw new Error(`No cipher for collection "${key}".`);
86
+ }
87
+ return [
88
+ key,
89
+ {
90
+ schema: syncedDocSchema(),
91
+ conflictHandler: makeLwwConflictHandler(envelope => cipher.decrypt({ envelope }))
92
+ }
93
+ ];
94
+ }));
95
+ const collectionsMap = (await db.addCollections(collectionsConfig));
96
+ return new LocalStore({ db, collections: collectionsMap, ciphers });
97
+ }
98
+ _collection(key) {
99
+ const collection = this._collections[key];
100
+ if (!collection) {
101
+ throw new Error(`Unknown collection "${key}".`);
102
+ }
103
+ return collection;
104
+ }
105
+ _cipher(key) {
106
+ const cipher = this._ciphers[key];
107
+ if (!cipher) {
108
+ throw new Error(`No cipher for collection "${key}".`);
109
+ }
110
+ return cipher;
111
+ }
112
+ /**
113
+ * Builds (or rebuilds) the `uuid -> envelopeId` index for one collection by
114
+ * decrypting every live row. Returns the index map.
115
+ */
116
+ async _ensureIndex(key) {
117
+ const existing = this._index[key];
118
+ if (existing) {
119
+ return existing;
120
+ }
121
+ const index = new Map();
122
+ const cipher = this._cipher(key);
123
+ const docs = await this._collection(key).find().exec();
124
+ for (const doc of docs) {
125
+ const { id: envelopeId, data } = doc.toMutableJSON();
126
+ if (data === undefined) {
127
+ continue;
128
+ }
129
+ const payload = (await cipher.decrypt({
130
+ envelope: data
131
+ }));
132
+ index.set(payload.id, envelopeId);
133
+ }
134
+ this._index[key] = index;
135
+ return index;
136
+ }
137
+ /**
138
+ * Encrypts `payload` into a fresh EDV envelope and inserts it as a new row.
139
+ *
140
+ * @param key {string} the collection logical key
141
+ * @param payload {EntityPayload} the plaintext entity (carries its own uuid)
142
+ * @returns {Promise<void>}
143
+ */
144
+ async insertEntity(key, payload) {
145
+ const cipher = this._cipher(key);
146
+ const { id: envelopeId, envelope } = await cipher.encrypt({
147
+ data: payload
148
+ });
149
+ await this._collection(key).insert({
150
+ id: envelopeId,
151
+ updatedAt: new Date().toISOString(),
152
+ version: 0,
153
+ data: envelope
154
+ });
155
+ const index = await this._ensureIndex(key);
156
+ index.set(payload.id, envelopeId);
157
+ }
158
+ /**
159
+ * Re-encrypts `payload` in place under its existing envelope id, advancing the
160
+ * envelope `sequence` (the mutable-head update). The row keeps its primary key;
161
+ * only `data` and the checkpoint `updatedAt` change.
162
+ *
163
+ * @param key {string}
164
+ * @param payload {EntityPayload}
165
+ * @returns {Promise<void>}
166
+ */
167
+ async updateEntity(key, payload) {
168
+ const index = await this._ensureIndex(key);
169
+ const envelopeId = index.get(payload.id);
170
+ if (!envelopeId) {
171
+ throw new Error(`Cannot update: no envelope for entity "${payload.id}" in "${key}".`);
172
+ }
173
+ const doc = await this._collection(key).findOne(envelopeId).exec();
174
+ if (!doc) {
175
+ throw new Error(`Cannot update: row "${envelopeId}" is gone in "${key}".`);
176
+ }
177
+ const current = doc.toMutableJSON().data;
178
+ if (current === undefined) {
179
+ throw new Error(`Cannot update: row "${envelopeId}" has no envelope.`);
180
+ }
181
+ const cipher = this._cipher(key);
182
+ const { envelope } = await cipher.encryptUpdate({
183
+ id: envelopeId,
184
+ data: payload,
185
+ current
186
+ });
187
+ await doc.incrementalPatch({
188
+ data: envelope,
189
+ updatedAt: new Date().toISOString()
190
+ });
191
+ }
192
+ /**
193
+ * Tombstones the entity's row (RxDB soft delete) so the deletion replicates.
194
+ *
195
+ * @param key {string}
196
+ * @param uuid {string} the logical entity uuid
197
+ * @returns {Promise<void>}
198
+ */
199
+ async deleteEntity(key, uuid) {
200
+ const index = await this._ensureIndex(key);
201
+ const envelopeId = index.get(uuid);
202
+ if (!envelopeId) {
203
+ return;
204
+ }
205
+ const doc = await this._collection(key).findOne(envelopeId).exec();
206
+ if (doc) {
207
+ await doc.remove();
208
+ }
209
+ index.delete(uuid);
210
+ }
211
+ /**
212
+ * Decrypts every live row of a collection into its plaintext payload, and
213
+ * (re)builds the `uuid -> envelopeId` index as a side effect of hydration.
214
+ *
215
+ * @param key {string}
216
+ * @returns {Promise<T[]>}
217
+ */
218
+ async listEntities(key) {
219
+ const cipher = this._cipher(key);
220
+ const index = new Map();
221
+ const docs = await this._collection(key).find().exec();
222
+ const payloads = [];
223
+ for (const doc of docs) {
224
+ const { id: envelopeId, data } = doc.toMutableJSON();
225
+ if (data === undefined) {
226
+ continue;
227
+ }
228
+ const payload = (await cipher.decrypt({ envelope: data }));
229
+ index.set(payload.id, envelopeId);
230
+ payloads.push(payload);
231
+ }
232
+ this._index[key] = index;
233
+ return payloads;
234
+ }
235
+ /**
236
+ * Decrypts a single EDV envelope into its plaintext payload, for per-doc
237
+ * reactive patching of a pulled remote change (without a whole-collection
238
+ * re-hydrate).
239
+ *
240
+ * @param key {string}
241
+ * @param envelope {Json} the `data` field of the at-rest row
242
+ * @returns {Promise<T>}
243
+ */
244
+ async decryptEnvelope(key, envelope) {
245
+ return (await this._cipher(key).decrypt({ envelope }));
246
+ }
247
+ /**
248
+ * Records a `uuid -> envelopeId` mapping for a remotely-pulled row so a
249
+ * subsequent LOCAL edit of that entity can find its envelope. A no-op until
250
+ * the collection has been hydrated once (hydration builds the full index).
251
+ *
252
+ * @param key {string}
253
+ * @param uuid {string}
254
+ * @param envelopeId {string}
255
+ * @returns {void}
256
+ */
257
+ rememberEnvelope(key, uuid, envelopeId) {
258
+ this._index[key]?.set(uuid, envelopeId);
259
+ }
260
+ /**
261
+ * Forgets a `uuid -> envelopeId` mapping (a remotely-pulled tombstone).
262
+ *
263
+ * @param key {string}
264
+ * @param uuid {string}
265
+ * @returns {void}
266
+ */
267
+ forgetEnvelope(key, uuid) {
268
+ this._index[key]?.delete(uuid);
269
+ }
270
+ /**
271
+ * The live RxDB collection handle, for reactive subscriptions and the sync
272
+ * controller.
273
+ *
274
+ * @param key {string}
275
+ * @returns {RxCollection<SyncedDoc>}
276
+ */
277
+ rxCollection(key) {
278
+ return this._collection(key);
279
+ }
280
+ /**
281
+ * Closes the database (without removing data).
282
+ *
283
+ * @returns {Promise<void>}
284
+ */
285
+ async close() {
286
+ await this._db.close();
287
+ }
288
+ }
289
+ //# sourceMappingURL=localStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localStore.js","sourceRoot":"","sources":["../../src/storage/localStore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * The hydration MECHANISM: the per-collection re-hydrate hooks (decrypt the
6
+ * localStore rows into the app's stores) plus the debounced remote-change
7
+ * scheduler used by every sync entry point. Generic over an injected
8
+ * {@link StoreRegistry} -- the app supplies the concrete per-collection
9
+ * handlers; nothing here knows the domain entities.
10
+ */
11
+ import type { Json } from '../sync/index.js';
12
+ import type { StoreRegistry } from '../config.js';
13
+ /**
14
+ * Hydrates every registered store from the (already opened) localStore.
15
+ *
16
+ * @param registry {StoreRegistry}
17
+ * @returns {Promise<void>}
18
+ */
19
+ export declare function hydrateAll(registry: StoreRegistry): Promise<void>;
20
+ /**
21
+ * Empties every registered store (logout).
22
+ *
23
+ * @param registry {StoreRegistry}
24
+ * @returns {void}
25
+ */
26
+ export declare function clearAllEntityStores(registry: StoreRegistry): void;
27
+ /**
28
+ * Patches ONE store from a single RxDB change event (per-doc, no whole-collection
29
+ * re-hydrate): decrypt the changed envelope, then upsert the payload (INSERT /
30
+ * UPDATE, including conflict-resolved rows) or drop it (DELETE / tombstone). The
31
+ * `uuid -> envelopeId` index is kept in step so a later local edit of a
32
+ * remotely-created doc still finds its envelope. Falls back to a debounced
33
+ * whole-collection re-hydrate if the envelope is missing or fails to decrypt.
34
+ *
35
+ * @param registry {StoreRegistry}
36
+ * @param collectionKey {string}
37
+ * @param event {object} an RxDB change event (operation + documentData)
38
+ * @returns {Promise<void>}
39
+ */
40
+ export declare function patchFromChange(registry: StoreRegistry, collectionKey: string, event: {
41
+ operation: string;
42
+ documentData?: {
43
+ id: string;
44
+ data?: Json;
45
+ _deleted?: boolean;
46
+ };
47
+ }): Promise<void>;
48
+ /**
49
+ * Schedules a debounced re-hydrate of one collection's store after a pull.
50
+ *
51
+ * @param registry {StoreRegistry}
52
+ * @param collectionKey {string}
53
+ * @returns {void}
54
+ */
55
+ export declare function scheduleRehydrate(registry: StoreRegistry, collectionKey: string): void;
56
+ //# sourceMappingURL=rehydrate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rehydrate.d.ts","sourceRoot":"","sources":["../../src/storage/rehydrate.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAGjD;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAEvE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI,CAIlE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,aAAa,EACvB,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE;IACL,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,IAAI,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAC/D,GACA,OAAO,CAAC,IAAI,CAAC,CA6Bf;AAKD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,aAAa,EACvB,aAAa,EAAE,MAAM,GACpB,IAAI,CAgBN"}
@@ -0,0 +1,87 @@
1
+ import { requireStore } from './storageManager.js';
2
+ /**
3
+ * Hydrates every registered store from the (already opened) localStore.
4
+ *
5
+ * @param registry {StoreRegistry}
6
+ * @returns {Promise<void>}
7
+ */
8
+ export async function hydrateAll(registry) {
9
+ await Promise.all(Object.values(registry).map(entry => entry.hydrate()));
10
+ }
11
+ /**
12
+ * Empties every registered store (logout).
13
+ *
14
+ * @param registry {StoreRegistry}
15
+ * @returns {void}
16
+ */
17
+ export function clearAllEntityStores(registry) {
18
+ for (const entry of Object.values(registry)) {
19
+ entry.clear();
20
+ }
21
+ }
22
+ /**
23
+ * Patches ONE store from a single RxDB change event (per-doc, no whole-collection
24
+ * re-hydrate): decrypt the changed envelope, then upsert the payload (INSERT /
25
+ * UPDATE, including conflict-resolved rows) or drop it (DELETE / tombstone). The
26
+ * `uuid -> envelopeId` index is kept in step so a later local edit of a
27
+ * remotely-created doc still finds its envelope. Falls back to a debounced
28
+ * whole-collection re-hydrate if the envelope is missing or fails to decrypt.
29
+ *
30
+ * @param registry {StoreRegistry}
31
+ * @param collectionKey {string}
32
+ * @param event {object} an RxDB change event (operation + documentData)
33
+ * @returns {Promise<void>}
34
+ */
35
+ export async function patchFromChange(registry, collectionKey, event) {
36
+ const entry = registry[collectionKey];
37
+ if (!entry) {
38
+ return;
39
+ }
40
+ const row = event.documentData;
41
+ const envelope = row?.data;
42
+ const deleted = event.operation === 'DELETE' || row?._deleted === true;
43
+ if (!row || envelope === undefined) {
44
+ scheduleRehydrate(registry, collectionKey);
45
+ return;
46
+ }
47
+ let payload;
48
+ try {
49
+ payload = await requireStore().decryptEnvelope(collectionKey, envelope);
50
+ }
51
+ catch {
52
+ scheduleRehydrate(registry, collectionKey);
53
+ return;
54
+ }
55
+ if (deleted) {
56
+ requireStore().forgetEnvelope(collectionKey, payload.id);
57
+ entry.drop(payload.id);
58
+ }
59
+ else {
60
+ requireStore().rememberEnvelope(collectionKey, payload.id, row.id);
61
+ entry.upsert(payload);
62
+ }
63
+ }
64
+ /** Per-collection debounce timers coalescing a pull burst into one hydrate. */
65
+ const rehydrateTimers = new Map();
66
+ /**
67
+ * Schedules a debounced re-hydrate of one collection's store after a pull.
68
+ *
69
+ * @param registry {StoreRegistry}
70
+ * @param collectionKey {string}
71
+ * @returns {void}
72
+ */
73
+ export function scheduleRehydrate(registry, collectionKey) {
74
+ const entry = registry[collectionKey];
75
+ if (!entry) {
76
+ return;
77
+ }
78
+ const existing = rehydrateTimers.get(collectionKey);
79
+ if (existing) {
80
+ clearTimeout(existing);
81
+ }
82
+ rehydrateTimers.set(collectionKey, setTimeout(() => {
83
+ rehydrateTimers.delete(collectionKey);
84
+ void entry.hydrate();
85
+ }, 50));
86
+ }
87
+ //# sourceMappingURL=rehydrate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rehydrate.js","sourceRoot":"","sources":["../../src/storage/rehydrate.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAElD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAuB;IACtD,MAAM,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;AAC1E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAuB;IAC1D,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5C,KAAK,CAAC,KAAK,EAAE,CAAA;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAAuB,EACvB,aAAqB,EACrB,KAGC;IAED,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAA;IACrC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAM;IACR,CAAC;IACD,MAAM,GAAG,GAAG,KAAK,CAAC,YAAY,CAAA;IAC9B,MAAM,QAAQ,GAAG,GAAG,EAAE,IAAI,CAAA;IAC1B,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,KAAK,QAAQ,IAAI,GAAG,EAAE,QAAQ,KAAK,IAAI,CAAA;IACtE,IAAI,CAAC,GAAG,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QACnC,iBAAiB,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAA;QAC1C,OAAM;IACR,CAAC;IACD,IAAI,OAAuB,CAAA;IAC3B,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,YAAY,EAAE,CAAC,eAAe,CAC5C,aAAa,EACb,QAAQ,CACT,CAAA;IACH,CAAC;IAAC,MAAM,CAAC;QACP,iBAAiB,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAA;QAC1C,OAAM;IACR,CAAC;IACD,IAAI,OAAO,EAAE,CAAC;QACZ,YAAY,EAAE,CAAC,cAAc,CAAC,aAAa,EAAE,OAAO,CAAC,EAAE,CAAC,CAAA;QACxD,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACxB,CAAC;SAAM,CAAC;QACN,YAAY,EAAE,CAAC,gBAAgB,CAAC,aAAa,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC,CAAA;QAClE,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACvB,CAAC;AACH,CAAC;AAED,+EAA+E;AAC/E,MAAM,eAAe,GAAG,IAAI,GAAG,EAAyC,CAAA;AAExE;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAAuB,EACvB,aAAqB;IAErB,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAA;IACrC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAM;IACR,CAAC;IACD,MAAM,QAAQ,GAAG,eAAe,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;IACnD,IAAI,QAAQ,EAAE,CAAC;QACb,YAAY,CAAC,QAAQ,CAAC,CAAA;IACxB,CAAC;IACD,eAAe,CAAC,GAAG,CACjB,aAAa,EACb,UAAU,CAAC,GAAG,EAAE;QACd,eAAe,CAAC,MAAM,CAAC,aAAa,CAAC,CAAA;QACrC,KAAK,KAAK,CAAC,OAAO,EAAE,CAAA;IACtB,CAAC,EAAE,EAAE,CAAC,CACP,CAAA;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=rehydrate.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rehydrate.test.d.ts","sourceRoot":"","sources":["../../src/storage/rehydrate.test.ts"],"names":[],"mappings":""}