@noy-db/hub 0.1.0-pre.9 → 0.2.0-pre.2

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 (288) hide show
  1. package/dist/aggregate/index.cjs +91 -36
  2. package/dist/aggregate/index.cjs.map +1 -1
  3. package/dist/aggregate/index.d.cts +2 -2
  4. package/dist/aggregate/index.d.ts +2 -2
  5. package/dist/aggregate/index.js +16 -9
  6. package/dist/aggregate/index.js.map +1 -1
  7. package/dist/attestation/index.cjs +305 -0
  8. package/dist/attestation/index.cjs.map +1 -0
  9. package/dist/attestation/index.d.cts +52 -0
  10. package/dist/attestation/index.d.ts +52 -0
  11. package/dist/attestation/index.js +36 -0
  12. package/dist/attestation/index.js.map +1 -0
  13. package/dist/blobs/index.cjs.map +1 -1
  14. package/dist/blobs/index.d.cts +7 -6
  15. package/dist/blobs/index.d.ts +7 -6
  16. package/dist/blobs/index.js +10 -8
  17. package/dist/blobs/index.js.map +1 -1
  18. package/dist/bundle/index.cjs +16923 -60
  19. package/dist/bundle/index.cjs.map +1 -1
  20. package/dist/bundle/index.d.cts +175 -6
  21. package/dist/bundle/index.d.ts +175 -6
  22. package/dist/bundle/index.js +543 -4
  23. package/dist/bundle/index.js.map +1 -1
  24. package/dist/{chunk-PTVMYYON.js → chunk-243PNUA6.js} +3 -3
  25. package/dist/{chunk-MR4424N3.js → chunk-2PAQNPE3.js} +2 -2
  26. package/dist/chunk-3QAKZ37R.js +83 -0
  27. package/dist/chunk-3QAKZ37R.js.map +1 -0
  28. package/dist/chunk-3S4BJX25.js +36 -0
  29. package/dist/chunk-3S4BJX25.js.map +1 -0
  30. package/dist/chunk-3XHOCQK4.js +118 -0
  31. package/dist/chunk-3XHOCQK4.js.map +1 -0
  32. package/dist/{chunk-AVVPZ4BC.js → chunk-3Y53S2SA.js} +4 -4
  33. package/dist/chunk-3Z2TPHC4.js +291 -0
  34. package/dist/chunk-3Z2TPHC4.js.map +1 -0
  35. package/dist/chunk-4HIL6AHQ.js +57 -0
  36. package/dist/chunk-4HIL6AHQ.js.map +1 -0
  37. package/dist/chunk-5ZGZ6HIZ.js +100 -0
  38. package/dist/chunk-5ZGZ6HIZ.js.map +1 -0
  39. package/dist/{chunk-ZFKD4QMV.js → chunk-7BRE6EUA.js} +3 -3
  40. package/dist/chunk-7BUTTVMR.js +34 -0
  41. package/dist/chunk-7BUTTVMR.js.map +1 -0
  42. package/dist/{chunk-VQBTTTUN.js → chunk-7Q5PLD5C.js} +4 -4
  43. package/dist/{chunk-VQBTTTUN.js.map → chunk-7Q5PLD5C.js.map} +1 -1
  44. package/dist/{chunk-QAVUREFT.js → chunk-7Z23ZFLV.js} +12 -6
  45. package/dist/chunk-7Z23ZFLV.js.map +1 -0
  46. package/dist/chunk-AHPFONIL.js +59 -0
  47. package/dist/chunk-AHPFONIL.js.map +1 -0
  48. package/dist/chunk-CXSCDO5T.js +51 -0
  49. package/dist/chunk-CXSCDO5T.js.map +1 -0
  50. package/dist/chunk-E535SAN4.js +8834 -0
  51. package/dist/chunk-E535SAN4.js.map +1 -0
  52. package/dist/chunk-EUYOGYGV.js +830 -0
  53. package/dist/chunk-EUYOGYGV.js.map +1 -0
  54. package/dist/chunk-FAQVNJD4.js +61 -0
  55. package/dist/chunk-FAQVNJD4.js.map +1 -0
  56. package/dist/{chunk-SCZXXXU4.js → chunk-G6FRSBKK.js} +7 -32
  57. package/dist/chunk-G6FRSBKK.js.map +1 -0
  58. package/dist/chunk-GIV6DWBG.js +79 -0
  59. package/dist/chunk-GIV6DWBG.js.map +1 -0
  60. package/dist/chunk-HXJXPZRE.js +73 -0
  61. package/dist/chunk-HXJXPZRE.js.map +1 -0
  62. package/dist/{chunk-GOUT6DND.js → chunk-J4KLMEUL.js} +173 -91
  63. package/dist/chunk-J4KLMEUL.js.map +1 -0
  64. package/dist/{chunk-2CSJGFCB.js → chunk-JYQTXEIO.js} +6 -229
  65. package/dist/chunk-JYQTXEIO.js.map +1 -0
  66. package/dist/{chunk-MDDTIZUO.js → chunk-LRAZDV5X.js} +7 -119
  67. package/dist/chunk-LRAZDV5X.js.map +1 -0
  68. package/dist/{chunk-M5INGEFC.js → chunk-MRIBLZL3.js} +3 -1
  69. package/dist/chunk-MRIBLZL3.js.map +1 -0
  70. package/dist/{chunk-USKYUS74.js → chunk-MUWOSVEP.js} +2 -2
  71. package/dist/{chunk-4PWAI7Q4.js → chunk-NWZ3I6R6.js} +5 -5
  72. package/dist/chunk-OVZDFEOR.js +124 -0
  73. package/dist/chunk-OVZDFEOR.js.map +1 -0
  74. package/dist/chunk-PEULZC6M.js +118 -0
  75. package/dist/chunk-PEULZC6M.js.map +1 -0
  76. package/dist/chunk-PFSNOPBQ.js +233 -0
  77. package/dist/chunk-PFSNOPBQ.js.map +1 -0
  78. package/dist/chunk-PLI5TV7N.js +53 -0
  79. package/dist/chunk-PLI5TV7N.js.map +1 -0
  80. package/dist/{chunk-WDM5XGGS.js → chunk-Q6W2CMEJ.js} +181 -11
  81. package/dist/chunk-Q6W2CMEJ.js.map +1 -0
  82. package/dist/{chunk-QGZRWRSL.js → chunk-QPEXPHJR.js} +4 -4
  83. package/dist/{chunk-R36SIKES.js → chunk-QXQRKXCU.js} +2 -2
  84. package/dist/chunk-RTZVQAJ7.js +82 -0
  85. package/dist/chunk-RTZVQAJ7.js.map +1 -0
  86. package/dist/chunk-TBKOGSYR.js +296 -0
  87. package/dist/chunk-TBKOGSYR.js.map +1 -0
  88. package/dist/chunk-UMLVJTYV.js +20 -0
  89. package/dist/chunk-UMLVJTYV.js.map +1 -0
  90. package/dist/chunk-UND4XIB6.js +251 -0
  91. package/dist/chunk-UND4XIB6.js.map +1 -0
  92. package/dist/chunk-VCGTOS2A.js +795 -0
  93. package/dist/chunk-VCGTOS2A.js.map +1 -0
  94. package/dist/chunk-VE6YVP32.js +19 -0
  95. package/dist/chunk-VE6YVP32.js.map +1 -0
  96. package/dist/{chunk-M62XNWRA.js → chunk-VK5EER6C.js} +2 -2
  97. package/dist/{chunk-NXFEYLVG.js → chunk-VPSUZLOJ.js} +4 -3
  98. package/dist/{chunk-NXFEYLVG.js.map → chunk-VPSUZLOJ.js.map} +1 -1
  99. package/dist/{chunk-TDR6T5CJ.js → chunk-VRBCTEKQ.js} +91 -132
  100. package/dist/chunk-VRBCTEKQ.js.map +1 -0
  101. package/dist/{chunk-ACLDOTNQ.js → chunk-W3XXT26A.js} +303 -3
  102. package/dist/chunk-W3XXT26A.js.map +1 -0
  103. package/dist/{chunk-CIMZBAZB.js → chunk-XG3PTSCD.js} +1 -1
  104. package/dist/chunk-XG3PTSCD.js.map +1 -0
  105. package/dist/chunk-Y2RKOPNC.js +145 -0
  106. package/dist/chunk-Y2RKOPNC.js.map +1 -0
  107. package/dist/{chunk-NPC4LFV5.js → chunk-YMYK7US4.js} +2 -2
  108. package/dist/{chunk-RKJ6OL7K.js → chunk-YS3POABP.js} +1 -1
  109. package/dist/chunk-YS3POABP.js.map +1 -0
  110. package/dist/chunk-YTXSFG3C.js +179 -0
  111. package/dist/chunk-YTXSFG3C.js.map +1 -0
  112. package/dist/consent/index.cjs.map +1 -1
  113. package/dist/consent/index.d.cts +7 -6
  114. package/dist/consent/index.d.ts +7 -6
  115. package/dist/consent/index.js +3 -3
  116. package/dist/{crypto-IVKU7YTT.js → crypto-5ZDIY3NG.js} +3 -3
  117. package/dist/{delegation-2DBS2EOH.js → delegation-QYXZW25W.js} +5 -4
  118. package/dist/derivations/index.cjs +351 -0
  119. package/dist/derivations/index.cjs.map +1 -0
  120. package/dist/derivations/index.d.cts +72 -0
  121. package/dist/derivations/index.d.ts +72 -0
  122. package/dist/derivations/index.js +27 -0
  123. package/dist/{dev-unlock-Da1B0TIK.d.cts → dev-unlock-DQCNDfFp.d.cts} +1 -1
  124. package/dist/{dev-unlock-BdPp68qn.d.ts → dev-unlock-utkybTKb.d.ts} +1 -1
  125. package/dist/executor-AS2IDHKZ.js +11 -0
  126. package/dist/executor-HLXFXNFM.js +8 -0
  127. package/dist/executor-HLXFXNFM.js.map +1 -0
  128. package/dist/executor-HN6YBHZ5.js +8 -0
  129. package/dist/executor-HN6YBHZ5.js.map +1 -0
  130. package/dist/fanout-sidecar-VJ52RIEY.js +51 -0
  131. package/dist/fanout-sidecar-VJ52RIEY.js.map +1 -0
  132. package/dist/guards/index.cjs +315 -0
  133. package/dist/guards/index.cjs.map +1 -0
  134. package/dist/guards/index.d.cts +31 -0
  135. package/dist/guards/index.d.ts +31 -0
  136. package/dist/guards/index.js +29 -0
  137. package/dist/guards/index.js.map +1 -0
  138. package/dist/{hash-lsoL3eEW.d.ts → hash-DcoYWfJ_.d.ts} +1 -1
  139. package/dist/{hash-BEfzPKwo.d.cts → hash-jDowCrK2.d.cts} +1 -1
  140. package/dist/history/index.cjs +8 -1
  141. package/dist/history/index.cjs.map +1 -1
  142. package/dist/history/index.d.cts +8 -7
  143. package/dist/history/index.d.ts +8 -7
  144. package/dist/history/index.js +6 -6
  145. package/dist/i18n/index.cjs +81 -0
  146. package/dist/i18n/index.cjs.map +1 -1
  147. package/dist/i18n/index.d.cts +7 -6
  148. package/dist/i18n/index.d.ts +7 -6
  149. package/dist/i18n/index.js +27 -12
  150. package/dist/i18n/index.js.map +1 -1
  151. package/dist/{index-6xNpPsxR.d.cts → index-BCKdioeh.d.ts} +331 -5
  152. package/dist/{index-DJTf9yxn.d.ts → index-BMjrzNZr.d.cts} +331 -5
  153. package/dist/index.cjs +6065 -959
  154. package/dist/index.cjs.map +1 -1
  155. package/dist/index.d.cts +208 -16
  156. package/dist/index.d.ts +208 -16
  157. package/dist/index.js +242 -7392
  158. package/dist/index.js.map +1 -1
  159. package/dist/indexing/index.cjs +2 -0
  160. package/dist/indexing/index.cjs.map +1 -1
  161. package/dist/indexing/index.d.cts +3 -3
  162. package/dist/indexing/index.d.ts +3 -3
  163. package/dist/indexing/index.js +4 -4
  164. package/dist/issue-ORP37MVW.js +12 -0
  165. package/dist/issue-ORP37MVW.js.map +1 -0
  166. package/dist/{lazy-builder-CZVLKh0Z.d.cts → lazy-builder-C-rPfWG0.d.cts} +1 -1
  167. package/dist/{lazy-builder-BwEoBQZ9.d.ts → lazy-builder-Rpd-V3jP.d.ts} +1 -1
  168. package/dist/{ledger-QZTTHQAQ.js → ledger-3IU5GMXA.js} +6 -6
  169. package/dist/ledger-3IU5GMXA.js.map +1 -0
  170. package/dist/materialized-views/index.cjs +837 -0
  171. package/dist/materialized-views/index.cjs.map +1 -0
  172. package/dist/materialized-views/index.d.cts +184 -0
  173. package/dist/materialized-views/index.d.ts +184 -0
  174. package/dist/materialized-views/index.js +45 -0
  175. package/dist/materialized-views/index.js.map +1 -0
  176. package/dist/noydb-5H3C24GG.js +34 -0
  177. package/dist/noydb-5H3C24GG.js.map +1 -0
  178. package/dist/overlay-views/index.cjs +359 -0
  179. package/dist/overlay-views/index.cjs.map +1 -0
  180. package/dist/overlay-views/index.d.cts +82 -0
  181. package/dist/overlay-views/index.d.ts +82 -0
  182. package/dist/overlay-views/index.js +25 -0
  183. package/dist/overlay-views/index.js.map +1 -0
  184. package/dist/periods/index.cjs +7 -1
  185. package/dist/periods/index.cjs.map +1 -1
  186. package/dist/periods/index.d.cts +7 -6
  187. package/dist/periods/index.d.ts +7 -6
  188. package/dist/periods/index.js +6 -6
  189. package/dist/{predicate-SBHmi6D0.d.cts → predicate-Dnu81tsS.d.cts} +25 -1
  190. package/dist/{predicate-SBHmi6D0.d.ts → predicate-Dnu81tsS.d.ts} +25 -1
  191. package/dist/{public-envelope-6JTACYJV.js → public-envelope-U3CMEOMV.js} +4 -4
  192. package/dist/public-envelope-U3CMEOMV.js.map +1 -0
  193. package/dist/query/index.cjs +302 -124
  194. package/dist/query/index.cjs.map +1 -1
  195. package/dist/query/index.d.cts +3 -3
  196. package/dist/query/index.d.ts +3 -3
  197. package/dist/query/index.js +26 -11
  198. package/dist/read-only-facade-ITU6L7BL.js +7 -0
  199. package/dist/read-only-facade-ITU6L7BL.js.map +1 -0
  200. package/dist/registry-3ALP62P6.js +10 -0
  201. package/dist/registry-3ALP62P6.js.map +1 -0
  202. package/dist/registry-7HE6VJGC.js +8 -0
  203. package/dist/registry-7HE6VJGC.js.map +1 -0
  204. package/dist/registry-PSIPG2QR.js +8 -0
  205. package/dist/registry-PSIPG2QR.js.map +1 -0
  206. package/dist/registry-RFGGMVNJ.js +7 -0
  207. package/dist/registry-RFGGMVNJ.js.map +1 -0
  208. package/dist/revoke-KY2GB4KP.js +17 -0
  209. package/dist/revoke-KY2GB4KP.js.map +1 -0
  210. package/dist/session/index.cjs +7 -1
  211. package/dist/session/index.cjs.map +1 -1
  212. package/dist/session/index.d.cts +8 -7
  213. package/dist/session/index.d.ts +8 -7
  214. package/dist/session/index.js +10 -3
  215. package/dist/session/index.js.map +1 -1
  216. package/dist/shadow/index.cjs.map +1 -1
  217. package/dist/shadow/index.d.cts +7 -6
  218. package/dist/shadow/index.d.ts +7 -6
  219. package/dist/shadow/index.js +2 -2
  220. package/dist/signer-GRI5TZKH.js +18 -0
  221. package/dist/signer-GRI5TZKH.js.map +1 -0
  222. package/dist/stale-OTOF3FH7.js +13 -0
  223. package/dist/stale-OTOF3FH7.js.map +1 -0
  224. package/dist/store/index.cjs +14 -0
  225. package/dist/store/index.cjs.map +1 -1
  226. package/dist/store/index.d.cts +7 -6
  227. package/dist/store/index.d.ts +7 -6
  228. package/dist/store/index.js +5 -2
  229. package/dist/{strategy-D-SrOLCl.d.cts → strategy-DSTrsZ8t.d.cts} +72 -19
  230. package/dist/{strategy-D-SrOLCl.d.ts → strategy-DSTrsZ8t.d.ts} +72 -19
  231. package/dist/sync/index.cjs.map +1 -1
  232. package/dist/sync/index.d.cts +6 -5
  233. package/dist/sync/index.d.ts +6 -5
  234. package/dist/sync/index.js +4 -4
  235. package/dist/team/index.cjs +1554 -2
  236. package/dist/team/index.cjs.map +1 -1
  237. package/dist/team/index.d.cts +7 -6
  238. package/dist/team/index.d.ts +7 -6
  239. package/dist/team/index.js +77 -8
  240. package/dist/tx/index.cjs +296 -44
  241. package/dist/tx/index.cjs.map +1 -1
  242. package/dist/tx/index.d.cts +7 -6
  243. package/dist/tx/index.d.ts +7 -6
  244. package/dist/tx/index.js +2 -2
  245. package/dist/{types-Bo7NSXJr.d.ts → types-BoFFiskX.d.ts} +2714 -321
  246. package/dist/{types-Bnb82f5R.d.cts → types-DJG8HG6F.d.cts} +2714 -321
  247. package/dist/{index-CywCC1qZ.d.cts → ulid-BmBgooGm.d.ts} +215 -26
  248. package/dist/{index-8QDuznDr.d.ts → ulid-C7ms9oli.d.cts} +215 -26
  249. package/dist/util/index.cjs.map +1 -1
  250. package/dist/util/index.js +1 -1
  251. package/dist/with-derivation-BKXXa8Vt.d.ts +13 -0
  252. package/dist/with-derivation-BjQ7q4NE.d.cts +13 -0
  253. package/dist/with-guard-C25yNjzd.d.ts +18 -0
  254. package/dist/with-guard-DQme5DKE.d.cts +18 -0
  255. package/dist/with-materialized-view-BbEPFIIJ.d.cts +27 -0
  256. package/dist/with-materialized-view-CqnRwI2S.d.ts +27 -0
  257. package/dist/with-overlayed-view-Ct1fSJt-.d.ts +13 -0
  258. package/dist/with-overlayed-view-bwlmmFjx.d.cts +13 -0
  259. package/package.json +65 -2
  260. package/dist/chunk-2CSJGFCB.js.map +0 -1
  261. package/dist/chunk-ACLDOTNQ.js.map +0 -1
  262. package/dist/chunk-BTDCBVJW.js +0 -160
  263. package/dist/chunk-BTDCBVJW.js.map +0 -1
  264. package/dist/chunk-CIMZBAZB.js.map +0 -1
  265. package/dist/chunk-EXHNQEV4.js +0 -392
  266. package/dist/chunk-EXHNQEV4.js.map +0 -1
  267. package/dist/chunk-GOUT6DND.js.map +0 -1
  268. package/dist/chunk-M5INGEFC.js.map +0 -1
  269. package/dist/chunk-MDDTIZUO.js.map +0 -1
  270. package/dist/chunk-QAVUREFT.js.map +0 -1
  271. package/dist/chunk-RKJ6OL7K.js.map +0 -1
  272. package/dist/chunk-SCZXXXU4.js.map +0 -1
  273. package/dist/chunk-TDR6T5CJ.js.map +0 -1
  274. package/dist/chunk-WDM5XGGS.js.map +0 -1
  275. /package/dist/{chunk-PTVMYYON.js.map → chunk-243PNUA6.js.map} +0 -0
  276. /package/dist/{chunk-MR4424N3.js.map → chunk-2PAQNPE3.js.map} +0 -0
  277. /package/dist/{chunk-AVVPZ4BC.js.map → chunk-3Y53S2SA.js.map} +0 -0
  278. /package/dist/{chunk-ZFKD4QMV.js.map → chunk-7BRE6EUA.js.map} +0 -0
  279. /package/dist/{chunk-USKYUS74.js.map → chunk-MUWOSVEP.js.map} +0 -0
  280. /package/dist/{chunk-4PWAI7Q4.js.map → chunk-NWZ3I6R6.js.map} +0 -0
  281. /package/dist/{chunk-QGZRWRSL.js.map → chunk-QPEXPHJR.js.map} +0 -0
  282. /package/dist/{chunk-R36SIKES.js.map → chunk-QXQRKXCU.js.map} +0 -0
  283. /package/dist/{chunk-M62XNWRA.js.map → chunk-VK5EER6C.js.map} +0 -0
  284. /package/dist/{chunk-NPC4LFV5.js.map → chunk-YMYK7US4.js.map} +0 -0
  285. /package/dist/{crypto-IVKU7YTT.js.map → crypto-5ZDIY3NG.js.map} +0 -0
  286. /package/dist/{delegation-2DBS2EOH.js.map → delegation-QYXZW25W.js.map} +0 -0
  287. /package/dist/{ledger-QZTTHQAQ.js.map → derivations/index.js.map} +0 -0
  288. /package/dist/{public-envelope-6JTACYJV.js.map → executor-AS2IDHKZ.js.map} +0 -0
@@ -24,15 +24,48 @@ __export(team_exports, {
24
24
  SYNC_CREDENTIALS_COLLECTION: () => SYNC_CREDENTIALS_COLLECTION,
25
25
  SyncEngine: () => SyncEngine,
26
26
  SyncTransaction: () => SyncTransaction,
27
+ buildRecipientKeyringFile: () => buildRecipientKeyringFile,
28
+ burnPaperRecoveryEntry: () => burnPaperRecoveryEntry,
29
+ changeSecret: () => changeSecret,
30
+ createOwnerKeyring: () => createOwnerKeyring,
27
31
  credentialStatus: () => credentialStatus,
28
32
  deleteCredential: () => deleteCredential,
33
+ deriveMagicLinkContentKey: () => deriveMagicLinkContentKey,
34
+ enrollAuthenticator: () => enrollAuthenticator,
35
+ ensureCollectionDEK: () => ensureCollectionDEK,
29
36
  evaluateExportCapability: () => evaluateExportCapability,
30
37
  evaluateImportCapability: () => evaluateImportCapability,
38
+ findAuthenticator: () => findAuthenticator,
31
39
  getCredential: () => getCredential,
40
+ grant: () => grant,
32
41
  hasExportCapability: () => hasExportCapability,
33
42
  hasImportCapability: () => hasImportCapability,
43
+ isMagicLinkGrantExpired: () => isMagicLinkGrantExpired,
34
44
  listCredentials: () => listCredentials,
35
- putCredential: () => putCredential
45
+ listMagicLinkGrants: () => listMagicLinkGrants,
46
+ listUsers: () => listUsers,
47
+ listUsersWithEnvelopes: () => listUsersWithEnvelopes,
48
+ loadKeyring: () => loadKeyring,
49
+ loadPaperRecoveryEntries: () => loadPaperRecoveryEntries,
50
+ magicLinkGrantRecordId: () => magicLinkGrantRecordId,
51
+ mintPaperRecoveryEntry: () => mintPaperRecoveryEntry,
52
+ mintWrappedDeksBlob: () => mintWrappedDeksBlob,
53
+ persistKeyring: () => persistKeyring,
54
+ putCredential: () => putCredential,
55
+ readMagicLinkGrantRecord: () => readMagicLinkGrantRecord,
56
+ recoverPassphrase: () => recoverPassphrase,
57
+ recoverUser: () => recoverUser,
58
+ removeAuthenticator: () => removeAuthenticator,
59
+ revoke: () => revoke,
60
+ revokeMagicLinkGrant: () => revokeMagicLinkGrant,
61
+ rotatePassphrase: () => rotatePassphrase,
62
+ savePaperRecoveryEntries: () => savePaperRecoveryEntries,
63
+ unwrapDeksFromBlob: () => unwrapDeksFromBlob,
64
+ unwrapDeksFromPaperEntry: () => unwrapDeksFromPaperEntry,
65
+ unwrapMagicLinkGrant: () => unwrapMagicLinkGrant,
66
+ updateAuthenticator: () => updateAuthenticator,
67
+ updateKeyringIdentity: () => updateKeyringIdentity,
68
+ writeMagicLinkGrant: () => writeMagicLinkGrant
36
69
  });
37
70
  module.exports = __toCommonJS(team_exports);
38
71
 
@@ -63,12 +96,83 @@ var TamperedError = class extends NoydbError {
63
96
  this.name = "TamperedError";
64
97
  }
65
98
  };
99
+ var InvalidKeyError = class extends NoydbError {
100
+ constructor(message = "Invalid key \u2014 wrong passphrase or corrupted keyring") {
101
+ super("INVALID_KEY", message);
102
+ this.name = "InvalidKeyError";
103
+ }
104
+ };
105
+ var KeyringCorruptError = class extends NoydbError {
106
+ failedCollections;
107
+ intactCount;
108
+ constructor(opts) {
109
+ super(
110
+ "KEYRING_CORRUPT",
111
+ opts.message ?? `Keyring has ${opts.failedCollections.length} corrupted wrapped DEK(s) (${opts.failedCollections.join(", ")}); ${opts.intactCount} other DEK(s) unwrapped successfully \u2014 the passphrase is correct, the entries are damaged. Do NOT use onInvalidKey: 'reset' here \u2014 that would destroy the intact DEKs.`
112
+ );
113
+ this.name = "KeyringCorruptError";
114
+ this.failedCollections = opts.failedCollections;
115
+ this.intactCount = opts.intactCount;
116
+ }
117
+ };
118
+ var NoAccessError = class extends NoydbError {
119
+ constructor(message = "No access \u2014 user does not have a key for this collection") {
120
+ super("NO_ACCESS", message);
121
+ this.name = "NoAccessError";
122
+ }
123
+ };
66
124
  var PermissionDeniedError = class extends NoydbError {
67
125
  constructor(message = "Permission denied \u2014 insufficient role for this operation") {
68
126
  super("PERMISSION_DENIED", message);
69
127
  this.name = "PermissionDeniedError";
70
128
  }
71
129
  };
130
+ var KeyringExpiredError = class extends NoydbError {
131
+ userId;
132
+ expiresAt;
133
+ constructor(opts) {
134
+ super(
135
+ "KEYRING_EXPIRED",
136
+ `Keyring "${opts.userId}" expired at ${opts.expiresAt}. The slot refuses to unlock past its expiry timestamp.`
137
+ );
138
+ this.name = "KeyringExpiredError";
139
+ this.userId = opts.userId;
140
+ this.expiresAt = opts.expiresAt;
141
+ }
142
+ };
143
+ var PrivilegeEscalationError = class extends NoydbError {
144
+ offendingCollection;
145
+ constructor(offendingCollection, message) {
146
+ super(
147
+ "PRIVILEGE_ESCALATION",
148
+ message ?? `Privilege escalation: grantor has no DEK for collection "${offendingCollection}" and cannot grant access to it.`
149
+ );
150
+ this.name = "PrivilegeEscalationError";
151
+ this.offendingCollection = offendingCollection;
152
+ }
153
+ };
154
+ var DirectoryDisabledError = class extends NoydbError {
155
+ vault;
156
+ constructor(vault) {
157
+ super(
158
+ "DIRECTORY_DISABLED",
159
+ `Vault "${vault}" has its user directory disabled. Only owners and admins can call listUsersWithEnvelopes() here. Use db.setDirectoryEnabled(vault, true) to re-enable.`
160
+ );
161
+ this.name = "DirectoryDisabledError";
162
+ this.vault = vault;
163
+ }
164
+ };
165
+ var DelegationTargetMissingError = class extends NoydbError {
166
+ toUser;
167
+ constructor(toUser) {
168
+ super(
169
+ "DELEGATION_TARGET_MISSING",
170
+ `Delegation target user "${toUser}" has no keyring in this vault`
171
+ );
172
+ this.name = "DelegationTargetMissingError";
173
+ this.toUser = toUser;
174
+ }
175
+ };
72
176
  var ConflictError = class extends NoydbError {
73
177
  /** The actual stored version at the time of conflict. */
74
178
  version;
@@ -86,9 +190,32 @@ var ValidationError = class extends NoydbError {
86
190
  };
87
191
 
88
192
  // src/crypto.ts
193
+ var PBKDF2_ITERATIONS = 6e5;
194
+ var SALT_BYTES = 32;
89
195
  var IV_BYTES = 12;
90
196
  var KEY_BITS = 256;
91
197
  var subtle = globalThis.crypto.subtle;
198
+ async function deriveKey(passphrase, salt) {
199
+ const keyMaterial = await subtle.importKey(
200
+ "raw",
201
+ new TextEncoder().encode(passphrase),
202
+ "PBKDF2",
203
+ false,
204
+ ["deriveKey"]
205
+ );
206
+ return subtle.deriveKey(
207
+ {
208
+ name: "PBKDF2",
209
+ salt,
210
+ iterations: PBKDF2_ITERATIONS,
211
+ hash: "SHA-256"
212
+ },
213
+ keyMaterial,
214
+ { name: "AES-KW", length: KEY_BITS },
215
+ false,
216
+ ["wrapKey", "unwrapKey"]
217
+ );
218
+ }
92
219
  async function generateDEK() {
93
220
  return subtle.generateKey(
94
221
  { name: "AES-GCM", length: KEY_BITS },
@@ -101,6 +228,21 @@ async function wrapKey(dek, kek) {
101
228
  const wrapped = await subtle.wrapKey("raw", dek, kek, "AES-KW");
102
229
  return bufferToBase64(wrapped);
103
230
  }
231
+ async function unwrapKey(wrappedBase64, kek) {
232
+ try {
233
+ return await subtle.unwrapKey(
234
+ "raw",
235
+ base64ToBuffer(wrappedBase64),
236
+ kek,
237
+ "AES-KW",
238
+ { name: "AES-GCM", length: KEY_BITS },
239
+ true,
240
+ ["encrypt", "decrypt"]
241
+ );
242
+ } catch {
243
+ throw new InvalidKeyError();
244
+ }
245
+ }
104
246
  async function encrypt(plaintext, dek) {
105
247
  const iv = generateIV();
106
248
  const encoded = new TextEncoder().encode(plaintext);
@@ -160,6 +302,9 @@ async function derivePresenceKey(dek, collectionName) {
160
302
  function generateIV() {
161
303
  return globalThis.crypto.getRandomValues(new Uint8Array(IV_BYTES));
162
304
  }
305
+ function generateSalt() {
306
+ return globalThis.crypto.getRandomValues(new Uint8Array(SALT_BYTES));
307
+ }
163
308
  function bufferToBase64(buffer) {
164
309
  const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
165
310
  let binary = "";
@@ -177,7 +322,686 @@ function base64ToBuffer(base64) {
177
322
  return bytes;
178
323
  }
179
324
 
325
+ // src/directory/storage.ts
326
+ var META_COLLECTION = "_meta";
327
+ var DIRECTORY_RECORD_ID = "directory";
328
+ async function readDirectoryConfig(store, vault) {
329
+ const envelope = await store.get(vault, META_COLLECTION, DIRECTORY_RECORD_ID);
330
+ if (!envelope) return void 0;
331
+ try {
332
+ const parsed = JSON.parse(envelope._data);
333
+ if (!isDirectoryConfig(parsed)) return void 0;
334
+ return parsed;
335
+ } catch {
336
+ return void 0;
337
+ }
338
+ }
339
+ function isDirectoryConfig(x) {
340
+ if (x === null || typeof x !== "object") return false;
341
+ if (!("enabled" in x)) return false;
342
+ return typeof x.enabled === "boolean";
343
+ }
344
+
345
+ // src/directory/visibility.ts
346
+ var VISIBILITY_RECORD_PREFIX = "visibility/";
347
+ function visibilityRecordId(keyringId) {
348
+ return VISIBILITY_RECORD_PREFIX + keyringId;
349
+ }
350
+ async function readUserVisibility(store, vault, keyringId) {
351
+ const envelope = await store.get(vault, META_COLLECTION, visibilityRecordId(keyringId));
352
+ if (!envelope) return void 0;
353
+ try {
354
+ const parsed = JSON.parse(envelope._data);
355
+ if (!isUserVisibility(parsed)) return void 0;
356
+ return parsed;
357
+ } catch {
358
+ return void 0;
359
+ }
360
+ }
361
+ async function deleteUserVisibility(store, vault, keyringId) {
362
+ await store.delete(vault, META_COLLECTION, visibilityRecordId(keyringId));
363
+ }
364
+ function isUserVisibility(x) {
365
+ if (x === null || typeof x !== "object") return false;
366
+ if (!("hidden" in x)) return false;
367
+ return typeof x.hidden === "boolean";
368
+ }
369
+
370
+ // src/validation.ts
371
+ var WeakPassphraseError = class extends NoydbError {
372
+ reason;
373
+ suggestion;
374
+ constructor(reason, suggestion) {
375
+ super("WEAK_PASSPHRASE", `Weak passphrase (${reason}). ${suggestion}`);
376
+ this.name = "WeakPassphraseError";
377
+ this.reason = reason;
378
+ this.suggestion = suggestion;
379
+ }
380
+ };
381
+ var DEFAULT_MIN_WORDS = 6;
382
+ var DEFAULT_MIN_WORD_LENGTH = 3;
383
+ var SUGGESTIONS = {
384
+ empty: "Provide a phrase of at least 6 lowercase words separated by single spaces.",
385
+ "invalid-chars": "Use only lowercase letters [a-z] and single spaces. No punctuation, symbols, digits, or uppercase.",
386
+ "leading-or-trailing-space": "Trim leading and trailing spaces.",
387
+ "double-space": "Use exactly one space between words.",
388
+ "too-few-words": 'Use at least 6 words by default (8 under strict policy). Example: "correct horse battery staple printer toaster".',
389
+ "word-too-short": 'Each word must be at least 3 characters. Drop short fillers like "a", "is", "of".',
390
+ "repeated-adjacent": "Avoid repeating the same word twice in a row."
391
+ };
392
+ function validatePassphrase(s, opts) {
393
+ if (opts?.customValidator) {
394
+ return opts.customValidator(s);
395
+ }
396
+ const minWords = opts?.minWords ?? DEFAULT_MIN_WORDS;
397
+ const minWordLength = opts?.minWordLength ?? DEFAULT_MIN_WORD_LENGTH;
398
+ const rejectRepeated = opts?.rejectRepeatedAdjacent ?? true;
399
+ if (s.length === 0) {
400
+ return { ok: false, reason: "empty" };
401
+ }
402
+ if (s !== s.trim()) {
403
+ return { ok: false, reason: "leading-or-trailing-space" };
404
+ }
405
+ if (s.includes(" ")) {
406
+ return { ok: false, reason: "double-space" };
407
+ }
408
+ const charPattern = opts?.pattern ?? /^[a-z]+( [a-z]+)*$/;
409
+ if (!charPattern.test(s)) {
410
+ return { ok: false, reason: "invalid-chars" };
411
+ }
412
+ const words = s.split(" ");
413
+ if (words.length < minWords) {
414
+ return { ok: false, reason: "too-few-words", minimum: minWords, got: words.length };
415
+ }
416
+ for (const w of words) {
417
+ if (w.length < minWordLength) {
418
+ return { ok: false, reason: "word-too-short", minimum: minWordLength, got: w.length };
419
+ }
420
+ }
421
+ if (rejectRepeated) {
422
+ for (let i = 1; i < words.length; i++) {
423
+ if (words[i] === words[i - 1]) {
424
+ return { ok: false, reason: "repeated-adjacent" };
425
+ }
426
+ }
427
+ }
428
+ return { ok: true, words: words.length };
429
+ }
430
+ function assertStrongPassphrase(s, opts) {
431
+ if (opts?.allowWeakPassphrase) return;
432
+ const result = validatePassphrase(s, opts);
433
+ if (result.ok) return;
434
+ throw new WeakPassphraseError(result.reason, SUGGESTIONS[result.reason]);
435
+ }
436
+
437
+ // src/meta/user-envelope/types.ts
438
+ var USER_ENVELOPE_MAX_BYTES = 64 * 1024;
439
+ var USER_ENVELOPE_COLLECTION = "_users";
440
+ var UserEnvelopeOversizedError = class extends NoydbError {
441
+ bytes;
442
+ limit;
443
+ constructor(bytes, limit = USER_ENVELOPE_MAX_BYTES) {
444
+ super(
445
+ "USER_ENVELOPE_OVERSIZED",
446
+ `User envelope payload is ${bytes} bytes; soft cap is ${limit} bytes. Move large data into the vault's regular collections.`
447
+ );
448
+ this.name = "UserEnvelopeOversizedError";
449
+ this.bytes = bytes;
450
+ this.limit = limit;
451
+ }
452
+ };
453
+
454
+ // src/meta/user-envelope/storage.ts
455
+ async function loadUserEnvelope(store, vault, keyringId, dek) {
456
+ const envelope = await store.get(vault, USER_ENVELOPE_COLLECTION, keyringId);
457
+ if (!envelope) return null;
458
+ const plaintext = await decrypt(envelope._iv, envelope._data, dek);
459
+ const data = JSON.parse(plaintext);
460
+ return {
461
+ keyringId,
462
+ data,
463
+ _v: envelope._v,
464
+ _ts: envelope._ts
465
+ };
466
+ }
467
+ async function saveUserEnvelope(store, vault, keyringId, payload, dek, expectedVersion) {
468
+ const json = JSON.stringify(payload);
469
+ const bytes = new TextEncoder().encode(json).byteLength;
470
+ if (bytes > USER_ENVELOPE_MAX_BYTES) {
471
+ throw new UserEnvelopeOversizedError(bytes);
472
+ }
473
+ const prior = await store.get(vault, USER_ENVELOPE_COLLECTION, keyringId);
474
+ if (expectedVersion !== void 0) {
475
+ const priorVersion = prior?._v ?? 0;
476
+ if (priorVersion !== expectedVersion) {
477
+ throw new ConflictError(
478
+ priorVersion,
479
+ `User envelope for "${keyringId}" expected version ${expectedVersion}, actual ${priorVersion}`
480
+ );
481
+ }
482
+ }
483
+ const nextVersion = (prior?._v ?? 0) + 1;
484
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
485
+ const { iv, data } = await encrypt(json, dek);
486
+ const envelope = {
487
+ _noydb: NOYDB_FORMAT_VERSION,
488
+ _v: nextVersion,
489
+ _ts: ts,
490
+ _iv: iv,
491
+ _data: data
492
+ };
493
+ await store.put(vault, USER_ENVELOPE_COLLECTION, keyringId, envelope);
494
+ return {
495
+ keyringId,
496
+ data: payload,
497
+ _v: nextVersion,
498
+ _ts: ts
499
+ };
500
+ }
501
+ async function deleteUserEnvelope(store, vault, keyringId) {
502
+ await store.delete(vault, USER_ENVELOPE_COLLECTION, keyringId);
503
+ }
504
+
180
505
  // src/team/keyring.ts
506
+ var ADMIN_GRANTABLE_TARGETS = ["operator", "viewer", "client", "admin"];
507
+ function canGrant(callerRole, targetRole) {
508
+ if (callerRole === "owner") return true;
509
+ if (callerRole === "admin") return ADMIN_GRANTABLE_TARGETS.includes(targetRole);
510
+ return false;
511
+ }
512
+ function canRevoke(callerRole, targetRole) {
513
+ if (targetRole === "owner") return false;
514
+ if (callerRole === "owner") return true;
515
+ if (callerRole === "admin") return ADMIN_GRANTABLE_TARGETS.includes(targetRole);
516
+ return false;
517
+ }
518
+ function canUpdateRole(callerRole, targetRole) {
519
+ if (callerRole === "owner") return true;
520
+ if (callerRole === "admin") return ADMIN_GRANTABLE_TARGETS.includes(targetRole);
521
+ return false;
522
+ }
523
+ var CANARY_PLAINTEXT_BYTES = new Uint8Array(32);
524
+ var canaryKeyPromise = null;
525
+ function getCanaryKey() {
526
+ if (canaryKeyPromise === null) {
527
+ canaryKeyPromise = globalThis.crypto.subtle.importKey(
528
+ "raw",
529
+ CANARY_PLAINTEXT_BYTES,
530
+ { name: "AES-GCM", length: 256 },
531
+ true,
532
+ // extractable so AES-KW can wrap it
533
+ ["encrypt", "decrypt"]
534
+ );
535
+ }
536
+ return canaryKeyPromise;
537
+ }
538
+ async function mintKeyringCanary(kek) {
539
+ const canaryKey = await getCanaryKey();
540
+ return wrapKey(canaryKey, kek);
541
+ }
542
+ async function verifyKeyringCanary(wrappedCanary, kek) {
543
+ try {
544
+ await unwrapKey(wrappedCanary, kek);
545
+ return true;
546
+ } catch {
547
+ return false;
548
+ }
549
+ }
550
+ async function loadKeyring(adapter, vault, userId, passphrase) {
551
+ const envelope = await adapter.get(vault, "_keyring", userId);
552
+ if (!envelope) {
553
+ throw new NoAccessError(`No keyring found for user "${userId}" in vault "${vault}"`);
554
+ }
555
+ const keyringFile = JSON.parse(envelope._data);
556
+ if (keyringFile.expires_at !== void 0) {
557
+ const cutoff = Date.parse(keyringFile.expires_at);
558
+ if (Number.isFinite(cutoff) && Date.now() >= cutoff) {
559
+ throw new KeyringExpiredError({ userId: keyringFile.user_id, expiresAt: keyringFile.expires_at });
560
+ }
561
+ }
562
+ const salt = base64ToBuffer(keyringFile.salt);
563
+ const kek = await deriveKey(passphrase, salt);
564
+ const canaryOk = keyringFile.canary !== void 0 ? await verifyKeyringCanary(keyringFile.canary, kek) : null;
565
+ const deks = /* @__PURE__ */ new Map();
566
+ const failedCollections = [];
567
+ let firstUnwrapError = null;
568
+ for (const [collName, wrappedDek] of Object.entries(keyringFile.deks)) {
569
+ try {
570
+ const dek = await unwrapKey(wrappedDek, kek);
571
+ deks.set(collName, dek);
572
+ } catch (err) {
573
+ failedCollections.push(collName);
574
+ if (firstUnwrapError === null) firstUnwrapError = err;
575
+ }
576
+ }
577
+ if (canaryOk === true) {
578
+ if (failedCollections.length > 0) {
579
+ throw new KeyringCorruptError({ failedCollections, intactCount: deks.size });
580
+ }
581
+ } else if (canaryOk === false) {
582
+ if (deks.size > 0) {
583
+ throw new KeyringCorruptError({
584
+ failedCollections: [...failedCollections, "_canary"],
585
+ intactCount: deks.size
586
+ });
587
+ }
588
+ throw firstUnwrapError instanceof Error ? firstUnwrapError : new InvalidKeyError();
589
+ } else {
590
+ if (failedCollections.length > 0) {
591
+ if (deks.size > 0) {
592
+ throw new KeyringCorruptError({ failedCollections, intactCount: deks.size });
593
+ }
594
+ throw firstUnwrapError instanceof Error ? firstUnwrapError : new InvalidKeyError();
595
+ }
596
+ }
597
+ return {
598
+ userId: keyringFile.user_id,
599
+ displayName: keyringFile.display_name,
600
+ role: keyringFile.role,
601
+ permissions: keyringFile.permissions,
602
+ deks,
603
+ kek,
604
+ salt,
605
+ authenticators: keyringFile.authenticators ?? [],
606
+ ...keyringFile.export_capability !== void 0 && { exportCapability: keyringFile.export_capability },
607
+ ...keyringFile.import_capability !== void 0 && { importCapability: keyringFile.import_capability },
608
+ ...keyringFile.policy !== void 0 && { policy: keyringFile.policy }
609
+ };
610
+ }
611
+ async function createOwnerKeyring(adapter, vault, userId, passphrase, passphraseOpts) {
612
+ if (passphraseOpts?.validate && !passphraseOpts.allowWeakPassphrase) {
613
+ assertStrongPassphrase(passphrase, passphraseOpts);
614
+ }
615
+ const salt = generateSalt();
616
+ const kek = await deriveKey(passphrase, salt);
617
+ const userEnvelopeDek = await generateDEK();
618
+ const wrappedUserEnvelopeDek = await wrapKey(userEnvelopeDek, kek);
619
+ const canary = await mintKeyringCanary(kek);
620
+ const keyringFile = {
621
+ _noydb_keyring: NOYDB_KEYRING_VERSION,
622
+ user_id: userId,
623
+ display_name: userId,
624
+ role: "owner",
625
+ permissions: {},
626
+ deks: { [USER_ENVELOPE_COLLECTION]: wrappedUserEnvelopeDek },
627
+ salt: bufferToBase64(salt),
628
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
629
+ granted_by: userId,
630
+ canary
631
+ };
632
+ await writeKeyringFile(adapter, vault, userId, keyringFile);
633
+ return {
634
+ userId,
635
+ displayName: userId,
636
+ role: "owner",
637
+ permissions: {},
638
+ deks: /* @__PURE__ */ new Map([[USER_ENVELOPE_COLLECTION, userEnvelopeDek]]),
639
+ kek,
640
+ salt,
641
+ authenticators: []
642
+ };
643
+ }
644
+ async function grant(adapter, vault, callerKeyring, options) {
645
+ if (!callerKeyring.kek) {
646
+ throw new ValidationError(
647
+ "grant: caller keyring has no KEK \u2014 tier-2 wrap-DEKs and tier-3 PIN-resume sessions cannot grant access to other users. Re-authenticate at tier 1 (passphrase) before granting."
648
+ );
649
+ }
650
+ if (!canGrant(callerKeyring.role, options.role)) {
651
+ throw new PermissionDeniedError(
652
+ `Role "${callerKeyring.role}" cannot grant role "${options.role}"`
653
+ );
654
+ }
655
+ if (options.validatePassphrase && !options.allowWeakPassphrase) {
656
+ assertStrongPassphrase(options.passphrase);
657
+ }
658
+ const permissions = resolvePermissions(options.role, options.permissions);
659
+ const newSalt = generateSalt();
660
+ const newKek = await deriveKey(options.passphrase, newSalt);
661
+ const wrappedDeks = {};
662
+ for (const collName of Object.keys(permissions)) {
663
+ const dek = callerKeyring.deks.get(collName);
664
+ if (dek) {
665
+ wrappedDeks[collName] = await wrapKey(dek, newKek);
666
+ }
667
+ }
668
+ if (options.role === "owner" || options.role === "admin" || options.role === "viewer") {
669
+ for (const [collName, dek] of callerKeyring.deks) {
670
+ if (!(collName in wrappedDeks)) {
671
+ wrappedDeks[collName] = await wrapKey(dek, newKek);
672
+ }
673
+ }
674
+ }
675
+ for (const [collName, dek] of callerKeyring.deks) {
676
+ if (collName.startsWith("_") && !(collName in wrappedDeks)) {
677
+ wrappedDeks[collName] = await wrapKey(dek, newKek);
678
+ }
679
+ }
680
+ for (const collName of Object.keys(wrappedDeks)) {
681
+ if (!callerKeyring.deks.has(collName)) {
682
+ throw new PrivilegeEscalationError(collName);
683
+ }
684
+ }
685
+ const canary = await mintKeyringCanary(newKek);
686
+ const keyringFile = {
687
+ _noydb_keyring: NOYDB_KEYRING_VERSION,
688
+ user_id: options.userId,
689
+ display_name: options.displayName,
690
+ role: options.role,
691
+ permissions,
692
+ deks: wrappedDeks,
693
+ salt: bufferToBase64(newSalt),
694
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
695
+ granted_by: callerKeyring.userId,
696
+ canary,
697
+ ...options.exportCapability !== void 0 && { export_capability: options.exportCapability },
698
+ ...options.importCapability !== void 0 && { import_capability: options.importCapability }
699
+ };
700
+ await writeKeyringFile(adapter, vault, options.userId, keyringFile);
701
+ const userEnvelopeDek = callerKeyring.deks.get(USER_ENVELOPE_COLLECTION);
702
+ if (userEnvelopeDek) {
703
+ const initialPayload = options.initialProfile ?? {};
704
+ await saveUserEnvelope(
705
+ adapter,
706
+ vault,
707
+ options.userId,
708
+ initialPayload,
709
+ userEnvelopeDek
710
+ );
711
+ }
712
+ }
713
+ async function findAdminDescendants(adapter, vault, rootUserId) {
714
+ const allUserIds = await adapter.list(vault, "_keyring");
715
+ const childrenByParent = /* @__PURE__ */ new Map();
716
+ for (const userId of allUserIds) {
717
+ const env = await adapter.get(vault, "_keyring", userId);
718
+ if (!env) continue;
719
+ const kf = JSON.parse(env._data);
720
+ if (kf.role !== "admin") continue;
721
+ if (kf.user_id === rootUserId) continue;
722
+ const list = childrenByParent.get(kf.granted_by) ?? [];
723
+ list.push(kf.user_id);
724
+ childrenByParent.set(kf.granted_by, list);
725
+ }
726
+ const visited = /* @__PURE__ */ new Set();
727
+ const order = [];
728
+ const stack = [...childrenByParent.get(rootUserId) ?? []];
729
+ while (stack.length > 0) {
730
+ const next = stack.pop();
731
+ if (visited.has(next)) continue;
732
+ visited.add(next);
733
+ order.push(next);
734
+ for (const grandchild of childrenByParent.get(next) ?? []) {
735
+ if (!visited.has(grandchild)) stack.push(grandchild);
736
+ }
737
+ }
738
+ return order;
739
+ }
740
+ async function revoke(adapter, vault, callerKeyring, options) {
741
+ const targetEnvelope = await adapter.get(vault, "_keyring", options.userId);
742
+ if (!targetEnvelope) {
743
+ throw new NoAccessError(`User "${options.userId}" has no keyring in vault "${vault}"`);
744
+ }
745
+ const targetKeyring = JSON.parse(targetEnvelope._data);
746
+ if (!canRevoke(callerKeyring.role, targetKeyring.role)) {
747
+ throw new PermissionDeniedError(
748
+ `Role "${callerKeyring.role}" cannot revoke role "${targetKeyring.role}"`
749
+ );
750
+ }
751
+ const cascadeMode = options.cascade ?? "strict";
752
+ const usersToRevoke = [options.userId];
753
+ const affectedCollections = new Set(Object.keys(targetKeyring.deks));
754
+ if (targetKeyring.role === "admin") {
755
+ const descendants = await findAdminDescendants(adapter, vault, options.userId);
756
+ if (descendants.length > 0) {
757
+ if (cascadeMode === "warn") {
758
+ console.warn(
759
+ `[noy-db] revoke(${options.userId}): cascade='warn' \u2014 leaving ${descendants.length} descendant admin(s) in place: ${descendants.join(", ")}. These admins were granted by the revoked user (transitively) and will become orphans in the delegation tree.`
760
+ );
761
+ } else {
762
+ for (const userId of descendants) {
763
+ const descEnv = await adapter.get(vault, "_keyring", userId);
764
+ if (!descEnv) continue;
765
+ const descKf = JSON.parse(descEnv._data);
766
+ usersToRevoke.push(userId);
767
+ for (const c of Object.keys(descKf.deks)) affectedCollections.add(c);
768
+ }
769
+ }
770
+ }
771
+ }
772
+ for (const userId of usersToRevoke) {
773
+ await adapter.delete(vault, "_keyring", userId);
774
+ await deleteUserEnvelope(adapter, vault, userId);
775
+ await deleteUserVisibility(adapter, vault, userId);
776
+ }
777
+ if (options.rotateKeys !== false && affectedCollections.size > 0) {
778
+ await rotateKeys(adapter, vault, callerKeyring, [...affectedCollections]);
779
+ }
780
+ }
781
+ async function updateKeyringIdentity(adapter, vault, callerKeyring, options) {
782
+ if (options.role === void 0 && options.displayName === void 0 && options.permissions === void 0) {
783
+ throw new ValidationError(
784
+ `updateUser: at least one of role / displayName / permissions must be provided (userId: "${options.userId}").`
785
+ );
786
+ }
787
+ const env = await adapter.get(vault, "_keyring", options.userId);
788
+ if (!env) {
789
+ throw new NoAccessError(
790
+ `updateUser: user "${options.userId}" has no keyring in vault "${vault}".`
791
+ );
792
+ }
793
+ const target = JSON.parse(env._data);
794
+ if (!canUpdateRole(callerKeyring.role, target.role)) {
795
+ throw new PermissionDeniedError(
796
+ `Role "${callerKeyring.role}" cannot update a keyring with role "${target.role}"`
797
+ );
798
+ }
799
+ if (options.role !== void 0 && options.role !== target.role && !canUpdateRole(callerKeyring.role, options.role)) {
800
+ throw new PermissionDeniedError(
801
+ `Role "${callerKeyring.role}" cannot promote target to role "${options.role}"`
802
+ );
803
+ }
804
+ const next = {
805
+ ...target,
806
+ ...options.role !== void 0 && { role: options.role },
807
+ ...options.displayName !== void 0 && {
808
+ // null clears the field (stored as ""); a string sets it.
809
+ display_name: options.displayName ?? ""
810
+ },
811
+ ...options.permissions !== void 0 && { permissions: options.permissions }
812
+ };
813
+ await writeKeyringFile(adapter, vault, options.userId, next);
814
+ }
815
+ async function rotateKeys(adapter, vault, callerKeyring, collections) {
816
+ const newDeks = /* @__PURE__ */ new Map();
817
+ for (const collName of collections) {
818
+ newDeks.set(collName, await generateDEK());
819
+ }
820
+ for (const collName of collections) {
821
+ const oldDek = callerKeyring.deks.get(collName);
822
+ const newDek = newDeks.get(collName);
823
+ if (!oldDek) continue;
824
+ const ids = await adapter.list(vault, collName);
825
+ for (const id of ids) {
826
+ const envelope = await adapter.get(vault, collName, id);
827
+ if (!envelope || !envelope._iv) continue;
828
+ const plaintext = await decrypt(envelope._iv, envelope._data, oldDek);
829
+ const { iv, data } = await encrypt(plaintext, newDek);
830
+ const newEnvelope = {
831
+ _noydb: NOYDB_FORMAT_VERSION,
832
+ _v: envelope._v,
833
+ _ts: (/* @__PURE__ */ new Date()).toISOString(),
834
+ _iv: iv,
835
+ _data: data
836
+ };
837
+ await adapter.put(vault, collName, id, newEnvelope);
838
+ }
839
+ }
840
+ for (const [collName, newDek] of newDeks) {
841
+ callerKeyring.deks.set(collName, newDek);
842
+ }
843
+ await persistKeyring(adapter, vault, callerKeyring);
844
+ const userIds = await adapter.list(vault, "_keyring");
845
+ for (const userId of userIds) {
846
+ if (userId === callerKeyring.userId) continue;
847
+ const userEnvelope = await adapter.get(vault, "_keyring", userId);
848
+ if (!userEnvelope) continue;
849
+ const userKeyringFile = JSON.parse(userEnvelope._data);
850
+ const updatedDeks = { ...userKeyringFile.deks };
851
+ for (const collName of collections) {
852
+ delete updatedDeks[collName];
853
+ }
854
+ const updatedPermissions = { ...userKeyringFile.permissions };
855
+ for (const collName of collections) {
856
+ delete updatedPermissions[collName];
857
+ }
858
+ const updatedKeyring = {
859
+ ...userKeyringFile,
860
+ deks: updatedDeks,
861
+ permissions: updatedPermissions
862
+ };
863
+ await writeKeyringFile(adapter, vault, userId, updatedKeyring);
864
+ }
865
+ }
866
+ async function changeSecret(adapter, vault, keyring, newPassphrase, passphraseOpts) {
867
+ if (!passphraseOpts?.allowWeakPassphrase) {
868
+ assertStrongPassphrase(newPassphrase, passphraseOpts);
869
+ }
870
+ const newSalt = generateSalt();
871
+ const newKek = await deriveKey(newPassphrase, newSalt);
872
+ const wrappedDeks = {};
873
+ for (const [collName, dek] of keyring.deks) {
874
+ wrappedDeks[collName] = await wrapKey(dek, newKek);
875
+ }
876
+ const canary = await mintKeyringCanary(newKek);
877
+ const keyringFile = {
878
+ _noydb_keyring: NOYDB_KEYRING_VERSION,
879
+ user_id: keyring.userId,
880
+ display_name: keyring.displayName,
881
+ role: keyring.role,
882
+ permissions: keyring.permissions,
883
+ deks: wrappedDeks,
884
+ salt: bufferToBase64(newSalt),
885
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
886
+ granted_by: keyring.userId,
887
+ canary
888
+ };
889
+ await writeKeyringFile(adapter, vault, keyring.userId, keyringFile);
890
+ return {
891
+ userId: keyring.userId,
892
+ displayName: keyring.displayName,
893
+ role: keyring.role,
894
+ permissions: keyring.permissions,
895
+ deks: keyring.deks,
896
+ // Same DEKs, different wrapping
897
+ kek: newKek,
898
+ salt: newSalt,
899
+ // Tier-2 slots are NOT preserved through `changeSecret` —
900
+ // each slot wraps the OLD KEK, so the new keyring has no
901
+ // authenticator slots until the user re-enrolls. The higher-level
902
+ // `db.rotatePassphrase()` (#10) preserves slots by rewrapping the
903
+ // KEK reference, not the KEK itself.
904
+ authenticators: [],
905
+ ...keyring.policy !== void 0 && { policy: keyring.policy }
906
+ };
907
+ }
908
+ async function buildRecipientKeyringFile(callerKeyring, recipient) {
909
+ if (!callerKeyring.kek) {
910
+ throw new ValidationError(
911
+ "buildRecipientKeyringFile: caller keyring has no KEK \u2014 tier-2 wrap-DEKs and tier-3 PIN-resume sessions cannot create bundle recipients. Re-authenticate at tier 1 (passphrase) before building a bundle."
912
+ );
913
+ }
914
+ const role = recipient.role ?? "viewer";
915
+ const permissions = resolvePermissions(role, recipient.permissions);
916
+ const newSalt = generateSalt();
917
+ const newKek = await deriveKey(recipient.passphrase, newSalt);
918
+ const wrappedDeks = {};
919
+ for (const collName of Object.keys(permissions)) {
920
+ const dek = callerKeyring.deks.get(collName);
921
+ if (dek) {
922
+ wrappedDeks[collName] = await wrapKey(dek, newKek);
923
+ }
924
+ }
925
+ if (role === "owner" || role === "admin" || role === "viewer") {
926
+ for (const [collName, dek] of callerKeyring.deks) {
927
+ if (!(collName in wrappedDeks)) {
928
+ wrappedDeks[collName] = await wrapKey(dek, newKek);
929
+ }
930
+ }
931
+ }
932
+ for (const [collName, dek] of callerKeyring.deks) {
933
+ if (collName.startsWith("_") && !(collName in wrappedDeks)) {
934
+ wrappedDeks[collName] = await wrapKey(dek, newKek);
935
+ }
936
+ }
937
+ for (const collName of Object.keys(wrappedDeks)) {
938
+ if (!callerKeyring.deks.has(collName)) {
939
+ throw new PrivilegeEscalationError(collName);
940
+ }
941
+ }
942
+ const canary = await mintKeyringCanary(newKek);
943
+ return {
944
+ _noydb_keyring: NOYDB_KEYRING_VERSION,
945
+ user_id: recipient.id,
946
+ display_name: recipient.displayName ?? recipient.id,
947
+ role,
948
+ permissions,
949
+ deks: wrappedDeks,
950
+ salt: bufferToBase64(newSalt),
951
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
952
+ granted_by: callerKeyring.userId,
953
+ canary,
954
+ ...recipient.exportCapability !== void 0 ? { export_capability: recipient.exportCapability } : {},
955
+ ...recipient.importCapability !== void 0 ? { import_capability: recipient.importCapability } : {},
956
+ ...recipient.expiresAt !== void 0 ? { expires_at: recipient.expiresAt } : {}
957
+ };
958
+ }
959
+ async function listUsers(adapter, vault) {
960
+ const userIds = await adapter.list(vault, "_keyring");
961
+ const users = [];
962
+ for (const userId of userIds) {
963
+ const envelope = await adapter.get(vault, "_keyring", userId);
964
+ if (!envelope) continue;
965
+ const kf = JSON.parse(envelope._data);
966
+ users.push({
967
+ userId: kf.user_id,
968
+ displayName: kf.display_name,
969
+ role: kf.role,
970
+ permissions: kf.permissions,
971
+ createdAt: kf.created_at,
972
+ grantedBy: kf.granted_by
973
+ });
974
+ }
975
+ return users;
976
+ }
977
+ async function listUsersWithEnvelopes(adapter, vault, userEnvelopeDek, callerRole, options = {}) {
978
+ const isPrivileged = callerRole === "owner" || callerRole === "admin";
979
+ const dirConfig = await readDirectoryConfig(adapter, vault);
980
+ if (dirConfig?.enabled === false && !isPrivileged) {
981
+ throw new DirectoryDisabledError(vault);
982
+ }
983
+ if (options.includeHidden && !isPrivileged) {
984
+ throw new PermissionDeniedError(
985
+ "Permission denied \u2014 listUsersWithEnvelopes({ includeHidden: true }) requires owner or admin role"
986
+ );
987
+ }
988
+ const users = await listUsers(adapter, vault);
989
+ const out = [];
990
+ for (const user of users) {
991
+ if (!options.includeHidden) {
992
+ const visibility = await readUserVisibility(adapter, vault, user.userId);
993
+ if (visibility?.hidden) continue;
994
+ }
995
+ const envelope = await loadUserEnvelope(
996
+ adapter,
997
+ vault,
998
+ user.userId,
999
+ userEnvelopeDek
1000
+ );
1001
+ out.push({ user, envelope });
1002
+ }
1003
+ return out;
1004
+ }
181
1005
  async function ensureCollectionDEK(adapter, vault, keyring) {
182
1006
  const inFlight = /* @__PURE__ */ new Map();
183
1007
  return async (collectionName) => {
@@ -209,6 +1033,7 @@ async function persistKeyring(adapter, vault, keyring) {
209
1033
  for (const [collName, dek] of keyring.deks) {
210
1034
  wrappedDeks[collName] = await wrapKey(dek, keyring.kek);
211
1035
  }
1036
+ const canary = await mintKeyringCanary(keyring.kek);
212
1037
  const keyringFile = {
213
1038
  _noydb_keyring: NOYDB_KEYRING_VERSION,
214
1039
  user_id: keyring.userId,
@@ -219,6 +1044,7 @@ async function persistKeyring(adapter, vault, keyring) {
219
1044
  salt: bufferToBase64(keyring.salt),
220
1045
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
221
1046
  granted_by: keyring.userId,
1047
+ canary,
222
1048
  ...keyring.exportCapability !== void 0 && { export_capability: keyring.exportCapability },
223
1049
  ...keyring.importCapability !== void 0 && { import_capability: keyring.importCapability },
224
1050
  ...keyring.authenticators.length > 0 && { authenticators: keyring.authenticators },
@@ -259,6 +1085,10 @@ function evaluateImportCapability(capability, _role, tier, format) {
259
1085
  }
260
1086
  return capability?.bundle === true;
261
1087
  }
1088
+ function resolvePermissions(role, explicit) {
1089
+ if (role === "owner" || role === "admin" || role === "viewer") return {};
1090
+ return explicit ?? {};
1091
+ }
262
1092
  async function writeKeyringFile(adapter, vault, userId, keyringFile) {
263
1093
  const envelope = {
264
1094
  _noydb: 1,
@@ -270,6 +1100,695 @@ async function writeKeyringFile(adapter, vault, userId, keyringFile) {
270
1100
  await adapter.put(vault, "_keyring", userId, envelope);
271
1101
  }
272
1102
 
1103
+ // src/team/authenticators.ts
1104
+ async function enrollAuthenticator(store, vault, keyring, options) {
1105
+ const existing = keyring.authenticators.find((a) => a.id === options.id);
1106
+ if (existing) {
1107
+ throw new ValidationError(
1108
+ `enrollAuthenticator: slot id "${options.id}" already exists in vault "${vault}". Remove the slot first or pick a unique id.`
1109
+ );
1110
+ }
1111
+ const base = {
1112
+ id: options.id,
1113
+ method: options.method,
1114
+ enrolled_at: (/* @__PURE__ */ new Date()).toISOString(),
1115
+ enrolled_via_tier: options.enrolled_via_tier ?? 1,
1116
+ meta: options.meta
1117
+ };
1118
+ const slot = options.wrapKind === "deks" ? {
1119
+ ...base,
1120
+ wrapKind: "deks",
1121
+ wrapped_deks: options.wrapped_deks,
1122
+ iv: options.iv
1123
+ } : {
1124
+ ...base,
1125
+ wrapped_kek: options.wrapped_kek
1126
+ };
1127
+ const next = appendSlot(keyring, slot);
1128
+ await persistKeyring(store, vault, next);
1129
+ return next;
1130
+ }
1131
+ async function updateAuthenticator(store, vault, keyring, slotId, options) {
1132
+ if (options.meta === void 0) {
1133
+ throw new ValidationError(
1134
+ `updateAuthenticator: at least one of meta must be provided (slotId: "${slotId}").`
1135
+ );
1136
+ }
1137
+ const idx = keyring.authenticators.findIndex((a) => a.id === slotId);
1138
+ if (idx === -1) {
1139
+ throw new NoAccessError(
1140
+ `updateAuthenticator: slot "${slotId}" not found in vault "${vault}".`
1141
+ );
1142
+ }
1143
+ const existing = keyring.authenticators[idx];
1144
+ const mergedMeta = { ...existing.meta };
1145
+ for (const [k, v] of Object.entries(options.meta)) {
1146
+ if (v === void 0) continue;
1147
+ if (v === null) {
1148
+ delete mergedMeta[k];
1149
+ continue;
1150
+ }
1151
+ mergedMeta[k] = v;
1152
+ }
1153
+ const next = { ...existing, meta: mergedMeta };
1154
+ const nextSlots = [...keyring.authenticators];
1155
+ nextSlots[idx] = next;
1156
+ const nextKeyring = {
1157
+ ...keyring,
1158
+ authenticators: nextSlots
1159
+ };
1160
+ await persistKeyring(store, vault, nextKeyring);
1161
+ return nextKeyring;
1162
+ }
1163
+ async function removeAuthenticator(store, vault, keyring, slotId) {
1164
+ const filtered = keyring.authenticators.filter((a) => a.id !== slotId);
1165
+ if (filtered.length === keyring.authenticators.length) {
1166
+ return keyring;
1167
+ }
1168
+ const next = {
1169
+ ...keyring,
1170
+ authenticators: filtered
1171
+ };
1172
+ await persistKeyring(store, vault, next);
1173
+ return next;
1174
+ }
1175
+ function findAuthenticator(keyring, slotId) {
1176
+ return keyring.authenticators.find((a) => a.id === slotId);
1177
+ }
1178
+ function appendSlot(keyring, slot) {
1179
+ return {
1180
+ ...keyring,
1181
+ authenticators: [...keyring.authenticators, slot]
1182
+ };
1183
+ }
1184
+
1185
+ // src/policy/errors.ts
1186
+ var RecoveryProfileNotImplementedError = class extends NoydbError {
1187
+ profile;
1188
+ tracking;
1189
+ constructor(profile, tracking) {
1190
+ super(
1191
+ "RECOVERY_PROFILE_NOT_IMPLEMENTED",
1192
+ `Recovery profile "${profile}" is not yet implemented in this hub release. Tracking: ${tracking}. Use the "paper" profile via @noy-db/on-recovery in the meantime.`
1193
+ );
1194
+ this.name = "RecoveryProfileNotImplementedError";
1195
+ this.profile = profile;
1196
+ this.tracking = tracking;
1197
+ }
1198
+ };
1199
+
1200
+ // src/team/wrapped-deks.ts
1201
+ var PBKDF2_ITERATIONS2 = 6e5;
1202
+ var SALT_BYTES2 = 32;
1203
+ var IV_BYTES2 = 12;
1204
+ var subtle2 = globalThis.crypto.subtle;
1205
+ async function mintWrappedDeksBlob(deks, credential) {
1206
+ const salt = crypto.getRandomValues(new Uint8Array(SALT_BYTES2));
1207
+ const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES2));
1208
+ const wrappingKey = await deriveWrappingKey(credential, salt);
1209
+ const exported = {};
1210
+ for (const [coll, dek] of deks) {
1211
+ const raw = await subtle2.exportKey("raw", dek);
1212
+ exported[coll] = bytesToBase64(new Uint8Array(raw));
1213
+ }
1214
+ const plaintext = new TextEncoder().encode(JSON.stringify({ deks: exported }));
1215
+ const ciphertext = await subtle2.encrypt(
1216
+ { name: "AES-GCM", iv },
1217
+ wrappingKey,
1218
+ plaintext
1219
+ );
1220
+ return {
1221
+ salt: bytesToBase64(salt),
1222
+ iv: bytesToBase64(iv),
1223
+ wrappedDeks: bytesToBase64(new Uint8Array(ciphertext))
1224
+ };
1225
+ }
1226
+ async function unwrapDeksFromBlob(blob, credential) {
1227
+ const wrappingKey = await deriveWrappingKey(credential, base64ToBytes(blob.salt));
1228
+ const plaintext = await subtle2.decrypt(
1229
+ { name: "AES-GCM", iv: base64ToBytes(blob.iv) },
1230
+ wrappingKey,
1231
+ base64ToBytes(blob.wrappedDeks)
1232
+ );
1233
+ const parsed = JSON.parse(new TextDecoder().decode(plaintext));
1234
+ const deks = /* @__PURE__ */ new Map();
1235
+ for (const [coll, b64] of Object.entries(parsed.deks)) {
1236
+ const raw = base64ToBytes(b64);
1237
+ const key = await subtle2.importKey(
1238
+ "raw",
1239
+ raw,
1240
+ { name: "AES-GCM", length: 256 },
1241
+ true,
1242
+ ["encrypt", "decrypt"]
1243
+ );
1244
+ deks.set(coll, key);
1245
+ }
1246
+ return deks;
1247
+ }
1248
+ async function deriveWrappingKey(credential, salt) {
1249
+ const ikm = await subtle2.importKey(
1250
+ "raw",
1251
+ new TextEncoder().encode(credential),
1252
+ "PBKDF2",
1253
+ false,
1254
+ ["deriveKey"]
1255
+ );
1256
+ return subtle2.deriveKey(
1257
+ {
1258
+ name: "PBKDF2",
1259
+ salt,
1260
+ iterations: PBKDF2_ITERATIONS2,
1261
+ hash: "SHA-256"
1262
+ },
1263
+ ikm,
1264
+ { name: "AES-GCM", length: 256 },
1265
+ false,
1266
+ ["encrypt", "decrypt"]
1267
+ );
1268
+ }
1269
+ function bytesToBase64(b) {
1270
+ let s = "";
1271
+ for (const x of b) s += String.fromCharCode(x);
1272
+ return btoa(s);
1273
+ }
1274
+ function base64ToBytes(b64) {
1275
+ const s = atob(b64);
1276
+ const out = new Uint8Array(s.length);
1277
+ for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i);
1278
+ return out;
1279
+ }
1280
+
1281
+ // src/team/recovery.ts
1282
+ var PAPER_DOC_ID = "recovery-paper";
1283
+ async function loadPaperRecoveryEntries(store, vault) {
1284
+ const env = await store.get(vault, "_meta", PAPER_DOC_ID);
1285
+ if (!env) return [];
1286
+ try {
1287
+ const doc = JSON.parse(env._data);
1288
+ if (doc.profile !== "paper" || !Array.isArray(doc.entries)) return [];
1289
+ return doc.entries;
1290
+ } catch {
1291
+ return [];
1292
+ }
1293
+ }
1294
+ async function savePaperRecoveryEntries(store, vault, entries) {
1295
+ const doc = {
1296
+ _noydb_recovery: 1,
1297
+ profile: "paper",
1298
+ entries
1299
+ };
1300
+ const envelope = {
1301
+ _noydb: NOYDB_FORMAT_VERSION,
1302
+ _v: 1,
1303
+ _ts: (/* @__PURE__ */ new Date()).toISOString(),
1304
+ _iv: "",
1305
+ _data: JSON.stringify(doc)
1306
+ };
1307
+ await store.put(vault, "_meta", PAPER_DOC_ID, envelope);
1308
+ }
1309
+ async function burnPaperRecoveryEntry(store, vault, codeId) {
1310
+ const entries = await loadPaperRecoveryEntries(store, vault);
1311
+ const remaining = entries.filter((e) => e.codeId !== codeId);
1312
+ await savePaperRecoveryEntries(store, vault, remaining);
1313
+ }
1314
+ var SHAMIR_DOC_ID = "recovery-shamir";
1315
+ async function loadShamirRecoveryEntries(store, vault) {
1316
+ const env = await store.get(vault, "_meta", SHAMIR_DOC_ID);
1317
+ if (!env) return [];
1318
+ try {
1319
+ const doc = JSON.parse(env._data);
1320
+ if (doc.profile !== "shamir" || !Array.isArray(doc.entries)) return [];
1321
+ return doc.entries;
1322
+ } catch {
1323
+ return [];
1324
+ }
1325
+ }
1326
+ async function unwrapDeksFromShamirEntry(provider, entry, shareStrings) {
1327
+ if (shareStrings.length < entry.k) {
1328
+ throw new Error(
1329
+ `Insufficient shares: this Shamir entry needs ${entry.k} of ${entry.n}, but ${shareStrings.length} were provided.`
1330
+ );
1331
+ }
1332
+ const secret = provider.combineShares(shareStrings);
1333
+ try {
1334
+ return await unwrapDeksFromBlob(entry, bytesToBase642(secret));
1335
+ } finally {
1336
+ secret.fill(0);
1337
+ }
1338
+ }
1339
+ function bytesToBase642(b) {
1340
+ let s = "";
1341
+ for (const x of b) s += String.fromCharCode(x);
1342
+ return btoa(s);
1343
+ }
1344
+ async function mintPaperRecoveryEntry(deks, code, codeId) {
1345
+ const blob = await mintWrappedDeksBlob(deks, code);
1346
+ return {
1347
+ ...blob,
1348
+ codeId,
1349
+ enrolledAt: (/* @__PURE__ */ new Date()).toISOString()
1350
+ };
1351
+ }
1352
+ async function unwrapDeksFromPaperEntry(entry, code) {
1353
+ return unwrapDeksFromBlob(entry, code);
1354
+ }
1355
+
1356
+ // src/team/rotate-recover.ts
1357
+ async function rotatePassphrase(store, vault, userId, input) {
1358
+ if (!input.allowWeakPassphrase) {
1359
+ assertStrongPassphrase(input.newPassphrase, input.passphrasePolicy);
1360
+ }
1361
+ const env = await store.get(vault, "_keyring", userId);
1362
+ if (!env) {
1363
+ throw new NoAccessError(`No keyring found for user "${userId}" in vault "${vault}".`);
1364
+ }
1365
+ const file = JSON.parse(env._data);
1366
+ const oldSalt = base64ToBuffer(file.salt);
1367
+ const oldKek = await deriveKey(input.oldPassphrase, oldSalt);
1368
+ const deks = /* @__PURE__ */ new Map();
1369
+ for (const [coll, wrapped] of Object.entries(file.deks)) {
1370
+ deks.set(coll, await unwrapKey(wrapped, oldKek));
1371
+ }
1372
+ const newSalt = generateSalt();
1373
+ const newKek = await deriveKey(input.newPassphrase, newSalt);
1374
+ const wrappedDeks = {};
1375
+ for (const [coll, dek] of deks) {
1376
+ wrappedDeks[coll] = await wrapKey(dek, newKek);
1377
+ }
1378
+ const oldSlots = file.authenticators ?? [];
1379
+ const newSlots = [];
1380
+ if (input.slotCeremonies && oldSlots.length > 0) {
1381
+ for (const oldSlot of oldSlots) {
1382
+ const ceremony = input.slotCeremonies[oldSlot.id];
1383
+ if (!ceremony) continue;
1384
+ const result = await ceremony({ newKek, newDeks: deks, oldSlot });
1385
+ if (result.id !== oldSlot.id) {
1386
+ throw new ValidationError(
1387
+ `slotCeremonies['${oldSlot.id}'] returned id="${result.id}". The id must match the rotated slot \u2014 a ceremony cannot change a slot's identity.`
1388
+ );
1389
+ }
1390
+ if (result.method !== oldSlot.method) {
1391
+ throw new ValidationError(
1392
+ `slotCeremonies['${oldSlot.id}'] returned method="${result.method}", expected "${oldSlot.method}". The method must match the rotated slot \u2014 a ceremony cannot change the auth method (e.g. webauthn \u2192 password) under cover of rotation.`
1393
+ );
1394
+ }
1395
+ const oldWrapKind = oldSlot.wrapKind ?? "kek";
1396
+ const newWrapKind = result.wrapKind ?? "kek";
1397
+ if (oldWrapKind !== newWrapKind) {
1398
+ throw new ValidationError(
1399
+ `slotCeremonies['${oldSlot.id}'] returned wrapKind="${newWrapKind}", expected "${oldWrapKind}". The wrap format must match the rotated slot \u2014 a ceremony cannot change the wrap shape (e.g. wrap-KEK \u2192 wrap-DEKs) under cover of rotation, since that would silently change the session tier produced at unlock.`
1400
+ );
1401
+ }
1402
+ const baseFields = {
1403
+ id: result.id,
1404
+ method: result.method,
1405
+ // Preserve original enrolled_at — rotation is rewrapping, not
1406
+ // re-enrollment. The slot's enrolment timestamp tracks when
1407
+ // the user originally added the slot, not when it was last
1408
+ // rewrapped. Forensics consumers reading enrolled_at are
1409
+ // tracking the slot's ORIGIN, not its CURRENT wrapping.
1410
+ enrolled_at: oldSlot.enrolled_at,
1411
+ enrolled_via_tier: result.enrolled_via_tier ?? oldSlot.enrolled_via_tier,
1412
+ meta: result.meta
1413
+ };
1414
+ const newSlot = result.wrapKind === "deks" ? {
1415
+ ...baseFields,
1416
+ wrapKind: "deks",
1417
+ wrapped_deks: result.wrapped_deks,
1418
+ iv: result.iv
1419
+ } : {
1420
+ ...baseFields,
1421
+ wrapped_kek: result.wrapped_kek
1422
+ };
1423
+ newSlots.push(newSlot);
1424
+ }
1425
+ }
1426
+ const canary = await mintKeyringCanary(newKek);
1427
+ const next = {
1428
+ ...file,
1429
+ _noydb_keyring: NOYDB_KEYRING_VERSION,
1430
+ deks: wrappedDeks,
1431
+ salt: bufferToBase64(newSalt),
1432
+ authenticators: newSlots,
1433
+ canary
1434
+ };
1435
+ await writeKeyringFile2(store, vault, userId, next);
1436
+ return {
1437
+ userId: file.user_id,
1438
+ displayName: file.display_name,
1439
+ role: file.role,
1440
+ permissions: file.permissions,
1441
+ deks,
1442
+ kek: newKek,
1443
+ salt: newSalt,
1444
+ authenticators: newSlots,
1445
+ ...file.export_capability !== void 0 && { exportCapability: file.export_capability },
1446
+ ...file.import_capability !== void 0 && { importCapability: file.import_capability }
1447
+ };
1448
+ }
1449
+ async function recoverPassphrase(provider, store, vault, userId, input) {
1450
+ if (!input.allowWeakPassphrase) {
1451
+ assertStrongPassphrase(input.newPassphrase, input.passphrasePolicy);
1452
+ }
1453
+ const profile = input.recoveryProof.profile;
1454
+ if (profile === "paper") {
1455
+ return recoverViaPaperCode(store, vault, userId, input);
1456
+ }
1457
+ if (profile === "shamir") {
1458
+ return recoverViaShamir(provider, store, vault, userId, input);
1459
+ }
1460
+ throw new RecoveryProfileNotImplementedError(
1461
+ profile,
1462
+ "https://github.com/vLannaAi/noy-db/issues/196"
1463
+ );
1464
+ }
1465
+ async function recoverViaPaperCode(store, vault, userId, input) {
1466
+ if (input.recoveryProof.profile !== "paper") throw new Error("unreachable");
1467
+ const { code } = input.recoveryProof.payload;
1468
+ const env = await store.get(vault, "_keyring", userId);
1469
+ if (!env) {
1470
+ throw new NoAccessError(`No keyring found for user "${userId}" in vault "${vault}".`);
1471
+ }
1472
+ const file = JSON.parse(env._data);
1473
+ const entries = await loadPaperRecoveryEntries(store, vault);
1474
+ if (entries.length === 0) {
1475
+ throw new NoAccessError(
1476
+ `No paper-recovery entries enrolled for vault "${vault}". Enroll via \`db.enrollRecovery({ profile: "paper", entries })\` before relying on recovery.`
1477
+ );
1478
+ }
1479
+ const normalized = normalizePaperCode(code);
1480
+ let recovered;
1481
+ for (const entry of entries) {
1482
+ try {
1483
+ const deks2 = await unwrapDeksFromPaperEntry(entry, normalized);
1484
+ recovered = { deks: deks2, entry };
1485
+ break;
1486
+ } catch {
1487
+ }
1488
+ }
1489
+ if (!recovered) {
1490
+ throw new InvalidKeyError(
1491
+ "Recovery code does not match any enrolled paper entry. The code may have been previously used (single-use) or typed incorrectly."
1492
+ );
1493
+ }
1494
+ const deks = recovered.deks;
1495
+ const newSalt = generateSalt();
1496
+ const newKek = await deriveKey(input.newPassphrase, newSalt);
1497
+ const wrappedDeks = {};
1498
+ for (const [coll, dek] of deks) {
1499
+ wrappedDeks[coll] = await wrapKey(dek, newKek);
1500
+ }
1501
+ const canary = await mintKeyringCanary(newKek);
1502
+ const next = {
1503
+ ...file,
1504
+ _noydb_keyring: NOYDB_KEYRING_VERSION,
1505
+ deks: wrappedDeks,
1506
+ salt: bufferToBase64(newSalt),
1507
+ authenticators: [],
1508
+ // tier-2 slots wrap old KEK, drop them
1509
+ canary
1510
+ };
1511
+ await burnPaperRecoveryEntry(store, vault, recovered.entry.codeId);
1512
+ await writeKeyringFile2(store, vault, userId, next);
1513
+ return {
1514
+ userId: file.user_id,
1515
+ displayName: file.display_name,
1516
+ role: file.role,
1517
+ permissions: file.permissions,
1518
+ deks,
1519
+ kek: newKek,
1520
+ salt: newSalt,
1521
+ authenticators: [],
1522
+ ...file.export_capability !== void 0 && { exportCapability: file.export_capability },
1523
+ ...file.import_capability !== void 0 && { importCapability: file.import_capability }
1524
+ };
1525
+ }
1526
+ function normalizePaperCode(input) {
1527
+ return input.toUpperCase().replace(/[\s\-_]/g, "");
1528
+ }
1529
+ async function recoverViaShamir(provider, store, vault, userId, input) {
1530
+ if (input.recoveryProof.profile !== "shamir") throw new Error("unreachable");
1531
+ const { entryId: requestedEntryId, shares: shareStrings } = input.recoveryProof.payload;
1532
+ if (shareStrings.length === 0) {
1533
+ throw new ValidationError(
1534
+ "Shamir recovery requires at least one share; received an empty array."
1535
+ );
1536
+ }
1537
+ const env = await store.get(vault, "_keyring", userId);
1538
+ if (!env) {
1539
+ throw new NoAccessError(`No keyring found for user "${userId}" in vault "${vault}".`);
1540
+ }
1541
+ const file = JSON.parse(env._data);
1542
+ const allEntries = await loadShamirRecoveryEntries(store, vault);
1543
+ if (allEntries.length === 0) {
1544
+ throw new NoAccessError(
1545
+ `No Shamir-recovery entries enrolled for vault "${vault}". Enroll via \`db.enrollRecovery({ profile: "shamir", k, n })\` before relying on recovery.`
1546
+ );
1547
+ }
1548
+ if (!provider) {
1549
+ throw new Error(
1550
+ "shamir recovery requires a ShamirRecoveryProvider \u2014 pass shamirRecovery: shamirRecoveryProvider() from '@noy-db/on-shamir' to createNoydb()"
1551
+ );
1552
+ }
1553
+ let candidates;
1554
+ if (requestedEntryId !== void 0) {
1555
+ candidates = allEntries.filter((e) => e.entryId === requestedEntryId);
1556
+ if (candidates.length === 0) {
1557
+ throw new NoAccessError(
1558
+ `No Shamir-recovery entry with entryId="${requestedEntryId}" found in vault "${vault}". Available entries: ` + allEntries.map((e) => `"${e.entryId}"`).join(", ")
1559
+ );
1560
+ }
1561
+ } else {
1562
+ candidates = allEntries;
1563
+ }
1564
+ let recoveredDeks;
1565
+ for (const entry of candidates) {
1566
+ if (shareStrings.length < entry.k) {
1567
+ continue;
1568
+ }
1569
+ try {
1570
+ const deks = await unwrapDeksFromShamirEntry(provider, entry, shareStrings);
1571
+ recoveredDeks = deks;
1572
+ break;
1573
+ } catch {
1574
+ }
1575
+ }
1576
+ if (!recoveredDeks) {
1577
+ const minK = Math.min(...candidates.map((e) => e.k));
1578
+ if (shareStrings.length < minK) {
1579
+ throw new InvalidKeyError(
1580
+ `Insufficient Shamir shares to combine: the smallest enrolled threshold is ${minK}, but only ${shareStrings.length} share${shareStrings.length === 1 ? " was" : "s were"} provided.`
1581
+ );
1582
+ }
1583
+ throw new InvalidKeyError(
1584
+ "Shamir shares do not match any enrolled entry. Possible causes: shares were tampered with, came from a different enrollment, or the entry was rotated after these shares were distributed."
1585
+ );
1586
+ }
1587
+ const newSalt = generateSalt();
1588
+ const newKek = await deriveKey(input.newPassphrase, newSalt);
1589
+ const wrappedDeks = {};
1590
+ for (const [coll, dek] of recoveredDeks) {
1591
+ wrappedDeks[coll] = await wrapKey(dek, newKek);
1592
+ }
1593
+ const canary = await mintKeyringCanary(newKek);
1594
+ const next = {
1595
+ ...file,
1596
+ _noydb_keyring: NOYDB_KEYRING_VERSION,
1597
+ deks: wrappedDeks,
1598
+ salt: bufferToBase64(newSalt),
1599
+ authenticators: [],
1600
+ // tier-2 slots wrap old KEK, drop them on recovery
1601
+ canary
1602
+ };
1603
+ await writeKeyringFile2(store, vault, userId, next);
1604
+ return {
1605
+ userId: file.user_id,
1606
+ displayName: file.display_name,
1607
+ role: file.role,
1608
+ permissions: file.permissions,
1609
+ deks: recoveredDeks,
1610
+ kek: newKek,
1611
+ salt: newSalt,
1612
+ authenticators: [],
1613
+ ...file.export_capability !== void 0 && { exportCapability: file.export_capability },
1614
+ ...file.import_capability !== void 0 && { importCapability: file.import_capability }
1615
+ };
1616
+ }
1617
+ async function writeKeyringFile2(store, vault, userId, file) {
1618
+ const envelope = {
1619
+ _noydb: 1,
1620
+ _v: 1,
1621
+ _ts: (/* @__PURE__ */ new Date()).toISOString(),
1622
+ _iv: "",
1623
+ _data: JSON.stringify(file)
1624
+ };
1625
+ await store.put(vault, "_keyring", userId, envelope);
1626
+ }
1627
+
1628
+ // src/team/peer-recover.ts
1629
+ var ADMIN_RECOVERABLE_TARGETS = ["operator", "viewer", "client", "admin"];
1630
+ function canRecover(callerRole, targetRole) {
1631
+ if (callerRole === "owner") return true;
1632
+ if (callerRole === "admin") return ADMIN_RECOVERABLE_TARGETS.includes(targetRole);
1633
+ return false;
1634
+ }
1635
+ async function recoverUser(store, vault, callerKeyring, options) {
1636
+ const env = await store.get(vault, "_keyring", options.userId);
1637
+ if (!env) {
1638
+ throw new NoAccessError(
1639
+ `recoverUser: user "${options.userId}" has no keyring in vault "${vault}".`
1640
+ );
1641
+ }
1642
+ const target = JSON.parse(env._data);
1643
+ const targetRole = options.role ?? target.role;
1644
+ if (!canRecover(callerKeyring.role, targetRole)) {
1645
+ throw new PermissionDeniedError(
1646
+ `Role "${callerKeyring.role}" cannot recover role "${targetRole}"`
1647
+ );
1648
+ }
1649
+ if (!canRecover(callerKeyring.role, target.role)) {
1650
+ throw new PermissionDeniedError(
1651
+ `Role "${callerKeyring.role}" cannot recover role "${target.role}"`
1652
+ );
1653
+ }
1654
+ for (const coll of Object.keys(target.deks)) {
1655
+ if (!callerKeyring.deks.has(coll)) {
1656
+ throw new PrivilegeEscalationError(coll);
1657
+ }
1658
+ }
1659
+ if (options.validatePassphrase && !options.allowWeakPassphrase) {
1660
+ assertStrongPassphrase(options.passphrase, options.passphrasePolicy);
1661
+ }
1662
+ const newSalt = generateSalt();
1663
+ const newKek = await deriveKey(options.passphrase, newSalt);
1664
+ const wrappedDeks = {};
1665
+ for (const coll of Object.keys(target.deks)) {
1666
+ const callerDek = callerKeyring.deks.get(coll);
1667
+ if (!callerDek) {
1668
+ throw new PrivilegeEscalationError(coll);
1669
+ }
1670
+ wrappedDeks[coll] = await wrapKey(callerDek, newKek);
1671
+ }
1672
+ const canary = await mintKeyringCanary(newKek);
1673
+ const next = {
1674
+ ...target,
1675
+ _noydb_keyring: NOYDB_KEYRING_VERSION,
1676
+ role: targetRole,
1677
+ display_name: options.displayName ?? target.display_name,
1678
+ deks: wrappedDeks,
1679
+ salt: bufferToBase64(newSalt),
1680
+ granted_by: callerKeyring.userId,
1681
+ authenticators: [],
1682
+ canary
1683
+ };
1684
+ const envelope = {
1685
+ _noydb: 1,
1686
+ _v: 1,
1687
+ _ts: (/* @__PURE__ */ new Date()).toISOString(),
1688
+ _iv: "",
1689
+ _data: JSON.stringify(next)
1690
+ };
1691
+ await store.put(vault, "_keyring", options.userId, envelope);
1692
+ }
1693
+
1694
+ // src/team/tiers.ts
1695
+ function dekKey(collection, tier) {
1696
+ if (tier <= 0) return collection;
1697
+ return `${collection}#${tier}`;
1698
+ }
1699
+
1700
+ // src/team/magic-link-grant.ts
1701
+ var MAGIC_LINK_GRANTS_COLLECTION = "_magic_link_grants";
1702
+ var MAGIC_LINK_CONTENT_INFO_PREFIX = "noydb-magic-link-content-v1:";
1703
+ async function deriveMagicLinkContentKey(serverSecret, token, vault) {
1704
+ const subtle3 = globalThis.crypto.subtle;
1705
+ const ikmBytes = serverSecret instanceof Uint8Array ? serverSecret : new TextEncoder().encode(serverSecret);
1706
+ const tokenBytes = new TextEncoder().encode(token);
1707
+ const saltBuffer = await subtle3.digest("SHA-256", tokenBytes);
1708
+ const info = new TextEncoder().encode(MAGIC_LINK_CONTENT_INFO_PREFIX + vault);
1709
+ const ikm = await subtle3.importKey("raw", ikmBytes, "HKDF", false, ["deriveKey"]);
1710
+ return subtle3.deriveKey(
1711
+ { name: "HKDF", hash: "SHA-256", salt: saltBuffer, info },
1712
+ ikm,
1713
+ { name: "AES-GCM", length: 256 },
1714
+ false,
1715
+ ["encrypt", "decrypt"]
1716
+ );
1717
+ }
1718
+ async function writeMagicLinkGrant(store, vault, grantor, contentKey, grantKek, recordId, opts) {
1719
+ const collectionName = opts.collection ?? null;
1720
+ const sourceKey = collectionName ? dekKey(collectionName, opts.tier) : `__any#${opts.tier}`;
1721
+ const sourceDek = grantor.deks.get(sourceKey);
1722
+ if (!sourceDek) {
1723
+ throw new DelegationTargetMissingError(
1724
+ `grantor cannot find tier ${opts.tier} DEK for ${collectionName ?? "(any)"}`
1725
+ );
1726
+ }
1727
+ const wrappedDek = await wrapKey(sourceDek, grantKek);
1728
+ const until = typeof opts.until === "string" ? opts.until : opts.until.toISOString();
1729
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
1730
+ const payload = {
1731
+ id: recordId,
1732
+ toUser: opts.toUser,
1733
+ fromUser: grantor.userId,
1734
+ tier: opts.tier,
1735
+ collection: collectionName,
1736
+ ...opts.record && { record: opts.record },
1737
+ until,
1738
+ wrappedDek,
1739
+ createdAt,
1740
+ ...opts.note && { note: opts.note }
1741
+ };
1742
+ const { iv, data } = await encrypt(JSON.stringify(payload), contentKey);
1743
+ const envelope = {
1744
+ _noydb: 1,
1745
+ _v: 1,
1746
+ _ts: createdAt,
1747
+ _iv: iv,
1748
+ _data: data,
1749
+ _by: grantor.userId
1750
+ };
1751
+ await store.put(vault, MAGIC_LINK_GRANTS_COLLECTION, recordId, envelope);
1752
+ return { recordId, payload };
1753
+ }
1754
+ async function readMagicLinkGrantRecord(store, vault, contentKey, recordId) {
1755
+ const env = await store.get(vault, MAGIC_LINK_GRANTS_COLLECTION, recordId);
1756
+ if (!env) return null;
1757
+ try {
1758
+ const json = await decrypt(env._iv, env._data, contentKey);
1759
+ return JSON.parse(json);
1760
+ } catch {
1761
+ return null;
1762
+ }
1763
+ }
1764
+ async function listMagicLinkGrants(store, vault, contentKey, token) {
1765
+ const ids = await store.list(vault, MAGIC_LINK_GRANTS_COLLECTION);
1766
+ const matching = ids.filter((id) => id === token || id.startsWith(`${token}:`));
1767
+ const out = [];
1768
+ for (const id of matching) {
1769
+ const payload = await readMagicLinkGrantRecord(store, vault, contentKey, id);
1770
+ if (payload) out.push(payload);
1771
+ }
1772
+ return out;
1773
+ }
1774
+ async function unwrapMagicLinkGrant(payload, grantKek) {
1775
+ return unwrapKey(payload.wrappedDek, grantKek);
1776
+ }
1777
+ async function revokeMagicLinkGrant(store, vault, token) {
1778
+ const ids = await store.list(vault, MAGIC_LINK_GRANTS_COLLECTION);
1779
+ const matching = ids.filter((id) => id === token || id.startsWith(`${token}:`));
1780
+ for (const id of matching) {
1781
+ await store.delete(vault, MAGIC_LINK_GRANTS_COLLECTION, id);
1782
+ }
1783
+ return matching.length;
1784
+ }
1785
+ function magicLinkGrantRecordId(token, index) {
1786
+ return index === 0 ? token : `${token}:${index}`;
1787
+ }
1788
+ function isMagicLinkGrantExpired(payload, now = /* @__PURE__ */ new Date()) {
1789
+ return payload.until <= now.toISOString();
1790
+ }
1791
+
273
1792
  // src/store/sync-policy.ts
274
1793
  var SyncScheduler = class {
275
1794
  policy;
@@ -1233,14 +2752,47 @@ async function credentialStatus(adapter, vault, keyring, adapterId) {
1233
2752
  SYNC_CREDENTIALS_COLLECTION,
1234
2753
  SyncEngine,
1235
2754
  SyncTransaction,
2755
+ buildRecipientKeyringFile,
2756
+ burnPaperRecoveryEntry,
2757
+ changeSecret,
2758
+ createOwnerKeyring,
1236
2759
  credentialStatus,
1237
2760
  deleteCredential,
2761
+ deriveMagicLinkContentKey,
2762
+ enrollAuthenticator,
2763
+ ensureCollectionDEK,
1238
2764
  evaluateExportCapability,
1239
2765
  evaluateImportCapability,
2766
+ findAuthenticator,
1240
2767
  getCredential,
2768
+ grant,
1241
2769
  hasExportCapability,
1242
2770
  hasImportCapability,
2771
+ isMagicLinkGrantExpired,
1243
2772
  listCredentials,
1244
- putCredential
2773
+ listMagicLinkGrants,
2774
+ listUsers,
2775
+ listUsersWithEnvelopes,
2776
+ loadKeyring,
2777
+ loadPaperRecoveryEntries,
2778
+ magicLinkGrantRecordId,
2779
+ mintPaperRecoveryEntry,
2780
+ mintWrappedDeksBlob,
2781
+ persistKeyring,
2782
+ putCredential,
2783
+ readMagicLinkGrantRecord,
2784
+ recoverPassphrase,
2785
+ recoverUser,
2786
+ removeAuthenticator,
2787
+ revoke,
2788
+ revokeMagicLinkGrant,
2789
+ rotatePassphrase,
2790
+ savePaperRecoveryEntries,
2791
+ unwrapDeksFromBlob,
2792
+ unwrapDeksFromPaperEntry,
2793
+ unwrapMagicLinkGrant,
2794
+ updateAuthenticator,
2795
+ updateKeyringIdentity,
2796
+ writeMagicLinkGrant
1245
2797
  });
1246
2798
  //# sourceMappingURL=index.cjs.map