@noy-db/hub 0.6.0-pre.17 → 0.6.0-pre.19

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 (426) hide show
  1. package/dist/api-Z5VHH24A.js +20 -0
  2. package/dist/attestation/index.js +18 -18
  3. package/dist/{backup-7X3PFTSI.js → backup-K62BFMYK.js} +16 -16
  4. package/dist/{backup-7X3PFTSI.js.map → backup-K62BFMYK.js.map} +1 -1
  5. package/dist/blobs/index.js +13 -13
  6. package/dist/broker/index.js +12 -12
  7. package/dist/cargo/index.js +32 -32
  8. package/dist/{chunk-5SK5AFYH.js → chunk-2E2MKBMI.js} +2 -2
  9. package/dist/{chunk-SDEHNGWW.js → chunk-2IK6AC6A.js} +7 -6
  10. package/dist/{chunk-SDEHNGWW.js.map → chunk-2IK6AC6A.js.map} +1 -1
  11. package/dist/{chunk-7TZTQS4H.js → chunk-32O2N2CH.js} +48 -21
  12. package/dist/chunk-32O2N2CH.js.map +1 -0
  13. package/dist/{chunk-QKRJUPAC.js → chunk-37P5VB4N.js} +13 -16
  14. package/dist/chunk-37P5VB4N.js.map +1 -0
  15. package/dist/{chunk-5UGEGEIJ.js → chunk-3Y4OFWQJ.js} +1 -1
  16. package/dist/chunk-3Y4OFWQJ.js.map +1 -0
  17. package/dist/{chunk-7LLKPODL.js → chunk-437FDIHO.js} +3 -3
  18. package/dist/{chunk-AMKCY454.js → chunk-47ZFULEZ.js} +8 -3
  19. package/dist/{chunk-AMKCY454.js.map → chunk-47ZFULEZ.js.map} +1 -1
  20. package/dist/{chunk-4GR422R5.js → chunk-4HVTRHSA.js} +16 -9
  21. package/dist/chunk-4HVTRHSA.js.map +1 -0
  22. package/dist/{chunk-VEA35MQP.js → chunk-5BS6IQZZ.js} +11 -14
  23. package/dist/chunk-5BS6IQZZ.js.map +1 -0
  24. package/dist/{chunk-QWNYUTI2.js → chunk-5EJQNA26.js} +2 -2
  25. package/dist/{chunk-C66T4W5P.js → chunk-5J5TTSGW.js} +3 -3
  26. package/dist/{chunk-JHYISAID.js → chunk-5JT4Y5UE.js} +10 -10
  27. package/dist/{chunk-VUQZYVYX.js → chunk-6S466CMQ.js} +92 -35
  28. package/dist/chunk-6S466CMQ.js.map +1 -0
  29. package/dist/{chunk-IRHOQFA7.js → chunk-7RCZ436O.js} +2 -2
  30. package/dist/{chunk-42IDLU5V.js → chunk-A3NTKHDB.js} +2 -2
  31. package/dist/{chunk-3JQVA527.js → chunk-A4K5GLE6.js} +4 -4
  32. package/dist/{chunk-K2MR7ROB.js → chunk-APV42WPR.js} +2 -2
  33. package/dist/{chunk-MVG67SMN.js → chunk-AQF7JSQR.js} +2 -2
  34. package/dist/{chunk-VTTRWLOR.js → chunk-BP4I7IBK.js} +2 -2
  35. package/dist/{chunk-T7AEAFEE.js → chunk-BTNKPDZM.js} +3 -3
  36. package/dist/{chunk-PCXAUC65.js → chunk-C7WJNVJG.js} +2 -2
  37. package/dist/{chunk-UNR6G3EJ.js → chunk-CV7WXCZD.js} +37 -33
  38. package/dist/chunk-CV7WXCZD.js.map +1 -0
  39. package/dist/{chunk-67XA44HH.js → chunk-CVYXIN5B.js} +3 -3
  40. package/dist/{chunk-BKYQNNO3.js → chunk-D5UDZANO.js} +203 -137
  41. package/dist/chunk-D5UDZANO.js.map +1 -0
  42. package/dist/{chunk-HVR74IBG.js → chunk-DJ23QZIN.js} +2 -2
  43. package/dist/{chunk-BMDF4TLU.js → chunk-DKYEVHZH.js} +7 -10
  44. package/dist/chunk-DKYEVHZH.js.map +1 -0
  45. package/dist/{chunk-N7HOHWKF.js → chunk-DYASJSKI.js} +8 -11
  46. package/dist/{chunk-N7HOHWKF.js.map → chunk-DYASJSKI.js.map} +1 -1
  47. package/dist/{chunk-OAWVM5L6.js → chunk-E6VDPBE3.js} +13 -15
  48. package/dist/chunk-E6VDPBE3.js.map +1 -0
  49. package/dist/{chunk-WBHQX6ME.js → chunk-E6WANBD6.js} +14 -15
  50. package/dist/chunk-E6WANBD6.js.map +1 -0
  51. package/dist/{chunk-VQQUOEON.js → chunk-EG6AQBYW.js} +10 -10
  52. package/dist/{chunk-GWI65NDQ.js → chunk-EJIVFOOQ.js} +6 -6
  53. package/dist/{chunk-YGJZLOSR.js → chunk-GDGTIWB4.js} +2 -2
  54. package/dist/{chunk-TQ7IRULX.js → chunk-GILI76SU.js} +2 -2
  55. package/dist/{chunk-4444HN72.js → chunk-GT72D4FO.js} +3 -3
  56. package/dist/{chunk-OXRPH6AR.js → chunk-GYUCRDAU.js} +15 -12
  57. package/dist/chunk-GYUCRDAU.js.map +1 -0
  58. package/dist/{chunk-JV2CNEX2.js → chunk-HGAR7H5M.js} +239 -188
  59. package/dist/chunk-HGAR7H5M.js.map +1 -0
  60. package/dist/{chunk-SGEI3IJK.js → chunk-HGJC7I5F.js} +2 -2
  61. package/dist/{chunk-C52CJDFO.js → chunk-HJRTFXAA.js} +7 -10
  62. package/dist/chunk-HJRTFXAA.js.map +1 -0
  63. package/dist/chunk-HK67PX4J.js +27 -0
  64. package/dist/chunk-HK67PX4J.js.map +1 -0
  65. package/dist/{chunk-V4M4BZNW.js → chunk-HTHRCGWP.js} +2 -2
  66. package/dist/{chunk-YVOW35HH.js → chunk-HTMQBBF3.js} +5 -7
  67. package/dist/chunk-HTMQBBF3.js.map +1 -0
  68. package/dist/{chunk-QC2HGYFX.js → chunk-HUYNPLTT.js} +4 -4
  69. package/dist/{chunk-APUP2J5M.js → chunk-I26TE27S.js} +7 -7
  70. package/dist/{chunk-DEP24EE3.js → chunk-ICCRAPWD.js} +4 -4
  71. package/dist/{chunk-ES36R3U2.js → chunk-IOURTXTM.js} +52 -65
  72. package/dist/chunk-IOURTXTM.js.map +1 -0
  73. package/dist/{chunk-JU3JKOY5.js → chunk-IPDS2TYW.js} +3 -3
  74. package/dist/{chunk-QJBXB4WU.js → chunk-JHO6FGZX.js} +25 -31
  75. package/dist/chunk-JHO6FGZX.js.map +1 -0
  76. package/dist/{chunk-YDJXGYYA.js → chunk-JIF3IBRK.js} +2 -2
  77. package/dist/{chunk-S6CP5FWF.js → chunk-KBOPY6W2.js} +2 -2
  78. package/dist/{chunk-RVTOCAHQ.js → chunk-KDX6VEYV.js} +2 -2
  79. package/dist/chunk-KDX6VEYV.js.map +1 -0
  80. package/dist/{chunk-H545OIWE.js → chunk-KM6YR5XF.js} +2 -2
  81. package/dist/{chunk-AFSMT3WI.js → chunk-KUXTWVAR.js} +3 -3
  82. package/dist/{chunk-466KGOQB.js → chunk-L2XQ776M.js} +2 -2
  83. package/dist/{chunk-QI2KXQOM.js → chunk-L6FSZFG7.js} +2 -2
  84. package/dist/{chunk-EEBMKMA7.js → chunk-LAFP5H5U.js} +35 -47
  85. package/dist/chunk-LAFP5H5U.js.map +1 -0
  86. package/dist/{chunk-I7JF5TNL.js → chunk-LBDTQM3R.js} +3 -3
  87. package/dist/{chunk-V3OLO4TK.js → chunk-M4IKEOJS.js} +20 -17
  88. package/dist/chunk-M4IKEOJS.js.map +1 -0
  89. package/dist/{chunk-5NKSNW25.js → chunk-MNMYHZVU.js} +2 -2
  90. package/dist/{chunk-IEA3TZJE.js → chunk-MNUTPZRQ.js} +31 -49
  91. package/dist/chunk-MNUTPZRQ.js.map +1 -0
  92. package/dist/{chunk-Z2OFE6IX.js → chunk-MQXYR65M.js} +15 -24
  93. package/dist/chunk-MQXYR65M.js.map +1 -0
  94. package/dist/{chunk-5VVBZ6E4.js → chunk-N2HKBESK.js} +4 -4
  95. package/dist/{chunk-HNOBRU24.js → chunk-N7YAQKAO.js} +2 -2
  96. package/dist/{chunk-6U7M7AX6.js → chunk-NH2MST6J.js} +2 -2
  97. package/dist/{chunk-JAHIL6WO.js → chunk-NRNTXBHT.js} +7 -10
  98. package/dist/chunk-NRNTXBHT.js.map +1 -0
  99. package/dist/{chunk-URVL4EKU.js → chunk-NYSD43OE.js} +15 -24
  100. package/dist/chunk-NYSD43OE.js.map +1 -0
  101. package/dist/{chunk-LUFZPROB.js → chunk-ODVIFBAQ.js} +16 -17
  102. package/dist/chunk-ODVIFBAQ.js.map +1 -0
  103. package/dist/{chunk-SJOK23XT.js → chunk-OIIAXFKM.js} +4 -4
  104. package/dist/chunk-OIIAXFKM.js.map +1 -0
  105. package/dist/{chunk-FSZPQCDR.js → chunk-OQLYMBJI.js} +3 -3
  106. package/dist/{chunk-EMFFWSAD.js → chunk-OWGEHDT2.js} +10 -10
  107. package/dist/chunk-OWGEHDT2.js.map +1 -0
  108. package/dist/{chunk-I64WQSN3.js → chunk-P7L3OQLL.js} +19 -2
  109. package/dist/chunk-P7L3OQLL.js.map +1 -0
  110. package/dist/{chunk-FZ2SSI5R.js → chunk-PG6PFMDC.js} +2 -2
  111. package/dist/{chunk-2RGQNFJB.js → chunk-PZSS7MUM.js} +11 -9
  112. package/dist/chunk-PZSS7MUM.js.map +1 -0
  113. package/dist/{chunk-UKKBKEUI.js → chunk-QPIUFRUC.js} +3 -3
  114. package/dist/{chunk-RJTGXZT6.js → chunk-REL4OLWW.js} +2 -2
  115. package/dist/{chunk-N4GAJSRQ.js → chunk-RNSC2ARR.js} +3 -3
  116. package/dist/{chunk-RWPNRCK2.js → chunk-RORE2GX2.js} +2 -2
  117. package/dist/{chunk-YH65A6WM.js → chunk-RSFG7XHM.js} +2 -2
  118. package/dist/{chunk-QLMUW7OC.js → chunk-S55DBWRC.js} +2 -2
  119. package/dist/{chunk-AKFNLRA2.js → chunk-SA77BQ7F.js} +27 -143
  120. package/dist/chunk-SA77BQ7F.js.map +1 -0
  121. package/dist/{chunk-RBTZLXZJ.js → chunk-SFD2LL7G.js} +2 -2
  122. package/dist/{chunk-XTC5EKST.js → chunk-SG3OQCRG.js} +3 -3
  123. package/dist/{chunk-JN64WH3U.js → chunk-T7IR4N2S.js} +1 -1
  124. package/dist/{chunk-JN64WH3U.js.map → chunk-T7IR4N2S.js.map} +1 -1
  125. package/dist/{chunk-P5ARVLNW.js → chunk-TC6MBEGH.js} +11 -8
  126. package/dist/chunk-TC6MBEGH.js.map +1 -0
  127. package/dist/{chunk-D6E5N2O3.js → chunk-TENLCGYD.js} +12 -10
  128. package/dist/{chunk-D6E5N2O3.js.map → chunk-TENLCGYD.js.map} +1 -1
  129. package/dist/{chunk-X2FXEEQT.js → chunk-VH2W6BOG.js} +7 -7
  130. package/dist/{chunk-FHFMWYGM.js → chunk-VHVMJWM6.js} +7 -7
  131. package/dist/{chunk-FHFMWYGM.js.map → chunk-VHVMJWM6.js.map} +1 -1
  132. package/dist/{chunk-QTDAPDDV.js → chunk-VJ4B3ZXY.js} +15 -3
  133. package/dist/chunk-VJ4B3ZXY.js.map +1 -0
  134. package/dist/{chunk-RDPZMUCX.js → chunk-W6NFONOV.js} +2 -2
  135. package/dist/{chunk-Z3U2YVNM.js → chunk-WBNXZU42.js} +4 -4
  136. package/dist/{chunk-7EIPPOEL.js → chunk-WKD5ESK4.js} +2 -2
  137. package/dist/{chunk-6VAM5THN.js → chunk-YEZW6UJA.js} +15 -15
  138. package/dist/{chunk-6VAM5THN.js.map → chunk-YEZW6UJA.js.map} +1 -1
  139. package/dist/chunk-YOT7D63X.js +190 -0
  140. package/dist/chunk-YOT7D63X.js.map +1 -0
  141. package/dist/{chunk-6JRIYG2C.js → chunk-YVTADFTC.js} +14 -15
  142. package/dist/chunk-YVTADFTC.js.map +1 -0
  143. package/dist/{chunk-C3YP5KKS.js → chunk-ZBDWKPBW.js} +15 -16
  144. package/dist/chunk-ZBDWKPBW.js.map +1 -0
  145. package/dist/{chunk-NWPR2V5W.js → chunk-ZPMXYPBR.js} +2 -2
  146. package/dist/{chunk-RGPB25DB.js → chunk-ZVAFJQZB.js} +25 -19
  147. package/dist/chunk-ZVAFJQZB.js.map +1 -0
  148. package/dist/{chunk-TQLYMYOK.js → chunk-ZWKF3CD3.js} +2 -2
  149. package/dist/classified/index.js +5 -5
  150. package/dist/{classified-marker-W2PUYC3K.js → classified-marker-PMDG3O36.js} +3 -3
  151. package/dist/collection-facade-ZJK5EB33.js +45 -0
  152. package/dist/{computed-5E2OM6EN.js → computed-UHCBRILW.js} +3 -3
  153. package/dist/consent/index.js +9 -9
  154. package/dist/cover/index.js +10 -3
  155. package/dist/custody/index.js +11 -11
  156. package/dist/{dead-filter-MVFQCLXY.js → dead-filter-777VWDER.js} +2 -2
  157. package/dist/debug/index.js +1 -1
  158. package/dist/{delegation-I7XXO5LL.js → delegation-4R5DU23X.js} +23 -24
  159. package/dist/delegation-4R5DU23X.js.map +1 -0
  160. package/dist/derivations/index.js +12 -12
  161. package/dist/derive-3WAS2G5T.js +22 -0
  162. package/dist/directory/index.js +10 -10
  163. package/dist/{dispatch-PY5RUHJF.js → dispatch-3EN6YCCS.js} +18 -18
  164. package/dist/{dispatch-K3MDLCXG.js → dispatch-ASL257KG.js} +5 -5
  165. package/dist/{enclave-77K7UDLS.js → enclave-ZJS5R5RH.js} +17 -9
  166. package/dist/executor-GPM6DNWV.js +9 -0
  167. package/dist/executor-LJSMZ4IB.js +9 -0
  168. package/dist/executor-SHUVRZUJ.js +29 -0
  169. package/dist/export-accessible-GNY6DC6U.js +23 -0
  170. package/dist/extract-partition-BG3AF4NZ.js +38 -0
  171. package/dist/{fanout-sidecar-JI3MUPQV.js → fanout-sidecar-TFPWDOME.js} +18 -27
  172. package/dist/fanout-sidecar-TFPWDOME.js.map +1 -0
  173. package/dist/find-KX6OKHDP.js +11 -0
  174. package/dist/forget/index.js +9 -9
  175. package/dist/guards/index.js +4 -4
  176. package/dist/history/index.js +23 -20
  177. package/dist/history/index.js.map +1 -1
  178. package/dist/i18n/index.js +14 -14
  179. package/dist/index.d.ts +16 -0
  180. package/dist/index.js +156 -154
  181. package/dist/index.js.map +1 -1
  182. package/dist/indexing/index.js +2 -2
  183. package/dist/introspection/index.js +11 -11
  184. package/dist/issue-PAUM7NCX.js +19 -0
  185. package/dist/kernel/enclave/index.d.ts +6 -5
  186. package/dist/kernel/enclave/record-aad.d.ts +79 -7
  187. package/dist/kernel/enclave/record-envelope.d.ts +54 -7
  188. package/dist/kernel/enclave/record-keys/envelope-body.d.ts +34 -2
  189. package/dist/kernel/enclave/record-keys/lifecycle.d.ts +22 -5
  190. package/dist/kernel/enclave/record-keys/record-codec.d.ts +47 -14
  191. package/dist/kernel/enclave/record-keys/rekey.d.ts +8 -2
  192. package/dist/kernel/enclave/record-keys/tombstone.d.ts +3 -3
  193. package/dist/kernel/errors.d.ts +17 -1
  194. package/dist/kernel/merge-authority.d.ts +25 -0
  195. package/dist/kernel/tier-visibility.d.ts +15 -0
  196. package/dist/kernel/types.d.ts +8 -0
  197. package/dist/kernel/vault.d.ts +1 -1
  198. package/dist/kernel/via/index.d.ts +11 -2
  199. package/dist/{ledger-3RDSSMGD.js → ledger-62CEPRPX.js} +11 -11
  200. package/dist/liberate-7QY7TULB.js +24 -0
  201. package/dist/{link-set-VJIWWUZO.js → link-set-ZRYNUHN3.js} +11 -11
  202. package/dist/{managed-secret-XGGJ3BC3.js → managed-secret-T2WB4O47.js} +11 -3
  203. package/dist/materialized-views/index.js +22 -22
  204. package/dist/money/index.js +4 -4
  205. package/dist/noydb-VZIVPDXG.js +84 -0
  206. package/dist/overlay-views/index.js +4 -4
  207. package/dist/periods/index.js +12 -12
  208. package/dist/pod/index.js +58 -57
  209. package/dist/{pod-handle-7BVSWP5B.js → pod-handle-UPLXOGIK.js} +11 -11
  210. package/dist/{pod-handle-7BVSWP5B.js.map → pod-handle-UPLXOGIK.js.map} +1 -1
  211. package/dist/policy/index.js +11 -4
  212. package/dist/port/with/merge-authority.d.ts +52 -0
  213. package/dist/port/with/strategies.d.ts +2 -0
  214. package/dist/portability/index.js +8 -8
  215. package/dist/{post-register-W3T4U57K.js → post-register-J4DEVNXK.js} +6 -6
  216. package/dist/query/index.js +3 -3
  217. package/dist/reduce/index.js +4 -4
  218. package/dist/register-UPD4GKBC.js +32 -0
  219. package/dist/registry-7KX2HWFS.js +18 -0
  220. package/dist/registry-PKYYEHEW.js +9 -0
  221. package/dist/registry-XHCYMD34.js +19 -0
  222. package/dist/registry-XIRTZHPC.js +9 -0
  223. package/dist/request-withdrawal-XZSESWZV.js +31 -0
  224. package/dist/{reveal-G7W7N3AZ.js → reveal-525XWQU2.js} +6 -6
  225. package/dist/revoke-QI7YHQ2B.js +24 -0
  226. package/dist/satellites/index.js +1 -1
  227. package/dist/schema-update/index.js +2 -2
  228. package/dist/sealed-record/index.js +20 -13
  229. package/dist/sealed-record/index.js.map +1 -1
  230. package/dist/search/index.js +4 -4
  231. package/dist/{seed-2FUULTQV.js → seed-2KZDTFS4.js} +19 -22
  232. package/dist/seed-2KZDTFS4.js.map +1 -0
  233. package/dist/sequence/index.js +9 -9
  234. package/dist/session/index.js +9 -9
  235. package/dist/shadow/index.js +2 -2
  236. package/dist/signer-BZWDPGGN.js +25 -0
  237. package/dist/snapshots/index.js +10 -10
  238. package/dist/{stale-42FPALK2.js → stale-PQYPVCER.js} +10 -10
  239. package/dist/storage-MJHKNQRO.js +23 -0
  240. package/dist/{store-coordination-provider-Y3VXBHPL.js → store-coordination-provider-TFHZLKHG.js} +18 -12
  241. package/dist/store-coordination-provider-TFHZLKHG.js.map +1 -0
  242. package/dist/sync/index.js +11 -11
  243. package/dist/team/index.js +15 -14
  244. package/dist/tiers/index.js +11 -10
  245. package/dist/to/index.js +1 -1
  246. package/dist/transactions/index.js +3 -3
  247. package/dist/util/index.js +1 -1
  248. package/dist/vault-head/index.js +111 -0
  249. package/dist/vault-head/index.js.map +1 -0
  250. package/dist/{verify-SHJXGZGM.js → verify-J6DEOXP3.js} +5 -5
  251. package/dist/walk-BX4NSB5L.js +21 -0
  252. package/dist/with-audit/periods/periods.d.ts +1 -1
  253. package/dist/with-audit/periods/strategy.d.ts +1 -1
  254. package/dist/with-audit/sealed-record/index.d.ts +13 -0
  255. package/dist/with-audit/tiers/index.d.ts +17 -2
  256. package/dist/with-commit/history/history.d.ts +26 -0
  257. package/dist/with-commit/history/index.d.ts +2 -1
  258. package/dist/with-commit/history/ledger/store.d.ts +7 -1
  259. package/dist/with-commit/history/strategy.d.ts +11 -1
  260. package/dist/with-commit/vault-head/active.d.ts +2 -0
  261. package/dist/with-commit/vault-head/head.d.ts +28 -0
  262. package/dist/with-commit/vault-head/index.d.ts +10 -0
  263. package/dist/with-commit/vault-head/strategy.d.ts +127 -0
  264. package/dist/with-commit/vault-head/verify.d.ts +50 -0
  265. package/dist/with-party/directory/cover/storage.d.ts +0 -8
  266. package/dist/with-party/directory/storage.d.ts +0 -11
  267. package/dist/with-party/directory/visibility.d.ts +0 -21
  268. package/dist/with-party/policy/storage.d.ts +0 -10
  269. package/dist/with-party/team/tiers.d.ts +2 -1
  270. package/dist/with-sync/engine.d.ts +33 -5
  271. package/dist/with-sync/strategy.d.ts +3 -0
  272. package/dist/withdraw-accessible-K4CPMH4C.js +28 -0
  273. package/package.json +7 -3
  274. package/dist/api-AEIN3KAQ.js +0 -20
  275. package/dist/chunk-2RGQNFJB.js.map +0 -1
  276. package/dist/chunk-4GR422R5.js.map +0 -1
  277. package/dist/chunk-5UGEGEIJ.js.map +0 -1
  278. package/dist/chunk-6JRIYG2C.js.map +0 -1
  279. package/dist/chunk-7TZTQS4H.js.map +0 -1
  280. package/dist/chunk-AFHQATY4.js +0 -47
  281. package/dist/chunk-AFHQATY4.js.map +0 -1
  282. package/dist/chunk-AKFNLRA2.js.map +0 -1
  283. package/dist/chunk-BKYQNNO3.js.map +0 -1
  284. package/dist/chunk-BMDF4TLU.js.map +0 -1
  285. package/dist/chunk-C3YP5KKS.js.map +0 -1
  286. package/dist/chunk-C52CJDFO.js.map +0 -1
  287. package/dist/chunk-EEBMKMA7.js.map +0 -1
  288. package/dist/chunk-EMFFWSAD.js.map +0 -1
  289. package/dist/chunk-ES36R3U2.js.map +0 -1
  290. package/dist/chunk-I64WQSN3.js.map +0 -1
  291. package/dist/chunk-IEA3TZJE.js.map +0 -1
  292. package/dist/chunk-JAHIL6WO.js.map +0 -1
  293. package/dist/chunk-JV2CNEX2.js.map +0 -1
  294. package/dist/chunk-LUFZPROB.js.map +0 -1
  295. package/dist/chunk-OAWVM5L6.js.map +0 -1
  296. package/dist/chunk-OXRPH6AR.js.map +0 -1
  297. package/dist/chunk-P5ARVLNW.js.map +0 -1
  298. package/dist/chunk-QJBXB4WU.js.map +0 -1
  299. package/dist/chunk-QKRJUPAC.js.map +0 -1
  300. package/dist/chunk-QTDAPDDV.js.map +0 -1
  301. package/dist/chunk-RGPB25DB.js.map +0 -1
  302. package/dist/chunk-RVTOCAHQ.js.map +0 -1
  303. package/dist/chunk-SJOK23XT.js.map +0 -1
  304. package/dist/chunk-UNR6G3EJ.js.map +0 -1
  305. package/dist/chunk-URVL4EKU.js.map +0 -1
  306. package/dist/chunk-V3OLO4TK.js.map +0 -1
  307. package/dist/chunk-VEA35MQP.js.map +0 -1
  308. package/dist/chunk-VUQZYVYX.js.map +0 -1
  309. package/dist/chunk-WBHQX6ME.js.map +0 -1
  310. package/dist/chunk-YVOW35HH.js.map +0 -1
  311. package/dist/chunk-Z2OFE6IX.js.map +0 -1
  312. package/dist/collection-facade-CRXN72DU.js +0 -45
  313. package/dist/delegation-I7XXO5LL.js.map +0 -1
  314. package/dist/derive-NL7X2EFT.js +0 -22
  315. package/dist/executor-FNGVGDW3.js +0 -29
  316. package/dist/executor-PCZN2LTY.js +0 -9
  317. package/dist/executor-WFTHIDT7.js +0 -9
  318. package/dist/export-accessible-QWUYY2OY.js +0 -23
  319. package/dist/extract-partition-CGCURVS4.js +0 -38
  320. package/dist/fanout-sidecar-JI3MUPQV.js.map +0 -1
  321. package/dist/find-VBNMB42N.js +0 -11
  322. package/dist/issue-7IPUNAFA.js +0 -19
  323. package/dist/liberate-2DNJ7AJ7.js +0 -24
  324. package/dist/noydb-CLQP36JR.js +0 -83
  325. package/dist/register-YUVC4G27.js +0 -32
  326. package/dist/registry-BPKJX562.js +0 -19
  327. package/dist/registry-E4FM32KD.js +0 -9
  328. package/dist/registry-HACMULJJ.js +0 -9
  329. package/dist/registry-HRFRBFDV.js +0 -18
  330. package/dist/request-withdrawal-CXTQLMJK.js +0 -31
  331. package/dist/revoke-ZX3TZUI6.js +0 -24
  332. package/dist/seed-2FUULTQV.js.map +0 -1
  333. package/dist/signer-FPUIX63Z.js +0 -25
  334. package/dist/storage-6QODT6K5.js +0 -23
  335. package/dist/store-coordination-provider-Y3VXBHPL.js.map +0 -1
  336. package/dist/walk-5PGJI3ND.js +0 -21
  337. package/dist/withdraw-accessible-PFCQQGNP.js +0 -28
  338. /package/dist/{api-AEIN3KAQ.js.map → api-Z5VHH24A.js.map} +0 -0
  339. /package/dist/{chunk-5SK5AFYH.js.map → chunk-2E2MKBMI.js.map} +0 -0
  340. /package/dist/{chunk-7LLKPODL.js.map → chunk-437FDIHO.js.map} +0 -0
  341. /package/dist/{chunk-QWNYUTI2.js.map → chunk-5EJQNA26.js.map} +0 -0
  342. /package/dist/{chunk-C66T4W5P.js.map → chunk-5J5TTSGW.js.map} +0 -0
  343. /package/dist/{chunk-JHYISAID.js.map → chunk-5JT4Y5UE.js.map} +0 -0
  344. /package/dist/{chunk-IRHOQFA7.js.map → chunk-7RCZ436O.js.map} +0 -0
  345. /package/dist/{chunk-42IDLU5V.js.map → chunk-A3NTKHDB.js.map} +0 -0
  346. /package/dist/{chunk-3JQVA527.js.map → chunk-A4K5GLE6.js.map} +0 -0
  347. /package/dist/{chunk-K2MR7ROB.js.map → chunk-APV42WPR.js.map} +0 -0
  348. /package/dist/{chunk-MVG67SMN.js.map → chunk-AQF7JSQR.js.map} +0 -0
  349. /package/dist/{chunk-VTTRWLOR.js.map → chunk-BP4I7IBK.js.map} +0 -0
  350. /package/dist/{chunk-T7AEAFEE.js.map → chunk-BTNKPDZM.js.map} +0 -0
  351. /package/dist/{chunk-PCXAUC65.js.map → chunk-C7WJNVJG.js.map} +0 -0
  352. /package/dist/{chunk-67XA44HH.js.map → chunk-CVYXIN5B.js.map} +0 -0
  353. /package/dist/{chunk-HVR74IBG.js.map → chunk-DJ23QZIN.js.map} +0 -0
  354. /package/dist/{chunk-VQQUOEON.js.map → chunk-EG6AQBYW.js.map} +0 -0
  355. /package/dist/{chunk-GWI65NDQ.js.map → chunk-EJIVFOOQ.js.map} +0 -0
  356. /package/dist/{chunk-YGJZLOSR.js.map → chunk-GDGTIWB4.js.map} +0 -0
  357. /package/dist/{chunk-TQ7IRULX.js.map → chunk-GILI76SU.js.map} +0 -0
  358. /package/dist/{chunk-4444HN72.js.map → chunk-GT72D4FO.js.map} +0 -0
  359. /package/dist/{chunk-SGEI3IJK.js.map → chunk-HGJC7I5F.js.map} +0 -0
  360. /package/dist/{chunk-V4M4BZNW.js.map → chunk-HTHRCGWP.js.map} +0 -0
  361. /package/dist/{chunk-QC2HGYFX.js.map → chunk-HUYNPLTT.js.map} +0 -0
  362. /package/dist/{chunk-APUP2J5M.js.map → chunk-I26TE27S.js.map} +0 -0
  363. /package/dist/{chunk-DEP24EE3.js.map → chunk-ICCRAPWD.js.map} +0 -0
  364. /package/dist/{chunk-JU3JKOY5.js.map → chunk-IPDS2TYW.js.map} +0 -0
  365. /package/dist/{chunk-YDJXGYYA.js.map → chunk-JIF3IBRK.js.map} +0 -0
  366. /package/dist/{chunk-S6CP5FWF.js.map → chunk-KBOPY6W2.js.map} +0 -0
  367. /package/dist/{chunk-H545OIWE.js.map → chunk-KM6YR5XF.js.map} +0 -0
  368. /package/dist/{chunk-AFSMT3WI.js.map → chunk-KUXTWVAR.js.map} +0 -0
  369. /package/dist/{chunk-466KGOQB.js.map → chunk-L2XQ776M.js.map} +0 -0
  370. /package/dist/{chunk-QI2KXQOM.js.map → chunk-L6FSZFG7.js.map} +0 -0
  371. /package/dist/{chunk-I7JF5TNL.js.map → chunk-LBDTQM3R.js.map} +0 -0
  372. /package/dist/{chunk-5NKSNW25.js.map → chunk-MNMYHZVU.js.map} +0 -0
  373. /package/dist/{chunk-5VVBZ6E4.js.map → chunk-N2HKBESK.js.map} +0 -0
  374. /package/dist/{chunk-HNOBRU24.js.map → chunk-N7YAQKAO.js.map} +0 -0
  375. /package/dist/{chunk-6U7M7AX6.js.map → chunk-NH2MST6J.js.map} +0 -0
  376. /package/dist/{chunk-FSZPQCDR.js.map → chunk-OQLYMBJI.js.map} +0 -0
  377. /package/dist/{chunk-FZ2SSI5R.js.map → chunk-PG6PFMDC.js.map} +0 -0
  378. /package/dist/{chunk-UKKBKEUI.js.map → chunk-QPIUFRUC.js.map} +0 -0
  379. /package/dist/{chunk-RJTGXZT6.js.map → chunk-REL4OLWW.js.map} +0 -0
  380. /package/dist/{chunk-N4GAJSRQ.js.map → chunk-RNSC2ARR.js.map} +0 -0
  381. /package/dist/{chunk-RWPNRCK2.js.map → chunk-RORE2GX2.js.map} +0 -0
  382. /package/dist/{chunk-YH65A6WM.js.map → chunk-RSFG7XHM.js.map} +0 -0
  383. /package/dist/{chunk-QLMUW7OC.js.map → chunk-S55DBWRC.js.map} +0 -0
  384. /package/dist/{chunk-RBTZLXZJ.js.map → chunk-SFD2LL7G.js.map} +0 -0
  385. /package/dist/{chunk-XTC5EKST.js.map → chunk-SG3OQCRG.js.map} +0 -0
  386. /package/dist/{chunk-X2FXEEQT.js.map → chunk-VH2W6BOG.js.map} +0 -0
  387. /package/dist/{chunk-RDPZMUCX.js.map → chunk-W6NFONOV.js.map} +0 -0
  388. /package/dist/{chunk-Z3U2YVNM.js.map → chunk-WBNXZU42.js.map} +0 -0
  389. /package/dist/{chunk-7EIPPOEL.js.map → chunk-WKD5ESK4.js.map} +0 -0
  390. /package/dist/{chunk-NWPR2V5W.js.map → chunk-ZPMXYPBR.js.map} +0 -0
  391. /package/dist/{chunk-TQLYMYOK.js.map → chunk-ZWKF3CD3.js.map} +0 -0
  392. /package/dist/{classified-marker-W2PUYC3K.js.map → classified-marker-PMDG3O36.js.map} +0 -0
  393. /package/dist/{collection-facade-CRXN72DU.js.map → collection-facade-ZJK5EB33.js.map} +0 -0
  394. /package/dist/{computed-5E2OM6EN.js.map → computed-UHCBRILW.js.map} +0 -0
  395. /package/dist/{dead-filter-MVFQCLXY.js.map → dead-filter-777VWDER.js.map} +0 -0
  396. /package/dist/{derive-NL7X2EFT.js.map → derive-3WAS2G5T.js.map} +0 -0
  397. /package/dist/{dispatch-PY5RUHJF.js.map → dispatch-3EN6YCCS.js.map} +0 -0
  398. /package/dist/{dispatch-K3MDLCXG.js.map → dispatch-ASL257KG.js.map} +0 -0
  399. /package/dist/{enclave-77K7UDLS.js.map → enclave-ZJS5R5RH.js.map} +0 -0
  400. /package/dist/{executor-FNGVGDW3.js.map → executor-GPM6DNWV.js.map} +0 -0
  401. /package/dist/{executor-PCZN2LTY.js.map → executor-LJSMZ4IB.js.map} +0 -0
  402. /package/dist/{executor-WFTHIDT7.js.map → executor-SHUVRZUJ.js.map} +0 -0
  403. /package/dist/{export-accessible-QWUYY2OY.js.map → export-accessible-GNY6DC6U.js.map} +0 -0
  404. /package/dist/{extract-partition-CGCURVS4.js.map → extract-partition-BG3AF4NZ.js.map} +0 -0
  405. /package/dist/{find-VBNMB42N.js.map → find-KX6OKHDP.js.map} +0 -0
  406. /package/dist/{issue-7IPUNAFA.js.map → issue-PAUM7NCX.js.map} +0 -0
  407. /package/dist/{ledger-3RDSSMGD.js.map → ledger-62CEPRPX.js.map} +0 -0
  408. /package/dist/{liberate-2DNJ7AJ7.js.map → liberate-7QY7TULB.js.map} +0 -0
  409. /package/dist/{link-set-VJIWWUZO.js.map → link-set-ZRYNUHN3.js.map} +0 -0
  410. /package/dist/{managed-secret-XGGJ3BC3.js.map → managed-secret-T2WB4O47.js.map} +0 -0
  411. /package/dist/{noydb-CLQP36JR.js.map → noydb-VZIVPDXG.js.map} +0 -0
  412. /package/dist/{post-register-W3T4U57K.js.map → post-register-J4DEVNXK.js.map} +0 -0
  413. /package/dist/{register-YUVC4G27.js.map → register-UPD4GKBC.js.map} +0 -0
  414. /package/dist/{registry-BPKJX562.js.map → registry-7KX2HWFS.js.map} +0 -0
  415. /package/dist/{registry-E4FM32KD.js.map → registry-PKYYEHEW.js.map} +0 -0
  416. /package/dist/{registry-HACMULJJ.js.map → registry-XHCYMD34.js.map} +0 -0
  417. /package/dist/{registry-HRFRBFDV.js.map → registry-XIRTZHPC.js.map} +0 -0
  418. /package/dist/{request-withdrawal-CXTQLMJK.js.map → request-withdrawal-XZSESWZV.js.map} +0 -0
  419. /package/dist/{reveal-G7W7N3AZ.js.map → reveal-525XWQU2.js.map} +0 -0
  420. /package/dist/{revoke-ZX3TZUI6.js.map → revoke-QI7YHQ2B.js.map} +0 -0
  421. /package/dist/{signer-FPUIX63Z.js.map → signer-BZWDPGGN.js.map} +0 -0
  422. /package/dist/{stale-42FPALK2.js.map → stale-PQYPVCER.js.map} +0 -0
  423. /package/dist/{storage-6QODT6K5.js.map → storage-MJHKNQRO.js.map} +0 -0
  424. /package/dist/{verify-SHJXGZGM.js.map → verify-J6DEOXP3.js.map} +0 -0
  425. /package/dist/{walk-5PGJI3ND.js.map → walk-BX4NSB5L.js.map} +0 -0
  426. /package/dist/{withdraw-accessible-PFCQQGNP.js.map → withdraw-accessible-K4CPMH4C.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SealedRecordNotEnabledError
3
- } from "./chunk-I64WQSN3.js";
3
+ } from "./chunk-P7L3OQLL.js";
4
4
 
5
5
  // src/with-audit/sealed-record/strategy.ts
6
6
  var NO_SEALED_RECORD = {
@@ -18,4 +18,4 @@ var NO_SEALED_RECORD = {
18
18
  export {
19
19
  NO_SEALED_RECORD
20
20
  };
21
- //# sourceMappingURL=chunk-QLMUW7OC.js.map
21
+ //# sourceMappingURL=chunk-S55DBWRC.js.map
@@ -1,130 +1,16 @@
1
1
  import {
2
- liveRecordIsElevated
3
- } from "./chunk-AMKCY454.js";
2
+ getHistory,
3
+ historyIdentity
4
+ } from "./chunk-YOT7D63X.js";
4
5
  import {
5
- isRewrappedUnder,
6
- openEnvelopeJson,
7
- rewrapEnvelope
8
- } from "./chunk-BKYQNNO3.js";
6
+ liveRecordIsElevated
7
+ } from "./chunk-47ZFULEZ.js";
9
8
  import {
10
- buildRecordEnvelope,
11
- isTombstone,
12
- isTombstoneShape
13
- } from "./chunk-RGPB25DB.js";
9
+ openEnvelopeJson
10
+ } from "./chunk-D5UDZANO.js";
14
11
  import {
15
12
  ReadOnlyAtInstantError
16
- } from "./chunk-I64WQSN3.js";
17
-
18
- // src/with-commit/history/history.ts
19
- var HISTORY_COLLECTION = "_history";
20
- var VERSION_PAD = 10;
21
- function historyId(collection, recordId, version) {
22
- return `${collection}:${recordId}:${String(version).padStart(VERSION_PAD, "0")}`;
23
- }
24
- function matchesPrefix(id, collection, recordId) {
25
- if (recordId) {
26
- return id.startsWith(`${collection}:${recordId}:`);
27
- }
28
- return id.startsWith(`${collection}:`);
29
- }
30
- async function saveHistory(adapter, vault, collection, recordId, envelope) {
31
- const id = historyId(collection, recordId, envelope._v);
32
- await adapter.put(vault, HISTORY_COLLECTION, id, envelope);
33
- }
34
- async function getHistory(adapter, vault, collection, recordId, options) {
35
- const allIds = await adapter.list(vault, HISTORY_COLLECTION);
36
- const matchingIds = allIds.filter((id) => matchesPrefix(id, collection, recordId)).sort().reverse();
37
- const entries = [];
38
- for (const id of matchingIds) {
39
- const envelope = await adapter.get(vault, HISTORY_COLLECTION, id);
40
- if (!envelope) continue;
41
- if (options?.from && envelope._ts < options.from) continue;
42
- if (options?.to && envelope._ts > options.to) continue;
43
- entries.push(envelope);
44
- if (options?.limit && entries.length >= options.limit) break;
45
- }
46
- return entries;
47
- }
48
- async function getVersionEnvelope(adapter, vault, collection, recordId, version) {
49
- const id = historyId(collection, recordId, version);
50
- return adapter.get(vault, HISTORY_COLLECTION, id);
51
- }
52
- async function pruneHistory(adapter, vault, collection, recordId, options) {
53
- const allIds = await adapter.list(vault, HISTORY_COLLECTION);
54
- const matchingIds = allIds.filter((id) => recordId ? matchesPrefix(id, collection, recordId) : matchesPrefix(id, collection)).sort();
55
- let toDelete = [];
56
- if (options.keepVersions !== void 0) {
57
- const keep = options.keepVersions;
58
- if (matchingIds.length > keep) {
59
- toDelete = matchingIds.slice(0, matchingIds.length - keep);
60
- }
61
- }
62
- if (options.beforeDate) {
63
- for (const id of matchingIds) {
64
- if (toDelete.includes(id)) continue;
65
- const envelope = await adapter.get(vault, HISTORY_COLLECTION, id);
66
- if (envelope && envelope._ts < options.beforeDate) {
67
- toDelete.push(id);
68
- }
69
- }
70
- }
71
- const uniqueDeletes = [...new Set(toDelete)];
72
- for (const id of uniqueDeletes) {
73
- await adapter.delete(vault, HISTORY_COLLECTION, id);
74
- }
75
- return uniqueDeletes.length;
76
- }
77
- async function clearHistory(adapter, vault, collection, recordId) {
78
- const allIds = await adapter.list(vault, HISTORY_COLLECTION);
79
- let toDelete;
80
- if (collection && recordId) {
81
- toDelete = allIds.filter((id) => matchesPrefix(id, collection, recordId));
82
- } else if (collection) {
83
- toDelete = allIds.filter((id) => matchesPrefix(id, collection));
84
- } else {
85
- toDelete = allIds;
86
- }
87
- for (const id of toDelete) {
88
- await adapter.delete(vault, HISTORY_COLLECTION, id);
89
- }
90
- return toDelete.length;
91
- }
92
- async function tombstoneHistory(adapter, vault, collection, recordId, actor, encrypted) {
93
- const allIds = await adapter.list(vault, HISTORY_COLLECTION);
94
- const matchingIds = allIds.filter((id) => matchesPrefix(id, collection, recordId));
95
- const now = (/* @__PURE__ */ new Date()).toISOString();
96
- let count = 0;
97
- for (const id of matchingIds) {
98
- const env = await adapter.get(vault, HISTORY_COLLECTION, id);
99
- if (!env) continue;
100
- if (isTombstone(env, encrypted)) continue;
101
- const tombstone = buildRecordEnvelope(
102
- { collection: HISTORY_COLLECTION, id },
103
- { version: env._v, ts: now, iv: "", data: "", ...actor ? { by: actor } : {} }
104
- );
105
- await adapter.put(vault, HISTORY_COLLECTION, id, tombstone);
106
- count++;
107
- }
108
- return count;
109
- }
110
- async function rewrapHistory(adapter, vault, collection, recordId, fromDek, toDek, tier0Dek) {
111
- const allIds = await adapter.list(vault, HISTORY_COLLECTION);
112
- const matchingIds = allIds.filter((id) => matchesPrefix(id, collection, recordId));
113
- for (const id of matchingIds) {
114
- const env = await adapter.get(vault, HISTORY_COLLECTION, id);
115
- if (!env) continue;
116
- if (isTombstoneShape(env)) continue;
117
- if (await isRewrappedUnder(env, toDek)) continue;
118
- let next;
119
- try {
120
- next = await rewrapEnvelope(env, fromDek, toDek);
121
- } catch (err) {
122
- if (!tier0Dek) throw err;
123
- next = await rewrapEnvelope(env, tier0Dek, toDek);
124
- }
125
- await adapter.put(vault, HISTORY_COLLECTION, id, next);
126
- }
127
- }
13
+ } from "./chunk-P7L3OQLL.js";
128
14
 
129
15
  // src/with-commit/history/time-machine.ts
130
16
  var VaultInstant = class {
@@ -164,9 +50,9 @@ var CollectionInstant = class {
164
50
  * DEK directly.
165
51
  */
166
52
  async get(id) {
167
- const envelope = await this.resolveVisibleEnvelope(id);
168
- if (!envelope) return null;
169
- const plaintext = this.engine.encrypted ? await openEnvelopeJson(envelope, await this.engine.getDEK(this.name)) : envelope._data;
53
+ const found = await this.resolveVisibleEnvelope(id);
54
+ if (!found) return null;
55
+ const plaintext = this.engine.encrypted ? await openEnvelopeJson(found.ref, found.envelope, await this.engine.getDEK(this.name)) : found.envelope._data;
170
56
  return JSON.parse(plaintext);
171
57
  }
172
58
  /**
@@ -213,10 +99,10 @@ var CollectionInstant = class {
213
99
  * just a decrypt failure.
214
100
  */
215
101
  async resolveVisibleEnvelope(id) {
216
- const envelope = await this.resolveEnvelope(id);
217
- if (!envelope || (envelope._tier ?? 0) > 0) return null;
102
+ const found = await this.resolveEnvelope(id);
103
+ if (!found || (found.envelope._tier ?? 0) > 0) return null;
218
104
  if (await liveRecordIsElevated(this.engine.adapter, this.engine.name, this.name, id)) return null;
219
- return envelope;
105
+ return found;
220
106
  }
221
107
  /**
222
108
  * Return the envelope that represents the record's state at
@@ -277,12 +163,14 @@ var CollectionInstant = class {
277
163
  );
278
164
  const live = await this.engine.adapter.get(this.engine.name, this.name, id);
279
165
  const byVersion = /* @__PURE__ */ new Map();
280
- for (const e of history) byVersion.set(e._v, e);
281
- if (live) byVersion.set(live._v, live);
166
+ for (const e of history) {
167
+ byVersion.set(e._v, { envelope: e, ref: historyIdentity(this.name, id, e._v) });
168
+ }
169
+ if (live) byVersion.set(live._v, { envelope: live, ref: { collection: this.name, id } });
282
170
  const sorted = [...byVersion.values()].sort(
283
- (a, b) => a._ts < b._ts ? 1 : a._ts > b._ts ? -1 : 0
171
+ (a, b) => a.envelope._ts < b.envelope._ts ? 1 : a.envelope._ts > b.envelope._ts ? -1 : 0
284
172
  );
285
- return sorted.find((e) => e._ts <= this.targetTs) ?? null;
173
+ return sorted.find((e) => e.envelope._ts <= this.targetTs) ?? null;
286
174
  }
287
175
  /**
288
176
  * Fetch the envelope for a specific version. The live record (most
@@ -294,9 +182,12 @@ var CollectionInstant = class {
294
182
  */
295
183
  async loadVersion(id, version) {
296
184
  const live = await this.engine.adapter.get(this.engine.name, this.name, id);
297
- if (live && live._v === version) return live;
298
- const historyId2 = `${this.name}:${id}:${String(version).padStart(10, "0")}`;
299
- return await this.engine.adapter.get(this.engine.name, "_history", historyId2);
185
+ if (live && live._v === version) {
186
+ return { envelope: live, ref: { collection: this.name, id } };
187
+ }
188
+ const ref = historyIdentity(this.name, id, version);
189
+ const envelope = await this.engine.adapter.get(this.engine.name, ref.collection, ref.id);
190
+ return envelope ? { envelope, ref } : null;
300
191
  }
301
192
  };
302
193
  async function collectHistoryIds(adapter, vault, collection) {
@@ -314,14 +205,7 @@ async function collectHistoryIds(adapter, vault, collection) {
314
205
  }
315
206
 
316
207
  export {
317
- saveHistory,
318
- getHistory,
319
- getVersionEnvelope,
320
- pruneHistory,
321
- clearHistory,
322
- tombstoneHistory,
323
- rewrapHistory,
324
208
  VaultInstant,
325
209
  CollectionInstant
326
210
  };
327
- //# sourceMappingURL=chunk-AKFNLRA2.js.map
211
+ //# sourceMappingURL=chunk-SA77BQ7F.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/with-commit/history/time-machine.ts"],"sourcesContent":["/**\n * Time-machine queries — point-in-time reads reconstructed from the\n * existing history + ledger infrastructure.\n *\n * ## Usage\n *\n * ```ts\n * const vault = await db.openVault('acme', { secret })\n * const q1End = vault.at('2026-03-31T23:59:59Z')\n * const invoice = await q1End.collection<Invoice>('invoices').get('inv-001')\n * // → the record as it stood at the close of Q1 2026\n * ```\n *\n * ## How it works\n *\n * Every write path already fans out into two persistence lanes:\n *\n * 1. `saveHistory(...)` persists a **full encrypted envelope snapshot**\n * per version under the `_history` collection (one envelope per\n * version, keyed by `{collection}:{id}:{paddedVersion}`). Each\n * envelope carries its own `_ts` (the write timestamp).\n * 2. `ledger.append(...)` appends a hash-chained audit entry that\n * records the `op` (put / delete), `version`, and `ts`.\n *\n * Reconstruction at a target timestamp T is therefore:\n *\n * - Find the newest history envelope for `(collection, id)` whose\n * `_ts ≤ T` — that's the state the record was in at T.\n * - Check the ledger for any `op: 'delete'` entry for the same\n * `(collection, id)` with `entry.ts` in `(latestEnvelope._ts, T]` —\n * if present, the record was deleted before T, so return `null`.\n * - Decrypt the surviving envelope with the current collection DEK\n * (DEKs are per-collection but stable across versions — the same\n * key encrypts v1 and v15 of a record).\n *\n * No delta replay. The existing `history.ts` module already stores\n * complete snapshots; we just pick the right one.\n *\n * ## Read-only contract\n *\n * Every write method on `CollectionInstant` throws\n * {@link ReadOnlyAtInstantError}. A historical view is a *read*\n * surface — mutating the past would require either a branch/shadow\n * mechanism (tracked under shadow vaults) or a rewrite of\n * history, which breaks the ledger's tamper-evidence guarantee.\n *\n * @module\n */\nimport type { EncryptedEnvelope, NoydbStore } from '../../kernel/types.js'\nimport type { LedgerStore } from './ledger/store.js'\nimport { getHistory, historyIdentity } from './history.js'\n\n/**\n * An envelope together with **the address it was fetched from**.\n *\n * The time machine resolves a record's state at an instant from two different\n * places — the live collection, or a `_history` snapshot — and those are sealed\n * against different identities (#1041). Returning the envelope alone discards\n * the one fact the reader needs to open it, which is precisely the read-side\n * data-flow gap that binding identity exposes.\n */\ninterface ResolvedEnvelope {\n readonly envelope: EncryptedEnvelope\n readonly ref: { readonly collection: string; readonly id: string }\n}\nimport { openEnvelopeJson, type EnclaveKey } from '../../kernel/enclave/index.js'\nimport { ReadOnlyAtInstantError } from '../../kernel/errors.js'\nimport { liveRecordIsElevated } from '../../kernel/tier-visibility.js'\n\n/**\n * Narrow view of a {@link Vault}'s internals that\n * {@link VaultInstant} needs. Passed in by `Vault.at()` rather than\n * constructed here so all crypto + adapter access stays inside the\n * Vault class.\n *\n * Not exported from the public barrel — consumers should get a\n * `VaultInstant` via `vault.at(ts)`, never by constructing one\n * directly.\n */\nexport interface VaultEngine {\n readonly adapter: NoydbStore\n /** Vault name (the compartment). */\n readonly name: string\n /**\n * `true` when the vault was opened with a secret (the normal\n * case). `false` in plaintext-mode vaults (`encrypt: false`) — in\n * that case `envelope._data` is raw JSON and we skip the DEK lookup.\n */\n readonly encrypted: boolean\n /**\n * Resolves the DEK used to decrypt a given collection's envelopes.\n * Not called when `encrypted` is false.\n */\n getDEK(collection: string): Promise<EnclaveKey>\n /**\n * Lazily-initialised ledger. We consult it to detect deletes that\n * happened between the latest history snapshot and the target\n * timestamp. `null` when history is disabled for this vault — in\n * that case time-machine reads fall back to history-only\n * reconstruction (which may miss deletes).\n */\n getLedger(): LedgerStore | null\n}\n\n/**\n * A vault at a fixed instant. Produced by `vault.at(timestamp)`.\n * Carries no session state of its own — every read is a fresh\n * lookup through the vault's adapter.\n *\n * Cheap to construct; safe to throw away. Create one per query.\n */\nexport class VaultInstant {\n constructor(\n private readonly engine: VaultEngine,\n /** Fully-resolved target timestamp (ISO-8601 UTC). */\n public readonly timestamp: string,\n ) {}\n\n /** Get a point-in-time view of a collection. */\n collection<T = unknown>(name: string): CollectionInstant<T> {\n return new CollectionInstant<T>(this.engine, this.timestamp, name)\n }\n}\n\n/**\n * A read-only collection view anchored to a past instant.\n *\n * Every write method throws {@link ReadOnlyAtInstantError} — see the\n * module docstring for why. The read surface is intentionally smaller\n * than the live {@link Collection}: `get` and `list` cover the\n * \"what did the books look like on date X\" use case without pulling\n * in the full query DSL / joins / aggregates at this stage. Follow-up\n * work tracked under.\n */\nexport class CollectionInstant<T = unknown> {\n constructor(\n private readonly engine: VaultEngine,\n private readonly targetTs: string,\n public readonly name: string,\n ) {}\n\n /**\n * Return the record as it existed at the target timestamp, or\n * `null` if the record had not been created yet or had already been\n * deleted by then.\n *\n * Gated on the LIVE record's current tier — #730, mirroring the #712\n * read-gate `history()`/`getVersion()` already apply: an elevated record\n * is invisible through the whole time-machine surface, not just a\n * decrypt failure. See {@link resolveVisibleEnvelope}.\n *\n * Decrypts through {@link openEnvelopeJson}, the `_cek`-aware envelope\n * body opener — a snapshot from a `perRecordKeys` collection is encrypted\n * under its own per-record CEK (wrapped in `_cek`), not the collection\n * DEK directly.\n */\n async get(id: string): Promise<T | null> {\n const found = await this.resolveVisibleEnvelope(id)\n if (!found) return null\n // The envelope may be the LIVE record or a `_history` snapshot, and the two\n // are sealed against different identities (#1041). `resolveEnvelope` carries\n // the address down with the bytes precisely so this read can name it.\n const plaintext = this.engine.encrypted\n ? await openEnvelopeJson(found.ref, found.envelope, await this.engine.getDEK(this.name))\n : found.envelope._data\n return JSON.parse(plaintext) as T\n }\n\n /**\n * IDs of records that existed (had at least one `put` and were not\n * subsequently deleted) at the target timestamp.\n *\n * Implemented as a linear scan over history + ledger. Performance\n * is bounded by total history size (not live-vault size), so the\n * memory-first vault-scale cap (1K–50K records × average history\n * depth) still applies.\n */\n async list(): Promise<string[]> {\n const historyIds = await collectHistoryIds(this.engine.adapter, this.engine.name, this.name)\n const liveIds = await this.engine.adapter.list(this.engine.name, this.name)\n const candidateIds = new Set<string>([...historyIds, ...liveIds])\n const alive: string[] = []\n for (const id of candidateIds) {\n const env = await this.resolveVisibleEnvelope(id)\n if (env) alive.push(id)\n }\n return alive.sort()\n }\n\n // ── write guards ───────────────────────────────────────────────────\n\n async put(_id: string, _record: T): Promise<never> {\n throw new ReadOnlyAtInstantError('put', this.targetTs)\n }\n async delete(_id: string): Promise<never> {\n throw new ReadOnlyAtInstantError('delete', this.targetTs)\n }\n async update(_id: string, _patch: Partial<T>): Promise<never> {\n throw new ReadOnlyAtInstantError('update', this.targetTs)\n }\n\n // ── internals ─────────────────────────────────────────────────────\n\n /**\n * {@link resolveEnvelope}, additionally gated on the record's LIVE\n * (current, not historical) tier — #730. Mirrors the #712 read-gate\n * `history()`/`getVersion()` apply: history snapshots keep their\n * tier-0-wrapped CEKs and carry no `_tier` of their own, so an elevated\n * record's prior versions would otherwise stay tier-0-decryptable here.\n * `null` for a resolved envelope carrying its own `_tier > 0` (a\n * tier-aware snapshot reached some other way) or whose LIVE record is\n * currently elevated — both `get()` and `list()` route through this so\n * the invisibility law holds on the whole time-machine read surface, not\n * just a decrypt failure.\n */\n private async resolveVisibleEnvelope(id: string): Promise<ResolvedEnvelope | null> {\n const found = await this.resolveEnvelope(id)\n if (!found || (found.envelope._tier ?? 0) > 0) return null\n if (await liveRecordIsElevated(this.engine.adapter, this.engine.name, this.name, id)) return null\n return found\n }\n\n /**\n * Return the envelope that represents the record's state at\n * `targetTs`, accounting for deletes. `null` if the record didn't\n * exist at that instant.\n *\n * ## Why we use the ledger as the authoritative timeline\n *\n * The per-version history snapshots saved by `saveHistory()` do\n * carry a `_ts` field, but that timestamp is the moment the\n * snapshot was *captured* (i.e. the instant right before the\n * subsequent overwrite), not the original write time. The ledger,\n * by contrast, records `ts` at the moment of each `put` / `delete`\n * — it's the only source that tracks the real timeline. So:\n *\n * 1. Walk the ledger; find the latest entry for `(collection, id)`\n * with `ts ≤ targetTs`.\n * 2. If that entry is a `delete`, the record was gone at the\n * target instant — return null.\n * 3. Otherwise it's a `put` with a specific `version`. Load the\n * envelope for that version from history, falling back to the\n * live collection for the most recent version.\n *\n * ## Fallback when the ledger is disabled\n *\n * If the vault has history disabled, `getLedger()` returns null and\n * we fall back to comparing envelope `_ts` fields. This is\n * approximate and gets the *last write* right but may confuse the\n * intermediate versions; adopters needing accurate time-machine\n * reads should leave history enabled.\n */\n private async resolveEnvelope(id: string): Promise<ResolvedEnvelope | null> {\n const ledger = this.engine.getLedger()\n if (ledger) {\n return this.resolveViaLedger(id, ledger)\n }\n return this.resolveViaEnvelopeTs(id)\n }\n\n private async resolveViaLedger(id: string, ledger: LedgerStore): Promise<ResolvedEnvelope | null> {\n const entries = await ledger.entries()\n // Entries are already ordered by index which is the mutation order.\n let latest: { op: 'put' | 'delete'; version: number } | null = null\n for (const e of entries) {\n if (e.collection !== this.name || e.id !== id) continue\n if (e.ts > this.targetTs) break // entries are time-ordered by index\n // `amendment` + `lifecycle` entries are audit-only summaries — they\n // carry no (collection, id) tuple of their own and would never match\n // the filter above. The narrow here is a type guard, not a runtime\n // skip.\n // `forget` is a subject-erasure summary with empty (collection, id) —\n // never matches the filter above; the narrow is a type guard.\n if (e.op === 'amendment' || e.op === 'lifecycle' || e.op === 'forget') continue\n // `migration` is a record rewrite (cutover) — resolve it like a put.\n latest = { op: e.op === 'migration' ? 'put' : e.op, version: e.version }\n }\n if (!latest) return null\n if (latest.op === 'delete') return null\n return this.loadVersion(id, latest.version)\n }\n\n private async resolveViaEnvelopeTs(id: string): Promise<ResolvedEnvelope | null> {\n const history = await getHistory(\n this.engine.adapter, this.engine.name, this.name, id,\n )\n const live = await this.engine.adapter.get(this.engine.name, this.name, id)\n // Each candidate keeps the address it came from — a snapshot and the live\n // record are sealed against different identities, and this map is exactly\n // where that provenance used to be discarded (#1041).\n const byVersion = new Map<number, ResolvedEnvelope>()\n for (const e of history) {\n byVersion.set(e._v, { envelope: e, ref: historyIdentity(this.name, id, e._v) })\n }\n if (live) byVersion.set(live._v, { envelope: live, ref: { collection: this.name, id } })\n const sorted = [...byVersion.values()].sort((a, b) =>\n a.envelope._ts < b.envelope._ts ? 1 : a.envelope._ts > b.envelope._ts ? -1 : 0,\n )\n return sorted.find((e) => e.envelope._ts <= this.targetTs) ?? null\n }\n\n /**\n * Fetch the envelope for a specific version. The live record (most\n * recent put) lives in the main collection; prior versions live in\n * `_history`. We check live first because the common case after a\n * delete is that we're trying to load the last-live version from\n * history, and skipping live for the current-version case avoids a\n * redundant lookup.\n */\n private async loadVersion(id: string, version: number): Promise<ResolvedEnvelope | null> {\n const live = await this.engine.adapter.get(this.engine.name, this.name, id)\n if (live && live._v === version) {\n return { envelope: live, ref: { collection: this.name, id } }\n }\n\n // Direct lookup by (collection, id, version) — avoids scanning all history.\n //\n // This used to rebuild the key inline as\n // `${name}:${id}:${padStart(10,'0')}` — a SECOND definition of the history\n // layout, free to drift from `historyId`'s. It now goes through\n // `historyIdentity`, the same function `saveHistory` writes with, so the\n // read address and the write address cannot disagree (#1041).\n const ref = historyIdentity(this.name, id, version)\n const envelope = await this.engine.adapter.get(this.engine.name, ref.collection, ref.id)\n return envelope ? { envelope, ref } : null\n }\n}\n\n/**\n * Scan the `_history` collection once and collect every distinct\n * `recordId` for the given collection. History keys follow the\n * shape `<collection>:<recordId>:<paddedVersion>`; we split on the\n * last two colons (delimiter-safe because `paddedVersion` is\n * exactly 10 digits).\n */\nasync function collectHistoryIds(\n adapter: NoydbStore,\n vault: string,\n collection: string,\n): Promise<string[]> {\n const all = await adapter.list(vault, '_history')\n const prefix = `${collection}:`\n const seen = new Set<string>()\n for (const key of all) {\n if (!key.startsWith(prefix)) continue\n const lastColon = key.lastIndexOf(':')\n if (lastColon <= prefix.length) continue\n const middle = key.slice(prefix.length, lastColon)\n seen.add(middle)\n }\n return [...seen]\n}\n"],"mappings":"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","names":[]}
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-LP7BEXCT.js";
6
6
  import {
7
7
  bufferToBase64
8
- } from "./chunk-QTDAPDDV.js";
8
+ } from "./chunk-VJ4B3ZXY.js";
9
9
 
10
10
  // src/kernel/enclave/classify/bidx.ts
11
11
  var subtle = globalThis.crypto.subtle;
@@ -66,4 +66,4 @@ export {
66
66
  mintBidxTag,
67
67
  computeBidxTarget
68
68
  };
69
- //# sourceMappingURL=chunk-RBTZLXZJ.js.map
69
+ //# sourceMappingURL=chunk-SFD2LL7G.js.map
@@ -4,10 +4,10 @@ import {
4
4
  } from "./chunk-KHKKD62N.js";
5
5
  import {
6
6
  derivePersistedSchema
7
- } from "./chunk-HNOBRU24.js";
7
+ } from "./chunk-N7YAQKAO.js";
8
8
  import {
9
9
  loadPersistedSchema
10
- } from "./chunk-6JRIYG2C.js";
10
+ } from "./chunk-YVTADFTC.js";
11
11
 
12
12
  // src/with-shape/introspection/walk.ts
13
13
  var INTERNAL_PREFIX = "_";
@@ -238,4 +238,4 @@ function summariseAggregateOp(value) {
238
238
  export {
239
239
  dumpVaultSchema
240
240
  };
241
- //# sourceMappingURL=chunk-XTC5EKST.js.map
241
+ //# sourceMappingURL=chunk-SG3OQCRG.js.map
@@ -310,4 +310,4 @@ export {
310
310
  SyncScheduler,
311
311
  NO_SYNC
312
312
  };
313
- //# sourceMappingURL=chunk-JN64WH3U.js.map
313
+ //# sourceMappingURL=chunk-T7IR4N2S.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/kernel/sync-policy.ts","../src/with-sync/strategy.ts"],"sourcesContent":["/**\n * Sync scheduling policy.\n *\n * ## What it controls\n *\n * A {@link SyncPolicy} has two halves:\n * - **push** ({@link PushPolicy}) — when dirty local writes are sent to the remote.\n * - **pull** ({@link PullPolicy}) — when the remote is polled for new data.\n *\n * ## Choosing a policy\n *\n * The right policy depends on the backend's operational characteristics:\n *\n * | Backend type | Recommended policy |\n * |---|---|\n * | Per-record (DynamoDB, S3, IDB) | {@link INDEXED_STORE_POLICY} — `on-change` push, `manual` pull |\n * | Bundle (Drive, WebDAV, Git) | {@link POD_STORE_POLICY} — `debounce` push, `interval` pull |\n *\n * Consumers can override via `createNoydb({ syncPolicy: { ... } })`:\n *\n * ```ts\n * const db = await createNoydb({\n * store: toFile({ dir: './data' }),\n * syncPolicy: {\n * push: { mode: 'debounce', debounceMs: 5_000 },\n * pull: { mode: 'on-focus' },\n * },\n * })\n * ```\n *\n * ## Scheduler lifecycle\n *\n * {@link SyncScheduler} owns all timers, debounce logic, and browser lifecycle\n * hooks (`visibilitychange`, `pagehide`, `beforeExit`). Call `scheduler.start()`\n * after opening a vault and `scheduler.stop()` when closing it. The scheduler\n * delegates actual push/pull work to {@link SyncSchedulerCallbacks} provided\n * by the {@link SyncEngine}.\n *\n * @module\n */\n\n// ─── Policy types ───────────────────────────────────────────────────────\n\n/**\n * When push operations are triggered automatically.\n *\n * - `'manual'` — only on explicit `sync.push()` calls.\n * - `'on-change'` — immediately after every local write (respecting `minIntervalMs`).\n * - `'debounce'` — after `debounceMs` of inactivity following a write.\n * - `'interval'` — on a fixed timer regardless of writes.\n */\nexport type PushMode = 'manual' | 'on-change' | 'debounce' | 'interval'\n\n/**\n * When pull operations are triggered automatically.\n *\n * - `'manual'` — only on explicit `sync.pull()` calls.\n * - `'interval'` — on a fixed `intervalMs` timer.\n * - `'on-focus'` — when the browser tab regains visibility.\n * - `'phased'` — pull the collections named in `sequence`, in order, one at a\n * time, then settle into steady state. For thin clients that need their\n * navigation-critical collections before bulk history.\n */\nexport type PullMode = 'manual' | 'interval' | 'on-focus' | 'phased'\n\n/**\n * Push half of a sync policy. Controls the trigger mode and timing guards\n * for outbound sync operations.\n */\nexport interface PushPolicy {\n /** Push trigger mode. */\n readonly mode: PushMode\n /** Debounce delay in ms. Only used when `mode: 'debounce'`. Default: 30_000. */\n readonly debounceMs?: number\n /** Interval in ms between automatic pushes. Used by `'interval'` and as floor for `'debounce'`. */\n readonly intervalMs?: number\n /**\n * Hard floor between pushes regardless of mode. Prevents burst writes\n * from hammering the remote. Default: 0 (no floor).\n */\n readonly minIntervalMs?: number\n /**\n * Force a push on page unload (`pagehide` / `visibilitychange → hidden`)\n * in browsers, `beforeExit` in Node. Default: true for non-manual modes.\n */\n readonly onUnload?: boolean\n}\n\n/**\n * Pull half of a sync policy. Controls when and how often inbound sync\n * operations are triggered.\n */\nexport interface PullPolicy {\n /** Pull trigger mode. */\n readonly mode: PullMode\n /**\n * Interval in ms between automatic pulls. Used by `'interval'` mode. Default: 60_000.\n * Under `'phased'` it is the steady-state cadence adopted *after* the sequence\n * completes; omit it to go idle instead.\n */\n readonly intervalMs?: number\n /**\n * Collection names to pull in order, one at a time. Required when\n * `mode: 'phased'` and rejected otherwise. Entries must be non-empty and\n * unique — without period narrowing a repeated collection can only be a\n * mistake, so it is rejected rather than silently merged.\n *\n * Period-scoped phases (`collection@period`) are deferred to `partitions`;\n * use `db.pull(vault, { periods })` explicitly meanwhile.\n */\n readonly sequence?: readonly string[]\n}\n\n/**\n * Combined push + pull sync scheduling policy for a vault.\n *\n * Pass via `createNoydb({ syncPolicy })` to override the default policy\n * derived from the active store type. Pre-built defaults are available\n * as `INDEXED_STORE_POLICY` and `POD_STORE_POLICY`.\n */\nexport interface SyncPolicy {\n readonly push: PushPolicy\n readonly pull: PullPolicy\n}\n\n// ─── Default policies by store category ─────────────────────────────────\n\n/** Default for per-record stores (DynamoDB, S3, file, IDB). */\nexport const INDEXED_STORE_POLICY: SyncPolicy = {\n push: { mode: 'on-change', minIntervalMs: 0, onUnload: true },\n pull: { mode: 'manual' },\n}\n\n/** Default for bundle stores (Drive, WebDAV, Git). */\nexport const POD_STORE_POLICY: SyncPolicy = {\n push: { mode: 'debounce', debounceMs: 30_000, minIntervalMs: 120_000, onUnload: true },\n pull: { mode: 'interval', intervalMs: 60_000 },\n}\n\n\n// ─── Sync scheduler ─────────────────────────────────────────────────────\n\n/**\n * Current operational state of the `SyncScheduler`.\n *\n * - `'idle'` — no pending or active sync operations.\n * - `'pending'` — local writes are queued, waiting for debounce/interval to fire.\n * - `'pushing'` — push in progress.\n * - `'pulling'` — pull in progress.\n * - `'error'` — last sync operation failed; `lastError` holds the cause.\n */\nexport type SyncSchedulerState = 'idle' | 'pending' | 'pushing' | 'pulling' | 'error'\n\n/**\n * How much of a collection a caller may rely on, under a `'phased'` pull.\n *\n * - `'cold'` — not pulled yet, or its phase did not complete cleanly. A miss\n * from `get()` proves nothing.\n * - `'pulling'` — its phase is in flight. Never terminal: a phase always ends\n * in `'live'` or back in `'cold'`, because a stuck `'pulling'` would leave a\n * permanent skeleton in the UI.\n * - `'live'` — its phase completed cleanly, so a miss is a real absence.\n */\nexport type ReadinessState = 'cold' | 'pulling' | 'live'\n\n/**\n * What a scheduler-initiated pull achieved, as reported by the callback.\n *\n * `'incomplete'` covers both a pull that reported errors (`PullResult.errors`\n * accumulates without throwing) and one that was skipped — e.g. role-gated on a\n * backup target. Either way nothing may be claimed about completeness.\n */\nexport type PullPhaseOutcome = 'complete' | 'incomplete'\n\n/**\n * Snapshot of the sync scheduler's state, returned by `SyncScheduler.status`.\n * Safe to expose in a reactive UI status indicator.\n */\nexport interface SyncSchedulerStatus {\n readonly state: SyncSchedulerState\n readonly lastPushAt: string | null\n readonly lastPullAt: string | null\n readonly lastError: Error | null\n readonly pendingWrites: number\n /**\n * Per-collection readiness. Empty unless `pull.mode === 'phased'`.\n *\n * A collection the sequence never names is **absent** from the map, and\n * `undefined` means *\"no claim made\"* — never a reason to gate a UI.\n */\n readonly readiness: ReadonlyMap<string, ReadinessState>\n /** 1-based position in the sequence, or `null` outside a phased run. */\n readonly phase: { readonly index: number; readonly total: number } | null\n}\n\n/**\n * Callbacks injected into `SyncScheduler` by the SyncEngine.\n *\n * The scheduler owns timers and lifecycle hooks; it delegates actual push/pull\n * work to these callbacks to stay decoupled from the sync implementation.\n */\nexport interface SyncSchedulerCallbacks {\n push(): Promise<void>\n /**\n * @param collections - Narrow the pull to these collections. Passed by a\n * `'phased'` sequence, one collection per phase; omitted for a whole-vault pull.\n * @returns Whether the pull may be treated as complete. Returning `void` reads\n * as `'complete'`, so callers with nothing to report need not opt in.\n */\n pull(collections?: readonly string[]): Promise<PullPhaseOutcome | void>\n getDirtyCount(): number\n}\n\n/**\n * Reject a policy that cannot mean anything, at construction rather than on\n * the first tick. Module-private: `SyncScheduler` is the choke point every\n * policy that can actually schedule must pass through — primary and per-target\n * alike — so validating here needs no new public surface and costs the kernel\n * floor nothing.\n */\nfunction validatePullPolicy(pull: PullPolicy): void {\n if (pull.mode !== 'phased') {\n if (pull.sequence !== undefined) {\n throw new Error(\n `syncPolicy.pull.sequence is only meaningful with mode: 'phased' (got '${pull.mode}'). ` +\n `Set mode: 'phased' to run the sequence, or drop the field.`,\n )\n }\n return\n }\n\n const { sequence } = pull\n if (!sequence || sequence.length === 0) {\n throw new Error(\n `syncPolicy.pull.mode: 'phased' requires a non-empty 'sequence' of collection names.`,\n )\n }\n for (const name of sequence) {\n if (typeof name !== 'string' || name.length === 0) {\n throw new Error(\n `syncPolicy.pull.sequence entries must be non-empty collection names (got ${JSON.stringify(name)}).`,\n )\n }\n }\n const duplicate = sequence.find((name, i) => sequence.indexOf(name) !== i)\n if (duplicate !== undefined) {\n throw new Error(\n `syncPolicy.pull.sequence lists \"${duplicate}\" more than once. ` +\n `Each collection is pulled whole, so a repeat does no additional work.`,\n )\n }\n}\n\n/**\n * Manages sync timing according to a `SyncPolicy`.\n *\n * The scheduler owns all timers and lifecycle hooks. It delegates actual\n * push/pull work to callbacks provided by the SyncEngine.\n */\nexport class SyncScheduler {\n private readonly policy: SyncPolicy\n private readonly callbacks: SyncSchedulerCallbacks\n\n private _state: SyncSchedulerState = 'idle'\n private _lastPushAt: string | null = null\n private _lastPullAt: string | null = null\n private _lastError: Error | null = null\n private _lastPushTime = 0 // monotonic ms for minIntervalMs enforcement\n\n // Phased-pull progress. Empty for every other mode, so non-adopters carry\n // an empty Map and a null — no per-read cost, nothing to interpret.\n private readonly _readiness = new Map<string, ReadinessState>()\n private _phase: { readonly index: number; readonly total: number } | null = null\n\n // Timers\n private debounceTimer: ReturnType<typeof setTimeout> | null = null\n private pushIntervalTimer: ReturnType<typeof setInterval> | null = null\n private pullIntervalTimer: ReturnType<typeof setInterval> | null = null\n\n // Bound handlers for cleanup\n private readonly boundOnVisibilityChange: (() => void) | null = null\n private readonly boundOnBeforeExit: (() => void) | null = null\n private readonly boundOnPageHide: (() => void) | null = null\n\n private started = false\n\n constructor(policy: SyncPolicy, callbacks: SyncSchedulerCallbacks) {\n validatePullPolicy(policy.pull)\n this.policy = policy\n this.callbacks = callbacks\n\n // Pre-bind handlers\n if (this.shouldRegisterUnload()) {\n this.boundOnVisibilityChange = this.handleVisibilityChange.bind(this)\n this.boundOnPageHide = this.handlePageHide.bind(this)\n this.boundOnBeforeExit = this.handleBeforeExit.bind(this)\n }\n }\n\n /** Current scheduler status snapshot. */\n get status(): SyncSchedulerStatus {\n return {\n state: this._state,\n lastPushAt: this._lastPushAt,\n lastPullAt: this._lastPullAt,\n lastError: this._lastError,\n pendingWrites: this.callbacks.getDirtyCount(),\n // Copied, not aliased — a status snapshot must not mutate under its reader.\n readiness: new Map(this._readiness),\n phase: this._phase,\n }\n }\n\n /** Start the scheduler — registers timers, event listeners. */\n start(): void {\n if (this.started) return\n this.started = true\n\n // Push: interval mode\n if (this.policy.push.mode === 'interval' && this.policy.push.intervalMs) {\n this.pushIntervalTimer = setInterval(() => {\n void this.executePush()\n }, this.policy.push.intervalMs)\n }\n\n // Pull: interval mode\n if (this.policy.pull.mode === 'interval') {\n this.startPullInterval()\n }\n\n // Pull: phased mode — walk the sequence in order, then settle into steady\n // state. Unawaited by design: start() is synchronous and the caller\n // (vault open) must not block on a bootstrap pull.\n if (this.policy.pull.mode === 'phased') {\n const sequence = this.policy.pull.sequence ?? []\n // Every named collection is 'cold' from the moment we start, so a reader\n // that races the first phase sees \"not ready\", never a missing entry.\n for (const collection of sequence) this._readiness.set(collection, 'cold')\n void this.runSequence(sequence)\n }\n\n // Pull: on-focus mode\n if (this.policy.pull.mode === 'on-focus' && typeof document !== 'undefined') {\n document.addEventListener('visibilitychange', this.handleFocusPull)\n }\n\n // Unload hooks\n if (this.shouldRegisterUnload()) {\n if (typeof document !== 'undefined' && this.boundOnVisibilityChange) {\n document.addEventListener('visibilitychange', this.boundOnVisibilityChange)\n }\n if (typeof globalThis.addEventListener === 'function' && this.boundOnPageHide) {\n globalThis.addEventListener('pagehide', this.boundOnPageHide)\n }\n if (typeof process !== 'undefined' && this.boundOnBeforeExit) {\n process.on('beforeExit', this.boundOnBeforeExit)\n }\n }\n }\n\n /** Stop the scheduler — clears timers, removes event listeners. */\n stop(): void {\n if (!this.started) return\n this.started = false\n\n // A sequence cut mid-phase must not leave that collection 'pulling' —\n // a reader would show a skeleton that never resolves.\n for (const [collection, state] of this._readiness) {\n if (state === 'pulling') this._readiness.set(collection, 'cold')\n }\n this._phase = null\n\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer)\n this.debounceTimer = null\n }\n if (this.pushIntervalTimer) {\n clearInterval(this.pushIntervalTimer)\n this.pushIntervalTimer = null\n }\n if (this.pullIntervalTimer) {\n clearInterval(this.pullIntervalTimer)\n this.pullIntervalTimer = null\n }\n\n // Focus pull\n if (this.policy.pull.mode === 'on-focus' && typeof document !== 'undefined') {\n document.removeEventListener('visibilitychange', this.handleFocusPull)\n }\n\n // Unload hooks\n if (typeof document !== 'undefined' && this.boundOnVisibilityChange) {\n document.removeEventListener('visibilitychange', this.boundOnVisibilityChange)\n }\n if (typeof globalThis.removeEventListener === 'function' && this.boundOnPageHide) {\n globalThis.removeEventListener('pagehide', this.boundOnPageHide)\n }\n if (typeof process !== 'undefined' && this.boundOnBeforeExit) {\n process.removeListener('beforeExit', this.boundOnBeforeExit)\n }\n }\n\n /**\n * Notify the scheduler that a local write occurred.\n * For `on-change` mode: triggers immediate push (respecting minIntervalMs).\n * For `debounce` mode: resets the debounce timer.\n * For `manual` / `interval`: no-op.\n */\n notifyChange(): void {\n if (!this.started) return\n\n if (this.policy.push.mode === 'on-change') {\n void this.executePush()\n } else if (this.policy.push.mode === 'debounce') {\n this.resetDebounce()\n }\n }\n\n /** Force an immediate push, bypassing the scheduler. */\n async forcePush(): Promise<void> {\n await this.executePush()\n }\n\n /** Force an immediate pull, bypassing the scheduler. */\n async forcePull(): Promise<void> {\n await this.executePull()\n }\n\n // ─── Internal ─────────────────────────────────────────────────────\n\n /**\n * Arm the steady-state pull timer, if the policy specifies a cadence.\n * Shared by `'interval'` mode and by `'phased'` once its sequence drains.\n */\n private startPullInterval(): void {\n if (this.pullIntervalTimer || !this.policy.pull.intervalMs) return\n this.pullIntervalTimer = setInterval(() => {\n void this.executePull()\n }, this.policy.pull.intervalMs)\n }\n\n /**\n * Pull each collection in turn, then hand over to steady state.\n *\n * Strictly sequential — running phases concurrently would defeat the\n * prioritisation that is the entire point of a sequence. Each phase is an\n * ordinary scoped pull, so a phase that reports errors does not abort the\n * ones behind it; `executePull` records the failure and the walk continues.\n * `started` is re-checked between phases so `stop()` cuts a sequence short\n * instead of letting it run on against a closed vault.\n */\n private async runSequence(sequence: readonly string[]): Promise<void> {\n for (const [i, collection] of sequence.entries()) {\n if (!this.started) return\n this._readiness.set(collection, 'pulling')\n this._phase = { index: i + 1, total: sequence.length }\n\n const outcome = await this.executePull([collection])\n\n // 'live' only on a clean phase. Anything else — reported errors, a\n // role-gated skip, a throw — goes back to 'cold', because 'live' is a\n // promise that a miss from get() is a real absence.\n this._readiness.set(collection, outcome === 'complete' ? 'live' : 'cold')\n }\n this._phase = null\n if (this.started) this.startPullInterval()\n }\n\n private async executePush(): Promise<void> {\n if (this._state === 'pushing') return // already in progress\n\n // minIntervalMs enforcement\n const minInterval = this.policy.push.minIntervalMs ?? 0\n if (minInterval > 0) {\n const elapsed = Date.now() - this._lastPushTime\n if (elapsed < minInterval) {\n // Schedule for later if debounce mode\n if (this.policy.push.mode === 'debounce') {\n this.scheduleDebounce(minInterval - elapsed)\n }\n return\n }\n }\n\n // Nothing to push\n if (this.callbacks.getDirtyCount() === 0) {\n this._state = 'idle'\n return\n }\n\n this._state = 'pushing'\n try {\n await this.callbacks.push()\n this._lastPushAt = new Date().toISOString()\n this._lastPushTime = Date.now()\n this._lastError = null\n this._state = this.callbacks.getDirtyCount() > 0 ? 'pending' : 'idle'\n } catch (err) {\n this._lastError = err instanceof Error ? err : new Error(String(err))\n this._state = 'error'\n }\n }\n\n private async executePull(collections?: readonly string[]): Promise<PullPhaseOutcome> {\n if (this._state === 'pulling') return 'incomplete'\n\n const previousState = this._state\n this._state = 'pulling'\n try {\n const outcome = await this.callbacks.pull(collections)\n this._lastPullAt = new Date().toISOString()\n this._lastError = null\n this._state = previousState === 'pending' ? 'pending' : 'idle'\n return outcome ?? 'complete'\n } catch (err) {\n this._lastError = err instanceof Error ? err : new Error(String(err))\n this._state = 'error'\n return 'incomplete'\n }\n }\n\n private resetDebounce(): void {\n if (this.debounceTimer) clearTimeout(this.debounceTimer)\n const ms = this.policy.push.debounceMs ?? 30_000\n this._state = 'pending'\n this.scheduleDebounce(ms)\n }\n\n private scheduleDebounce(ms: number): void {\n if (this.debounceTimer) clearTimeout(this.debounceTimer)\n this.debounceTimer = setTimeout(() => {\n this.debounceTimer = null\n void this.executePush()\n }, ms)\n }\n\n private shouldRegisterUnload(): boolean {\n const onUnload = this.policy.push.onUnload\n if (onUnload !== undefined) return onUnload\n return this.policy.push.mode !== 'manual'\n }\n\n // ─── Event handlers ───────────────────────────────────────────────\n\n private handleVisibilityChange(): void {\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') {\n this.fireUnloadPush()\n }\n }\n\n private handlePageHide(): void {\n this.fireUnloadPush()\n }\n\n private handleBeforeExit(): void {\n this.fireUnloadPush()\n }\n\n private handleFocusPull = (): void => {\n if (typeof document !== 'undefined' && document.visibilityState === 'visible') {\n void this.executePull()\n }\n }\n\n private fireUnloadPush(): void {\n if (this.callbacks.getDirtyCount() === 0) return\n // Best-effort synchronous-ish push on unload\n void this.callbacks.push().catch(() => {})\n }\n}\n","/**\n * Strategy seam for the optional sync engine + presence service.\n * Core imports `SyncStrategy` type-only + `NO_SYNC` stub; the real\n * `SyncEngine`, `SyncTransaction`, and `PresenceHandle` constructors\n * are only reachable via `withSync()` in `./sync-active.ts`.\n *\n * Solo apps that never configure `sync` and never call\n * `collection.presence()` ship none of the ~856 LOC behind this seam\n * (`sync.ts` + `sync-transaction.ts` + `presence.ts`).\n *\n * Note: `keyring.ts` (~746 LOC) stays in core because it's required\n * for any multi-user vault — even single-owner vaults use a keyring\n * to wrap the DEK. The team package's grant/revoke/magic-link/\n * delegation modules tree-shake naturally via direct named imports.\n *\n * Behavior under NO_SYNC:\n *\n * - **buildSyncEngine** — throws. Only fires when `createNoydb({ sync })`\n * passes a remote target.\n * - **buildSyncTransaction** — throws. Only fires when `db.transaction(vault)`\n * is called on a vault with sync configured.\n * - **buildPresence** — throws. Only fires when user code calls\n * `collection.presence()`.\n *\n * @internal\n */\n\nimport type {\n NoydbStore,\n ConflictStrategy,\n SyncTargetRole,\n} from '../kernel/types.js'\nimport type { NoydbEventEmitter } from '../kernel/events.js'\nimport type { SyncPolicy } from '../kernel/sync-policy.js'\nimport type { SyncEngine } from './engine.js'\nimport type { SyncTransaction } from './transaction.js'\nimport type { PresenceHandle, PresenceHandleOpts } from './presence.js'\nimport type { Vault } from '../kernel/vault.js'\n\n/**\n * Options accepted by `SyncStrategy.buildSyncEngine`. Mirrors the\n * `SyncEngine` constructor verbatim — kept here so core code never\n * imports the sync module at runtime.\n *\n * @internal\n */\nexport interface BuildSyncEngineOptions {\n local: NoydbStore\n remote: NoydbStore\n vault: string\n strategy: ConflictStrategy\n emitter: NoydbEventEmitter\n syncPolicy?: SyncPolicy\n role?: SyncTargetRole\n label?: string\n}\n\n/**\n * @internal\n */\nexport interface SyncStrategy {\n buildSyncEngine(opts: BuildSyncEngineOptions): SyncEngine\n buildSyncTransaction(vault: Vault, engine: SyncEngine): SyncTransaction\n buildPresence<P>(opts: PresenceHandleOpts): PresenceHandle<P>\n}\n\nfunction notEnabled(op: string): Error {\n return new Error(\n `${op} requires the sync strategy. Import ` +\n '`{ withSync }` from \"@noy-db/hub/sync\" and pass it to ' +\n '`createNoydb({ syncStrategy: withSync() })`.',\n )\n}\n\n/**\n * No-sync stub. Every constructor throws with an actionable pointer\n * — there is no useful \"off\" mode for sync engine / presence /\n * sync-transaction; if the consumer reached one of these surfaces,\n * they intended to use it.\n *\n * @internal\n */\nexport const NO_SYNC: SyncStrategy = {\n buildSyncEngine() { throw notEnabled('SyncEngine') },\n buildSyncTransaction() { throw notEnabled('SyncTransaction') },\n buildPresence() { throw notEnabled('collection.presence()') },\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/kernel/sync-policy.ts","../src/with-sync/strategy.ts"],"sourcesContent":["/**\n * Sync scheduling policy.\n *\n * ## What it controls\n *\n * A {@link SyncPolicy} has two halves:\n * - **push** ({@link PushPolicy}) — when dirty local writes are sent to the remote.\n * - **pull** ({@link PullPolicy}) — when the remote is polled for new data.\n *\n * ## Choosing a policy\n *\n * The right policy depends on the backend's operational characteristics:\n *\n * | Backend type | Recommended policy |\n * |---|---|\n * | Per-record (DynamoDB, S3, IDB) | {@link INDEXED_STORE_POLICY} — `on-change` push, `manual` pull |\n * | Bundle (Drive, WebDAV, Git) | {@link POD_STORE_POLICY} — `debounce` push, `interval` pull |\n *\n * Consumers can override via `createNoydb({ syncPolicy: { ... } })`:\n *\n * ```ts\n * const db = await createNoydb({\n * store: toFile({ dir: './data' }),\n * syncPolicy: {\n * push: { mode: 'debounce', debounceMs: 5_000 },\n * pull: { mode: 'on-focus' },\n * },\n * })\n * ```\n *\n * ## Scheduler lifecycle\n *\n * {@link SyncScheduler} owns all timers, debounce logic, and browser lifecycle\n * hooks (`visibilitychange`, `pagehide`, `beforeExit`). Call `scheduler.start()`\n * after opening a vault and `scheduler.stop()` when closing it. The scheduler\n * delegates actual push/pull work to {@link SyncSchedulerCallbacks} provided\n * by the {@link SyncEngine}.\n *\n * @module\n */\n\n// ─── Policy types ───────────────────────────────────────────────────────\n\n/**\n * When push operations are triggered automatically.\n *\n * - `'manual'` — only on explicit `sync.push()` calls.\n * - `'on-change'` — immediately after every local write (respecting `minIntervalMs`).\n * - `'debounce'` — after `debounceMs` of inactivity following a write.\n * - `'interval'` — on a fixed timer regardless of writes.\n */\nexport type PushMode = 'manual' | 'on-change' | 'debounce' | 'interval'\n\n/**\n * When pull operations are triggered automatically.\n *\n * - `'manual'` — only on explicit `sync.pull()` calls.\n * - `'interval'` — on a fixed `intervalMs` timer.\n * - `'on-focus'` — when the browser tab regains visibility.\n * - `'phased'` — pull the collections named in `sequence`, in order, one at a\n * time, then settle into steady state. For thin clients that need their\n * navigation-critical collections before bulk history.\n */\nexport type PullMode = 'manual' | 'interval' | 'on-focus' | 'phased'\n\n/**\n * Push half of a sync policy. Controls the trigger mode and timing guards\n * for outbound sync operations.\n */\nexport interface PushPolicy {\n /** Push trigger mode. */\n readonly mode: PushMode\n /** Debounce delay in ms. Only used when `mode: 'debounce'`. Default: 30_000. */\n readonly debounceMs?: number\n /** Interval in ms between automatic pushes. Used by `'interval'` and as floor for `'debounce'`. */\n readonly intervalMs?: number\n /**\n * Hard floor between pushes regardless of mode. Prevents burst writes\n * from hammering the remote. Default: 0 (no floor).\n */\n readonly minIntervalMs?: number\n /**\n * Force a push on page unload (`pagehide` / `visibilitychange → hidden`)\n * in browsers, `beforeExit` in Node. Default: true for non-manual modes.\n */\n readonly onUnload?: boolean\n}\n\n/**\n * Pull half of a sync policy. Controls when and how often inbound sync\n * operations are triggered.\n */\nexport interface PullPolicy {\n /** Pull trigger mode. */\n readonly mode: PullMode\n /**\n * Interval in ms between automatic pulls. Used by `'interval'` mode. Default: 60_000.\n * Under `'phased'` it is the steady-state cadence adopted *after* the sequence\n * completes; omit it to go idle instead.\n */\n readonly intervalMs?: number\n /**\n * Collection names to pull in order, one at a time. Required when\n * `mode: 'phased'` and rejected otherwise. Entries must be non-empty and\n * unique — without period narrowing a repeated collection can only be a\n * mistake, so it is rejected rather than silently merged.\n *\n * Period-scoped phases (`collection@period`) are deferred to `partitions`;\n * use `db.pull(vault, { periods })` explicitly meanwhile.\n */\n readonly sequence?: readonly string[]\n}\n\n/**\n * Combined push + pull sync scheduling policy for a vault.\n *\n * Pass via `createNoydb({ syncPolicy })` to override the default policy\n * derived from the active store type. Pre-built defaults are available\n * as `INDEXED_STORE_POLICY` and `POD_STORE_POLICY`.\n */\nexport interface SyncPolicy {\n readonly push: PushPolicy\n readonly pull: PullPolicy\n}\n\n// ─── Default policies by store category ─────────────────────────────────\n\n/** Default for per-record stores (DynamoDB, S3, file, IDB). */\nexport const INDEXED_STORE_POLICY: SyncPolicy = {\n push: { mode: 'on-change', minIntervalMs: 0, onUnload: true },\n pull: { mode: 'manual' },\n}\n\n/** Default for bundle stores (Drive, WebDAV, Git). */\nexport const POD_STORE_POLICY: SyncPolicy = {\n push: { mode: 'debounce', debounceMs: 30_000, minIntervalMs: 120_000, onUnload: true },\n pull: { mode: 'interval', intervalMs: 60_000 },\n}\n\n\n// ─── Sync scheduler ─────────────────────────────────────────────────────\n\n/**\n * Current operational state of the `SyncScheduler`.\n *\n * - `'idle'` — no pending or active sync operations.\n * - `'pending'` — local writes are queued, waiting for debounce/interval to fire.\n * - `'pushing'` — push in progress.\n * - `'pulling'` — pull in progress.\n * - `'error'` — last sync operation failed; `lastError` holds the cause.\n */\nexport type SyncSchedulerState = 'idle' | 'pending' | 'pushing' | 'pulling' | 'error'\n\n/**\n * How much of a collection a caller may rely on, under a `'phased'` pull.\n *\n * - `'cold'` — not pulled yet, or its phase did not complete cleanly. A miss\n * from `get()` proves nothing.\n * - `'pulling'` — its phase is in flight. Never terminal: a phase always ends\n * in `'live'` or back in `'cold'`, because a stuck `'pulling'` would leave a\n * permanent skeleton in the UI.\n * - `'live'` — its phase completed cleanly, so a miss is a real absence.\n */\nexport type ReadinessState = 'cold' | 'pulling' | 'live'\n\n/**\n * What a scheduler-initiated pull achieved, as reported by the callback.\n *\n * `'incomplete'` covers both a pull that reported errors (`PullResult.errors`\n * accumulates without throwing) and one that was skipped — e.g. role-gated on a\n * backup target. Either way nothing may be claimed about completeness.\n */\nexport type PullPhaseOutcome = 'complete' | 'incomplete'\n\n/**\n * Snapshot of the sync scheduler's state, returned by `SyncScheduler.status`.\n * Safe to expose in a reactive UI status indicator.\n */\nexport interface SyncSchedulerStatus {\n readonly state: SyncSchedulerState\n readonly lastPushAt: string | null\n readonly lastPullAt: string | null\n readonly lastError: Error | null\n readonly pendingWrites: number\n /**\n * Per-collection readiness. Empty unless `pull.mode === 'phased'`.\n *\n * A collection the sequence never names is **absent** from the map, and\n * `undefined` means *\"no claim made\"* — never a reason to gate a UI.\n */\n readonly readiness: ReadonlyMap<string, ReadinessState>\n /** 1-based position in the sequence, or `null` outside a phased run. */\n readonly phase: { readonly index: number; readonly total: number } | null\n}\n\n/**\n * Callbacks injected into `SyncScheduler` by the SyncEngine.\n *\n * The scheduler owns timers and lifecycle hooks; it delegates actual push/pull\n * work to these callbacks to stay decoupled from the sync implementation.\n */\nexport interface SyncSchedulerCallbacks {\n push(): Promise<void>\n /**\n * @param collections - Narrow the pull to these collections. Passed by a\n * `'phased'` sequence, one collection per phase; omitted for a whole-vault pull.\n * @returns Whether the pull may be treated as complete. Returning `void` reads\n * as `'complete'`, so callers with nothing to report need not opt in.\n */\n pull(collections?: readonly string[]): Promise<PullPhaseOutcome | void>\n getDirtyCount(): number\n}\n\n/**\n * Reject a policy that cannot mean anything, at construction rather than on\n * the first tick. Module-private: `SyncScheduler` is the choke point every\n * policy that can actually schedule must pass through — primary and per-target\n * alike — so validating here needs no new public surface and costs the kernel\n * floor nothing.\n */\nfunction validatePullPolicy(pull: PullPolicy): void {\n if (pull.mode !== 'phased') {\n if (pull.sequence !== undefined) {\n throw new Error(\n `syncPolicy.pull.sequence is only meaningful with mode: 'phased' (got '${pull.mode}'). ` +\n `Set mode: 'phased' to run the sequence, or drop the field.`,\n )\n }\n return\n }\n\n const { sequence } = pull\n if (!sequence || sequence.length === 0) {\n throw new Error(\n `syncPolicy.pull.mode: 'phased' requires a non-empty 'sequence' of collection names.`,\n )\n }\n for (const name of sequence) {\n if (typeof name !== 'string' || name.length === 0) {\n throw new Error(\n `syncPolicy.pull.sequence entries must be non-empty collection names (got ${JSON.stringify(name)}).`,\n )\n }\n }\n const duplicate = sequence.find((name, i) => sequence.indexOf(name) !== i)\n if (duplicate !== undefined) {\n throw new Error(\n `syncPolicy.pull.sequence lists \"${duplicate}\" more than once. ` +\n `Each collection is pulled whole, so a repeat does no additional work.`,\n )\n }\n}\n\n/**\n * Manages sync timing according to a `SyncPolicy`.\n *\n * The scheduler owns all timers and lifecycle hooks. It delegates actual\n * push/pull work to callbacks provided by the SyncEngine.\n */\nexport class SyncScheduler {\n private readonly policy: SyncPolicy\n private readonly callbacks: SyncSchedulerCallbacks\n\n private _state: SyncSchedulerState = 'idle'\n private _lastPushAt: string | null = null\n private _lastPullAt: string | null = null\n private _lastError: Error | null = null\n private _lastPushTime = 0 // monotonic ms for minIntervalMs enforcement\n\n // Phased-pull progress. Empty for every other mode, so non-adopters carry\n // an empty Map and a null — no per-read cost, nothing to interpret.\n private readonly _readiness = new Map<string, ReadinessState>()\n private _phase: { readonly index: number; readonly total: number } | null = null\n\n // Timers\n private debounceTimer: ReturnType<typeof setTimeout> | null = null\n private pushIntervalTimer: ReturnType<typeof setInterval> | null = null\n private pullIntervalTimer: ReturnType<typeof setInterval> | null = null\n\n // Bound handlers for cleanup\n private readonly boundOnVisibilityChange: (() => void) | null = null\n private readonly boundOnBeforeExit: (() => void) | null = null\n private readonly boundOnPageHide: (() => void) | null = null\n\n private started = false\n\n constructor(policy: SyncPolicy, callbacks: SyncSchedulerCallbacks) {\n validatePullPolicy(policy.pull)\n this.policy = policy\n this.callbacks = callbacks\n\n // Pre-bind handlers\n if (this.shouldRegisterUnload()) {\n this.boundOnVisibilityChange = this.handleVisibilityChange.bind(this)\n this.boundOnPageHide = this.handlePageHide.bind(this)\n this.boundOnBeforeExit = this.handleBeforeExit.bind(this)\n }\n }\n\n /** Current scheduler status snapshot. */\n get status(): SyncSchedulerStatus {\n return {\n state: this._state,\n lastPushAt: this._lastPushAt,\n lastPullAt: this._lastPullAt,\n lastError: this._lastError,\n pendingWrites: this.callbacks.getDirtyCount(),\n // Copied, not aliased — a status snapshot must not mutate under its reader.\n readiness: new Map(this._readiness),\n phase: this._phase,\n }\n }\n\n /** Start the scheduler — registers timers, event listeners. */\n start(): void {\n if (this.started) return\n this.started = true\n\n // Push: interval mode\n if (this.policy.push.mode === 'interval' && this.policy.push.intervalMs) {\n this.pushIntervalTimer = setInterval(() => {\n void this.executePush()\n }, this.policy.push.intervalMs)\n }\n\n // Pull: interval mode\n if (this.policy.pull.mode === 'interval') {\n this.startPullInterval()\n }\n\n // Pull: phased mode — walk the sequence in order, then settle into steady\n // state. Unawaited by design: start() is synchronous and the caller\n // (vault open) must not block on a bootstrap pull.\n if (this.policy.pull.mode === 'phased') {\n const sequence = this.policy.pull.sequence ?? []\n // Every named collection is 'cold' from the moment we start, so a reader\n // that races the first phase sees \"not ready\", never a missing entry.\n for (const collection of sequence) this._readiness.set(collection, 'cold')\n void this.runSequence(sequence)\n }\n\n // Pull: on-focus mode\n if (this.policy.pull.mode === 'on-focus' && typeof document !== 'undefined') {\n document.addEventListener('visibilitychange', this.handleFocusPull)\n }\n\n // Unload hooks\n if (this.shouldRegisterUnload()) {\n if (typeof document !== 'undefined' && this.boundOnVisibilityChange) {\n document.addEventListener('visibilitychange', this.boundOnVisibilityChange)\n }\n if (typeof globalThis.addEventListener === 'function' && this.boundOnPageHide) {\n globalThis.addEventListener('pagehide', this.boundOnPageHide)\n }\n if (typeof process !== 'undefined' && this.boundOnBeforeExit) {\n process.on('beforeExit', this.boundOnBeforeExit)\n }\n }\n }\n\n /** Stop the scheduler — clears timers, removes event listeners. */\n stop(): void {\n if (!this.started) return\n this.started = false\n\n // A sequence cut mid-phase must not leave that collection 'pulling' —\n // a reader would show a skeleton that never resolves.\n for (const [collection, state] of this._readiness) {\n if (state === 'pulling') this._readiness.set(collection, 'cold')\n }\n this._phase = null\n\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer)\n this.debounceTimer = null\n }\n if (this.pushIntervalTimer) {\n clearInterval(this.pushIntervalTimer)\n this.pushIntervalTimer = null\n }\n if (this.pullIntervalTimer) {\n clearInterval(this.pullIntervalTimer)\n this.pullIntervalTimer = null\n }\n\n // Focus pull\n if (this.policy.pull.mode === 'on-focus' && typeof document !== 'undefined') {\n document.removeEventListener('visibilitychange', this.handleFocusPull)\n }\n\n // Unload hooks\n if (typeof document !== 'undefined' && this.boundOnVisibilityChange) {\n document.removeEventListener('visibilitychange', this.boundOnVisibilityChange)\n }\n if (typeof globalThis.removeEventListener === 'function' && this.boundOnPageHide) {\n globalThis.removeEventListener('pagehide', this.boundOnPageHide)\n }\n if (typeof process !== 'undefined' && this.boundOnBeforeExit) {\n process.removeListener('beforeExit', this.boundOnBeforeExit)\n }\n }\n\n /**\n * Notify the scheduler that a local write occurred.\n * For `on-change` mode: triggers immediate push (respecting minIntervalMs).\n * For `debounce` mode: resets the debounce timer.\n * For `manual` / `interval`: no-op.\n */\n notifyChange(): void {\n if (!this.started) return\n\n if (this.policy.push.mode === 'on-change') {\n void this.executePush()\n } else if (this.policy.push.mode === 'debounce') {\n this.resetDebounce()\n }\n }\n\n /** Force an immediate push, bypassing the scheduler. */\n async forcePush(): Promise<void> {\n await this.executePush()\n }\n\n /** Force an immediate pull, bypassing the scheduler. */\n async forcePull(): Promise<void> {\n await this.executePull()\n }\n\n // ─── Internal ─────────────────────────────────────────────────────\n\n /**\n * Arm the steady-state pull timer, if the policy specifies a cadence.\n * Shared by `'interval'` mode and by `'phased'` once its sequence drains.\n */\n private startPullInterval(): void {\n if (this.pullIntervalTimer || !this.policy.pull.intervalMs) return\n this.pullIntervalTimer = setInterval(() => {\n void this.executePull()\n }, this.policy.pull.intervalMs)\n }\n\n /**\n * Pull each collection in turn, then hand over to steady state.\n *\n * Strictly sequential — running phases concurrently would defeat the\n * prioritisation that is the entire point of a sequence. Each phase is an\n * ordinary scoped pull, so a phase that reports errors does not abort the\n * ones behind it; `executePull` records the failure and the walk continues.\n * `started` is re-checked between phases so `stop()` cuts a sequence short\n * instead of letting it run on against a closed vault.\n */\n private async runSequence(sequence: readonly string[]): Promise<void> {\n for (const [i, collection] of sequence.entries()) {\n if (!this.started) return\n this._readiness.set(collection, 'pulling')\n this._phase = { index: i + 1, total: sequence.length }\n\n const outcome = await this.executePull([collection])\n\n // 'live' only on a clean phase. Anything else — reported errors, a\n // role-gated skip, a throw — goes back to 'cold', because 'live' is a\n // promise that a miss from get() is a real absence.\n this._readiness.set(collection, outcome === 'complete' ? 'live' : 'cold')\n }\n this._phase = null\n if (this.started) this.startPullInterval()\n }\n\n private async executePush(): Promise<void> {\n if (this._state === 'pushing') return // already in progress\n\n // minIntervalMs enforcement\n const minInterval = this.policy.push.minIntervalMs ?? 0\n if (minInterval > 0) {\n const elapsed = Date.now() - this._lastPushTime\n if (elapsed < minInterval) {\n // Schedule for later if debounce mode\n if (this.policy.push.mode === 'debounce') {\n this.scheduleDebounce(minInterval - elapsed)\n }\n return\n }\n }\n\n // Nothing to push\n if (this.callbacks.getDirtyCount() === 0) {\n this._state = 'idle'\n return\n }\n\n this._state = 'pushing'\n try {\n await this.callbacks.push()\n this._lastPushAt = new Date().toISOString()\n this._lastPushTime = Date.now()\n this._lastError = null\n this._state = this.callbacks.getDirtyCount() > 0 ? 'pending' : 'idle'\n } catch (err) {\n this._lastError = err instanceof Error ? err : new Error(String(err))\n this._state = 'error'\n }\n }\n\n private async executePull(collections?: readonly string[]): Promise<PullPhaseOutcome> {\n if (this._state === 'pulling') return 'incomplete'\n\n const previousState = this._state\n this._state = 'pulling'\n try {\n const outcome = await this.callbacks.pull(collections)\n this._lastPullAt = new Date().toISOString()\n this._lastError = null\n this._state = previousState === 'pending' ? 'pending' : 'idle'\n return outcome ?? 'complete'\n } catch (err) {\n this._lastError = err instanceof Error ? err : new Error(String(err))\n this._state = 'error'\n return 'incomplete'\n }\n }\n\n private resetDebounce(): void {\n if (this.debounceTimer) clearTimeout(this.debounceTimer)\n const ms = this.policy.push.debounceMs ?? 30_000\n this._state = 'pending'\n this.scheduleDebounce(ms)\n }\n\n private scheduleDebounce(ms: number): void {\n if (this.debounceTimer) clearTimeout(this.debounceTimer)\n this.debounceTimer = setTimeout(() => {\n this.debounceTimer = null\n void this.executePush()\n }, ms)\n }\n\n private shouldRegisterUnload(): boolean {\n const onUnload = this.policy.push.onUnload\n if (onUnload !== undefined) return onUnload\n return this.policy.push.mode !== 'manual'\n }\n\n // ─── Event handlers ───────────────────────────────────────────────\n\n private handleVisibilityChange(): void {\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') {\n this.fireUnloadPush()\n }\n }\n\n private handlePageHide(): void {\n this.fireUnloadPush()\n }\n\n private handleBeforeExit(): void {\n this.fireUnloadPush()\n }\n\n private handleFocusPull = (): void => {\n if (typeof document !== 'undefined' && document.visibilityState === 'visible') {\n void this.executePull()\n }\n }\n\n private fireUnloadPush(): void {\n if (this.callbacks.getDirtyCount() === 0) return\n // Best-effort synchronous-ish push on unload\n void this.callbacks.push().catch(() => {})\n }\n}\n","/**\n * Strategy seam for the optional sync engine + presence service.\n * Core imports `SyncStrategy` type-only + `NO_SYNC` stub; the real\n * `SyncEngine`, `SyncTransaction`, and `PresenceHandle` constructors\n * are only reachable via `withSync()` in `./sync-active.ts`.\n *\n * Solo apps that never configure `sync` and never call\n * `collection.presence()` ship none of the ~856 LOC behind this seam\n * (`sync.ts` + `sync-transaction.ts` + `presence.ts`).\n *\n * Note: `keyring.ts` (~746 LOC) stays in core because it's required\n * for any multi-user vault — even single-owner vaults use a keyring\n * to wrap the DEK. The team package's grant/revoke/magic-link/\n * delegation modules tree-shake naturally via direct named imports.\n *\n * Behavior under NO_SYNC:\n *\n * - **buildSyncEngine** — throws. Only fires when `createNoydb({ sync })`\n * passes a remote target.\n * - **buildSyncTransaction** — throws. Only fires when `db.transaction(vault)`\n * is called on a vault with sync configured.\n * - **buildPresence** — throws. Only fires when user code calls\n * `collection.presence()`.\n *\n * @internal\n */\n\nimport type {\n NoydbStore,\n ConflictStrategy,\n SyncTargetRole,\n} from '../kernel/types.js'\nimport type { MergeAuthority } from '../port/with/merge-authority.js'\nimport type { NoydbEventEmitter } from '../kernel/events.js'\nimport type { SyncPolicy } from '../kernel/sync-policy.js'\nimport type { SyncEngine } from './engine.js'\nimport type { SyncTransaction } from './transaction.js'\nimport type { PresenceHandle, PresenceHandleOpts } from './presence.js'\nimport type { Vault } from '../kernel/vault.js'\n\n/**\n * Options accepted by `SyncStrategy.buildSyncEngine`. Mirrors the\n * `SyncEngine` constructor verbatim — kept here so core code never\n * imports the sync module at runtime.\n *\n * @internal\n */\nexport interface BuildSyncEngineOptions {\n local: NoydbStore\n remote: NoydbStore\n vault: string\n strategy: ConflictStrategy\n emitter: NoydbEventEmitter\n syncPolicy?: SyncPolicy\n role?: SyncTargetRole\n label?: string\n /** #1042 — the merge's verify/re-stamp capability, bound to the keys by the caller. */\n mergeAuthority?: MergeAuthority\n}\n\n/**\n * @internal\n */\nexport interface SyncStrategy {\n buildSyncEngine(opts: BuildSyncEngineOptions): SyncEngine\n buildSyncTransaction(vault: Vault, engine: SyncEngine): SyncTransaction\n buildPresence<P>(opts: PresenceHandleOpts): PresenceHandle<P>\n}\n\nfunction notEnabled(op: string): Error {\n return new Error(\n `${op} requires the sync strategy. Import ` +\n '`{ withSync }` from \"@noy-db/hub/sync\" and pass it to ' +\n '`createNoydb({ syncStrategy: withSync() })`.',\n )\n}\n\n/**\n * No-sync stub. Every constructor throws with an actionable pointer\n * — there is no useful \"off\" mode for sync engine / presence /\n * sync-transaction; if the consumer reached one of these surfaces,\n * they intended to use it.\n *\n * @internal\n */\nexport const NO_SYNC: SyncStrategy = {\n buildSyncEngine() { throw notEnabled('SyncEngine') },\n buildSyncTransaction() { throw notEnabled('SyncTransaction') },\n buildPresence() { throw notEnabled('collection.presence()') },\n}\n"],"mappings":"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","names":[]}
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  buildAccessibleBundle,
3
3
  resolveAccessibleCollections
4
- } from "./chunk-NWPR2V5W.js";
4
+ } from "./chunk-ZPMXYPBR.js";
5
5
  import {
6
- sha256Hex
7
- } from "./chunk-QTDAPDDV.js";
6
+ buildRecordEnvelope
7
+ } from "./chunk-ZVAFJQZB.js";
8
8
  import {
9
- NOYDB_FORMAT_VERSION
10
- } from "./chunk-5UGEGEIJ.js";
9
+ sha256Hex
10
+ } from "./chunk-VJ4B3ZXY.js";
11
11
  import {
12
12
  ReadOnlyError
13
- } from "./chunk-I64WQSN3.js";
13
+ } from "./chunk-P7L3OQLL.js";
14
14
 
15
15
  // src/with-audit/portability/withdraw-accessible.ts
16
16
  var FROZEN_SNAPSHOTS_COLLECTION = "_frozen_snapshots";
@@ -38,7 +38,10 @@ async function freezeSnapshotOnly(vault, collections, opts) {
38
38
  vaultName,
39
39
  FROZEN_SNAPSHOTS_COLLECTION,
40
40
  withdrawalId,
41
- { _noydb: NOYDB_FORMAT_VERSION, _v: 1, _ts: frozenAt, _iv: "", _data: body, _by: opts.actorUserId },
41
+ buildRecordEnvelope(
42
+ { collection: FROZEN_SNAPSHOTS_COLLECTION, id: withdrawalId, by: opts.actorUserId, version: 1 },
43
+ { ts: frozenAt, iv: "", data: body }
44
+ ),
42
45
  0
43
46
  );
44
47
  await vault._getLedgerOrNull()?.append({
@@ -114,4 +117,4 @@ export {
114
117
  freezeAndDeleteClosure,
115
118
  withdrawAccessibleData
116
119
  };
117
- //# sourceMappingURL=chunk-P5ARVLNW.js.map
120
+ //# sourceMappingURL=chunk-TC6MBEGH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/with-audit/portability/withdraw-accessible.ts"],"sourcesContent":["/**\n * `unilateralWithdrawal`: a non-owner extracts their accessible scope\n * AND disposes of the source copy. Destructive; gated by the default-off\n * fail-closed built-in `client-unilateral-withdraw` policy (checked in the\n * UserApi wrapper).\n *\n * Two source dispositions:\n * - 'delete' — delete-closure: the records leave the source vault entirely.\n * - 'freeze' — the firm retains a cryptographically-frozen, read-only, write-once\n * snapshot (hash-pinned in the tamper-evident ledger) while the live\n * records are removed.\n *\n * Ordering guarantees no data loss: the client's re-keyed export bundle (and the\n * freeze snapshot) are produced BEFORE anything is deleted.\n *\n * The `freezeAndDeleteClosure` core is shared with the two-party approval path\n * (`request-withdrawal.ts`).\n */\nimport type { Vault } from '../../kernel/vault.js'\nimport { buildRecordEnvelope, sha256Hex } from '../../kernel/enclave/index.js'\nimport { ReadOnlyError } from '../../kernel/errors.js'\nimport { resolveAccessibleCollections, buildAccessibleBundle } from './export-accessible.js'\n\nexport const FROZEN_SNAPSHOTS_COLLECTION = '_frozen_snapshots'\nconst ENC = new TextEncoder()\n\n/** 24-hex-char random id (used for withdrawal ids + request ids). */\nexport function randomId(): string {\n const b = globalThis.crypto.getRandomValues(new Uint8Array(12))\n return Array.from(b, (x) => x.toString(16).padStart(2, '0')).join('')\n}\n\nexport interface FrozenSnapshotRef {\n readonly withdrawalId: string\n readonly sha256: string\n readonly recordCount: number\n readonly frozenAt: string\n}\n\nexport interface WithdrawAccessibleOptions {\n /** Legal/contractual basis recorded in the audit (e.g. 'gdpr-art-17'). */\n readonly legalBasis: string\n /** Re-key the exported bundle to a new owner secret. */\n readonly reKey?: { readonly secret: string }\n /** Source disposition. Default 'delete'. */\n readonly disposition?: 'delete' | 'freeze'\n readonly scope?: { readonly collections?: readonly string[] }\n /** Stable id for idempotent resume (default random). */\n readonly withdrawalId?: string\n}\n\nexport interface WithdrawResult {\n readonly bundle: Uint8Array\n readonly snapshot?: FrozenSnapshotRef\n}\n\n/**\n * Freeze a write-once, hash-pinned snapshot of the current ciphertext for\n * `collections` WITHOUT touching the live records. Returns the snapshot ref.\n *\n * This is the non-destructive core shared by two callers:\n * - `freezeAndDeleteClosure` (withdrawal, disposition `'freeze'`) — which\n * pins the snapshot and THEN delete-closures the live records.\n * - FR-6 `liberateVault` (custody) — which pins a PRE-liberation evidence\n * snapshot but PRESERVES the live data for the new owner (operational\n * continuity; liberation transfers, it does not erase).\n *\n * The snapshot put is write-once (CAS expectedVersion 0) and the ledger pin is\n * appended on success, so a crashed run re-run with the same `withdrawalId` is\n * safe. The snapshot is taken under the vault's own firm-owned DEKs — no re-key.\n */\nexport async function freezeSnapshotOnly(\n vault: Vault,\n collections: readonly string[],\n opts: { actorUserId: string; withdrawalId?: string },\n): Promise<FrozenSnapshotRef> {\n const { name: vaultName, adapter } = vault._introspectState()\n\n // Enumerate the closure (record ids per collection).\n const closure: Array<{ collection: string; id: string }> = []\n for (const c of collections) {\n for (const id of await adapter.list(vaultName, c)) closure.push({ collection: c, id })\n }\n\n const withdrawalId = opts.withdrawalId ?? `wd-${randomId()}`\n const snap: Record<string, Record<string, unknown>> = {}\n for (const { collection, id } of closure) {\n const env = await adapter.get(vaultName, collection, id)\n if (env) (snap[collection] ??= {})[id] = env\n }\n const frozenAt = new Date().toISOString()\n const body = JSON.stringify({ withdrawalId, frozenAt, by: opts.actorUserId, collections: snap })\n const sha = await sha256Hex(ENC.encode(body))\n // Write-once: expectedVersion 0 rejects an overwrite (idempotent resume).\n await adapter.put(\n vaultName,\n FROZEN_SNAPSHOTS_COLLECTION,\n withdrawalId,\n buildRecordEnvelope(\n { collection: FROZEN_SNAPSHOTS_COLLECTION, id: withdrawalId, by: opts.actorUserId, version: 1 },\n { ts: frozenAt, iv: '', data: body},\n ),\n 0,\n )\n // Hash-pin into the tamper-evident ledger — makes the snapshot provably unaltered.\n await vault._getLedgerOrNull()?.append({\n op: 'lifecycle', collection: '', id: '', version: 0, actor: opts.actorUserId, payloadHash: '',\n reason: `withdrawal-frozen-snapshot:${withdrawalId}:${sha}`,\n })\n return { withdrawalId, sha256: sha, recordCount: closure.length, frozenAt }\n}\n\n/**\n * Dispose of the source records for `collections`: optionally freeze a\n * write-once hash-pinned snapshot of the original ciphertext, then\n * delete-closure the live records. Returns the snapshot ref on freeze.\n *\n * Caller MUST have produced the portable bundle FIRST — this is destructive.\n * The delete is best-effort + idempotent (re-deleting an absent record is a\n * no-op) and the snapshot put is write-once (CAS expectedVersion 0), so a\n * crashed run re-run with the same `withdrawalId` is safe.\n */\nexport async function freezeAndDeleteClosure(\n vault: Vault,\n collections: readonly string[],\n opts: { disposition: 'delete' | 'freeze'; actorUserId: string; withdrawalId?: string },\n): Promise<FrozenSnapshotRef | undefined> {\n // freeze: pin a write-once, hash-pinned snapshot BEFORE any delete. The\n // snapshot-only core is shared with FR-6 liberation (which never deletes).\n const snapshot = opts.disposition === 'freeze'\n ? await freezeSnapshotOnly(vault, collections, {\n actorUserId: opts.actorUserId,\n ...(opts.withdrawalId ? { withdrawalId: opts.withdrawalId } : {}),\n })\n : undefined\n\n // delete-closure — only after the snapshot is durable.\n const { name: vaultName, adapter } = vault._introspectState()\n for (const c of collections) {\n for (const id of await adapter.list(vaultName, c)) {\n await vault.collection(c).delete(id)\n }\n }\n\n return snapshot\n}\n\nexport async function withdrawAccessibleData(\n vault: Vault,\n opts: WithdrawAccessibleOptions,\n): Promise<WithdrawResult> {\n const { keyring } = vault._introspectState()\n const disposition = opts.disposition ?? 'delete'\n\n // Owner-class roles hold blanket authority — they use extractPartition.\n if (keyring.role === 'owner' || keyring.role === 'admin') {\n throw new ReadOnlyError(\n 'unilateralWithdrawal is the scoped self-service path; an owner/admin should use extractPartition',\n )\n }\n // FR-6: a custodian must NOT destructively SEVER the vault. It holds the DEKs\n // and operates fully, but ownership is claimed only through the audited\n // Liberate ceremony — never by a one-sided delete/freeze withdrawal. Fires\n // BEFORE the operator rw-scope path below (where a custodian would otherwise\n // be rejected with the wrong \"requires rw access\" message).\n if (keyring.role === 'custodian') {\n throw new ReadOnlyError(\n 'a custodian cannot destructively withdraw/sever; use vault.custody.liberate for an audited ownership claim',\n )\n }\n // client/viewer are read-only by construction (see hasWritePermission): a\n // destructive withdrawal they cannot self-serve — they use the two-party\n // requestWithdrawal flow where firm authority executes the delete-closure.\n if (keyring.role === 'client' || keyring.role === 'viewer') {\n throw new ReadOnlyError(\n 'read-only role cannot self-serve a destructive withdrawal — use requestWithdrawal (two-party)',\n )\n }\n // Operator path: destructive ops need rw on the whole scope.\n const collections = resolveAccessibleCollections(keyring, opts.scope, true)\n if (!collections || collections.length === 0) {\n throw new ReadOnlyError(\n 'unilateralWithdrawal requires rw access on the withdrawn collections — use requestWithdrawal for read-only scope',\n )\n }\n\n // 1. Produce the client's re-keyed portable copy FIRST (nothing destroyed yet).\n const bundle = await buildAccessibleBundle(vault, collections, opts.reKey)\n\n // 2. + 3. freeze (optional) then delete-closure.\n const snapshot = await freezeAndDeleteClosure(vault, collections, {\n disposition, actorUserId: keyring.userId, ...(opts.withdrawalId ? { withdrawalId: opts.withdrawalId } : {}),\n })\n\n // 4. audit\n await vault._getLedgerOrNull()?.append({\n op: 'lifecycle', collection: '', id: '', version: 0, actor: keyring.userId, payloadHash: '',\n reason: `user-unilateral-withdrawal:${keyring.userId}:${disposition}:${opts.legalBasis}`,\n })\n\n return snapshot ? { bundle, snapshot } : { bundle }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAuBO,IAAM,8BAA8B;AAC3C,IAAM,MAAM,IAAI,YAAY;AAGrB,SAAS,WAAmB;AACjC,QAAM,IAAI,WAAW,OAAO,gBAAgB,IAAI,WAAW,EAAE,CAAC;AAC9D,SAAO,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AACtE;AAyCA,eAAsB,mBACpB,OACA,aACA,MAC4B;AAC5B,QAAM,EAAE,MAAM,WAAW,QAAQ,IAAI,MAAM,iBAAiB;AAG5D,QAAM,UAAqD,CAAC;AAC5D,aAAW,KAAK,aAAa;AAC3B,eAAW,MAAM,MAAM,QAAQ,KAAK,WAAW,CAAC,EAAG,SAAQ,KAAK,EAAE,YAAY,GAAG,GAAG,CAAC;AAAA,EACvF;AAEA,QAAM,eAAe,KAAK,gBAAgB,MAAM,SAAS,CAAC;AAC1D,QAAM,OAAgD,CAAC;AACvD,aAAW,EAAE,YAAY,GAAG,KAAK,SAAS;AACxC,UAAM,MAAM,MAAM,QAAQ,IAAI,WAAW,YAAY,EAAE;AACvD,QAAI,IAAK,EAAC,KAAK,UAAU,MAAM,CAAC,GAAG,EAAE,IAAI;AAAA,EAC3C;AACA,QAAM,YAAW,oBAAI,KAAK,GAAE,YAAY;AACxC,QAAM,OAAO,KAAK,UAAU,EAAE,cAAc,UAAU,IAAI,KAAK,aAAa,aAAa,KAAK,CAAC;AAC/F,QAAM,MAAM,MAAM,UAAU,IAAI,OAAO,IAAI,CAAC;AAE5C,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,MACE,EAAE,YAAY,6BAA6B,IAAI,cAAc,IAAI,KAAK,aAAa,SAAS,EAAE;AAAA,MAC9F,EAAE,IAAI,UAAU,IAAI,IAAI,MAAM,KAAI;AAAA,IACpC;AAAA,IACA;AAAA,EACF;AAEA,QAAM,MAAM,iBAAiB,GAAG,OAAO;AAAA,IACrC,IAAI;AAAA,IAAa,YAAY;AAAA,IAAI,IAAI;AAAA,IAAI,SAAS;AAAA,IAAG,OAAO,KAAK;AAAA,IAAa,aAAa;AAAA,IAC3F,QAAQ,8BAA8B,YAAY,IAAI,GAAG;AAAA,EAC3D,CAAC;AACD,SAAO,EAAE,cAAc,QAAQ,KAAK,aAAa,QAAQ,QAAQ,SAAS;AAC5E;AAYA,eAAsB,uBACpB,OACA,aACA,MACwC;AAGxC,QAAM,WAAW,KAAK,gBAAgB,WAClC,MAAM,mBAAmB,OAAO,aAAa;AAAA,IAC3C,aAAa,KAAK;AAAA,IAClB,GAAI,KAAK,eAAe,EAAE,cAAc,KAAK,aAAa,IAAI,CAAC;AAAA,EACjE,CAAC,IACD;AAGJ,QAAM,EAAE,MAAM,WAAW,QAAQ,IAAI,MAAM,iBAAiB;AAC5D,aAAW,KAAK,aAAa;AAC3B,eAAW,MAAM,MAAM,QAAQ,KAAK,WAAW,CAAC,GAAG;AACjD,YAAM,MAAM,WAAW,CAAC,EAAE,OAAO,EAAE;AAAA,IACrC;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAsB,uBACpB,OACA,MACyB;AACzB,QAAM,EAAE,QAAQ,IAAI,MAAM,iBAAiB;AAC3C,QAAM,cAAc,KAAK,eAAe;AAGxC,MAAI,QAAQ,SAAS,WAAW,QAAQ,SAAS,SAAS;AACxD,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAMA,MAAI,QAAQ,SAAS,aAAa;AAChC,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAIA,MAAI,QAAQ,SAAS,YAAY,QAAQ,SAAS,UAAU;AAC1D,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,cAAc,6BAA6B,SAAS,KAAK,OAAO,IAAI;AAC1E,MAAI,CAAC,eAAe,YAAY,WAAW,GAAG;AAC5C,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,QAAM,SAAS,MAAM,sBAAsB,OAAO,aAAa,KAAK,KAAK;AAGzE,QAAM,WAAW,MAAM,uBAAuB,OAAO,aAAa;AAAA,IAChE;AAAA,IAAa,aAAa,QAAQ;AAAA,IAAQ,GAAI,KAAK,eAAe,EAAE,cAAc,KAAK,aAAa,IAAI,CAAC;AAAA,EAC3G,CAAC;AAGD,QAAM,MAAM,iBAAiB,GAAG,OAAO;AAAA,IACrC,IAAI;AAAA,IAAa,YAAY;AAAA,IAAI,IAAI;AAAA,IAAI,SAAS;AAAA,IAAG,OAAO,QAAQ;AAAA,IAAQ,aAAa;AAAA,IACzF,QAAQ,8BAA8B,QAAQ,MAAM,IAAI,WAAW,IAAI,KAAK,UAAU;AAAA,EACxF,CAAC;AAED,SAAO,WAAW,EAAE,QAAQ,SAAS,IAAI,EAAE,OAAO;AACpD;","names":[]}
@@ -1,14 +1,15 @@
1
1
  import {
2
+ buildRecordAad,
2
3
  openEnvelopeJson
3
- } from "./chunk-BKYQNNO3.js";
4
+ } from "./chunk-D5UDZANO.js";
5
+ import {
6
+ buildRecordEnvelope
7
+ } from "./chunk-ZVAFJQZB.js";
4
8
  import {
5
9
  encrypt,
6
10
  hmacSha256Hex,
7
11
  sha256Hex
8
- } from "./chunk-QTDAPDDV.js";
9
- import {
10
- NOYDB_FORMAT_VERSION
11
- } from "./chunk-5UGEGEIJ.js";
12
+ } from "./chunk-VJ4B3ZXY.js";
12
13
 
13
14
  // src/with-audit/forget/strategy.ts
14
15
  var NO_FORGET = { subjects: {} };
@@ -43,17 +44,18 @@ async function readRefs(adapter, vault, getDEK, encrypted, key) {
43
44
  if (!env || !env._data) return [];
44
45
  if (!encrypted) return parseRefs(env._data);
45
46
  const dek = await getDEK(SUBJECT_INDEX_COLLECTION);
46
- const json = await openEnvelopeJson(env, dek);
47
+ const json = await openEnvelopeJson({ collection: SUBJECT_INDEX_COLLECTION, id: key }, env, dek);
47
48
  return parseRefs(json);
48
49
  }
49
50
  async function writeRefs(adapter, vault, getDEK, encrypted, key, refs) {
51
+ const identity = { collection: SUBJECT_INDEX_COLLECTION, id: key, version: 1 };
50
52
  let env;
51
53
  if (!encrypted) {
52
- env = { _noydb: NOYDB_FORMAT_VERSION, _v: 1, _ts: (/* @__PURE__ */ new Date()).toISOString(), _iv: "", _data: JSON.stringify(refs) };
54
+ env = buildRecordEnvelope(identity, { iv: "", data: JSON.stringify(refs) });
53
55
  } else {
54
56
  const dek = await getDEK(SUBJECT_INDEX_COLLECTION);
55
- const { iv, data } = await encrypt(serializeRefs(refs), dek);
56
- env = { _noydb: NOYDB_FORMAT_VERSION, _v: 1, _ts: (/* @__PURE__ */ new Date()).toISOString(), _iv: iv, _data: data };
57
+ const { iv, data } = await encrypt(serializeRefs(refs), dek, buildRecordAad(identity));
58
+ env = buildRecordEnvelope(identity, { iv, data });
57
59
  }
58
60
  await adapter.put(vault, SUBJECT_INDEX_COLLECTION, key, env);
59
61
  }
@@ -147,4 +149,4 @@ export {
147
149
  coerceSubjectId,
148
150
  readDottedPath
149
151
  };
150
- //# sourceMappingURL=chunk-D6E5N2O3.js.map
152
+ //# sourceMappingURL=chunk-TENLCGYD.js.map