@noy-db/hub 0.2.0-pre.16 → 0.2.0-pre.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +1087 -95
- 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-G4SCICH5.js → chunk-2FU2FTXD.js} +2 -2
- 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-L2BNJ6HM.js → chunk-7H2GEJ3O.js} +3 -3
- package/dist/{chunk-GNI5STXQ.js → chunk-AEIKD3PP.js} +52 -38
- package/dist/chunk-AEIKD3PP.js.map +1 -0
- package/dist/{chunk-QSUK7YWK.js → chunk-BH3X5L6A.js} +4 -4
- package/dist/{chunk-BQ65SS5A.js → chunk-BJSLBUJ7.js} +2 -2
- package/dist/{chunk-FFXM3ZIF.js → chunk-BL5GYANC.js} +3 -3
- package/dist/{chunk-6H2ZUNR7.js → chunk-BSZOCSDZ.js} +4 -4
- package/dist/{chunk-ZNQYHJXX.js → chunk-C3HYQPV4.js} +2 -2
- package/dist/{chunk-E2CDVKMH.js → chunk-CD2AVTEM.js} +5 -5
- package/dist/{chunk-667MB6AH.js → chunk-D77ZQSQQ.js} +769 -131
- package/dist/chunk-D77ZQSQQ.js.map +1 -0
- package/dist/{chunk-BR3AMFGS.js → chunk-DWEBTE2W.js} +5 -5
- package/dist/{chunk-Z4DO7YSI.js → chunk-DYYYUW5D.js} +2 -2
- package/dist/{chunk-SCJPI4Z5.js → chunk-E77UKJYL.js} +5 -5
- package/dist/{chunk-OMAMZKKD.js → chunk-F4G63NTZ.js} +2 -2
- package/dist/{chunk-TKIY625R.js → chunk-FEJDVE3Z.js} +2 -2
- package/dist/{chunk-7Z7KSVA5.js → chunk-GP3SDSH2.js} +2 -2
- package/dist/{chunk-IQLVUT37.js → chunk-H2MRGONI.js} +2 -2
- package/dist/{chunk-DUREQF5W.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-XL35NSEN.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-OQSRJG6A.js → chunk-PDULVIBY.js} +2 -2
- 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-DLZ2ONOD.js → chunk-QHM6XEAH.js} +6 -6
- package/dist/{chunk-HBXJ37ZY.js → chunk-QO6RGLLD.js} +4 -4
- package/dist/{chunk-7BQ4QWYX.js → chunk-ROPJVUG3.js} +23 -6
- package/dist/chunk-ROPJVUG3.js.map +1 -0
- package/dist/{chunk-42FEUPZQ.js → chunk-ROVO6NPJ.js} +2 -2
- package/dist/{chunk-6RR3MNMG.js → chunk-SHX5QBCI.js} +3 -3
- package/dist/{chunk-F3BPIPLS.js → chunk-SISBMAPO.js} +1 -1
- package/dist/chunk-SISBMAPO.js.map +1 -0
- package/dist/{chunk-3YWP3WBP.js → chunk-SNMJ7SB3.js} +5 -5
- package/dist/{chunk-IXBIFDEW.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-KABJXG2F.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-VLMPU56Q.js → chunk-XMHUK5PN.js} +2 -2
- package/dist/{chunk-BI6ETQPF.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-OB2ZJQ2D.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-QVIEAYTP.js → chunk-ZNGPEV5J.js} +3 -3
- 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.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-DR3upLd1.d.ts → dev-unlock-CI1ijTML.d.ts} +1 -1
- package/dist/{dev-unlock-8XzcD2Z4.d.cts → dev-unlock-iXbYFAWl.d.cts} +1 -1
- package/dist/{strategy-BtW8fAjz.d.ts → errors-Dz64FA65.d.cts} +98 -727
- package/dist/{strategy-BtW8fAjz.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-67CMI3UT.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.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-CDjye9KV.d.ts → hash-blk7Bkes.d.ts} +1 -1
- package/dist/{hash-DuQ88_5W.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-Dov3WvwF.d.ts → immutable-guard-B5M95nbq.d.ts} +1 -1
- package/dist/{immutable-guard-CRPvu24K.d.cts → immutable-guard-qN3zF8o1.d.cts} +1 -1
- package/dist/index-C-SSRIxP.d.cts +348 -0
- package/dist/index-C-SSRIxP.d.ts +348 -0
- package/dist/{index-nP99bXLg.d.ts → index-DpU6KWof.d.ts} +9 -1
- package/dist/{index-C8Bk3-VF.d.cts → index-u-kWzSrL.d.cts} +9 -1
- package/dist/index.cjs +7271 -6079
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -12
- package/dist/index.d.ts +15 -12
- package/dist/index.js +130 -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-A3LL253R.js → ledger-LFVLHE5H.js} +6 -6
- 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.map +1 -1
- package/dist/overlay-views/index.d.cts +7 -5
- package/dist/overlay-views/index.d.ts +7 -5
- 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-YP2UWMLG.js → public-envelope-RXZNP3V6.js} +4 -4
- package/dist/query/index.cjs +4 -1
- 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-UTA4CLQS.js → registry-SECUWSGY.js} +3 -3
- package/dist/registry-TGZISEWC.js +8 -0
- package/dist/{revoke-HNMQZSCL.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-DCMNKXSF.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-W5PQTRYH.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.map +1 -1
- package/dist/tx/index.d.cts +6 -4
- package/dist/tx/index.d.ts +6 -4
- package/dist/tx/index.js +3 -3
- package/dist/{types-DrmBTscX.d.ts → types-CljIHm_J.d.ts} +789 -500
- package/dist/{types-Bze6vkwm.d.cts → types-CrSpRDuG.d.cts} +789 -500
- package/dist/{ulid-DbBVrNSt.d.ts → ulid-CWfL2Vfv.d.ts} +1 -1
- package/dist/{ulid-DfZlAh0u.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-DX2HFQMX.js → vault-group-DHAHFX2A.js} +4 -4
- package/dist/{with-derivation-_lySGdlm.d.ts → with-derivation-BZ2y4bzF.d.ts} +1 -1
- package/dist/{with-derivation-CCqAchD5.d.cts → with-derivation-Bozs8DmD.d.cts} +1 -1
- package/dist/{with-materialized-view-QT1Tp7NO.d.ts → with-materialized-view-B892zYZV.d.ts} +1 -1
- package/dist/{with-materialized-view--4PsvMDu.d.cts → with-materialized-view-NzF71cG_.d.cts} +1 -1
- package/dist/{with-overlayed-view-BEXfpzSb.d.ts → with-overlayed-view-CR6m7CHe.d.ts} +1 -1
- package/dist/{with-overlayed-view-DlH5qmeB.d.cts → with-overlayed-view-UI8qSGL4.d.cts} +1 -1
- package/package.json +23 -3
- package/dist/chunk-535SSHBS.js.map +0 -1
- package/dist/chunk-667MB6AH.js.map +0 -1
- package/dist/chunk-7BQ4QWYX.js.map +0 -1
- package/dist/chunk-DUREQF5W.js.map +0 -1
- package/dist/chunk-F3BPIPLS.js.map +0 -1
- package/dist/chunk-GNI5STXQ.js.map +0 -1
- package/dist/chunk-HOR4R722.js.map +0 -1
- package/dist/chunk-KABJXG2F.js.map +0 -1
- package/dist/chunk-YULZKK4F.js.map +0 -1
- package/dist/executor-6ZDSDZ6V.js +0 -8
- package/dist/executor-AZLS3KBK.js +0 -11
- package/dist/executor-IDZDAFNH.js +0 -8
- package/dist/issue-RZP3VI6O.js +0 -12
- package/dist/noydb-WCMY2ZOW.js +0 -35
- package/dist/registry-EB6SISTA.js +0 -8
- package/dist/registry-IUZQVVBB.js +0 -8
- /package/dist/{chunk-G4SCICH5.js.map → chunk-2FU2FTXD.js.map} +0 -0
- /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-L2BNJ6HM.js.map → chunk-7H2GEJ3O.js.map} +0 -0
- /package/dist/{chunk-QSUK7YWK.js.map → chunk-BH3X5L6A.js.map} +0 -0
- /package/dist/{chunk-BQ65SS5A.js.map → chunk-BJSLBUJ7.js.map} +0 -0
- /package/dist/{chunk-FFXM3ZIF.js.map → chunk-BL5GYANC.js.map} +0 -0
- /package/dist/{chunk-6H2ZUNR7.js.map → chunk-BSZOCSDZ.js.map} +0 -0
- /package/dist/{chunk-ZNQYHJXX.js.map → chunk-C3HYQPV4.js.map} +0 -0
- /package/dist/{chunk-E2CDVKMH.js.map → chunk-CD2AVTEM.js.map} +0 -0
- /package/dist/{chunk-BR3AMFGS.js.map → chunk-DWEBTE2W.js.map} +0 -0
- /package/dist/{chunk-Z4DO7YSI.js.map → chunk-DYYYUW5D.js.map} +0 -0
- /package/dist/{chunk-SCJPI4Z5.js.map → chunk-E77UKJYL.js.map} +0 -0
- /package/dist/{chunk-OMAMZKKD.js.map → chunk-F4G63NTZ.js.map} +0 -0
- /package/dist/{chunk-TKIY625R.js.map → chunk-FEJDVE3Z.js.map} +0 -0
- /package/dist/{chunk-7Z7KSVA5.js.map → chunk-GP3SDSH2.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-XL35NSEN.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-OQSRJG6A.js.map → chunk-PDULVIBY.js.map} +0 -0
- /package/dist/{chunk-DLZ2ONOD.js.map → chunk-QHM6XEAH.js.map} +0 -0
- /package/dist/{chunk-HBXJ37ZY.js.map → chunk-QO6RGLLD.js.map} +0 -0
- /package/dist/{chunk-42FEUPZQ.js.map → chunk-ROVO6NPJ.js.map} +0 -0
- /package/dist/{chunk-6RR3MNMG.js.map → chunk-SHX5QBCI.js.map} +0 -0
- /package/dist/{chunk-3YWP3WBP.js.map → chunk-SNMJ7SB3.js.map} +0 -0
- /package/dist/{chunk-IXBIFDEW.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-VLMPU56Q.js.map → chunk-XMHUK5PN.js.map} +0 -0
- /package/dist/{chunk-BI6ETQPF.js.map → chunk-XMVHEWF6.js.map} +0 -0
- /package/dist/{chunk-OB2ZJQ2D.js.map → chunk-YYVZYTWW.js.map} +0 -0
- /package/dist/{chunk-QVIEAYTP.js.map → chunk-ZNGPEV5J.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-AZLS3KBK.js.map → executor-CFFWPWBJ.js.map} +0 -0
- /package/dist/{executor-IDZDAFNH.js.map → executor-VDQQOR4F.js.map} +0 -0
- /package/dist/{fanout-sidecar-67CMI3UT.js.map → fanout-sidecar-FIJJ46YG.js.map} +0 -0
- /package/dist/{issue-RZP3VI6O.js.map → forget/index.js.map} +0 -0
- /package/dist/{ledger-A3LL253R.js.map → issue-TTMGHQ2J.js.map} +0 -0
- /package/dist/{noydb-WCMY2ZOW.js.map → ledger-LFVLHE5H.js.map} +0 -0
- /package/dist/{public-envelope-YP2UWMLG.js.map → noydb-36S6GQNC.js.map} +0 -0
- /package/dist/{registry-EB6SISTA.js.map → public-envelope-RXZNP3V6.js.map} +0 -0
- /package/dist/{registry-IUZQVVBB.js.map → registry-3YFLZ7WD.js.map} +0 -0
- /package/dist/{registry-UTA4CLQS.js.map → registry-SECUWSGY.js.map} +0 -0
- /package/dist/{revoke-HNMQZSCL.js.map → registry-TGZISEWC.js.map} +0 -0
- /package/dist/{signer-DCMNKXSF.js.map → revoke-B54H2S2W.js.map} +0 -0
- /package/dist/{stale-W5PQTRYH.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-DX2HFQMX.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-DUREQF5W.js";
|
|
115
|
-
import {
|
|
116
|
-
canonicalGroupKey
|
|
117
|
-
} from "./chunk-L2BNJ6HM.js";
|
|
135
|
+
} from "./chunk-PDVP3C2I.js";
|
|
118
136
|
import {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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,7 +1821,7 @@ 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"));
|
|
1759
1825
|
}
|
|
1760
1826
|
let sourceWithId;
|
|
1761
1827
|
let sourceVersion = version;
|
|
@@ -1783,7 +1849,7 @@ var Collection = class {
|
|
|
1783
1849
|
const outputCollection = this.derivationSource.getCollection(outSpec.collection);
|
|
1784
1850
|
const txCtx = this.derivationSource.getActiveTxContext();
|
|
1785
1851
|
if (out.kind === "array") {
|
|
1786
|
-
const { loadFanoutSidecar, saveFanoutSidecar } = await import("./fanout-sidecar-
|
|
1852
|
+
const { loadFanoutSidecar, saveFanoutSidecar } = await import("./fanout-sidecar-FIJJ46YG.js");
|
|
1787
1853
|
const prior = await loadFanoutSidecar(
|
|
1788
1854
|
this.adapter,
|
|
1789
1855
|
this.vault,
|
|
@@ -1890,11 +1956,14 @@ var Collection = class {
|
|
|
1890
1956
|
let count = 0;
|
|
1891
1957
|
for (const id of ids) {
|
|
1892
1958
|
const env = await this.adapter.get(this.vault, this.name, id);
|
|
1893
|
-
if (!env) continue;
|
|
1894
|
-
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;
|
|
1895
1963
|
const next = transform(record);
|
|
1896
1964
|
const nextVersion = (env._v ?? 0) + 1;
|
|
1897
|
-
const
|
|
1965
|
+
const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
|
|
1966
|
+
const newEnv = await this.encryptRecord(next, nextVersion, cek);
|
|
1898
1967
|
await this.adapter.put(this.vault, this.name, id, newEnv);
|
|
1899
1968
|
await this._invalidateCacheEntry(id);
|
|
1900
1969
|
if (this.ledger) {
|
|
@@ -2006,14 +2075,17 @@ var Collection = class {
|
|
|
2006
2075
|
const previousEnvelope2 = await this.adapter.get(this.vault, this.name, id);
|
|
2007
2076
|
if (previousEnvelope2) {
|
|
2008
2077
|
const previousRecord = await this.decryptRecord(previousEnvelope2);
|
|
2009
|
-
|
|
2078
|
+
if (previousRecord !== null) {
|
|
2079
|
+
existing = { record: previousRecord, version: previousEnvelope2._v };
|
|
2080
|
+
}
|
|
2010
2081
|
}
|
|
2011
2082
|
}
|
|
2012
2083
|
} else {
|
|
2013
2084
|
existing = this.cache.get(id);
|
|
2014
2085
|
}
|
|
2015
2086
|
if (existing && this.historyConfig.enabled !== false) {
|
|
2016
|
-
const
|
|
2087
|
+
const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
|
|
2088
|
+
const historyEnvelope = await this.encryptRecord(existing.record, existing.version, cek);
|
|
2017
2089
|
await this.historyStrategy.saveHistory(this.adapter, this.vault, this.name, id, historyEnvelope);
|
|
2018
2090
|
}
|
|
2019
2091
|
const previousEnvelope = await this.adapter.get(this.vault, this.name, id);
|
|
@@ -2054,6 +2126,50 @@ var Collection = class {
|
|
|
2054
2126
|
await this.dispatchArrayDerivationsOnDelete(id);
|
|
2055
2127
|
}
|
|
2056
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
|
+
}
|
|
2057
2173
|
/**
|
|
2058
2174
|
* Cascade deletes of array-shape derived rows when a source row is
|
|
2059
2175
|
* deleted. Reads each registered strategy's fanout sidecar
|
|
@@ -2076,7 +2192,7 @@ var Collection = class {
|
|
|
2076
2192
|
for (const [outputKey, outSpec] of Object.entries(spec.outputs)) {
|
|
2077
2193
|
if (outSpec.shape !== "array") continue;
|
|
2078
2194
|
if (helpers === null) {
|
|
2079
|
-
helpers = await import("./fanout-sidecar-
|
|
2195
|
+
helpers = await import("./fanout-sidecar-FIJJ46YG.js");
|
|
2080
2196
|
}
|
|
2081
2197
|
const sidecar = await helpers.loadFanoutSidecar(
|
|
2082
2198
|
this.adapter,
|
|
@@ -2116,7 +2232,7 @@ var Collection = class {
|
|
|
2116
2232
|
if (mode === "eager") {
|
|
2117
2233
|
if (executor === null) {
|
|
2118
2234
|
;
|
|
2119
|
-
({ MaterializedViewExecutor: executor } = await import("./executor-
|
|
2235
|
+
({ MaterializedViewExecutor: executor } = await import("./executor-3W63Y44O.js"));
|
|
2120
2236
|
}
|
|
2121
2237
|
await executor.refresh(reg, {
|
|
2122
2238
|
getCollection: (name) => this.materializedViewSource.getCollection(name),
|
|
@@ -2125,7 +2241,7 @@ var Collection = class {
|
|
|
2125
2241
|
});
|
|
2126
2242
|
} else if (mode === "lazy") {
|
|
2127
2243
|
if (staleHelpers === null) {
|
|
2128
|
-
staleHelpers = await import("./stale-
|
|
2244
|
+
staleHelpers = await import("./stale-TOA36SRK.js");
|
|
2129
2245
|
}
|
|
2130
2246
|
staleHelpers.markMVStale(registry, reg.spec.name);
|
|
2131
2247
|
}
|
|
@@ -2148,7 +2264,7 @@ var Collection = class {
|
|
|
2148
2264
|
);
|
|
2149
2265
|
}
|
|
2150
2266
|
if (this.materializedViewSource !== void 0) {
|
|
2151
|
-
const { resolveStaleMVOnRead } = await import("./stale-
|
|
2267
|
+
const { resolveStaleMVOnRead } = await import("./stale-TOA36SRK.js");
|
|
2152
2268
|
await resolveStaleMVOnRead(this.materializedViewSource, this.name);
|
|
2153
2269
|
}
|
|
2154
2270
|
await this.ensureHydrated();
|
|
@@ -2466,6 +2582,7 @@ var Collection = class {
|
|
|
2466
2582
|
const entries = [];
|
|
2467
2583
|
for (const env of envelopes) {
|
|
2468
2584
|
const record = await this.decryptRecord(env, { skipValidation: true });
|
|
2585
|
+
if (record === null) continue;
|
|
2469
2586
|
entries.push({
|
|
2470
2587
|
version: env._v,
|
|
2471
2588
|
timestamp: env._ts,
|
|
@@ -2602,6 +2719,7 @@ var Collection = class {
|
|
|
2602
2719
|
const envelope = await this.adapter.get(this.vault, this.name, id);
|
|
2603
2720
|
if (envelope) {
|
|
2604
2721
|
const record = await this.decryptRecord(envelope);
|
|
2722
|
+
if (record === null) continue;
|
|
2605
2723
|
items.push(record);
|
|
2606
2724
|
if (!this.lazy && !this.cache.has(id)) {
|
|
2607
2725
|
this.cache.set(id, { record, version: envelope._v });
|
|
@@ -2678,6 +2796,7 @@ var Collection = class {
|
|
|
2678
2796
|
const out = [];
|
|
2679
2797
|
for (const { id, envelope } of items) {
|
|
2680
2798
|
const record = await this.decryptRecord(envelope);
|
|
2799
|
+
if (record === null) continue;
|
|
2681
2800
|
out.push({ id, record, version: envelope._v });
|
|
2682
2801
|
}
|
|
2683
2802
|
return out;
|
|
@@ -2699,6 +2818,18 @@ var Collection = class {
|
|
|
2699
2818
|
* the cache entry (record still present) or deletes it (record was
|
|
2700
2819
|
* gone before the tx and the revert deleted it again).
|
|
2701
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
|
+
}
|
|
2702
2833
|
async _invalidateCacheEntry(id) {
|
|
2703
2834
|
if (this.lazy && this.lru) {
|
|
2704
2835
|
this.lru.remove(id);
|
|
@@ -2716,6 +2847,14 @@ var Collection = class {
|
|
|
2716
2847
|
return;
|
|
2717
2848
|
}
|
|
2718
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
|
+
}
|
|
2719
2858
|
this.cache.set(id, { record, version: envelope._v });
|
|
2720
2859
|
this.indexes?.upsert(id, record, previous ? previous.record : null);
|
|
2721
2860
|
this.uniqueConstraints?.upsert(id, record, previous?.record);
|
|
@@ -2740,8 +2879,9 @@ var Collection = class {
|
|
|
2740
2879
|
const ids = await this.adapter.list(this.vault, this.name);
|
|
2741
2880
|
for (const id of ids) {
|
|
2742
2881
|
const envelope = await this.adapter.get(this.vault, this.name, id);
|
|
2743
|
-
if (envelope) {
|
|
2744
|
-
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;
|
|
2745
2885
|
this.cache.set(id, { record, version: envelope._v });
|
|
2746
2886
|
}
|
|
2747
2887
|
}
|
|
@@ -2752,7 +2892,9 @@ var Collection = class {
|
|
|
2752
2892
|
/** Hydrate from a pre-loaded snapshot (used by Vault). */
|
|
2753
2893
|
async hydrateFromSnapshot(records) {
|
|
2754
2894
|
for (const [id, envelope] of Object.entries(records)) {
|
|
2755
|
-
|
|
2895
|
+
if (isTombstone(envelope, this.encrypted)) continue;
|
|
2896
|
+
const record = await this.decryptRecord(envelope, { id });
|
|
2897
|
+
if (record === null) continue;
|
|
2756
2898
|
this.cache.set(id, { record, version: envelope._v });
|
|
2757
2899
|
}
|
|
2758
2900
|
this.hydrated = true;
|
|
@@ -2840,6 +2982,7 @@ var Collection = class {
|
|
|
2840
2982
|
const envelope = await this.adapter.get(this.vault, this.name, recordId);
|
|
2841
2983
|
if (!envelope) continue;
|
|
2842
2984
|
const record = await this.decryptRecord(envelope, { skipValidation: true });
|
|
2985
|
+
if (record === null) continue;
|
|
2843
2986
|
await this.maintainPersistedIndexesOnPut(recordId, record, null, envelope._v);
|
|
2844
2987
|
}
|
|
2845
2988
|
this.persistedIndexesLoaded = true;
|
|
@@ -2890,8 +3033,13 @@ var Collection = class {
|
|
|
2890
3033
|
const env = await this.adapter.get(this.vault, this.name, id);
|
|
2891
3034
|
if (!env) continue;
|
|
2892
3035
|
try {
|
|
2893
|
-
const
|
|
2894
|
-
|
|
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
|
+
}
|
|
2895
3043
|
} catch {
|
|
2896
3044
|
sidecar.set(decoded.recordId, void 0);
|
|
2897
3045
|
}
|
|
@@ -2905,6 +3053,7 @@ var Collection = class {
|
|
|
2905
3053
|
const env = await this.adapter.get(this.vault, this.name, id);
|
|
2906
3054
|
if (!env) continue;
|
|
2907
3055
|
const record = await this.decryptRecord(env, { skipValidation: true });
|
|
3056
|
+
if (record === null) continue;
|
|
2908
3057
|
const live = readPersistedValue(record, field);
|
|
2909
3058
|
const stored = sidecar.get(id);
|
|
2910
3059
|
const hasSidecar = sidecarIds.has(id);
|
|
@@ -2987,7 +3136,8 @@ var Collection = class {
|
|
|
2987
3136
|
recordId: id,
|
|
2988
3137
|
getDEK: this.getDEK,
|
|
2989
3138
|
encrypted: this.encrypted,
|
|
2990
|
-
userId: this.keyring.userId
|
|
3139
|
+
userId: this.keyring.userId,
|
|
3140
|
+
erasableBlobs: this.perRecordCek
|
|
2991
3141
|
});
|
|
2992
3142
|
}
|
|
2993
3143
|
/** Get all records as encrypted envelopes (for dump). */
|
|
@@ -2995,7 +3145,8 @@ var Collection = class {
|
|
|
2995
3145
|
await this.ensureHydrated();
|
|
2996
3146
|
const result = {};
|
|
2997
3147
|
for (const [id, entry] of this.cache) {
|
|
2998
|
-
|
|
3148
|
+
const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
|
|
3149
|
+
result[id] = await this.encryptRecord(entry.record, entry.version, cek);
|
|
2999
3150
|
}
|
|
3000
3151
|
return result;
|
|
3001
3152
|
}
|
|
@@ -3023,8 +3174,11 @@ var Collection = class {
|
|
|
3023
3174
|
if (hasMoney && this.moneyFields) {
|
|
3024
3175
|
result = decodeMoneyFields(result, this.moneyFields, typeof locale === "string" ? locale : void 0);
|
|
3025
3176
|
}
|
|
3026
|
-
|
|
3027
|
-
|
|
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) {
|
|
3028
3182
|
result = this.i18nStrategy.applyI18nLocale(result, this.i18nFields, locale, localeOpts?.fallback);
|
|
3029
3183
|
}
|
|
3030
3184
|
if (hasDict && this.dictKeyFields && this.dictLabelResolver && locale !== "raw") {
|
|
@@ -3033,13 +3187,23 @@ var Collection = class {
|
|
|
3033
3187
|
for (const [field, desc] of Object.entries(this.dictKeyFields)) {
|
|
3034
3188
|
const policy = desc.onMissing ? resolvePolicy(desc.onMissing, "read") : "null";
|
|
3035
3189
|
const fallback = policy === "substitute" ? localeOpts?.fallback ?? desc.substitute : localeOpts?.fallback;
|
|
3190
|
+
const effLocale = locale ?? (isStaticDictDescriptor(desc) ? desc.displayLocale : void 0);
|
|
3036
3191
|
const resolveKey = async (key) => {
|
|
3037
|
-
|
|
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);
|
|
3038
3202
|
if (label === void 0) {
|
|
3039
3203
|
if (policy === "throw") {
|
|
3040
3204
|
throw new LocaleNotSpecifiedError(
|
|
3041
3205
|
field,
|
|
3042
|
-
`dictKey "${field}": no label for key "${key}" in locale "${
|
|
3206
|
+
`dictKey "${field}": no label for key "${key}" in locale "${effLocale}".`
|
|
3043
3207
|
);
|
|
3044
3208
|
}
|
|
3045
3209
|
return null;
|
|
@@ -3196,6 +3360,7 @@ var Collection = class {
|
|
|
3196
3360
|
if (!envelope) continue;
|
|
3197
3361
|
try {
|
|
3198
3362
|
const json = await this.decryptJsonString(envelope);
|
|
3363
|
+
if (json === null) continue;
|
|
3199
3364
|
const body = JSON.parse(json);
|
|
3200
3365
|
if (typeof body.recordId !== "string") continue;
|
|
3201
3366
|
const rows = byField.get(decoded.field) ?? [];
|
|
@@ -3305,7 +3470,31 @@ var Collection = class {
|
|
|
3305
3470
|
};
|
|
3306
3471
|
return new LazyQuery(source);
|
|
3307
3472
|
}
|
|
3308
|
-
|
|
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) {
|
|
3309
3498
|
const by = this.keyring.userId;
|
|
3310
3499
|
if (!this.encrypted) {
|
|
3311
3500
|
return {
|
|
@@ -3318,6 +3507,19 @@ var Collection = class {
|
|
|
3318
3507
|
};
|
|
3319
3508
|
}
|
|
3320
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
|
+
}
|
|
3321
3523
|
const { iv, data } = await encrypt(json, dek);
|
|
3322
3524
|
return {
|
|
3323
3525
|
_noydb: NOYDB_FORMAT_VERSION,
|
|
@@ -3328,8 +3530,8 @@ var Collection = class {
|
|
|
3328
3530
|
_by: by
|
|
3329
3531
|
};
|
|
3330
3532
|
}
|
|
3331
|
-
async encryptRecord(record, version) {
|
|
3332
|
-
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);
|
|
3333
3535
|
if (!this.deterministicFields || !this.encrypted) return base;
|
|
3334
3536
|
const dek = await this.getDEK(this.name);
|
|
3335
3537
|
const rec = record;
|
|
@@ -3407,7 +3609,8 @@ var Collection = class {
|
|
|
3407
3609
|
const env = await this.adapter.get(this.vault, this.name, id);
|
|
3408
3610
|
if (!env || !env._det) continue;
|
|
3409
3611
|
if (env._det[field] === target) {
|
|
3410
|
-
|
|
3612
|
+
const rec = await this.decryptRecord(env);
|
|
3613
|
+
if (rec !== null) matches.push(rec);
|
|
3411
3614
|
}
|
|
3412
3615
|
}
|
|
3413
3616
|
return matches;
|
|
@@ -3508,7 +3711,14 @@ var Collection = class {
|
|
|
3508
3711
|
return null;
|
|
3509
3712
|
}
|
|
3510
3713
|
const dek = await this.getDEK(key);
|
|
3511
|
-
|
|
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
|
+
}
|
|
3512
3722
|
const record = JSON.parse(plaintext);
|
|
3513
3723
|
this.emitCrossTierEvent({
|
|
3514
3724
|
actor: this.keyring.userId,
|
|
@@ -3564,18 +3774,19 @@ var Collection = class {
|
|
|
3564
3774
|
const toKey = dekKey(this.name, toTier);
|
|
3565
3775
|
const fromDek = await this.getDEK(fromKey);
|
|
3566
3776
|
const toDek = await this.getDEK(toKey);
|
|
3567
|
-
const plaintext = await decrypt(envelope._iv, envelope._data, fromDek);
|
|
3568
|
-
const { iv, data } = await encrypt(plaintext, toDek);
|
|
3569
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);
|
|
3570
3780
|
const next = {
|
|
3571
3781
|
_noydb: NOYDB_FORMAT_VERSION,
|
|
3572
3782
|
_v: envelope._v + 1,
|
|
3573
3783
|
_ts: now,
|
|
3574
|
-
_iv:
|
|
3575
|
-
_data:
|
|
3784
|
+
_iv: body._iv,
|
|
3785
|
+
_data: body._data,
|
|
3576
3786
|
_by: this.keyring.userId,
|
|
3577
3787
|
_tier: toTier,
|
|
3578
|
-
_elevatedBy: this.keyring.userId
|
|
3788
|
+
_elevatedBy: this.keyring.userId,
|
|
3789
|
+
...body._cek !== void 0 ? { _cek: body._cek } : {}
|
|
3579
3790
|
};
|
|
3580
3791
|
await this.adapter.put(this.vault, this.name, id, next);
|
|
3581
3792
|
this.emitCrossTierEvent({
|
|
@@ -3611,17 +3822,18 @@ var Collection = class {
|
|
|
3611
3822
|
if (toTier > 0) this.assertDeclaredTier(toTier);
|
|
3612
3823
|
const fromDek = await this.getDEK(dekKey(this.name, fromTier));
|
|
3613
3824
|
const toDek = await this.getDEK(dekKey(this.name, toTier));
|
|
3614
|
-
const plaintext = await decrypt(envelope._iv, envelope._data, fromDek);
|
|
3615
|
-
const { iv, data } = await encrypt(plaintext, toDek);
|
|
3616
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);
|
|
3617
3828
|
const next = {
|
|
3618
3829
|
_noydb: NOYDB_FORMAT_VERSION,
|
|
3619
3830
|
_v: envelope._v + 1,
|
|
3620
3831
|
_ts: now,
|
|
3621
|
-
_iv:
|
|
3622
|
-
_data:
|
|
3832
|
+
_iv: body._iv,
|
|
3833
|
+
_data: body._data,
|
|
3623
3834
|
_by: this.keyring.userId,
|
|
3624
|
-
...toTier > 0 && { _tier: toTier }
|
|
3835
|
+
...toTier > 0 && { _tier: toTier },
|
|
3836
|
+
...body._cek !== void 0 ? { _cek: body._cek } : {}
|
|
3625
3837
|
};
|
|
3626
3838
|
await this.adapter.put(this.vault, this.name, id, next);
|
|
3627
3839
|
this.emitCrossTierEvent({
|
|
@@ -3643,10 +3855,30 @@ var Collection = class {
|
|
|
3643
3855
|
} catch {
|
|
3644
3856
|
}
|
|
3645
3857
|
}
|
|
3646
|
-
/**
|
|
3647
|
-
|
|
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;
|
|
3648
3874
|
if (!this.encrypted) return envelope._data;
|
|
3649
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
|
+
}
|
|
3650
3882
|
return decrypt(envelope._iv, envelope._data, dek);
|
|
3651
3883
|
}
|
|
3652
3884
|
/**
|
|
@@ -3665,7 +3897,8 @@ var Collection = class {
|
|
|
3665
3897
|
* false positive. Every non-history read leaves this flag `false`.
|
|
3666
3898
|
*/
|
|
3667
3899
|
async decryptRecord(envelope, opts = {}) {
|
|
3668
|
-
const json = await this.decryptJsonString(envelope);
|
|
3900
|
+
const json = await this.decryptJsonString(envelope, opts.id);
|
|
3901
|
+
if (json === null) return null;
|
|
3669
3902
|
let parsed = JSON.parse(json);
|
|
3670
3903
|
if (this.crdtMode && parsed !== null && typeof parsed === "object" && "_crdt" in parsed) {
|
|
3671
3904
|
parsed = this.crdtStrategy.resolveCrdtSnapshot(parsed);
|
|
@@ -5149,6 +5382,19 @@ function summariseAggregateOp(value) {
|
|
|
5149
5382
|
}
|
|
5150
5383
|
|
|
5151
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
|
+
}
|
|
5152
5398
|
var Vault = class {
|
|
5153
5399
|
adapter;
|
|
5154
5400
|
/** The vault's name as passed to `openVault()`. Stable for the instance lifetime. */
|
|
@@ -5192,6 +5438,7 @@ var Vault = class {
|
|
|
5192
5438
|
periodsStrategy;
|
|
5193
5439
|
shadowStrategy;
|
|
5194
5440
|
historyStrategy;
|
|
5441
|
+
forgetStrategy;
|
|
5195
5442
|
i18nStrategy;
|
|
5196
5443
|
syncStrategy;
|
|
5197
5444
|
/**
|
|
@@ -5358,6 +5605,27 @@ var Vault = class {
|
|
|
5358
5605
|
* Populated by `collection()` when the `dictKeyFields` option is passed.
|
|
5359
5606
|
*/
|
|
5360
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();
|
|
5361
5629
|
/**
|
|
5362
5630
|
* Registry of i18nText fields declared across all collections. Keyed
|
|
5363
5631
|
* by collection name → field name → I18nTextDescriptor. Used by
|
|
@@ -5404,6 +5672,7 @@ var Vault = class {
|
|
|
5404
5672
|
this.periodsStrategy = opts.periodsStrategy ?? NO_PERIODS;
|
|
5405
5673
|
this.shadowStrategy = opts.shadowStrategy ?? NO_SHADOW;
|
|
5406
5674
|
this.historyStrategy = opts.historyStrategy ?? NO_HISTORY;
|
|
5675
|
+
this.forgetStrategy = opts.forgetStrategy ?? NO_FORGET;
|
|
5407
5676
|
this.i18nStrategy = opts.i18nStrategy ?? NO_I18N;
|
|
5408
5677
|
this.syncStrategy = opts.syncStrategy ?? NO_SYNC;
|
|
5409
5678
|
void opts.guardStrategies;
|
|
@@ -5508,10 +5777,22 @@ var Vault = class {
|
|
|
5508
5777
|
}
|
|
5509
5778
|
if (options?.dictKeyFields) {
|
|
5510
5779
|
const dictFieldMap = {};
|
|
5780
|
+
const staticFieldMap = {};
|
|
5511
5781
|
for (const [field, desc] of Object.entries(options.dictKeyFields)) {
|
|
5512
|
-
|
|
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);
|
|
5513
5795
|
}
|
|
5514
|
-
this.dictKeyFieldRegistry.set(collectionName, dictFieldMap);
|
|
5515
5796
|
}
|
|
5516
5797
|
if ((options?.schemaUpdate?.length ?? 0) > 0) {
|
|
5517
5798
|
this.#schemaUpdateNames.set(collectionName, (options.schemaUpdate ?? []).map((s) => s.name));
|
|
@@ -5613,6 +5894,17 @@ var Vault = class {
|
|
|
5613
5894
|
if (options?.acknowledgeDeterministicRisk !== void 0) {
|
|
5614
5895
|
collOpts.acknowledgeDeterministicRisk = options.acknowledgeDeterministicRisk;
|
|
5615
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
|
+
}
|
|
5616
5908
|
if (options?.tiers !== void 0) collOpts.tiers = options.tiers;
|
|
5617
5909
|
if (options?.tierMode !== void 0) collOpts.tierMode = options.tierMode;
|
|
5618
5910
|
collOpts.onCrossTierAccess = (event) => this.emitCrossTier(event);
|
|
@@ -5622,6 +5914,11 @@ var Vault = class {
|
|
|
5622
5914
|
if (options?.computed !== void 0) collOpts.computed = options.computed;
|
|
5623
5915
|
if (options?.dictKeyFields !== void 0) {
|
|
5624
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
|
+
}
|
|
5625
5922
|
const handle = this.dictionary(dictName);
|
|
5626
5923
|
return handle.resolveLabel(key, locale, fallback);
|
|
5627
5924
|
};
|
|
@@ -5630,6 +5927,7 @@ var Vault = class {
|
|
|
5630
5927
|
if (options?.i18nFields !== void 0 || options?.dictKeyFields !== void 0) {
|
|
5631
5928
|
collOpts.i18nPutValidator = (record) => {
|
|
5632
5929
|
this.enforceI18nOnPut(collectionName, record);
|
|
5930
|
+
this.enforceStaticDictOnPut(collectionName, record);
|
|
5633
5931
|
};
|
|
5634
5932
|
}
|
|
5635
5933
|
if (options?.i18nFields !== void 0 && this.translateText) {
|
|
@@ -5769,6 +6067,34 @@ var Vault = class {
|
|
|
5769
6067
|
}
|
|
5770
6068
|
}
|
|
5771
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
|
+
}
|
|
5772
6098
|
/**
|
|
5773
6099
|
* Apply locale resolution to a record for the given collection.
|
|
5774
6100
|
*
|
|
@@ -5777,14 +6103,18 @@ var Vault = class {
|
|
|
5777
6103
|
*/
|
|
5778
6104
|
async applyLocale(collectionName, record, localeOpts) {
|
|
5779
6105
|
const locale = localeOpts.locale ?? this.locale;
|
|
5780
|
-
|
|
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;
|
|
5781
6109
|
let result = record;
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
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
|
+
}
|
|
5785
6115
|
}
|
|
5786
6116
|
const dictFields = this.dictKeyFieldRegistry.get(collectionName);
|
|
5787
|
-
if (dictFields && Object.keys(dictFields).length > 0 && locale !== "raw") {
|
|
6117
|
+
if (locale && dictFields && Object.keys(dictFields).length > 0 && locale !== "raw") {
|
|
5788
6118
|
const withLabels = { ...result };
|
|
5789
6119
|
for (const [field, dictName] of Object.entries(dictFields)) {
|
|
5790
6120
|
const key = result[field];
|
|
@@ -5797,6 +6127,22 @@ var Vault = class {
|
|
|
5797
6127
|
}
|
|
5798
6128
|
result = withLabels;
|
|
5799
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
|
+
}
|
|
5800
6146
|
return result;
|
|
5801
6147
|
}
|
|
5802
6148
|
/**
|
|
@@ -5818,6 +6164,9 @@ var Vault = class {
|
|
|
5818
6164
|
* ```
|
|
5819
6165
|
*/
|
|
5820
6166
|
dictionary(name, options = {}) {
|
|
6167
|
+
if (this.staticDictNames.has(name)) {
|
|
6168
|
+
throw new StaticDictReadonlyError(name);
|
|
6169
|
+
}
|
|
5821
6170
|
let handle = this.dictionaryCache.get(name);
|
|
5822
6171
|
if (!handle) {
|
|
5823
6172
|
handle = this.i18nStrategy.buildDictionaryHandle({
|
|
@@ -5887,6 +6236,26 @@ var Vault = class {
|
|
|
5887
6236
|
* Returns `null` when `field` is not a dictKey in `leftCollection`.
|
|
5888
6237
|
*/
|
|
5889
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
|
+
}
|
|
5890
6259
|
const dictFields = this.dictKeyFieldRegistry.get(leftCollection);
|
|
5891
6260
|
if (!dictFields || !(field in dictFields)) return null;
|
|
5892
6261
|
const dictName = dictFields[field];
|
|
@@ -6197,12 +6566,12 @@ var Vault = class {
|
|
|
6197
6566
|
if (!fieldSchema) {
|
|
6198
6567
|
throw new AttestationError(`issueAttestation: collection '${collectionName}' has no attestation field-schema. Declare it via vault.collection('${collectionName}', { attestation: { fields: [...] } }).`);
|
|
6199
6568
|
}
|
|
6200
|
-
const { issueAttestationCore } = await import("./issue-
|
|
6569
|
+
const { issueAttestationCore } = await import("./issue-TTMGHQ2J.js");
|
|
6201
6570
|
const out = await issueAttestationCore(this.makeIssueContext(), { collection: collectionName, id, fieldSchema });
|
|
6202
6571
|
return { docId: out.docId, qr: out.qr, keyId: out.keyId, publicKeyB64: out.publicKeyB64 };
|
|
6203
6572
|
}
|
|
6204
6573
|
async getDocumentSigningPublicKey() {
|
|
6205
|
-
const { loadSigner, loadOrCreateSigner } = await import("./signer-
|
|
6574
|
+
const { loadSigner, loadOrCreateSigner } = await import("./signer-YSXZT574.js");
|
|
6206
6575
|
const existing = await loadSigner(this.adapter, this.name, this.getDEK);
|
|
6207
6576
|
if (existing) return { keyId: existing.keyId, publicKeyB64: existing.publicKeyB64 };
|
|
6208
6577
|
if (this.keyring.role !== "owner") {
|
|
@@ -6228,19 +6597,19 @@ var Vault = class {
|
|
|
6228
6597
|
};
|
|
6229
6598
|
}
|
|
6230
6599
|
async revokeAttestation(docId) {
|
|
6231
|
-
const { revokeDocCore } = await import("./revoke-
|
|
6600
|
+
const { revokeDocCore } = await import("./revoke-B54H2S2W.js");
|
|
6232
6601
|
await revokeDocCore(this.makeRevokeContext(), docId);
|
|
6233
6602
|
}
|
|
6234
6603
|
async unrevokeAttestation(docId) {
|
|
6235
|
-
const { unrevokeDocCore } = await import("./revoke-
|
|
6604
|
+
const { unrevokeDocCore } = await import("./revoke-B54H2S2W.js");
|
|
6236
6605
|
await unrevokeDocCore(this.makeRevokeContext(), docId);
|
|
6237
6606
|
}
|
|
6238
6607
|
async getRevokedDocIds() {
|
|
6239
|
-
const { getRevokedDocIdsCore } = await import("./revoke-
|
|
6608
|
+
const { getRevokedDocIdsCore } = await import("./revoke-B54H2S2W.js");
|
|
6240
6609
|
return getRevokedDocIdsCore(this.makeRevokeContext());
|
|
6241
6610
|
}
|
|
6242
6611
|
async publishRevocationList() {
|
|
6243
|
-
const { publishRevocationListCore } = await import("./revoke-
|
|
6612
|
+
const { publishRevocationListCore } = await import("./revoke-B54H2S2W.js");
|
|
6244
6613
|
return publishRevocationListCore(this.makeRevokeContext());
|
|
6245
6614
|
}
|
|
6246
6615
|
makeRevokeContext() {
|
|
@@ -6530,6 +6899,218 @@ var Vault = class {
|
|
|
6530
6899
|
}
|
|
6531
6900
|
return this.ledgerStore;
|
|
6532
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
|
+
}
|
|
6533
7114
|
/**
|
|
6534
7115
|
* @internal — called by `Noydb.openVault` after construction.
|
|
6535
7116
|
* Dynamic-imports `GuardRegistry` + `ReadOnlyVaultFacade` and seeds
|
|
@@ -6572,7 +7153,7 @@ var Vault = class {
|
|
|
6572
7153
|
async _initDerivations(handles) {
|
|
6573
7154
|
if (handles.length === 0) return;
|
|
6574
7155
|
const [{ DerivationRegistry }, { ReadOnlyVaultFacade }] = await Promise.all([
|
|
6575
|
-
import("./registry-
|
|
7156
|
+
import("./registry-TGZISEWC.js"),
|
|
6576
7157
|
import("./read-only-facade-ITU6L7BL.js")
|
|
6577
7158
|
]);
|
|
6578
7159
|
const registry = new DerivationRegistry();
|
|
@@ -6603,7 +7184,7 @@ var Vault = class {
|
|
|
6603
7184
|
*/
|
|
6604
7185
|
async _initMaterializedViews(handles) {
|
|
6605
7186
|
if (handles.length === 0) return;
|
|
6606
|
-
const { MaterializedViewRegistry } = await import("./registry-
|
|
7187
|
+
const { MaterializedViewRegistry } = await import("./registry-SECUWSGY.js");
|
|
6607
7188
|
const registry = new MaterializedViewRegistry();
|
|
6608
7189
|
this.materializedViewRegistry = registry;
|
|
6609
7190
|
const db = this;
|
|
@@ -6627,7 +7208,7 @@ var Vault = class {
|
|
|
6627
7208
|
*/
|
|
6628
7209
|
async _initOverlayedViews(handles) {
|
|
6629
7210
|
if (handles.length === 0) return;
|
|
6630
|
-
const { OverlayedViewRegistry } = await import("./registry-
|
|
7211
|
+
const { OverlayedViewRegistry } = await import("./registry-3YFLZ7WD.js");
|
|
6631
7212
|
const registry = new OverlayedViewRegistry();
|
|
6632
7213
|
const mvRegistry = this.materializedViewRegistry;
|
|
6633
7214
|
const overlayNames = /* @__PURE__ */ new Set();
|
|
@@ -6674,13 +7255,13 @@ var Vault = class {
|
|
|
6674
7255
|
if (!reg) {
|
|
6675
7256
|
throw new Error(`refreshView: no MV registered with name "${name}"`);
|
|
6676
7257
|
}
|
|
6677
|
-
const { MaterializedViewExecutor } = await import("./executor-
|
|
7258
|
+
const { MaterializedViewExecutor } = await import("./executor-3W63Y44O.js");
|
|
6678
7259
|
const result = await MaterializedViewExecutor.refresh(reg, {
|
|
6679
7260
|
getCollection: (n) => this.collection(n),
|
|
6680
7261
|
getActiveTxContext: () => this.noydb._activeTxContextOrNull,
|
|
6681
7262
|
getQueryContext: () => this
|
|
6682
7263
|
});
|
|
6683
|
-
const { clearMVStale } = await import("./stale-
|
|
7264
|
+
const { clearMVStale } = await import("./stale-TOA36SRK.js");
|
|
6684
7265
|
clearMVStale(registry, name);
|
|
6685
7266
|
return result;
|
|
6686
7267
|
}
|
|
@@ -6696,7 +7277,7 @@ var Vault = class {
|
|
|
6696
7277
|
if (registry === null) return { derived: 0, failed: 0 };
|
|
6697
7278
|
const strategies = registry.strategiesForSource(sourceCollection);
|
|
6698
7279
|
if (strategies.length === 0) return { derived: 0, failed: 0 };
|
|
6699
|
-
const { DerivationExecutor } = await import("./executor-
|
|
7280
|
+
const { DerivationExecutor } = await import("./executor-CFFWPWBJ.js");
|
|
6700
7281
|
const sourceColl = this.collection(sourceCollection);
|
|
6701
7282
|
const records = await sourceColl.list();
|
|
6702
7283
|
const ctx = { vault: this.readOnlyFacade ?? new (await import("./read-only-facade-ITU6L7BL.js")).ReadOnlyVaultFacade(this) };
|
|
@@ -6721,7 +7302,7 @@ var Vault = class {
|
|
|
6721
7302
|
if (!outSpec) continue;
|
|
6722
7303
|
const outputColl = this.collection(outSpec.collection);
|
|
6723
7304
|
if (out.kind === "array") {
|
|
6724
|
-
const { loadFanoutSidecar, saveFanoutSidecar } = await import("./fanout-sidecar-
|
|
7305
|
+
const { loadFanoutSidecar, saveFanoutSidecar } = await import("./fanout-sidecar-FIJJ46YG.js");
|
|
6725
7306
|
const prior = await loadFanoutSidecar(this.adapter, this.name, spec.source, id, key);
|
|
6726
7307
|
const prevKeys = new Set(prior?.keys ?? []);
|
|
6727
7308
|
const newKeysList = out.entries.map((e) => e.key);
|
|
@@ -6942,7 +7523,7 @@ var Vault = class {
|
|
|
6942
7523
|
* collection.
|
|
6943
7524
|
*/
|
|
6944
7525
|
async delegate(opts) {
|
|
6945
|
-
const { issueDelegation, DELEGATIONS_COLLECTION } = await import("./delegation-
|
|
7526
|
+
const { issueDelegation, DELEGATIONS_COLLECTION } = await import("./delegation-MGH5SODX.js");
|
|
6946
7527
|
if (!this.keyring.kek) {
|
|
6947
7528
|
throw new ValidationError(
|
|
6948
7529
|
"issueDelegation: keyring.kek is null \u2014 issuing a delegation requires a tier-1 unlock. Re-authenticate at tier 1 (passphrase) first."
|
|
@@ -6964,7 +7545,7 @@ var Vault = class {
|
|
|
6964
7545
|
* if the id does not exist.
|
|
6965
7546
|
*/
|
|
6966
7547
|
async revokeDelegation(id) {
|
|
6967
|
-
const { revokeDelegation, DELEGATIONS_COLLECTION } = await import("./delegation-
|
|
7548
|
+
const { revokeDelegation, DELEGATIONS_COLLECTION } = await import("./delegation-MGH5SODX.js");
|
|
6968
7549
|
await revokeDelegation(this.adapter, this.name, id);
|
|
6969
7550
|
void DELEGATIONS_COLLECTION;
|
|
6970
7551
|
}
|
|
@@ -7433,7 +8014,7 @@ var Vault = class {
|
|
|
7433
8014
|
* @see docs/subsystems/public-envelope.md
|
|
7434
8015
|
*/
|
|
7435
8016
|
async getPublicEnvelope(opts = {}) {
|
|
7436
|
-
const { readPublicEnvelope: readPublicEnvelope2 } = await import("./public-envelope-
|
|
8017
|
+
const { readPublicEnvelope: readPublicEnvelope2 } = await import("./public-envelope-RXZNP3V6.js");
|
|
7437
8018
|
return readPublicEnvelope2(this.adapter, this.name, opts);
|
|
7438
8019
|
}
|
|
7439
8020
|
/**
|
|
@@ -8977,6 +9558,7 @@ var Noydb = class {
|
|
|
8977
9558
|
policyEnforcers = /* @__PURE__ */ new Map();
|
|
8978
9559
|
vaultTemplates = /* @__PURE__ */ new Map();
|
|
8979
9560
|
txStrategy;
|
|
9561
|
+
forgetStrategy;
|
|
8980
9562
|
sessionStrategy;
|
|
8981
9563
|
syncStrategy;
|
|
8982
9564
|
snapshotStrategy;
|
|
@@ -9004,6 +9586,7 @@ var Noydb = class {
|
|
|
9004
9586
|
constructor(options) {
|
|
9005
9587
|
this.options = options;
|
|
9006
9588
|
this.txStrategy = options.txStrategy ?? NO_TX;
|
|
9589
|
+
this.forgetStrategy = options.forgetStrategy ?? NO_FORGET;
|
|
9007
9590
|
this.sessionStrategy = options.sessionStrategy ?? NO_SESSION;
|
|
9008
9591
|
this.syncStrategy = options.syncStrategy ?? NO_SYNC;
|
|
9009
9592
|
this.snapshotStrategy = options.snapshotStrategy ?? NO_SNAPSHOTS;
|
|
@@ -9014,8 +9597,61 @@ var Noydb = class {
|
|
|
9014
9597
|
}
|
|
9015
9598
|
this.#registerGuardGate();
|
|
9016
9599
|
this.#registerPeriodGate();
|
|
9600
|
+
this.#registerForgetHooks();
|
|
9017
9601
|
this.resetSessionTimer();
|
|
9018
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
|
+
}
|
|
9019
9655
|
// Track A — guards migration. Registers record-lock / field-freeze / onDelete
|
|
9020
9656
|
// / amendment-collect as gate-bus handlers (only when guards are opted in, so
|
|
9021
9657
|
// the write path is zero-cost otherwise). Resolves the live vault's
|
|
@@ -9043,7 +9679,7 @@ var Noydb = class {
|
|
|
9043
9679
|
if (!facade) return;
|
|
9044
9680
|
const ctx = { existing, vault: facade, userId: e.userId, role: e.role };
|
|
9045
9681
|
await registry.runChecks(e.collection, incoming, ctx);
|
|
9046
|
-
const { GuardExecutor } = await import("./executor-
|
|
9682
|
+
const { GuardExecutor } = await import("./executor-VDQQOR4F.js");
|
|
9047
9683
|
for (const g of guards) {
|
|
9048
9684
|
await GuardExecutor.checkFrozenFields(g, e.docId, existing, incoming, e.computedFieldNames);
|
|
9049
9685
|
}
|
|
@@ -9236,6 +9872,7 @@ var Noydb = class {
|
|
|
9236
9872
|
...this.options.syncStrategy !== void 0 ? { syncStrategy: this.options.syncStrategy } : {},
|
|
9237
9873
|
...this.options.guardStrategies !== void 0 ? { guardStrategies: this.options.guardStrategies } : {},
|
|
9238
9874
|
...this.options.numbering !== void 0 ? { numberingConfigs: this.options.numbering } : {},
|
|
9875
|
+
forgetStrategy: this.forgetStrategy,
|
|
9239
9876
|
locale: opts?.locale,
|
|
9240
9877
|
// Thread the translator hook so Collection.put() can invoke it
|
|
9241
9878
|
plaintextTranslator: this.options.plaintextTranslator ? (text, from, to, field, collection) => this.invokeTranslator(text, from, to, field, collection) : void 0,
|
|
@@ -9290,7 +9927,8 @@ var Noydb = class {
|
|
|
9290
9927
|
...this.options.i18nStrategy !== void 0 ? { i18nStrategy: this.options.i18nStrategy } : {},
|
|
9291
9928
|
...this.options.syncStrategy !== void 0 ? { syncStrategy: this.options.syncStrategy } : {},
|
|
9292
9929
|
...this.options.guardStrategies !== void 0 ? { guardStrategies: this.options.guardStrategies } : {},
|
|
9293
|
-
...this.options.numbering !== void 0 ? { numberingConfigs: this.options.numbering } : {}
|
|
9930
|
+
...this.options.numbering !== void 0 ? { numberingConfigs: this.options.numbering } : {},
|
|
9931
|
+
forgetStrategy: this.forgetStrategy
|
|
9294
9932
|
});
|
|
9295
9933
|
this.vaultCache.set(name, comp2);
|
|
9296
9934
|
return comp2;
|
|
@@ -9642,8 +10280,8 @@ var Noydb = class {
|
|
|
9642
10280
|
if (name === STATE_VAULT_NAME) throw new ReservedVaultNameError(name);
|
|
9643
10281
|
const template = this.vaultTemplates.get(opts.sharding.vaultTemplate);
|
|
9644
10282
|
if (!template) throw new VaultTemplateNotFoundError(opts.sharding.vaultTemplate);
|
|
9645
|
-
const { VaultGroup } = await import("./vault-group-
|
|
9646
|
-
const { StateManagementVault } = await import("./state-vault-
|
|
10283
|
+
const { VaultGroup } = await import("./vault-group-DHAHFX2A.js");
|
|
10284
|
+
const { StateManagementVault } = await import("./state-vault-W2OEABNO.js");
|
|
9647
10285
|
const stateVault = opts.registry ? void 0 : await StateManagementVault.open(this);
|
|
9648
10286
|
const registry = opts.registry ?? stateVault.registry;
|
|
9649
10287
|
const group = new VaultGroup(this, name, registry, opts.sharding, template);
|
|
@@ -9670,7 +10308,7 @@ var Noydb = class {
|
|
|
9670
10308
|
*/
|
|
9671
10309
|
async openStateManagementVault() {
|
|
9672
10310
|
if (this.closed) throw new ValidationError("Instance is closed");
|
|
9673
|
-
const { StateManagementVault } = await import("./state-vault-
|
|
10311
|
+
const { StateManagementVault } = await import("./state-vault-W2OEABNO.js");
|
|
9674
10312
|
return StateManagementVault.open(this);
|
|
9675
10313
|
}
|
|
9676
10314
|
/**
|
|
@@ -11211,4 +11849,4 @@ export {
|
|
|
11211
11849
|
Noydb,
|
|
11212
11850
|
createNoydb
|
|
11213
11851
|
};
|
|
11214
|
-
//# sourceMappingURL=chunk-
|
|
11852
|
+
//# sourceMappingURL=chunk-D77ZQSQQ.js.map
|