@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
|
@@ -7,37 +7,68 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
TxContext,
|
|
9
9
|
revertExecuted
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-ZNGPEV5J.js";
|
|
11
11
|
import {
|
|
12
12
|
OverlayedCollection
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-XMHUK5PN.js";
|
|
14
|
+
import {
|
|
15
|
+
NO_AGGREGATE,
|
|
16
|
+
Query,
|
|
17
|
+
ScanBuilder,
|
|
18
|
+
canonicalizeIncomingMoney,
|
|
19
|
+
canonicalizeStoredMoney,
|
|
20
|
+
decodeMoneyFields,
|
|
21
|
+
quantizeMoneyFields,
|
|
22
|
+
validateMoneyFieldPaths
|
|
23
|
+
} from "./chunk-HGVSHKZW.js";
|
|
24
|
+
import {
|
|
25
|
+
EXPORT_AUDIT_COLLECTION,
|
|
26
|
+
createExportBlobsHandle,
|
|
27
|
+
runCompaction
|
|
28
|
+
} from "./chunk-KCEHMDZF.js";
|
|
14
29
|
import {
|
|
15
30
|
LazyQuery,
|
|
16
31
|
decodeIdxId,
|
|
17
32
|
encodeIdxId
|
|
18
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-NYSYPFXJ.js";
|
|
34
|
+
import {
|
|
35
|
+
canonicalGroupKey
|
|
36
|
+
} from "./chunk-7H2GEJ3O.js";
|
|
37
|
+
import {
|
|
38
|
+
readPath
|
|
39
|
+
} from "./chunk-J7RWBXFY.js";
|
|
19
40
|
import {
|
|
20
41
|
SCHEMAS_COLLECTION,
|
|
21
42
|
loadPersistedSchema,
|
|
22
43
|
resolveManagedSecret,
|
|
23
44
|
savePersistedSchema,
|
|
24
45
|
saveSealedPassphrase
|
|
25
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-AEIKD3PP.js";
|
|
26
47
|
import {
|
|
27
48
|
loadPublicEnvelope,
|
|
28
49
|
readPublicEnvelope,
|
|
29
50
|
savePublicEnvelope,
|
|
30
51
|
validatePublicEnvelopeInput
|
|
31
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-YYVZYTWW.js";
|
|
53
|
+
import {
|
|
54
|
+
buildTombstone,
|
|
55
|
+
isTombstone,
|
|
56
|
+
resolveStableCek,
|
|
57
|
+
revokeSealedRecord,
|
|
58
|
+
rewrapBodyToDek,
|
|
59
|
+
rotateRecordCek,
|
|
60
|
+
sealRecordToHost
|
|
61
|
+
} from "./chunk-U5QCMH3W.js";
|
|
32
62
|
import {
|
|
33
63
|
PERIODS_COLLECTION
|
|
34
|
-
} from "./chunk-
|
|
64
|
+
} from "./chunk-BH3X5L6A.js";
|
|
35
65
|
import {
|
|
36
66
|
getAtPath,
|
|
37
67
|
isDictCollectionName,
|
|
68
|
+
isStaticDictDescriptor,
|
|
38
69
|
resolvePolicy,
|
|
39
70
|
setAtPathInPlace
|
|
40
|
-
} from "./chunk-
|
|
71
|
+
} from "./chunk-ROPJVUG3.js";
|
|
41
72
|
import {
|
|
42
73
|
ManagedRecoveryNotEnrolledError,
|
|
43
74
|
PolicyDeniedError,
|
|
@@ -59,11 +90,11 @@ import {
|
|
|
59
90
|
saveShamirRecoveryEntries,
|
|
60
91
|
updateAuthenticator,
|
|
61
92
|
writeMagicLinkGrant
|
|
62
|
-
} from "./chunk-
|
|
93
|
+
} from "./chunk-QHM6XEAH.js";
|
|
63
94
|
import {
|
|
64
95
|
assertTierAccess,
|
|
65
96
|
dekKey
|
|
66
|
-
} from "./chunk-
|
|
97
|
+
} from "./chunk-5LIROIDM.js";
|
|
67
98
|
import {
|
|
68
99
|
USER_ENVELOPE_COLLECTION,
|
|
69
100
|
assertKeyringOpenAllowed,
|
|
@@ -88,7 +119,7 @@ import {
|
|
|
88
119
|
rotateKeys,
|
|
89
120
|
saveUserEnvelope,
|
|
90
121
|
updateKeyringIdentity
|
|
91
|
-
} from "./chunk-
|
|
122
|
+
} from "./chunk-BSZOCSDZ.js";
|
|
92
123
|
import {
|
|
93
124
|
INDEXED_STORE_POLICY
|
|
94
125
|
} from "./chunk-2QR2PQTT.js";
|
|
@@ -98,41 +129,31 @@ import {
|
|
|
98
129
|
import {
|
|
99
130
|
LEDGER_COLLECTION,
|
|
100
131
|
LEDGER_DELTAS_COLLECTION
|
|
101
|
-
} from "./chunk-
|
|
132
|
+
} from "./chunk-DWEBTE2W.js";
|
|
102
133
|
import {
|
|
103
134
|
sha256Hex as sha256Hex2
|
|
104
|
-
} from "./chunk-
|
|
105
|
-
import {
|
|
106
|
-
NO_AGGREGATE,
|
|
107
|
-
Query,
|
|
108
|
-
ScanBuilder,
|
|
109
|
-
canonicalizeIncomingMoney,
|
|
110
|
-
canonicalizeStoredMoney,
|
|
111
|
-
decodeMoneyFields,
|
|
112
|
-
quantizeMoneyFields,
|
|
113
|
-
validateMoneyFieldPaths
|
|
114
|
-
} from "./chunk-EYVQHAGH.js";
|
|
115
|
-
import {
|
|
116
|
-
canonicalGroupKey
|
|
117
|
-
} from "./chunk-3EWXMOK3.js";
|
|
118
|
-
import {
|
|
119
|
-
readPath
|
|
120
|
-
} from "./chunk-CJORTUJ2.js";
|
|
135
|
+
} from "./chunk-PDVP3C2I.js";
|
|
121
136
|
import {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
137
|
+
NO_FORGET,
|
|
138
|
+
addSubjectRef,
|
|
139
|
+
coerceSubjectId,
|
|
140
|
+
lookupSubject,
|
|
141
|
+
readDottedPath,
|
|
142
|
+
rebuildSubjectIndex,
|
|
143
|
+
removeSubjectRef
|
|
144
|
+
} from "./chunk-I5IUYN7B.js";
|
|
126
145
|
import {
|
|
127
146
|
NOYDB_BACKUP_VERSION,
|
|
128
147
|
NOYDB_FORMAT_VERSION
|
|
129
|
-
} from "./chunk-
|
|
148
|
+
} from "./chunk-SISBMAPO.js";
|
|
130
149
|
import {
|
|
131
150
|
decrypt,
|
|
132
151
|
encrypt,
|
|
133
152
|
encryptDeterministic,
|
|
134
|
-
sha256Hex
|
|
135
|
-
|
|
153
|
+
sha256Hex,
|
|
154
|
+
unwrapCek,
|
|
155
|
+
wrapCek
|
|
156
|
+
} from "./chunk-UNTGHX5A.js";
|
|
136
157
|
import {
|
|
137
158
|
AlreadyElevatedError,
|
|
138
159
|
AttestationError,
|
|
@@ -141,6 +162,7 @@ import {
|
|
|
141
162
|
ConflictError,
|
|
142
163
|
ElevationExpiredError,
|
|
143
164
|
ExportCapabilityError,
|
|
165
|
+
ForgetStrategyNotConfiguredError,
|
|
144
166
|
ImportCapabilityError,
|
|
145
167
|
IndexWriteFailureError,
|
|
146
168
|
InvalidKeyError,
|
|
@@ -159,15 +181,17 @@ import {
|
|
|
159
181
|
SchemaValidationError,
|
|
160
182
|
SequenceContentionError,
|
|
161
183
|
SequenceOfflineError,
|
|
184
|
+
StaticDictReadonlyError,
|
|
162
185
|
StoreCapabilityError,
|
|
163
186
|
TierDemoteDeniedError,
|
|
164
187
|
TierNotGrantedError,
|
|
165
188
|
TranslatorNotConfiguredError,
|
|
166
189
|
UniqueConstraintError,
|
|
190
|
+
UnknownDictCodeError,
|
|
167
191
|
UnsupportedIndexOptionError,
|
|
168
192
|
ValidationError,
|
|
169
193
|
VaultTemplateNotFoundError
|
|
170
|
-
} from "./chunk-
|
|
194
|
+
} from "./chunk-ZEGSDPB7.js";
|
|
171
195
|
|
|
172
196
|
// src/policy/storage.ts
|
|
173
197
|
var META_COLLECTION = "_meta";
|
|
@@ -450,6 +474,9 @@ var NO_HISTORY = {
|
|
|
450
474
|
async clearHistory() {
|
|
451
475
|
return 0;
|
|
452
476
|
},
|
|
477
|
+
async tombstoneHistory() {
|
|
478
|
+
return 0;
|
|
479
|
+
},
|
|
453
480
|
async envelopePayloadHash() {
|
|
454
481
|
return "";
|
|
455
482
|
},
|
|
@@ -810,7 +837,7 @@ async function resolveStaleOnRead(accessor, outputCollection, id) {
|
|
|
810
837
|
}
|
|
811
838
|
const sourceWithId = { ...source, id };
|
|
812
839
|
if (DerivationExecutor === null) {
|
|
813
|
-
({ DerivationExecutor } = await import("./executor-
|
|
840
|
+
({ DerivationExecutor } = await import("./executor-CFFWPWBJ.js"));
|
|
814
841
|
}
|
|
815
842
|
const ctx = { vault: accessor.getReadOnlyFacade() };
|
|
816
843
|
const result = await DerivationExecutor.run(spec, sourceWithId, 0, strategyHash, ctx);
|
|
@@ -1035,6 +1062,25 @@ var Collection = class {
|
|
|
1035
1062
|
* is inactive for this collection; a frozen `Set` otherwise.
|
|
1036
1063
|
*/
|
|
1037
1064
|
deterministicFields;
|
|
1065
|
+
/**
|
|
1066
|
+
* Per-record CEK opt-in (`perRecordKeys: true`). When set, writes mint /
|
|
1067
|
+
* reuse a per-record content-encryption key and stamp `_cek` on the
|
|
1068
|
+
* envelope (see {@link EncryptedEnvelope._cek}). OFF by default — a
|
|
1069
|
+
* non-adopting collection takes the byte-identical legacy path. The READ
|
|
1070
|
+
* path does not consult this flag: `_cek` presence on the envelope is the
|
|
1071
|
+
* format discriminant, so a mixed vault (and a recipient that never set the
|
|
1072
|
+
* flag) still decrypts CEK records.
|
|
1073
|
+
*/
|
|
1074
|
+
perRecordCek;
|
|
1075
|
+
/**
|
|
1076
|
+
* Session-scoped `(id) → CEK` cache for this collection. Lets updates
|
|
1077
|
+
* reuse a record's stable CEK and lets repeated reads skip the AES-KW
|
|
1078
|
+
* unwrap. Bounded by LRU; never persisted. Dropped when the owning
|
|
1079
|
+
* collection instance is discarded — `vault.load()` clears the
|
|
1080
|
+
* collectionCache, so a keyring refresh drops every CEK alongside the
|
|
1081
|
+
* DEK cache. `null` unless `perRecordCek` is set.
|
|
1082
|
+
*/
|
|
1083
|
+
cekCache;
|
|
1038
1084
|
/**
|
|
1039
1085
|
* declared tiers for this collection. `null` when
|
|
1040
1086
|
* tier-aware methods are disabled. Tier 0 is implicit and never
|
|
@@ -1181,19 +1227,24 @@ var Collection = class {
|
|
|
1181
1227
|
} else {
|
|
1182
1228
|
this.deterministicFields = null;
|
|
1183
1229
|
}
|
|
1230
|
+
this.perRecordCek = opts.perRecordKeys === true;
|
|
1231
|
+
this.cekCache = this.perRecordCek ? new Lru({ maxRecords: 4096 }) : null;
|
|
1184
1232
|
if (opts.crdt && opts.onRegisterConflictResolver) {
|
|
1185
1233
|
const crdtMode = opts.crdt;
|
|
1186
|
-
const crdtResolver = async (
|
|
1234
|
+
const crdtResolver = async (id, local, remote) => {
|
|
1187
1235
|
if (crdtMode === "yjs") {
|
|
1188
1236
|
return local._v >= remote._v ? local : remote;
|
|
1189
1237
|
}
|
|
1190
|
-
const localJson = await this.decryptJsonString(local);
|
|
1191
|
-
const remoteJson = await this.decryptJsonString(remote);
|
|
1238
|
+
const localJson = await this.decryptJsonString(local, id);
|
|
1239
|
+
const remoteJson = await this.decryptJsonString(remote, id);
|
|
1240
|
+
if (localJson === null) return local;
|
|
1241
|
+
if (remoteJson === null) return remote;
|
|
1192
1242
|
const localState = JSON.parse(localJson);
|
|
1193
1243
|
const remoteState = JSON.parse(remoteJson);
|
|
1194
1244
|
const merged = this.crdtStrategy.mergeCrdtStates(localState, remoteState);
|
|
1195
1245
|
const mergedVersion = Math.max(local._v, remote._v) + 1;
|
|
1196
|
-
|
|
1246
|
+
const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
|
|
1247
|
+
return this.encryptJsonString(JSON.stringify(merged), mergedVersion, cek);
|
|
1197
1248
|
};
|
|
1198
1249
|
opts.onRegisterConflictResolver(this.name, crdtResolver);
|
|
1199
1250
|
}
|
|
@@ -1233,12 +1284,15 @@ var Collection = class {
|
|
|
1233
1284
|
});
|
|
1234
1285
|
} else {
|
|
1235
1286
|
const mergeFn = policy;
|
|
1236
|
-
resolver = async (
|
|
1237
|
-
const localRecord = await this.decryptRecord(local, { skipValidation: true });
|
|
1238
|
-
const remoteRecord = await this.decryptRecord(remote, { skipValidation: true });
|
|
1287
|
+
resolver = async (id, local, remote) => {
|
|
1288
|
+
const localRecord = await this.decryptRecord(local, { skipValidation: true, id });
|
|
1289
|
+
const remoteRecord = await this.decryptRecord(remote, { skipValidation: true, id });
|
|
1290
|
+
if (localRecord === null) return local;
|
|
1291
|
+
if (remoteRecord === null) return remote;
|
|
1239
1292
|
const merged = mergeFn(localRecord, remoteRecord);
|
|
1240
1293
|
const mergedVersion = Math.max(local._v, remote._v) + 1;
|
|
1241
|
-
|
|
1294
|
+
const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
|
|
1295
|
+
return this.encryptRecord(merged, mergedVersion, cek);
|
|
1242
1296
|
};
|
|
1243
1297
|
}
|
|
1244
1298
|
opts.onRegisterConflictResolver(collectionName, resolver);
|
|
@@ -1319,7 +1373,7 @@ var Collection = class {
|
|
|
1319
1373
|
}
|
|
1320
1374
|
}
|
|
1321
1375
|
if (this.materializedViewSource !== void 0) {
|
|
1322
|
-
const { resolveStaleMVOnRead } = await import("./stale-
|
|
1376
|
+
const { resolveStaleMVOnRead } = await import("./stale-TOA36SRK.js");
|
|
1323
1377
|
await resolveStaleMVOnRead(this.materializedViewSource, this.name);
|
|
1324
1378
|
}
|
|
1325
1379
|
let record;
|
|
@@ -1330,7 +1384,9 @@ var Collection = class {
|
|
|
1330
1384
|
} else {
|
|
1331
1385
|
const envelope = await this.adapter.get(this.vault, this.name, id);
|
|
1332
1386
|
if (!envelope) return null;
|
|
1333
|
-
|
|
1387
|
+
if (isTombstone(envelope, this.encrypted)) return null;
|
|
1388
|
+
record = await this.decryptRecord(envelope, { id });
|
|
1389
|
+
if (record === null) return null;
|
|
1334
1390
|
this.lru.set(id, { record, version: envelope._v }, estimateRecordBytes(record));
|
|
1335
1391
|
}
|
|
1336
1392
|
} else {
|
|
@@ -1357,6 +1413,7 @@ var Collection = class {
|
|
|
1357
1413
|
const envelope = await this.adapter.get(this.vault, this.name, id);
|
|
1358
1414
|
if (!envelope) return null;
|
|
1359
1415
|
const json = await this.decryptJsonString(envelope);
|
|
1416
|
+
if (json === null) return null;
|
|
1360
1417
|
return JSON.parse(json);
|
|
1361
1418
|
}
|
|
1362
1419
|
/**
|
|
@@ -1445,7 +1502,7 @@ var Collection = class {
|
|
|
1445
1502
|
if (cached2) return { record: cached2.record, version: cached2.version };
|
|
1446
1503
|
const env = await this.adapter.get(this.vault, this.name, id);
|
|
1447
1504
|
if (!env) return { record: null, version: 0 };
|
|
1448
|
-
return { record: await this.decryptRecord(env, { skipValidation: true }), version: env._v };
|
|
1505
|
+
return { record: await this.decryptRecord(env, { skipValidation: true }) ?? null, version: env._v };
|
|
1449
1506
|
}
|
|
1450
1507
|
await this.ensureHydrated();
|
|
1451
1508
|
const cached = this.cache.get(id);
|
|
@@ -1558,9 +1615,11 @@ var Collection = class {
|
|
|
1558
1615
|
let existingState;
|
|
1559
1616
|
if (existingEnvelope) {
|
|
1560
1617
|
const prevJson = await this.decryptJsonString(existingEnvelope);
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1618
|
+
if (prevJson !== null) {
|
|
1619
|
+
const prevParsed = JSON.parse(prevJson);
|
|
1620
|
+
if (prevParsed !== null && typeof prevParsed === "object" && "_crdt" in prevParsed) {
|
|
1621
|
+
existingState = prevParsed;
|
|
1622
|
+
}
|
|
1564
1623
|
}
|
|
1565
1624
|
}
|
|
1566
1625
|
crdtState = this.crdtStrategy.buildLwwMapState(record, existingState, now);
|
|
@@ -1568,9 +1627,11 @@ var Collection = class {
|
|
|
1568
1627
|
let existingState;
|
|
1569
1628
|
if (existingEnvelope) {
|
|
1570
1629
|
const prevJson = await this.decryptJsonString(existingEnvelope);
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1630
|
+
if (prevJson !== null) {
|
|
1631
|
+
const prevParsed = JSON.parse(prevJson);
|
|
1632
|
+
if (prevParsed !== null && typeof prevParsed === "object" && "_crdt" in prevParsed) {
|
|
1633
|
+
existingState = prevParsed;
|
|
1634
|
+
}
|
|
1574
1635
|
}
|
|
1575
1636
|
}
|
|
1576
1637
|
const arr = Array.isArray(record) ? record : [record];
|
|
@@ -1579,12 +1640,14 @@ var Collection = class {
|
|
|
1579
1640
|
crdtState = { _crdt: "yjs", update: record };
|
|
1580
1641
|
}
|
|
1581
1642
|
const version2 = existingVersion + 1;
|
|
1582
|
-
const
|
|
1643
|
+
const cek2 = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
|
|
1644
|
+
const envelope2 = await this.encryptJsonString(JSON.stringify(crdtState), version2, cek2);
|
|
1583
1645
|
await this.adapter.put(this.vault, this.name, id, envelope2);
|
|
1584
1646
|
const resolvedRecord = this.crdtStrategy.resolveCrdtSnapshot(crdtState);
|
|
1585
|
-
const
|
|
1647
|
+
const existingResolvedRecord = existingEnvelope ? await this.decryptRecord(existingEnvelope, { skipValidation: true }) : null;
|
|
1648
|
+
const existingResolved = existingResolvedRecord !== null ? { record: existingResolvedRecord, version: existingVersion } : void 0;
|
|
1586
1649
|
if (existingResolved && this.historyConfig.enabled !== false) {
|
|
1587
|
-
const histEnvelope = await this.encryptRecord(existingResolved.record, existingResolved.version);
|
|
1650
|
+
const histEnvelope = await this.encryptRecord(existingResolved.record, existingResolved.version, cek2);
|
|
1588
1651
|
await this.historyStrategy.saveHistory(this.adapter, this.vault, this.name, id, histEnvelope);
|
|
1589
1652
|
this.emitter.emit("history:save", { vault: this.vault, collection: this.name, id, version: existingResolved.version });
|
|
1590
1653
|
if (this.historyConfig.maxVersions) {
|
|
@@ -1630,7 +1693,9 @@ var Collection = class {
|
|
|
1630
1693
|
const previousEnvelope = await this.adapter.get(this.vault, this.name, id);
|
|
1631
1694
|
if (previousEnvelope) {
|
|
1632
1695
|
const previousRecord = await this.decryptRecord(previousEnvelope);
|
|
1633
|
-
|
|
1696
|
+
if (previousRecord !== null) {
|
|
1697
|
+
existing = { record: previousRecord, version: previousEnvelope._v };
|
|
1698
|
+
}
|
|
1634
1699
|
}
|
|
1635
1700
|
}
|
|
1636
1701
|
} else {
|
|
@@ -1639,8 +1704,9 @@ var Collection = class {
|
|
|
1639
1704
|
}
|
|
1640
1705
|
const version = existing ? existing.version + 1 : 1;
|
|
1641
1706
|
this.uniqueConstraints?.check(id, record);
|
|
1707
|
+
const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
|
|
1642
1708
|
if (existing && this.historyConfig.enabled !== false) {
|
|
1643
|
-
const historyEnvelope = await this.encryptRecord(existing.record, existing.version);
|
|
1709
|
+
const historyEnvelope = await this.encryptRecord(existing.record, existing.version, cek);
|
|
1644
1710
|
await this.historyStrategy.saveHistory(this.adapter, this.vault, this.name, id, historyEnvelope);
|
|
1645
1711
|
this.emitter.emit("history:save", {
|
|
1646
1712
|
vault: this.vault,
|
|
@@ -1654,7 +1720,7 @@ var Collection = class {
|
|
|
1654
1720
|
});
|
|
1655
1721
|
}
|
|
1656
1722
|
}
|
|
1657
|
-
const envelope = await this.encryptRecord(record, version);
|
|
1723
|
+
const envelope = await this.encryptRecord(record, version, cek);
|
|
1658
1724
|
await this.adapter.put(this.vault, this.name, id, envelope);
|
|
1659
1725
|
if (this.ledger) {
|
|
1660
1726
|
const appendInput = {
|
|
@@ -1717,7 +1783,7 @@ var Collection = class {
|
|
|
1717
1783
|
if (mode === "eager") {
|
|
1718
1784
|
if (executor === null) {
|
|
1719
1785
|
;
|
|
1720
|
-
({ MaterializedViewExecutor: executor } = await import("./executor-
|
|
1786
|
+
({ MaterializedViewExecutor: executor } = await import("./executor-3W63Y44O.js"));
|
|
1721
1787
|
}
|
|
1722
1788
|
await executor.refresh(reg, {
|
|
1723
1789
|
getCollection: (name) => this.materializedViewSource.getCollection(name),
|
|
@@ -1726,7 +1792,7 @@ var Collection = class {
|
|
|
1726
1792
|
});
|
|
1727
1793
|
} else if (mode === "lazy") {
|
|
1728
1794
|
if (staleHelpers === null) {
|
|
1729
|
-
staleHelpers = await import("./stale-
|
|
1795
|
+
staleHelpers = await import("./stale-TOA36SRK.js");
|
|
1730
1796
|
}
|
|
1731
1797
|
staleHelpers.markMVStale(registry, reg.spec.name);
|
|
1732
1798
|
}
|
|
@@ -1755,11 +1821,20 @@ var Collection = class {
|
|
|
1755
1821
|
const mode = typeof spec.lifecycle === "string" ? spec.lifecycle : spec.lifecycle.mode;
|
|
1756
1822
|
if (mode === "eager") {
|
|
1757
1823
|
if (DerivationExecutor === null) {
|
|
1758
|
-
({ DerivationExecutor } = await import("./executor-
|
|
1824
|
+
({ DerivationExecutor } = await import("./executor-CFFWPWBJ.js"));
|
|
1825
|
+
}
|
|
1826
|
+
let sourceWithId;
|
|
1827
|
+
let sourceVersion = version;
|
|
1828
|
+
if (spec.source === this.name) {
|
|
1829
|
+
sourceWithId = { ...incoming, id };
|
|
1830
|
+
} else {
|
|
1831
|
+
const primary = await this.derivationSource.getCollection(spec.source).get(id);
|
|
1832
|
+
if (primary === null || primary === void 0) continue;
|
|
1833
|
+
sourceWithId = { ...primary, id };
|
|
1834
|
+
sourceVersion = 0;
|
|
1759
1835
|
}
|
|
1760
|
-
const sourceWithId = { ...incoming, id };
|
|
1761
1836
|
const ctx = { vault: this.derivationSource.getReadOnlyFacade() };
|
|
1762
|
-
const result = await DerivationExecutor.run(spec, sourceWithId,
|
|
1837
|
+
const result = await DerivationExecutor.run(spec, sourceWithId, sourceVersion, strategyHash, ctx);
|
|
1763
1838
|
for (const key of Object.keys(spec.outputs)) {
|
|
1764
1839
|
const out = result.outputs[key];
|
|
1765
1840
|
if (!out) continue;
|
|
@@ -1774,7 +1849,7 @@ var Collection = class {
|
|
|
1774
1849
|
const outputCollection = this.derivationSource.getCollection(outSpec.collection);
|
|
1775
1850
|
const txCtx = this.derivationSource.getActiveTxContext();
|
|
1776
1851
|
if (out.kind === "array") {
|
|
1777
|
-
const { loadFanoutSidecar, saveFanoutSidecar } = await import("./fanout-sidecar-
|
|
1852
|
+
const { loadFanoutSidecar, saveFanoutSidecar } = await import("./fanout-sidecar-FIJJ46YG.js");
|
|
1778
1853
|
const prior = await loadFanoutSidecar(
|
|
1779
1854
|
this.adapter,
|
|
1780
1855
|
this.vault,
|
|
@@ -1881,11 +1956,14 @@ var Collection = class {
|
|
|
1881
1956
|
let count = 0;
|
|
1882
1957
|
for (const id of ids) {
|
|
1883
1958
|
const env = await this.adapter.get(this.vault, this.name, id);
|
|
1884
|
-
if (!env) continue;
|
|
1885
|
-
const
|
|
1959
|
+
if (!env || isTombstone(env, this.encrypted)) continue;
|
|
1960
|
+
const decoded = await this.decryptRecord(env, { skipValidation: true, id });
|
|
1961
|
+
if (decoded === null) continue;
|
|
1962
|
+
const record = decoded;
|
|
1886
1963
|
const next = transform(record);
|
|
1887
1964
|
const nextVersion = (env._v ?? 0) + 1;
|
|
1888
|
-
const
|
|
1965
|
+
const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
|
|
1966
|
+
const newEnv = await this.encryptRecord(next, nextVersion, cek);
|
|
1889
1967
|
await this.adapter.put(this.vault, this.name, id, newEnv);
|
|
1890
1968
|
await this._invalidateCacheEntry(id);
|
|
1891
1969
|
if (this.ledger) {
|
|
@@ -1997,14 +2075,17 @@ var Collection = class {
|
|
|
1997
2075
|
const previousEnvelope2 = await this.adapter.get(this.vault, this.name, id);
|
|
1998
2076
|
if (previousEnvelope2) {
|
|
1999
2077
|
const previousRecord = await this.decryptRecord(previousEnvelope2);
|
|
2000
|
-
|
|
2078
|
+
if (previousRecord !== null) {
|
|
2079
|
+
existing = { record: previousRecord, version: previousEnvelope2._v };
|
|
2080
|
+
}
|
|
2001
2081
|
}
|
|
2002
2082
|
}
|
|
2003
2083
|
} else {
|
|
2004
2084
|
existing = this.cache.get(id);
|
|
2005
2085
|
}
|
|
2006
2086
|
if (existing && this.historyConfig.enabled !== false) {
|
|
2007
|
-
const
|
|
2087
|
+
const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
|
|
2088
|
+
const historyEnvelope = await this.encryptRecord(existing.record, existing.version, cek);
|
|
2008
2089
|
await this.historyStrategy.saveHistory(this.adapter, this.vault, this.name, id, historyEnvelope);
|
|
2009
2090
|
}
|
|
2010
2091
|
const previousEnvelope = await this.adapter.get(this.vault, this.name, id);
|
|
@@ -2045,6 +2126,50 @@ var Collection = class {
|
|
|
2045
2126
|
await this.dispatchArrayDerivationsOnDelete(id);
|
|
2046
2127
|
}
|
|
2047
2128
|
}
|
|
2129
|
+
/**
|
|
2130
|
+
* @internal — GDPR crypto-shred a LIVE record to a tombstone (#304).
|
|
2131
|
+
*
|
|
2132
|
+
* Rewrites the on-disk envelope to `{ _noydb, _v, _ts, _by, _iv:'', _data:'' }`,
|
|
2133
|
+
* dropping `_iv`/`_data`/`_cek`/`_det`. The wrapped per-record CEK is gone, so
|
|
2134
|
+
* the body — and (via {@link tombstoneHistory}) every history version under
|
|
2135
|
+
* the same CEK — is permanently undecryptable; the collection DEK and every
|
|
2136
|
+
* other record are untouched. `_det` is stripped too, so `findByDet` no
|
|
2137
|
+
* longer matches the shredded record (avoiding a post-shred TamperedError).
|
|
2138
|
+
*
|
|
2139
|
+
* Unlike `delete()`/`_internalDelete`, this:
|
|
2140
|
+
* - does NOT fire onDelete guards / MV / derivation dispatch (a shred is an
|
|
2141
|
+
* erasure, not a domain delete — re-running those would be wrong),
|
|
2142
|
+
* - does NOT append a per-record ledger entry (`vault.forget()` appends a
|
|
2143
|
+
* single `op:'forget'` summary for the whole subject),
|
|
2144
|
+
* - keeps the record KEY present (it's an overwrite, not an adapter delete)
|
|
2145
|
+
* so the version counter + "record existed" survive for audit.
|
|
2146
|
+
*
|
|
2147
|
+
* Idempotent: returns `null` when the record is absent or already a tombstone.
|
|
2148
|
+
* Otherwise returns `{ previousVersion }`. Invalidates the eager cache, the
|
|
2149
|
+
* lazy LRU, and the per-record CEK cache for this id.
|
|
2150
|
+
*/
|
|
2151
|
+
/**
|
|
2152
|
+
* @internal — decrypt an envelope to a plain record for subject-index
|
|
2153
|
+
* rebuild (#304). Returns `null` for a tombstone or unreadable envelope.
|
|
2154
|
+
* Skips schema validation — the rebuild only reads the subject field.
|
|
2155
|
+
*/
|
|
2156
|
+
async _decodeEnvelope(envelope, id) {
|
|
2157
|
+
try {
|
|
2158
|
+
const rec = await this.decryptRecord(envelope, { skipValidation: true, id });
|
|
2159
|
+
return rec === null ? null : rec;
|
|
2160
|
+
} catch {
|
|
2161
|
+
return null;
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
async _writeTombstone(id, actor) {
|
|
2165
|
+
const live = await this.adapter.get(this.vault, this.name, id);
|
|
2166
|
+
if (!live || isTombstone(live, this.encrypted)) return null;
|
|
2167
|
+
await this.adapter.put(this.vault, this.name, id, buildTombstone(live._v, actor));
|
|
2168
|
+
this.cache.delete(id);
|
|
2169
|
+
this.lru?.remove(id);
|
|
2170
|
+
this.cekCache?.remove(id);
|
|
2171
|
+
return { previousVersion: live._v };
|
|
2172
|
+
}
|
|
2048
2173
|
/**
|
|
2049
2174
|
* Cascade deletes of array-shape derived rows when a source row is
|
|
2050
2175
|
* deleted. Reads each registered strategy's fanout sidecar
|
|
@@ -2067,7 +2192,7 @@ var Collection = class {
|
|
|
2067
2192
|
for (const [outputKey, outSpec] of Object.entries(spec.outputs)) {
|
|
2068
2193
|
if (outSpec.shape !== "array") continue;
|
|
2069
2194
|
if (helpers === null) {
|
|
2070
|
-
helpers = await import("./fanout-sidecar-
|
|
2195
|
+
helpers = await import("./fanout-sidecar-FIJJ46YG.js");
|
|
2071
2196
|
}
|
|
2072
2197
|
const sidecar = await helpers.loadFanoutSidecar(
|
|
2073
2198
|
this.adapter,
|
|
@@ -2107,7 +2232,7 @@ var Collection = class {
|
|
|
2107
2232
|
if (mode === "eager") {
|
|
2108
2233
|
if (executor === null) {
|
|
2109
2234
|
;
|
|
2110
|
-
({ MaterializedViewExecutor: executor } = await import("./executor-
|
|
2235
|
+
({ MaterializedViewExecutor: executor } = await import("./executor-3W63Y44O.js"));
|
|
2111
2236
|
}
|
|
2112
2237
|
await executor.refresh(reg, {
|
|
2113
2238
|
getCollection: (name) => this.materializedViewSource.getCollection(name),
|
|
@@ -2116,7 +2241,7 @@ var Collection = class {
|
|
|
2116
2241
|
});
|
|
2117
2242
|
} else if (mode === "lazy") {
|
|
2118
2243
|
if (staleHelpers === null) {
|
|
2119
|
-
staleHelpers = await import("./stale-
|
|
2244
|
+
staleHelpers = await import("./stale-TOA36SRK.js");
|
|
2120
2245
|
}
|
|
2121
2246
|
staleHelpers.markMVStale(registry, reg.spec.name);
|
|
2122
2247
|
}
|
|
@@ -2139,7 +2264,7 @@ var Collection = class {
|
|
|
2139
2264
|
);
|
|
2140
2265
|
}
|
|
2141
2266
|
if (this.materializedViewSource !== void 0) {
|
|
2142
|
-
const { resolveStaleMVOnRead } = await import("./stale-
|
|
2267
|
+
const { resolveStaleMVOnRead } = await import("./stale-TOA36SRK.js");
|
|
2143
2268
|
await resolveStaleMVOnRead(this.materializedViewSource, this.name);
|
|
2144
2269
|
}
|
|
2145
2270
|
await this.ensureHydrated();
|
|
@@ -2457,6 +2582,7 @@ var Collection = class {
|
|
|
2457
2582
|
const entries = [];
|
|
2458
2583
|
for (const env of envelopes) {
|
|
2459
2584
|
const record = await this.decryptRecord(env, { skipValidation: true });
|
|
2585
|
+
if (record === null) continue;
|
|
2460
2586
|
entries.push({
|
|
2461
2587
|
version: env._v,
|
|
2462
2588
|
timestamp: env._ts,
|
|
@@ -2593,6 +2719,7 @@ var Collection = class {
|
|
|
2593
2719
|
const envelope = await this.adapter.get(this.vault, this.name, id);
|
|
2594
2720
|
if (envelope) {
|
|
2595
2721
|
const record = await this.decryptRecord(envelope);
|
|
2722
|
+
if (record === null) continue;
|
|
2596
2723
|
items.push(record);
|
|
2597
2724
|
if (!this.lazy && !this.cache.has(id)) {
|
|
2598
2725
|
this.cache.set(id, { record, version: envelope._v });
|
|
@@ -2669,6 +2796,7 @@ var Collection = class {
|
|
|
2669
2796
|
const out = [];
|
|
2670
2797
|
for (const { id, envelope } of items) {
|
|
2671
2798
|
const record = await this.decryptRecord(envelope);
|
|
2799
|
+
if (record === null) continue;
|
|
2672
2800
|
out.push({ id, record, version: envelope._v });
|
|
2673
2801
|
}
|
|
2674
2802
|
return out;
|
|
@@ -2690,6 +2818,18 @@ var Collection = class {
|
|
|
2690
2818
|
* the cache entry (record still present) or deletes it (record was
|
|
2691
2819
|
* gone before the tx and the revert deleted it again).
|
|
2692
2820
|
*/
|
|
2821
|
+
/**
|
|
2822
|
+
* @internal — evict ONLY the per-record CEK cache entry for `id`. Used by
|
|
2823
|
+
* `vault.rotateRecordCek()`: after a hard CEK rotation the cached unwrapped
|
|
2824
|
+
* CEK is stale (it would decrypt the pre-rotation body and fail GCM auth on
|
|
2825
|
+
* the post-rotation body). Eviction must be synchronous with the live-envelope
|
|
2826
|
+
* rewrite so no concurrent read observes the old CEK. Paired with
|
|
2827
|
+
* {@link _invalidateCacheEntry} (which refreshes the decrypted-record cache).
|
|
2828
|
+
* No-op when the collection is not `perRecordKeys`.
|
|
2829
|
+
*/
|
|
2830
|
+
_invalidateCekCacheEntry(id) {
|
|
2831
|
+
this.cekCache?.remove(id);
|
|
2832
|
+
}
|
|
2693
2833
|
async _invalidateCacheEntry(id) {
|
|
2694
2834
|
if (this.lazy && this.lru) {
|
|
2695
2835
|
this.lru.remove(id);
|
|
@@ -2707,6 +2847,14 @@ var Collection = class {
|
|
|
2707
2847
|
return;
|
|
2708
2848
|
}
|
|
2709
2849
|
const record = await this.decryptRecord(envelope);
|
|
2850
|
+
if (record === null) {
|
|
2851
|
+
this.cache.delete(id);
|
|
2852
|
+
if (previous) {
|
|
2853
|
+
this.indexes?.remove(id, previous.record);
|
|
2854
|
+
this.uniqueConstraints?.remove(id, previous.record);
|
|
2855
|
+
}
|
|
2856
|
+
return;
|
|
2857
|
+
}
|
|
2710
2858
|
this.cache.set(id, { record, version: envelope._v });
|
|
2711
2859
|
this.indexes?.upsert(id, record, previous ? previous.record : null);
|
|
2712
2860
|
this.uniqueConstraints?.upsert(id, record, previous?.record);
|
|
@@ -2731,8 +2879,9 @@ var Collection = class {
|
|
|
2731
2879
|
const ids = await this.adapter.list(this.vault, this.name);
|
|
2732
2880
|
for (const id of ids) {
|
|
2733
2881
|
const envelope = await this.adapter.get(this.vault, this.name, id);
|
|
2734
|
-
if (envelope) {
|
|
2735
|
-
const record = await this.decryptRecord(envelope);
|
|
2882
|
+
if (envelope && !isTombstone(envelope, this.encrypted)) {
|
|
2883
|
+
const record = await this.decryptRecord(envelope, { id });
|
|
2884
|
+
if (record === null) continue;
|
|
2736
2885
|
this.cache.set(id, { record, version: envelope._v });
|
|
2737
2886
|
}
|
|
2738
2887
|
}
|
|
@@ -2743,7 +2892,9 @@ var Collection = class {
|
|
|
2743
2892
|
/** Hydrate from a pre-loaded snapshot (used by Vault). */
|
|
2744
2893
|
async hydrateFromSnapshot(records) {
|
|
2745
2894
|
for (const [id, envelope] of Object.entries(records)) {
|
|
2746
|
-
|
|
2895
|
+
if (isTombstone(envelope, this.encrypted)) continue;
|
|
2896
|
+
const record = await this.decryptRecord(envelope, { id });
|
|
2897
|
+
if (record === null) continue;
|
|
2747
2898
|
this.cache.set(id, { record, version: envelope._v });
|
|
2748
2899
|
}
|
|
2749
2900
|
this.hydrated = true;
|
|
@@ -2831,6 +2982,7 @@ var Collection = class {
|
|
|
2831
2982
|
const envelope = await this.adapter.get(this.vault, this.name, recordId);
|
|
2832
2983
|
if (!envelope) continue;
|
|
2833
2984
|
const record = await this.decryptRecord(envelope, { skipValidation: true });
|
|
2985
|
+
if (record === null) continue;
|
|
2834
2986
|
await this.maintainPersistedIndexesOnPut(recordId, record, null, envelope._v);
|
|
2835
2987
|
}
|
|
2836
2988
|
this.persistedIndexesLoaded = true;
|
|
@@ -2881,8 +3033,13 @@ var Collection = class {
|
|
|
2881
3033
|
const env = await this.adapter.get(this.vault, this.name, id);
|
|
2882
3034
|
if (!env) continue;
|
|
2883
3035
|
try {
|
|
2884
|
-
const
|
|
2885
|
-
|
|
3036
|
+
const sidecarJson = await this.decryptJsonString(env);
|
|
3037
|
+
if (sidecarJson === null) {
|
|
3038
|
+
sidecar.set(decoded.recordId, void 0);
|
|
3039
|
+
} else {
|
|
3040
|
+
const body = JSON.parse(sidecarJson);
|
|
3041
|
+
sidecar.set(decoded.recordId, body.value);
|
|
3042
|
+
}
|
|
2886
3043
|
} catch {
|
|
2887
3044
|
sidecar.set(decoded.recordId, void 0);
|
|
2888
3045
|
}
|
|
@@ -2896,6 +3053,7 @@ var Collection = class {
|
|
|
2896
3053
|
const env = await this.adapter.get(this.vault, this.name, id);
|
|
2897
3054
|
if (!env) continue;
|
|
2898
3055
|
const record = await this.decryptRecord(env, { skipValidation: true });
|
|
3056
|
+
if (record === null) continue;
|
|
2899
3057
|
const live = readPersistedValue(record, field);
|
|
2900
3058
|
const stored = sidecar.get(id);
|
|
2901
3059
|
const hasSidecar = sidecarIds.has(id);
|
|
@@ -2978,7 +3136,8 @@ var Collection = class {
|
|
|
2978
3136
|
recordId: id,
|
|
2979
3137
|
getDEK: this.getDEK,
|
|
2980
3138
|
encrypted: this.encrypted,
|
|
2981
|
-
userId: this.keyring.userId
|
|
3139
|
+
userId: this.keyring.userId,
|
|
3140
|
+
erasableBlobs: this.perRecordCek
|
|
2982
3141
|
});
|
|
2983
3142
|
}
|
|
2984
3143
|
/** Get all records as encrypted envelopes (for dump). */
|
|
@@ -2986,7 +3145,8 @@ var Collection = class {
|
|
|
2986
3145
|
await this.ensureHydrated();
|
|
2987
3146
|
const result = {};
|
|
2988
3147
|
for (const [id, entry] of this.cache) {
|
|
2989
|
-
|
|
3148
|
+
const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
|
|
3149
|
+
result[id] = await this.encryptRecord(entry.record, entry.version, cek);
|
|
2990
3150
|
}
|
|
2991
3151
|
return result;
|
|
2992
3152
|
}
|
|
@@ -3014,8 +3174,11 @@ var Collection = class {
|
|
|
3014
3174
|
if (hasMoney && this.moneyFields) {
|
|
3015
3175
|
result = decodeMoneyFields(result, this.moneyFields, typeof locale === "string" ? locale : void 0);
|
|
3016
3176
|
}
|
|
3017
|
-
|
|
3018
|
-
|
|
3177
|
+
const hasStaticDisplay = hasDict && this.dictKeyFields !== void 0 && Object.values(this.dictKeyFields).some(
|
|
3178
|
+
(d) => isStaticDictDescriptor(d) && d.displayLocale !== void 0
|
|
3179
|
+
);
|
|
3180
|
+
if (!locale && !hasStaticDisplay) return result;
|
|
3181
|
+
if (locale && hasI18n && this.i18nFields) {
|
|
3019
3182
|
result = this.i18nStrategy.applyI18nLocale(result, this.i18nFields, locale, localeOpts?.fallback);
|
|
3020
3183
|
}
|
|
3021
3184
|
if (hasDict && this.dictKeyFields && this.dictLabelResolver && locale !== "raw") {
|
|
@@ -3024,13 +3187,23 @@ var Collection = class {
|
|
|
3024
3187
|
for (const [field, desc] of Object.entries(this.dictKeyFields)) {
|
|
3025
3188
|
const policy = desc.onMissing ? resolvePolicy(desc.onMissing, "read") : "null";
|
|
3026
3189
|
const fallback = policy === "substitute" ? localeOpts?.fallback ?? desc.substitute : localeOpts?.fallback;
|
|
3190
|
+
const effLocale = locale ?? (isStaticDictDescriptor(desc) ? desc.displayLocale : void 0);
|
|
3027
3191
|
const resolveKey = async (key) => {
|
|
3028
|
-
|
|
3192
|
+
if (!effLocale) {
|
|
3193
|
+
if (policy === "throw") {
|
|
3194
|
+
throw new LocaleNotSpecifiedError(
|
|
3195
|
+
field,
|
|
3196
|
+
`dictKey "${field}": no locale active to resolve key "${key}".`
|
|
3197
|
+
);
|
|
3198
|
+
}
|
|
3199
|
+
return null;
|
|
3200
|
+
}
|
|
3201
|
+
const label = await resolver(desc.name, key, effLocale, fallback);
|
|
3029
3202
|
if (label === void 0) {
|
|
3030
3203
|
if (policy === "throw") {
|
|
3031
3204
|
throw new LocaleNotSpecifiedError(
|
|
3032
3205
|
field,
|
|
3033
|
-
`dictKey "${field}": no label for key "${key}" in locale "${
|
|
3206
|
+
`dictKey "${field}": no label for key "${key}" in locale "${effLocale}".`
|
|
3034
3207
|
);
|
|
3035
3208
|
}
|
|
3036
3209
|
return null;
|
|
@@ -3187,6 +3360,7 @@ var Collection = class {
|
|
|
3187
3360
|
if (!envelope) continue;
|
|
3188
3361
|
try {
|
|
3189
3362
|
const json = await this.decryptJsonString(envelope);
|
|
3363
|
+
if (json === null) continue;
|
|
3190
3364
|
const body = JSON.parse(json);
|
|
3191
3365
|
if (typeof body.recordId !== "string") continue;
|
|
3192
3366
|
const rows = byField.get(decoded.field) ?? [];
|
|
@@ -3296,7 +3470,31 @@ var Collection = class {
|
|
|
3296
3470
|
};
|
|
3297
3471
|
return new LazyQuery(source);
|
|
3298
3472
|
}
|
|
3299
|
-
|
|
3473
|
+
/**
|
|
3474
|
+
* Resolve the stable CEK for a record on the WRITE path — see
|
|
3475
|
+
* {@link resolveStableCek}. Thin delegate that supplies the collection's
|
|
3476
|
+
* CEK cache, live-envelope reader, and DEK resolver.
|
|
3477
|
+
*/
|
|
3478
|
+
resolveRecordCek(id) {
|
|
3479
|
+
return resolveStableCek(
|
|
3480
|
+
{
|
|
3481
|
+
cache: this.cekCache,
|
|
3482
|
+
getLive: (rid) => this.adapter.get(this.vault, this.name, rid),
|
|
3483
|
+
getDEK: () => this.getDEK(this.name)
|
|
3484
|
+
},
|
|
3485
|
+
id
|
|
3486
|
+
);
|
|
3487
|
+
}
|
|
3488
|
+
/**
|
|
3489
|
+
* Encrypt a JSON body into an envelope.
|
|
3490
|
+
*
|
|
3491
|
+
* When `cek` is supplied (per-record CEK collections), the body is
|
|
3492
|
+
* encrypted under the CEK and the CEK is AES-KW-wrapped under the
|
|
3493
|
+
* collection DEK and stamped on `_cek`. When `cek` is omitted, the legacy
|
|
3494
|
+
* path encrypts the body directly under the collection DEK — byte-identical
|
|
3495
|
+
* to pre-CEK behaviour, so non-adopting collections pay nothing.
|
|
3496
|
+
*/
|
|
3497
|
+
async encryptJsonString(json, version, cek) {
|
|
3300
3498
|
const by = this.keyring.userId;
|
|
3301
3499
|
if (!this.encrypted) {
|
|
3302
3500
|
return {
|
|
@@ -3309,6 +3507,19 @@ var Collection = class {
|
|
|
3309
3507
|
};
|
|
3310
3508
|
}
|
|
3311
3509
|
const dek = await this.getDEK(this.name);
|
|
3510
|
+
if (cek !== void 0) {
|
|
3511
|
+
const { iv: iv2, data: data2 } = await encrypt(json, cek);
|
|
3512
|
+
const wrapped = await wrapCek(cek, dek);
|
|
3513
|
+
return {
|
|
3514
|
+
_noydb: NOYDB_FORMAT_VERSION,
|
|
3515
|
+
_v: version,
|
|
3516
|
+
_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3517
|
+
_iv: iv2,
|
|
3518
|
+
_data: data2,
|
|
3519
|
+
_by: by,
|
|
3520
|
+
_cek: wrapped
|
|
3521
|
+
};
|
|
3522
|
+
}
|
|
3312
3523
|
const { iv, data } = await encrypt(json, dek);
|
|
3313
3524
|
return {
|
|
3314
3525
|
_noydb: NOYDB_FORMAT_VERSION,
|
|
@@ -3319,8 +3530,8 @@ var Collection = class {
|
|
|
3319
3530
|
_by: by
|
|
3320
3531
|
};
|
|
3321
3532
|
}
|
|
3322
|
-
async encryptRecord(record, version) {
|
|
3323
|
-
const base = await this.encryptJsonString(JSON.stringify(record), version);
|
|
3533
|
+
async encryptRecord(record, version, cek) {
|
|
3534
|
+
const base = await this.encryptJsonString(JSON.stringify(record), version, cek);
|
|
3324
3535
|
if (!this.deterministicFields || !this.encrypted) return base;
|
|
3325
3536
|
const dek = await this.getDEK(this.name);
|
|
3326
3537
|
const rec = record;
|
|
@@ -3398,7 +3609,8 @@ var Collection = class {
|
|
|
3398
3609
|
const env = await this.adapter.get(this.vault, this.name, id);
|
|
3399
3610
|
if (!env || !env._det) continue;
|
|
3400
3611
|
if (env._det[field] === target) {
|
|
3401
|
-
|
|
3612
|
+
const rec = await this.decryptRecord(env);
|
|
3613
|
+
if (rec !== null) matches.push(rec);
|
|
3402
3614
|
}
|
|
3403
3615
|
}
|
|
3404
3616
|
return matches;
|
|
@@ -3499,7 +3711,14 @@ var Collection = class {
|
|
|
3499
3711
|
return null;
|
|
3500
3712
|
}
|
|
3501
3713
|
const dek = await this.getDEK(key);
|
|
3502
|
-
|
|
3714
|
+
let plaintext;
|
|
3715
|
+
if (envelope._cek !== void 0) {
|
|
3716
|
+
const cek = await unwrapCek(envelope._cek, dek);
|
|
3717
|
+
this.cekCache?.set(id, cek, 1);
|
|
3718
|
+
plaintext = await decrypt(envelope._iv, envelope._data, cek);
|
|
3719
|
+
} else {
|
|
3720
|
+
plaintext = await decrypt(envelope._iv, envelope._data, dek);
|
|
3721
|
+
}
|
|
3503
3722
|
const record = JSON.parse(plaintext);
|
|
3504
3723
|
this.emitCrossTierEvent({
|
|
3505
3724
|
actor: this.keyring.userId,
|
|
@@ -3555,18 +3774,19 @@ var Collection = class {
|
|
|
3555
3774
|
const toKey = dekKey(this.name, toTier);
|
|
3556
3775
|
const fromDek = await this.getDEK(fromKey);
|
|
3557
3776
|
const toDek = await this.getDEK(toKey);
|
|
3558
|
-
const plaintext = await decrypt(envelope._iv, envelope._data, fromDek);
|
|
3559
|
-
const { iv, data } = await encrypt(plaintext, toDek);
|
|
3560
3777
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3778
|
+
const body = await rewrapBodyToDek(envelope, fromDek, toDek);
|
|
3779
|
+
if (body.cek) this.cekCache?.set(id, body.cek, 1);
|
|
3561
3780
|
const next = {
|
|
3562
3781
|
_noydb: NOYDB_FORMAT_VERSION,
|
|
3563
3782
|
_v: envelope._v + 1,
|
|
3564
3783
|
_ts: now,
|
|
3565
|
-
_iv:
|
|
3566
|
-
_data:
|
|
3784
|
+
_iv: body._iv,
|
|
3785
|
+
_data: body._data,
|
|
3567
3786
|
_by: this.keyring.userId,
|
|
3568
3787
|
_tier: toTier,
|
|
3569
|
-
_elevatedBy: this.keyring.userId
|
|
3788
|
+
_elevatedBy: this.keyring.userId,
|
|
3789
|
+
...body._cek !== void 0 ? { _cek: body._cek } : {}
|
|
3570
3790
|
};
|
|
3571
3791
|
await this.adapter.put(this.vault, this.name, id, next);
|
|
3572
3792
|
this.emitCrossTierEvent({
|
|
@@ -3602,17 +3822,18 @@ var Collection = class {
|
|
|
3602
3822
|
if (toTier > 0) this.assertDeclaredTier(toTier);
|
|
3603
3823
|
const fromDek = await this.getDEK(dekKey(this.name, fromTier));
|
|
3604
3824
|
const toDek = await this.getDEK(dekKey(this.name, toTier));
|
|
3605
|
-
const plaintext = await decrypt(envelope._iv, envelope._data, fromDek);
|
|
3606
|
-
const { iv, data } = await encrypt(plaintext, toDek);
|
|
3607
3825
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3826
|
+
const body = await rewrapBodyToDek(envelope, fromDek, toDek);
|
|
3827
|
+
if (body.cek) this.cekCache?.set(id, body.cek, 1);
|
|
3608
3828
|
const next = {
|
|
3609
3829
|
_noydb: NOYDB_FORMAT_VERSION,
|
|
3610
3830
|
_v: envelope._v + 1,
|
|
3611
3831
|
_ts: now,
|
|
3612
|
-
_iv:
|
|
3613
|
-
_data:
|
|
3832
|
+
_iv: body._iv,
|
|
3833
|
+
_data: body._data,
|
|
3614
3834
|
_by: this.keyring.userId,
|
|
3615
|
-
...toTier > 0 && { _tier: toTier }
|
|
3835
|
+
...toTier > 0 && { _tier: toTier },
|
|
3836
|
+
...body._cek !== void 0 ? { _cek: body._cek } : {}
|
|
3616
3837
|
};
|
|
3617
3838
|
await this.adapter.put(this.vault, this.name, id, next);
|
|
3618
3839
|
this.emitCrossTierEvent({
|
|
@@ -3634,10 +3855,30 @@ var Collection = class {
|
|
|
3634
3855
|
} catch {
|
|
3635
3856
|
}
|
|
3636
3857
|
}
|
|
3637
|
-
/**
|
|
3638
|
-
|
|
3858
|
+
/**
|
|
3859
|
+
* Low-level: decrypt an envelope and return the raw JSON string.
|
|
3860
|
+
*
|
|
3861
|
+
* `_cek` presence is the format discriminant (NOT `this.perRecordCek`),
|
|
3862
|
+
* so a mixed vault — and a recipient that never opted into
|
|
3863
|
+
* `perRecordKeys` — decrypts both legacy and CEK records:
|
|
3864
|
+
* - `_cek` present → unwrap the CEK under the collection DEK, decrypt the
|
|
3865
|
+
* body under the CEK (cache the unwrapped CEK so repeated reads skip it).
|
|
3866
|
+
* - `_cek` absent → legacy path, body decrypts directly under the
|
|
3867
|
+
* collection DEK.
|
|
3868
|
+
*
|
|
3869
|
+
* The optional `id` lets reads populate the CEK cache; it is omitted by
|
|
3870
|
+
* callers (history, conflict merge) that have only the envelope.
|
|
3871
|
+
*/
|
|
3872
|
+
async decryptJsonString(envelope, id) {
|
|
3873
|
+
if (isTombstone(envelope, this.encrypted)) return null;
|
|
3639
3874
|
if (!this.encrypted) return envelope._data;
|
|
3640
3875
|
const dek = await this.getDEK(this.name);
|
|
3876
|
+
if (envelope._cek !== void 0) {
|
|
3877
|
+
const cached = id !== void 0 ? this.cekCache?.get(id) : void 0;
|
|
3878
|
+
const cek = cached ?? await unwrapCek(envelope._cek, dek);
|
|
3879
|
+
if (cached === void 0 && id !== void 0) this.cekCache?.set(id, cek, 1);
|
|
3880
|
+
return decrypt(envelope._iv, envelope._data, cek);
|
|
3881
|
+
}
|
|
3641
3882
|
return decrypt(envelope._iv, envelope._data, dek);
|
|
3642
3883
|
}
|
|
3643
3884
|
/**
|
|
@@ -3656,7 +3897,8 @@ var Collection = class {
|
|
|
3656
3897
|
* false positive. Every non-history read leaves this flag `false`.
|
|
3657
3898
|
*/
|
|
3658
3899
|
async decryptRecord(envelope, opts = {}) {
|
|
3659
|
-
const json = await this.decryptJsonString(envelope);
|
|
3900
|
+
const json = await this.decryptJsonString(envelope, opts.id);
|
|
3901
|
+
if (json === null) return null;
|
|
3660
3902
|
let parsed = JSON.parse(json);
|
|
3661
3903
|
if (this.crdtMode && parsed !== null && typeof parsed === "object" && "_crdt" in parsed) {
|
|
3662
3904
|
parsed = this.crdtStrategy.resolveCrdtSnapshot(parsed);
|
|
@@ -3784,6 +4026,21 @@ function withArchive(opts) {
|
|
|
3784
4026
|
// src/sequence/index.ts
|
|
3785
4027
|
var SEQUENCE_COLLECTION = "_sequences";
|
|
3786
4028
|
var MAX_NEXT_ATTEMPTS = 16;
|
|
4029
|
+
function resolveSequenceKey(series, opts) {
|
|
4030
|
+
const partition = opts?.partition;
|
|
4031
|
+
if (!partition || partition.length === 0) return series;
|
|
4032
|
+
const parts = partition.map((p) => {
|
|
4033
|
+
if (typeof p === "number" && !Number.isFinite(p)) {
|
|
4034
|
+
throw new ValidationError(`sequence partition component must be a finite number, got ${p}`);
|
|
4035
|
+
}
|
|
4036
|
+
const s = String(p);
|
|
4037
|
+
if (s === "") {
|
|
4038
|
+
throw new ValidationError("sequence partition component must not be empty");
|
|
4039
|
+
}
|
|
4040
|
+
return encodeURIComponent(s);
|
|
4041
|
+
});
|
|
4042
|
+
return `${series}\0${parts.join("/")}`;
|
|
4043
|
+
}
|
|
3787
4044
|
async function sleepBackoff(attempt) {
|
|
3788
4045
|
const ceil = Math.min(2 ** attempt, 32);
|
|
3789
4046
|
const ms = Math.floor(Math.random() * ceil);
|
|
@@ -3814,7 +4071,8 @@ var SequenceStore = class {
|
|
|
3814
4071
|
handle(name) {
|
|
3815
4072
|
return {
|
|
3816
4073
|
next: () => this.next(name),
|
|
3817
|
-
peek: () => this.peek(name)
|
|
4074
|
+
peek: () => this.peek(name),
|
|
4075
|
+
seedTo: (n) => this.seedTo(name, n)
|
|
3818
4076
|
};
|
|
3819
4077
|
}
|
|
3820
4078
|
assertOnline() {
|
|
@@ -3867,6 +4125,30 @@ var SequenceStore = class {
|
|
|
3867
4125
|
void lastConflict;
|
|
3868
4126
|
throw new SequenceContentionError(name, MAX_NEXT_ATTEMPTS);
|
|
3869
4127
|
}
|
|
4128
|
+
async seedTo(name, n) {
|
|
4129
|
+
this.assertOnline();
|
|
4130
|
+
if (n <= 0) return;
|
|
4131
|
+
let lastConflict;
|
|
4132
|
+
for (let attempt = 0; attempt < MAX_NEXT_ATTEMPTS; attempt++) {
|
|
4133
|
+
const { env, value } = await this.read(name);
|
|
4134
|
+
if (value >= n) return;
|
|
4135
|
+
const expectedVersion = env?._v ?? 0;
|
|
4136
|
+
const envelope = await this.encryptState({ value: n }, expectedVersion + 1);
|
|
4137
|
+
try {
|
|
4138
|
+
await this.adapter.put(this.vault, SEQUENCE_COLLECTION, name, envelope, expectedVersion);
|
|
4139
|
+
return;
|
|
4140
|
+
} catch (err) {
|
|
4141
|
+
if (err instanceof ConflictError) {
|
|
4142
|
+
lastConflict = err;
|
|
4143
|
+
if (attempt < MAX_NEXT_ATTEMPTS - 1) await sleepBackoff(attempt);
|
|
4144
|
+
continue;
|
|
4145
|
+
}
|
|
4146
|
+
throw err;
|
|
4147
|
+
}
|
|
4148
|
+
}
|
|
4149
|
+
void lastConflict;
|
|
4150
|
+
throw new SequenceContentionError(name, MAX_NEXT_ATTEMPTS);
|
|
4151
|
+
}
|
|
3870
4152
|
};
|
|
3871
4153
|
|
|
3872
4154
|
// src/numbering/index.ts
|
|
@@ -5100,6 +5382,19 @@ function summariseAggregateOp(value) {
|
|
|
5100
5382
|
}
|
|
5101
5383
|
|
|
5102
5384
|
// src/vault.ts
|
|
5385
|
+
function resolveLabelFromMap(labels, locale, fallback) {
|
|
5386
|
+
if (labels[locale] !== void 0) return labels[locale];
|
|
5387
|
+
const chain = Array.isArray(fallback) ? fallback : fallback ? [fallback] : [];
|
|
5388
|
+
for (const fb of chain) {
|
|
5389
|
+
if (fb === "any") {
|
|
5390
|
+
const any = Object.values(labels)[0];
|
|
5391
|
+
if (any !== void 0) return any;
|
|
5392
|
+
} else if (labels[fb] !== void 0) {
|
|
5393
|
+
return labels[fb];
|
|
5394
|
+
}
|
|
5395
|
+
}
|
|
5396
|
+
return void 0;
|
|
5397
|
+
}
|
|
5103
5398
|
var Vault = class {
|
|
5104
5399
|
adapter;
|
|
5105
5400
|
/** The vault's name as passed to `openVault()`. Stable for the instance lifetime. */
|
|
@@ -5143,6 +5438,7 @@ var Vault = class {
|
|
|
5143
5438
|
periodsStrategy;
|
|
5144
5439
|
shadowStrategy;
|
|
5145
5440
|
historyStrategy;
|
|
5441
|
+
forgetStrategy;
|
|
5146
5442
|
i18nStrategy;
|
|
5147
5443
|
syncStrategy;
|
|
5148
5444
|
/**
|
|
@@ -5309,6 +5605,27 @@ var Vault = class {
|
|
|
5309
5605
|
* Populated by `collection()` when the `dictKeyFields` option is passed.
|
|
5310
5606
|
*/
|
|
5311
5607
|
dictKeyFieldRegistry = /* @__PURE__ */ new Map();
|
|
5608
|
+
/**
|
|
5609
|
+
* Names of dictionaries backed by a `staticDict()` descriptor (#291).
|
|
5610
|
+
* A static dict skips the `dictKeyFieldRegistry` rename machinery, but the
|
|
5611
|
+
* vault must still *know* a name is static so `vault.dictionary(name)` can
|
|
5612
|
+
* refuse mutation (`StaticDictReadonlyError`). Populated at `collection()`
|
|
5613
|
+
* config time whenever a `StaticDictDescriptor` is seen.
|
|
5614
|
+
*/
|
|
5615
|
+
staticDictNames = /* @__PURE__ */ new Set();
|
|
5616
|
+
/**
|
|
5617
|
+
* Static-dict descriptors keyed by dictionary name (#291). Backs the
|
|
5618
|
+
* read-path label resolver (resolve from the in-memory table) and the
|
|
5619
|
+
* query-seam `resolveDictSource` snapshot. Last writer wins when the same
|
|
5620
|
+
* name is registered by multiple collections (identical-across-vaults by
|
|
5621
|
+
* construction, so the tables match).
|
|
5622
|
+
*/
|
|
5623
|
+
staticByName = /* @__PURE__ */ new Map();
|
|
5624
|
+
/**
|
|
5625
|
+
* Per-collection map of field name → StaticDictDescriptor (#291). Used by
|
|
5626
|
+
* `enforceStaticDictOnPut` to validate stored codes against `desc.keys`.
|
|
5627
|
+
*/
|
|
5628
|
+
staticDescriptorByField = /* @__PURE__ */ new Map();
|
|
5312
5629
|
/**
|
|
5313
5630
|
* Registry of i18nText fields declared across all collections. Keyed
|
|
5314
5631
|
* by collection name → field name → I18nTextDescriptor. Used by
|
|
@@ -5355,6 +5672,7 @@ var Vault = class {
|
|
|
5355
5672
|
this.periodsStrategy = opts.periodsStrategy ?? NO_PERIODS;
|
|
5356
5673
|
this.shadowStrategy = opts.shadowStrategy ?? NO_SHADOW;
|
|
5357
5674
|
this.historyStrategy = opts.historyStrategy ?? NO_HISTORY;
|
|
5675
|
+
this.forgetStrategy = opts.forgetStrategy ?? NO_FORGET;
|
|
5358
5676
|
this.i18nStrategy = opts.i18nStrategy ?? NO_I18N;
|
|
5359
5677
|
this.syncStrategy = opts.syncStrategy ?? NO_SYNC;
|
|
5360
5678
|
void opts.guardStrategies;
|
|
@@ -5459,10 +5777,22 @@ var Vault = class {
|
|
|
5459
5777
|
}
|
|
5460
5778
|
if (options?.dictKeyFields) {
|
|
5461
5779
|
const dictFieldMap = {};
|
|
5780
|
+
const staticFieldMap = {};
|
|
5462
5781
|
for (const [field, desc] of Object.entries(options.dictKeyFields)) {
|
|
5463
|
-
|
|
5782
|
+
if (isStaticDictDescriptor(desc)) {
|
|
5783
|
+
staticFieldMap[field] = desc;
|
|
5784
|
+
this.staticDictNames.add(desc.name);
|
|
5785
|
+
this.staticByName.set(desc.name, desc);
|
|
5786
|
+
} else {
|
|
5787
|
+
dictFieldMap[field] = desc.name;
|
|
5788
|
+
}
|
|
5789
|
+
}
|
|
5790
|
+
if (Object.keys(dictFieldMap).length > 0) {
|
|
5791
|
+
this.dictKeyFieldRegistry.set(collectionName, dictFieldMap);
|
|
5792
|
+
}
|
|
5793
|
+
if (Object.keys(staticFieldMap).length > 0) {
|
|
5794
|
+
this.staticDescriptorByField.set(collectionName, staticFieldMap);
|
|
5464
5795
|
}
|
|
5465
|
-
this.dictKeyFieldRegistry.set(collectionName, dictFieldMap);
|
|
5466
5796
|
}
|
|
5467
5797
|
if ((options?.schemaUpdate?.length ?? 0) > 0) {
|
|
5468
5798
|
this.#schemaUpdateNames.set(collectionName, (options.schemaUpdate ?? []).map((s) => s.name));
|
|
@@ -5564,6 +5894,17 @@ var Vault = class {
|
|
|
5564
5894
|
if (options?.acknowledgeDeterministicRisk !== void 0) {
|
|
5565
5895
|
collOpts.acknowledgeDeterministicRisk = options.acknowledgeDeterministicRisk;
|
|
5566
5896
|
}
|
|
5897
|
+
if (options?.perRecordKeys !== void 0) {
|
|
5898
|
+
collOpts.perRecordKeys = options.perRecordKeys;
|
|
5899
|
+
}
|
|
5900
|
+
if (this.forgetStrategy.subjects[collectionName] !== void 0) {
|
|
5901
|
+
if (options?.perRecordKeys === false) {
|
|
5902
|
+
console.warn(
|
|
5903
|
+
`[noy-db] Collection "${collectionName}" is declared in withForgetCascade but opened with perRecordKeys: false. Forcing perRecordKeys: true \u2014 GDPR crypto-shred requires per-record CEKs.`
|
|
5904
|
+
);
|
|
5905
|
+
}
|
|
5906
|
+
collOpts.perRecordKeys = true;
|
|
5907
|
+
}
|
|
5567
5908
|
if (options?.tiers !== void 0) collOpts.tiers = options.tiers;
|
|
5568
5909
|
if (options?.tierMode !== void 0) collOpts.tierMode = options.tierMode;
|
|
5569
5910
|
collOpts.onCrossTierAccess = (event) => this.emitCrossTier(event);
|
|
@@ -5573,6 +5914,11 @@ var Vault = class {
|
|
|
5573
5914
|
if (options?.computed !== void 0) collOpts.computed = options.computed;
|
|
5574
5915
|
if (options?.dictKeyFields !== void 0) {
|
|
5575
5916
|
collOpts.dictLabelResolver = async (dictName, key, locale, fallback) => {
|
|
5917
|
+
const stat = this.staticByName.get(dictName);
|
|
5918
|
+
if (stat) {
|
|
5919
|
+
const labels = stat.table[key];
|
|
5920
|
+
return labels ? resolveLabelFromMap(labels, locale, fallback) : void 0;
|
|
5921
|
+
}
|
|
5576
5922
|
const handle = this.dictionary(dictName);
|
|
5577
5923
|
return handle.resolveLabel(key, locale, fallback);
|
|
5578
5924
|
};
|
|
@@ -5581,6 +5927,7 @@ var Vault = class {
|
|
|
5581
5927
|
if (options?.i18nFields !== void 0 || options?.dictKeyFields !== void 0) {
|
|
5582
5928
|
collOpts.i18nPutValidator = (record) => {
|
|
5583
5929
|
this.enforceI18nOnPut(collectionName, record);
|
|
5930
|
+
this.enforceStaticDictOnPut(collectionName, record);
|
|
5584
5931
|
};
|
|
5585
5932
|
}
|
|
5586
5933
|
if (options?.i18nFields !== void 0 && this.translateText) {
|
|
@@ -5720,6 +6067,34 @@ var Vault = class {
|
|
|
5720
6067
|
}
|
|
5721
6068
|
}
|
|
5722
6069
|
}
|
|
6070
|
+
/**
|
|
6071
|
+
* Validate staticDict codes on a `put()` (#291). For each `staticDict()`
|
|
6072
|
+
* field, every stored code must be a declared key of the descriptor's
|
|
6073
|
+
* table, else `UnknownDictCodeError`. Opt out per descriptor with
|
|
6074
|
+
* `{ validateCodes: false }`. Supports scalar, dotted, and `[].`-wildcard
|
|
6075
|
+
* field paths via `getAtPath` (same path support as i18n validation).
|
|
6076
|
+
*/
|
|
6077
|
+
enforceStaticDictOnPut(collectionName, record) {
|
|
6078
|
+
const staticFields = this.staticDescriptorByField.get(collectionName);
|
|
6079
|
+
if (!staticFields || Object.keys(staticFields).length === 0) return;
|
|
6080
|
+
if (!record || typeof record !== "object") return;
|
|
6081
|
+
const obj = record;
|
|
6082
|
+
for (const [field, desc] of Object.entries(staticFields)) {
|
|
6083
|
+
if (desc.validateCodes === false) continue;
|
|
6084
|
+
const known = new Set(desc.keys);
|
|
6085
|
+
const values = getAtPath(obj, field);
|
|
6086
|
+
for (const value of values) {
|
|
6087
|
+
if (value === void 0 || value === null) continue;
|
|
6088
|
+
const codes = Array.isArray(value) ? value : [value];
|
|
6089
|
+
for (const code of codes) {
|
|
6090
|
+
if (typeof code !== "string") continue;
|
|
6091
|
+
if (!known.has(code)) {
|
|
6092
|
+
throw new UnknownDictCodeError(desc.name, field, code);
|
|
6093
|
+
}
|
|
6094
|
+
}
|
|
6095
|
+
}
|
|
6096
|
+
}
|
|
6097
|
+
}
|
|
5723
6098
|
/**
|
|
5724
6099
|
* Apply locale resolution to a record for the given collection.
|
|
5725
6100
|
*
|
|
@@ -5728,14 +6103,18 @@ var Vault = class {
|
|
|
5728
6103
|
*/
|
|
5729
6104
|
async applyLocale(collectionName, record, localeOpts) {
|
|
5730
6105
|
const locale = localeOpts.locale ?? this.locale;
|
|
5731
|
-
|
|
6106
|
+
const staticFields = this.staticDescriptorByField.get(collectionName);
|
|
6107
|
+
const hasStaticDisplay = staticFields !== void 0 && Object.values(staticFields).some((d) => d.displayLocale !== void 0);
|
|
6108
|
+
if (!locale && !hasStaticDisplay) return record;
|
|
5732
6109
|
let result = record;
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
6110
|
+
if (locale) {
|
|
6111
|
+
const i18nFields = this.i18nFieldRegistry.get(collectionName);
|
|
6112
|
+
if (i18nFields && Object.keys(i18nFields).length > 0) {
|
|
6113
|
+
result = this.i18nStrategy.applyI18nLocale(result, i18nFields, locale, localeOpts.fallback);
|
|
6114
|
+
}
|
|
5736
6115
|
}
|
|
5737
6116
|
const dictFields = this.dictKeyFieldRegistry.get(collectionName);
|
|
5738
|
-
if (dictFields && Object.keys(dictFields).length > 0 && locale !== "raw") {
|
|
6117
|
+
if (locale && dictFields && Object.keys(dictFields).length > 0 && locale !== "raw") {
|
|
5739
6118
|
const withLabels = { ...result };
|
|
5740
6119
|
for (const [field, dictName] of Object.entries(dictFields)) {
|
|
5741
6120
|
const key = result[field];
|
|
@@ -5748,6 +6127,22 @@ var Vault = class {
|
|
|
5748
6127
|
}
|
|
5749
6128
|
result = withLabels;
|
|
5750
6129
|
}
|
|
6130
|
+
if (staticFields && Object.keys(staticFields).length > 0 && locale !== "raw") {
|
|
6131
|
+
const withLabels = { ...result };
|
|
6132
|
+
for (const [field, desc] of Object.entries(staticFields)) {
|
|
6133
|
+
const effLocale = locale ?? desc.displayLocale;
|
|
6134
|
+
if (!effLocale) continue;
|
|
6135
|
+
const key = result[field];
|
|
6136
|
+
if (typeof key !== "string") continue;
|
|
6137
|
+
const labels = desc.table[key];
|
|
6138
|
+
if (!labels) continue;
|
|
6139
|
+
const label = resolveLabelFromMap(labels, effLocale, localeOpts.fallback ?? desc.substitute);
|
|
6140
|
+
if (label !== void 0) {
|
|
6141
|
+
withLabels[`${field}Label`] = label;
|
|
6142
|
+
}
|
|
6143
|
+
}
|
|
6144
|
+
result = withLabels;
|
|
6145
|
+
}
|
|
5751
6146
|
return result;
|
|
5752
6147
|
}
|
|
5753
6148
|
/**
|
|
@@ -5769,6 +6164,9 @@ var Vault = class {
|
|
|
5769
6164
|
* ```
|
|
5770
6165
|
*/
|
|
5771
6166
|
dictionary(name, options = {}) {
|
|
6167
|
+
if (this.staticDictNames.has(name)) {
|
|
6168
|
+
throw new StaticDictReadonlyError(name);
|
|
6169
|
+
}
|
|
5772
6170
|
let handle = this.dictionaryCache.get(name);
|
|
5773
6171
|
if (!handle) {
|
|
5774
6172
|
handle = this.i18nStrategy.buildDictionaryHandle({
|
|
@@ -5838,6 +6236,26 @@ var Vault = class {
|
|
|
5838
6236
|
* Returns `null` when `field` is not a dictKey in `leftCollection`.
|
|
5839
6237
|
*/
|
|
5840
6238
|
resolveDictSource(leftCollection, field) {
|
|
6239
|
+
const staticFields = this.staticDescriptorByField.get(leftCollection);
|
|
6240
|
+
if (staticFields && field in staticFields) {
|
|
6241
|
+
const desc = staticFields[field];
|
|
6242
|
+
const rows = Object.entries(desc.table).map(
|
|
6243
|
+
([key, labels]) => ({ key, labels, ...labels })
|
|
6244
|
+
);
|
|
6245
|
+
const source = {
|
|
6246
|
+
snapshot() {
|
|
6247
|
+
return rows;
|
|
6248
|
+
},
|
|
6249
|
+
lookupById(id) {
|
|
6250
|
+
return rows.find((e) => e["key"] === id);
|
|
6251
|
+
}
|
|
6252
|
+
};
|
|
6253
|
+
if (desc.displayLocale !== void 0) {
|
|
6254
|
+
;
|
|
6255
|
+
source.displayLocale = desc.displayLocale;
|
|
6256
|
+
}
|
|
6257
|
+
return source;
|
|
6258
|
+
}
|
|
5841
6259
|
const dictFields = this.dictKeyFieldRegistry.get(leftCollection);
|
|
5842
6260
|
if (!dictFields || !(field in dictFields)) return null;
|
|
5843
6261
|
const dictName = dictFields[field];
|
|
@@ -6005,17 +6423,23 @@ var Vault = class {
|
|
|
6005
6423
|
* const cur = await vault.sequence('invoice-2026').peek() // current value, no allocation
|
|
6006
6424
|
* ```
|
|
6007
6425
|
*/
|
|
6008
|
-
sequence(
|
|
6009
|
-
if (
|
|
6426
|
+
sequence(series, opts) {
|
|
6427
|
+
if (series.includes("\0")) {
|
|
6428
|
+
throw new ValidationError(`sequence("${series}"): series name must not contain a null byte (\\x00).`);
|
|
6429
|
+
}
|
|
6430
|
+
if (this.numberingConfigs.has(series)) {
|
|
6010
6431
|
const eng = this.deferred();
|
|
6011
6432
|
return {
|
|
6012
|
-
next: async (
|
|
6013
|
-
if (!
|
|
6014
|
-
throw new ValidationError(`sequence("${
|
|
6433
|
+
next: async (nextOpts) => {
|
|
6434
|
+
if (!nextOpts?.for) {
|
|
6435
|
+
throw new ValidationError(`sequence("${series}") is a deferred-numbering series; call next({ for: recordId }).`);
|
|
6015
6436
|
}
|
|
6016
|
-
return (await eng.enqueue(
|
|
6437
|
+
return (await eng.enqueue(series, nextOpts.for)).assigned;
|
|
6017
6438
|
},
|
|
6018
|
-
peek: () => eng.peek(
|
|
6439
|
+
peek: () => eng.peek(series),
|
|
6440
|
+
seedTo: () => {
|
|
6441
|
+
throw new ValidationError(`sequence("${series}") is a deferred-numbering series; seedTo is CAS-only.`);
|
|
6442
|
+
}
|
|
6019
6443
|
};
|
|
6020
6444
|
}
|
|
6021
6445
|
if (!this.sequenceStore) {
|
|
@@ -6027,7 +6451,7 @@ var Vault = class {
|
|
|
6027
6451
|
actor: this.keyring.userId
|
|
6028
6452
|
});
|
|
6029
6453
|
}
|
|
6030
|
-
return this.sequenceStore.handle(
|
|
6454
|
+
return this.sequenceStore.handle(resolveSequenceKey(series, opts));
|
|
6031
6455
|
}
|
|
6032
6456
|
/** @internal — lazily build the deferred-numbering engine with a cache-coherent stamp. */
|
|
6033
6457
|
deferred() {
|
|
@@ -6142,12 +6566,12 @@ var Vault = class {
|
|
|
6142
6566
|
if (!fieldSchema) {
|
|
6143
6567
|
throw new AttestationError(`issueAttestation: collection '${collectionName}' has no attestation field-schema. Declare it via vault.collection('${collectionName}', { attestation: { fields: [...] } }).`);
|
|
6144
6568
|
}
|
|
6145
|
-
const { issueAttestationCore } = await import("./issue-
|
|
6569
|
+
const { issueAttestationCore } = await import("./issue-TTMGHQ2J.js");
|
|
6146
6570
|
const out = await issueAttestationCore(this.makeIssueContext(), { collection: collectionName, id, fieldSchema });
|
|
6147
6571
|
return { docId: out.docId, qr: out.qr, keyId: out.keyId, publicKeyB64: out.publicKeyB64 };
|
|
6148
6572
|
}
|
|
6149
6573
|
async getDocumentSigningPublicKey() {
|
|
6150
|
-
const { loadSigner, loadOrCreateSigner } = await import("./signer-
|
|
6574
|
+
const { loadSigner, loadOrCreateSigner } = await import("./signer-YSXZT574.js");
|
|
6151
6575
|
const existing = await loadSigner(this.adapter, this.name, this.getDEK);
|
|
6152
6576
|
if (existing) return { keyId: existing.keyId, publicKeyB64: existing.publicKeyB64 };
|
|
6153
6577
|
if (this.keyring.role !== "owner") {
|
|
@@ -6173,19 +6597,19 @@ var Vault = class {
|
|
|
6173
6597
|
};
|
|
6174
6598
|
}
|
|
6175
6599
|
async revokeAttestation(docId) {
|
|
6176
|
-
const { revokeDocCore } = await import("./revoke-
|
|
6600
|
+
const { revokeDocCore } = await import("./revoke-B54H2S2W.js");
|
|
6177
6601
|
await revokeDocCore(this.makeRevokeContext(), docId);
|
|
6178
6602
|
}
|
|
6179
6603
|
async unrevokeAttestation(docId) {
|
|
6180
|
-
const { unrevokeDocCore } = await import("./revoke-
|
|
6604
|
+
const { unrevokeDocCore } = await import("./revoke-B54H2S2W.js");
|
|
6181
6605
|
await unrevokeDocCore(this.makeRevokeContext(), docId);
|
|
6182
6606
|
}
|
|
6183
6607
|
async getRevokedDocIds() {
|
|
6184
|
-
const { getRevokedDocIdsCore } = await import("./revoke-
|
|
6608
|
+
const { getRevokedDocIdsCore } = await import("./revoke-B54H2S2W.js");
|
|
6185
6609
|
return getRevokedDocIdsCore(this.makeRevokeContext());
|
|
6186
6610
|
}
|
|
6187
6611
|
async publishRevocationList() {
|
|
6188
|
-
const { publishRevocationListCore } = await import("./revoke-
|
|
6612
|
+
const { publishRevocationListCore } = await import("./revoke-B54H2S2W.js");
|
|
6189
6613
|
return publishRevocationListCore(this.makeRevokeContext());
|
|
6190
6614
|
}
|
|
6191
6615
|
makeRevokeContext() {
|
|
@@ -6319,9 +6743,24 @@ var Vault = class {
|
|
|
6319
6743
|
});
|
|
6320
6744
|
}
|
|
6321
6745
|
if (rule.mode === "cascade") {
|
|
6746
|
+
const txCtx = this.noydb._activeTxContextOrNull;
|
|
6322
6747
|
for (const match of matches) {
|
|
6323
6748
|
const matchId = match["id"] ?? null;
|
|
6324
6749
|
if (matchId === null) continue;
|
|
6750
|
+
if (txCtx !== null) {
|
|
6751
|
+
const prior = await this.adapter.get(this.name, rule.collection, matchId);
|
|
6752
|
+
if (prior !== null) {
|
|
6753
|
+
txCtx._executed.push({
|
|
6754
|
+
op: {
|
|
6755
|
+
type: "delete",
|
|
6756
|
+
vaultName: this.name,
|
|
6757
|
+
collectionName: rule.collection,
|
|
6758
|
+
id: matchId
|
|
6759
|
+
},
|
|
6760
|
+
priorEnvelope: prior
|
|
6761
|
+
});
|
|
6762
|
+
}
|
|
6763
|
+
}
|
|
6325
6764
|
await fromCollection.delete(matchId);
|
|
6326
6765
|
}
|
|
6327
6766
|
}
|
|
@@ -6460,6 +6899,218 @@ var Vault = class {
|
|
|
6460
6899
|
}
|
|
6461
6900
|
return this.ledgerStore;
|
|
6462
6901
|
}
|
|
6902
|
+
// ─── GDPR right-to-erasure (#304) ────────────────────────────────
|
|
6903
|
+
/** @internal — add a subject→record ref to the encrypted subject index. */
|
|
6904
|
+
async _addSubjectRef(subjectId, ref2) {
|
|
6905
|
+
await addSubjectRef(this.adapter, this.name, this.getDEK, this.encrypted, subjectId, ref2);
|
|
6906
|
+
}
|
|
6907
|
+
/** @internal — drop a subject→record ref from the encrypted subject index. */
|
|
6908
|
+
async _removeSubjectRef(subjectId, ref2) {
|
|
6909
|
+
await removeSubjectRef(this.adapter, this.name, this.getDEK, this.encrypted, subjectId, ref2);
|
|
6910
|
+
}
|
|
6911
|
+
/**
|
|
6912
|
+
* Rebuild the encrypted subject index from canonical records. The recovery
|
|
6913
|
+
* path for the documented read-modify-write race (RISK #3). Returns the
|
|
6914
|
+
* number of distinct subjects re-indexed.
|
|
6915
|
+
*/
|
|
6916
|
+
async rebuildSubjectIndex() {
|
|
6917
|
+
if (Object.keys(this.forgetStrategy.subjects).length === 0) {
|
|
6918
|
+
throw new ForgetStrategyNotConfiguredError();
|
|
6919
|
+
}
|
|
6920
|
+
return rebuildSubjectIndex(
|
|
6921
|
+
this.adapter,
|
|
6922
|
+
this.name,
|
|
6923
|
+
this.getDEK,
|
|
6924
|
+
this.encrypted,
|
|
6925
|
+
this.forgetStrategy.subjects,
|
|
6926
|
+
async (collectionName, id, env) => {
|
|
6927
|
+
const coll = this.collection(collectionName);
|
|
6928
|
+
return coll._decodeEnvelope(env, id);
|
|
6929
|
+
}
|
|
6930
|
+
);
|
|
6931
|
+
}
|
|
6932
|
+
/**
|
|
6933
|
+
* GDPR crypto-shred of a data subject (#304). Consults the encrypted subject
|
|
6934
|
+
* index and, per matching record:
|
|
6935
|
+
* - rewrites the LIVE envelope to a tombstone (drops `_iv`/`_data`/`_cek`/`_det`),
|
|
6936
|
+
* - tombstones every `_history` version of the record,
|
|
6937
|
+
* so the body and all prior versions become permanently undecryptable while
|
|
6938
|
+
* the collection DEK and every OTHER record stay intact. Then appends ONE
|
|
6939
|
+
* `op:'forget'` ledger entry whose `payloadHash` is `sha256Hex(subjectId)` —
|
|
6940
|
+
* the chain still `verify()`s, PROVING the subject existed and was erased
|
|
6941
|
+
* without retaining any plaintext.
|
|
6942
|
+
*
|
|
6943
|
+
* Reports — but does not silently swallow — two completeness gaps:
|
|
6944
|
+
* - `unmigratedRecords`: a record whose body was NOT yet migrated to a
|
|
6945
|
+
* per-record CEK (legacy body still under the shared collection DEK). It
|
|
6946
|
+
* is still tombstoned, but its pre-shred ciphertext (if leaked to a
|
|
6947
|
+
* backup before migration) stays decryptable. Migrate, then re-forget.
|
|
6948
|
+
* - `blobResidueCollections`: a shredded record still has blob attachments,
|
|
6949
|
+
* which are keyed off a separate `_blob` DEK and are out of scope here.
|
|
6950
|
+
*
|
|
6951
|
+
* @throws ForgetStrategyNotConfiguredError when no `withForgetCascade` was set.
|
|
6952
|
+
*/
|
|
6953
|
+
async forget(subjectId) {
|
|
6954
|
+
if (Object.keys(this.forgetStrategy.subjects).length === 0) {
|
|
6955
|
+
throw new ForgetStrategyNotConfiguredError();
|
|
6956
|
+
}
|
|
6957
|
+
const refs = await lookupSubject(this.adapter, this.name, this.getDEK, this.encrypted, subjectId);
|
|
6958
|
+
let recordsShredded = 0;
|
|
6959
|
+
let historyVersionsShredded = 0;
|
|
6960
|
+
const collections = /* @__PURE__ */ new Set();
|
|
6961
|
+
const unmigratedRecords = [];
|
|
6962
|
+
const blobResidueCollections = /* @__PURE__ */ new Set();
|
|
6963
|
+
let blobsShredded = 0;
|
|
6964
|
+
let blobsRetainedShared = 0;
|
|
6965
|
+
const blobsEnabled = this.blobStrategy !== void 0;
|
|
6966
|
+
const actor = this.keyring.userId;
|
|
6967
|
+
for (const ref2 of refs) {
|
|
6968
|
+
const coll = this.collection(ref2.collection);
|
|
6969
|
+
const perRecordKeys = this.forgetStrategy.subjects[ref2.collection] !== void 0;
|
|
6970
|
+
const live = await this.adapter.get(this.name, ref2.collection, ref2.id);
|
|
6971
|
+
if (perRecordKeys && live && live._data && live._cek === void 0) {
|
|
6972
|
+
unmigratedRecords.push(`${ref2.collection}:${ref2.id}`);
|
|
6973
|
+
}
|
|
6974
|
+
const shred = await coll._writeTombstone(ref2.id, actor);
|
|
6975
|
+
if (shred !== null) {
|
|
6976
|
+
recordsShredded++;
|
|
6977
|
+
collections.add(ref2.collection);
|
|
6978
|
+
}
|
|
6979
|
+
historyVersionsShredded += await this.historyStrategy.tombstoneHistory(
|
|
6980
|
+
this.adapter,
|
|
6981
|
+
this.name,
|
|
6982
|
+
ref2.collection,
|
|
6983
|
+
ref2.id,
|
|
6984
|
+
actor
|
|
6985
|
+
);
|
|
6986
|
+
if (blobsEnabled) {
|
|
6987
|
+
const r = await this.collection(ref2.collection).blob(ref2.id).shredAllForRecord();
|
|
6988
|
+
blobsShredded += r.shredded.length;
|
|
6989
|
+
blobsRetainedShared += r.retainedShared.length;
|
|
6990
|
+
if (r.residue.length > 0) blobResidueCollections.add(ref2.collection);
|
|
6991
|
+
} else {
|
|
6992
|
+
try {
|
|
6993
|
+
const slotIds = await this.adapter.list(this.name, `_blob_slots_${ref2.collection}`);
|
|
6994
|
+
if (slotIds.includes(ref2.id)) blobResidueCollections.add(ref2.collection);
|
|
6995
|
+
} catch {
|
|
6996
|
+
}
|
|
6997
|
+
}
|
|
6998
|
+
await this._removeSubjectRef(subjectId, ref2);
|
|
6999
|
+
}
|
|
7000
|
+
const subjectHash = await sha256Hex2(subjectId);
|
|
7001
|
+
const ledger = this.getLedgerOrNull();
|
|
7002
|
+
if (!ledger) {
|
|
7003
|
+
throw new Error(
|
|
7004
|
+
'vault.forget() requires the history strategy for the erasure-proof ledger entry. Pass `historyStrategy: withHistory()` from "@noy-db/hub/history" to createNoydb().'
|
|
7005
|
+
);
|
|
7006
|
+
}
|
|
7007
|
+
const ledgerEntry = await ledger.append({
|
|
7008
|
+
op: "forget",
|
|
7009
|
+
collection: "",
|
|
7010
|
+
id: "",
|
|
7011
|
+
version: 0,
|
|
7012
|
+
actor,
|
|
7013
|
+
payloadHash: subjectHash,
|
|
7014
|
+
reason: JSON.stringify({
|
|
7015
|
+
recordsShredded,
|
|
7016
|
+
historyVersionsShredded,
|
|
7017
|
+
collections: [...collections],
|
|
7018
|
+
unmigratedCount: unmigratedRecords.length,
|
|
7019
|
+
blobsShredded,
|
|
7020
|
+
blobsRetainedShared,
|
|
7021
|
+
blobResidueCollections: [...blobResidueCollections]
|
|
7022
|
+
})
|
|
7023
|
+
});
|
|
7024
|
+
return {
|
|
7025
|
+
subject: subjectId,
|
|
7026
|
+
recordsShredded,
|
|
7027
|
+
historyVersionsShredded,
|
|
7028
|
+
collections: [...collections],
|
|
7029
|
+
unmigratedRecords,
|
|
7030
|
+
blobsShredded,
|
|
7031
|
+
blobsRetainedShared,
|
|
7032
|
+
blobResidueCollections: [...blobResidueCollections],
|
|
7033
|
+
ledgerEntry
|
|
7034
|
+
};
|
|
7035
|
+
}
|
|
7036
|
+
// ─── Record-scoped CEK sealing (#306 slices 2-3) ──────────────────────
|
|
7037
|
+
/**
|
|
7038
|
+
* Seal ONE record's content-encryption key (CEK) to an `at-*` host so that
|
|
7039
|
+
* host — and only that host — can decrypt exactly that record, with no
|
|
7040
|
+
* access to the vault DEK and no ability to read any other record.
|
|
7041
|
+
*
|
|
7042
|
+
* The grantor (this caller, who holds the collection DEK) reads the record's
|
|
7043
|
+
* live `_cek`, unwraps it under the collection DEK, exports the raw CEK
|
|
7044
|
+
* bytes, builds a {@link SealedCekBinding} `{collection, id, cek, expiresAt}`,
|
|
7045
|
+
* seals that binding for the recipient host via the host's published hint,
|
|
7046
|
+
* and persists a thin {@link SealedCekDeliveryEnvelope} at
|
|
7047
|
+
* `_sealed_cek/<collection>/<id>/<pid>`. The binding (not the delivery
|
|
7048
|
+
* envelope) is the security boundary: the host re-verifies `{collection, id}`
|
|
7049
|
+
* and `expiresAt` from inside the sealed payload.
|
|
7050
|
+
*
|
|
7051
|
+
* Only works on a `perRecordKeys` record — a legacy record has no `_cek` to
|
|
7052
|
+
* seal (its body is under the shared collection DEK, which is never exposed
|
|
7053
|
+
* by sealing) → {@link RecordCekNotFoundError}.
|
|
7054
|
+
*
|
|
7055
|
+
* @param collection Collection holding the record.
|
|
7056
|
+
* @param id Record id.
|
|
7057
|
+
* @param hostSealer The recipient host's {@link RecipientSealer}.
|
|
7058
|
+
* @param opts.expiresAt REQUIRED authoritative expiry (ISO 8601), sealed into
|
|
7059
|
+
* the binding the host verifies.
|
|
7060
|
+
* @returns `{ pid, envelopeKey }` — the host provider id and the
|
|
7061
|
+
* `<collection>/<id>/<pid>` key the delivery envelope was written under.
|
|
7062
|
+
*/
|
|
7063
|
+
async sealRecordToHost(collection, id, hostSealer, opts) {
|
|
7064
|
+
return sealRecordToHost(this.sealingContext(), collection, id, hostSealer, opts);
|
|
7065
|
+
}
|
|
7066
|
+
/**
|
|
7067
|
+
* Revoke a single sealed-CEK delivery envelope by deleting it from the store.
|
|
7068
|
+
* A soft revocation: it removes the host's copy of the sealed CEK, but a host
|
|
7069
|
+
* that already fetched the envelope keeps whatever it cached. For a hard
|
|
7070
|
+
* revocation that makes the live record undecryptable to every prior grant,
|
|
7071
|
+
* use {@link rotateRecordCek}.
|
|
7072
|
+
*/
|
|
7073
|
+
async revokeSealedRecord(collection, id, pid) {
|
|
7074
|
+
return revokeSealedRecord(this.sealingContext(), collection, id, pid);
|
|
7075
|
+
}
|
|
7076
|
+
/**
|
|
7077
|
+
* HARD-rotate a record's CEK: decrypt the live body under the old CEK,
|
|
7078
|
+
* re-encrypt it under a freshly-minted CEK, write the new live envelope, evict
|
|
7079
|
+
* the in-memory caches, and delete EVERY sealed-CEK delivery envelope for the
|
|
7080
|
+
* record. After this, any host holding a previously-sealed CEK can still
|
|
7081
|
+
* decrypt PRE-rotation history versions (they keep their old `_cek`) but NOT
|
|
7082
|
+
* the rotated live record (its body is under the new CEK → the old CEK fails
|
|
7083
|
+
* the AES-GCM auth tag → `TamperedError`). That asymmetry IS the revocation:
|
|
7084
|
+
* old grants lose the live record.
|
|
7085
|
+
*
|
|
7086
|
+
* Administrative path — bypasses `Collection.put` deliberately (no guards, no
|
|
7087
|
+
* history snapshot, no materialized-view refresh): rotation is a key-rotation
|
|
7088
|
+
* operation, not a business write, and must not version-bump history (which
|
|
7089
|
+
* would re-encrypt the prior version under the NEW CEK and defeat the point).
|
|
7090
|
+
*
|
|
7091
|
+
* @throws {@link RecordCekNotFoundError} if the record is missing or has no `_cek`.
|
|
7092
|
+
*/
|
|
7093
|
+
async rotateRecordCek(collection, id) {
|
|
7094
|
+
return rotateRecordCek(this.sealingContext(), collection, id);
|
|
7095
|
+
}
|
|
7096
|
+
/**
|
|
7097
|
+
* Build the {@link SealingContext} the record-keys grantor functions need:
|
|
7098
|
+
* the vault-bound adapter, DEK resolver, actor, and the dual-cache eviction
|
|
7099
|
+
* `rotateRecordCek` performs (per-record CEK cache + decrypted-record cache).
|
|
7100
|
+
*/
|
|
7101
|
+
sealingContext() {
|
|
7102
|
+
return {
|
|
7103
|
+
adapter: this.adapter,
|
|
7104
|
+
vault: this.name,
|
|
7105
|
+
getDEK: (collection) => this.getDEK(collection),
|
|
7106
|
+
actor: this.keyring.userId,
|
|
7107
|
+
invalidateRecordCaches: async (collection, id) => {
|
|
7108
|
+
const coll = this.collection(collection);
|
|
7109
|
+
coll._invalidateCekCacheEntry(id);
|
|
7110
|
+
await coll._invalidateCacheEntry(id);
|
|
7111
|
+
}
|
|
7112
|
+
};
|
|
7113
|
+
}
|
|
6463
7114
|
/**
|
|
6464
7115
|
* @internal — called by `Noydb.openVault` after construction.
|
|
6465
7116
|
* Dynamic-imports `GuardRegistry` + `ReadOnlyVaultFacade` and seeds
|
|
@@ -6502,7 +7153,7 @@ var Vault = class {
|
|
|
6502
7153
|
async _initDerivations(handles) {
|
|
6503
7154
|
if (handles.length === 0) return;
|
|
6504
7155
|
const [{ DerivationRegistry }, { ReadOnlyVaultFacade }] = await Promise.all([
|
|
6505
|
-
import("./registry-
|
|
7156
|
+
import("./registry-TGZISEWC.js"),
|
|
6506
7157
|
import("./read-only-facade-ITU6L7BL.js")
|
|
6507
7158
|
]);
|
|
6508
7159
|
const registry = new DerivationRegistry();
|
|
@@ -6533,7 +7184,7 @@ var Vault = class {
|
|
|
6533
7184
|
*/
|
|
6534
7185
|
async _initMaterializedViews(handles) {
|
|
6535
7186
|
if (handles.length === 0) return;
|
|
6536
|
-
const { MaterializedViewRegistry } = await import("./registry-
|
|
7187
|
+
const { MaterializedViewRegistry } = await import("./registry-SECUWSGY.js");
|
|
6537
7188
|
const registry = new MaterializedViewRegistry();
|
|
6538
7189
|
this.materializedViewRegistry = registry;
|
|
6539
7190
|
const db = this;
|
|
@@ -6557,7 +7208,7 @@ var Vault = class {
|
|
|
6557
7208
|
*/
|
|
6558
7209
|
async _initOverlayedViews(handles) {
|
|
6559
7210
|
if (handles.length === 0) return;
|
|
6560
|
-
const { OverlayedViewRegistry } = await import("./registry-
|
|
7211
|
+
const { OverlayedViewRegistry } = await import("./registry-3YFLZ7WD.js");
|
|
6561
7212
|
const registry = new OverlayedViewRegistry();
|
|
6562
7213
|
const mvRegistry = this.materializedViewRegistry;
|
|
6563
7214
|
const overlayNames = /* @__PURE__ */ new Set();
|
|
@@ -6604,13 +7255,13 @@ var Vault = class {
|
|
|
6604
7255
|
if (!reg) {
|
|
6605
7256
|
throw new Error(`refreshView: no MV registered with name "${name}"`);
|
|
6606
7257
|
}
|
|
6607
|
-
const { MaterializedViewExecutor } = await import("./executor-
|
|
7258
|
+
const { MaterializedViewExecutor } = await import("./executor-3W63Y44O.js");
|
|
6608
7259
|
const result = await MaterializedViewExecutor.refresh(reg, {
|
|
6609
7260
|
getCollection: (n) => this.collection(n),
|
|
6610
7261
|
getActiveTxContext: () => this.noydb._activeTxContextOrNull,
|
|
6611
7262
|
getQueryContext: () => this
|
|
6612
7263
|
});
|
|
6613
|
-
const { clearMVStale } = await import("./stale-
|
|
7264
|
+
const { clearMVStale } = await import("./stale-TOA36SRK.js");
|
|
6614
7265
|
clearMVStale(registry, name);
|
|
6615
7266
|
return result;
|
|
6616
7267
|
}
|
|
@@ -6626,7 +7277,7 @@ var Vault = class {
|
|
|
6626
7277
|
if (registry === null) return { derived: 0, failed: 0 };
|
|
6627
7278
|
const strategies = registry.strategiesForSource(sourceCollection);
|
|
6628
7279
|
if (strategies.length === 0) return { derived: 0, failed: 0 };
|
|
6629
|
-
const { DerivationExecutor } = await import("./executor-
|
|
7280
|
+
const { DerivationExecutor } = await import("./executor-CFFWPWBJ.js");
|
|
6630
7281
|
const sourceColl = this.collection(sourceCollection);
|
|
6631
7282
|
const records = await sourceColl.list();
|
|
6632
7283
|
const ctx = { vault: this.readOnlyFacade ?? new (await import("./read-only-facade-ITU6L7BL.js")).ReadOnlyVaultFacade(this) };
|
|
@@ -6651,7 +7302,7 @@ var Vault = class {
|
|
|
6651
7302
|
if (!outSpec) continue;
|
|
6652
7303
|
const outputColl = this.collection(outSpec.collection);
|
|
6653
7304
|
if (out.kind === "array") {
|
|
6654
|
-
const { loadFanoutSidecar, saveFanoutSidecar } = await import("./fanout-sidecar-
|
|
7305
|
+
const { loadFanoutSidecar, saveFanoutSidecar } = await import("./fanout-sidecar-FIJJ46YG.js");
|
|
6655
7306
|
const prior = await loadFanoutSidecar(this.adapter, this.name, spec.source, id, key);
|
|
6656
7307
|
const prevKeys = new Set(prior?.keys ?? []);
|
|
6657
7308
|
const newKeysList = out.entries.map((e) => e.key);
|
|
@@ -6872,7 +7523,7 @@ var Vault = class {
|
|
|
6872
7523
|
* collection.
|
|
6873
7524
|
*/
|
|
6874
7525
|
async delegate(opts) {
|
|
6875
|
-
const { issueDelegation, DELEGATIONS_COLLECTION } = await import("./delegation-
|
|
7526
|
+
const { issueDelegation, DELEGATIONS_COLLECTION } = await import("./delegation-MGH5SODX.js");
|
|
6876
7527
|
if (!this.keyring.kek) {
|
|
6877
7528
|
throw new ValidationError(
|
|
6878
7529
|
"issueDelegation: keyring.kek is null \u2014 issuing a delegation requires a tier-1 unlock. Re-authenticate at tier 1 (passphrase) first."
|
|
@@ -6894,7 +7545,7 @@ var Vault = class {
|
|
|
6894
7545
|
* if the id does not exist.
|
|
6895
7546
|
*/
|
|
6896
7547
|
async revokeDelegation(id) {
|
|
6897
|
-
const { revokeDelegation, DELEGATIONS_COLLECTION } = await import("./delegation-
|
|
7548
|
+
const { revokeDelegation, DELEGATIONS_COLLECTION } = await import("./delegation-MGH5SODX.js");
|
|
6898
7549
|
await revokeDelegation(this.adapter, this.name, id);
|
|
6899
7550
|
void DELEGATIONS_COLLECTION;
|
|
6900
7551
|
}
|
|
@@ -7363,7 +8014,7 @@ var Vault = class {
|
|
|
7363
8014
|
* @see docs/subsystems/public-envelope.md
|
|
7364
8015
|
*/
|
|
7365
8016
|
async getPublicEnvelope(opts = {}) {
|
|
7366
|
-
const { readPublicEnvelope: readPublicEnvelope2 } = await import("./public-envelope-
|
|
8017
|
+
const { readPublicEnvelope: readPublicEnvelope2 } = await import("./public-envelope-RXZNP3V6.js");
|
|
7367
8018
|
return readPublicEnvelope2(this.adapter, this.name, opts);
|
|
7368
8019
|
}
|
|
7369
8020
|
/**
|
|
@@ -8561,7 +9212,7 @@ var NOT_ENABLED5 = new Error(
|
|
|
8561
9212
|
'Multi-record transactions require the tx strategy. Import `{ withTransactions }` from "@noy-db/hub/tx" and pass it to `createNoydb({ txStrategy: withTransactions() })`.'
|
|
8562
9213
|
);
|
|
8563
9214
|
var NO_TX = {
|
|
8564
|
-
async runTransaction() {
|
|
9215
|
+
async runTransaction(_db, _fn, _options, _txInvariants) {
|
|
8565
9216
|
throw NOT_ENABLED5;
|
|
8566
9217
|
},
|
|
8567
9218
|
async runDryRun() {
|
|
@@ -8907,6 +9558,7 @@ var Noydb = class {
|
|
|
8907
9558
|
policyEnforcers = /* @__PURE__ */ new Map();
|
|
8908
9559
|
vaultTemplates = /* @__PURE__ */ new Map();
|
|
8909
9560
|
txStrategy;
|
|
9561
|
+
forgetStrategy;
|
|
8910
9562
|
sessionStrategy;
|
|
8911
9563
|
syncStrategy;
|
|
8912
9564
|
snapshotStrategy;
|
|
@@ -8934,6 +9586,7 @@ var Noydb = class {
|
|
|
8934
9586
|
constructor(options) {
|
|
8935
9587
|
this.options = options;
|
|
8936
9588
|
this.txStrategy = options.txStrategy ?? NO_TX;
|
|
9589
|
+
this.forgetStrategy = options.forgetStrategy ?? NO_FORGET;
|
|
8937
9590
|
this.sessionStrategy = options.sessionStrategy ?? NO_SESSION;
|
|
8938
9591
|
this.syncStrategy = options.syncStrategy ?? NO_SYNC;
|
|
8939
9592
|
this.snapshotStrategy = options.snapshotStrategy ?? NO_SNAPSHOTS;
|
|
@@ -8944,8 +9597,61 @@ var Noydb = class {
|
|
|
8944
9597
|
}
|
|
8945
9598
|
this.#registerGuardGate();
|
|
8946
9599
|
this.#registerPeriodGate();
|
|
9600
|
+
this.#registerForgetHooks();
|
|
8947
9601
|
this.resetSessionTimer();
|
|
8948
9602
|
}
|
|
9603
|
+
/** @internal — resolved forget strategy (NO_FORGET when not configured). */
|
|
9604
|
+
get _forgetStrategy() {
|
|
9605
|
+
return this.forgetStrategy;
|
|
9606
|
+
}
|
|
9607
|
+
// #304 — GDPR subject-index maintenance. When `withForgetCascade` declares
|
|
9608
|
+
// any subject fields, keep the encrypted `_subject_index` in lock-step with
|
|
9609
|
+
// writes so `vault.forget(subjectId)` can find every record for a subject.
|
|
9610
|
+
//
|
|
9611
|
+
// Two consumers are required because they cover disjoint events:
|
|
9612
|
+
// - onAfterWrite fires on create/update (NOT delete) — add the new ref;
|
|
9613
|
+
// on an update that changed the subject value, drop the stale ref.
|
|
9614
|
+
// - the subsystemBus `afterDelete` observer fires on delete (onAfterWrite
|
|
9615
|
+
// does NOT) — drop the ref so a deleted record never lingers in the
|
|
9616
|
+
// index (RISK #2). Without it, forget() would try to shred a ghost.
|
|
9617
|
+
#registerForgetHooks() {
|
|
9618
|
+
const subjects = this.forgetStrategy.subjects;
|
|
9619
|
+
if (Object.keys(subjects).length === 0) return;
|
|
9620
|
+
const subjectFieldFor = (collection) => subjects[collection];
|
|
9621
|
+
this.writeHooks.onAfterWrite(async (event) => {
|
|
9622
|
+
const field = subjectFieldFor(event.collection);
|
|
9623
|
+
if (field === void 0) return;
|
|
9624
|
+
const vault = this.vaultCache.get(event.vault);
|
|
9625
|
+
if (!vault) return;
|
|
9626
|
+
if (event.after !== null && typeof event.after === "object") {
|
|
9627
|
+
const subjectValue = readDottedPath(event.after, field);
|
|
9628
|
+
if (subjectValue !== void 0 && subjectValue !== null) {
|
|
9629
|
+
await vault._addSubjectRef(coerceSubjectId(subjectValue), { collection: event.collection, id: event.docId });
|
|
9630
|
+
}
|
|
9631
|
+
}
|
|
9632
|
+
if (event.op === "update" && event.before !== null && typeof event.before === "object") {
|
|
9633
|
+
const beforeValue = readDottedPath(event.before, field);
|
|
9634
|
+
const afterValue = event.after !== null && typeof event.after === "object" ? readDottedPath(event.after, field) : void 0;
|
|
9635
|
+
const beforeId = beforeValue === void 0 || beforeValue === null ? void 0 : coerceSubjectId(beforeValue);
|
|
9636
|
+
const afterId = afterValue === void 0 || afterValue === null ? void 0 : coerceSubjectId(afterValue);
|
|
9637
|
+
if (beforeId !== void 0 && beforeId !== afterId) {
|
|
9638
|
+
await vault._removeSubjectRef(beforeId, { collection: event.collection, id: event.docId });
|
|
9639
|
+
}
|
|
9640
|
+
}
|
|
9641
|
+
});
|
|
9642
|
+
this.subsystemBus.register("afterDelete", async (event) => {
|
|
9643
|
+
const field = subjectFieldFor(event.collection);
|
|
9644
|
+
if (field === void 0) return;
|
|
9645
|
+
const vault = this.vaultCache.get(event.vault);
|
|
9646
|
+
if (!vault) return;
|
|
9647
|
+
if (event.before !== null && typeof event.before === "object") {
|
|
9648
|
+
const subjectValue = readDottedPath(event.before, field);
|
|
9649
|
+
if (subjectValue !== void 0 && subjectValue !== null) {
|
|
9650
|
+
await vault._removeSubjectRef(coerceSubjectId(subjectValue), { collection: event.collection, id: event.docId });
|
|
9651
|
+
}
|
|
9652
|
+
}
|
|
9653
|
+
});
|
|
9654
|
+
}
|
|
8949
9655
|
// Track A — guards migration. Registers record-lock / field-freeze / onDelete
|
|
8950
9656
|
// / amendment-collect as gate-bus handlers (only when guards are opted in, so
|
|
8951
9657
|
// the write path is zero-cost otherwise). Resolves the live vault's
|
|
@@ -8973,7 +9679,7 @@ var Noydb = class {
|
|
|
8973
9679
|
if (!facade) return;
|
|
8974
9680
|
const ctx = { existing, vault: facade, userId: e.userId, role: e.role };
|
|
8975
9681
|
await registry.runChecks(e.collection, incoming, ctx);
|
|
8976
|
-
const { GuardExecutor } = await import("./executor-
|
|
9682
|
+
const { GuardExecutor } = await import("./executor-VDQQOR4F.js");
|
|
8977
9683
|
for (const g of guards) {
|
|
8978
9684
|
await GuardExecutor.checkFrozenFields(g, e.docId, existing, incoming, e.computedFieldNames);
|
|
8979
9685
|
}
|
|
@@ -9166,6 +9872,7 @@ var Noydb = class {
|
|
|
9166
9872
|
...this.options.syncStrategy !== void 0 ? { syncStrategy: this.options.syncStrategy } : {},
|
|
9167
9873
|
...this.options.guardStrategies !== void 0 ? { guardStrategies: this.options.guardStrategies } : {},
|
|
9168
9874
|
...this.options.numbering !== void 0 ? { numberingConfigs: this.options.numbering } : {},
|
|
9875
|
+
forgetStrategy: this.forgetStrategy,
|
|
9169
9876
|
locale: opts?.locale,
|
|
9170
9877
|
// Thread the translator hook so Collection.put() can invoke it
|
|
9171
9878
|
plaintextTranslator: this.options.plaintextTranslator ? (text, from, to, field, collection) => this.invokeTranslator(text, from, to, field, collection) : void 0,
|
|
@@ -9220,7 +9927,8 @@ var Noydb = class {
|
|
|
9220
9927
|
...this.options.i18nStrategy !== void 0 ? { i18nStrategy: this.options.i18nStrategy } : {},
|
|
9221
9928
|
...this.options.syncStrategy !== void 0 ? { syncStrategy: this.options.syncStrategy } : {},
|
|
9222
9929
|
...this.options.guardStrategies !== void 0 ? { guardStrategies: this.options.guardStrategies } : {},
|
|
9223
|
-
...this.options.numbering !== void 0 ? { numberingConfigs: this.options.numbering } : {}
|
|
9930
|
+
...this.options.numbering !== void 0 ? { numberingConfigs: this.options.numbering } : {},
|
|
9931
|
+
forgetStrategy: this.forgetStrategy
|
|
9224
9932
|
});
|
|
9225
9933
|
this.vaultCache.set(name, comp2);
|
|
9226
9934
|
return comp2;
|
|
@@ -9572,8 +10280,8 @@ var Noydb = class {
|
|
|
9572
10280
|
if (name === STATE_VAULT_NAME) throw new ReservedVaultNameError(name);
|
|
9573
10281
|
const template = this.vaultTemplates.get(opts.sharding.vaultTemplate);
|
|
9574
10282
|
if (!template) throw new VaultTemplateNotFoundError(opts.sharding.vaultTemplate);
|
|
9575
|
-
const { VaultGroup } = await import("./vault-group-
|
|
9576
|
-
const { StateManagementVault } = await import("./state-vault-
|
|
10283
|
+
const { VaultGroup } = await import("./vault-group-DHAHFX2A.js");
|
|
10284
|
+
const { StateManagementVault } = await import("./state-vault-W2OEABNO.js");
|
|
9577
10285
|
const stateVault = opts.registry ? void 0 : await StateManagementVault.open(this);
|
|
9578
10286
|
const registry = opts.registry ?? stateVault.registry;
|
|
9579
10287
|
const group = new VaultGroup(this, name, registry, opts.sharding, template);
|
|
@@ -9600,7 +10308,7 @@ var Noydb = class {
|
|
|
9600
10308
|
*/
|
|
9601
10309
|
async openStateManagementVault() {
|
|
9602
10310
|
if (this.closed) throw new ValidationError("Instance is closed");
|
|
9603
|
-
const { StateManagementVault } = await import("./state-vault-
|
|
10311
|
+
const { StateManagementVault } = await import("./state-vault-W2OEABNO.js");
|
|
9604
10312
|
return StateManagementVault.open(this);
|
|
9605
10313
|
}
|
|
9606
10314
|
/**
|
|
@@ -11102,6 +11810,7 @@ function normalizeSyncTargets(sync) {
|
|
|
11102
11810
|
|
|
11103
11811
|
export {
|
|
11104
11812
|
withArchive,
|
|
11813
|
+
resolveSequenceKey,
|
|
11105
11814
|
SequenceStore,
|
|
11106
11815
|
validateSchemaInput,
|
|
11107
11816
|
validateSchemaOutput,
|
|
@@ -11140,4 +11849,4 @@ export {
|
|
|
11140
11849
|
Noydb,
|
|
11141
11850
|
createNoydb
|
|
11142
11851
|
};
|
|
11143
|
-
//# sourceMappingURL=chunk-
|
|
11852
|
+
//# sourceMappingURL=chunk-D77ZQSQQ.js.map
|