@noy-db/hub 0.2.0-pre.9 → 0.3.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 (541) hide show
  1. package/README.md +129 -3
  2. package/dist/adapter/index.d.ts +5 -0
  3. package/dist/adapter/index.js +15 -0
  4. package/dist/aggregate/index.d.ts +6 -2
  5. package/dist/aggregate/index.js +24 -16
  6. package/dist/aggregate/index.js.map +1 -1
  7. package/dist/api-RW3KP7WU.js +14 -0
  8. package/dist/as/index.d.ts +7 -0
  9. package/dist/as/index.js +24 -0
  10. package/dist/at/index.d.ts +5 -0
  11. package/dist/at/index.js +1 -0
  12. package/dist/attestation/index.d.ts +15 -47
  13. package/dist/attestation/index.js +54 -10
  14. package/dist/attestation/index.js.map +1 -1
  15. package/dist/backup-KMJQ66MF.js +219 -0
  16. package/dist/backup-KMJQ66MF.js.map +1 -0
  17. package/dist/blobs/index.d.ts +6 -8
  18. package/dist/blobs/index.js +19 -12
  19. package/dist/blobs/index.js.map +1 -1
  20. package/dist/bundle/index.d.ts +16 -70
  21. package/dist/bundle/index.js +39 -476
  22. package/dist/bundle/index.js.map +1 -1
  23. package/dist/{ulid-CJ8RzRrm.d.ts → bundle-PwBGkhpe.d.ts} +31 -86
  24. package/dist/by/index.d.ts +1 -0
  25. package/dist/by/index.js +11 -0
  26. package/dist/cargo/index.d.ts +9 -0
  27. package/dist/cargo/index.js +89 -0
  28. package/dist/chunk-26LGBE4T.js +42 -0
  29. package/dist/chunk-26LGBE4T.js.map +1 -0
  30. package/dist/{chunk-K3DK3NU5.js → chunk-3YFXJ3ZP.js} +20 -5
  31. package/dist/chunk-3YFXJ3ZP.js.map +1 -0
  32. package/dist/chunk-4Q6HSHHL.js +90 -0
  33. package/dist/chunk-4Q6HSHHL.js.map +1 -0
  34. package/dist/chunk-5LUY7UTV.js +380 -0
  35. package/dist/chunk-5LUY7UTV.js.map +1 -0
  36. package/dist/chunk-5N6CZTCY.js +367 -0
  37. package/dist/chunk-5N6CZTCY.js.map +1 -0
  38. package/dist/chunk-5O3AOPDT.js +992 -0
  39. package/dist/chunk-5O3AOPDT.js.map +1 -0
  40. package/dist/chunk-5R3ERCDH.js +239 -0
  41. package/dist/chunk-5R3ERCDH.js.map +1 -0
  42. package/dist/chunk-5TDJ56JE.js +32 -0
  43. package/dist/chunk-5TDJ56JE.js.map +1 -0
  44. package/dist/{chunk-T7JEBOGN.js → chunk-62QYRORB.js} +18 -11
  45. package/dist/chunk-62QYRORB.js.map +1 -0
  46. package/dist/{chunk-J66GRPNH.js → chunk-6S7T6WC4.js} +2 -2
  47. package/dist/chunk-6S7T6WC4.js.map +1 -0
  48. package/dist/chunk-76722EBH.js +451 -0
  49. package/dist/chunk-76722EBH.js.map +1 -0
  50. package/dist/{chunk-Z6FNBOTC.js → chunk-AIWULCUO.js} +13 -17
  51. package/dist/chunk-AIWULCUO.js.map +1 -0
  52. package/dist/{chunk-O3VZPBZG.js → chunk-AQTBSL7R.js} +47 -11
  53. package/dist/chunk-AQTBSL7R.js.map +1 -0
  54. package/dist/chunk-AURFOK3D.js +35 -0
  55. package/dist/chunk-AURFOK3D.js.map +1 -0
  56. package/dist/{chunk-53XBRIRT.js → chunk-AXRXJTE2.js} +9 -9
  57. package/dist/chunk-AXRXJTE2.js.map +1 -0
  58. package/dist/chunk-AZAOEIKW.js +155 -0
  59. package/dist/chunk-AZAOEIKW.js.map +1 -0
  60. package/dist/{chunk-HNRHLRDC.js → chunk-BG3SPSR4.js} +3 -3
  61. package/dist/chunk-BG3SPSR4.js.map +1 -0
  62. package/dist/chunk-BGIZAFY7.js +1 -0
  63. package/dist/chunk-CHFZDSE4.js +1 -0
  64. package/dist/{chunk-DJHHA6XH.js → chunk-CMGR4ZEW.js} +50 -20
  65. package/dist/chunk-CMGR4ZEW.js.map +1 -0
  66. package/dist/chunk-CWPPNPOH.js +23 -0
  67. package/dist/chunk-CWPPNPOH.js.map +1 -0
  68. package/dist/{chunk-QODLLGQ7.js → chunk-DFEMTNPJ.js} +371 -38
  69. package/dist/chunk-DFEMTNPJ.js.map +1 -0
  70. package/dist/{chunk-ZYWZWG6G.js → chunk-DGDI2D4M.js} +10 -10
  71. package/dist/chunk-DGDI2D4M.js.map +1 -0
  72. package/dist/{chunk-SYMWGEET.js → chunk-EIZUR2XW.js} +3 -3
  73. package/dist/chunk-EIZUR2XW.js.map +1 -0
  74. package/dist/{chunk-BVRYKATC.js → chunk-FISN7WE4.js} +36 -33
  75. package/dist/chunk-FISN7WE4.js.map +1 -0
  76. package/dist/chunk-GHVIKOHT.js +1 -0
  77. package/dist/chunk-GYGWYIOZ.js +200 -0
  78. package/dist/chunk-GYGWYIOZ.js.map +1 -0
  79. package/dist/chunk-HCIPRAGS.js +45 -0
  80. package/dist/chunk-HCIPRAGS.js.map +1 -0
  81. package/dist/{chunk-PX35GA7L.js → chunk-I2NOIW44.js} +10 -10
  82. package/dist/chunk-I2NOIW44.js.map +1 -0
  83. package/dist/{chunk-OIF6LZUR.js → chunk-I3TIKTJO.js} +3 -3
  84. package/dist/chunk-I3TIKTJO.js.map +1 -0
  85. package/dist/chunk-I7FD46FF.js +9 -0
  86. package/dist/chunk-I7FD46FF.js.map +1 -0
  87. package/dist/chunk-IAGBDSCS.js +40 -0
  88. package/dist/chunk-IAGBDSCS.js.map +1 -0
  89. package/dist/{chunk-JWRVQKRZ.js → chunk-IELYAOGG.js} +6 -18
  90. package/dist/chunk-IELYAOGG.js.map +1 -0
  91. package/dist/chunk-IGUYVGGI.js +205 -0
  92. package/dist/chunk-IGUYVGGI.js.map +1 -0
  93. package/dist/{chunk-U26HQ6KJ.js → chunk-IO6GRPT6.js} +4 -4
  94. package/dist/chunk-IO6GRPT6.js.map +1 -0
  95. package/dist/chunk-IPB7FTQX.js +273 -0
  96. package/dist/chunk-IPB7FTQX.js.map +1 -0
  97. package/dist/chunk-ITNUCOXM.js +148 -0
  98. package/dist/chunk-ITNUCOXM.js.map +1 -0
  99. package/dist/{chunk-WPLVTJ5D.js → chunk-IUEN57TV.js} +10 -10
  100. package/dist/chunk-IUEN57TV.js.map +1 -0
  101. package/dist/{chunk-DL3HWOQ5.js → chunk-JNUWZ6D3.js} +9 -9
  102. package/dist/chunk-JNUWZ6D3.js.map +1 -0
  103. package/dist/chunk-K2WCO3NX.js +1 -0
  104. package/dist/chunk-KVOXU4T5.js +30 -0
  105. package/dist/chunk-KVOXU4T5.js.map +1 -0
  106. package/dist/chunk-KXGNJJXB.js +135 -0
  107. package/dist/chunk-KXGNJJXB.js.map +1 -0
  108. package/dist/chunk-LB7FD65R.js +163 -0
  109. package/dist/chunk-LB7FD65R.js.map +1 -0
  110. package/dist/{chunk-22DWZL57.js → chunk-LFLXAY2W.js} +43 -218
  111. package/dist/chunk-LFLXAY2W.js.map +1 -0
  112. package/dist/chunk-LMTH2RM6.js +129 -0
  113. package/dist/chunk-LMTH2RM6.js.map +1 -0
  114. package/dist/{aggregate/index.cjs → chunk-LV7M27WM.js} +390 -219
  115. package/dist/chunk-LV7M27WM.js.map +1 -0
  116. package/dist/{chunk-PE2FR4J3.js → chunk-LXQT4WMP.js} +16 -18
  117. package/dist/chunk-LXQT4WMP.js.map +1 -0
  118. package/dist/chunk-M2YKN37S.js +524 -0
  119. package/dist/chunk-M2YKN37S.js.map +1 -0
  120. package/dist/chunk-M6OST55S.js +14 -0
  121. package/dist/chunk-M6OST55S.js.map +1 -0
  122. package/dist/{chunk-F3GDRNUT.js → chunk-MD3Y6J3L.js} +35 -69
  123. package/dist/chunk-MD3Y6J3L.js.map +1 -0
  124. package/dist/{chunk-XJFLDA7A.js → chunk-MTMJVJ5W.js} +8 -7
  125. package/dist/chunk-MTMJVJ5W.js.map +1 -0
  126. package/dist/{chunk-DFMLEQZB.js → chunk-NF5JWERG.js} +5 -10
  127. package/dist/chunk-NF5JWERG.js.map +1 -0
  128. package/dist/{chunk-DO7C2TOG.js → chunk-PKHL44ER.js} +3 -3
  129. package/dist/{chunk-DO7C2TOG.js.map → chunk-PKHL44ER.js.map} +1 -1
  130. package/dist/{chunk-6CME4UEK.js → chunk-PSHOXR6C.js} +7008 -4827
  131. package/dist/chunk-PSHOXR6C.js.map +1 -0
  132. package/dist/chunk-PSMV73A5.js +117 -0
  133. package/dist/chunk-PSMV73A5.js.map +1 -0
  134. package/dist/chunk-PY4KJPYU.js +9 -0
  135. package/dist/chunk-PY4KJPYU.js.map +1 -0
  136. package/dist/chunk-PZ5AY32C.js +10 -0
  137. package/dist/{chunk-DE6GKS6G.js → chunk-Q7SS3IME.js} +50 -9
  138. package/dist/chunk-Q7SS3IME.js.map +1 -0
  139. package/dist/chunk-QHJW5EXU.js +54 -0
  140. package/dist/chunk-QHJW5EXU.js.map +1 -0
  141. package/dist/chunk-QZISFCVG.js +233 -0
  142. package/dist/chunk-QZISFCVG.js.map +1 -0
  143. package/dist/{chunk-NONAAENK.js → chunk-RUEKROOS.js} +11 -4
  144. package/dist/chunk-RUEKROOS.js.map +1 -0
  145. package/dist/chunk-RUIMKQTA.js +23 -0
  146. package/dist/chunk-RUIMKQTA.js.map +1 -0
  147. package/dist/{chunk-TFBP23BX.js → chunk-SKF73JOP.js} +66 -7
  148. package/dist/chunk-SKF73JOP.js.map +1 -0
  149. package/dist/chunk-SM3AVUHC.js +42 -0
  150. package/dist/chunk-SM3AVUHC.js.map +1 -0
  151. package/dist/{chunk-ML236QKC.js → chunk-SMXWYP24.js} +5 -5
  152. package/dist/chunk-SMXWYP24.js.map +1 -0
  153. package/dist/chunk-SRB2XEWB.js +148 -0
  154. package/dist/chunk-SRB2XEWB.js.map +1 -0
  155. package/dist/chunk-SVLR4POP.js +64 -0
  156. package/dist/chunk-SVLR4POP.js.map +1 -0
  157. package/dist/chunk-TA66UMI4.js +123 -0
  158. package/dist/chunk-TA66UMI4.js.map +1 -0
  159. package/dist/chunk-TEILUWOI.js +664 -0
  160. package/dist/chunk-TEILUWOI.js.map +1 -0
  161. package/dist/chunk-TJYQIGAP.js +82 -0
  162. package/dist/chunk-TJYQIGAP.js.map +1 -0
  163. package/dist/{chunk-H2X3ISXG.js → chunk-UAG5KWVA.js} +7 -7
  164. package/dist/chunk-UAG5KWVA.js.map +1 -0
  165. package/dist/chunk-UDRIWL7A.js +382 -0
  166. package/dist/chunk-UDRIWL7A.js.map +1 -0
  167. package/dist/{chunk-2GLDA55J.js → chunk-UIU2THRG.js} +498 -133
  168. package/dist/chunk-UIU2THRG.js.map +1 -0
  169. package/dist/{chunk-3YPCK6JX.js → chunk-UPJNJGGA.js} +165 -423
  170. package/dist/chunk-UPJNJGGA.js.map +1 -0
  171. package/dist/chunk-UV5MFVO3.js +21 -0
  172. package/dist/chunk-UV5MFVO3.js.map +1 -0
  173. package/dist/{chunk-2WUSG3IT.js → chunk-V7RWQRG7.js} +5 -5
  174. package/dist/chunk-V7RWQRG7.js.map +1 -0
  175. package/dist/{chunk-VTKGMEPP.js → chunk-VCTFO5CO.js} +13 -3
  176. package/dist/chunk-VCTFO5CO.js.map +1 -0
  177. package/dist/{chunk-5T22KDPN.js → chunk-VFQGRQIH.js} +8 -8
  178. package/dist/chunk-VFQGRQIH.js.map +1 -0
  179. package/dist/{chunk-2QR2PQTT.js → chunk-VQNJ7UZL.js} +5 -3
  180. package/dist/chunk-VQNJ7UZL.js.map +1 -0
  181. package/dist/{chunk-DONMZAD2.js → chunk-WWGT2MDV.js} +43 -165
  182. package/dist/chunk-WWGT2MDV.js.map +1 -0
  183. package/dist/{chunk-EMIGCR7X.js → chunk-WXSYDNBT.js} +2 -2
  184. package/dist/chunk-WXSYDNBT.js.map +1 -0
  185. package/dist/chunk-WYSO2NIH.js +30 -0
  186. package/dist/chunk-WYSO2NIH.js.map +1 -0
  187. package/dist/chunk-XXYIOFUB.js +164 -0
  188. package/dist/chunk-XXYIOFUB.js.map +1 -0
  189. package/dist/{chunk-3BFJOWSU.js → chunk-Y22T3KQE.js} +35 -7
  190. package/dist/chunk-Y22T3KQE.js.map +1 -0
  191. package/dist/{chunk-A3JMGXPG.js → chunk-YMUGBZN2.js} +2 -2
  192. package/dist/chunk-YMUGBZN2.js.map +1 -0
  193. package/dist/{chunk-I5FOWO4J.js → chunk-ZCGAHGUS.js} +52 -73
  194. package/dist/chunk-ZCGAHGUS.js.map +1 -0
  195. package/dist/{chunk-FZU343FL.js → chunk-ZJADGNYF.js} +2 -2
  196. package/dist/chunk-ZJADGNYF.js.map +1 -0
  197. package/dist/{chunk-B6PB7JLN.js → chunk-ZYUC53LT.js} +427 -6
  198. package/dist/chunk-ZYUC53LT.js.map +1 -0
  199. package/dist/collection-facade-GQAOGJP2.js +33 -0
  200. package/dist/consent/index.d.ts +5 -7
  201. package/dist/consent/index.js +7 -5
  202. package/dist/consent/index.js.map +1 -1
  203. package/dist/crdt/index.d.ts +6 -2
  204. package/dist/crdt/index.js +3 -2
  205. package/dist/crdt/index.js.map +1 -1
  206. package/dist/decrypt-partition-IHtxEnTi.d.ts +21 -0
  207. package/dist/delegation-UL5HKLER.js +19 -0
  208. package/dist/derivations/index.d.ts +7 -9
  209. package/dist/derivations/index.js +11 -6
  210. package/dist/describe/index.d.ts +1 -0
  211. package/dist/describe/index.js +1 -0
  212. package/dist/describe-Ccd1hS7a.d.ts +143 -0
  213. package/dist/{dev-unlock-B4kDxep_.d.ts → dev-unlock-h0K-UZTk.d.ts} +1 -1
  214. package/dist/discriminant-BN9REW3o.d.ts +60 -0
  215. package/dist/enclave-UL5LW66V.js +88 -0
  216. package/dist/executor-2FWQRLMG.js +15 -0
  217. package/dist/executor-QZ7BXICW.js +9 -0
  218. package/dist/executor-Z5ZLJVTV.js +9 -0
  219. package/dist/export-accessible-KRV5W4GH.js +17 -0
  220. package/dist/extract-partition-GLKBOXFQ.js +30 -0
  221. package/dist/{fanout-sidecar-OKPMMPLG.js → fanout-sidecar-GF3DJ6KR.js} +34 -14
  222. package/dist/fanout-sidecar-GF3DJ6KR.js.map +1 -0
  223. package/dist/forget/index.d.ts +36 -0
  224. package/dist/forget/index.js +19 -0
  225. package/dist/forget/index.js.map +1 -0
  226. package/dist/guards/index.d.ts +13 -9
  227. package/dist/guards/index.js +12 -5
  228. package/dist/{hash-DdpVypUp.d.cts → hash-CdSr06sm.d.ts} +5 -1
  229. package/dist/history/index.d.ts +6 -8
  230. package/dist/history/index.js +19 -12
  231. package/dist/history/index.js.map +1 -1
  232. package/dist/i18n/index.d.ts +5 -7
  233. package/dist/i18n/index.js +104 -15
  234. package/dist/i18n/index.js.map +1 -1
  235. package/dist/in/index.d.ts +5 -0
  236. package/dist/in/index.js +1 -0
  237. package/dist/in/index.js.map +1 -0
  238. package/dist/index-B9QdHytu.d.ts +123 -0
  239. package/dist/index-CGLLRtFc.d.ts +123 -0
  240. package/dist/{types-Df28QFKb.d.ts → index-_6At2_9_.d.ts} +16270 -8358
  241. package/dist/index.d.ts +1088 -450
  242. package/dist/index.js +1165 -293
  243. package/dist/index.js.map +1 -1
  244. package/dist/indexing/index.d.ts +6 -3
  245. package/dist/indexing/index.js +6 -5
  246. package/dist/indexing/index.js.map +1 -1
  247. package/dist/issue-Y6UVIR43.js +13 -0
  248. package/dist/issue-Y6UVIR43.js.map +1 -0
  249. package/dist/kernel/index.d.ts +32 -0
  250. package/dist/kernel/index.js +53 -0
  251. package/dist/kernel/index.js.map +1 -0
  252. package/dist/{ledger-TMRZYNVJ.js → ledger-RYI35CDS.js} +12 -9
  253. package/dist/ledger-RYI35CDS.js.map +1 -0
  254. package/dist/liberate-CRPZEV7O.js +18 -0
  255. package/dist/liberate-CRPZEV7O.js.map +1 -0
  256. package/dist/materialized-views/index.d.ts +6 -19
  257. package/dist/materialized-views/index.js +16 -12
  258. package/dist/mime-magic-B4RoFj3B.d.ts +103 -0
  259. package/dist/noydb-LDEBLNHY.js +50 -0
  260. package/dist/noydb-LDEBLNHY.js.map +1 -0
  261. package/dist/on/index.d.ts +5 -0
  262. package/dist/on/index.js +11 -0
  263. package/dist/on/index.js.map +1 -0
  264. package/dist/overlay-views/index.d.ts +27 -11
  265. package/dist/overlay-views/index.js +7 -6
  266. package/dist/periods/index.d.ts +5 -7
  267. package/dist/periods/index.js +13 -9
  268. package/dist/pod/index.d.ts +63 -0
  269. package/dist/pod/index.js +61 -0
  270. package/dist/pod/index.js.map +1 -0
  271. package/dist/policy-IXK4FVXP.js +41 -0
  272. package/dist/policy-IXK4FVXP.js.map +1 -0
  273. package/dist/portability/index.d.ts +20 -0
  274. package/dist/portability/index.js +43 -0
  275. package/dist/portability/index.js.map +1 -0
  276. package/dist/{public-envelope-BW6OXORV.js → public-envelope-JHDQCPSX.js} +6 -5
  277. package/dist/public-envelope-JHDQCPSX.js.map +1 -0
  278. package/dist/query/index.d.ts +5 -3
  279. package/dist/query/index.js +21 -13
  280. package/dist/read-only-facade-5ADRJVTM.js +8 -0
  281. package/dist/read-only-facade-5ADRJVTM.js.map +1 -0
  282. package/dist/registry-45RJNWGU.js +9 -0
  283. package/dist/registry-45RJNWGU.js.map +1 -0
  284. package/dist/registry-5GRV5VXI.js +13 -0
  285. package/dist/registry-5GRV5VXI.js.map +1 -0
  286. package/dist/registry-VW6P6A3J.js +8 -0
  287. package/dist/registry-VW6P6A3J.js.map +1 -0
  288. package/dist/registry-WEUCHBO4.js +11 -0
  289. package/dist/registry-WEUCHBO4.js.map +1 -0
  290. package/dist/request-withdrawal-GBPH2FDY.js +25 -0
  291. package/dist/request-withdrawal-GBPH2FDY.js.map +1 -0
  292. package/dist/revoke-TUFRGI6S.js +18 -0
  293. package/dist/revoke-TUFRGI6S.js.map +1 -0
  294. package/dist/sealed-record/index.d.ts +69 -0
  295. package/dist/sealed-record/index.js +65 -0
  296. package/dist/sealed-record/index.js.map +1 -0
  297. package/dist/session/index.d.ts +6 -8
  298. package/dist/session/index.js +7 -5
  299. package/dist/session/index.js.map +1 -1
  300. package/dist/shadow/index.d.ts +5 -7
  301. package/dist/shadow/index.js +4 -3
  302. package/dist/shadow/index.js.map +1 -1
  303. package/dist/{signer-72QAUSVW.js → signer-PNKTBVF7.js} +6 -5
  304. package/dist/signer-PNKTBVF7.js.map +1 -0
  305. package/dist/snapshots/index.d.ts +6 -8
  306. package/dist/snapshots/index.js +13 -11
  307. package/dist/snapshots/index.js.map +1 -1
  308. package/dist/{stale-6ZDBTQT7.js → stale-3JWHNDIY.js} +3 -2
  309. package/dist/stale-3JWHNDIY.js.map +1 -0
  310. package/dist/store-coordination-provider-3I7XWZZK.js +142 -0
  311. package/dist/store-coordination-provider-3I7XWZZK.js.map +1 -0
  312. package/dist/subject-index-O75wKshW.d.ts +362 -0
  313. package/dist/sync/index.d.ts +4 -6
  314. package/dist/sync/index.js +7 -6
  315. package/dist/sync/index.js.map +1 -1
  316. package/dist/team/index.d.ts +5 -7
  317. package/dist/team/index.js +20 -16
  318. package/dist/tiers/index.d.ts +5 -0
  319. package/dist/tiers/index.js +33 -0
  320. package/dist/tiers/index.js.map +1 -0
  321. package/dist/to/index.d.ts +5 -0
  322. package/dist/to/index.js +17 -0
  323. package/dist/to/index.js.map +1 -0
  324. package/dist/transition-guard-DqodPNKw.d.ts +165 -0
  325. package/dist/tx/index.d.ts +23 -9
  326. package/dist/tx/index.js +13 -8
  327. package/dist/tx/index.js.map +1 -1
  328. package/dist/ui/index.d.ts +1 -0
  329. package/dist/ui/index.js +1 -0
  330. package/dist/ui/index.js.map +1 -0
  331. package/dist/ulid-DRH25k3y.d.ts +66 -0
  332. package/dist/ulid-PTAIMDG4.js +10 -0
  333. package/dist/ulid-PTAIMDG4.js.map +1 -0
  334. package/dist/util/index.d.ts +2 -0
  335. package/dist/util/index.js +7 -2
  336. package/dist/util/index.js.map +1 -1
  337. package/dist/vault-diff-BtpiBvic.d.ts +147 -0
  338. package/dist/with/index.d.ts +38 -0
  339. package/dist/with/index.js +25 -0
  340. package/dist/with/index.js.map +1 -0
  341. package/dist/{with-materialized-view-BLkQxoQN.d.ts → with-materialized-view-DMmWtYfJ.d.ts} +1 -1
  342. package/dist/{with-overlayed-view-CRsSEwHR.d.ts → with-overlayed-view-D_IB2nkM.d.ts} +2 -3
  343. package/dist/with-rollup-em2wxoHP.d.ts +47 -0
  344. package/dist/withdraw-accessible-FFS46EOD.js +22 -0
  345. package/dist/withdraw-accessible-FFS46EOD.js.map +1 -0
  346. package/dist/zod-HAJM7LMS.js +14763 -0
  347. package/dist/zod-HAJM7LMS.js.map +1 -0
  348. package/package.json +121 -201
  349. package/dist/aggregate/index.cjs.map +0 -1
  350. package/dist/aggregate/index.d.cts +0 -38
  351. package/dist/attestation/index.cjs +0 -305
  352. package/dist/attestation/index.cjs.map +0 -1
  353. package/dist/attestation/index.d.cts +0 -52
  354. package/dist/blobs/index.cjs +0 -1480
  355. package/dist/blobs/index.cjs.map +0 -1
  356. package/dist/blobs/index.d.cts +0 -46
  357. package/dist/bundle/index.cjs +0 -19264
  358. package/dist/bundle/index.cjs.map +0 -1
  359. package/dist/bundle/index.d.cts +0 -176
  360. package/dist/chunk-22DWZL57.js.map +0 -1
  361. package/dist/chunk-2GLDA55J.js.map +0 -1
  362. package/dist/chunk-2QR2PQTT.js.map +0 -1
  363. package/dist/chunk-2WUSG3IT.js.map +0 -1
  364. package/dist/chunk-3BFJOWSU.js.map +0 -1
  365. package/dist/chunk-3YPCK6JX.js.map +0 -1
  366. package/dist/chunk-53XBRIRT.js.map +0 -1
  367. package/dist/chunk-5T22KDPN.js.map +0 -1
  368. package/dist/chunk-5XHKQ56N.js +0 -340
  369. package/dist/chunk-5XHKQ56N.js.map +0 -1
  370. package/dist/chunk-6CME4UEK.js.map +0 -1
  371. package/dist/chunk-6STK5TQP.js +0 -139
  372. package/dist/chunk-6STK5TQP.js.map +0 -1
  373. package/dist/chunk-A3JMGXPG.js.map +0 -1
  374. package/dist/chunk-B6PB7JLN.js.map +0 -1
  375. package/dist/chunk-BVRYKATC.js.map +0 -1
  376. package/dist/chunk-DE6GKS6G.js.map +0 -1
  377. package/dist/chunk-DFMLEQZB.js.map +0 -1
  378. package/dist/chunk-DJHHA6XH.js.map +0 -1
  379. package/dist/chunk-DL3HWOQ5.js.map +0 -1
  380. package/dist/chunk-DONMZAD2.js.map +0 -1
  381. package/dist/chunk-EMIGCR7X.js.map +0 -1
  382. package/dist/chunk-F3GDRNUT.js.map +0 -1
  383. package/dist/chunk-FZU343FL.js.map +0 -1
  384. package/dist/chunk-H2X3ISXG.js.map +0 -1
  385. package/dist/chunk-HNRHLRDC.js.map +0 -1
  386. package/dist/chunk-I5FOWO4J.js.map +0 -1
  387. package/dist/chunk-J66GRPNH.js.map +0 -1
  388. package/dist/chunk-JWRVQKRZ.js.map +0 -1
  389. package/dist/chunk-K3DK3NU5.js.map +0 -1
  390. package/dist/chunk-ML236QKC.js.map +0 -1
  391. package/dist/chunk-NONAAENK.js.map +0 -1
  392. package/dist/chunk-O3VZPBZG.js.map +0 -1
  393. package/dist/chunk-OIF6LZUR.js.map +0 -1
  394. package/dist/chunk-OQ6PIGHA.js +0 -19
  395. package/dist/chunk-OQ6PIGHA.js.map +0 -1
  396. package/dist/chunk-PE2FR4J3.js.map +0 -1
  397. package/dist/chunk-PP6W64Y3.js +0 -275
  398. package/dist/chunk-PP6W64Y3.js.map +0 -1
  399. package/dist/chunk-PX35GA7L.js.map +0 -1
  400. package/dist/chunk-QEILDE6R.js +0 -793
  401. package/dist/chunk-QEILDE6R.js.map +0 -1
  402. package/dist/chunk-QODLLGQ7.js.map +0 -1
  403. package/dist/chunk-RAZ4OVLL.js +0 -51
  404. package/dist/chunk-RAZ4OVLL.js.map +0 -1
  405. package/dist/chunk-SYMWGEET.js.map +0 -1
  406. package/dist/chunk-T7JEBOGN.js.map +0 -1
  407. package/dist/chunk-TFBP23BX.js.map +0 -1
  408. package/dist/chunk-TV3YZ35S.js +0 -90
  409. package/dist/chunk-TV3YZ35S.js.map +0 -1
  410. package/dist/chunk-U26HQ6KJ.js.map +0 -1
  411. package/dist/chunk-UF3BUNQZ.js +0 -1
  412. package/dist/chunk-UMLVJTYV.js +0 -20
  413. package/dist/chunk-UMLVJTYV.js.map +0 -1
  414. package/dist/chunk-VTKGMEPP.js.map +0 -1
  415. package/dist/chunk-W6EQLGMB.js +0 -100
  416. package/dist/chunk-W6EQLGMB.js.map +0 -1
  417. package/dist/chunk-WIRRPTFH.js +0 -17
  418. package/dist/chunk-WIRRPTFH.js.map +0 -1
  419. package/dist/chunk-WPLVTJ5D.js.map +0 -1
  420. package/dist/chunk-XJFLDA7A.js.map +0 -1
  421. package/dist/chunk-Z6FNBOTC.js.map +0 -1
  422. package/dist/chunk-ZYWZWG6G.js.map +0 -1
  423. package/dist/consent/index.cjs +0 -204
  424. package/dist/consent/index.cjs.map +0 -1
  425. package/dist/consent/index.d.cts +0 -25
  426. package/dist/crdt/index.cjs +0 -152
  427. package/dist/crdt/index.cjs.map +0 -1
  428. package/dist/crdt/index.d.cts +0 -30
  429. package/dist/crypto-HTZ4FJOP.js +0 -44
  430. package/dist/delegation-RI54P6I5.js +0 -17
  431. package/dist/derivations/index.cjs +0 -351
  432. package/dist/derivations/index.cjs.map +0 -1
  433. package/dist/derivations/index.d.cts +0 -72
  434. package/dist/dev-unlock-BIoEFbwm.d.cts +0 -263
  435. package/dist/executor-5PNY7LGW.js +0 -8
  436. package/dist/executor-B4QIYGZX.js +0 -8
  437. package/dist/executor-BWXXDQ7K.js +0 -11
  438. package/dist/fanout-sidecar-OKPMMPLG.js.map +0 -1
  439. package/dist/guards/index.cjs +0 -322
  440. package/dist/guards/index.cjs.map +0 -1
  441. package/dist/guards/index.d.cts +0 -31
  442. package/dist/hash-HJqD14vS.d.ts +0 -63
  443. package/dist/history/index.cjs +0 -1222
  444. package/dist/history/index.cjs.map +0 -1
  445. package/dist/history/index.d.cts +0 -63
  446. package/dist/i18n/index.cjs +0 -1100
  447. package/dist/i18n/index.cjs.map +0 -1
  448. package/dist/i18n/index.d.cts +0 -39
  449. package/dist/index-4fBVt8j9.d.cts +0 -2387
  450. package/dist/index-D8I_pyJD.d.ts +0 -2387
  451. package/dist/index.cjs +0 -24487
  452. package/dist/index.cjs.map +0 -1
  453. package/dist/index.d.cts +0 -776
  454. package/dist/indexing/index.cjs +0 -742
  455. package/dist/indexing/index.cjs.map +0 -1
  456. package/dist/indexing/index.d.cts +0 -36
  457. package/dist/issue-VGDPP4B5.js +0 -12
  458. package/dist/lazy-builder-7tIpFyWN.d.ts +0 -304
  459. package/dist/lazy-builder-wY4pMCEe.d.cts +0 -304
  460. package/dist/materialized-views/index.cjs +0 -854
  461. package/dist/materialized-views/index.cjs.map +0 -1
  462. package/dist/materialized-views/index.d.cts +0 -186
  463. package/dist/mime-magic-CBBSOkjm.d.cts +0 -50
  464. package/dist/mime-magic-CBBSOkjm.d.ts +0 -50
  465. package/dist/noydb-G725ZSZG.js +0 -34
  466. package/dist/overlay-views/index.cjs +0 -359
  467. package/dist/overlay-views/index.cjs.map +0 -1
  468. package/dist/overlay-views/index.d.cts +0 -82
  469. package/dist/periods/index.cjs +0 -1041
  470. package/dist/periods/index.cjs.map +0 -1
  471. package/dist/periods/index.d.cts +0 -22
  472. package/dist/predicate-BSAGEyu5.d.cts +0 -209
  473. package/dist/predicate-BSAGEyu5.d.ts +0 -209
  474. package/dist/query/index.cjs +0 -2375
  475. package/dist/query/index.cjs.map +0 -1
  476. package/dist/query/index.d.cts +0 -3
  477. package/dist/read-only-facade-ITU6L7BL.js +0 -7
  478. package/dist/registry-3QP3YKQS.js +0 -8
  479. package/dist/registry-DKEXOJVO.js +0 -7
  480. package/dist/registry-OJIOSBV6.js +0 -10
  481. package/dist/registry-USRVT6YF.js +0 -8
  482. package/dist/revoke-JCC7N56X.js +0 -17
  483. package/dist/session/index.cjs +0 -495
  484. package/dist/session/index.cjs.map +0 -1
  485. package/dist/session/index.d.cts +0 -46
  486. package/dist/shadow/index.cjs +0 -133
  487. package/dist/shadow/index.cjs.map +0 -1
  488. package/dist/shadow/index.d.cts +0 -17
  489. package/dist/snapshots/index.cjs +0 -937
  490. package/dist/snapshots/index.cjs.map +0 -1
  491. package/dist/snapshots/index.d.cts +0 -28
  492. package/dist/store/index.cjs +0 -1083
  493. package/dist/store/index.cjs.map +0 -1
  494. package/dist/store/index.d.cts +0 -492
  495. package/dist/store/index.d.ts +0 -492
  496. package/dist/store/index.js +0 -37
  497. package/dist/strategy-BSxFXGzb.d.cts +0 -110
  498. package/dist/strategy-BSxFXGzb.d.ts +0 -110
  499. package/dist/strategy-DSTrsZ8t.d.cts +0 -601
  500. package/dist/strategy-DSTrsZ8t.d.ts +0 -601
  501. package/dist/sync/index.cjs +0 -1062
  502. package/dist/sync/index.cjs.map +0 -1
  503. package/dist/sync/index.d.cts +0 -43
  504. package/dist/team/index.cjs +0 -2798
  505. package/dist/team/index.cjs.map +0 -1
  506. package/dist/team/index.d.cts +0 -118
  507. package/dist/tx/index.cjs +0 -544
  508. package/dist/tx/index.cjs.map +0 -1
  509. package/dist/tx/index.d.cts +0 -21
  510. package/dist/types-DyXYr8rE.d.cts +0 -12818
  511. package/dist/ulid-COREQ2RQ.js +0 -9
  512. package/dist/ulid-Drr7ykr6.d.cts +0 -603
  513. package/dist/util/index.cjs +0 -230
  514. package/dist/util/index.cjs.map +0 -1
  515. package/dist/util/index.d.cts +0 -77
  516. package/dist/with-derivation-DFnFByiQ.d.ts +0 -13
  517. package/dist/with-derivation-DU3Sjazm.d.cts +0 -13
  518. package/dist/with-guard-ByyxmEe7.d.cts +0 -18
  519. package/dist/with-guard-qube6BMI.d.ts +0 -18
  520. package/dist/with-materialized-view-BmEToIR1.d.cts +0 -27
  521. package/dist/with-overlayed-view-BMsQQbWm.d.cts +0 -13
  522. /package/dist/{store → adapter}/index.js.map +0 -0
  523. /package/dist/{chunk-UF3BUNQZ.js.map → api-RW3KP7WU.js.map} +0 -0
  524. /package/dist/{crypto-HTZ4FJOP.js.map → as/index.js.map} +0 -0
  525. /package/dist/{delegation-RI54P6I5.js.map → at/index.js.map} +0 -0
  526. /package/dist/{executor-5PNY7LGW.js.map → by/index.js.map} +0 -0
  527. /package/dist/{executor-B4QIYGZX.js.map → cargo/index.js.map} +0 -0
  528. /package/dist/{executor-BWXXDQ7K.js.map → chunk-BGIZAFY7.js.map} +0 -0
  529. /package/dist/{issue-VGDPP4B5.js.map → chunk-CHFZDSE4.js.map} +0 -0
  530. /package/dist/{ledger-TMRZYNVJ.js.map → chunk-GHVIKOHT.js.map} +0 -0
  531. /package/dist/{noydb-G725ZSZG.js.map → chunk-K2WCO3NX.js.map} +0 -0
  532. /package/dist/{public-envelope-BW6OXORV.js.map → chunk-PZ5AY32C.js.map} +0 -0
  533. /package/dist/{read-only-facade-ITU6L7BL.js.map → collection-facade-GQAOGJP2.js.map} +0 -0
  534. /package/dist/{registry-3QP3YKQS.js.map → delegation-UL5HKLER.js.map} +0 -0
  535. /package/dist/{registry-DKEXOJVO.js.map → describe/index.js.map} +0 -0
  536. /package/dist/{registry-OJIOSBV6.js.map → enclave-UL5LW66V.js.map} +0 -0
  537. /package/dist/{registry-USRVT6YF.js.map → executor-2FWQRLMG.js.map} +0 -0
  538. /package/dist/{revoke-JCC7N56X.js.map → executor-QZ7BXICW.js.map} +0 -0
  539. /package/dist/{signer-72QAUSVW.js.map → executor-Z5ZLJVTV.js.map} +0 -0
  540. /package/dist/{stale-6ZDBTQT7.js.map → export-accessible-KRV5W4GH.js.map} +0 -0
  541. /package/dist/{ulid-COREQ2RQ.js.map → extract-partition-GLKBOXFQ.js.map} +0 -0
@@ -1,2798 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/team/index.ts
21
- var team_exports = {};
22
- __export(team_exports, {
23
- PresenceHandle: () => PresenceHandle,
24
- SYNC_CREDENTIALS_COLLECTION: () => SYNC_CREDENTIALS_COLLECTION,
25
- SyncEngine: () => SyncEngine,
26
- SyncTransaction: () => SyncTransaction,
27
- buildRecipientKeyringFile: () => buildRecipientKeyringFile,
28
- burnPaperRecoveryEntry: () => burnPaperRecoveryEntry,
29
- changeSecret: () => changeSecret,
30
- createOwnerKeyring: () => createOwnerKeyring,
31
- credentialStatus: () => credentialStatus,
32
- deleteCredential: () => deleteCredential,
33
- deriveMagicLinkContentKey: () => deriveMagicLinkContentKey,
34
- enrollAuthenticator: () => enrollAuthenticator,
35
- ensureCollectionDEK: () => ensureCollectionDEK,
36
- evaluateExportCapability: () => evaluateExportCapability,
37
- evaluateImportCapability: () => evaluateImportCapability,
38
- findAuthenticator: () => findAuthenticator,
39
- getCredential: () => getCredential,
40
- grant: () => grant,
41
- hasExportCapability: () => hasExportCapability,
42
- hasImportCapability: () => hasImportCapability,
43
- isMagicLinkGrantExpired: () => isMagicLinkGrantExpired,
44
- listCredentials: () => listCredentials,
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
69
- });
70
- module.exports = __toCommonJS(team_exports);
71
-
72
- // src/types.ts
73
- var NOYDB_FORMAT_VERSION = 1;
74
- var NOYDB_KEYRING_VERSION = 1;
75
- var NOYDB_SYNC_VERSION = 1;
76
-
77
- // src/errors.ts
78
- var NoydbError = class extends Error {
79
- /** Machine-readable error code. Stable across library versions. */
80
- code;
81
- constructor(code, message) {
82
- super(message);
83
- this.name = "NoydbError";
84
- this.code = code;
85
- }
86
- };
87
- var DecryptionError = class extends NoydbError {
88
- constructor(message = "Decryption failed") {
89
- super("DECRYPTION_FAILED", message);
90
- this.name = "DecryptionError";
91
- }
92
- };
93
- var TamperedError = class extends NoydbError {
94
- constructor(message = "Data integrity check failed \u2014 record may have been tampered with") {
95
- super("TAMPERED", message);
96
- this.name = "TamperedError";
97
- }
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
- };
124
- var PermissionDeniedError = class extends NoydbError {
125
- constructor(message = "Permission denied \u2014 insufficient role for this operation") {
126
- super("PERMISSION_DENIED", message);
127
- this.name = "PermissionDeniedError";
128
- }
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
- };
176
- var ConflictError = class extends NoydbError {
177
- /** The actual stored version at the time of conflict. */
178
- version;
179
- constructor(version, message = "Version conflict") {
180
- super("CONFLICT", message);
181
- this.name = "ConflictError";
182
- this.version = version;
183
- }
184
- };
185
- var ValidationError = class extends NoydbError {
186
- constructor(message = "Validation error") {
187
- super("VALIDATION_ERROR", message);
188
- this.name = "ValidationError";
189
- }
190
- };
191
-
192
- // src/crypto.ts
193
- var PBKDF2_ITERATIONS = 6e5;
194
- var SALT_BYTES = 32;
195
- var IV_BYTES = 12;
196
- var KEY_BITS = 256;
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
- }
219
- async function generateDEK() {
220
- return subtle.generateKey(
221
- { name: "AES-GCM", length: KEY_BITS },
222
- true,
223
- // extractable — needed for AES-KW wrapping
224
- ["encrypt", "decrypt"]
225
- );
226
- }
227
- async function wrapKey(dek, kek) {
228
- const wrapped = await subtle.wrapKey("raw", dek, kek, "AES-KW");
229
- return bufferToBase64(wrapped);
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
- }
246
- async function encrypt(plaintext, dek) {
247
- const iv = generateIV();
248
- const encoded = new TextEncoder().encode(plaintext);
249
- const ciphertext = await subtle.encrypt(
250
- { name: "AES-GCM", iv },
251
- dek,
252
- encoded
253
- );
254
- return {
255
- iv: bufferToBase64(iv),
256
- data: bufferToBase64(ciphertext)
257
- };
258
- }
259
- async function decrypt(ivBase64, dataBase64, dek) {
260
- const iv = base64ToBuffer(ivBase64);
261
- const ciphertext = base64ToBuffer(dataBase64);
262
- try {
263
- const plaintext = await subtle.decrypt(
264
- { name: "AES-GCM", iv },
265
- dek,
266
- ciphertext
267
- );
268
- return new TextDecoder().decode(plaintext);
269
- } catch (err) {
270
- if (err instanceof Error && err.name === "OperationError") {
271
- throw new TamperedError();
272
- }
273
- throw new DecryptionError(
274
- err instanceof Error ? err.message : "Decryption failed"
275
- );
276
- }
277
- }
278
- async function derivePresenceKey(dek, collectionName) {
279
- const rawDek = await subtle.exportKey("raw", dek);
280
- const hkdfKey = await subtle.importKey(
281
- "raw",
282
- rawDek,
283
- "HKDF",
284
- false,
285
- ["deriveBits"]
286
- );
287
- const salt = new TextEncoder().encode("noydb-presence");
288
- const info = new TextEncoder().encode(collectionName);
289
- const bits = await subtle.deriveBits(
290
- { name: "HKDF", hash: "SHA-256", salt, info },
291
- hkdfKey,
292
- KEY_BITS
293
- );
294
- return subtle.importKey(
295
- "raw",
296
- bits,
297
- { name: "AES-GCM", length: KEY_BITS },
298
- false,
299
- ["encrypt", "decrypt"]
300
- );
301
- }
302
- function generateIV() {
303
- return globalThis.crypto.getRandomValues(new Uint8Array(IV_BYTES));
304
- }
305
- function generateSalt() {
306
- return globalThis.crypto.getRandomValues(new Uint8Array(SALT_BYTES));
307
- }
308
- function bufferToBase64(buffer) {
309
- const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
310
- let binary = "";
311
- for (let i = 0; i < bytes.length; i++) {
312
- binary += String.fromCharCode(bytes[i]);
313
- }
314
- return btoa(binary);
315
- }
316
- function base64ToBuffer(base64) {
317
- const binary = atob(base64);
318
- const bytes = new Uint8Array(binary.length);
319
- for (let i = 0; i < binary.length; i++) {
320
- bytes[i] = binary.charCodeAt(i);
321
- }
322
- return bytes;
323
- }
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
-
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()` 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
- }
1005
- async function ensureCollectionDEK(adapter, vault, keyring) {
1006
- const inFlight = /* @__PURE__ */ new Map();
1007
- return async (collectionName) => {
1008
- const existing = keyring.deks.get(collectionName);
1009
- if (existing) return existing;
1010
- const pending = inFlight.get(collectionName);
1011
- if (pending) return pending;
1012
- const promise = (async () => {
1013
- const dek = await generateDEK();
1014
- keyring.deks.set(collectionName, dek);
1015
- await persistKeyring(adapter, vault, keyring);
1016
- return dek;
1017
- })();
1018
- inFlight.set(collectionName, promise);
1019
- try {
1020
- return await promise;
1021
- } finally {
1022
- inFlight.delete(collectionName);
1023
- }
1024
- };
1025
- }
1026
- async function persistKeyring(adapter, vault, keyring) {
1027
- if (!keyring.kek) {
1028
- throw new ValidationError(
1029
- "persistKeyring: keyring.kek is null \u2014 cannot wrap DEKs without the KEK. This typically means the keyring was opened via tier-3 PIN resume, session restore, or a wrap-DEKs tier-2 unlock. Re-authenticate at tier 1 (passphrase) before persisting."
1030
- );
1031
- }
1032
- const wrappedDeks = {};
1033
- for (const [collName, dek] of keyring.deks) {
1034
- wrappedDeks[collName] = await wrapKey(dek, keyring.kek);
1035
- }
1036
- const canary = await mintKeyringCanary(keyring.kek);
1037
- const keyringFile = {
1038
- _noydb_keyring: NOYDB_KEYRING_VERSION,
1039
- user_id: keyring.userId,
1040
- display_name: keyring.displayName,
1041
- role: keyring.role,
1042
- permissions: keyring.permissions,
1043
- deks: wrappedDeks,
1044
- salt: bufferToBase64(keyring.salt),
1045
- created_at: (/* @__PURE__ */ new Date()).toISOString(),
1046
- granted_by: keyring.userId,
1047
- canary,
1048
- ...keyring.exportCapability !== void 0 && { export_capability: keyring.exportCapability },
1049
- ...keyring.importCapability !== void 0 && { import_capability: keyring.importCapability },
1050
- ...keyring.authenticators.length > 0 && { authenticators: keyring.authenticators },
1051
- ...keyring.policy !== void 0 && { policy: keyring.policy }
1052
- };
1053
- await writeKeyringFile(adapter, vault, keyring.userId, keyringFile);
1054
- }
1055
- function defaultBundleCapability(role) {
1056
- return role === "owner" || role === "admin";
1057
- }
1058
- function hasExportCapability(keyring, tier, format) {
1059
- const cap = keyring.exportCapability;
1060
- if (tier === "plaintext") {
1061
- const allowed = cap?.plaintext ?? [];
1062
- return allowed.includes("*") || format !== void 0 && allowed.includes(format);
1063
- }
1064
- return cap?.bundle ?? defaultBundleCapability(keyring.role);
1065
- }
1066
- function evaluateExportCapability(capability, role, tier, format) {
1067
- if (tier === "plaintext") {
1068
- const allowed = capability?.plaintext ?? [];
1069
- return allowed.includes("*") || format !== void 0 && allowed.includes(format);
1070
- }
1071
- return capability?.bundle ?? defaultBundleCapability(role);
1072
- }
1073
- function hasImportCapability(keyring, tier, format) {
1074
- const cap = keyring.importCapability;
1075
- if (tier === "plaintext") {
1076
- const allowed = cap?.plaintext ?? [];
1077
- return allowed.includes("*") || format !== void 0 && allowed.includes(format);
1078
- }
1079
- return cap?.bundle === true;
1080
- }
1081
- function evaluateImportCapability(capability, _role, tier, format) {
1082
- if (tier === "plaintext") {
1083
- const allowed = capability?.plaintext ?? [];
1084
- return allowed.includes("*") || format !== void 0 && allowed.includes(format);
1085
- }
1086
- return capability?.bundle === true;
1087
- }
1088
- function resolvePermissions(role, explicit) {
1089
- if (role === "owner" || role === "admin" || role === "viewer") return {};
1090
- return explicit ?? {};
1091
- }
1092
- async function writeKeyringFile(adapter, vault, userId, keyringFile) {
1093
- const envelope = {
1094
- _noydb: 1,
1095
- _v: 1,
1096
- _ts: (/* @__PURE__ */ new Date()).toISOString(),
1097
- _iv: "",
1098
- _data: JSON.stringify(keyringFile)
1099
- };
1100
- await adapter.put(vault, "_keyring", userId, envelope);
1101
- }
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
-
1792
- // src/store/sync-policy.ts
1793
- var SyncScheduler = class {
1794
- policy;
1795
- callbacks;
1796
- _state = "idle";
1797
- _lastPushAt = null;
1798
- _lastPullAt = null;
1799
- _lastError = null;
1800
- _lastPushTime = 0;
1801
- // monotonic ms for minIntervalMs enforcement
1802
- // Timers
1803
- debounceTimer = null;
1804
- pushIntervalTimer = null;
1805
- pullIntervalTimer = null;
1806
- // Bound handlers for cleanup
1807
- boundOnVisibilityChange = null;
1808
- boundOnBeforeExit = null;
1809
- boundOnPageHide = null;
1810
- started = false;
1811
- constructor(policy, callbacks) {
1812
- this.policy = policy;
1813
- this.callbacks = callbacks;
1814
- if (this.shouldRegisterUnload()) {
1815
- this.boundOnVisibilityChange = this.handleVisibilityChange.bind(this);
1816
- this.boundOnPageHide = this.handlePageHide.bind(this);
1817
- this.boundOnBeforeExit = this.handleBeforeExit.bind(this);
1818
- }
1819
- }
1820
- /** Current scheduler status snapshot. */
1821
- get status() {
1822
- return {
1823
- state: this._state,
1824
- lastPushAt: this._lastPushAt,
1825
- lastPullAt: this._lastPullAt,
1826
- lastError: this._lastError,
1827
- pendingWrites: this.callbacks.getDirtyCount()
1828
- };
1829
- }
1830
- /** Start the scheduler — registers timers, event listeners. */
1831
- start() {
1832
- if (this.started) return;
1833
- this.started = true;
1834
- if (this.policy.push.mode === "interval" && this.policy.push.intervalMs) {
1835
- this.pushIntervalTimer = setInterval(() => {
1836
- void this.executePush();
1837
- }, this.policy.push.intervalMs);
1838
- }
1839
- if (this.policy.pull.mode === "interval" && this.policy.pull.intervalMs) {
1840
- this.pullIntervalTimer = setInterval(() => {
1841
- void this.executePull();
1842
- }, this.policy.pull.intervalMs);
1843
- }
1844
- if (this.policy.pull.mode === "on-focus" && typeof document !== "undefined") {
1845
- document.addEventListener("visibilitychange", this.handleFocusPull);
1846
- }
1847
- if (this.shouldRegisterUnload()) {
1848
- if (typeof document !== "undefined" && this.boundOnVisibilityChange) {
1849
- document.addEventListener("visibilitychange", this.boundOnVisibilityChange);
1850
- }
1851
- if (typeof globalThis.addEventListener === "function" && this.boundOnPageHide) {
1852
- globalThis.addEventListener("pagehide", this.boundOnPageHide);
1853
- }
1854
- if (typeof process !== "undefined" && this.boundOnBeforeExit) {
1855
- process.on("beforeExit", this.boundOnBeforeExit);
1856
- }
1857
- }
1858
- }
1859
- /** Stop the scheduler — clears timers, removes event listeners. */
1860
- stop() {
1861
- if (!this.started) return;
1862
- this.started = false;
1863
- if (this.debounceTimer) {
1864
- clearTimeout(this.debounceTimer);
1865
- this.debounceTimer = null;
1866
- }
1867
- if (this.pushIntervalTimer) {
1868
- clearInterval(this.pushIntervalTimer);
1869
- this.pushIntervalTimer = null;
1870
- }
1871
- if (this.pullIntervalTimer) {
1872
- clearInterval(this.pullIntervalTimer);
1873
- this.pullIntervalTimer = null;
1874
- }
1875
- if (this.policy.pull.mode === "on-focus" && typeof document !== "undefined") {
1876
- document.removeEventListener("visibilitychange", this.handleFocusPull);
1877
- }
1878
- if (typeof document !== "undefined" && this.boundOnVisibilityChange) {
1879
- document.removeEventListener("visibilitychange", this.boundOnVisibilityChange);
1880
- }
1881
- if (typeof globalThis.removeEventListener === "function" && this.boundOnPageHide) {
1882
- globalThis.removeEventListener("pagehide", this.boundOnPageHide);
1883
- }
1884
- if (typeof process !== "undefined" && this.boundOnBeforeExit) {
1885
- process.removeListener("beforeExit", this.boundOnBeforeExit);
1886
- }
1887
- }
1888
- /**
1889
- * Notify the scheduler that a local write occurred.
1890
- * For `on-change` mode: triggers immediate push (respecting minIntervalMs).
1891
- * For `debounce` mode: resets the debounce timer.
1892
- * For `manual` / `interval`: no-op.
1893
- */
1894
- notifyChange() {
1895
- if (!this.started) return;
1896
- if (this.policy.push.mode === "on-change") {
1897
- void this.executePush();
1898
- } else if (this.policy.push.mode === "debounce") {
1899
- this.resetDebounce();
1900
- }
1901
- }
1902
- /** Force an immediate push, bypassing the scheduler. */
1903
- async forcePush() {
1904
- await this.executePush();
1905
- }
1906
- /** Force an immediate pull, bypassing the scheduler. */
1907
- async forcePull() {
1908
- await this.executePull();
1909
- }
1910
- // ─── Internal ─────────────────────────────────────────────────────
1911
- async executePush() {
1912
- if (this._state === "pushing") return;
1913
- const minInterval = this.policy.push.minIntervalMs ?? 0;
1914
- if (minInterval > 0) {
1915
- const elapsed = Date.now() - this._lastPushTime;
1916
- if (elapsed < minInterval) {
1917
- if (this.policy.push.mode === "debounce") {
1918
- this.scheduleDebounce(minInterval - elapsed);
1919
- }
1920
- return;
1921
- }
1922
- }
1923
- if (this.callbacks.getDirtyCount() === 0) {
1924
- this._state = "idle";
1925
- return;
1926
- }
1927
- this._state = "pushing";
1928
- try {
1929
- await this.callbacks.push();
1930
- this._lastPushAt = (/* @__PURE__ */ new Date()).toISOString();
1931
- this._lastPushTime = Date.now();
1932
- this._lastError = null;
1933
- this._state = this.callbacks.getDirtyCount() > 0 ? "pending" : "idle";
1934
- } catch (err) {
1935
- this._lastError = err instanceof Error ? err : new Error(String(err));
1936
- this._state = "error";
1937
- }
1938
- }
1939
- async executePull() {
1940
- if (this._state === "pulling") return;
1941
- const previousState = this._state;
1942
- this._state = "pulling";
1943
- try {
1944
- await this.callbacks.pull();
1945
- this._lastPullAt = (/* @__PURE__ */ new Date()).toISOString();
1946
- this._lastError = null;
1947
- this._state = previousState === "pending" ? "pending" : "idle";
1948
- } catch (err) {
1949
- this._lastError = err instanceof Error ? err : new Error(String(err));
1950
- this._state = "error";
1951
- }
1952
- }
1953
- resetDebounce() {
1954
- if (this.debounceTimer) clearTimeout(this.debounceTimer);
1955
- const ms = this.policy.push.debounceMs ?? 3e4;
1956
- this._state = "pending";
1957
- this.scheduleDebounce(ms);
1958
- }
1959
- scheduleDebounce(ms) {
1960
- if (this.debounceTimer) clearTimeout(this.debounceTimer);
1961
- this.debounceTimer = setTimeout(() => {
1962
- this.debounceTimer = null;
1963
- void this.executePush();
1964
- }, ms);
1965
- }
1966
- shouldRegisterUnload() {
1967
- const onUnload = this.policy.push.onUnload;
1968
- if (onUnload !== void 0) return onUnload;
1969
- return this.policy.push.mode !== "manual";
1970
- }
1971
- // ─── Event handlers ───────────────────────────────────────────────
1972
- handleVisibilityChange() {
1973
- if (typeof document !== "undefined" && document.visibilityState === "hidden") {
1974
- this.fireUnloadPush();
1975
- }
1976
- }
1977
- handlePageHide() {
1978
- this.fireUnloadPush();
1979
- }
1980
- handleBeforeExit() {
1981
- this.fireUnloadPush();
1982
- }
1983
- handleFocusPull = () => {
1984
- if (typeof document !== "undefined" && document.visibilityState === "visible") {
1985
- void this.executePull();
1986
- }
1987
- };
1988
- fireUnloadPush() {
1989
- if (this.callbacks.getDirtyCount() === 0) return;
1990
- void this.callbacks.push().catch(() => {
1991
- });
1992
- }
1993
- };
1994
-
1995
- // src/team/sync.ts
1996
- var SyncEngine = class {
1997
- local;
1998
- remote;
1999
- strategy;
2000
- emitter;
2001
- vault;
2002
- role;
2003
- label;
2004
- dirty = [];
2005
- lastPush = null;
2006
- lastPull = null;
2007
- loaded = false;
2008
- autoSyncInterval = null;
2009
- isOnline = true;
2010
- /** Sync scheduler. Manages push/pull timing. */
2011
- scheduler;
2012
- /** Per-collection conflict resolvers registered by Collection instances. */
2013
- conflictResolvers = /* @__PURE__ */ new Map();
2014
- constructor(opts) {
2015
- this.local = opts.local;
2016
- this.remote = opts.remote;
2017
- this.vault = opts.vault;
2018
- this.strategy = opts.strategy;
2019
- this.emitter = opts.emitter;
2020
- this.role = opts.role ?? "sync-peer";
2021
- this.label = opts.label;
2022
- const policy = opts.syncPolicy;
2023
- if (policy && policy.push.mode !== "manual") {
2024
- this.scheduler = new SyncScheduler(policy, {
2025
- push: () => this.push().then(() => {
2026
- }),
2027
- pull: () => this.pull().then(() => {
2028
- }),
2029
- getDirtyCount: () => this.dirty.length
2030
- });
2031
- } else {
2032
- this.scheduler = null;
2033
- }
2034
- }
2035
- /** Start the sync scheduler. Called after vault is fully opened. */
2036
- startScheduler() {
2037
- this.scheduler?.start();
2038
- }
2039
- /** Stop the sync scheduler. Called on close. */
2040
- stopScheduler() {
2041
- this.scheduler?.stop();
2042
- }
2043
- /**
2044
- * Register a per-collection conflict resolver.
2045
- * Called by Collection when `conflictPolicy` is set.
2046
- */
2047
- registerConflictResolver(collection, resolver) {
2048
- this.conflictResolvers.set(collection, resolver);
2049
- }
2050
- /** Record a local change for later push. */
2051
- async trackChange(collection, id, action, version) {
2052
- await this.ensureLoaded();
2053
- const idx = this.dirty.findIndex((d) => d.collection === collection && d.id === id);
2054
- const entry = {
2055
- vault: this.vault,
2056
- collection,
2057
- id,
2058
- action,
2059
- version,
2060
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2061
- };
2062
- if (idx >= 0) {
2063
- this.dirty[idx] = entry;
2064
- } else {
2065
- this.dirty.push(entry);
2066
- }
2067
- await this.persistMeta();
2068
- this.scheduler?.notifyChange();
2069
- }
2070
- /** Push dirty records to remote adapter. Accepts optional `PushOptions` for partial sync. */
2071
- async push(options) {
2072
- await this.ensureLoaded();
2073
- let pushed = 0;
2074
- const conflicts = [];
2075
- const errors = [];
2076
- const completed = [];
2077
- for (let i = 0; i < this.dirty.length; i++) {
2078
- const entry = this.dirty[i];
2079
- if (options?.collections && !options.collections.includes(entry.collection)) {
2080
- continue;
2081
- }
2082
- try {
2083
- if (entry.action === "delete") {
2084
- await this.remote.delete(this.vault, entry.collection, entry.id);
2085
- completed.push(i);
2086
- pushed++;
2087
- } else {
2088
- const envelope = await this.local.get(this.vault, entry.collection, entry.id);
2089
- if (!envelope) {
2090
- completed.push(i);
2091
- continue;
2092
- }
2093
- try {
2094
- await this.remote.put(
2095
- this.vault,
2096
- entry.collection,
2097
- entry.id,
2098
- envelope,
2099
- entry.version - 1
2100
- );
2101
- completed.push(i);
2102
- pushed++;
2103
- } catch (err) {
2104
- if (err instanceof ConflictError) {
2105
- const remoteEnvelope = await this.remote.get(this.vault, entry.collection, entry.id);
2106
- if (remoteEnvelope) {
2107
- const { handled, conflict } = await this.handleConflict(
2108
- entry.collection,
2109
- entry.id,
2110
- envelope,
2111
- remoteEnvelope,
2112
- "push"
2113
- );
2114
- conflicts.push(conflict);
2115
- if (handled === "local") {
2116
- await this.remote.put(this.vault, entry.collection, entry.id, conflict.local);
2117
- completed.push(i);
2118
- pushed++;
2119
- } else if (handled === "remote") {
2120
- await this.local.put(this.vault, entry.collection, entry.id, conflict.remote);
2121
- completed.push(i);
2122
- } else if (handled === "merged" && conflict.local !== envelope) {
2123
- const merged = conflict.local;
2124
- await this.remote.put(this.vault, entry.collection, entry.id, merged);
2125
- await this.local.put(this.vault, entry.collection, entry.id, merged);
2126
- completed.push(i);
2127
- pushed++;
2128
- }
2129
- }
2130
- } else {
2131
- throw err;
2132
- }
2133
- }
2134
- }
2135
- } catch (err) {
2136
- errors.push(err instanceof Error ? err : new Error(String(err)));
2137
- }
2138
- }
2139
- for (const i of completed.sort((a, b) => b - a)) {
2140
- this.dirty.splice(i, 1);
2141
- }
2142
- this.lastPush = (/* @__PURE__ */ new Date()).toISOString();
2143
- await this.persistMeta();
2144
- const result = { pushed, conflicts, errors };
2145
- this.emitter.emit("sync:push", result);
2146
- return result;
2147
- }
2148
- /** Pull remote records to local adapter. Accepts optional `PullOptions` for partial sync. */
2149
- async pull(options) {
2150
- await this.ensureLoaded();
2151
- let pulled = 0;
2152
- const conflicts = [];
2153
- const errors = [];
2154
- try {
2155
- const remoteSnapshot = await this.remote.loadAll(this.vault);
2156
- for (const [collName, records] of Object.entries(remoteSnapshot)) {
2157
- if (options?.collections && !options.collections.includes(collName)) {
2158
- continue;
2159
- }
2160
- for (const [id, remoteEnvelope] of Object.entries(records)) {
2161
- if (options?.modifiedSince && remoteEnvelope._ts <= options.modifiedSince) {
2162
- continue;
2163
- }
2164
- try {
2165
- const localEnvelope = await this.local.get(this.vault, collName, id);
2166
- if (!localEnvelope) {
2167
- await this.local.put(this.vault, collName, id, remoteEnvelope);
2168
- pulled++;
2169
- } else if (remoteEnvelope._v > localEnvelope._v) {
2170
- const isDirty = this.dirty.some((d) => d.collection === collName && d.id === id);
2171
- if (isDirty) {
2172
- const { handled, conflict } = await this.handleConflict(
2173
- collName,
2174
- id,
2175
- localEnvelope,
2176
- remoteEnvelope,
2177
- "pull"
2178
- );
2179
- conflicts.push(conflict);
2180
- if (handled === "remote") {
2181
- await this.local.put(this.vault, collName, id, conflict.remote);
2182
- this.dirty = this.dirty.filter((d) => !(d.collection === collName && d.id === id));
2183
- pulled++;
2184
- } else if (handled === "merged" && conflict.local !== localEnvelope) {
2185
- const merged = conflict.local;
2186
- await this.local.put(this.vault, collName, id, merged);
2187
- this.dirty = this.dirty.filter((d) => !(d.collection === collName && d.id === id));
2188
- pulled++;
2189
- }
2190
- } else {
2191
- await this.local.put(this.vault, collName, id, remoteEnvelope);
2192
- pulled++;
2193
- }
2194
- }
2195
- } catch (err) {
2196
- errors.push(err instanceof Error ? err : new Error(String(err)));
2197
- }
2198
- }
2199
- }
2200
- } catch (err) {
2201
- errors.push(err instanceof Error ? err : new Error(String(err)));
2202
- }
2203
- this.lastPull = (/* @__PURE__ */ new Date()).toISOString();
2204
- await this.persistMeta();
2205
- const result = { pulled, conflicts, errors };
2206
- this.emitter.emit("sync:pull", result);
2207
- return result;
2208
- }
2209
- /** Bidirectional sync: pull then push. */
2210
- async sync(options) {
2211
- const pullResult = await this.pull(options?.pull);
2212
- const pushResult = await this.push(options?.push);
2213
- return { pull: pullResult, push: pushResult };
2214
- }
2215
- /**
2216
- * Push a specific subset of dirty entries (for sync transactions, ).
2217
- * Entries are matched by collection+id from the dirty log; matched entries
2218
- * are removed from the dirty log on success.
2219
- */
2220
- async pushFiltered(predicate) {
2221
- await this.ensureLoaded();
2222
- let pushed = 0;
2223
- const conflicts = [];
2224
- const errors = [];
2225
- const completed = [];
2226
- for (let i = 0; i < this.dirty.length; i++) {
2227
- const entry = this.dirty[i];
2228
- if (!predicate(entry)) continue;
2229
- try {
2230
- if (entry.action === "delete") {
2231
- await this.remote.delete(this.vault, entry.collection, entry.id);
2232
- completed.push(i);
2233
- pushed++;
2234
- } else {
2235
- const envelope = await this.local.get(this.vault, entry.collection, entry.id);
2236
- if (!envelope) {
2237
- completed.push(i);
2238
- continue;
2239
- }
2240
- try {
2241
- await this.remote.put(
2242
- this.vault,
2243
- entry.collection,
2244
- entry.id,
2245
- envelope,
2246
- entry.version - 1
2247
- );
2248
- completed.push(i);
2249
- pushed++;
2250
- } catch (err) {
2251
- if (err instanceof ConflictError) {
2252
- const remoteEnvelope = await this.remote.get(this.vault, entry.collection, entry.id);
2253
- if (remoteEnvelope) {
2254
- const { handled, conflict } = await this.handleConflict(
2255
- entry.collection,
2256
- entry.id,
2257
- envelope,
2258
- remoteEnvelope,
2259
- "push"
2260
- );
2261
- conflicts.push(conflict);
2262
- if (handled === "local") {
2263
- await this.remote.put(this.vault, entry.collection, entry.id, conflict.local);
2264
- completed.push(i);
2265
- pushed++;
2266
- } else if (handled === "remote") {
2267
- await this.local.put(this.vault, entry.collection, entry.id, conflict.remote);
2268
- completed.push(i);
2269
- } else if (handled === "merged" && conflict.local !== envelope) {
2270
- const merged = conflict.local;
2271
- await this.remote.put(this.vault, entry.collection, entry.id, merged);
2272
- await this.local.put(this.vault, entry.collection, entry.id, merged);
2273
- completed.push(i);
2274
- pushed++;
2275
- }
2276
- }
2277
- } else {
2278
- throw err;
2279
- }
2280
- }
2281
- }
2282
- } catch (err) {
2283
- errors.push(err instanceof Error ? err : new Error(String(err)));
2284
- }
2285
- }
2286
- for (const i of completed.sort((a, b) => b - a)) {
2287
- this.dirty.splice(i, 1);
2288
- }
2289
- this.lastPush = (/* @__PURE__ */ new Date()).toISOString();
2290
- await this.persistMeta();
2291
- const result = { pushed, conflicts, errors };
2292
- this.emitter.emit("sync:push", result);
2293
- return result;
2294
- }
2295
- /** Get current sync status. */
2296
- status() {
2297
- return {
2298
- dirty: this.dirty.length,
2299
- lastPush: this.lastPush,
2300
- lastPull: this.lastPull,
2301
- online: this.isOnline
2302
- };
2303
- }
2304
- // ─── Auto-Sync ───────────────────────────────────────────────────
2305
- /** Start auto-sync: listen for online/offline events, optional periodic sync. */
2306
- startAutoSync(intervalMs) {
2307
- if (typeof globalThis.addEventListener === "function") {
2308
- globalThis.addEventListener("online", this.handleOnline);
2309
- globalThis.addEventListener("offline", this.handleOffline);
2310
- }
2311
- if (intervalMs && intervalMs > 0) {
2312
- this.autoSyncInterval = setInterval(() => {
2313
- if (this.isOnline) {
2314
- void this.sync();
2315
- }
2316
- }, intervalMs);
2317
- }
2318
- }
2319
- /** Stop auto-sync and scheduler. */
2320
- stopAutoSync() {
2321
- this.stopScheduler();
2322
- if (typeof globalThis.removeEventListener === "function") {
2323
- globalThis.removeEventListener("online", this.handleOnline);
2324
- globalThis.removeEventListener("offline", this.handleOffline);
2325
- }
2326
- if (this.autoSyncInterval) {
2327
- clearInterval(this.autoSyncInterval);
2328
- this.autoSyncInterval = null;
2329
- }
2330
- }
2331
- handleOnline = () => {
2332
- this.isOnline = true;
2333
- this.emitter.emit("sync:online", void 0);
2334
- void this.sync();
2335
- };
2336
- handleOffline = () => {
2337
- this.isOnline = false;
2338
- this.emitter.emit("sync:offline", void 0);
2339
- };
2340
- /**
2341
- * Resolve a conflict, checking per-collection resolvers first,
2342
- * then falling back to the db-level `ConflictStrategy`.
2343
- *
2344
- * Returns the resolved `Conflict` object (possibly with `resolve` set for
2345
- * manual mode) and a `handled` discriminant:
2346
- * - `'local'` — keep the local envelope; push it to remote.
2347
- * - `'remote'` — keep the remote envelope; update local.
2348
- * - `'merged'` — a custom merge fn produced a new envelope stored as `conflict.local`.
2349
- * - `'deferred'` — manual mode, resolve was not called synchronously.
2350
- */
2351
- async handleConflict(collection, id, local, remote, _phase) {
2352
- const resolver = this.conflictResolvers.get(collection);
2353
- if (resolver) {
2354
- const winner = await resolver(id, local, remote);
2355
- const base = {
2356
- vault: this.vault,
2357
- collection,
2358
- id,
2359
- local,
2360
- remote,
2361
- localVersion: local._v,
2362
- remoteVersion: remote._v
2363
- };
2364
- if (winner === null) return { handled: "deferred", conflict: base };
2365
- if (winner === local) return { handled: "local", conflict: base };
2366
- if (winner === remote) return { handled: "remote", conflict: base };
2367
- return {
2368
- handled: "merged",
2369
- conflict: { ...base, local: winner, localVersion: winner._v }
2370
- };
2371
- }
2372
- const baseConflict = {
2373
- vault: this.vault,
2374
- collection,
2375
- id,
2376
- local,
2377
- remote,
2378
- localVersion: local._v,
2379
- remoteVersion: remote._v
2380
- };
2381
- this.emitter.emit("sync:conflict", baseConflict);
2382
- const side = this.legacyResolve(baseConflict);
2383
- return { handled: side, conflict: baseConflict };
2384
- }
2385
- /** DB-level ConflictStrategy resolution (legacy, kept for backward compat). */
2386
- legacyResolve(conflict) {
2387
- if (typeof this.strategy === "function") {
2388
- return this.strategy(conflict);
2389
- }
2390
- switch (this.strategy) {
2391
- case "local-wins":
2392
- return "local";
2393
- case "remote-wins":
2394
- return "remote";
2395
- case "version":
2396
- default:
2397
- return conflict.localVersion >= conflict.remoteVersion ? "local" : "remote";
2398
- }
2399
- }
2400
- // ─── Persistence ─────────────────────────────────────────────────
2401
- async ensureLoaded() {
2402
- if (this.loaded) return;
2403
- const envelope = await this.local.get(this.vault, "_sync", "meta");
2404
- if (envelope) {
2405
- const meta = JSON.parse(envelope._data);
2406
- this.dirty = [...meta.dirty];
2407
- this.lastPush = meta.last_push;
2408
- this.lastPull = meta.last_pull;
2409
- }
2410
- this.loaded = true;
2411
- }
2412
- async persistMeta() {
2413
- const meta = {
2414
- _noydb_sync: NOYDB_SYNC_VERSION,
2415
- last_push: this.lastPush,
2416
- last_pull: this.lastPull,
2417
- dirty: this.dirty
2418
- };
2419
- const envelope = {
2420
- _noydb: 1,
2421
- _v: 1,
2422
- _ts: (/* @__PURE__ */ new Date()).toISOString(),
2423
- _iv: "",
2424
- _data: JSON.stringify(meta)
2425
- };
2426
- await this.local.put(this.vault, "_sync", "meta", envelope);
2427
- }
2428
- };
2429
-
2430
- // src/team/sync-transaction.ts
2431
- var SyncTransaction = class {
2432
- comp;
2433
- engine;
2434
- ops = [];
2435
- /** @internal — constructed by `Noydb.transaction()` */
2436
- constructor(comp, engine) {
2437
- this.comp = comp;
2438
- this.engine = engine;
2439
- }
2440
- /** Stage a record write. Does not write to any adapter until `commit()`. */
2441
- put(collection, id, record) {
2442
- this.ops.push({ type: "put", collection, id, record });
2443
- return this;
2444
- }
2445
- /** Stage a record delete. Does not write to any adapter until `commit()`. */
2446
- delete(collection, id) {
2447
- this.ops.push({ type: "delete", collection, id });
2448
- return this;
2449
- }
2450
- /**
2451
- * Commit the transaction.
2452
- *
2453
- * Phase 1 — writes all staged operations to the local adapter via the
2454
- * collection layer (encryption + dirty-log tracking).
2455
- *
2456
- * Phase 2 — pushes only the records that were written in this
2457
- * transaction to the remote adapter. Existing dirty entries from
2458
- * outside this transaction are not affected.
2459
- *
2460
- * If any record conflicts during the push, `status` is `'conflict'`
2461
- * and `conflicts` lists the affected records. No automatic rollback is
2462
- * performed.
2463
- */
2464
- async commit() {
2465
- for (const op of this.ops) {
2466
- if (op.type === "put") {
2467
- await this.comp.collection(op.collection).put(op.id, op.record);
2468
- } else {
2469
- await this.comp.collection(op.collection).delete(op.id);
2470
- }
2471
- }
2472
- const opSet = /* @__PURE__ */ new Set();
2473
- for (const op of this.ops) {
2474
- opSet.add(`${op.collection}::${op.id}`);
2475
- }
2476
- const pushResult = await this.engine.pushFiltered(
2477
- (entry) => opSet.has(`${entry.collection}::${entry.id}`)
2478
- );
2479
- return {
2480
- status: pushResult.conflicts.length > 0 ? "conflict" : "committed",
2481
- pushed: pushResult.pushed,
2482
- conflicts: pushResult.conflicts
2483
- };
2484
- }
2485
- };
2486
-
2487
- // src/team/presence.ts
2488
- var PresenceHandle = class {
2489
- adapter;
2490
- syncAdapter;
2491
- vault;
2492
- collectionName;
2493
- userId;
2494
- encrypted;
2495
- getDEK;
2496
- staleMs;
2497
- pollIntervalMs;
2498
- channel;
2499
- storageCollection;
2500
- presenceKey = null;
2501
- subscribers = [];
2502
- unsubscribePubSub = null;
2503
- pollTimer = null;
2504
- stopped = false;
2505
- constructor(opts) {
2506
- this.adapter = opts.adapter;
2507
- this.syncAdapter = opts.syncAdapter;
2508
- this.vault = opts.vault;
2509
- this.collectionName = opts.collectionName;
2510
- this.userId = opts.userId;
2511
- this.encrypted = opts.encrypted;
2512
- this.getDEK = opts.getDEK;
2513
- this.staleMs = opts.staleMs ?? 3e4;
2514
- this.pollIntervalMs = opts.pollIntervalMs ?? 5e3;
2515
- this.channel = `${opts.vault}:${opts.collectionName}:presence`;
2516
- this.storageCollection = `_presence_${opts.collectionName}`;
2517
- }
2518
- /**
2519
- * Announce yourself (or update your cursor/status).
2520
- * Encrypts `payload` with the presence key and publishes it.
2521
- */
2522
- async update(payload) {
2523
- if (this.stopped) return;
2524
- const key = await this.getPresenceKey();
2525
- const now = (/* @__PURE__ */ new Date()).toISOString();
2526
- const plaintext = JSON.stringify({ userId: this.userId, lastSeen: now, payload });
2527
- let encryptedPayload;
2528
- if (this.encrypted && key) {
2529
- const iv = generateIV();
2530
- const ivB64 = bufferToBase64(iv);
2531
- const { data } = await encrypt(plaintext, key);
2532
- encryptedPayload = JSON.stringify({ iv: ivB64, data });
2533
- } else {
2534
- encryptedPayload = plaintext;
2535
- }
2536
- const pubAdapter = this.getPubSubAdapter();
2537
- if (pubAdapter?.presencePublish) {
2538
- await pubAdapter.presencePublish(this.channel, encryptedPayload);
2539
- }
2540
- await this.writeStorageRecord(payload, now);
2541
- }
2542
- /**
2543
- * Subscribe to presence updates. The callback receives a filtered, decrypted
2544
- * list of all currently-active peers (excluding yourself, excluding stale).
2545
- *
2546
- * Returns an unsubscribe function. Also call `stop()` to release all resources.
2547
- */
2548
- subscribe(cb) {
2549
- if (this.stopped) return () => {
2550
- };
2551
- this.subscribers.push(cb);
2552
- if (this.subscribers.length === 1) {
2553
- this.startListening();
2554
- }
2555
- return () => {
2556
- this.subscribers = this.subscribers.filter((s) => s !== cb);
2557
- if (this.subscribers.length === 0) this.stopListening();
2558
- };
2559
- }
2560
- /** Stop all listening and clear resources. */
2561
- stop() {
2562
- this.stopped = true;
2563
- this.stopListening();
2564
- this.subscribers = [];
2565
- }
2566
- // ─── Private ────────────────────────────────────────────────────────
2567
- async getPresenceKey() {
2568
- if (!this.encrypted) return null;
2569
- if (!this.presenceKey) {
2570
- try {
2571
- const dek = await this.getDEK(this.collectionName);
2572
- this.presenceKey = await derivePresenceKey(dek, this.collectionName);
2573
- } catch {
2574
- }
2575
- }
2576
- return this.presenceKey;
2577
- }
2578
- getPubSubAdapter() {
2579
- if (this.syncAdapter?.presencePublish) return this.syncAdapter;
2580
- if (this.adapter.presencePublish) return this.adapter;
2581
- return void 0;
2582
- }
2583
- startListening() {
2584
- const pubAdapter = this.getPubSubAdapter();
2585
- if (pubAdapter?.presenceSubscribe) {
2586
- this.unsubscribePubSub = pubAdapter.presenceSubscribe(
2587
- this.channel,
2588
- (encryptedPayload) => {
2589
- void this.handlePubSubMessage(encryptedPayload);
2590
- }
2591
- );
2592
- } else {
2593
- this.pollTimer = setInterval(
2594
- () => {
2595
- void this.pollStoragePresence();
2596
- },
2597
- this.pollIntervalMs
2598
- );
2599
- void this.pollStoragePresence();
2600
- }
2601
- }
2602
- stopListening() {
2603
- if (this.unsubscribePubSub) {
2604
- this.unsubscribePubSub();
2605
- this.unsubscribePubSub = null;
2606
- }
2607
- if (this.pollTimer) {
2608
- clearInterval(this.pollTimer);
2609
- this.pollTimer = null;
2610
- }
2611
- }
2612
- async handlePubSubMessage(encryptedPayload) {
2613
- try {
2614
- const peer = await this.decryptPresencePayload(encryptedPayload);
2615
- if (!peer || peer.userId === this.userId) return;
2616
- const cutoff = new Date(Date.now() - this.staleMs).toISOString();
2617
- if (peer.lastSeen < cutoff) return;
2618
- await this.pollStoragePresence();
2619
- } catch {
2620
- }
2621
- }
2622
- async decryptPresencePayload(encryptedPayload) {
2623
- const key = await this.getPresenceKey();
2624
- if (!this.encrypted || !key) {
2625
- return JSON.parse(encryptedPayload);
2626
- }
2627
- const { iv: ivB64, data } = JSON.parse(encryptedPayload);
2628
- const plaintext = await decrypt(ivB64, data, key);
2629
- return JSON.parse(plaintext);
2630
- }
2631
- async writeStorageRecord(payload, now) {
2632
- const key = await this.getPresenceKey();
2633
- const plaintext = JSON.stringify(payload);
2634
- let iv = "";
2635
- let data;
2636
- if (this.encrypted && key) {
2637
- const ivBytes = generateIV();
2638
- iv = bufferToBase64(ivBytes);
2639
- const result = await encrypt(plaintext, key);
2640
- data = result.data;
2641
- } else {
2642
- data = plaintext;
2643
- }
2644
- const record = { userId: this.userId, lastSeen: now, iv, data };
2645
- const json = JSON.stringify(record);
2646
- const storeAdapter = this.syncAdapter ?? this.adapter;
2647
- const envelope = {
2648
- _noydb: 1,
2649
- _v: 1,
2650
- _ts: now,
2651
- _iv: "",
2652
- _data: json
2653
- };
2654
- try {
2655
- await storeAdapter.put(
2656
- this.vault,
2657
- this.storageCollection,
2658
- this.userId,
2659
- envelope
2660
- );
2661
- } catch {
2662
- }
2663
- }
2664
- async pollStoragePresence() {
2665
- if (this.stopped || this.subscribers.length === 0) return;
2666
- try {
2667
- const storeAdapter = this.syncAdapter ?? this.adapter;
2668
- const ids = await storeAdapter.list(this.vault, this.storageCollection);
2669
- const cutoff = new Date(Date.now() - this.staleMs).toISOString();
2670
- const peers = [];
2671
- for (const id of ids) {
2672
- if (id === this.userId) continue;
2673
- const envelope = await storeAdapter.get(this.vault, this.storageCollection, id);
2674
- if (!envelope) continue;
2675
- const record = JSON.parse(envelope._data);
2676
- if (record.lastSeen < cutoff) continue;
2677
- let peerPayload;
2678
- if (this.encrypted && this.presenceKey && record.iv) {
2679
- const plaintext = await decrypt(record.iv, record.data, this.presenceKey);
2680
- peerPayload = JSON.parse(plaintext);
2681
- } else {
2682
- peerPayload = JSON.parse(record.data);
2683
- }
2684
- peers.push({ userId: record.userId, payload: peerPayload, lastSeen: record.lastSeen });
2685
- }
2686
- for (const cb of this.subscribers) {
2687
- cb(peers);
2688
- }
2689
- } catch {
2690
- }
2691
- }
2692
- };
2693
-
2694
- // src/team/sync-credentials.ts
2695
- var SYNC_CREDENTIALS_COLLECTION = "_sync_credentials";
2696
- function requireAdminAccess(keyring) {
2697
- if (keyring.role !== "owner" && keyring.role !== "admin") {
2698
- throw new PermissionDeniedError(
2699
- `Sync credentials require owner or admin role. Current role: "${keyring.role}"`
2700
- );
2701
- }
2702
- }
2703
- async function putCredential(adapter, vault, keyring, credential) {
2704
- requireAdminAccess(keyring);
2705
- const getDek = await ensureCollectionDEK(adapter, vault, keyring);
2706
- const dek = await getDek(SYNC_CREDENTIALS_COLLECTION);
2707
- const { iv, data } = await encrypt(JSON.stringify(credential), dek);
2708
- const existing = await adapter.get(vault, SYNC_CREDENTIALS_COLLECTION, credential.adapterId);
2709
- const version = existing ? existing._v + 1 : 1;
2710
- const envelope = {
2711
- _noydb: NOYDB_FORMAT_VERSION,
2712
- _v: version,
2713
- _ts: (/* @__PURE__ */ new Date()).toISOString(),
2714
- _iv: iv,
2715
- _data: data,
2716
- _by: keyring.userId
2717
- };
2718
- await adapter.put(
2719
- vault,
2720
- SYNC_CREDENTIALS_COLLECTION,
2721
- credential.adapterId,
2722
- envelope,
2723
- existing ? existing._v : void 0
2724
- );
2725
- }
2726
- async function getCredential(adapter, vault, keyring, adapterId) {
2727
- requireAdminAccess(keyring);
2728
- const getDek = await ensureCollectionDEK(adapter, vault, keyring);
2729
- const dek = await getDek(SYNC_CREDENTIALS_COLLECTION);
2730
- const envelope = await adapter.get(vault, SYNC_CREDENTIALS_COLLECTION, adapterId);
2731
- if (!envelope) return null;
2732
- const plaintext = await decrypt(envelope._iv, envelope._data, dek);
2733
- return JSON.parse(plaintext);
2734
- }
2735
- async function deleteCredential(adapter, vault, keyring, adapterId) {
2736
- requireAdminAccess(keyring);
2737
- await adapter.delete(vault, SYNC_CREDENTIALS_COLLECTION, adapterId);
2738
- }
2739
- async function listCredentials(adapter, vault, keyring) {
2740
- requireAdminAccess(keyring);
2741
- return adapter.list(vault, SYNC_CREDENTIALS_COLLECTION);
2742
- }
2743
- async function credentialStatus(adapter, vault, keyring, adapterId) {
2744
- const credential = await getCredential(adapter, vault, keyring, adapterId);
2745
- if (!credential) return { exists: false };
2746
- const expired = credential.expiresAt ? Date.now() > new Date(credential.expiresAt).getTime() : false;
2747
- return { exists: true, expired };
2748
- }
2749
- // Annotate the CommonJS export names for ESM import in node:
2750
- 0 && (module.exports = {
2751
- PresenceHandle,
2752
- SYNC_CREDENTIALS_COLLECTION,
2753
- SyncEngine,
2754
- SyncTransaction,
2755
- buildRecipientKeyringFile,
2756
- burnPaperRecoveryEntry,
2757
- changeSecret,
2758
- createOwnerKeyring,
2759
- credentialStatus,
2760
- deleteCredential,
2761
- deriveMagicLinkContentKey,
2762
- enrollAuthenticator,
2763
- ensureCollectionDEK,
2764
- evaluateExportCapability,
2765
- evaluateImportCapability,
2766
- findAuthenticator,
2767
- getCredential,
2768
- grant,
2769
- hasExportCapability,
2770
- hasImportCapability,
2771
- isMagicLinkGrantExpired,
2772
- listCredentials,
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
2797
- });
2798
- //# sourceMappingURL=index.cjs.map