@noy-db/hub 0.2.0-pre.16 → 0.2.0-pre.17

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 (286) hide show
  1. package/dist/aggregate/index.cjs.map +1 -1
  2. package/dist/aggregate/index.d.cts +3 -2
  3. package/dist/aggregate/index.d.ts +3 -2
  4. package/dist/aggregate/index.js +4 -4
  5. package/dist/attestation/index.cjs.map +1 -1
  6. package/dist/attestation/index.d.cts +5 -3
  7. package/dist/attestation/index.d.ts +5 -3
  8. package/dist/attestation/index.js +6 -6
  9. package/dist/blobs/index.cjs +226 -11
  10. package/dist/blobs/index.cjs.map +1 -1
  11. package/dist/blobs/index.d.cts +6 -4
  12. package/dist/blobs/index.d.ts +6 -4
  13. package/dist/blobs/index.js +6 -5
  14. package/dist/blobs/index.js.map +1 -1
  15. package/dist/bundle/index.cjs +1087 -95
  16. package/dist/bundle/index.cjs.map +1 -1
  17. package/dist/bundle/index.d.cts +7 -5
  18. package/dist/bundle/index.d.ts +7 -5
  19. package/dist/bundle/index.js +21 -10
  20. package/dist/bundle/index.js.map +1 -1
  21. package/dist/{chunk-G4SCICH5.js → chunk-2FU2FTXD.js} +2 -2
  22. package/dist/{chunk-JD3OZAI4.js → chunk-3G3W65EQ.js} +2 -2
  23. package/dist/{chunk-XWH4MXIU.js → chunk-5AXTH4QZ.js} +2 -2
  24. package/dist/{chunk-4TBBMHVC.js → chunk-5LIROIDM.js} +2 -2
  25. package/dist/{chunk-L2BNJ6HM.js → chunk-7H2GEJ3O.js} +3 -3
  26. package/dist/{chunk-GNI5STXQ.js → chunk-AEIKD3PP.js} +52 -38
  27. package/dist/chunk-AEIKD3PP.js.map +1 -0
  28. package/dist/{chunk-QSUK7YWK.js → chunk-BH3X5L6A.js} +4 -4
  29. package/dist/{chunk-BQ65SS5A.js → chunk-BJSLBUJ7.js} +2 -2
  30. package/dist/{chunk-FFXM3ZIF.js → chunk-BL5GYANC.js} +3 -3
  31. package/dist/{chunk-6H2ZUNR7.js → chunk-BSZOCSDZ.js} +4 -4
  32. package/dist/{chunk-ZNQYHJXX.js → chunk-C3HYQPV4.js} +2 -2
  33. package/dist/{chunk-E2CDVKMH.js → chunk-CD2AVTEM.js} +5 -5
  34. package/dist/{chunk-667MB6AH.js → chunk-D77ZQSQQ.js} +769 -131
  35. package/dist/chunk-D77ZQSQQ.js.map +1 -0
  36. package/dist/{chunk-BR3AMFGS.js → chunk-DWEBTE2W.js} +5 -5
  37. package/dist/{chunk-Z4DO7YSI.js → chunk-DYYYUW5D.js} +2 -2
  38. package/dist/{chunk-SCJPI4Z5.js → chunk-E77UKJYL.js} +5 -5
  39. package/dist/{chunk-OMAMZKKD.js → chunk-F4G63NTZ.js} +2 -2
  40. package/dist/{chunk-TKIY625R.js → chunk-FEJDVE3Z.js} +2 -2
  41. package/dist/{chunk-7Z7KSVA5.js → chunk-GP3SDSH2.js} +2 -2
  42. package/dist/{chunk-IQLVUT37.js → chunk-H2MRGONI.js} +2 -2
  43. package/dist/{chunk-DUREQF5W.js → chunk-HGVSHKZW.js} +8 -5
  44. package/dist/chunk-HGVSHKZW.js.map +1 -0
  45. package/dist/chunk-I5IUYN7B.js +125 -0
  46. package/dist/chunk-I5IUYN7B.js.map +1 -0
  47. package/dist/{chunk-CJORTUJ2.js → chunk-J7RWBXFY.js} +2 -2
  48. package/dist/{chunk-AAVWKNZW.js → chunk-JDWE6JMX.js} +2 -2
  49. package/dist/{chunk-XL35NSEN.js → chunk-KCEHMDZF.js} +3 -3
  50. package/dist/{chunk-TS26M2SB.js → chunk-M476FOQ7.js} +2 -2
  51. package/dist/{chunk-F4OJZIWQ.js → chunk-NBBMMJ2H.js} +4 -4
  52. package/dist/{chunk-CZI2A4MQ.js → chunk-NYSYPFXJ.js} +3 -3
  53. package/dist/{chunk-OQSRJG6A.js → chunk-PDULVIBY.js} +2 -2
  54. package/dist/{chunk-Z6FNBOTC.js → chunk-PDVP3C2I.js} +1 -1
  55. package/dist/{chunk-Z6FNBOTC.js.map → chunk-PDVP3C2I.js.map} +1 -1
  56. package/dist/{chunk-DLZ2ONOD.js → chunk-QHM6XEAH.js} +6 -6
  57. package/dist/{chunk-HBXJ37ZY.js → chunk-QO6RGLLD.js} +4 -4
  58. package/dist/{chunk-7BQ4QWYX.js → chunk-ROPJVUG3.js} +23 -6
  59. package/dist/chunk-ROPJVUG3.js.map +1 -0
  60. package/dist/{chunk-42FEUPZQ.js → chunk-ROVO6NPJ.js} +2 -2
  61. package/dist/{chunk-6RR3MNMG.js → chunk-SHX5QBCI.js} +3 -3
  62. package/dist/{chunk-F3BPIPLS.js → chunk-SISBMAPO.js} +1 -1
  63. package/dist/chunk-SISBMAPO.js.map +1 -0
  64. package/dist/{chunk-3YWP3WBP.js → chunk-SNMJ7SB3.js} +5 -5
  65. package/dist/{chunk-IXBIFDEW.js → chunk-TIDXB5DF.js} +4 -4
  66. package/dist/chunk-U5QCMH3W.js +151 -0
  67. package/dist/chunk-U5QCMH3W.js.map +1 -0
  68. package/dist/{chunk-YULZKK4F.js → chunk-UNTGHX5A.js} +37 -2
  69. package/dist/chunk-UNTGHX5A.js.map +1 -0
  70. package/dist/{chunk-FWPKCXTN.js → chunk-WIAOUFFB.js} +2 -2
  71. package/dist/{chunk-KABJXG2F.js → chunk-WV7WV6JO.js} +195 -17
  72. package/dist/chunk-WV7WV6JO.js.map +1 -0
  73. package/dist/{chunk-X73VS74Y.js → chunk-XJV6OB4D.js} +2 -2
  74. package/dist/{chunk-VLMPU56Q.js → chunk-XMHUK5PN.js} +2 -2
  75. package/dist/{chunk-BI6ETQPF.js → chunk-XMVHEWF6.js} +4 -4
  76. package/dist/{chunk-HOR4R722.js → chunk-XPIHJ34I.js} +30 -4
  77. package/dist/chunk-XPIHJ34I.js.map +1 -0
  78. package/dist/{chunk-OB2ZJQ2D.js → chunk-YYVZYTWW.js} +3 -3
  79. package/dist/{chunk-535SSHBS.js → chunk-ZEGSDPB7.js} +81 -2
  80. package/dist/chunk-ZEGSDPB7.js.map +1 -0
  81. package/dist/{chunk-QVIEAYTP.js → chunk-ZNGPEV5J.js} +3 -3
  82. package/dist/consent/index.cjs.map +1 -1
  83. package/dist/consent/index.d.cts +6 -4
  84. package/dist/consent/index.d.ts +6 -4
  85. package/dist/consent/index.js +3 -3
  86. package/dist/{crypto-QXQOHMHF.js → crypto-7BN2HDWG.js} +7 -3
  87. package/dist/{delegation-NIQ43IPU.js → delegation-MGH5SODX.js} +5 -5
  88. package/dist/derivations/index.cjs.map +1 -1
  89. package/dist/derivations/index.d.cts +7 -5
  90. package/dist/derivations/index.d.ts +7 -5
  91. package/dist/derivations/index.js +4 -4
  92. package/dist/{dev-unlock-DR3upLd1.d.ts → dev-unlock-CI1ijTML.d.ts} +1 -1
  93. package/dist/{dev-unlock-8XzcD2Z4.d.cts → dev-unlock-iXbYFAWl.d.cts} +1 -1
  94. package/dist/{strategy-BtW8fAjz.d.ts → errors-Dz64FA65.d.cts} +98 -727
  95. package/dist/{strategy-BtW8fAjz.d.cts → errors-Dz64FA65.d.ts} +98 -727
  96. package/dist/executor-3W63Y44O.js +11 -0
  97. package/dist/executor-CFFWPWBJ.js +8 -0
  98. package/dist/executor-VDQQOR4F.js +8 -0
  99. package/dist/{fanout-sidecar-67CMI3UT.js → fanout-sidecar-FIJJ46YG.js} +2 -2
  100. package/dist/forget/index.cjs +43 -0
  101. package/dist/forget/index.cjs.map +1 -0
  102. package/dist/forget/index.d.cts +1 -0
  103. package/dist/forget/index.d.ts +1 -0
  104. package/dist/forget/index.js +14 -0
  105. package/dist/guards/index.cjs.map +1 -1
  106. package/dist/guards/index.d.cts +7 -5
  107. package/dist/guards/index.d.ts +7 -5
  108. package/dist/guards/index.js +6 -6
  109. package/dist/{hash-CDjye9KV.d.ts → hash-blk7Bkes.d.ts} +1 -1
  110. package/dist/{hash-DuQ88_5W.d.cts → hash-tEcM5fnv.d.cts} +1 -1
  111. package/dist/history/index.cjs +27 -4
  112. package/dist/history/index.cjs.map +1 -1
  113. package/dist/history/index.d.cts +7 -5
  114. package/dist/history/index.d.ts +7 -5
  115. package/dist/history/index.js +9 -7
  116. package/dist/history/index.js.map +1 -1
  117. package/dist/i18n/index.cjs +53 -0
  118. package/dist/i18n/index.cjs.map +1 -1
  119. package/dist/i18n/index.d.cts +6 -4
  120. package/dist/i18n/index.d.ts +6 -4
  121. package/dist/i18n/index.js +16 -8
  122. package/dist/i18n/index.js.map +1 -1
  123. package/dist/{immutable-guard-Dov3WvwF.d.ts → immutable-guard-B5M95nbq.d.ts} +1 -1
  124. package/dist/{immutable-guard-CRPvu24K.d.cts → immutable-guard-qN3zF8o1.d.cts} +1 -1
  125. package/dist/index-C-SSRIxP.d.cts +348 -0
  126. package/dist/index-C-SSRIxP.d.ts +348 -0
  127. package/dist/{index-nP99bXLg.d.ts → index-DpU6KWof.d.ts} +9 -1
  128. package/dist/{index-C8Bk3-VF.d.cts → index-u-kWzSrL.d.cts} +9 -1
  129. package/dist/index.cjs +7271 -6079
  130. package/dist/index.cjs.map +1 -1
  131. package/dist/index.d.cts +15 -12
  132. package/dist/index.d.ts +15 -12
  133. package/dist/index.js +130 -106
  134. package/dist/index.js.map +1 -1
  135. package/dist/indexing/index.cjs.map +1 -1
  136. package/dist/indexing/index.js +4 -4
  137. package/dist/issue-TTMGHQ2J.js +12 -0
  138. package/dist/{ledger-A3LL253R.js → ledger-LFVLHE5H.js} +6 -6
  139. package/dist/materialized-views/index.cjs.map +1 -1
  140. package/dist/materialized-views/index.d.cts +7 -5
  141. package/dist/materialized-views/index.d.ts +7 -5
  142. package/dist/materialized-views/index.js +12 -12
  143. package/dist/noydb-36S6GQNC.js +37 -0
  144. package/dist/overlay-views/index.cjs.map +1 -1
  145. package/dist/overlay-views/index.d.cts +7 -5
  146. package/dist/overlay-views/index.d.ts +7 -5
  147. package/dist/overlay-views/index.js +4 -4
  148. package/dist/periods/index.cjs.map +1 -1
  149. package/dist/periods/index.d.cts +6 -4
  150. package/dist/periods/index.d.ts +6 -4
  151. package/dist/periods/index.js +6 -6
  152. package/dist/{public-envelope-YP2UWMLG.js → public-envelope-RXZNP3V6.js} +4 -4
  153. package/dist/query/index.cjs +4 -1
  154. package/dist/query/index.cjs.map +1 -1
  155. package/dist/query/index.d.cts +3 -2
  156. package/dist/query/index.d.ts +3 -2
  157. package/dist/query/index.js +6 -6
  158. package/dist/registry-3YFLZ7WD.js +8 -0
  159. package/dist/{registry-UTA4CLQS.js → registry-SECUWSGY.js} +3 -3
  160. package/dist/registry-TGZISEWC.js +8 -0
  161. package/dist/{revoke-HNMQZSCL.js → revoke-B54H2S2W.js} +6 -6
  162. package/dist/sealed-record/index.cjs +139 -0
  163. package/dist/sealed-record/index.cjs.map +1 -0
  164. package/dist/sealed-record/index.d.cts +123 -0
  165. package/dist/sealed-record/index.d.ts +123 -0
  166. package/dist/sealed-record/index.js +42 -0
  167. package/dist/sealed-record/index.js.map +1 -0
  168. package/dist/session/index.cjs.map +1 -1
  169. package/dist/session/index.d.cts +7 -5
  170. package/dist/session/index.d.ts +7 -5
  171. package/dist/session/index.js +3 -3
  172. package/dist/shadow/index.cjs.map +1 -1
  173. package/dist/shadow/index.d.cts +6 -4
  174. package/dist/shadow/index.d.ts +6 -4
  175. package/dist/shadow/index.js +2 -2
  176. package/dist/{signer-DCMNKXSF.js → signer-YSXZT574.js} +5 -5
  177. package/dist/snapshots/index.cjs.map +1 -1
  178. package/dist/snapshots/index.d.cts +6 -4
  179. package/dist/snapshots/index.d.ts +6 -4
  180. package/dist/snapshots/index.js +4 -4
  181. package/dist/{stale-W5PQTRYH.js → stale-TOA36SRK.js} +2 -2
  182. package/dist/stale-TOA36SRK.js.map +1 -0
  183. package/dist/{state-vault-TMXZRTY5.js → state-vault-W2OEABNO.js} +3 -3
  184. package/dist/store/index.cjs.map +1 -1
  185. package/dist/store/index.d.cts +6 -4
  186. package/dist/store/index.d.ts +6 -4
  187. package/dist/store/index.js +2 -2
  188. package/dist/strategy-4M9jo172.d.ts +739 -0
  189. package/dist/strategy-CLC1j79g.d.cts +739 -0
  190. package/dist/sync/index.cjs.map +1 -1
  191. package/dist/sync/index.d.cts +5 -3
  192. package/dist/sync/index.d.ts +5 -3
  193. package/dist/sync/index.js +4 -4
  194. package/dist/team/index.cjs.map +1 -1
  195. package/dist/team/index.d.cts +6 -4
  196. package/dist/team/index.d.ts +6 -4
  197. package/dist/team/index.js +8 -8
  198. package/dist/tx/index.cjs.map +1 -1
  199. package/dist/tx/index.d.cts +6 -4
  200. package/dist/tx/index.d.ts +6 -4
  201. package/dist/tx/index.js +3 -3
  202. package/dist/{types-DrmBTscX.d.ts → types-CljIHm_J.d.ts} +789 -500
  203. package/dist/{types-Bze6vkwm.d.cts → types-CrSpRDuG.d.cts} +789 -500
  204. package/dist/{ulid-DbBVrNSt.d.ts → ulid-CWfL2Vfv.d.ts} +1 -1
  205. package/dist/{ulid-DfZlAh0u.d.cts → ulid-CrI7PPbA.d.cts} +1 -1
  206. package/dist/util/index.cjs.map +1 -1
  207. package/dist/util/index.js +1 -1
  208. package/dist/{vault-group-DX2HFQMX.js → vault-group-DHAHFX2A.js} +4 -4
  209. package/dist/{with-derivation-_lySGdlm.d.ts → with-derivation-BZ2y4bzF.d.ts} +1 -1
  210. package/dist/{with-derivation-CCqAchD5.d.cts → with-derivation-Bozs8DmD.d.cts} +1 -1
  211. package/dist/{with-materialized-view-QT1Tp7NO.d.ts → with-materialized-view-B892zYZV.d.ts} +1 -1
  212. package/dist/{with-materialized-view--4PsvMDu.d.cts → with-materialized-view-NzF71cG_.d.cts} +1 -1
  213. package/dist/{with-overlayed-view-BEXfpzSb.d.ts → with-overlayed-view-CR6m7CHe.d.ts} +1 -1
  214. package/dist/{with-overlayed-view-DlH5qmeB.d.cts → with-overlayed-view-UI8qSGL4.d.cts} +1 -1
  215. package/package.json +23 -3
  216. package/dist/chunk-535SSHBS.js.map +0 -1
  217. package/dist/chunk-667MB6AH.js.map +0 -1
  218. package/dist/chunk-7BQ4QWYX.js.map +0 -1
  219. package/dist/chunk-DUREQF5W.js.map +0 -1
  220. package/dist/chunk-F3BPIPLS.js.map +0 -1
  221. package/dist/chunk-GNI5STXQ.js.map +0 -1
  222. package/dist/chunk-HOR4R722.js.map +0 -1
  223. package/dist/chunk-KABJXG2F.js.map +0 -1
  224. package/dist/chunk-YULZKK4F.js.map +0 -1
  225. package/dist/executor-6ZDSDZ6V.js +0 -8
  226. package/dist/executor-AZLS3KBK.js +0 -11
  227. package/dist/executor-IDZDAFNH.js +0 -8
  228. package/dist/issue-RZP3VI6O.js +0 -12
  229. package/dist/noydb-WCMY2ZOW.js +0 -35
  230. package/dist/registry-EB6SISTA.js +0 -8
  231. package/dist/registry-IUZQVVBB.js +0 -8
  232. /package/dist/{chunk-G4SCICH5.js.map → chunk-2FU2FTXD.js.map} +0 -0
  233. /package/dist/{chunk-JD3OZAI4.js.map → chunk-3G3W65EQ.js.map} +0 -0
  234. /package/dist/{chunk-XWH4MXIU.js.map → chunk-5AXTH4QZ.js.map} +0 -0
  235. /package/dist/{chunk-4TBBMHVC.js.map → chunk-5LIROIDM.js.map} +0 -0
  236. /package/dist/{chunk-L2BNJ6HM.js.map → chunk-7H2GEJ3O.js.map} +0 -0
  237. /package/dist/{chunk-QSUK7YWK.js.map → chunk-BH3X5L6A.js.map} +0 -0
  238. /package/dist/{chunk-BQ65SS5A.js.map → chunk-BJSLBUJ7.js.map} +0 -0
  239. /package/dist/{chunk-FFXM3ZIF.js.map → chunk-BL5GYANC.js.map} +0 -0
  240. /package/dist/{chunk-6H2ZUNR7.js.map → chunk-BSZOCSDZ.js.map} +0 -0
  241. /package/dist/{chunk-ZNQYHJXX.js.map → chunk-C3HYQPV4.js.map} +0 -0
  242. /package/dist/{chunk-E2CDVKMH.js.map → chunk-CD2AVTEM.js.map} +0 -0
  243. /package/dist/{chunk-BR3AMFGS.js.map → chunk-DWEBTE2W.js.map} +0 -0
  244. /package/dist/{chunk-Z4DO7YSI.js.map → chunk-DYYYUW5D.js.map} +0 -0
  245. /package/dist/{chunk-SCJPI4Z5.js.map → chunk-E77UKJYL.js.map} +0 -0
  246. /package/dist/{chunk-OMAMZKKD.js.map → chunk-F4G63NTZ.js.map} +0 -0
  247. /package/dist/{chunk-TKIY625R.js.map → chunk-FEJDVE3Z.js.map} +0 -0
  248. /package/dist/{chunk-7Z7KSVA5.js.map → chunk-GP3SDSH2.js.map} +0 -0
  249. /package/dist/{chunk-IQLVUT37.js.map → chunk-H2MRGONI.js.map} +0 -0
  250. /package/dist/{chunk-CJORTUJ2.js.map → chunk-J7RWBXFY.js.map} +0 -0
  251. /package/dist/{chunk-AAVWKNZW.js.map → chunk-JDWE6JMX.js.map} +0 -0
  252. /package/dist/{chunk-XL35NSEN.js.map → chunk-KCEHMDZF.js.map} +0 -0
  253. /package/dist/{chunk-TS26M2SB.js.map → chunk-M476FOQ7.js.map} +0 -0
  254. /package/dist/{chunk-F4OJZIWQ.js.map → chunk-NBBMMJ2H.js.map} +0 -0
  255. /package/dist/{chunk-CZI2A4MQ.js.map → chunk-NYSYPFXJ.js.map} +0 -0
  256. /package/dist/{chunk-OQSRJG6A.js.map → chunk-PDULVIBY.js.map} +0 -0
  257. /package/dist/{chunk-DLZ2ONOD.js.map → chunk-QHM6XEAH.js.map} +0 -0
  258. /package/dist/{chunk-HBXJ37ZY.js.map → chunk-QO6RGLLD.js.map} +0 -0
  259. /package/dist/{chunk-42FEUPZQ.js.map → chunk-ROVO6NPJ.js.map} +0 -0
  260. /package/dist/{chunk-6RR3MNMG.js.map → chunk-SHX5QBCI.js.map} +0 -0
  261. /package/dist/{chunk-3YWP3WBP.js.map → chunk-SNMJ7SB3.js.map} +0 -0
  262. /package/dist/{chunk-IXBIFDEW.js.map → chunk-TIDXB5DF.js.map} +0 -0
  263. /package/dist/{chunk-FWPKCXTN.js.map → chunk-WIAOUFFB.js.map} +0 -0
  264. /package/dist/{chunk-X73VS74Y.js.map → chunk-XJV6OB4D.js.map} +0 -0
  265. /package/dist/{chunk-VLMPU56Q.js.map → chunk-XMHUK5PN.js.map} +0 -0
  266. /package/dist/{chunk-BI6ETQPF.js.map → chunk-XMVHEWF6.js.map} +0 -0
  267. /package/dist/{chunk-OB2ZJQ2D.js.map → chunk-YYVZYTWW.js.map} +0 -0
  268. /package/dist/{chunk-QVIEAYTP.js.map → chunk-ZNGPEV5J.js.map} +0 -0
  269. /package/dist/{crypto-QXQOHMHF.js.map → crypto-7BN2HDWG.js.map} +0 -0
  270. /package/dist/{delegation-NIQ43IPU.js.map → delegation-MGH5SODX.js.map} +0 -0
  271. /package/dist/{executor-6ZDSDZ6V.js.map → executor-3W63Y44O.js.map} +0 -0
  272. /package/dist/{executor-AZLS3KBK.js.map → executor-CFFWPWBJ.js.map} +0 -0
  273. /package/dist/{executor-IDZDAFNH.js.map → executor-VDQQOR4F.js.map} +0 -0
  274. /package/dist/{fanout-sidecar-67CMI3UT.js.map → fanout-sidecar-FIJJ46YG.js.map} +0 -0
  275. /package/dist/{issue-RZP3VI6O.js.map → forget/index.js.map} +0 -0
  276. /package/dist/{ledger-A3LL253R.js.map → issue-TTMGHQ2J.js.map} +0 -0
  277. /package/dist/{noydb-WCMY2ZOW.js.map → ledger-LFVLHE5H.js.map} +0 -0
  278. /package/dist/{public-envelope-YP2UWMLG.js.map → noydb-36S6GQNC.js.map} +0 -0
  279. /package/dist/{registry-EB6SISTA.js.map → public-envelope-RXZNP3V6.js.map} +0 -0
  280. /package/dist/{registry-IUZQVVBB.js.map → registry-3YFLZ7WD.js.map} +0 -0
  281. /package/dist/{registry-UTA4CLQS.js.map → registry-SECUWSGY.js.map} +0 -0
  282. /package/dist/{revoke-HNMQZSCL.js.map → registry-TGZISEWC.js.map} +0 -0
  283. /package/dist/{signer-DCMNKXSF.js.map → revoke-B54H2S2W.js.map} +0 -0
  284. /package/dist/{stale-W5PQTRYH.js.map → signer-YSXZT574.js.map} +0 -0
  285. /package/dist/{state-vault-TMXZRTY5.js.map → state-vault-W2OEABNO.js.map} +0 -0
  286. /package/dist/{vault-group-DX2HFQMX.js.map → vault-group-DHAHFX2A.js.map} +0 -0
@@ -31,7 +31,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
31
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
32
 
33
33
  // src/errors.ts
34
- var NoydbError, DecryptionError, TamperedError, InvalidKeyError, KeyringCorruptError, NoAccessError, ReadOnlyError, PermissionDeniedError, ExportCapabilityError, KeyringExpiredError, ImportCapabilityError, StoreCapabilityError, PrivilegeEscalationError, ReservedVaultNameError, FieldFrozenError, InvariantError, AmendmentForbiddenError, TierNotGrantedError, ElevationExpiredError, AlreadyElevatedError, TierDemoteDeniedError, DelegationTargetMissingError, ConflictError, LedgerContentionError, SequenceContentionError, SequenceOfflineError, NumberingUncertaintyError, BundleVersionConflictError, ValidationError, SchemaValidationError, SchemaUpdateError, SchemaFenceError, MigrationRequiredError, QuiesceTimeoutError, GroupCardinalityError, IndexRequiredError, UniqueConstraintError, UnsupportedIndexOptionError, IndexWriteFailureError, BundleIntegrityError, BundleSealMismatchError, ReservedCollectionNameError, LocaleNotSpecifiedError, TranslatorNotConfiguredError, BackupLedgerError, BackupCorruptedError, PartitionExtractionError, TransferSealError, AdoptionStateError, AttestationError, JoinTooLargeError, CrossJoinTooLargeError, CrossJoinSourceUnknownError, DanglingReferenceError, DerivationCycleError, DerivationOutputShapeError, DerivationCapExceededError, MaterializedViewCycleError, MaterializedViewSourceUnknownError, MaterializedViewTooLargeError, OverlayBaseIsVirtualError, OverlayCollectionUnavailableError, OverlayNameCollisionError, OverlayIdMismatchError, UnknownShardError, ShardProvisioningError, CrossShardJoinError, VaultTemplateNotFoundError;
34
+ var NoydbError, DecryptionError, TamperedError, InvalidKeyError, KeyringCorruptError, NoAccessError, ReadOnlyError, PermissionDeniedError, ExportCapabilityError, KeyringExpiredError, ImportCapabilityError, StoreCapabilityError, PrivilegeEscalationError, ReservedVaultNameError, FieldFrozenError, InvariantError, AmendmentForbiddenError, TierNotGrantedError, ElevationExpiredError, AlreadyElevatedError, TierDemoteDeniedError, DelegationTargetMissingError, ConflictError, LedgerContentionError, SequenceContentionError, SequenceOfflineError, NumberingUncertaintyError, BundleVersionConflictError, ValidationError, SchemaValidationError, SchemaUpdateError, SchemaFenceError, MigrationRequiredError, QuiesceTimeoutError, GroupCardinalityError, IndexRequiredError, UniqueConstraintError, UnsupportedIndexOptionError, IndexWriteFailureError, BundleIntegrityError, BundleSealMismatchError, ReservedCollectionNameError, LocaleNotSpecifiedError, StaticDictReadonlyError, UnknownDictCodeError, TranslatorNotConfiguredError, BackupLedgerError, BackupCorruptedError, PartitionExtractionError, TransferSealError, AdoptionStateError, AttestationError, JoinTooLargeError, CrossJoinTooLargeError, CrossJoinSourceUnknownError, DanglingReferenceError, DerivationCycleError, DerivationOutputShapeError, DerivationCapExceededError, MaterializedViewCycleError, MaterializedViewSourceUnknownError, MaterializedViewTooLargeError, OverlayBaseIsVirtualError, OverlayCollectionUnavailableError, OverlayNameCollisionError, OverlayIdMismatchError, UnknownShardError, ShardProvisioningError, CrossShardJoinError, VaultTemplateNotFoundError, ForgetStrategyNotConfiguredError, RecordCekNotFoundError;
35
35
  var init_errors = __esm({
36
36
  "src/errors.ts"() {
37
37
  "use strict";
@@ -485,6 +485,36 @@ Resolutions:
485
485
  this.field = field;
486
486
  }
487
487
  };
488
+ StaticDictReadonlyError = class extends NoydbError {
489
+ /** The static dictionary name that was the target of the mutation. */
490
+ dictionaryName;
491
+ constructor(dictionaryName) {
492
+ super(
493
+ "STATIC_DICT_READONLY",
494
+ `Dictionary "${dictionaryName}" is a staticDict \u2014 its labels are code constants with no mutation surface. put/putAll/rename/delete are not supported; change the label in the staticDict() table and redeploy.`
495
+ );
496
+ this.name = "StaticDictReadonlyError";
497
+ this.dictionaryName = dictionaryName;
498
+ }
499
+ };
500
+ UnknownDictCodeError = class extends NoydbError {
501
+ /** The static dictionary name. */
502
+ dictionaryName;
503
+ /** The field that carried the unknown code. */
504
+ field;
505
+ /** The offending code value. */
506
+ code;
507
+ constructor(dictionaryName, field, code) {
508
+ super(
509
+ "UNKNOWN_DICT_CODE",
510
+ `Field "${field}": code "${code}" is not a known key of staticDict "${dictionaryName}". Use a declared code, or pass { validateCodes: false } on the descriptor to allow open codes.`
511
+ );
512
+ this.name = "UnknownDictCodeError";
513
+ this.dictionaryName = dictionaryName;
514
+ this.field = field;
515
+ this.code = code;
516
+ }
517
+ };
488
518
  TranslatorNotConfiguredError = class extends NoydbError {
489
519
  /** The field that requested auto-translation. */
490
520
  field;
@@ -763,6 +793,25 @@ Resolutions:
763
793
  this.templateName = templateName;
764
794
  }
765
795
  };
796
+ ForgetStrategyNotConfiguredError = class extends NoydbError {
797
+ constructor(message = 'vault.forget() requires a forget strategy. Pass `forgetStrategy: withForgetCascade({ subjects: { <collection>: <subjectField> } })` from "@noy-db/hub/forget" to createNoydb().') {
798
+ super("FORGET_NOT_CONFIGURED", message);
799
+ this.name = "ForgetStrategyNotConfiguredError";
800
+ }
801
+ };
802
+ RecordCekNotFoundError = class extends NoydbError {
803
+ collection;
804
+ id;
805
+ constructor(collection, id) {
806
+ super(
807
+ "RECORD_CEK_NOT_FOUND",
808
+ `No per-record CEK for ${collection}/${id}. The record is missing, or its collection was not opened with { perRecordKeys: true } \u2014 only per-record-key records carry a sealable CEK.`
809
+ );
810
+ this.name = "RecordCekNotFoundError";
811
+ this.collection = collection;
812
+ this.id = id;
813
+ }
814
+ };
766
815
  }
767
816
  });
768
817
 
@@ -1016,6 +1065,39 @@ async function unwrapKey(wrappedBase64, kek) {
1016
1065
  throw new InvalidKeyError();
1017
1066
  }
1018
1067
  }
1068
+ async function asKwKey(dek) {
1069
+ const rawDek = await subtle.exportKey("raw", dek);
1070
+ const hkdfKey = await subtle.importKey("raw", rawDek, "HKDF", false, ["deriveBits"]);
1071
+ const salt = new TextEncoder().encode("noydb-cek-wrap");
1072
+ const info = new TextEncoder().encode("v1");
1073
+ const bits = await subtle.deriveBits(
1074
+ { name: "HKDF", hash: "SHA-256", salt, info },
1075
+ hkdfKey,
1076
+ KEY_BITS
1077
+ );
1078
+ return subtle.importKey("raw", bits, "AES-KW", false, ["wrapKey", "unwrapKey"]);
1079
+ }
1080
+ async function wrapCek(cek, dek) {
1081
+ const kw = await asKwKey(dek);
1082
+ const wrapped = await subtle.wrapKey("raw", cek, kw, "AES-KW");
1083
+ return bufferToBase64(wrapped);
1084
+ }
1085
+ async function unwrapCek(wrappedBase64, dek) {
1086
+ const kw = await asKwKey(dek);
1087
+ try {
1088
+ return await subtle.unwrapKey(
1089
+ "raw",
1090
+ base64ToBuffer(wrappedBase64),
1091
+ kw,
1092
+ "AES-KW",
1093
+ { name: "AES-GCM", length: KEY_BITS },
1094
+ true,
1095
+ ["encrypt", "decrypt"]
1096
+ );
1097
+ } catch {
1098
+ throw new InvalidKeyError();
1099
+ }
1100
+ }
1019
1101
  async function encrypt(plaintext, dek) {
1020
1102
  const iv = generateIV();
1021
1103
  const encoded = new TextEncoder().encode(plaintext);
@@ -1112,6 +1194,163 @@ var init_crypto = __esm({
1112
1194
  }
1113
1195
  });
1114
1196
 
1197
+ // src/record-keys/tombstone.ts
1198
+ function isTombstone(envelope, encrypted) {
1199
+ if (!encrypted) return false;
1200
+ return !envelope._data && envelope._cek === void 0;
1201
+ }
1202
+ function buildTombstone(version, actor) {
1203
+ return {
1204
+ _noydb: NOYDB_FORMAT_VERSION,
1205
+ _v: version,
1206
+ _ts: (/* @__PURE__ */ new Date()).toISOString(),
1207
+ _iv: "",
1208
+ _data: "",
1209
+ ...actor ? { _by: actor } : {}
1210
+ };
1211
+ }
1212
+ var init_tombstone = __esm({
1213
+ "src/record-keys/tombstone.ts"() {
1214
+ "use strict";
1215
+ init_types();
1216
+ }
1217
+ });
1218
+
1219
+ // src/record-keys/lifecycle.ts
1220
+ async function resolveStableCek(deps, id) {
1221
+ const cached = deps.cache?.get(id);
1222
+ if (cached) return cached;
1223
+ const live = await deps.getLive(id);
1224
+ if (live?._cek !== void 0) {
1225
+ const cek = await unwrapCek(live._cek, await deps.getDEK());
1226
+ deps.cache?.set(id, cek, 1);
1227
+ return cek;
1228
+ }
1229
+ const fresh = await generateDEK();
1230
+ deps.cache?.set(id, fresh, 1);
1231
+ return fresh;
1232
+ }
1233
+ async function rewrapBodyToDek(envelope, fromDek, toDek) {
1234
+ if (envelope._cek !== void 0) {
1235
+ const cek = await unwrapCek(envelope._cek, fromDek);
1236
+ const plaintext2 = await decrypt(envelope._iv, envelope._data, cek);
1237
+ const { iv: iv2, data: data2 } = await encrypt(plaintext2, cek);
1238
+ return { _iv: iv2, _data: data2, _cek: await wrapCek(cek, toDek), cek };
1239
+ }
1240
+ const plaintext = await decrypt(envelope._iv, envelope._data, fromDek);
1241
+ const { iv, data } = await encrypt(plaintext, toDek);
1242
+ return { _iv: iv, _data: data, cek: null };
1243
+ }
1244
+ var init_lifecycle = __esm({
1245
+ "src/record-keys/lifecycle.ts"() {
1246
+ "use strict";
1247
+ init_crypto();
1248
+ }
1249
+ });
1250
+
1251
+ // src/record-keys/sealing.ts
1252
+ async function sealRecordToHost(ctx, collection, id, hostSealer, opts) {
1253
+ if (collection.includes("/")) throw new ValidationError(`sealRecordToHost: collection "${collection}" must not contain "/"`);
1254
+ if (id.includes("/")) throw new ValidationError(`sealRecordToHost: id "${id}" must not contain "/"`);
1255
+ const live = await ctx.adapter.get(ctx.vault, collection, id);
1256
+ if (!live || live._cek === void 0) {
1257
+ throw new RecordCekNotFoundError(collection, id);
1258
+ }
1259
+ const dek = await ctx.getDEK(collection);
1260
+ const cek = await unwrapCek(live._cek, dek);
1261
+ const rawCek = await subtle2.exportKey("raw", cek);
1262
+ const cekB64 = bufferToBase64(rawCek);
1263
+ const hint = await hostSealer.publishRecipientHint();
1264
+ if (hint.pid.includes("/")) throw new ValidationError(`sealRecordToHost: recipient pid "${hint.pid}" must not contain "/"`);
1265
+ const binding = {
1266
+ collection,
1267
+ id,
1268
+ cek: cekB64,
1269
+ expiresAt: opts.expiresAt
1270
+ };
1271
+ const sealed = await hostSealer.sealForRecipient(
1272
+ new TextEncoder().encode(JSON.stringify(binding)),
1273
+ hint
1274
+ );
1275
+ const delivery = {
1276
+ v: 1,
1277
+ _noydb_sealed_cek: 1,
1278
+ pid: hint.pid,
1279
+ payload: bufferToBase64(sealed),
1280
+ expiresAt: opts.expiresAt
1281
+ };
1282
+ const envelopeKey = `${collection}/${id}/${hint.pid}`;
1283
+ const prior = await ctx.adapter.get(ctx.vault, SEALED_CEK_NS, envelopeKey);
1284
+ const env = {
1285
+ _noydb: NOYDB_FORMAT_VERSION,
1286
+ _v: (prior?._v ?? 0) + 1,
1287
+ _ts: (/* @__PURE__ */ new Date()).toISOString(),
1288
+ // AES-GCM bypassed — the sealing layer is the security boundary, exactly
1289
+ // like the managed-passphrase `_meta/sealed-passphrase` envelope.
1290
+ _iv: "",
1291
+ _data: JSON.stringify(delivery),
1292
+ ...ctx.actor ? { _by: ctx.actor } : {}
1293
+ };
1294
+ await ctx.adapter.put(ctx.vault, SEALED_CEK_NS, envelopeKey, env);
1295
+ return { pid: hint.pid, envelopeKey };
1296
+ }
1297
+ async function revokeSealedRecord(ctx, collection, id, pid) {
1298
+ await ctx.adapter.delete(ctx.vault, SEALED_CEK_NS, `${collection}/${id}/${pid}`);
1299
+ }
1300
+ async function rotateRecordCek(ctx, collection, id) {
1301
+ const live = await ctx.adapter.get(ctx.vault, collection, id);
1302
+ if (!live || live._cek === void 0) {
1303
+ throw new RecordCekNotFoundError(collection, id);
1304
+ }
1305
+ const dek = await ctx.getDEK(collection);
1306
+ const oldCek = await unwrapCek(live._cek, dek);
1307
+ const json = await decrypt(live._iv, live._data, oldCek);
1308
+ const newCek = await generateDEK();
1309
+ const { iv, data } = await encrypt(json, newCek);
1310
+ const env = {
1311
+ _noydb: NOYDB_FORMAT_VERSION,
1312
+ _v: live._v + 1,
1313
+ _ts: (/* @__PURE__ */ new Date()).toISOString(),
1314
+ _iv: iv,
1315
+ _data: data,
1316
+ _cek: await wrapCek(newCek, dek),
1317
+ ...ctx.actor ? { _by: ctx.actor } : {},
1318
+ ...live._tier !== void 0 ? { _tier: live._tier } : {},
1319
+ ...live._det !== void 0 ? { _det: live._det } : {}
1320
+ };
1321
+ await ctx.adapter.put(ctx.vault, collection, id, env);
1322
+ await ctx.invalidateRecordCaches(collection, id);
1323
+ const prefix = `${collection}/${id}/`;
1324
+ const keys = await ctx.adapter.list(ctx.vault, SEALED_CEK_NS);
1325
+ for (const key of keys) {
1326
+ if (key.startsWith(prefix)) {
1327
+ await ctx.adapter.delete(ctx.vault, SEALED_CEK_NS, key);
1328
+ }
1329
+ }
1330
+ }
1331
+ var subtle2, SEALED_CEK_NS;
1332
+ var init_sealing = __esm({
1333
+ "src/record-keys/sealing.ts"() {
1334
+ "use strict";
1335
+ init_crypto();
1336
+ init_types();
1337
+ init_errors();
1338
+ subtle2 = globalThis.crypto.subtle;
1339
+ SEALED_CEK_NS = "_sealed_cek";
1340
+ }
1341
+ });
1342
+
1343
+ // src/record-keys/index.ts
1344
+ var init_record_keys = __esm({
1345
+ "src/record-keys/index.ts"() {
1346
+ "use strict";
1347
+ init_crypto();
1348
+ init_tombstone();
1349
+ init_lifecycle();
1350
+ init_sealing();
1351
+ }
1352
+ });
1353
+
1115
1354
  // src/persisted-schemas/storage.ts
1116
1355
  async function loadPersistedSchema(store, vault, collection, dek) {
1117
1356
  const envelope = await store.get(vault, SCHEMAS_COLLECTION, collection);
@@ -2815,11 +3054,11 @@ async function mintWrappedDeksBlob(deks, credential) {
2815
3054
  const wrappingKey = await deriveWrappingKey(credential, salt);
2816
3055
  const exported = {};
2817
3056
  for (const [coll, dek] of deks) {
2818
- const raw = await subtle2.exportKey("raw", dek);
3057
+ const raw = await subtle3.exportKey("raw", dek);
2819
3058
  exported[coll] = bytesToBase643(new Uint8Array(raw));
2820
3059
  }
2821
3060
  const plaintext = new TextEncoder().encode(JSON.stringify({ deks: exported }));
2822
- const ciphertext = await subtle2.encrypt(
3061
+ const ciphertext = await subtle3.encrypt(
2823
3062
  { name: "AES-GCM", iv },
2824
3063
  wrappingKey,
2825
3064
  plaintext
@@ -2832,7 +3071,7 @@ async function mintWrappedDeksBlob(deks, credential) {
2832
3071
  }
2833
3072
  async function unwrapDeksFromBlob(blob, credential) {
2834
3073
  const wrappingKey = await deriveWrappingKey(credential, base64ToBytes3(blob.salt));
2835
- const plaintext = await subtle2.decrypt(
3074
+ const plaintext = await subtle3.decrypt(
2836
3075
  { name: "AES-GCM", iv: base64ToBytes3(blob.iv) },
2837
3076
  wrappingKey,
2838
3077
  base64ToBytes3(blob.wrappedDeks)
@@ -2841,7 +3080,7 @@ async function unwrapDeksFromBlob(blob, credential) {
2841
3080
  const deks = /* @__PURE__ */ new Map();
2842
3081
  for (const [coll, b64] of Object.entries(parsed.deks)) {
2843
3082
  const raw = base64ToBytes3(b64);
2844
- const key = await subtle2.importKey(
3083
+ const key = await subtle3.importKey(
2845
3084
  "raw",
2846
3085
  raw,
2847
3086
  { name: "AES-GCM", length: 256 },
@@ -2853,14 +3092,14 @@ async function unwrapDeksFromBlob(blob, credential) {
2853
3092
  return deks;
2854
3093
  }
2855
3094
  async function deriveWrappingKey(credential, salt) {
2856
- const ikm = await subtle2.importKey(
3095
+ const ikm = await subtle3.importKey(
2857
3096
  "raw",
2858
3097
  new TextEncoder().encode(credential),
2859
3098
  "PBKDF2",
2860
3099
  false,
2861
3100
  ["deriveKey"]
2862
3101
  );
2863
- return subtle2.deriveKey(
3102
+ return subtle3.deriveKey(
2864
3103
  {
2865
3104
  name: "PBKDF2",
2866
3105
  salt,
@@ -2884,14 +3123,14 @@ function base64ToBytes3(b64) {
2884
3123
  for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i);
2885
3124
  return out;
2886
3125
  }
2887
- var PBKDF2_ITERATIONS2, SALT_BYTES2, IV_BYTES2, subtle2;
3126
+ var PBKDF2_ITERATIONS2, SALT_BYTES2, IV_BYTES2, subtle3;
2888
3127
  var init_wrapped_deks = __esm({
2889
3128
  "src/team/wrapped-deks.ts"() {
2890
3129
  "use strict";
2891
3130
  PBKDF2_ITERATIONS2 = 6e5;
2892
3131
  SALT_BYTES2 = 32;
2893
3132
  IV_BYTES2 = 12;
2894
- subtle2 = globalThis.crypto.subtle;
3133
+ subtle3 = globalThis.crypto.subtle;
2895
3134
  }
2896
3135
  });
2897
3136
 
@@ -3696,6 +3935,21 @@ var init_core = __esm({
3696
3935
  }
3697
3936
  });
3698
3937
 
3938
+ // src/i18n/dictionary.ts
3939
+ function isDictCollectionName(name) {
3940
+ return name.startsWith(DICT_COLLECTION_PREFIX);
3941
+ }
3942
+ function isStaticDictDescriptor(x) {
3943
+ return typeof x === "object" && x !== null && x._noydbStaticDict === true;
3944
+ }
3945
+ var DICT_COLLECTION_PREFIX;
3946
+ var init_dictionary = __esm({
3947
+ "src/i18n/dictionary.ts"() {
3948
+ "use strict";
3949
+ DICT_COLLECTION_PREFIX = "_dict_";
3950
+ }
3951
+ });
3952
+
3699
3953
  // src/money/fixed-point.ts
3700
3954
  function expandExponent(s) {
3701
3955
  const m = /^([+-]?)(\d+)(?:\.(\d+))?[eE]([+-]?\d+)$/.exec(s);
@@ -4315,6 +4569,9 @@ var init_strategy3 = __esm({
4315
4569
  async clearHistory() {
4316
4570
  return 0;
4317
4571
  },
4572
+ async tombstoneHistory() {
4573
+ return 0;
4574
+ },
4318
4575
  async envelopePayloadHash() {
4319
4576
  return "";
4320
4577
  },
@@ -5279,6 +5536,7 @@ function buildDictLabelResolver(joinCtx, field) {
5279
5536
  const dictSource = joinCtx.resolveDictSource(field);
5280
5537
  if (!dictSource) return void 0;
5281
5538
  const snapshot = dictSource.snapshot();
5539
+ const displayLocale = dictSource.displayLocale;
5282
5540
  const dictMap = /* @__PURE__ */ new Map();
5283
5541
  for (const entry of snapshot) {
5284
5542
  const k = entry["key"];
@@ -5288,9 +5546,11 @@ function buildDictLabelResolver(joinCtx, field) {
5288
5546
  }
5289
5547
  }
5290
5548
  return async (key, locale, fallback) => {
5549
+ const effLocale = locale || displayLocale;
5550
+ if (!effLocale) return void 0;
5291
5551
  const labels = dictMap.get(key);
5292
5552
  if (!labels) return void 0;
5293
- if (labels[locale] !== void 0) return labels[locale];
5553
+ if (labels[effLocale] !== void 0) return labels[effLocale];
5294
5554
  const chain = Array.isArray(fallback) ? fallback : fallback ? [fallback] : [];
5295
5555
  for (const fb of chain) {
5296
5556
  if (fb === "any") {
@@ -8064,12 +8324,14 @@ var init_collection = __esm({
8064
8324
  init_types();
8065
8325
  init_strategy();
8066
8326
  init_core();
8327
+ init_dictionary();
8067
8328
  init_normalize();
8068
8329
  init_paths();
8069
8330
  init_computed();
8070
8331
  init_strategy2();
8071
8332
  init_policy();
8072
8333
  init_crypto();
8334
+ init_record_keys();
8073
8335
  init_errors();
8074
8336
  init_tiers();
8075
8337
  init_keyring();
@@ -8267,6 +8529,25 @@ var init_collection = __esm({
8267
8529
  * is inactive for this collection; a frozen `Set` otherwise.
8268
8530
  */
8269
8531
  deterministicFields;
8532
+ /**
8533
+ * Per-record CEK opt-in (`perRecordKeys: true`). When set, writes mint /
8534
+ * reuse a per-record content-encryption key and stamp `_cek` on the
8535
+ * envelope (see {@link EncryptedEnvelope._cek}). OFF by default — a
8536
+ * non-adopting collection takes the byte-identical legacy path. The READ
8537
+ * path does not consult this flag: `_cek` presence on the envelope is the
8538
+ * format discriminant, so a mixed vault (and a recipient that never set the
8539
+ * flag) still decrypts CEK records.
8540
+ */
8541
+ perRecordCek;
8542
+ /**
8543
+ * Session-scoped `(id) → CEK` cache for this collection. Lets updates
8544
+ * reuse a record's stable CEK and lets repeated reads skip the AES-KW
8545
+ * unwrap. Bounded by LRU; never persisted. Dropped when the owning
8546
+ * collection instance is discarded — `vault.load()` clears the
8547
+ * collectionCache, so a keyring refresh drops every CEK alongside the
8548
+ * DEK cache. `null` unless `perRecordCek` is set.
8549
+ */
8550
+ cekCache;
8270
8551
  /**
8271
8552
  * declared tiers for this collection. `null` when
8272
8553
  * tier-aware methods are disabled. Tier 0 is implicit and never
@@ -8413,19 +8694,24 @@ var init_collection = __esm({
8413
8694
  } else {
8414
8695
  this.deterministicFields = null;
8415
8696
  }
8697
+ this.perRecordCek = opts.perRecordKeys === true;
8698
+ this.cekCache = this.perRecordCek ? new Lru({ maxRecords: 4096 }) : null;
8416
8699
  if (opts.crdt && opts.onRegisterConflictResolver) {
8417
8700
  const crdtMode = opts.crdt;
8418
- const crdtResolver = async (_id, local, remote) => {
8701
+ const crdtResolver = async (id, local, remote) => {
8419
8702
  if (crdtMode === "yjs") {
8420
8703
  return local._v >= remote._v ? local : remote;
8421
8704
  }
8422
- const localJson = await this.decryptJsonString(local);
8423
- const remoteJson = await this.decryptJsonString(remote);
8705
+ const localJson = await this.decryptJsonString(local, id);
8706
+ const remoteJson = await this.decryptJsonString(remote, id);
8707
+ if (localJson === null) return local;
8708
+ if (remoteJson === null) return remote;
8424
8709
  const localState = JSON.parse(localJson);
8425
8710
  const remoteState = JSON.parse(remoteJson);
8426
8711
  const merged = this.crdtStrategy.mergeCrdtStates(localState, remoteState);
8427
8712
  const mergedVersion = Math.max(local._v, remote._v) + 1;
8428
- return this.encryptJsonString(JSON.stringify(merged), mergedVersion);
8713
+ const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
8714
+ return this.encryptJsonString(JSON.stringify(merged), mergedVersion, cek);
8429
8715
  };
8430
8716
  opts.onRegisterConflictResolver(this.name, crdtResolver);
8431
8717
  }
@@ -8465,12 +8751,15 @@ var init_collection = __esm({
8465
8751
  });
8466
8752
  } else {
8467
8753
  const mergeFn = policy;
8468
- resolver = async (_id, local, remote) => {
8469
- const localRecord = await this.decryptRecord(local, { skipValidation: true });
8470
- const remoteRecord = await this.decryptRecord(remote, { skipValidation: true });
8754
+ resolver = async (id, local, remote) => {
8755
+ const localRecord = await this.decryptRecord(local, { skipValidation: true, id });
8756
+ const remoteRecord = await this.decryptRecord(remote, { skipValidation: true, id });
8757
+ if (localRecord === null) return local;
8758
+ if (remoteRecord === null) return remote;
8471
8759
  const merged = mergeFn(localRecord, remoteRecord);
8472
8760
  const mergedVersion = Math.max(local._v, remote._v) + 1;
8473
- return this.encryptRecord(merged, mergedVersion);
8761
+ const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
8762
+ return this.encryptRecord(merged, mergedVersion, cek);
8474
8763
  };
8475
8764
  }
8476
8765
  opts.onRegisterConflictResolver(collectionName, resolver);
@@ -8562,7 +8851,9 @@ var init_collection = __esm({
8562
8851
  } else {
8563
8852
  const envelope = await this.adapter.get(this.vault, this.name, id);
8564
8853
  if (!envelope) return null;
8565
- record = await this.decryptRecord(envelope);
8854
+ if (isTombstone(envelope, this.encrypted)) return null;
8855
+ record = await this.decryptRecord(envelope, { id });
8856
+ if (record === null) return null;
8566
8857
  this.lru.set(id, { record, version: envelope._v }, estimateRecordBytes(record));
8567
8858
  }
8568
8859
  } else {
@@ -8589,6 +8880,7 @@ var init_collection = __esm({
8589
8880
  const envelope = await this.adapter.get(this.vault, this.name, id);
8590
8881
  if (!envelope) return null;
8591
8882
  const json = await this.decryptJsonString(envelope);
8883
+ if (json === null) return null;
8592
8884
  return JSON.parse(json);
8593
8885
  }
8594
8886
  /**
@@ -8677,7 +8969,7 @@ var init_collection = __esm({
8677
8969
  if (cached2) return { record: cached2.record, version: cached2.version };
8678
8970
  const env = await this.adapter.get(this.vault, this.name, id);
8679
8971
  if (!env) return { record: null, version: 0 };
8680
- return { record: await this.decryptRecord(env, { skipValidation: true }), version: env._v };
8972
+ return { record: await this.decryptRecord(env, { skipValidation: true }) ?? null, version: env._v };
8681
8973
  }
8682
8974
  await this.ensureHydrated();
8683
8975
  const cached = this.cache.get(id);
@@ -8790,9 +9082,11 @@ var init_collection = __esm({
8790
9082
  let existingState;
8791
9083
  if (existingEnvelope) {
8792
9084
  const prevJson = await this.decryptJsonString(existingEnvelope);
8793
- const prevParsed = JSON.parse(prevJson);
8794
- if (prevParsed !== null && typeof prevParsed === "object" && "_crdt" in prevParsed) {
8795
- existingState = prevParsed;
9085
+ if (prevJson !== null) {
9086
+ const prevParsed = JSON.parse(prevJson);
9087
+ if (prevParsed !== null && typeof prevParsed === "object" && "_crdt" in prevParsed) {
9088
+ existingState = prevParsed;
9089
+ }
8796
9090
  }
8797
9091
  }
8798
9092
  crdtState = this.crdtStrategy.buildLwwMapState(record, existingState, now);
@@ -8800,9 +9094,11 @@ var init_collection = __esm({
8800
9094
  let existingState;
8801
9095
  if (existingEnvelope) {
8802
9096
  const prevJson = await this.decryptJsonString(existingEnvelope);
8803
- const prevParsed = JSON.parse(prevJson);
8804
- if (prevParsed !== null && typeof prevParsed === "object" && "_crdt" in prevParsed) {
8805
- existingState = prevParsed;
9097
+ if (prevJson !== null) {
9098
+ const prevParsed = JSON.parse(prevJson);
9099
+ if (prevParsed !== null && typeof prevParsed === "object" && "_crdt" in prevParsed) {
9100
+ existingState = prevParsed;
9101
+ }
8806
9102
  }
8807
9103
  }
8808
9104
  const arr = Array.isArray(record) ? record : [record];
@@ -8811,12 +9107,14 @@ var init_collection = __esm({
8811
9107
  crdtState = { _crdt: "yjs", update: record };
8812
9108
  }
8813
9109
  const version2 = existingVersion + 1;
8814
- const envelope2 = await this.encryptJsonString(JSON.stringify(crdtState), version2);
9110
+ const cek2 = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
9111
+ const envelope2 = await this.encryptJsonString(JSON.stringify(crdtState), version2, cek2);
8815
9112
  await this.adapter.put(this.vault, this.name, id, envelope2);
8816
9113
  const resolvedRecord = this.crdtStrategy.resolveCrdtSnapshot(crdtState);
8817
- const existingResolved = existingEnvelope ? { record: await this.decryptRecord(existingEnvelope, { skipValidation: true }), version: existingVersion } : void 0;
9114
+ const existingResolvedRecord = existingEnvelope ? await this.decryptRecord(existingEnvelope, { skipValidation: true }) : null;
9115
+ const existingResolved = existingResolvedRecord !== null ? { record: existingResolvedRecord, version: existingVersion } : void 0;
8818
9116
  if (existingResolved && this.historyConfig.enabled !== false) {
8819
- const histEnvelope = await this.encryptRecord(existingResolved.record, existingResolved.version);
9117
+ const histEnvelope = await this.encryptRecord(existingResolved.record, existingResolved.version, cek2);
8820
9118
  await this.historyStrategy.saveHistory(this.adapter, this.vault, this.name, id, histEnvelope);
8821
9119
  this.emitter.emit("history:save", { vault: this.vault, collection: this.name, id, version: existingResolved.version });
8822
9120
  if (this.historyConfig.maxVersions) {
@@ -8862,7 +9160,9 @@ var init_collection = __esm({
8862
9160
  const previousEnvelope = await this.adapter.get(this.vault, this.name, id);
8863
9161
  if (previousEnvelope) {
8864
9162
  const previousRecord = await this.decryptRecord(previousEnvelope);
8865
- existing = { record: previousRecord, version: previousEnvelope._v };
9163
+ if (previousRecord !== null) {
9164
+ existing = { record: previousRecord, version: previousEnvelope._v };
9165
+ }
8866
9166
  }
8867
9167
  }
8868
9168
  } else {
@@ -8871,8 +9171,9 @@ var init_collection = __esm({
8871
9171
  }
8872
9172
  const version = existing ? existing.version + 1 : 1;
8873
9173
  this.uniqueConstraints?.check(id, record);
9174
+ const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
8874
9175
  if (existing && this.historyConfig.enabled !== false) {
8875
- const historyEnvelope = await this.encryptRecord(existing.record, existing.version);
9176
+ const historyEnvelope = await this.encryptRecord(existing.record, existing.version, cek);
8876
9177
  await this.historyStrategy.saveHistory(this.adapter, this.vault, this.name, id, historyEnvelope);
8877
9178
  this.emitter.emit("history:save", {
8878
9179
  vault: this.vault,
@@ -8886,7 +9187,7 @@ var init_collection = __esm({
8886
9187
  });
8887
9188
  }
8888
9189
  }
8889
- const envelope = await this.encryptRecord(record, version);
9190
+ const envelope = await this.encryptRecord(record, version, cek);
8890
9191
  await this.adapter.put(this.vault, this.name, id, envelope);
8891
9192
  if (this.ledger) {
8892
9193
  const appendInput = {
@@ -9122,11 +9423,14 @@ var init_collection = __esm({
9122
9423
  let count = 0;
9123
9424
  for (const id of ids) {
9124
9425
  const env = await this.adapter.get(this.vault, this.name, id);
9125
- if (!env) continue;
9126
- const record = await this.decryptRecord(env, { skipValidation: true });
9426
+ if (!env || isTombstone(env, this.encrypted)) continue;
9427
+ const decoded = await this.decryptRecord(env, { skipValidation: true, id });
9428
+ if (decoded === null) continue;
9429
+ const record = decoded;
9127
9430
  const next = transform(record);
9128
9431
  const nextVersion = (env._v ?? 0) + 1;
9129
- const newEnv = await this.encryptRecord(next, nextVersion);
9432
+ const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
9433
+ const newEnv = await this.encryptRecord(next, nextVersion, cek);
9130
9434
  await this.adapter.put(this.vault, this.name, id, newEnv);
9131
9435
  await this._invalidateCacheEntry(id);
9132
9436
  if (this.ledger) {
@@ -9238,14 +9542,17 @@ var init_collection = __esm({
9238
9542
  const previousEnvelope2 = await this.adapter.get(this.vault, this.name, id);
9239
9543
  if (previousEnvelope2) {
9240
9544
  const previousRecord = await this.decryptRecord(previousEnvelope2);
9241
- existing = { record: previousRecord, version: previousEnvelope2._v };
9545
+ if (previousRecord !== null) {
9546
+ existing = { record: previousRecord, version: previousEnvelope2._v };
9547
+ }
9242
9548
  }
9243
9549
  }
9244
9550
  } else {
9245
9551
  existing = this.cache.get(id);
9246
9552
  }
9247
9553
  if (existing && this.historyConfig.enabled !== false) {
9248
- const historyEnvelope = await this.encryptRecord(existing.record, existing.version);
9554
+ const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
9555
+ const historyEnvelope = await this.encryptRecord(existing.record, existing.version, cek);
9249
9556
  await this.historyStrategy.saveHistory(this.adapter, this.vault, this.name, id, historyEnvelope);
9250
9557
  }
9251
9558
  const previousEnvelope = await this.adapter.get(this.vault, this.name, id);
@@ -9286,6 +9593,50 @@ var init_collection = __esm({
9286
9593
  await this.dispatchArrayDerivationsOnDelete(id);
9287
9594
  }
9288
9595
  }
9596
+ /**
9597
+ * @internal — GDPR crypto-shred a LIVE record to a tombstone (#304).
9598
+ *
9599
+ * Rewrites the on-disk envelope to `{ _noydb, _v, _ts, _by, _iv:'', _data:'' }`,
9600
+ * dropping `_iv`/`_data`/`_cek`/`_det`. The wrapped per-record CEK is gone, so
9601
+ * the body — and (via {@link tombstoneHistory}) every history version under
9602
+ * the same CEK — is permanently undecryptable; the collection DEK and every
9603
+ * other record are untouched. `_det` is stripped too, so `findByDet` no
9604
+ * longer matches the shredded record (avoiding a post-shred TamperedError).
9605
+ *
9606
+ * Unlike `delete()`/`_internalDelete`, this:
9607
+ * - does NOT fire onDelete guards / MV / derivation dispatch (a shred is an
9608
+ * erasure, not a domain delete — re-running those would be wrong),
9609
+ * - does NOT append a per-record ledger entry (`vault.forget()` appends a
9610
+ * single `op:'forget'` summary for the whole subject),
9611
+ * - keeps the record KEY present (it's an overwrite, not an adapter delete)
9612
+ * so the version counter + "record existed" survive for audit.
9613
+ *
9614
+ * Idempotent: returns `null` when the record is absent or already a tombstone.
9615
+ * Otherwise returns `{ previousVersion }`. Invalidates the eager cache, the
9616
+ * lazy LRU, and the per-record CEK cache for this id.
9617
+ */
9618
+ /**
9619
+ * @internal — decrypt an envelope to a plain record for subject-index
9620
+ * rebuild (#304). Returns `null` for a tombstone or unreadable envelope.
9621
+ * Skips schema validation — the rebuild only reads the subject field.
9622
+ */
9623
+ async _decodeEnvelope(envelope, id) {
9624
+ try {
9625
+ const rec = await this.decryptRecord(envelope, { skipValidation: true, id });
9626
+ return rec === null ? null : rec;
9627
+ } catch {
9628
+ return null;
9629
+ }
9630
+ }
9631
+ async _writeTombstone(id, actor) {
9632
+ const live = await this.adapter.get(this.vault, this.name, id);
9633
+ if (!live || isTombstone(live, this.encrypted)) return null;
9634
+ await this.adapter.put(this.vault, this.name, id, buildTombstone(live._v, actor));
9635
+ this.cache.delete(id);
9636
+ this.lru?.remove(id);
9637
+ this.cekCache?.remove(id);
9638
+ return { previousVersion: live._v };
9639
+ }
9289
9640
  /**
9290
9641
  * Cascade deletes of array-shape derived rows when a source row is
9291
9642
  * deleted. Reads each registered strategy's fanout sidecar
@@ -9698,6 +10049,7 @@ var init_collection = __esm({
9698
10049
  const entries = [];
9699
10050
  for (const env of envelopes) {
9700
10051
  const record = await this.decryptRecord(env, { skipValidation: true });
10052
+ if (record === null) continue;
9701
10053
  entries.push({
9702
10054
  version: env._v,
9703
10055
  timestamp: env._ts,
@@ -9834,6 +10186,7 @@ var init_collection = __esm({
9834
10186
  const envelope = await this.adapter.get(this.vault, this.name, id);
9835
10187
  if (envelope) {
9836
10188
  const record = await this.decryptRecord(envelope);
10189
+ if (record === null) continue;
9837
10190
  items.push(record);
9838
10191
  if (!this.lazy && !this.cache.has(id)) {
9839
10192
  this.cache.set(id, { record, version: envelope._v });
@@ -9910,6 +10263,7 @@ var init_collection = __esm({
9910
10263
  const out = [];
9911
10264
  for (const { id, envelope } of items) {
9912
10265
  const record = await this.decryptRecord(envelope);
10266
+ if (record === null) continue;
9913
10267
  out.push({ id, record, version: envelope._v });
9914
10268
  }
9915
10269
  return out;
@@ -9931,6 +10285,18 @@ var init_collection = __esm({
9931
10285
  * the cache entry (record still present) or deletes it (record was
9932
10286
  * gone before the tx and the revert deleted it again).
9933
10287
  */
10288
+ /**
10289
+ * @internal — evict ONLY the per-record CEK cache entry for `id`. Used by
10290
+ * `vault.rotateRecordCek()`: after a hard CEK rotation the cached unwrapped
10291
+ * CEK is stale (it would decrypt the pre-rotation body and fail GCM auth on
10292
+ * the post-rotation body). Eviction must be synchronous with the live-envelope
10293
+ * rewrite so no concurrent read observes the old CEK. Paired with
10294
+ * {@link _invalidateCacheEntry} (which refreshes the decrypted-record cache).
10295
+ * No-op when the collection is not `perRecordKeys`.
10296
+ */
10297
+ _invalidateCekCacheEntry(id) {
10298
+ this.cekCache?.remove(id);
10299
+ }
9934
10300
  async _invalidateCacheEntry(id) {
9935
10301
  if (this.lazy && this.lru) {
9936
10302
  this.lru.remove(id);
@@ -9948,6 +10314,14 @@ var init_collection = __esm({
9948
10314
  return;
9949
10315
  }
9950
10316
  const record = await this.decryptRecord(envelope);
10317
+ if (record === null) {
10318
+ this.cache.delete(id);
10319
+ if (previous) {
10320
+ this.indexes?.remove(id, previous.record);
10321
+ this.uniqueConstraints?.remove(id, previous.record);
10322
+ }
10323
+ return;
10324
+ }
9951
10325
  this.cache.set(id, { record, version: envelope._v });
9952
10326
  this.indexes?.upsert(id, record, previous ? previous.record : null);
9953
10327
  this.uniqueConstraints?.upsert(id, record, previous?.record);
@@ -9972,8 +10346,9 @@ var init_collection = __esm({
9972
10346
  const ids = await this.adapter.list(this.vault, this.name);
9973
10347
  for (const id of ids) {
9974
10348
  const envelope = await this.adapter.get(this.vault, this.name, id);
9975
- if (envelope) {
9976
- const record = await this.decryptRecord(envelope);
10349
+ if (envelope && !isTombstone(envelope, this.encrypted)) {
10350
+ const record = await this.decryptRecord(envelope, { id });
10351
+ if (record === null) continue;
9977
10352
  this.cache.set(id, { record, version: envelope._v });
9978
10353
  }
9979
10354
  }
@@ -9984,7 +10359,9 @@ var init_collection = __esm({
9984
10359
  /** Hydrate from a pre-loaded snapshot (used by Vault). */
9985
10360
  async hydrateFromSnapshot(records) {
9986
10361
  for (const [id, envelope] of Object.entries(records)) {
9987
- const record = await this.decryptRecord(envelope);
10362
+ if (isTombstone(envelope, this.encrypted)) continue;
10363
+ const record = await this.decryptRecord(envelope, { id });
10364
+ if (record === null) continue;
9988
10365
  this.cache.set(id, { record, version: envelope._v });
9989
10366
  }
9990
10367
  this.hydrated = true;
@@ -10072,6 +10449,7 @@ var init_collection = __esm({
10072
10449
  const envelope = await this.adapter.get(this.vault, this.name, recordId2);
10073
10450
  if (!envelope) continue;
10074
10451
  const record = await this.decryptRecord(envelope, { skipValidation: true });
10452
+ if (record === null) continue;
10075
10453
  await this.maintainPersistedIndexesOnPut(recordId2, record, null, envelope._v);
10076
10454
  }
10077
10455
  this.persistedIndexesLoaded = true;
@@ -10122,8 +10500,13 @@ var init_collection = __esm({
10122
10500
  const env = await this.adapter.get(this.vault, this.name, id);
10123
10501
  if (!env) continue;
10124
10502
  try {
10125
- const body = JSON.parse(await this.decryptJsonString(env));
10126
- sidecar.set(decoded.recordId, body.value);
10503
+ const sidecarJson = await this.decryptJsonString(env);
10504
+ if (sidecarJson === null) {
10505
+ sidecar.set(decoded.recordId, void 0);
10506
+ } else {
10507
+ const body = JSON.parse(sidecarJson);
10508
+ sidecar.set(decoded.recordId, body.value);
10509
+ }
10127
10510
  } catch {
10128
10511
  sidecar.set(decoded.recordId, void 0);
10129
10512
  }
@@ -10137,6 +10520,7 @@ var init_collection = __esm({
10137
10520
  const env = await this.adapter.get(this.vault, this.name, id);
10138
10521
  if (!env) continue;
10139
10522
  const record = await this.decryptRecord(env, { skipValidation: true });
10523
+ if (record === null) continue;
10140
10524
  const live = readPersistedValue(record, field);
10141
10525
  const stored = sidecar.get(id);
10142
10526
  const hasSidecar = sidecarIds.has(id);
@@ -10219,7 +10603,8 @@ var init_collection = __esm({
10219
10603
  recordId: id,
10220
10604
  getDEK: this.getDEK,
10221
10605
  encrypted: this.encrypted,
10222
- userId: this.keyring.userId
10606
+ userId: this.keyring.userId,
10607
+ erasableBlobs: this.perRecordCek
10223
10608
  });
10224
10609
  }
10225
10610
  /** Get all records as encrypted envelopes (for dump). */
@@ -10227,7 +10612,8 @@ var init_collection = __esm({
10227
10612
  await this.ensureHydrated();
10228
10613
  const result = {};
10229
10614
  for (const [id, entry] of this.cache) {
10230
- result[id] = await this.encryptRecord(entry.record, entry.version);
10615
+ const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
10616
+ result[id] = await this.encryptRecord(entry.record, entry.version, cek);
10231
10617
  }
10232
10618
  return result;
10233
10619
  }
@@ -10255,8 +10641,11 @@ var init_collection = __esm({
10255
10641
  if (hasMoney && this.moneyFields) {
10256
10642
  result = decodeMoneyFields(result, this.moneyFields, typeof locale === "string" ? locale : void 0);
10257
10643
  }
10258
- if (!locale) return result;
10259
- if (hasI18n && this.i18nFields) {
10644
+ const hasStaticDisplay = hasDict && this.dictKeyFields !== void 0 && Object.values(this.dictKeyFields).some(
10645
+ (d) => isStaticDictDescriptor(d) && d.displayLocale !== void 0
10646
+ );
10647
+ if (!locale && !hasStaticDisplay) return result;
10648
+ if (locale && hasI18n && this.i18nFields) {
10260
10649
  result = this.i18nStrategy.applyI18nLocale(result, this.i18nFields, locale, localeOpts?.fallback);
10261
10650
  }
10262
10651
  if (hasDict && this.dictKeyFields && this.dictLabelResolver && locale !== "raw") {
@@ -10265,13 +10654,23 @@ var init_collection = __esm({
10265
10654
  for (const [field, desc] of Object.entries(this.dictKeyFields)) {
10266
10655
  const policy = desc.onMissing ? resolvePolicy(desc.onMissing, "read") : "null";
10267
10656
  const fallback = policy === "substitute" ? localeOpts?.fallback ?? desc.substitute : localeOpts?.fallback;
10657
+ const effLocale = locale ?? (isStaticDictDescriptor(desc) ? desc.displayLocale : void 0);
10268
10658
  const resolveKey = async (key) => {
10269
- const label = await resolver(desc.name, key, locale, fallback);
10659
+ if (!effLocale) {
10660
+ if (policy === "throw") {
10661
+ throw new LocaleNotSpecifiedError(
10662
+ field,
10663
+ `dictKey "${field}": no locale active to resolve key "${key}".`
10664
+ );
10665
+ }
10666
+ return null;
10667
+ }
10668
+ const label = await resolver(desc.name, key, effLocale, fallback);
10270
10669
  if (label === void 0) {
10271
10670
  if (policy === "throw") {
10272
10671
  throw new LocaleNotSpecifiedError(
10273
10672
  field,
10274
- `dictKey "${field}": no label for key "${key}" in locale "${locale}".`
10673
+ `dictKey "${field}": no label for key "${key}" in locale "${effLocale}".`
10275
10674
  );
10276
10675
  }
10277
10676
  return null;
@@ -10428,6 +10827,7 @@ var init_collection = __esm({
10428
10827
  if (!envelope) continue;
10429
10828
  try {
10430
10829
  const json = await this.decryptJsonString(envelope);
10830
+ if (json === null) continue;
10431
10831
  const body = JSON.parse(json);
10432
10832
  if (typeof body.recordId !== "string") continue;
10433
10833
  const rows = byField.get(decoded.field) ?? [];
@@ -10537,7 +10937,31 @@ var init_collection = __esm({
10537
10937
  };
10538
10938
  return new LazyQuery(source);
10539
10939
  }
10540
- async encryptJsonString(json, version) {
10940
+ /**
10941
+ * Resolve the stable CEK for a record on the WRITE path — see
10942
+ * {@link resolveStableCek}. Thin delegate that supplies the collection's
10943
+ * CEK cache, live-envelope reader, and DEK resolver.
10944
+ */
10945
+ resolveRecordCek(id) {
10946
+ return resolveStableCek(
10947
+ {
10948
+ cache: this.cekCache,
10949
+ getLive: (rid) => this.adapter.get(this.vault, this.name, rid),
10950
+ getDEK: () => this.getDEK(this.name)
10951
+ },
10952
+ id
10953
+ );
10954
+ }
10955
+ /**
10956
+ * Encrypt a JSON body into an envelope.
10957
+ *
10958
+ * When `cek` is supplied (per-record CEK collections), the body is
10959
+ * encrypted under the CEK and the CEK is AES-KW-wrapped under the
10960
+ * collection DEK and stamped on `_cek`. When `cek` is omitted, the legacy
10961
+ * path encrypts the body directly under the collection DEK — byte-identical
10962
+ * to pre-CEK behaviour, so non-adopting collections pay nothing.
10963
+ */
10964
+ async encryptJsonString(json, version, cek) {
10541
10965
  const by = this.keyring.userId;
10542
10966
  if (!this.encrypted) {
10543
10967
  return {
@@ -10550,6 +10974,19 @@ var init_collection = __esm({
10550
10974
  };
10551
10975
  }
10552
10976
  const dek = await this.getDEK(this.name);
10977
+ if (cek !== void 0) {
10978
+ const { iv: iv2, data: data2 } = await encrypt(json, cek);
10979
+ const wrapped = await wrapCek(cek, dek);
10980
+ return {
10981
+ _noydb: NOYDB_FORMAT_VERSION,
10982
+ _v: version,
10983
+ _ts: (/* @__PURE__ */ new Date()).toISOString(),
10984
+ _iv: iv2,
10985
+ _data: data2,
10986
+ _by: by,
10987
+ _cek: wrapped
10988
+ };
10989
+ }
10553
10990
  const { iv, data } = await encrypt(json, dek);
10554
10991
  return {
10555
10992
  _noydb: NOYDB_FORMAT_VERSION,
@@ -10560,8 +10997,8 @@ var init_collection = __esm({
10560
10997
  _by: by
10561
10998
  };
10562
10999
  }
10563
- async encryptRecord(record, version) {
10564
- const base = await this.encryptJsonString(JSON.stringify(record), version);
11000
+ async encryptRecord(record, version, cek) {
11001
+ const base = await this.encryptJsonString(JSON.stringify(record), version, cek);
10565
11002
  if (!this.deterministicFields || !this.encrypted) return base;
10566
11003
  const dek = await this.getDEK(this.name);
10567
11004
  const rec = record;
@@ -10639,7 +11076,8 @@ var init_collection = __esm({
10639
11076
  const env = await this.adapter.get(this.vault, this.name, id);
10640
11077
  if (!env || !env._det) continue;
10641
11078
  if (env._det[field] === target) {
10642
- matches.push(await this.decryptRecord(env));
11079
+ const rec = await this.decryptRecord(env);
11080
+ if (rec !== null) matches.push(rec);
10643
11081
  }
10644
11082
  }
10645
11083
  return matches;
@@ -10740,7 +11178,14 @@ var init_collection = __esm({
10740
11178
  return null;
10741
11179
  }
10742
11180
  const dek = await this.getDEK(key);
10743
- const plaintext = await decrypt(envelope._iv, envelope._data, dek);
11181
+ let plaintext;
11182
+ if (envelope._cek !== void 0) {
11183
+ const cek = await unwrapCek(envelope._cek, dek);
11184
+ this.cekCache?.set(id, cek, 1);
11185
+ plaintext = await decrypt(envelope._iv, envelope._data, cek);
11186
+ } else {
11187
+ plaintext = await decrypt(envelope._iv, envelope._data, dek);
11188
+ }
10744
11189
  const record = JSON.parse(plaintext);
10745
11190
  this.emitCrossTierEvent({
10746
11191
  actor: this.keyring.userId,
@@ -10796,18 +11241,19 @@ var init_collection = __esm({
10796
11241
  const toKey = dekKey(this.name, toTier);
10797
11242
  const fromDek = await this.getDEK(fromKey);
10798
11243
  const toDek = await this.getDEK(toKey);
10799
- const plaintext = await decrypt(envelope._iv, envelope._data, fromDek);
10800
- const { iv, data } = await encrypt(plaintext, toDek);
10801
11244
  const now = (/* @__PURE__ */ new Date()).toISOString();
11245
+ const body = await rewrapBodyToDek(envelope, fromDek, toDek);
11246
+ if (body.cek) this.cekCache?.set(id, body.cek, 1);
10802
11247
  const next = {
10803
11248
  _noydb: NOYDB_FORMAT_VERSION,
10804
11249
  _v: envelope._v + 1,
10805
11250
  _ts: now,
10806
- _iv: iv,
10807
- _data: data,
11251
+ _iv: body._iv,
11252
+ _data: body._data,
10808
11253
  _by: this.keyring.userId,
10809
11254
  _tier: toTier,
10810
- _elevatedBy: this.keyring.userId
11255
+ _elevatedBy: this.keyring.userId,
11256
+ ...body._cek !== void 0 ? { _cek: body._cek } : {}
10811
11257
  };
10812
11258
  await this.adapter.put(this.vault, this.name, id, next);
10813
11259
  this.emitCrossTierEvent({
@@ -10843,17 +11289,18 @@ var init_collection = __esm({
10843
11289
  if (toTier > 0) this.assertDeclaredTier(toTier);
10844
11290
  const fromDek = await this.getDEK(dekKey(this.name, fromTier));
10845
11291
  const toDek = await this.getDEK(dekKey(this.name, toTier));
10846
- const plaintext = await decrypt(envelope._iv, envelope._data, fromDek);
10847
- const { iv, data } = await encrypt(plaintext, toDek);
10848
11292
  const now = (/* @__PURE__ */ new Date()).toISOString();
11293
+ const body = await rewrapBodyToDek(envelope, fromDek, toDek);
11294
+ if (body.cek) this.cekCache?.set(id, body.cek, 1);
10849
11295
  const next = {
10850
11296
  _noydb: NOYDB_FORMAT_VERSION,
10851
11297
  _v: envelope._v + 1,
10852
11298
  _ts: now,
10853
- _iv: iv,
10854
- _data: data,
11299
+ _iv: body._iv,
11300
+ _data: body._data,
10855
11301
  _by: this.keyring.userId,
10856
- ...toTier > 0 && { _tier: toTier }
11302
+ ...toTier > 0 && { _tier: toTier },
11303
+ ...body._cek !== void 0 ? { _cek: body._cek } : {}
10857
11304
  };
10858
11305
  await this.adapter.put(this.vault, this.name, id, next);
10859
11306
  this.emitCrossTierEvent({
@@ -10875,10 +11322,30 @@ var init_collection = __esm({
10875
11322
  } catch {
10876
11323
  }
10877
11324
  }
10878
- /** Low-level: decrypt an envelope and return the raw JSON string. */
10879
- async decryptJsonString(envelope) {
11325
+ /**
11326
+ * Low-level: decrypt an envelope and return the raw JSON string.
11327
+ *
11328
+ * `_cek` presence is the format discriminant (NOT `this.perRecordCek`),
11329
+ * so a mixed vault — and a recipient that never opted into
11330
+ * `perRecordKeys` — decrypts both legacy and CEK records:
11331
+ * - `_cek` present → unwrap the CEK under the collection DEK, decrypt the
11332
+ * body under the CEK (cache the unwrapped CEK so repeated reads skip it).
11333
+ * - `_cek` absent → legacy path, body decrypts directly under the
11334
+ * collection DEK.
11335
+ *
11336
+ * The optional `id` lets reads populate the CEK cache; it is omitted by
11337
+ * callers (history, conflict merge) that have only the envelope.
11338
+ */
11339
+ async decryptJsonString(envelope, id) {
11340
+ if (isTombstone(envelope, this.encrypted)) return null;
10880
11341
  if (!this.encrypted) return envelope._data;
10881
11342
  const dek = await this.getDEK(this.name);
11343
+ if (envelope._cek !== void 0) {
11344
+ const cached = id !== void 0 ? this.cekCache?.get(id) : void 0;
11345
+ const cek = cached ?? await unwrapCek(envelope._cek, dek);
11346
+ if (cached === void 0 && id !== void 0) this.cekCache?.set(id, cek, 1);
11347
+ return decrypt(envelope._iv, envelope._data, cek);
11348
+ }
10882
11349
  return decrypt(envelope._iv, envelope._data, dek);
10883
11350
  }
10884
11351
  /**
@@ -10897,7 +11364,8 @@ var init_collection = __esm({
10897
11364
  * false positive. Every non-history read leaves this flag `false`.
10898
11365
  */
10899
11366
  async decryptRecord(envelope, opts = {}) {
10900
- const json = await this.decryptJsonString(envelope);
11367
+ const json = await this.decryptJsonString(envelope, opts.id);
11368
+ if (json === null) return null;
10901
11369
  let parsed = JSON.parse(json);
10902
11370
  if (this.crdtMode && parsed !== null && typeof parsed === "object" && "_crdt" in parsed) {
10903
11371
  parsed = this.crdtStrategy.resolveCrdtSnapshot(parsed);
@@ -11502,9 +11970,125 @@ var init_numbering = __esm({
11502
11970
  }
11503
11971
  });
11504
11972
 
11973
+ // src/forget/strategy.ts
11974
+ var NO_FORGET;
11975
+ var init_strategy7 = __esm({
11976
+ "src/forget/strategy.ts"() {
11977
+ "use strict";
11978
+ NO_FORGET = { subjects: {} };
11979
+ }
11980
+ });
11981
+
11982
+ // src/forget/subject-index.ts
11983
+ async function sha256HexString(input) {
11984
+ const bytes = new TextEncoder().encode(input);
11985
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
11986
+ return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
11987
+ }
11988
+ async function subjectKey(subjectId) {
11989
+ return sha256HexString(subjectId);
11990
+ }
11991
+ async function readRefs(adapter, vault, getDEK, encrypted, key) {
11992
+ const env = await adapter.get(vault, SUBJECT_INDEX_COLLECTION, key);
11993
+ if (!env || !env._data) return [];
11994
+ if (!encrypted) return JSON.parse(env._data);
11995
+ const dek = await getDEK(SUBJECT_INDEX_COLLECTION);
11996
+ const json = await decrypt(env._iv, env._data, dek);
11997
+ return JSON.parse(json);
11998
+ }
11999
+ async function writeRefs(adapter, vault, getDEK, encrypted, key, refs) {
12000
+ const json = JSON.stringify(refs);
12001
+ let env;
12002
+ if (!encrypted) {
12003
+ env = { _noydb: NOYDB_FORMAT_VERSION, _v: 1, _ts: (/* @__PURE__ */ new Date()).toISOString(), _iv: "", _data: json };
12004
+ } else {
12005
+ const dek = await getDEK(SUBJECT_INDEX_COLLECTION);
12006
+ const { iv, data } = await encrypt(json, dek);
12007
+ env = { _noydb: NOYDB_FORMAT_VERSION, _v: 1, _ts: (/* @__PURE__ */ new Date()).toISOString(), _iv: iv, _data: data };
12008
+ }
12009
+ await adapter.put(vault, SUBJECT_INDEX_COLLECTION, key, env);
12010
+ }
12011
+ async function addSubjectRef(adapter, vault, getDEK, encrypted, subjectId, ref) {
12012
+ const key = await subjectKey(subjectId);
12013
+ const refs = await readRefs(adapter, vault, getDEK, encrypted, key);
12014
+ if (refs.some((r) => r.collection === ref.collection && r.id === ref.id)) return;
12015
+ refs.push(ref);
12016
+ await writeRefs(adapter, vault, getDEK, encrypted, key, refs);
12017
+ }
12018
+ async function removeSubjectRef(adapter, vault, getDEK, encrypted, subjectId, ref) {
12019
+ const key = await subjectKey(subjectId);
12020
+ const refs = await readRefs(adapter, vault, getDEK, encrypted, key);
12021
+ const next = refs.filter((r) => !(r.collection === ref.collection && r.id === ref.id));
12022
+ if (next.length === refs.length) return;
12023
+ if (next.length === 0) {
12024
+ await adapter.delete(vault, SUBJECT_INDEX_COLLECTION, key);
12025
+ return;
12026
+ }
12027
+ await writeRefs(adapter, vault, getDEK, encrypted, key, next);
12028
+ }
12029
+ async function lookupSubject(adapter, vault, getDEK, encrypted, subjectId) {
12030
+ const key = await subjectKey(subjectId);
12031
+ return readRefs(adapter, vault, getDEK, encrypted, key);
12032
+ }
12033
+ async function rebuildSubjectIndex(adapter, vault, getDEK, encrypted, subjects, decodeRecord) {
12034
+ const existing = await adapter.list(vault, SUBJECT_INDEX_COLLECTION);
12035
+ for (const k of existing) {
12036
+ await adapter.delete(vault, SUBJECT_INDEX_COLLECTION, k);
12037
+ }
12038
+ const bySubject = /* @__PURE__ */ new Map();
12039
+ for (const [collection, field] of Object.entries(subjects)) {
12040
+ const ids = await adapter.list(vault, collection);
12041
+ for (const id of ids) {
12042
+ if (id.startsWith("_")) continue;
12043
+ const env = await adapter.get(vault, collection, id);
12044
+ if (!env || !env._data) continue;
12045
+ const record = await decodeRecord(collection, id, env);
12046
+ if (record === null) continue;
12047
+ const subjectValue = readDottedPath(record, field);
12048
+ if (subjectValue === void 0 || subjectValue === null) continue;
12049
+ const subjectId = coerceSubjectId(subjectValue);
12050
+ const list = bySubject.get(subjectId) ?? [];
12051
+ list.push({ collection, id });
12052
+ bySubject.set(subjectId, list);
12053
+ }
12054
+ }
12055
+ let entries = 0;
12056
+ for (const [subjectId, refs] of bySubject) {
12057
+ const key = await subjectKey(subjectId);
12058
+ await writeRefs(adapter, vault, getDEK, encrypted, key, refs);
12059
+ entries++;
12060
+ }
12061
+ return entries;
12062
+ }
12063
+ function coerceSubjectId(value) {
12064
+ if (typeof value === "string") return value;
12065
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
12066
+ return String(value);
12067
+ }
12068
+ return JSON.stringify(value);
12069
+ }
12070
+ function readDottedPath(record, field) {
12071
+ if (!field.includes(".")) return record[field];
12072
+ let cursor = record;
12073
+ for (const segment of field.split(".")) {
12074
+ if (cursor === null || cursor === void 0) return void 0;
12075
+ cursor = cursor[segment];
12076
+ }
12077
+ return cursor;
12078
+ }
12079
+ var SUBJECT_INDEX_COLLECTION;
12080
+ var init_subject_index = __esm({
12081
+ "src/forget/subject-index.ts"() {
12082
+ "use strict";
12083
+ init_crypto();
12084
+ init_types();
12085
+ SUBJECT_INDEX_COLLECTION = "_subject_index";
12086
+ }
12087
+ });
12088
+
11505
12089
  // src/shadow/strategy.ts
11506
12090
  var NOT_ENABLED3, NO_SHADOW;
11507
- var init_strategy7 = __esm({
12091
+ var init_strategy8 = __esm({
11508
12092
  "src/shadow/strategy.ts"() {
11509
12093
  "use strict";
11510
12094
  NOT_ENABLED3 = new Error(
@@ -11520,7 +12104,7 @@ var init_strategy7 = __esm({
11520
12104
 
11521
12105
  // src/consent/strategy.ts
11522
12106
  var NO_CONSENT;
11523
- var init_strategy8 = __esm({
12107
+ var init_strategy9 = __esm({
11524
12108
  "src/consent/strategy.ts"() {
11525
12109
  "use strict";
11526
12110
  NO_CONSENT = {
@@ -11535,7 +12119,7 @@ var init_strategy8 = __esm({
11535
12119
 
11536
12120
  // src/periods/strategy.ts
11537
12121
  var NOT_ENABLED4, NO_PERIODS;
11538
- var init_strategy9 = __esm({
12122
+ var init_strategy10 = __esm({
11539
12123
  "src/periods/strategy.ts"() {
11540
12124
  "use strict";
11541
12125
  NOT_ENABLED4 = new Error(
@@ -11647,18 +12231,6 @@ var init_refs = __esm({
11647
12231
  }
11648
12232
  });
11649
12233
 
11650
- // src/i18n/dictionary.ts
11651
- function isDictCollectionName(name) {
11652
- return name.startsWith(DICT_COLLECTION_PREFIX);
11653
- }
11654
- var DICT_COLLECTION_PREFIX;
11655
- var init_dictionary = __esm({
11656
- "src/i18n/dictionary.ts"() {
11657
- "use strict";
11658
- DICT_COLLECTION_PREFIX = "_dict_";
11659
- }
11660
- });
11661
-
11662
12234
  // src/periods/periods.ts
11663
12235
  var PERIODS_COLLECTION;
11664
12236
  var init_periods = __esm({
@@ -13627,6 +14199,19 @@ var init_delegation = __esm({
13627
14199
  });
13628
14200
 
13629
14201
  // src/vault.ts
14202
+ function resolveLabelFromMap(labels, locale, fallback) {
14203
+ if (labels[locale] !== void 0) return labels[locale];
14204
+ const chain = Array.isArray(fallback) ? fallback : fallback ? [fallback] : [];
14205
+ for (const fb of chain) {
14206
+ if (fb === "any") {
14207
+ const any = Object.values(labels)[0];
14208
+ if (any !== void 0) return any;
14209
+ } else if (labels[fb] !== void 0) {
14210
+ return labels[fb];
14211
+ }
14212
+ }
14213
+ return void 0;
14214
+ }
13630
14215
  var Vault, ELEVATION_AUDIT_COLLECTION, ElevatedHandle;
13631
14216
  var init_vault = __esm({
13632
14217
  "src/vault.ts"() {
@@ -13645,8 +14230,11 @@ var init_vault = __esm({
13645
14230
  init_entry();
13646
14231
  init_strategy3();
13647
14232
  init_strategy7();
14233
+ init_subject_index();
14234
+ init_errors();
13648
14235
  init_strategy8();
13649
14236
  init_strategy9();
14237
+ init_strategy10();
13650
14238
  init_refs();
13651
14239
  init_dictionary();
13652
14240
  init_core();
@@ -13655,6 +14243,7 @@ var init_vault = __esm({
13655
14243
  init_errors();
13656
14244
  init_periods2();
13657
14245
  init_crypto();
14246
+ init_record_keys();
13658
14247
  init_export_blobs();
13659
14248
  init_blob_compaction();
13660
14249
  init_magic_link_grant();
@@ -13710,6 +14299,7 @@ var init_vault = __esm({
13710
14299
  periodsStrategy;
13711
14300
  shadowStrategy;
13712
14301
  historyStrategy;
14302
+ forgetStrategy;
13713
14303
  i18nStrategy;
13714
14304
  syncStrategy;
13715
14305
  /**
@@ -13876,6 +14466,27 @@ var init_vault = __esm({
13876
14466
  * Populated by `collection()` when the `dictKeyFields` option is passed.
13877
14467
  */
13878
14468
  dictKeyFieldRegistry = /* @__PURE__ */ new Map();
14469
+ /**
14470
+ * Names of dictionaries backed by a `staticDict()` descriptor (#291).
14471
+ * A static dict skips the `dictKeyFieldRegistry` rename machinery, but the
14472
+ * vault must still *know* a name is static so `vault.dictionary(name)` can
14473
+ * refuse mutation (`StaticDictReadonlyError`). Populated at `collection()`
14474
+ * config time whenever a `StaticDictDescriptor` is seen.
14475
+ */
14476
+ staticDictNames = /* @__PURE__ */ new Set();
14477
+ /**
14478
+ * Static-dict descriptors keyed by dictionary name (#291). Backs the
14479
+ * read-path label resolver (resolve from the in-memory table) and the
14480
+ * query-seam `resolveDictSource` snapshot. Last writer wins when the same
14481
+ * name is registered by multiple collections (identical-across-vaults by
14482
+ * construction, so the tables match).
14483
+ */
14484
+ staticByName = /* @__PURE__ */ new Map();
14485
+ /**
14486
+ * Per-collection map of field name → StaticDictDescriptor (#291). Used by
14487
+ * `enforceStaticDictOnPut` to validate stored codes against `desc.keys`.
14488
+ */
14489
+ staticDescriptorByField = /* @__PURE__ */ new Map();
13879
14490
  /**
13880
14491
  * Registry of i18nText fields declared across all collections. Keyed
13881
14492
  * by collection name → field name → I18nTextDescriptor. Used by
@@ -13922,6 +14533,7 @@ var init_vault = __esm({
13922
14533
  this.periodsStrategy = opts.periodsStrategy ?? NO_PERIODS;
13923
14534
  this.shadowStrategy = opts.shadowStrategy ?? NO_SHADOW;
13924
14535
  this.historyStrategy = opts.historyStrategy ?? NO_HISTORY;
14536
+ this.forgetStrategy = opts.forgetStrategy ?? NO_FORGET;
13925
14537
  this.i18nStrategy = opts.i18nStrategy ?? NO_I18N;
13926
14538
  this.syncStrategy = opts.syncStrategy ?? NO_SYNC;
13927
14539
  void opts.guardStrategies;
@@ -14026,10 +14638,22 @@ var init_vault = __esm({
14026
14638
  }
14027
14639
  if (options?.dictKeyFields) {
14028
14640
  const dictFieldMap = {};
14641
+ const staticFieldMap = {};
14029
14642
  for (const [field, desc] of Object.entries(options.dictKeyFields)) {
14030
- dictFieldMap[field] = desc.name;
14643
+ if (isStaticDictDescriptor(desc)) {
14644
+ staticFieldMap[field] = desc;
14645
+ this.staticDictNames.add(desc.name);
14646
+ this.staticByName.set(desc.name, desc);
14647
+ } else {
14648
+ dictFieldMap[field] = desc.name;
14649
+ }
14650
+ }
14651
+ if (Object.keys(dictFieldMap).length > 0) {
14652
+ this.dictKeyFieldRegistry.set(collectionName, dictFieldMap);
14653
+ }
14654
+ if (Object.keys(staticFieldMap).length > 0) {
14655
+ this.staticDescriptorByField.set(collectionName, staticFieldMap);
14031
14656
  }
14032
- this.dictKeyFieldRegistry.set(collectionName, dictFieldMap);
14033
14657
  }
14034
14658
  if ((options?.schemaUpdate?.length ?? 0) > 0) {
14035
14659
  this.#schemaUpdateNames.set(collectionName, (options.schemaUpdate ?? []).map((s) => s.name));
@@ -14131,6 +14755,17 @@ var init_vault = __esm({
14131
14755
  if (options?.acknowledgeDeterministicRisk !== void 0) {
14132
14756
  collOpts.acknowledgeDeterministicRisk = options.acknowledgeDeterministicRisk;
14133
14757
  }
14758
+ if (options?.perRecordKeys !== void 0) {
14759
+ collOpts.perRecordKeys = options.perRecordKeys;
14760
+ }
14761
+ if (this.forgetStrategy.subjects[collectionName] !== void 0) {
14762
+ if (options?.perRecordKeys === false) {
14763
+ console.warn(
14764
+ `[noy-db] Collection "${collectionName}" is declared in withForgetCascade but opened with perRecordKeys: false. Forcing perRecordKeys: true \u2014 GDPR crypto-shred requires per-record CEKs.`
14765
+ );
14766
+ }
14767
+ collOpts.perRecordKeys = true;
14768
+ }
14134
14769
  if (options?.tiers !== void 0) collOpts.tiers = options.tiers;
14135
14770
  if (options?.tierMode !== void 0) collOpts.tierMode = options.tierMode;
14136
14771
  collOpts.onCrossTierAccess = (event) => this.emitCrossTier(event);
@@ -14140,6 +14775,11 @@ var init_vault = __esm({
14140
14775
  if (options?.computed !== void 0) collOpts.computed = options.computed;
14141
14776
  if (options?.dictKeyFields !== void 0) {
14142
14777
  collOpts.dictLabelResolver = async (dictName, key, locale, fallback) => {
14778
+ const stat = this.staticByName.get(dictName);
14779
+ if (stat) {
14780
+ const labels = stat.table[key];
14781
+ return labels ? resolveLabelFromMap(labels, locale, fallback) : void 0;
14782
+ }
14143
14783
  const handle = this.dictionary(dictName);
14144
14784
  return handle.resolveLabel(key, locale, fallback);
14145
14785
  };
@@ -14148,6 +14788,7 @@ var init_vault = __esm({
14148
14788
  if (options?.i18nFields !== void 0 || options?.dictKeyFields !== void 0) {
14149
14789
  collOpts.i18nPutValidator = (record) => {
14150
14790
  this.enforceI18nOnPut(collectionName, record);
14791
+ this.enforceStaticDictOnPut(collectionName, record);
14151
14792
  };
14152
14793
  }
14153
14794
  if (options?.i18nFields !== void 0 && this.translateText) {
@@ -14287,6 +14928,34 @@ var init_vault = __esm({
14287
14928
  }
14288
14929
  }
14289
14930
  }
14931
+ /**
14932
+ * Validate staticDict codes on a `put()` (#291). For each `staticDict()`
14933
+ * field, every stored code must be a declared key of the descriptor's
14934
+ * table, else `UnknownDictCodeError`. Opt out per descriptor with
14935
+ * `{ validateCodes: false }`. Supports scalar, dotted, and `[].`-wildcard
14936
+ * field paths via `getAtPath` (same path support as i18n validation).
14937
+ */
14938
+ enforceStaticDictOnPut(collectionName, record) {
14939
+ const staticFields = this.staticDescriptorByField.get(collectionName);
14940
+ if (!staticFields || Object.keys(staticFields).length === 0) return;
14941
+ if (!record || typeof record !== "object") return;
14942
+ const obj = record;
14943
+ for (const [field, desc] of Object.entries(staticFields)) {
14944
+ if (desc.validateCodes === false) continue;
14945
+ const known = new Set(desc.keys);
14946
+ const values = getAtPath(obj, field);
14947
+ for (const value of values) {
14948
+ if (value === void 0 || value === null) continue;
14949
+ const codes = Array.isArray(value) ? value : [value];
14950
+ for (const code of codes) {
14951
+ if (typeof code !== "string") continue;
14952
+ if (!known.has(code)) {
14953
+ throw new UnknownDictCodeError(desc.name, field, code);
14954
+ }
14955
+ }
14956
+ }
14957
+ }
14958
+ }
14290
14959
  /**
14291
14960
  * Apply locale resolution to a record for the given collection.
14292
14961
  *
@@ -14295,14 +14964,18 @@ var init_vault = __esm({
14295
14964
  */
14296
14965
  async applyLocale(collectionName, record, localeOpts) {
14297
14966
  const locale = localeOpts.locale ?? this.locale;
14298
- if (!locale) return record;
14967
+ const staticFields = this.staticDescriptorByField.get(collectionName);
14968
+ const hasStaticDisplay = staticFields !== void 0 && Object.values(staticFields).some((d) => d.displayLocale !== void 0);
14969
+ if (!locale && !hasStaticDisplay) return record;
14299
14970
  let result = record;
14300
- const i18nFields = this.i18nFieldRegistry.get(collectionName);
14301
- if (i18nFields && Object.keys(i18nFields).length > 0) {
14302
- result = this.i18nStrategy.applyI18nLocale(result, i18nFields, locale, localeOpts.fallback);
14971
+ if (locale) {
14972
+ const i18nFields = this.i18nFieldRegistry.get(collectionName);
14973
+ if (i18nFields && Object.keys(i18nFields).length > 0) {
14974
+ result = this.i18nStrategy.applyI18nLocale(result, i18nFields, locale, localeOpts.fallback);
14975
+ }
14303
14976
  }
14304
14977
  const dictFields = this.dictKeyFieldRegistry.get(collectionName);
14305
- if (dictFields && Object.keys(dictFields).length > 0 && locale !== "raw") {
14978
+ if (locale && dictFields && Object.keys(dictFields).length > 0 && locale !== "raw") {
14306
14979
  const withLabels = { ...result };
14307
14980
  for (const [field, dictName] of Object.entries(dictFields)) {
14308
14981
  const key = result[field];
@@ -14315,6 +14988,22 @@ var init_vault = __esm({
14315
14988
  }
14316
14989
  result = withLabels;
14317
14990
  }
14991
+ if (staticFields && Object.keys(staticFields).length > 0 && locale !== "raw") {
14992
+ const withLabels = { ...result };
14993
+ for (const [field, desc] of Object.entries(staticFields)) {
14994
+ const effLocale = locale ?? desc.displayLocale;
14995
+ if (!effLocale) continue;
14996
+ const key = result[field];
14997
+ if (typeof key !== "string") continue;
14998
+ const labels = desc.table[key];
14999
+ if (!labels) continue;
15000
+ const label = resolveLabelFromMap(labels, effLocale, localeOpts.fallback ?? desc.substitute);
15001
+ if (label !== void 0) {
15002
+ withLabels[`${field}Label`] = label;
15003
+ }
15004
+ }
15005
+ result = withLabels;
15006
+ }
14318
15007
  return result;
14319
15008
  }
14320
15009
  /**
@@ -14336,6 +15025,9 @@ var init_vault = __esm({
14336
15025
  * ```
14337
15026
  */
14338
15027
  dictionary(name, options = {}) {
15028
+ if (this.staticDictNames.has(name)) {
15029
+ throw new StaticDictReadonlyError(name);
15030
+ }
14339
15031
  let handle = this.dictionaryCache.get(name);
14340
15032
  if (!handle) {
14341
15033
  handle = this.i18nStrategy.buildDictionaryHandle({
@@ -14405,6 +15097,26 @@ var init_vault = __esm({
14405
15097
  * Returns `null` when `field` is not a dictKey in `leftCollection`.
14406
15098
  */
14407
15099
  resolveDictSource(leftCollection, field) {
15100
+ const staticFields = this.staticDescriptorByField.get(leftCollection);
15101
+ if (staticFields && field in staticFields) {
15102
+ const desc = staticFields[field];
15103
+ const rows = Object.entries(desc.table).map(
15104
+ ([key, labels]) => ({ key, labels, ...labels })
15105
+ );
15106
+ const source = {
15107
+ snapshot() {
15108
+ return rows;
15109
+ },
15110
+ lookupById(id) {
15111
+ return rows.find((e) => e["key"] === id);
15112
+ }
15113
+ };
15114
+ if (desc.displayLocale !== void 0) {
15115
+ ;
15116
+ source.displayLocale = desc.displayLocale;
15117
+ }
15118
+ return source;
15119
+ }
14408
15120
  const dictFields = this.dictKeyFieldRegistry.get(leftCollection);
14409
15121
  if (!dictFields || !(field in dictFields)) return null;
14410
15122
  const dictName = dictFields[field];
@@ -15048,6 +15760,218 @@ var init_vault = __esm({
15048
15760
  }
15049
15761
  return this.ledgerStore;
15050
15762
  }
15763
+ // ─── GDPR right-to-erasure (#304) ────────────────────────────────
15764
+ /** @internal — add a subject→record ref to the encrypted subject index. */
15765
+ async _addSubjectRef(subjectId, ref) {
15766
+ await addSubjectRef(this.adapter, this.name, this.getDEK, this.encrypted, subjectId, ref);
15767
+ }
15768
+ /** @internal — drop a subject→record ref from the encrypted subject index. */
15769
+ async _removeSubjectRef(subjectId, ref) {
15770
+ await removeSubjectRef(this.adapter, this.name, this.getDEK, this.encrypted, subjectId, ref);
15771
+ }
15772
+ /**
15773
+ * Rebuild the encrypted subject index from canonical records. The recovery
15774
+ * path for the documented read-modify-write race (RISK #3). Returns the
15775
+ * number of distinct subjects re-indexed.
15776
+ */
15777
+ async rebuildSubjectIndex() {
15778
+ if (Object.keys(this.forgetStrategy.subjects).length === 0) {
15779
+ throw new ForgetStrategyNotConfiguredError();
15780
+ }
15781
+ return rebuildSubjectIndex(
15782
+ this.adapter,
15783
+ this.name,
15784
+ this.getDEK,
15785
+ this.encrypted,
15786
+ this.forgetStrategy.subjects,
15787
+ async (collectionName, id, env) => {
15788
+ const coll = this.collection(collectionName);
15789
+ return coll._decodeEnvelope(env, id);
15790
+ }
15791
+ );
15792
+ }
15793
+ /**
15794
+ * GDPR crypto-shred of a data subject (#304). Consults the encrypted subject
15795
+ * index and, per matching record:
15796
+ * - rewrites the LIVE envelope to a tombstone (drops `_iv`/`_data`/`_cek`/`_det`),
15797
+ * - tombstones every `_history` version of the record,
15798
+ * so the body and all prior versions become permanently undecryptable while
15799
+ * the collection DEK and every OTHER record stay intact. Then appends ONE
15800
+ * `op:'forget'` ledger entry whose `payloadHash` is `sha256Hex(subjectId)` —
15801
+ * the chain still `verify()`s, PROVING the subject existed and was erased
15802
+ * without retaining any plaintext.
15803
+ *
15804
+ * Reports — but does not silently swallow — two completeness gaps:
15805
+ * - `unmigratedRecords`: a record whose body was NOT yet migrated to a
15806
+ * per-record CEK (legacy body still under the shared collection DEK). It
15807
+ * is still tombstoned, but its pre-shred ciphertext (if leaked to a
15808
+ * backup before migration) stays decryptable. Migrate, then re-forget.
15809
+ * - `blobResidueCollections`: a shredded record still has blob attachments,
15810
+ * which are keyed off a separate `_blob` DEK and are out of scope here.
15811
+ *
15812
+ * @throws ForgetStrategyNotConfiguredError when no `withForgetCascade` was set.
15813
+ */
15814
+ async forget(subjectId) {
15815
+ if (Object.keys(this.forgetStrategy.subjects).length === 0) {
15816
+ throw new ForgetStrategyNotConfiguredError();
15817
+ }
15818
+ const refs = await lookupSubject(this.adapter, this.name, this.getDEK, this.encrypted, subjectId);
15819
+ let recordsShredded = 0;
15820
+ let historyVersionsShredded = 0;
15821
+ const collections = /* @__PURE__ */ new Set();
15822
+ const unmigratedRecords = [];
15823
+ const blobResidueCollections = /* @__PURE__ */ new Set();
15824
+ let blobsShredded = 0;
15825
+ let blobsRetainedShared = 0;
15826
+ const blobsEnabled = this.blobStrategy !== void 0;
15827
+ const actor = this.keyring.userId;
15828
+ for (const ref of refs) {
15829
+ const coll = this.collection(ref.collection);
15830
+ const perRecordKeys = this.forgetStrategy.subjects[ref.collection] !== void 0;
15831
+ const live = await this.adapter.get(this.name, ref.collection, ref.id);
15832
+ if (perRecordKeys && live && live._data && live._cek === void 0) {
15833
+ unmigratedRecords.push(`${ref.collection}:${ref.id}`);
15834
+ }
15835
+ const shred = await coll._writeTombstone(ref.id, actor);
15836
+ if (shred !== null) {
15837
+ recordsShredded++;
15838
+ collections.add(ref.collection);
15839
+ }
15840
+ historyVersionsShredded += await this.historyStrategy.tombstoneHistory(
15841
+ this.adapter,
15842
+ this.name,
15843
+ ref.collection,
15844
+ ref.id,
15845
+ actor
15846
+ );
15847
+ if (blobsEnabled) {
15848
+ const r = await this.collection(ref.collection).blob(ref.id).shredAllForRecord();
15849
+ blobsShredded += r.shredded.length;
15850
+ blobsRetainedShared += r.retainedShared.length;
15851
+ if (r.residue.length > 0) blobResidueCollections.add(ref.collection);
15852
+ } else {
15853
+ try {
15854
+ const slotIds = await this.adapter.list(this.name, `_blob_slots_${ref.collection}`);
15855
+ if (slotIds.includes(ref.id)) blobResidueCollections.add(ref.collection);
15856
+ } catch {
15857
+ }
15858
+ }
15859
+ await this._removeSubjectRef(subjectId, ref);
15860
+ }
15861
+ const subjectHash = await sha256Hex3(subjectId);
15862
+ const ledger = this.getLedgerOrNull();
15863
+ if (!ledger) {
15864
+ throw new Error(
15865
+ 'vault.forget() requires the history strategy for the erasure-proof ledger entry. Pass `historyStrategy: withHistory()` from "@noy-db/hub/history" to createNoydb().'
15866
+ );
15867
+ }
15868
+ const ledgerEntry = await ledger.append({
15869
+ op: "forget",
15870
+ collection: "",
15871
+ id: "",
15872
+ version: 0,
15873
+ actor,
15874
+ payloadHash: subjectHash,
15875
+ reason: JSON.stringify({
15876
+ recordsShredded,
15877
+ historyVersionsShredded,
15878
+ collections: [...collections],
15879
+ unmigratedCount: unmigratedRecords.length,
15880
+ blobsShredded,
15881
+ blobsRetainedShared,
15882
+ blobResidueCollections: [...blobResidueCollections]
15883
+ })
15884
+ });
15885
+ return {
15886
+ subject: subjectId,
15887
+ recordsShredded,
15888
+ historyVersionsShredded,
15889
+ collections: [...collections],
15890
+ unmigratedRecords,
15891
+ blobsShredded,
15892
+ blobsRetainedShared,
15893
+ blobResidueCollections: [...blobResidueCollections],
15894
+ ledgerEntry
15895
+ };
15896
+ }
15897
+ // ─── Record-scoped CEK sealing (#306 slices 2-3) ──────────────────────
15898
+ /**
15899
+ * Seal ONE record's content-encryption key (CEK) to an `at-*` host so that
15900
+ * host — and only that host — can decrypt exactly that record, with no
15901
+ * access to the vault DEK and no ability to read any other record.
15902
+ *
15903
+ * The grantor (this caller, who holds the collection DEK) reads the record's
15904
+ * live `_cek`, unwraps it under the collection DEK, exports the raw CEK
15905
+ * bytes, builds a {@link SealedCekBinding} `{collection, id, cek, expiresAt}`,
15906
+ * seals that binding for the recipient host via the host's published hint,
15907
+ * and persists a thin {@link SealedCekDeliveryEnvelope} at
15908
+ * `_sealed_cek/<collection>/<id>/<pid>`. The binding (not the delivery
15909
+ * envelope) is the security boundary: the host re-verifies `{collection, id}`
15910
+ * and `expiresAt` from inside the sealed payload.
15911
+ *
15912
+ * Only works on a `perRecordKeys` record — a legacy record has no `_cek` to
15913
+ * seal (its body is under the shared collection DEK, which is never exposed
15914
+ * by sealing) → {@link RecordCekNotFoundError}.
15915
+ *
15916
+ * @param collection Collection holding the record.
15917
+ * @param id Record id.
15918
+ * @param hostSealer The recipient host's {@link RecipientSealer}.
15919
+ * @param opts.expiresAt REQUIRED authoritative expiry (ISO 8601), sealed into
15920
+ * the binding the host verifies.
15921
+ * @returns `{ pid, envelopeKey }` — the host provider id and the
15922
+ * `<collection>/<id>/<pid>` key the delivery envelope was written under.
15923
+ */
15924
+ async sealRecordToHost(collection, id, hostSealer, opts) {
15925
+ return sealRecordToHost(this.sealingContext(), collection, id, hostSealer, opts);
15926
+ }
15927
+ /**
15928
+ * Revoke a single sealed-CEK delivery envelope by deleting it from the store.
15929
+ * A soft revocation: it removes the host's copy of the sealed CEK, but a host
15930
+ * that already fetched the envelope keeps whatever it cached. For a hard
15931
+ * revocation that makes the live record undecryptable to every prior grant,
15932
+ * use {@link rotateRecordCek}.
15933
+ */
15934
+ async revokeSealedRecord(collection, id, pid) {
15935
+ return revokeSealedRecord(this.sealingContext(), collection, id, pid);
15936
+ }
15937
+ /**
15938
+ * HARD-rotate a record's CEK: decrypt the live body under the old CEK,
15939
+ * re-encrypt it under a freshly-minted CEK, write the new live envelope, evict
15940
+ * the in-memory caches, and delete EVERY sealed-CEK delivery envelope for the
15941
+ * record. After this, any host holding a previously-sealed CEK can still
15942
+ * decrypt PRE-rotation history versions (they keep their old `_cek`) but NOT
15943
+ * the rotated live record (its body is under the new CEK → the old CEK fails
15944
+ * the AES-GCM auth tag → `TamperedError`). That asymmetry IS the revocation:
15945
+ * old grants lose the live record.
15946
+ *
15947
+ * Administrative path — bypasses `Collection.put` deliberately (no guards, no
15948
+ * history snapshot, no materialized-view refresh): rotation is a key-rotation
15949
+ * operation, not a business write, and must not version-bump history (which
15950
+ * would re-encrypt the prior version under the NEW CEK and defeat the point).
15951
+ *
15952
+ * @throws {@link RecordCekNotFoundError} if the record is missing or has no `_cek`.
15953
+ */
15954
+ async rotateRecordCek(collection, id) {
15955
+ return rotateRecordCek(this.sealingContext(), collection, id);
15956
+ }
15957
+ /**
15958
+ * Build the {@link SealingContext} the record-keys grantor functions need:
15959
+ * the vault-bound adapter, DEK resolver, actor, and the dual-cache eviction
15960
+ * `rotateRecordCek` performs (per-record CEK cache + decrypted-record cache).
15961
+ */
15962
+ sealingContext() {
15963
+ return {
15964
+ adapter: this.adapter,
15965
+ vault: this.name,
15966
+ getDEK: (collection) => this.getDEK(collection),
15967
+ actor: this.keyring.userId,
15968
+ invalidateRecordCaches: async (collection, id) => {
15969
+ const coll = this.collection(collection);
15970
+ coll._invalidateCekCacheEntry(id);
15971
+ await coll._invalidateCacheEntry(id);
15972
+ }
15973
+ };
15974
+ }
15051
15975
  /**
15052
15976
  * @internal — called by `Noydb.openVault` after construction.
15053
15977
  * Dynamic-imports `GuardRegistry` + `ReadOnlyVaultFacade` and seeds
@@ -17150,7 +18074,7 @@ var init_unlock_state = __esm({
17150
18074
 
17151
18075
  // src/snapshots/strategy.ts
17152
18076
  var NOT_ENABLED5, NO_SNAPSHOTS;
17153
- var init_strategy10 = __esm({
18077
+ var init_strategy11 = __esm({
17154
18078
  "src/snapshots/strategy.ts"() {
17155
18079
  "use strict";
17156
18080
  NOT_ENABLED5 = new Error(
@@ -17291,7 +18215,7 @@ var init_scheduler = __esm({
17291
18215
 
17292
18216
  // src/tx/strategy.ts
17293
18217
  var NOT_ENABLED6, NO_TX;
17294
- var init_strategy11 = __esm({
18218
+ var init_strategy12 = __esm({
17295
18219
  "src/tx/strategy.ts"() {
17296
18220
  "use strict";
17297
18221
  NOT_ENABLED6 = new Error(
@@ -17327,7 +18251,7 @@ function notEnabled4(op) {
17327
18251
  );
17328
18252
  }
17329
18253
  var NO_SESSION;
17330
- var init_strategy12 = __esm({
18254
+ var init_strategy13 = __esm({
17331
18255
  "src/session/strategy.ts"() {
17332
18256
  "use strict";
17333
18257
  NO_SESSION = {
@@ -18553,12 +19477,14 @@ var init_noydb = __esm({
18553
19477
  init_authenticators();
18554
19478
  init_unlock_state();
18555
19479
  init_sync_strategy();
18556
- init_strategy10();
19480
+ init_strategy11();
18557
19481
  init_scheduler();
18558
19482
  init_transaction();
18559
- init_strategy11();
18560
- init_sync_policy();
18561
19483
  init_strategy12();
19484
+ init_strategy7();
19485
+ init_subject_index();
19486
+ init_sync_policy();
19487
+ init_strategy13();
18562
19488
  init_policy3();
18563
19489
  ROLE_RANK = {
18564
19490
  client: 1,
@@ -18614,6 +19540,7 @@ var init_noydb = __esm({
18614
19540
  policyEnforcers = /* @__PURE__ */ new Map();
18615
19541
  vaultTemplates = /* @__PURE__ */ new Map();
18616
19542
  txStrategy;
19543
+ forgetStrategy;
18617
19544
  sessionStrategy;
18618
19545
  syncStrategy;
18619
19546
  snapshotStrategy;
@@ -18641,6 +19568,7 @@ var init_noydb = __esm({
18641
19568
  constructor(options) {
18642
19569
  this.options = options;
18643
19570
  this.txStrategy = options.txStrategy ?? NO_TX;
19571
+ this.forgetStrategy = options.forgetStrategy ?? NO_FORGET;
18644
19572
  this.sessionStrategy = options.sessionStrategy ?? NO_SESSION;
18645
19573
  this.syncStrategy = options.syncStrategy ?? NO_SYNC;
18646
19574
  this.snapshotStrategy = options.snapshotStrategy ?? NO_SNAPSHOTS;
@@ -18651,8 +19579,61 @@ var init_noydb = __esm({
18651
19579
  }
18652
19580
  this.#registerGuardGate();
18653
19581
  this.#registerPeriodGate();
19582
+ this.#registerForgetHooks();
18654
19583
  this.resetSessionTimer();
18655
19584
  }
19585
+ /** @internal — resolved forget strategy (NO_FORGET when not configured). */
19586
+ get _forgetStrategy() {
19587
+ return this.forgetStrategy;
19588
+ }
19589
+ // #304 — GDPR subject-index maintenance. When `withForgetCascade` declares
19590
+ // any subject fields, keep the encrypted `_subject_index` in lock-step with
19591
+ // writes so `vault.forget(subjectId)` can find every record for a subject.
19592
+ //
19593
+ // Two consumers are required because they cover disjoint events:
19594
+ // - onAfterWrite fires on create/update (NOT delete) — add the new ref;
19595
+ // on an update that changed the subject value, drop the stale ref.
19596
+ // - the subsystemBus `afterDelete` observer fires on delete (onAfterWrite
19597
+ // does NOT) — drop the ref so a deleted record never lingers in the
19598
+ // index (RISK #2). Without it, forget() would try to shred a ghost.
19599
+ #registerForgetHooks() {
19600
+ const subjects = this.forgetStrategy.subjects;
19601
+ if (Object.keys(subjects).length === 0) return;
19602
+ const subjectFieldFor = (collection) => subjects[collection];
19603
+ this.writeHooks.onAfterWrite(async (event) => {
19604
+ const field = subjectFieldFor(event.collection);
19605
+ if (field === void 0) return;
19606
+ const vault = this.vaultCache.get(event.vault);
19607
+ if (!vault) return;
19608
+ if (event.after !== null && typeof event.after === "object") {
19609
+ const subjectValue = readDottedPath(event.after, field);
19610
+ if (subjectValue !== void 0 && subjectValue !== null) {
19611
+ await vault._addSubjectRef(coerceSubjectId(subjectValue), { collection: event.collection, id: event.docId });
19612
+ }
19613
+ }
19614
+ if (event.op === "update" && event.before !== null && typeof event.before === "object") {
19615
+ const beforeValue = readDottedPath(event.before, field);
19616
+ const afterValue = event.after !== null && typeof event.after === "object" ? readDottedPath(event.after, field) : void 0;
19617
+ const beforeId = beforeValue === void 0 || beforeValue === null ? void 0 : coerceSubjectId(beforeValue);
19618
+ const afterId = afterValue === void 0 || afterValue === null ? void 0 : coerceSubjectId(afterValue);
19619
+ if (beforeId !== void 0 && beforeId !== afterId) {
19620
+ await vault._removeSubjectRef(beforeId, { collection: event.collection, id: event.docId });
19621
+ }
19622
+ }
19623
+ });
19624
+ this.subsystemBus.register("afterDelete", async (event) => {
19625
+ const field = subjectFieldFor(event.collection);
19626
+ if (field === void 0) return;
19627
+ const vault = this.vaultCache.get(event.vault);
19628
+ if (!vault) return;
19629
+ if (event.before !== null && typeof event.before === "object") {
19630
+ const subjectValue = readDottedPath(event.before, field);
19631
+ if (subjectValue !== void 0 && subjectValue !== null) {
19632
+ await vault._removeSubjectRef(coerceSubjectId(subjectValue), { collection: event.collection, id: event.docId });
19633
+ }
19634
+ }
19635
+ });
19636
+ }
18656
19637
  // Track A — guards migration. Registers record-lock / field-freeze / onDelete
18657
19638
  // / amendment-collect as gate-bus handlers (only when guards are opted in, so
18658
19639
  // the write path is zero-cost otherwise). Resolves the live vault's
@@ -18873,6 +19854,7 @@ var init_noydb = __esm({
18873
19854
  ...this.options.syncStrategy !== void 0 ? { syncStrategy: this.options.syncStrategy } : {},
18874
19855
  ...this.options.guardStrategies !== void 0 ? { guardStrategies: this.options.guardStrategies } : {},
18875
19856
  ...this.options.numbering !== void 0 ? { numberingConfigs: this.options.numbering } : {},
19857
+ forgetStrategy: this.forgetStrategy,
18876
19858
  locale: opts?.locale,
18877
19859
  // Thread the translator hook so Collection.put() can invoke it
18878
19860
  plaintextTranslator: this.options.plaintextTranslator ? (text, from, to, field, collection) => this.invokeTranslator(text, from, to, field, collection) : void 0,
@@ -18927,7 +19909,8 @@ var init_noydb = __esm({
18927
19909
  ...this.options.i18nStrategy !== void 0 ? { i18nStrategy: this.options.i18nStrategy } : {},
18928
19910
  ...this.options.syncStrategy !== void 0 ? { syncStrategy: this.options.syncStrategy } : {},
18929
19911
  ...this.options.guardStrategies !== void 0 ? { guardStrategies: this.options.guardStrategies } : {},
18930
- ...this.options.numbering !== void 0 ? { numberingConfigs: this.options.numbering } : {}
19912
+ ...this.options.numbering !== void 0 ? { numberingConfigs: this.options.numbering } : {},
19913
+ forgetStrategy: this.forgetStrategy
18931
19914
  });
18932
19915
  this.vaultCache.set(name, comp2);
18933
19916
  return comp2;
@@ -21712,6 +22695,7 @@ async function describeExtraction(vault, opts) {
21712
22695
  // src/bundle/extract-partition.ts
21713
22696
  init_types();
21714
22697
  init_crypto();
22698
+ init_record_keys();
21715
22699
  init_errors();
21716
22700
  init_ulid();
21717
22701
  init_storage2();
@@ -21731,6 +22715,14 @@ async function reKeyClosure(vault, closure) {
21731
22715
  for (const id of ids) {
21732
22716
  const env = await adapter.get(vaultName, collectionName, id);
21733
22717
  if (!env) continue;
22718
+ if (env._cek !== void 0) {
22719
+ const cek = await unwrapCek(env._cek, srcDek);
22720
+ const plaintext2 = await decrypt(env._iv, env._data, cek);
22721
+ const { iv: iv2, data: data2 } = await encrypt(plaintext2, cek);
22722
+ const wrapped = await wrapCek(cek, destDek);
22723
+ out[id] = { ...env, _iv: iv2, _data: data2, _cek: wrapped };
22724
+ continue;
22725
+ }
21734
22726
  const plaintext = await decrypt(env._iv, env._data, srcDek);
21735
22727
  const { iv, data } = await encrypt(plaintext, destDek);
21736
22728
  out[id] = { ...env, _iv: iv, _data: data };