@noy-db/hub 0.2.0-pre.15 → 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.
- package/dist/aggregate/index.cjs +106 -10
- package/dist/aggregate/index.cjs.map +1 -1
- package/dist/aggregate/index.d.cts +3 -2
- package/dist/aggregate/index.d.ts +3 -2
- package/dist/aggregate/index.js +4 -4
- package/dist/attestation/index.cjs.map +1 -1
- package/dist/attestation/index.d.cts +5 -3
- package/dist/attestation/index.d.ts +5 -3
- package/dist/attestation/index.js +6 -6
- package/dist/blobs/index.cjs +226 -11
- package/dist/blobs/index.cjs.map +1 -1
- package/dist/blobs/index.d.cts +6 -4
- package/dist/blobs/index.d.ts +6 -4
- package/dist/blobs/index.js +6 -5
- package/dist/blobs/index.js.map +1 -1
- package/dist/bundle/index.cjs +1268 -141
- package/dist/bundle/index.cjs.map +1 -1
- package/dist/bundle/index.d.cts +7 -5
- package/dist/bundle/index.d.ts +7 -5
- package/dist/bundle/index.js +21 -10
- package/dist/bundle/index.js.map +1 -1
- package/dist/{chunk-5LQG6ZO2.js → chunk-2FU2FTXD.js} +9 -4
- package/dist/chunk-2FU2FTXD.js.map +1 -0
- package/dist/{chunk-JD3OZAI4.js → chunk-3G3W65EQ.js} +2 -2
- package/dist/{chunk-XWH4MXIU.js → chunk-5AXTH4QZ.js} +2 -2
- package/dist/{chunk-4TBBMHVC.js → chunk-5LIROIDM.js} +2 -2
- package/dist/{chunk-3EWXMOK3.js → chunk-7H2GEJ3O.js} +28 -13
- package/dist/chunk-7H2GEJ3O.js.map +1 -0
- package/dist/{chunk-WGHU7BLI.js → chunk-AEIKD3PP.js} +52 -38
- package/dist/chunk-AEIKD3PP.js.map +1 -0
- package/dist/{chunk-KI6HAJWL.js → chunk-BH3X5L6A.js} +4 -4
- package/dist/{chunk-BQ65SS5A.js → chunk-BJSLBUJ7.js} +2 -2
- package/dist/{chunk-L2FE64BU.js → chunk-BL5GYANC.js} +3 -3
- package/dist/{chunk-A5ZOOZFB.js → chunk-BSZOCSDZ.js} +4 -4
- package/dist/{chunk-ZNQYHJXX.js → chunk-C3HYQPV4.js} +2 -2
- package/dist/{chunk-PE4AQGFH.js → chunk-CD2AVTEM.js} +5 -5
- package/dist/{chunk-7EFFHEN5.js → chunk-D77ZQSQQ.js} +852 -143
- package/dist/chunk-D77ZQSQQ.js.map +1 -0
- package/dist/{chunk-56DJ7JVK.js → chunk-DWEBTE2W.js} +5 -5
- package/dist/{chunk-Z4DO7YSI.js → chunk-DYYYUW5D.js} +2 -2
- package/dist/{chunk-NSCVNK5K.js → chunk-E77UKJYL.js} +5 -5
- package/dist/{chunk-KIP6JLTF.js → chunk-F4G63NTZ.js} +2 -2
- package/dist/{chunk-NU6Q3FOR.js → chunk-FEJDVE3Z.js} +12 -2
- package/dist/{chunk-NU6Q3FOR.js.map → chunk-FEJDVE3Z.js.map} +1 -1
- package/dist/{chunk-DQU36Q7I.js → chunk-GP3SDSH2.js} +14 -5
- package/dist/chunk-GP3SDSH2.js.map +1 -0
- package/dist/{chunk-IQLVUT37.js → chunk-H2MRGONI.js} +2 -2
- package/dist/{chunk-EYVQHAGH.js → chunk-HGVSHKZW.js} +8 -5
- package/dist/chunk-HGVSHKZW.js.map +1 -0
- package/dist/chunk-I5IUYN7B.js +125 -0
- package/dist/chunk-I5IUYN7B.js.map +1 -0
- package/dist/{chunk-CJORTUJ2.js → chunk-J7RWBXFY.js} +2 -2
- package/dist/{chunk-AAVWKNZW.js → chunk-JDWE6JMX.js} +2 -2
- package/dist/{chunk-6AJBSQU4.js → chunk-KCEHMDZF.js} +3 -3
- package/dist/{chunk-TS26M2SB.js → chunk-M476FOQ7.js} +2 -2
- package/dist/{chunk-F4OJZIWQ.js → chunk-NBBMMJ2H.js} +4 -4
- package/dist/{chunk-CZI2A4MQ.js → chunk-NYSYPFXJ.js} +3 -3
- package/dist/{chunk-EGD5DXFT.js → chunk-PDULVIBY.js} +14 -2
- package/dist/chunk-PDULVIBY.js.map +1 -0
- package/dist/{chunk-Z6FNBOTC.js → chunk-PDVP3C2I.js} +1 -1
- package/dist/{chunk-Z6FNBOTC.js.map → chunk-PDVP3C2I.js.map} +1 -1
- package/dist/{chunk-COFPAMX6.js → chunk-QHM6XEAH.js} +6 -6
- package/dist/{chunk-C5T5AFWN.js → chunk-QO6RGLLD.js} +12 -6
- package/dist/chunk-QO6RGLLD.js.map +1 -0
- package/dist/{chunk-7HT2MEZ5.js → chunk-ROPJVUG3.js} +23 -6
- package/dist/chunk-ROPJVUG3.js.map +1 -0
- package/dist/{chunk-VU7SWWT5.js → chunk-ROVO6NPJ.js} +11 -7
- package/dist/chunk-ROVO6NPJ.js.map +1 -0
- package/dist/{chunk-6RR3MNMG.js → chunk-SHX5QBCI.js} +3 -3
- package/dist/{chunk-GC4V7RU7.js → chunk-SISBMAPO.js} +1 -1
- package/dist/chunk-SISBMAPO.js.map +1 -0
- package/dist/{chunk-BIYRQQV6.js → chunk-SNMJ7SB3.js} +5 -5
- package/dist/{chunk-7PS7EOCF.js → chunk-TIDXB5DF.js} +4 -4
- package/dist/chunk-U5QCMH3W.js +151 -0
- package/dist/chunk-U5QCMH3W.js.map +1 -0
- package/dist/{chunk-YULZKK4F.js → chunk-UNTGHX5A.js} +37 -2
- package/dist/chunk-UNTGHX5A.js.map +1 -0
- package/dist/{chunk-FWPKCXTN.js → chunk-WIAOUFFB.js} +2 -2
- package/dist/{chunk-LX3CB26H.js → chunk-WV7WV6JO.js} +195 -17
- package/dist/chunk-WV7WV6JO.js.map +1 -0
- package/dist/{chunk-X73VS74Y.js → chunk-XJV6OB4D.js} +2 -2
- package/dist/{chunk-OHVFWCJP.js → chunk-XMHUK5PN.js} +49 -19
- package/dist/chunk-XMHUK5PN.js.map +1 -0
- package/dist/{chunk-WBAYSNUQ.js → chunk-XMVHEWF6.js} +4 -4
- package/dist/{chunk-HOR4R722.js → chunk-XPIHJ34I.js} +30 -4
- package/dist/chunk-XPIHJ34I.js.map +1 -0
- package/dist/{chunk-DKO2QFSA.js → chunk-YYVZYTWW.js} +3 -3
- package/dist/{chunk-535SSHBS.js → chunk-ZEGSDPB7.js} +81 -2
- package/dist/chunk-ZEGSDPB7.js.map +1 -0
- package/dist/{chunk-YHPM5D7Y.js → chunk-ZNGPEV5J.js} +63 -4
- package/dist/chunk-ZNGPEV5J.js.map +1 -0
- package/dist/consent/index.cjs.map +1 -1
- package/dist/consent/index.d.cts +6 -4
- package/dist/consent/index.d.ts +6 -4
- package/dist/consent/index.js +3 -3
- package/dist/{crypto-QXQOHMHF.js → crypto-7BN2HDWG.js} +7 -3
- package/dist/{delegation-NIQ43IPU.js → delegation-MGH5SODX.js} +5 -5
- package/dist/derivations/index.cjs +24 -3
- package/dist/derivations/index.cjs.map +1 -1
- package/dist/derivations/index.d.cts +7 -5
- package/dist/derivations/index.d.ts +7 -5
- package/dist/derivations/index.js +4 -4
- package/dist/{dev-unlock-iAS8z9jc.d.ts → dev-unlock-CI1ijTML.d.ts} +1 -1
- package/dist/{dev-unlock-nVkuRLLe.d.cts → dev-unlock-iXbYFAWl.d.cts} +1 -1
- package/dist/{strategy-CbneC7bS.d.ts → errors-Dz64FA65.d.cts} +98 -727
- package/dist/{strategy-CbneC7bS.d.cts → errors-Dz64FA65.d.ts} +98 -727
- package/dist/executor-3W63Y44O.js +11 -0
- package/dist/executor-CFFWPWBJ.js +8 -0
- package/dist/executor-VDQQOR4F.js +8 -0
- package/dist/{fanout-sidecar-N6OJX6QR.js → fanout-sidecar-FIJJ46YG.js} +2 -2
- package/dist/forget/index.cjs +43 -0
- package/dist/forget/index.cjs.map +1 -0
- package/dist/forget/index.d.cts +1 -0
- package/dist/forget/index.d.ts +1 -0
- package/dist/forget/index.js +14 -0
- package/dist/guards/index.cjs +9 -5
- package/dist/guards/index.cjs.map +1 -1
- package/dist/guards/index.d.cts +7 -5
- package/dist/guards/index.d.ts +7 -5
- package/dist/guards/index.js +6 -6
- package/dist/{hash-DHOnRarj.d.ts → hash-blk7Bkes.d.ts} +1 -1
- package/dist/{hash-Cv6byZs7.d.cts → hash-tEcM5fnv.d.cts} +1 -1
- package/dist/history/index.cjs +27 -4
- package/dist/history/index.cjs.map +1 -1
- package/dist/history/index.d.cts +7 -5
- package/dist/history/index.d.ts +7 -5
- package/dist/history/index.js +9 -7
- package/dist/history/index.js.map +1 -1
- package/dist/i18n/index.cjs +53 -0
- package/dist/i18n/index.cjs.map +1 -1
- package/dist/i18n/index.d.cts +6 -4
- package/dist/i18n/index.d.ts +6 -4
- package/dist/i18n/index.js +16 -8
- package/dist/i18n/index.js.map +1 -1
- package/dist/{immutable-guard-BehB1YGB.d.ts → immutable-guard-B5M95nbq.d.ts} +16 -1
- package/dist/{immutable-guard-yBEOYmif.d.cts → immutable-guard-qN3zF8o1.d.cts} +16 -1
- package/dist/index-C-SSRIxP.d.cts +348 -0
- package/dist/index-C-SSRIxP.d.ts +348 -0
- package/dist/{index-XNB2r6bX.d.ts → index-DpU6KWof.d.ts} +9 -1
- package/dist/{index-D95VK1Qy.d.cts → index-u-kWzSrL.d.cts} +9 -1
- package/dist/index.cjs +2715 -1302
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -12
- package/dist/index.d.ts +16 -12
- package/dist/index.js +132 -106
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.cjs.map +1 -1
- package/dist/indexing/index.js +4 -4
- package/dist/issue-TTMGHQ2J.js +12 -0
- package/dist/{ledger-CWSE3BLF.js → ledger-LFVLHE5H.js} +6 -6
- package/dist/materialized-views/index.cjs +407 -5
- package/dist/materialized-views/index.cjs.map +1 -1
- package/dist/materialized-views/index.d.cts +7 -5
- package/dist/materialized-views/index.d.ts +7 -5
- package/dist/materialized-views/index.js +12 -12
- package/dist/noydb-36S6GQNC.js +37 -0
- package/dist/overlay-views/index.cjs +47 -17
- package/dist/overlay-views/index.cjs.map +1 -1
- package/dist/overlay-views/index.d.cts +28 -8
- package/dist/overlay-views/index.d.ts +28 -8
- package/dist/overlay-views/index.js +4 -4
- package/dist/periods/index.cjs.map +1 -1
- package/dist/periods/index.d.cts +6 -4
- package/dist/periods/index.d.ts +6 -4
- package/dist/periods/index.js +6 -6
- package/dist/{public-envelope-SYHEYQ3X.js → public-envelope-RXZNP3V6.js} +4 -4
- package/dist/query/index.cjs +28 -11
- package/dist/query/index.cjs.map +1 -1
- package/dist/query/index.d.cts +3 -2
- package/dist/query/index.d.ts +3 -2
- package/dist/query/index.js +6 -6
- package/dist/registry-3YFLZ7WD.js +8 -0
- package/dist/{registry-DK5YWAAA.js → registry-SECUWSGY.js} +3 -3
- package/dist/registry-TGZISEWC.js +8 -0
- package/dist/{revoke-ZDFKMR5E.js → revoke-B54H2S2W.js} +6 -6
- package/dist/sealed-record/index.cjs +139 -0
- package/dist/sealed-record/index.cjs.map +1 -0
- package/dist/sealed-record/index.d.cts +123 -0
- package/dist/sealed-record/index.d.ts +123 -0
- package/dist/sealed-record/index.js +42 -0
- package/dist/sealed-record/index.js.map +1 -0
- package/dist/session/index.cjs.map +1 -1
- package/dist/session/index.d.cts +7 -5
- package/dist/session/index.d.ts +7 -5
- package/dist/session/index.js +3 -3
- package/dist/shadow/index.cjs.map +1 -1
- package/dist/shadow/index.d.cts +6 -4
- package/dist/shadow/index.d.ts +6 -4
- package/dist/shadow/index.js +2 -2
- package/dist/{signer-P5D7Y72U.js → signer-YSXZT574.js} +5 -5
- package/dist/snapshots/index.cjs.map +1 -1
- package/dist/snapshots/index.d.cts +6 -4
- package/dist/snapshots/index.d.ts +6 -4
- package/dist/snapshots/index.js +4 -4
- package/dist/{stale-JH67FU57.js → stale-TOA36SRK.js} +2 -2
- package/dist/stale-TOA36SRK.js.map +1 -0
- package/dist/{state-vault-TMXZRTY5.js → state-vault-W2OEABNO.js} +3 -3
- package/dist/store/index.cjs.map +1 -1
- package/dist/store/index.d.cts +6 -4
- package/dist/store/index.d.ts +6 -4
- package/dist/store/index.js +2 -2
- package/dist/strategy-4M9jo172.d.ts +739 -0
- package/dist/strategy-CLC1j79g.d.cts +739 -0
- package/dist/sync/index.cjs.map +1 -1
- package/dist/sync/index.d.cts +5 -3
- package/dist/sync/index.d.ts +5 -3
- package/dist/sync/index.js +4 -4
- package/dist/team/index.cjs.map +1 -1
- package/dist/team/index.d.cts +6 -4
- package/dist/team/index.d.ts +6 -4
- package/dist/team/index.js +8 -8
- package/dist/tx/index.cjs +66 -3
- package/dist/tx/index.cjs.map +1 -1
- package/dist/tx/index.d.cts +24 -6
- package/dist/tx/index.d.ts +24 -6
- package/dist/tx/index.js +9 -5
- package/dist/tx/index.js.map +1 -1
- package/dist/{types-4t1-tWS4.d.ts → types-CljIHm_J.d.ts} +1127 -606
- package/dist/{types-BpPV5uyy.d.cts → types-CrSpRDuG.d.cts} +1127 -606
- package/dist/{ulid-DAfenvFd.d.ts → ulid-CWfL2Vfv.d.ts} +1 -1
- package/dist/{ulid-CiPrpGqm.d.cts → ulid-CrI7PPbA.d.cts} +1 -1
- package/dist/util/index.cjs.map +1 -1
- package/dist/util/index.js +1 -1
- package/dist/{vault-group-KOM7QRJG.js → vault-group-DHAHFX2A.js} +4 -4
- package/dist/{with-derivation-OK9M2sJE.d.ts → with-derivation-BZ2y4bzF.d.ts} +1 -1
- package/dist/{with-derivation-DBqJB3dQ.d.cts → with-derivation-Bozs8DmD.d.cts} +1 -1
- package/dist/{with-materialized-view-Dt-ufPWQ.d.ts → with-materialized-view-B892zYZV.d.ts} +1 -1
- package/dist/{with-materialized-view-NzuxYPDF.d.cts → with-materialized-view-NzF71cG_.d.cts} +1 -1
- package/dist/{with-overlayed-view-eDvMs6LO.d.ts → with-overlayed-view-CR6m7CHe.d.ts} +1 -1
- package/dist/{with-overlayed-view-CC0_ocy-.d.cts → with-overlayed-view-UI8qSGL4.d.cts} +1 -1
- package/package.json +23 -3
- package/dist/chunk-3EWXMOK3.js.map +0 -1
- package/dist/chunk-535SSHBS.js.map +0 -1
- package/dist/chunk-5LQG6ZO2.js.map +0 -1
- package/dist/chunk-7EFFHEN5.js.map +0 -1
- package/dist/chunk-7HT2MEZ5.js.map +0 -1
- package/dist/chunk-C5T5AFWN.js.map +0 -1
- package/dist/chunk-DQU36Q7I.js.map +0 -1
- package/dist/chunk-EGD5DXFT.js.map +0 -1
- package/dist/chunk-EYVQHAGH.js.map +0 -1
- package/dist/chunk-GC4V7RU7.js.map +0 -1
- package/dist/chunk-HOR4R722.js.map +0 -1
- package/dist/chunk-LX3CB26H.js.map +0 -1
- package/dist/chunk-OHVFWCJP.js.map +0 -1
- package/dist/chunk-VU7SWWT5.js.map +0 -1
- package/dist/chunk-WGHU7BLI.js.map +0 -1
- package/dist/chunk-YHPM5D7Y.js.map +0 -1
- package/dist/chunk-YULZKK4F.js.map +0 -1
- package/dist/executor-6ZDSDZ6V.js +0 -8
- package/dist/executor-HSSRXDOB.js +0 -11
- package/dist/executor-IDZDAFNH.js +0 -8
- package/dist/issue-ADVS4OVP.js +0 -12
- package/dist/noydb-GZGFBA4E.js +0 -35
- package/dist/registry-IUZQVVBB.js +0 -8
- package/dist/registry-XGLNADIE.js +0 -8
- /package/dist/{chunk-JD3OZAI4.js.map → chunk-3G3W65EQ.js.map} +0 -0
- /package/dist/{chunk-XWH4MXIU.js.map → chunk-5AXTH4QZ.js.map} +0 -0
- /package/dist/{chunk-4TBBMHVC.js.map → chunk-5LIROIDM.js.map} +0 -0
- /package/dist/{chunk-KI6HAJWL.js.map → chunk-BH3X5L6A.js.map} +0 -0
- /package/dist/{chunk-BQ65SS5A.js.map → chunk-BJSLBUJ7.js.map} +0 -0
- /package/dist/{chunk-L2FE64BU.js.map → chunk-BL5GYANC.js.map} +0 -0
- /package/dist/{chunk-A5ZOOZFB.js.map → chunk-BSZOCSDZ.js.map} +0 -0
- /package/dist/{chunk-ZNQYHJXX.js.map → chunk-C3HYQPV4.js.map} +0 -0
- /package/dist/{chunk-PE4AQGFH.js.map → chunk-CD2AVTEM.js.map} +0 -0
- /package/dist/{chunk-56DJ7JVK.js.map → chunk-DWEBTE2W.js.map} +0 -0
- /package/dist/{chunk-Z4DO7YSI.js.map → chunk-DYYYUW5D.js.map} +0 -0
- /package/dist/{chunk-NSCVNK5K.js.map → chunk-E77UKJYL.js.map} +0 -0
- /package/dist/{chunk-KIP6JLTF.js.map → chunk-F4G63NTZ.js.map} +0 -0
- /package/dist/{chunk-IQLVUT37.js.map → chunk-H2MRGONI.js.map} +0 -0
- /package/dist/{chunk-CJORTUJ2.js.map → chunk-J7RWBXFY.js.map} +0 -0
- /package/dist/{chunk-AAVWKNZW.js.map → chunk-JDWE6JMX.js.map} +0 -0
- /package/dist/{chunk-6AJBSQU4.js.map → chunk-KCEHMDZF.js.map} +0 -0
- /package/dist/{chunk-TS26M2SB.js.map → chunk-M476FOQ7.js.map} +0 -0
- /package/dist/{chunk-F4OJZIWQ.js.map → chunk-NBBMMJ2H.js.map} +0 -0
- /package/dist/{chunk-CZI2A4MQ.js.map → chunk-NYSYPFXJ.js.map} +0 -0
- /package/dist/{chunk-COFPAMX6.js.map → chunk-QHM6XEAH.js.map} +0 -0
- /package/dist/{chunk-6RR3MNMG.js.map → chunk-SHX5QBCI.js.map} +0 -0
- /package/dist/{chunk-BIYRQQV6.js.map → chunk-SNMJ7SB3.js.map} +0 -0
- /package/dist/{chunk-7PS7EOCF.js.map → chunk-TIDXB5DF.js.map} +0 -0
- /package/dist/{chunk-FWPKCXTN.js.map → chunk-WIAOUFFB.js.map} +0 -0
- /package/dist/{chunk-X73VS74Y.js.map → chunk-XJV6OB4D.js.map} +0 -0
- /package/dist/{chunk-WBAYSNUQ.js.map → chunk-XMVHEWF6.js.map} +0 -0
- /package/dist/{chunk-DKO2QFSA.js.map → chunk-YYVZYTWW.js.map} +0 -0
- /package/dist/{crypto-QXQOHMHF.js.map → crypto-7BN2HDWG.js.map} +0 -0
- /package/dist/{delegation-NIQ43IPU.js.map → delegation-MGH5SODX.js.map} +0 -0
- /package/dist/{executor-6ZDSDZ6V.js.map → executor-3W63Y44O.js.map} +0 -0
- /package/dist/{executor-HSSRXDOB.js.map → executor-CFFWPWBJ.js.map} +0 -0
- /package/dist/{executor-IDZDAFNH.js.map → executor-VDQQOR4F.js.map} +0 -0
- /package/dist/{fanout-sidecar-N6OJX6QR.js.map → fanout-sidecar-FIJJ46YG.js.map} +0 -0
- /package/dist/{issue-ADVS4OVP.js.map → forget/index.js.map} +0 -0
- /package/dist/{ledger-CWSE3BLF.js.map → issue-TTMGHQ2J.js.map} +0 -0
- /package/dist/{noydb-GZGFBA4E.js.map → ledger-LFVLHE5H.js.map} +0 -0
- /package/dist/{public-envelope-SYHEYQ3X.js.map → noydb-36S6GQNC.js.map} +0 -0
- /package/dist/{registry-DK5YWAAA.js.map → public-envelope-RXZNP3V6.js.map} +0 -0
- /package/dist/{registry-IUZQVVBB.js.map → registry-3YFLZ7WD.js.map} +0 -0
- /package/dist/{registry-XGLNADIE.js.map → registry-SECUWSGY.js.map} +0 -0
- /package/dist/{revoke-ZDFKMR5E.js.map → registry-TGZISEWC.js.map} +0 -0
- /package/dist/{signer-P5D7Y72U.js.map → revoke-B54H2S2W.js.map} +0 -0
- /package/dist/{stale-JH67FU57.js.map → signer-YSXZT574.js.map} +0 -0
- /package/dist/{state-vault-TMXZRTY5.js.map → state-vault-W2OEABNO.js.map} +0 -0
- /package/dist/{vault-group-KOM7QRJG.js.map → vault-group-DHAHFX2A.js.map} +0 -0
package/dist/bundle/index.cjs
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
},
|
|
@@ -4834,13 +5091,22 @@ var init_strategy4 = __esm({
|
|
|
4834
5091
|
});
|
|
4835
5092
|
|
|
4836
5093
|
// src/money/money-reducer.ts
|
|
4837
|
-
function
|
|
4838
|
-
if (typeof v === "
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
5094
|
+
function toScaledIntFromAny(v, scale) {
|
|
5095
|
+
if (typeof v === "bigint") return v;
|
|
5096
|
+
if (typeof v === "number") {
|
|
5097
|
+
const r = parseToScaledInt(v, scale);
|
|
5098
|
+
return r.ok ? r.value : null;
|
|
5099
|
+
}
|
|
5100
|
+
if (typeof v === "string") {
|
|
5101
|
+
if (!v.includes(".")) {
|
|
5102
|
+
try {
|
|
5103
|
+
return BigInt(v);
|
|
5104
|
+
} catch {
|
|
5105
|
+
return null;
|
|
5106
|
+
}
|
|
4843
5107
|
}
|
|
5108
|
+
const r = parseToScaledInt(v, scale);
|
|
5109
|
+
return r.ok ? r.value : null;
|
|
4844
5110
|
}
|
|
4845
5111
|
return null;
|
|
4846
5112
|
}
|
|
@@ -4848,13 +5114,15 @@ function readMoney(record, field, desc) {
|
|
|
4848
5114
|
const raw = readPath(record, field);
|
|
4849
5115
|
if (raw === null || raw === void 0) return null;
|
|
4850
5116
|
if (desc.mode === "fixed") {
|
|
4851
|
-
const
|
|
4852
|
-
|
|
5117
|
+
const cur = desc.fixedCurrency;
|
|
5118
|
+
const value2 = toScaledIntFromAny(raw, desc.scaleFor(cur));
|
|
5119
|
+
return value2 === null ? null : { currency: cur, value: value2 };
|
|
4853
5120
|
}
|
|
4854
5121
|
if (typeof raw !== "object") return null;
|
|
4855
5122
|
const o = raw;
|
|
4856
5123
|
if (typeof o.currency !== "string") return null;
|
|
4857
|
-
const
|
|
5124
|
+
const scale = desc.allows(o.currency) ? desc.scaleFor(o.currency) : 0;
|
|
5125
|
+
const value = toScaledIntFromAny(o.amount, scale);
|
|
4858
5126
|
return value === null ? null : { currency: o.currency, value };
|
|
4859
5127
|
}
|
|
4860
5128
|
function targetScaleFor(desc, currency) {
|
|
@@ -5268,6 +5536,7 @@ function buildDictLabelResolver(joinCtx, field) {
|
|
|
5268
5536
|
const dictSource = joinCtx.resolveDictSource(field);
|
|
5269
5537
|
if (!dictSource) return void 0;
|
|
5270
5538
|
const snapshot = dictSource.snapshot();
|
|
5539
|
+
const displayLocale = dictSource.displayLocale;
|
|
5271
5540
|
const dictMap = /* @__PURE__ */ new Map();
|
|
5272
5541
|
for (const entry of snapshot) {
|
|
5273
5542
|
const k = entry["key"];
|
|
@@ -5277,9 +5546,11 @@ function buildDictLabelResolver(joinCtx, field) {
|
|
|
5277
5546
|
}
|
|
5278
5547
|
}
|
|
5279
5548
|
return async (key, locale, fallback) => {
|
|
5549
|
+
const effLocale = locale || displayLocale;
|
|
5550
|
+
if (!effLocale) return void 0;
|
|
5280
5551
|
const labels = dictMap.get(key);
|
|
5281
5552
|
if (!labels) return void 0;
|
|
5282
|
-
if (labels[
|
|
5553
|
+
if (labels[effLocale] !== void 0) return labels[effLocale];
|
|
5283
5554
|
const chain = Array.isArray(fallback) ? fallback : fallback ? [fallback] : [];
|
|
5284
5555
|
for (const fb of chain) {
|
|
5285
5556
|
if (fb === "any") {
|
|
@@ -6005,11 +6276,14 @@ function warnCardinalityApproaching(fields, observed) {
|
|
|
6005
6276
|
`[noy-db] .groupBy(${label}) produced ${observed} distinct groups, ${Math.round(observed / GROUPBY_MAX_CARDINALITY * 100)}% of the ${GROUPBY_MAX_CARDINALITY}-group ceiling. Narrow the query with .where() before grouping, or switch to a lower-cardinality field.`
|
|
6006
6277
|
);
|
|
6007
6278
|
}
|
|
6008
|
-
function groupAndReduce(records, fieldOrFields, spec) {
|
|
6279
|
+
function groupAndReduce(records, fieldOrFields, spec, moneyFields) {
|
|
6009
6280
|
const fields = typeof fieldOrFields === "string" ? [fieldOrFields] : fieldOrFields;
|
|
6010
6281
|
if (fields.length === 0) {
|
|
6011
6282
|
throw new Error(".groupBy() requires at least one field");
|
|
6012
6283
|
}
|
|
6284
|
+
if (moneyFields) {
|
|
6285
|
+
spec = wrapMoneyReducers(spec, moneyFields);
|
|
6286
|
+
}
|
|
6013
6287
|
const buckets = /* @__PURE__ */ new Map();
|
|
6014
6288
|
const fieldLabel = fields.length === 1 ? fields[0] : `[${fields.join(", ")}]`;
|
|
6015
6289
|
for (const record of records) {
|
|
@@ -6065,6 +6339,7 @@ var init_groupby = __esm({
|
|
|
6065
6339
|
init_predicate();
|
|
6066
6340
|
init_canonical_key();
|
|
6067
6341
|
init_errors();
|
|
6342
|
+
init_money_reducer();
|
|
6068
6343
|
GROUPBY_WARN_CARDINALITY = 1e4;
|
|
6069
6344
|
GROUPBY_MAX_CARDINALITY = 1e5;
|
|
6070
6345
|
warnedCardinalityFields = /* @__PURE__ */ new Set();
|
|
@@ -7466,10 +7741,14 @@ function summarizeQueryPlan(query) {
|
|
|
7466
7741
|
});
|
|
7467
7742
|
}
|
|
7468
7743
|
function summarizeUnionPlan(spec) {
|
|
7469
|
-
const arms = (spec.unionSources ?? []).map((s) =>
|
|
7744
|
+
const arms = (spec.unionSources ?? []).map((s) => {
|
|
7745
|
+
const joins = s.join?.length ? `[${s.join.map((j) => `${j.field}\u2192${j.as}`).join(";")}]` : "";
|
|
7746
|
+
return `${s.collection}${joins}`;
|
|
7747
|
+
}).join(",");
|
|
7470
7748
|
const groupBy = Array.isArray(spec.groupBy) ? [...spec.groupBy].sort().join(",") : typeof spec.groupBy === "string" ? spec.groupBy : "";
|
|
7471
7749
|
const aggKeys = spec.aggregate ? Object.keys(spec.aggregate).sort().join(",") : "";
|
|
7472
|
-
|
|
7750
|
+
const moneyKeys = spec.moneyFields ? Object.keys(spec.moneyFields).sort().join(",") : "";
|
|
7751
|
+
return `union(${arms})|groupBy(${groupBy})|aggregate(${aggKeys})|money(${moneyKeys})`;
|
|
7473
7752
|
}
|
|
7474
7753
|
var init_dependency_analyzer = __esm({
|
|
7475
7754
|
"src/materialized-views/dependency-analyzer.ts"() {
|
|
@@ -7596,6 +7875,7 @@ var init_registry = __esm({
|
|
|
7596
7875
|
let isQuery = false;
|
|
7597
7876
|
if (spec.unionSources) {
|
|
7598
7877
|
dependencies = new Set(spec.unionSources.map((s) => s.collection));
|
|
7878
|
+
if (spec.sources) for (const s of spec.sources) dependencies.add(s);
|
|
7599
7879
|
queryPlanSummary = summarizeUnionPlan(spec);
|
|
7600
7880
|
} else {
|
|
7601
7881
|
const q = spec.query(dbForQuery);
|
|
@@ -7750,7 +8030,13 @@ async function materializeUnionResult(spec, db) {
|
|
|
7750
8030
|
const unified = [];
|
|
7751
8031
|
for (const arm of spec.unionSources) {
|
|
7752
8032
|
const coll = db.collection(arm.collection);
|
|
7753
|
-
|
|
8033
|
+
let q = coll.query();
|
|
8034
|
+
if (arm.join?.length) {
|
|
8035
|
+
for (const leg of arm.join) {
|
|
8036
|
+
q = q.join(leg.field, { as: leg.as, maxRows: leg.maxRows, strategy: leg.strategy });
|
|
8037
|
+
}
|
|
8038
|
+
}
|
|
8039
|
+
const sourceRows = q.toArray();
|
|
7754
8040
|
for (const r of sourceRows) {
|
|
7755
8041
|
const mapped = arm.map(r);
|
|
7756
8042
|
if (mapped == null) continue;
|
|
@@ -7767,7 +8053,7 @@ async function materializeUnionResult(spec, db) {
|
|
|
7767
8053
|
}
|
|
7768
8054
|
return [...seen.values()];
|
|
7769
8055
|
}
|
|
7770
|
-
return groupAndReduce(unified, groupFields, spec.aggregate);
|
|
8056
|
+
return groupAndReduce(unified, groupFields, spec.aggregate, spec.moneyFields);
|
|
7771
8057
|
}
|
|
7772
8058
|
async function listOutputIds(outputColl) {
|
|
7773
8059
|
const cAny = outputColl;
|
|
@@ -8038,12 +8324,14 @@ var init_collection = __esm({
|
|
|
8038
8324
|
init_types();
|
|
8039
8325
|
init_strategy();
|
|
8040
8326
|
init_core();
|
|
8327
|
+
init_dictionary();
|
|
8041
8328
|
init_normalize();
|
|
8042
8329
|
init_paths();
|
|
8043
8330
|
init_computed();
|
|
8044
8331
|
init_strategy2();
|
|
8045
8332
|
init_policy();
|
|
8046
8333
|
init_crypto();
|
|
8334
|
+
init_record_keys();
|
|
8047
8335
|
init_errors();
|
|
8048
8336
|
init_tiers();
|
|
8049
8337
|
init_keyring();
|
|
@@ -8241,6 +8529,25 @@ var init_collection = __esm({
|
|
|
8241
8529
|
* is inactive for this collection; a frozen `Set` otherwise.
|
|
8242
8530
|
*/
|
|
8243
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;
|
|
8244
8551
|
/**
|
|
8245
8552
|
* declared tiers for this collection. `null` when
|
|
8246
8553
|
* tier-aware methods are disabled. Tier 0 is implicit and never
|
|
@@ -8387,19 +8694,24 @@ var init_collection = __esm({
|
|
|
8387
8694
|
} else {
|
|
8388
8695
|
this.deterministicFields = null;
|
|
8389
8696
|
}
|
|
8697
|
+
this.perRecordCek = opts.perRecordKeys === true;
|
|
8698
|
+
this.cekCache = this.perRecordCek ? new Lru({ maxRecords: 4096 }) : null;
|
|
8390
8699
|
if (opts.crdt && opts.onRegisterConflictResolver) {
|
|
8391
8700
|
const crdtMode = opts.crdt;
|
|
8392
|
-
const crdtResolver = async (
|
|
8701
|
+
const crdtResolver = async (id, local, remote) => {
|
|
8393
8702
|
if (crdtMode === "yjs") {
|
|
8394
8703
|
return local._v >= remote._v ? local : remote;
|
|
8395
8704
|
}
|
|
8396
|
-
const localJson = await this.decryptJsonString(local);
|
|
8397
|
-
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;
|
|
8398
8709
|
const localState = JSON.parse(localJson);
|
|
8399
8710
|
const remoteState = JSON.parse(remoteJson);
|
|
8400
8711
|
const merged = this.crdtStrategy.mergeCrdtStates(localState, remoteState);
|
|
8401
8712
|
const mergedVersion = Math.max(local._v, remote._v) + 1;
|
|
8402
|
-
|
|
8713
|
+
const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
|
|
8714
|
+
return this.encryptJsonString(JSON.stringify(merged), mergedVersion, cek);
|
|
8403
8715
|
};
|
|
8404
8716
|
opts.onRegisterConflictResolver(this.name, crdtResolver);
|
|
8405
8717
|
}
|
|
@@ -8439,12 +8751,15 @@ var init_collection = __esm({
|
|
|
8439
8751
|
});
|
|
8440
8752
|
} else {
|
|
8441
8753
|
const mergeFn = policy;
|
|
8442
|
-
resolver = async (
|
|
8443
|
-
const localRecord = await this.decryptRecord(local, { skipValidation: true });
|
|
8444
|
-
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;
|
|
8445
8759
|
const merged = mergeFn(localRecord, remoteRecord);
|
|
8446
8760
|
const mergedVersion = Math.max(local._v, remote._v) + 1;
|
|
8447
|
-
|
|
8761
|
+
const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
|
|
8762
|
+
return this.encryptRecord(merged, mergedVersion, cek);
|
|
8448
8763
|
};
|
|
8449
8764
|
}
|
|
8450
8765
|
opts.onRegisterConflictResolver(collectionName, resolver);
|
|
@@ -8536,7 +8851,9 @@ var init_collection = __esm({
|
|
|
8536
8851
|
} else {
|
|
8537
8852
|
const envelope = await this.adapter.get(this.vault, this.name, id);
|
|
8538
8853
|
if (!envelope) return null;
|
|
8539
|
-
|
|
8854
|
+
if (isTombstone(envelope, this.encrypted)) return null;
|
|
8855
|
+
record = await this.decryptRecord(envelope, { id });
|
|
8856
|
+
if (record === null) return null;
|
|
8540
8857
|
this.lru.set(id, { record, version: envelope._v }, estimateRecordBytes(record));
|
|
8541
8858
|
}
|
|
8542
8859
|
} else {
|
|
@@ -8563,6 +8880,7 @@ var init_collection = __esm({
|
|
|
8563
8880
|
const envelope = await this.adapter.get(this.vault, this.name, id);
|
|
8564
8881
|
if (!envelope) return null;
|
|
8565
8882
|
const json = await this.decryptJsonString(envelope);
|
|
8883
|
+
if (json === null) return null;
|
|
8566
8884
|
return JSON.parse(json);
|
|
8567
8885
|
}
|
|
8568
8886
|
/**
|
|
@@ -8651,7 +8969,7 @@ var init_collection = __esm({
|
|
|
8651
8969
|
if (cached2) return { record: cached2.record, version: cached2.version };
|
|
8652
8970
|
const env = await this.adapter.get(this.vault, this.name, id);
|
|
8653
8971
|
if (!env) return { record: null, version: 0 };
|
|
8654
|
-
return { record: await this.decryptRecord(env, { skipValidation: true }), version: env._v };
|
|
8972
|
+
return { record: await this.decryptRecord(env, { skipValidation: true }) ?? null, version: env._v };
|
|
8655
8973
|
}
|
|
8656
8974
|
await this.ensureHydrated();
|
|
8657
8975
|
const cached = this.cache.get(id);
|
|
@@ -8764,9 +9082,11 @@ var init_collection = __esm({
|
|
|
8764
9082
|
let existingState;
|
|
8765
9083
|
if (existingEnvelope) {
|
|
8766
9084
|
const prevJson = await this.decryptJsonString(existingEnvelope);
|
|
8767
|
-
|
|
8768
|
-
|
|
8769
|
-
|
|
9085
|
+
if (prevJson !== null) {
|
|
9086
|
+
const prevParsed = JSON.parse(prevJson);
|
|
9087
|
+
if (prevParsed !== null && typeof prevParsed === "object" && "_crdt" in prevParsed) {
|
|
9088
|
+
existingState = prevParsed;
|
|
9089
|
+
}
|
|
8770
9090
|
}
|
|
8771
9091
|
}
|
|
8772
9092
|
crdtState = this.crdtStrategy.buildLwwMapState(record, existingState, now);
|
|
@@ -8774,9 +9094,11 @@ var init_collection = __esm({
|
|
|
8774
9094
|
let existingState;
|
|
8775
9095
|
if (existingEnvelope) {
|
|
8776
9096
|
const prevJson = await this.decryptJsonString(existingEnvelope);
|
|
8777
|
-
|
|
8778
|
-
|
|
8779
|
-
|
|
9097
|
+
if (prevJson !== null) {
|
|
9098
|
+
const prevParsed = JSON.parse(prevJson);
|
|
9099
|
+
if (prevParsed !== null && typeof prevParsed === "object" && "_crdt" in prevParsed) {
|
|
9100
|
+
existingState = prevParsed;
|
|
9101
|
+
}
|
|
8780
9102
|
}
|
|
8781
9103
|
}
|
|
8782
9104
|
const arr = Array.isArray(record) ? record : [record];
|
|
@@ -8785,12 +9107,14 @@ var init_collection = __esm({
|
|
|
8785
9107
|
crdtState = { _crdt: "yjs", update: record };
|
|
8786
9108
|
}
|
|
8787
9109
|
const version2 = existingVersion + 1;
|
|
8788
|
-
const
|
|
9110
|
+
const cek2 = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
|
|
9111
|
+
const envelope2 = await this.encryptJsonString(JSON.stringify(crdtState), version2, cek2);
|
|
8789
9112
|
await this.adapter.put(this.vault, this.name, id, envelope2);
|
|
8790
9113
|
const resolvedRecord = this.crdtStrategy.resolveCrdtSnapshot(crdtState);
|
|
8791
|
-
const
|
|
9114
|
+
const existingResolvedRecord = existingEnvelope ? await this.decryptRecord(existingEnvelope, { skipValidation: true }) : null;
|
|
9115
|
+
const existingResolved = existingResolvedRecord !== null ? { record: existingResolvedRecord, version: existingVersion } : void 0;
|
|
8792
9116
|
if (existingResolved && this.historyConfig.enabled !== false) {
|
|
8793
|
-
const histEnvelope = await this.encryptRecord(existingResolved.record, existingResolved.version);
|
|
9117
|
+
const histEnvelope = await this.encryptRecord(existingResolved.record, existingResolved.version, cek2);
|
|
8794
9118
|
await this.historyStrategy.saveHistory(this.adapter, this.vault, this.name, id, histEnvelope);
|
|
8795
9119
|
this.emitter.emit("history:save", { vault: this.vault, collection: this.name, id, version: existingResolved.version });
|
|
8796
9120
|
if (this.historyConfig.maxVersions) {
|
|
@@ -8836,7 +9160,9 @@ var init_collection = __esm({
|
|
|
8836
9160
|
const previousEnvelope = await this.adapter.get(this.vault, this.name, id);
|
|
8837
9161
|
if (previousEnvelope) {
|
|
8838
9162
|
const previousRecord = await this.decryptRecord(previousEnvelope);
|
|
8839
|
-
|
|
9163
|
+
if (previousRecord !== null) {
|
|
9164
|
+
existing = { record: previousRecord, version: previousEnvelope._v };
|
|
9165
|
+
}
|
|
8840
9166
|
}
|
|
8841
9167
|
}
|
|
8842
9168
|
} else {
|
|
@@ -8845,8 +9171,9 @@ var init_collection = __esm({
|
|
|
8845
9171
|
}
|
|
8846
9172
|
const version = existing ? existing.version + 1 : 1;
|
|
8847
9173
|
this.uniqueConstraints?.check(id, record);
|
|
9174
|
+
const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
|
|
8848
9175
|
if (existing && this.historyConfig.enabled !== false) {
|
|
8849
|
-
const historyEnvelope = await this.encryptRecord(existing.record, existing.version);
|
|
9176
|
+
const historyEnvelope = await this.encryptRecord(existing.record, existing.version, cek);
|
|
8850
9177
|
await this.historyStrategy.saveHistory(this.adapter, this.vault, this.name, id, historyEnvelope);
|
|
8851
9178
|
this.emitter.emit("history:save", {
|
|
8852
9179
|
vault: this.vault,
|
|
@@ -8860,7 +9187,7 @@ var init_collection = __esm({
|
|
|
8860
9187
|
});
|
|
8861
9188
|
}
|
|
8862
9189
|
}
|
|
8863
|
-
const envelope = await this.encryptRecord(record, version);
|
|
9190
|
+
const envelope = await this.encryptRecord(record, version, cek);
|
|
8864
9191
|
await this.adapter.put(this.vault, this.name, id, envelope);
|
|
8865
9192
|
if (this.ledger) {
|
|
8866
9193
|
const appendInput = {
|
|
@@ -8963,9 +9290,18 @@ var init_collection = __esm({
|
|
|
8963
9290
|
if (DerivationExecutor2 === null) {
|
|
8964
9291
|
({ DerivationExecutor: DerivationExecutor2 } = await Promise.resolve().then(() => (init_executor(), executor_exports)));
|
|
8965
9292
|
}
|
|
8966
|
-
|
|
9293
|
+
let sourceWithId;
|
|
9294
|
+
let sourceVersion = version;
|
|
9295
|
+
if (spec.source === this.name) {
|
|
9296
|
+
sourceWithId = { ...incoming, id };
|
|
9297
|
+
} else {
|
|
9298
|
+
const primary = await this.derivationSource.getCollection(spec.source).get(id);
|
|
9299
|
+
if (primary === null || primary === void 0) continue;
|
|
9300
|
+
sourceWithId = { ...primary, id };
|
|
9301
|
+
sourceVersion = 0;
|
|
9302
|
+
}
|
|
8967
9303
|
const ctx = { vault: this.derivationSource.getReadOnlyFacade() };
|
|
8968
|
-
const result = await DerivationExecutor2.run(spec, sourceWithId,
|
|
9304
|
+
const result = await DerivationExecutor2.run(spec, sourceWithId, sourceVersion, strategyHash, ctx);
|
|
8969
9305
|
for (const key of Object.keys(spec.outputs)) {
|
|
8970
9306
|
const out = result.outputs[key];
|
|
8971
9307
|
if (!out) continue;
|
|
@@ -9087,11 +9423,14 @@ var init_collection = __esm({
|
|
|
9087
9423
|
let count = 0;
|
|
9088
9424
|
for (const id of ids) {
|
|
9089
9425
|
const env = await this.adapter.get(this.vault, this.name, id);
|
|
9090
|
-
if (!env) continue;
|
|
9091
|
-
const
|
|
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;
|
|
9092
9430
|
const next = transform(record);
|
|
9093
9431
|
const nextVersion = (env._v ?? 0) + 1;
|
|
9094
|
-
const
|
|
9432
|
+
const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
|
|
9433
|
+
const newEnv = await this.encryptRecord(next, nextVersion, cek);
|
|
9095
9434
|
await this.adapter.put(this.vault, this.name, id, newEnv);
|
|
9096
9435
|
await this._invalidateCacheEntry(id);
|
|
9097
9436
|
if (this.ledger) {
|
|
@@ -9203,14 +9542,17 @@ var init_collection = __esm({
|
|
|
9203
9542
|
const previousEnvelope2 = await this.adapter.get(this.vault, this.name, id);
|
|
9204
9543
|
if (previousEnvelope2) {
|
|
9205
9544
|
const previousRecord = await this.decryptRecord(previousEnvelope2);
|
|
9206
|
-
|
|
9545
|
+
if (previousRecord !== null) {
|
|
9546
|
+
existing = { record: previousRecord, version: previousEnvelope2._v };
|
|
9547
|
+
}
|
|
9207
9548
|
}
|
|
9208
9549
|
}
|
|
9209
9550
|
} else {
|
|
9210
9551
|
existing = this.cache.get(id);
|
|
9211
9552
|
}
|
|
9212
9553
|
if (existing && this.historyConfig.enabled !== false) {
|
|
9213
|
-
const
|
|
9554
|
+
const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
|
|
9555
|
+
const historyEnvelope = await this.encryptRecord(existing.record, existing.version, cek);
|
|
9214
9556
|
await this.historyStrategy.saveHistory(this.adapter, this.vault, this.name, id, historyEnvelope);
|
|
9215
9557
|
}
|
|
9216
9558
|
const previousEnvelope = await this.adapter.get(this.vault, this.name, id);
|
|
@@ -9251,6 +9593,50 @@ var init_collection = __esm({
|
|
|
9251
9593
|
await this.dispatchArrayDerivationsOnDelete(id);
|
|
9252
9594
|
}
|
|
9253
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
|
+
}
|
|
9254
9640
|
/**
|
|
9255
9641
|
* Cascade deletes of array-shape derived rows when a source row is
|
|
9256
9642
|
* deleted. Reads each registered strategy's fanout sidecar
|
|
@@ -9663,6 +10049,7 @@ var init_collection = __esm({
|
|
|
9663
10049
|
const entries = [];
|
|
9664
10050
|
for (const env of envelopes) {
|
|
9665
10051
|
const record = await this.decryptRecord(env, { skipValidation: true });
|
|
10052
|
+
if (record === null) continue;
|
|
9666
10053
|
entries.push({
|
|
9667
10054
|
version: env._v,
|
|
9668
10055
|
timestamp: env._ts,
|
|
@@ -9799,6 +10186,7 @@ var init_collection = __esm({
|
|
|
9799
10186
|
const envelope = await this.adapter.get(this.vault, this.name, id);
|
|
9800
10187
|
if (envelope) {
|
|
9801
10188
|
const record = await this.decryptRecord(envelope);
|
|
10189
|
+
if (record === null) continue;
|
|
9802
10190
|
items.push(record);
|
|
9803
10191
|
if (!this.lazy && !this.cache.has(id)) {
|
|
9804
10192
|
this.cache.set(id, { record, version: envelope._v });
|
|
@@ -9875,6 +10263,7 @@ var init_collection = __esm({
|
|
|
9875
10263
|
const out = [];
|
|
9876
10264
|
for (const { id, envelope } of items) {
|
|
9877
10265
|
const record = await this.decryptRecord(envelope);
|
|
10266
|
+
if (record === null) continue;
|
|
9878
10267
|
out.push({ id, record, version: envelope._v });
|
|
9879
10268
|
}
|
|
9880
10269
|
return out;
|
|
@@ -9896,6 +10285,18 @@ var init_collection = __esm({
|
|
|
9896
10285
|
* the cache entry (record still present) or deletes it (record was
|
|
9897
10286
|
* gone before the tx and the revert deleted it again).
|
|
9898
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
|
+
}
|
|
9899
10300
|
async _invalidateCacheEntry(id) {
|
|
9900
10301
|
if (this.lazy && this.lru) {
|
|
9901
10302
|
this.lru.remove(id);
|
|
@@ -9913,6 +10314,14 @@ var init_collection = __esm({
|
|
|
9913
10314
|
return;
|
|
9914
10315
|
}
|
|
9915
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
|
+
}
|
|
9916
10325
|
this.cache.set(id, { record, version: envelope._v });
|
|
9917
10326
|
this.indexes?.upsert(id, record, previous ? previous.record : null);
|
|
9918
10327
|
this.uniqueConstraints?.upsert(id, record, previous?.record);
|
|
@@ -9937,8 +10346,9 @@ var init_collection = __esm({
|
|
|
9937
10346
|
const ids = await this.adapter.list(this.vault, this.name);
|
|
9938
10347
|
for (const id of ids) {
|
|
9939
10348
|
const envelope = await this.adapter.get(this.vault, this.name, id);
|
|
9940
|
-
if (envelope) {
|
|
9941
|
-
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;
|
|
9942
10352
|
this.cache.set(id, { record, version: envelope._v });
|
|
9943
10353
|
}
|
|
9944
10354
|
}
|
|
@@ -9949,7 +10359,9 @@ var init_collection = __esm({
|
|
|
9949
10359
|
/** Hydrate from a pre-loaded snapshot (used by Vault). */
|
|
9950
10360
|
async hydrateFromSnapshot(records) {
|
|
9951
10361
|
for (const [id, envelope] of Object.entries(records)) {
|
|
9952
|
-
|
|
10362
|
+
if (isTombstone(envelope, this.encrypted)) continue;
|
|
10363
|
+
const record = await this.decryptRecord(envelope, { id });
|
|
10364
|
+
if (record === null) continue;
|
|
9953
10365
|
this.cache.set(id, { record, version: envelope._v });
|
|
9954
10366
|
}
|
|
9955
10367
|
this.hydrated = true;
|
|
@@ -10037,6 +10449,7 @@ var init_collection = __esm({
|
|
|
10037
10449
|
const envelope = await this.adapter.get(this.vault, this.name, recordId2);
|
|
10038
10450
|
if (!envelope) continue;
|
|
10039
10451
|
const record = await this.decryptRecord(envelope, { skipValidation: true });
|
|
10452
|
+
if (record === null) continue;
|
|
10040
10453
|
await this.maintainPersistedIndexesOnPut(recordId2, record, null, envelope._v);
|
|
10041
10454
|
}
|
|
10042
10455
|
this.persistedIndexesLoaded = true;
|
|
@@ -10087,8 +10500,13 @@ var init_collection = __esm({
|
|
|
10087
10500
|
const env = await this.adapter.get(this.vault, this.name, id);
|
|
10088
10501
|
if (!env) continue;
|
|
10089
10502
|
try {
|
|
10090
|
-
const
|
|
10091
|
-
|
|
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
|
+
}
|
|
10092
10510
|
} catch {
|
|
10093
10511
|
sidecar.set(decoded.recordId, void 0);
|
|
10094
10512
|
}
|
|
@@ -10102,6 +10520,7 @@ var init_collection = __esm({
|
|
|
10102
10520
|
const env = await this.adapter.get(this.vault, this.name, id);
|
|
10103
10521
|
if (!env) continue;
|
|
10104
10522
|
const record = await this.decryptRecord(env, { skipValidation: true });
|
|
10523
|
+
if (record === null) continue;
|
|
10105
10524
|
const live = readPersistedValue(record, field);
|
|
10106
10525
|
const stored = sidecar.get(id);
|
|
10107
10526
|
const hasSidecar = sidecarIds.has(id);
|
|
@@ -10184,7 +10603,8 @@ var init_collection = __esm({
|
|
|
10184
10603
|
recordId: id,
|
|
10185
10604
|
getDEK: this.getDEK,
|
|
10186
10605
|
encrypted: this.encrypted,
|
|
10187
|
-
userId: this.keyring.userId
|
|
10606
|
+
userId: this.keyring.userId,
|
|
10607
|
+
erasableBlobs: this.perRecordCek
|
|
10188
10608
|
});
|
|
10189
10609
|
}
|
|
10190
10610
|
/** Get all records as encrypted envelopes (for dump). */
|
|
@@ -10192,7 +10612,8 @@ var init_collection = __esm({
|
|
|
10192
10612
|
await this.ensureHydrated();
|
|
10193
10613
|
const result = {};
|
|
10194
10614
|
for (const [id, entry] of this.cache) {
|
|
10195
|
-
|
|
10615
|
+
const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
|
|
10616
|
+
result[id] = await this.encryptRecord(entry.record, entry.version, cek);
|
|
10196
10617
|
}
|
|
10197
10618
|
return result;
|
|
10198
10619
|
}
|
|
@@ -10220,8 +10641,11 @@ var init_collection = __esm({
|
|
|
10220
10641
|
if (hasMoney && this.moneyFields) {
|
|
10221
10642
|
result = decodeMoneyFields(result, this.moneyFields, typeof locale === "string" ? locale : void 0);
|
|
10222
10643
|
}
|
|
10223
|
-
|
|
10224
|
-
|
|
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) {
|
|
10225
10649
|
result = this.i18nStrategy.applyI18nLocale(result, this.i18nFields, locale, localeOpts?.fallback);
|
|
10226
10650
|
}
|
|
10227
10651
|
if (hasDict && this.dictKeyFields && this.dictLabelResolver && locale !== "raw") {
|
|
@@ -10230,13 +10654,23 @@ var init_collection = __esm({
|
|
|
10230
10654
|
for (const [field, desc] of Object.entries(this.dictKeyFields)) {
|
|
10231
10655
|
const policy = desc.onMissing ? resolvePolicy(desc.onMissing, "read") : "null";
|
|
10232
10656
|
const fallback = policy === "substitute" ? localeOpts?.fallback ?? desc.substitute : localeOpts?.fallback;
|
|
10657
|
+
const effLocale = locale ?? (isStaticDictDescriptor(desc) ? desc.displayLocale : void 0);
|
|
10233
10658
|
const resolveKey = async (key) => {
|
|
10234
|
-
|
|
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);
|
|
10235
10669
|
if (label === void 0) {
|
|
10236
10670
|
if (policy === "throw") {
|
|
10237
10671
|
throw new LocaleNotSpecifiedError(
|
|
10238
10672
|
field,
|
|
10239
|
-
`dictKey "${field}": no label for key "${key}" in locale "${
|
|
10673
|
+
`dictKey "${field}": no label for key "${key}" in locale "${effLocale}".`
|
|
10240
10674
|
);
|
|
10241
10675
|
}
|
|
10242
10676
|
return null;
|
|
@@ -10393,6 +10827,7 @@ var init_collection = __esm({
|
|
|
10393
10827
|
if (!envelope) continue;
|
|
10394
10828
|
try {
|
|
10395
10829
|
const json = await this.decryptJsonString(envelope);
|
|
10830
|
+
if (json === null) continue;
|
|
10396
10831
|
const body = JSON.parse(json);
|
|
10397
10832
|
if (typeof body.recordId !== "string") continue;
|
|
10398
10833
|
const rows = byField.get(decoded.field) ?? [];
|
|
@@ -10502,7 +10937,31 @@ var init_collection = __esm({
|
|
|
10502
10937
|
};
|
|
10503
10938
|
return new LazyQuery(source);
|
|
10504
10939
|
}
|
|
10505
|
-
|
|
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) {
|
|
10506
10965
|
const by = this.keyring.userId;
|
|
10507
10966
|
if (!this.encrypted) {
|
|
10508
10967
|
return {
|
|
@@ -10515,6 +10974,19 @@ var init_collection = __esm({
|
|
|
10515
10974
|
};
|
|
10516
10975
|
}
|
|
10517
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
|
+
}
|
|
10518
10990
|
const { iv, data } = await encrypt(json, dek);
|
|
10519
10991
|
return {
|
|
10520
10992
|
_noydb: NOYDB_FORMAT_VERSION,
|
|
@@ -10525,8 +10997,8 @@ var init_collection = __esm({
|
|
|
10525
10997
|
_by: by
|
|
10526
10998
|
};
|
|
10527
10999
|
}
|
|
10528
|
-
async encryptRecord(record, version) {
|
|
10529
|
-
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);
|
|
10530
11002
|
if (!this.deterministicFields || !this.encrypted) return base;
|
|
10531
11003
|
const dek = await this.getDEK(this.name);
|
|
10532
11004
|
const rec = record;
|
|
@@ -10604,7 +11076,8 @@ var init_collection = __esm({
|
|
|
10604
11076
|
const env = await this.adapter.get(this.vault, this.name, id);
|
|
10605
11077
|
if (!env || !env._det) continue;
|
|
10606
11078
|
if (env._det[field] === target) {
|
|
10607
|
-
|
|
11079
|
+
const rec = await this.decryptRecord(env);
|
|
11080
|
+
if (rec !== null) matches.push(rec);
|
|
10608
11081
|
}
|
|
10609
11082
|
}
|
|
10610
11083
|
return matches;
|
|
@@ -10705,7 +11178,14 @@ var init_collection = __esm({
|
|
|
10705
11178
|
return null;
|
|
10706
11179
|
}
|
|
10707
11180
|
const dek = await this.getDEK(key);
|
|
10708
|
-
|
|
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
|
+
}
|
|
10709
11189
|
const record = JSON.parse(plaintext);
|
|
10710
11190
|
this.emitCrossTierEvent({
|
|
10711
11191
|
actor: this.keyring.userId,
|
|
@@ -10761,18 +11241,19 @@ var init_collection = __esm({
|
|
|
10761
11241
|
const toKey = dekKey(this.name, toTier);
|
|
10762
11242
|
const fromDek = await this.getDEK(fromKey);
|
|
10763
11243
|
const toDek = await this.getDEK(toKey);
|
|
10764
|
-
const plaintext = await decrypt(envelope._iv, envelope._data, fromDek);
|
|
10765
|
-
const { iv, data } = await encrypt(plaintext, toDek);
|
|
10766
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);
|
|
10767
11247
|
const next = {
|
|
10768
11248
|
_noydb: NOYDB_FORMAT_VERSION,
|
|
10769
11249
|
_v: envelope._v + 1,
|
|
10770
11250
|
_ts: now,
|
|
10771
|
-
_iv:
|
|
10772
|
-
_data:
|
|
11251
|
+
_iv: body._iv,
|
|
11252
|
+
_data: body._data,
|
|
10773
11253
|
_by: this.keyring.userId,
|
|
10774
11254
|
_tier: toTier,
|
|
10775
|
-
_elevatedBy: this.keyring.userId
|
|
11255
|
+
_elevatedBy: this.keyring.userId,
|
|
11256
|
+
...body._cek !== void 0 ? { _cek: body._cek } : {}
|
|
10776
11257
|
};
|
|
10777
11258
|
await this.adapter.put(this.vault, this.name, id, next);
|
|
10778
11259
|
this.emitCrossTierEvent({
|
|
@@ -10808,17 +11289,18 @@ var init_collection = __esm({
|
|
|
10808
11289
|
if (toTier > 0) this.assertDeclaredTier(toTier);
|
|
10809
11290
|
const fromDek = await this.getDEK(dekKey(this.name, fromTier));
|
|
10810
11291
|
const toDek = await this.getDEK(dekKey(this.name, toTier));
|
|
10811
|
-
const plaintext = await decrypt(envelope._iv, envelope._data, fromDek);
|
|
10812
|
-
const { iv, data } = await encrypt(plaintext, toDek);
|
|
10813
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);
|
|
10814
11295
|
const next = {
|
|
10815
11296
|
_noydb: NOYDB_FORMAT_VERSION,
|
|
10816
11297
|
_v: envelope._v + 1,
|
|
10817
11298
|
_ts: now,
|
|
10818
|
-
_iv:
|
|
10819
|
-
_data:
|
|
11299
|
+
_iv: body._iv,
|
|
11300
|
+
_data: body._data,
|
|
10820
11301
|
_by: this.keyring.userId,
|
|
10821
|
-
...toTier > 0 && { _tier: toTier }
|
|
11302
|
+
...toTier > 0 && { _tier: toTier },
|
|
11303
|
+
...body._cek !== void 0 ? { _cek: body._cek } : {}
|
|
10822
11304
|
};
|
|
10823
11305
|
await this.adapter.put(this.vault, this.name, id, next);
|
|
10824
11306
|
this.emitCrossTierEvent({
|
|
@@ -10840,10 +11322,30 @@ var init_collection = __esm({
|
|
|
10840
11322
|
} catch {
|
|
10841
11323
|
}
|
|
10842
11324
|
}
|
|
10843
|
-
/**
|
|
10844
|
-
|
|
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;
|
|
10845
11341
|
if (!this.encrypted) return envelope._data;
|
|
10846
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
|
+
}
|
|
10847
11349
|
return decrypt(envelope._iv, envelope._data, dek);
|
|
10848
11350
|
}
|
|
10849
11351
|
/**
|
|
@@ -10862,7 +11364,8 @@ var init_collection = __esm({
|
|
|
10862
11364
|
* false positive. Every non-history read leaves this flag `false`.
|
|
10863
11365
|
*/
|
|
10864
11366
|
async decryptRecord(envelope, opts = {}) {
|
|
10865
|
-
const json = await this.decryptJsonString(envelope);
|
|
11367
|
+
const json = await this.decryptJsonString(envelope, opts.id);
|
|
11368
|
+
if (json === null) return null;
|
|
10866
11369
|
let parsed = JSON.parse(json);
|
|
10867
11370
|
if (this.crdtMode && parsed !== null && typeof parsed === "object" && "_crdt" in parsed) {
|
|
10868
11371
|
parsed = this.crdtStrategy.resolveCrdtSnapshot(parsed);
|
|
@@ -10917,37 +11420,34 @@ var init_virtual_collection = __esm({
|
|
|
10917
11420
|
/** Get the merged row by id. */
|
|
10918
11421
|
async get(id) {
|
|
10919
11422
|
const overlayRow = await this.overlayCollection.get(id);
|
|
10920
|
-
if (overlayRow !== null && this.shadowPredicateApplies(overlayRow)) {
|
|
10921
|
-
return overlayRow;
|
|
10922
|
-
}
|
|
10923
11423
|
const baseRow = await this.baseCollection.get(id);
|
|
10924
|
-
|
|
10925
|
-
return null;
|
|
11424
|
+
return this.mergeRows(overlayRow, baseRow);
|
|
10926
11425
|
}
|
|
10927
11426
|
/** List union of base + overlay ids, applying the merge per row. */
|
|
10928
11427
|
async list() {
|
|
10929
11428
|
const baseRows = await this.baseCollection.list();
|
|
10930
11429
|
const overlayRows = await this.overlayCollection.list();
|
|
10931
|
-
const merged = /* @__PURE__ */ new Map();
|
|
10932
11430
|
const idOf = (row) => {
|
|
10933
11431
|
if (this.baseRowKey) return this.baseRowKey(row);
|
|
10934
11432
|
const idField = row.id;
|
|
10935
11433
|
return typeof idField === "string" ? idField : "";
|
|
10936
11434
|
};
|
|
11435
|
+
const baseById = /* @__PURE__ */ new Map();
|
|
11436
|
+
const overlayById = /* @__PURE__ */ new Map();
|
|
10937
11437
|
for (const row of baseRows) {
|
|
10938
11438
|
const id = idOf(row);
|
|
10939
|
-
if (id)
|
|
11439
|
+
if (id) baseById.set(id, row);
|
|
10940
11440
|
}
|
|
10941
11441
|
for (const row of overlayRows) {
|
|
10942
11442
|
const id = idOf(row);
|
|
10943
|
-
if (
|
|
10944
|
-
if (this.shadowPredicateApplies(row)) {
|
|
10945
|
-
merged.set(id, row);
|
|
10946
|
-
} else if (!merged.has(id)) {
|
|
10947
|
-
continue;
|
|
10948
|
-
}
|
|
11443
|
+
if (id) overlayById.set(id, row);
|
|
10949
11444
|
}
|
|
10950
|
-
|
|
11445
|
+
const out = [];
|
|
11446
|
+
for (const id of /* @__PURE__ */ new Set([...baseById.keys(), ...overlayById.keys()])) {
|
|
11447
|
+
const merged = this.mergeRows(overlayById.get(id) ?? null, baseById.get(id) ?? null);
|
|
11448
|
+
if (merged !== null) out.push(merged);
|
|
11449
|
+
}
|
|
11450
|
+
return out;
|
|
10951
11451
|
}
|
|
10952
11452
|
/**
|
|
10953
11453
|
* Write to the overlay. Two forms:
|
|
@@ -10987,9 +11487,42 @@ var init_virtual_collection = __esm({
|
|
|
10987
11487
|
async delete(id) {
|
|
10988
11488
|
await this.overlayCollection.delete(id);
|
|
10989
11489
|
}
|
|
10990
|
-
/**
|
|
10991
|
-
|
|
10992
|
-
|
|
11490
|
+
/**
|
|
11491
|
+
* Merge a single id's overlay + base rows into the visible row.
|
|
11492
|
+
*
|
|
11493
|
+
* Priority (first match wins):
|
|
11494
|
+
* 1. Binary shadow win — overlay present AND
|
|
11495
|
+
* `overlay[shadowField] === shadowValue` → return the overlay row
|
|
11496
|
+
* entirely. This stays FIRST so the original binary behaviour is
|
|
11497
|
+
* unchanged whether or not `mergeMode` is configured.
|
|
11498
|
+
* 2. Field-level merge — overlay present, `mergeMode` configured,
|
|
11499
|
+
* and a rule whose `whenStatus` equals `overlay[shadowField]`.
|
|
11500
|
+
* The matched rule pulls its `overlayFields` (those present on
|
|
11501
|
+
* the overlay row) on top of the base row. With no base row, the
|
|
11502
|
+
* overlay row is returned as-is.
|
|
11503
|
+
* 3. Fallback — return the base row (possibly `null`). An
|
|
11504
|
+
* overlay-only row that qualifies under neither (1) nor (2) and
|
|
11505
|
+
* has no base is therefore NOT surfaced.
|
|
11506
|
+
*/
|
|
11507
|
+
mergeRows(overlayRow, baseRow) {
|
|
11508
|
+
const shadowField = this.spec.shadowField;
|
|
11509
|
+
if (overlayRow !== null && overlayRow[shadowField] === this.spec.shadowValue) {
|
|
11510
|
+
return overlayRow;
|
|
11511
|
+
}
|
|
11512
|
+
if (overlayRow !== null && this.spec.mergeMode) {
|
|
11513
|
+
const status = overlayRow[shadowField];
|
|
11514
|
+
const rule = this.spec.mergeMode.rules.find((r) => r.whenStatus === status);
|
|
11515
|
+
if (rule) {
|
|
11516
|
+
if (baseRow === null) return overlayRow;
|
|
11517
|
+
const overlaySrc = overlayRow;
|
|
11518
|
+
const picked = {};
|
|
11519
|
+
for (const field of rule.overlayFields) {
|
|
11520
|
+
if (field in overlaySrc) picked[field] = overlaySrc[field];
|
|
11521
|
+
}
|
|
11522
|
+
return { ...baseRow, ...picked };
|
|
11523
|
+
}
|
|
11524
|
+
}
|
|
11525
|
+
return baseRow;
|
|
10993
11526
|
}
|
|
10994
11527
|
// ─── Throw-stubs for the unimplemented Collection<T> surface ───────
|
|
10995
11528
|
//
|
|
@@ -11139,6 +11672,21 @@ var init_archive = __esm({
|
|
|
11139
11672
|
});
|
|
11140
11673
|
|
|
11141
11674
|
// src/sequence/index.ts
|
|
11675
|
+
function resolveSequenceKey(series, opts) {
|
|
11676
|
+
const partition = opts?.partition;
|
|
11677
|
+
if (!partition || partition.length === 0) return series;
|
|
11678
|
+
const parts = partition.map((p) => {
|
|
11679
|
+
if (typeof p === "number" && !Number.isFinite(p)) {
|
|
11680
|
+
throw new ValidationError(`sequence partition component must be a finite number, got ${p}`);
|
|
11681
|
+
}
|
|
11682
|
+
const s = String(p);
|
|
11683
|
+
if (s === "") {
|
|
11684
|
+
throw new ValidationError("sequence partition component must not be empty");
|
|
11685
|
+
}
|
|
11686
|
+
return encodeURIComponent(s);
|
|
11687
|
+
});
|
|
11688
|
+
return `${series}\0${parts.join("/")}`;
|
|
11689
|
+
}
|
|
11142
11690
|
async function sleepBackoff2(attempt) {
|
|
11143
11691
|
const ceil = Math.min(2 ** attempt, 32);
|
|
11144
11692
|
const ms = Math.floor(Math.random() * ceil);
|
|
@@ -11178,7 +11726,8 @@ var init_sequence = __esm({
|
|
|
11178
11726
|
handle(name) {
|
|
11179
11727
|
return {
|
|
11180
11728
|
next: () => this.next(name),
|
|
11181
|
-
peek: () => this.peek(name)
|
|
11729
|
+
peek: () => this.peek(name),
|
|
11730
|
+
seedTo: (n) => this.seedTo(name, n)
|
|
11182
11731
|
};
|
|
11183
11732
|
}
|
|
11184
11733
|
assertOnline() {
|
|
@@ -11231,6 +11780,30 @@ var init_sequence = __esm({
|
|
|
11231
11780
|
void lastConflict;
|
|
11232
11781
|
throw new SequenceContentionError(name, MAX_NEXT_ATTEMPTS);
|
|
11233
11782
|
}
|
|
11783
|
+
async seedTo(name, n) {
|
|
11784
|
+
this.assertOnline();
|
|
11785
|
+
if (n <= 0) return;
|
|
11786
|
+
let lastConflict;
|
|
11787
|
+
for (let attempt = 0; attempt < MAX_NEXT_ATTEMPTS; attempt++) {
|
|
11788
|
+
const { env, value } = await this.read(name);
|
|
11789
|
+
if (value >= n) return;
|
|
11790
|
+
const expectedVersion = env?._v ?? 0;
|
|
11791
|
+
const envelope = await this.encryptState({ value: n }, expectedVersion + 1);
|
|
11792
|
+
try {
|
|
11793
|
+
await this.adapter.put(this.vault, SEQUENCE_COLLECTION, name, envelope, expectedVersion);
|
|
11794
|
+
return;
|
|
11795
|
+
} catch (err) {
|
|
11796
|
+
if (err instanceof ConflictError) {
|
|
11797
|
+
lastConflict = err;
|
|
11798
|
+
if (attempt < MAX_NEXT_ATTEMPTS - 1) await sleepBackoff2(attempt);
|
|
11799
|
+
continue;
|
|
11800
|
+
}
|
|
11801
|
+
throw err;
|
|
11802
|
+
}
|
|
11803
|
+
}
|
|
11804
|
+
void lastConflict;
|
|
11805
|
+
throw new SequenceContentionError(name, MAX_NEXT_ATTEMPTS);
|
|
11806
|
+
}
|
|
11234
11807
|
};
|
|
11235
11808
|
}
|
|
11236
11809
|
});
|
|
@@ -11397,9 +11970,125 @@ var init_numbering = __esm({
|
|
|
11397
11970
|
}
|
|
11398
11971
|
});
|
|
11399
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
|
+
|
|
11400
12089
|
// src/shadow/strategy.ts
|
|
11401
12090
|
var NOT_ENABLED3, NO_SHADOW;
|
|
11402
|
-
var
|
|
12091
|
+
var init_strategy8 = __esm({
|
|
11403
12092
|
"src/shadow/strategy.ts"() {
|
|
11404
12093
|
"use strict";
|
|
11405
12094
|
NOT_ENABLED3 = new Error(
|
|
@@ -11415,7 +12104,7 @@ var init_strategy7 = __esm({
|
|
|
11415
12104
|
|
|
11416
12105
|
// src/consent/strategy.ts
|
|
11417
12106
|
var NO_CONSENT;
|
|
11418
|
-
var
|
|
12107
|
+
var init_strategy9 = __esm({
|
|
11419
12108
|
"src/consent/strategy.ts"() {
|
|
11420
12109
|
"use strict";
|
|
11421
12110
|
NO_CONSENT = {
|
|
@@ -11430,7 +12119,7 @@ var init_strategy8 = __esm({
|
|
|
11430
12119
|
|
|
11431
12120
|
// src/periods/strategy.ts
|
|
11432
12121
|
var NOT_ENABLED4, NO_PERIODS;
|
|
11433
|
-
var
|
|
12122
|
+
var init_strategy10 = __esm({
|
|
11434
12123
|
"src/periods/strategy.ts"() {
|
|
11435
12124
|
"use strict";
|
|
11436
12125
|
NOT_ENABLED4 = new Error(
|
|
@@ -11542,18 +12231,6 @@ var init_refs = __esm({
|
|
|
11542
12231
|
}
|
|
11543
12232
|
});
|
|
11544
12233
|
|
|
11545
|
-
// src/i18n/dictionary.ts
|
|
11546
|
-
function isDictCollectionName(name) {
|
|
11547
|
-
return name.startsWith(DICT_COLLECTION_PREFIX);
|
|
11548
|
-
}
|
|
11549
|
-
var DICT_COLLECTION_PREFIX;
|
|
11550
|
-
var init_dictionary = __esm({
|
|
11551
|
-
"src/i18n/dictionary.ts"() {
|
|
11552
|
-
"use strict";
|
|
11553
|
-
DICT_COLLECTION_PREFIX = "_dict_";
|
|
11554
|
-
}
|
|
11555
|
-
});
|
|
11556
|
-
|
|
11557
12234
|
// src/periods/periods.ts
|
|
11558
12235
|
var PERIODS_COLLECTION;
|
|
11559
12236
|
var init_periods = __esm({
|
|
@@ -13245,11 +13922,15 @@ var init_read_only_facade = __esm({
|
|
|
13245
13922
|
});
|
|
13246
13923
|
|
|
13247
13924
|
// src/derivations/strategy-hash.ts
|
|
13248
|
-
async function computeStrategyHash(source, outputKeys, derive) {
|
|
13925
|
+
async function computeStrategyHash(source, outputKeys, derive, sources) {
|
|
13249
13926
|
const canonical2 = JSON.stringify({
|
|
13250
13927
|
source,
|
|
13251
13928
|
outputs: [...outputKeys].sort(),
|
|
13252
|
-
derive: derive.toString()
|
|
13929
|
+
derive: derive.toString(),
|
|
13930
|
+
// Declared sibling sources (#344) — adding/removing a trigger
|
|
13931
|
+
// collection invalidates cached derived records. Omitted when empty
|
|
13932
|
+
// so strategies without siblings keep their existing hash.
|
|
13933
|
+
...sources?.length ? { sources: [...sources].sort() } : {}
|
|
13253
13934
|
});
|
|
13254
13935
|
const bytes = new TextEncoder().encode(canonical2);
|
|
13255
13936
|
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
@@ -13278,11 +13959,16 @@ var init_registry3 = __esm({
|
|
|
13278
13959
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13279
13960
|
async register(spec) {
|
|
13280
13961
|
const outputKeys = Object.keys(spec.outputs);
|
|
13281
|
-
const strategyHash = await computeStrategyHash(spec.source, outputKeys, spec.derive);
|
|
13962
|
+
const strategyHash = await computeStrategyHash(spec.source, outputKeys, spec.derive, spec.sources);
|
|
13282
13963
|
const reg = { spec, strategyHash };
|
|
13283
13964
|
const fromSource = this._bySource.get(spec.source);
|
|
13284
13965
|
if (fromSource) fromSource.push(reg);
|
|
13285
13966
|
else this._bySource.set(spec.source, [reg]);
|
|
13967
|
+
for (const extra of spec.sources ?? []) {
|
|
13968
|
+
const fromExtra = this._bySource.get(extra);
|
|
13969
|
+
if (fromExtra) fromExtra.push(reg);
|
|
13970
|
+
else this._bySource.set(extra, [reg]);
|
|
13971
|
+
}
|
|
13286
13972
|
for (const key of outputKeys) {
|
|
13287
13973
|
const output = spec.outputs[key];
|
|
13288
13974
|
if (!output) continue;
|
|
@@ -13513,6 +14199,19 @@ var init_delegation = __esm({
|
|
|
13513
14199
|
});
|
|
13514
14200
|
|
|
13515
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
|
+
}
|
|
13516
14215
|
var Vault, ELEVATION_AUDIT_COLLECTION, ElevatedHandle;
|
|
13517
14216
|
var init_vault = __esm({
|
|
13518
14217
|
"src/vault.ts"() {
|
|
@@ -13531,8 +14230,11 @@ var init_vault = __esm({
|
|
|
13531
14230
|
init_entry();
|
|
13532
14231
|
init_strategy3();
|
|
13533
14232
|
init_strategy7();
|
|
14233
|
+
init_subject_index();
|
|
14234
|
+
init_errors();
|
|
13534
14235
|
init_strategy8();
|
|
13535
14236
|
init_strategy9();
|
|
14237
|
+
init_strategy10();
|
|
13536
14238
|
init_refs();
|
|
13537
14239
|
init_dictionary();
|
|
13538
14240
|
init_core();
|
|
@@ -13541,6 +14243,7 @@ var init_vault = __esm({
|
|
|
13541
14243
|
init_errors();
|
|
13542
14244
|
init_periods2();
|
|
13543
14245
|
init_crypto();
|
|
14246
|
+
init_record_keys();
|
|
13544
14247
|
init_export_blobs();
|
|
13545
14248
|
init_blob_compaction();
|
|
13546
14249
|
init_magic_link_grant();
|
|
@@ -13596,6 +14299,7 @@ var init_vault = __esm({
|
|
|
13596
14299
|
periodsStrategy;
|
|
13597
14300
|
shadowStrategy;
|
|
13598
14301
|
historyStrategy;
|
|
14302
|
+
forgetStrategy;
|
|
13599
14303
|
i18nStrategy;
|
|
13600
14304
|
syncStrategy;
|
|
13601
14305
|
/**
|
|
@@ -13762,6 +14466,27 @@ var init_vault = __esm({
|
|
|
13762
14466
|
* Populated by `collection()` when the `dictKeyFields` option is passed.
|
|
13763
14467
|
*/
|
|
13764
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();
|
|
13765
14490
|
/**
|
|
13766
14491
|
* Registry of i18nText fields declared across all collections. Keyed
|
|
13767
14492
|
* by collection name → field name → I18nTextDescriptor. Used by
|
|
@@ -13808,6 +14533,7 @@ var init_vault = __esm({
|
|
|
13808
14533
|
this.periodsStrategy = opts.periodsStrategy ?? NO_PERIODS;
|
|
13809
14534
|
this.shadowStrategy = opts.shadowStrategy ?? NO_SHADOW;
|
|
13810
14535
|
this.historyStrategy = opts.historyStrategy ?? NO_HISTORY;
|
|
14536
|
+
this.forgetStrategy = opts.forgetStrategy ?? NO_FORGET;
|
|
13811
14537
|
this.i18nStrategy = opts.i18nStrategy ?? NO_I18N;
|
|
13812
14538
|
this.syncStrategy = opts.syncStrategy ?? NO_SYNC;
|
|
13813
14539
|
void opts.guardStrategies;
|
|
@@ -13912,10 +14638,22 @@ var init_vault = __esm({
|
|
|
13912
14638
|
}
|
|
13913
14639
|
if (options?.dictKeyFields) {
|
|
13914
14640
|
const dictFieldMap = {};
|
|
14641
|
+
const staticFieldMap = {};
|
|
13915
14642
|
for (const [field, desc] of Object.entries(options.dictKeyFields)) {
|
|
13916
|
-
|
|
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);
|
|
13917
14656
|
}
|
|
13918
|
-
this.dictKeyFieldRegistry.set(collectionName, dictFieldMap);
|
|
13919
14657
|
}
|
|
13920
14658
|
if ((options?.schemaUpdate?.length ?? 0) > 0) {
|
|
13921
14659
|
this.#schemaUpdateNames.set(collectionName, (options.schemaUpdate ?? []).map((s) => s.name));
|
|
@@ -14017,6 +14755,17 @@ var init_vault = __esm({
|
|
|
14017
14755
|
if (options?.acknowledgeDeterministicRisk !== void 0) {
|
|
14018
14756
|
collOpts.acknowledgeDeterministicRisk = options.acknowledgeDeterministicRisk;
|
|
14019
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
|
+
}
|
|
14020
14769
|
if (options?.tiers !== void 0) collOpts.tiers = options.tiers;
|
|
14021
14770
|
if (options?.tierMode !== void 0) collOpts.tierMode = options.tierMode;
|
|
14022
14771
|
collOpts.onCrossTierAccess = (event) => this.emitCrossTier(event);
|
|
@@ -14026,6 +14775,11 @@ var init_vault = __esm({
|
|
|
14026
14775
|
if (options?.computed !== void 0) collOpts.computed = options.computed;
|
|
14027
14776
|
if (options?.dictKeyFields !== void 0) {
|
|
14028
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
|
+
}
|
|
14029
14783
|
const handle = this.dictionary(dictName);
|
|
14030
14784
|
return handle.resolveLabel(key, locale, fallback);
|
|
14031
14785
|
};
|
|
@@ -14034,6 +14788,7 @@ var init_vault = __esm({
|
|
|
14034
14788
|
if (options?.i18nFields !== void 0 || options?.dictKeyFields !== void 0) {
|
|
14035
14789
|
collOpts.i18nPutValidator = (record) => {
|
|
14036
14790
|
this.enforceI18nOnPut(collectionName, record);
|
|
14791
|
+
this.enforceStaticDictOnPut(collectionName, record);
|
|
14037
14792
|
};
|
|
14038
14793
|
}
|
|
14039
14794
|
if (options?.i18nFields !== void 0 && this.translateText) {
|
|
@@ -14173,6 +14928,34 @@ var init_vault = __esm({
|
|
|
14173
14928
|
}
|
|
14174
14929
|
}
|
|
14175
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
|
+
}
|
|
14176
14959
|
/**
|
|
14177
14960
|
* Apply locale resolution to a record for the given collection.
|
|
14178
14961
|
*
|
|
@@ -14181,14 +14964,18 @@ var init_vault = __esm({
|
|
|
14181
14964
|
*/
|
|
14182
14965
|
async applyLocale(collectionName, record, localeOpts) {
|
|
14183
14966
|
const locale = localeOpts.locale ?? this.locale;
|
|
14184
|
-
|
|
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;
|
|
14185
14970
|
let result = record;
|
|
14186
|
-
|
|
14187
|
-
|
|
14188
|
-
|
|
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
|
+
}
|
|
14189
14976
|
}
|
|
14190
14977
|
const dictFields = this.dictKeyFieldRegistry.get(collectionName);
|
|
14191
|
-
if (dictFields && Object.keys(dictFields).length > 0 && locale !== "raw") {
|
|
14978
|
+
if (locale && dictFields && Object.keys(dictFields).length > 0 && locale !== "raw") {
|
|
14192
14979
|
const withLabels = { ...result };
|
|
14193
14980
|
for (const [field, dictName] of Object.entries(dictFields)) {
|
|
14194
14981
|
const key = result[field];
|
|
@@ -14201,6 +14988,22 @@ var init_vault = __esm({
|
|
|
14201
14988
|
}
|
|
14202
14989
|
result = withLabels;
|
|
14203
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
|
+
}
|
|
14204
15007
|
return result;
|
|
14205
15008
|
}
|
|
14206
15009
|
/**
|
|
@@ -14222,6 +15025,9 @@ var init_vault = __esm({
|
|
|
14222
15025
|
* ```
|
|
14223
15026
|
*/
|
|
14224
15027
|
dictionary(name, options = {}) {
|
|
15028
|
+
if (this.staticDictNames.has(name)) {
|
|
15029
|
+
throw new StaticDictReadonlyError(name);
|
|
15030
|
+
}
|
|
14225
15031
|
let handle = this.dictionaryCache.get(name);
|
|
14226
15032
|
if (!handle) {
|
|
14227
15033
|
handle = this.i18nStrategy.buildDictionaryHandle({
|
|
@@ -14291,6 +15097,26 @@ var init_vault = __esm({
|
|
|
14291
15097
|
* Returns `null` when `field` is not a dictKey in `leftCollection`.
|
|
14292
15098
|
*/
|
|
14293
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
|
+
}
|
|
14294
15120
|
const dictFields = this.dictKeyFieldRegistry.get(leftCollection);
|
|
14295
15121
|
if (!dictFields || !(field in dictFields)) return null;
|
|
14296
15122
|
const dictName = dictFields[field];
|
|
@@ -14458,17 +15284,23 @@ var init_vault = __esm({
|
|
|
14458
15284
|
* const cur = await vault.sequence('invoice-2026').peek() // current value, no allocation
|
|
14459
15285
|
* ```
|
|
14460
15286
|
*/
|
|
14461
|
-
sequence(
|
|
14462
|
-
if (
|
|
15287
|
+
sequence(series, opts) {
|
|
15288
|
+
if (series.includes("\0")) {
|
|
15289
|
+
throw new ValidationError(`sequence("${series}"): series name must not contain a null byte (\\x00).`);
|
|
15290
|
+
}
|
|
15291
|
+
if (this.numberingConfigs.has(series)) {
|
|
14463
15292
|
const eng = this.deferred();
|
|
14464
15293
|
return {
|
|
14465
|
-
next: async (
|
|
14466
|
-
if (!
|
|
14467
|
-
throw new ValidationError(`sequence("${
|
|
15294
|
+
next: async (nextOpts) => {
|
|
15295
|
+
if (!nextOpts?.for) {
|
|
15296
|
+
throw new ValidationError(`sequence("${series}") is a deferred-numbering series; call next({ for: recordId }).`);
|
|
14468
15297
|
}
|
|
14469
|
-
return (await eng.enqueue(
|
|
15298
|
+
return (await eng.enqueue(series, nextOpts.for)).assigned;
|
|
14470
15299
|
},
|
|
14471
|
-
peek: () => eng.peek(
|
|
15300
|
+
peek: () => eng.peek(series),
|
|
15301
|
+
seedTo: () => {
|
|
15302
|
+
throw new ValidationError(`sequence("${series}") is a deferred-numbering series; seedTo is CAS-only.`);
|
|
15303
|
+
}
|
|
14472
15304
|
};
|
|
14473
15305
|
}
|
|
14474
15306
|
if (!this.sequenceStore) {
|
|
@@ -14480,7 +15312,7 @@ var init_vault = __esm({
|
|
|
14480
15312
|
actor: this.keyring.userId
|
|
14481
15313
|
});
|
|
14482
15314
|
}
|
|
14483
|
-
return this.sequenceStore.handle(
|
|
15315
|
+
return this.sequenceStore.handle(resolveSequenceKey(series, opts));
|
|
14484
15316
|
}
|
|
14485
15317
|
/** @internal — lazily build the deferred-numbering engine with a cache-coherent stamp. */
|
|
14486
15318
|
deferred() {
|
|
@@ -14772,9 +15604,24 @@ var init_vault = __esm({
|
|
|
14772
15604
|
});
|
|
14773
15605
|
}
|
|
14774
15606
|
if (rule.mode === "cascade") {
|
|
15607
|
+
const txCtx = this.noydb._activeTxContextOrNull;
|
|
14775
15608
|
for (const match of matches) {
|
|
14776
15609
|
const matchId = match["id"] ?? null;
|
|
14777
15610
|
if (matchId === null) continue;
|
|
15611
|
+
if (txCtx !== null) {
|
|
15612
|
+
const prior = await this.adapter.get(this.name, rule.collection, matchId);
|
|
15613
|
+
if (prior !== null) {
|
|
15614
|
+
txCtx._executed.push({
|
|
15615
|
+
op: {
|
|
15616
|
+
type: "delete",
|
|
15617
|
+
vaultName: this.name,
|
|
15618
|
+
collectionName: rule.collection,
|
|
15619
|
+
id: matchId
|
|
15620
|
+
},
|
|
15621
|
+
priorEnvelope: prior
|
|
15622
|
+
});
|
|
15623
|
+
}
|
|
15624
|
+
}
|
|
14778
15625
|
await fromCollection.delete(matchId);
|
|
14779
15626
|
}
|
|
14780
15627
|
}
|
|
@@ -14913,6 +15760,218 @@ var init_vault = __esm({
|
|
|
14913
15760
|
}
|
|
14914
15761
|
return this.ledgerStore;
|
|
14915
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
|
+
}
|
|
14916
15975
|
/**
|
|
14917
15976
|
* @internal — called by `Noydb.openVault` after construction.
|
|
14918
15977
|
* Dynamic-imports `GuardRegistry` + `ReadOnlyVaultFacade` and seeds
|
|
@@ -17015,7 +18074,7 @@ var init_unlock_state = __esm({
|
|
|
17015
18074
|
|
|
17016
18075
|
// src/snapshots/strategy.ts
|
|
17017
18076
|
var NOT_ENABLED5, NO_SNAPSHOTS;
|
|
17018
|
-
var
|
|
18077
|
+
var init_strategy11 = __esm({
|
|
17019
18078
|
"src/snapshots/strategy.ts"() {
|
|
17020
18079
|
"use strict";
|
|
17021
18080
|
NOT_ENABLED5 = new Error(
|
|
@@ -17156,14 +18215,14 @@ var init_scheduler = __esm({
|
|
|
17156
18215
|
|
|
17157
18216
|
// src/tx/strategy.ts
|
|
17158
18217
|
var NOT_ENABLED6, NO_TX;
|
|
17159
|
-
var
|
|
18218
|
+
var init_strategy12 = __esm({
|
|
17160
18219
|
"src/tx/strategy.ts"() {
|
|
17161
18220
|
"use strict";
|
|
17162
18221
|
NOT_ENABLED6 = new Error(
|
|
17163
18222
|
'Multi-record transactions require the tx strategy. Import `{ withTransactions }` from "@noy-db/hub/tx" and pass it to `createNoydb({ txStrategy: withTransactions() })`.'
|
|
17164
18223
|
);
|
|
17165
18224
|
NO_TX = {
|
|
17166
|
-
async runTransaction() {
|
|
18225
|
+
async runTransaction(_db, _fn, _options, _txInvariants) {
|
|
17167
18226
|
throw NOT_ENABLED6;
|
|
17168
18227
|
},
|
|
17169
18228
|
async runDryRun() {
|
|
@@ -17192,7 +18251,7 @@ function notEnabled4(op) {
|
|
|
17192
18251
|
);
|
|
17193
18252
|
}
|
|
17194
18253
|
var NO_SESSION;
|
|
17195
|
-
var
|
|
18254
|
+
var init_strategy13 = __esm({
|
|
17196
18255
|
"src/session/strategy.ts"() {
|
|
17197
18256
|
"use strict";
|
|
17198
18257
|
NO_SESSION = {
|
|
@@ -18418,12 +19477,14 @@ var init_noydb = __esm({
|
|
|
18418
19477
|
init_authenticators();
|
|
18419
19478
|
init_unlock_state();
|
|
18420
19479
|
init_sync_strategy();
|
|
18421
|
-
|
|
19480
|
+
init_strategy11();
|
|
18422
19481
|
init_scheduler();
|
|
18423
19482
|
init_transaction();
|
|
18424
|
-
init_strategy11();
|
|
18425
|
-
init_sync_policy();
|
|
18426
19483
|
init_strategy12();
|
|
19484
|
+
init_strategy7();
|
|
19485
|
+
init_subject_index();
|
|
19486
|
+
init_sync_policy();
|
|
19487
|
+
init_strategy13();
|
|
18427
19488
|
init_policy3();
|
|
18428
19489
|
ROLE_RANK = {
|
|
18429
19490
|
client: 1,
|
|
@@ -18479,6 +19540,7 @@ var init_noydb = __esm({
|
|
|
18479
19540
|
policyEnforcers = /* @__PURE__ */ new Map();
|
|
18480
19541
|
vaultTemplates = /* @__PURE__ */ new Map();
|
|
18481
19542
|
txStrategy;
|
|
19543
|
+
forgetStrategy;
|
|
18482
19544
|
sessionStrategy;
|
|
18483
19545
|
syncStrategy;
|
|
18484
19546
|
snapshotStrategy;
|
|
@@ -18506,6 +19568,7 @@ var init_noydb = __esm({
|
|
|
18506
19568
|
constructor(options) {
|
|
18507
19569
|
this.options = options;
|
|
18508
19570
|
this.txStrategy = options.txStrategy ?? NO_TX;
|
|
19571
|
+
this.forgetStrategy = options.forgetStrategy ?? NO_FORGET;
|
|
18509
19572
|
this.sessionStrategy = options.sessionStrategy ?? NO_SESSION;
|
|
18510
19573
|
this.syncStrategy = options.syncStrategy ?? NO_SYNC;
|
|
18511
19574
|
this.snapshotStrategy = options.snapshotStrategy ?? NO_SNAPSHOTS;
|
|
@@ -18516,8 +19579,61 @@ var init_noydb = __esm({
|
|
|
18516
19579
|
}
|
|
18517
19580
|
this.#registerGuardGate();
|
|
18518
19581
|
this.#registerPeriodGate();
|
|
19582
|
+
this.#registerForgetHooks();
|
|
18519
19583
|
this.resetSessionTimer();
|
|
18520
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
|
+
}
|
|
18521
19637
|
// Track A — guards migration. Registers record-lock / field-freeze / onDelete
|
|
18522
19638
|
// / amendment-collect as gate-bus handlers (only when guards are opted in, so
|
|
18523
19639
|
// the write path is zero-cost otherwise). Resolves the live vault's
|
|
@@ -18738,6 +19854,7 @@ var init_noydb = __esm({
|
|
|
18738
19854
|
...this.options.syncStrategy !== void 0 ? { syncStrategy: this.options.syncStrategy } : {},
|
|
18739
19855
|
...this.options.guardStrategies !== void 0 ? { guardStrategies: this.options.guardStrategies } : {},
|
|
18740
19856
|
...this.options.numbering !== void 0 ? { numberingConfigs: this.options.numbering } : {},
|
|
19857
|
+
forgetStrategy: this.forgetStrategy,
|
|
18741
19858
|
locale: opts?.locale,
|
|
18742
19859
|
// Thread the translator hook so Collection.put() can invoke it
|
|
18743
19860
|
plaintextTranslator: this.options.plaintextTranslator ? (text, from, to, field, collection) => this.invokeTranslator(text, from, to, field, collection) : void 0,
|
|
@@ -18792,7 +19909,8 @@ var init_noydb = __esm({
|
|
|
18792
19909
|
...this.options.i18nStrategy !== void 0 ? { i18nStrategy: this.options.i18nStrategy } : {},
|
|
18793
19910
|
...this.options.syncStrategy !== void 0 ? { syncStrategy: this.options.syncStrategy } : {},
|
|
18794
19911
|
...this.options.guardStrategies !== void 0 ? { guardStrategies: this.options.guardStrategies } : {},
|
|
18795
|
-
...this.options.numbering !== void 0 ? { numberingConfigs: this.options.numbering } : {}
|
|
19912
|
+
...this.options.numbering !== void 0 ? { numberingConfigs: this.options.numbering } : {},
|
|
19913
|
+
forgetStrategy: this.forgetStrategy
|
|
18796
19914
|
});
|
|
18797
19915
|
this.vaultCache.set(name, comp2);
|
|
18798
19916
|
return comp2;
|
|
@@ -21577,6 +22695,7 @@ async function describeExtraction(vault, opts) {
|
|
|
21577
22695
|
// src/bundle/extract-partition.ts
|
|
21578
22696
|
init_types();
|
|
21579
22697
|
init_crypto();
|
|
22698
|
+
init_record_keys();
|
|
21580
22699
|
init_errors();
|
|
21581
22700
|
init_ulid();
|
|
21582
22701
|
init_storage2();
|
|
@@ -21596,6 +22715,14 @@ async function reKeyClosure(vault, closure) {
|
|
|
21596
22715
|
for (const id of ids) {
|
|
21597
22716
|
const env = await adapter.get(vaultName, collectionName, id);
|
|
21598
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
|
+
}
|
|
21599
22726
|
const plaintext = await decrypt(env._iv, env._data, srcDek);
|
|
21600
22727
|
const { iv, data } = await encrypt(plaintext, destDek);
|
|
21601
22728
|
out[id] = { ...env, _iv: iv, _data: data };
|