@noy-db/hub 0.2.0-pre.2 → 0.2.0-pre.21
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/README.md +126 -0
- package/dist/aggregate/index.cjs +643 -37
- 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 +9 -8
- package/dist/aggregate/index.js.map +1 -1
- package/dist/attestation/index.cjs.map +1 -1
- package/dist/attestation/index.d.cts +7 -5
- package/dist/attestation/index.d.ts +7 -5
- package/dist/attestation/index.js +6 -6
- package/dist/blobs/index.cjs +509 -22
- package/dist/blobs/index.cjs.map +1 -1
- package/dist/blobs/index.d.cts +9 -7
- package/dist/blobs/index.d.ts +9 -7
- package/dist/blobs/index.js +11 -6
- package/dist/blobs/index.js.map +1 -1
- package/dist/bundle/index.cjs +7886 -841
- package/dist/bundle/index.cjs.map +1 -1
- package/dist/bundle/index.d.cts +20 -18
- package/dist/bundle/index.d.ts +20 -18
- package/dist/bundle/index.js +24 -13
- package/dist/bundle/index.js.map +1 -1
- package/dist/{chunk-PFSNOPBQ.js → chunk-2XA2ZML4.js} +31 -3
- package/dist/chunk-2XA2ZML4.js.map +1 -0
- package/dist/{chunk-2PAQNPE3.js → chunk-37VGJM3T.js} +37 -2
- package/dist/chunk-37VGJM3T.js.map +1 -0
- package/dist/{chunk-7BRE6EUA.js → chunk-3HNKR65T.js} +4 -4
- package/dist/chunk-3HNKR65T.js.map +1 -0
- package/dist/{chunk-Y2RKOPNC.js → chunk-5YTXYPES.js} +46 -10
- package/dist/chunk-5YTXYPES.js.map +1 -0
- package/dist/{chunk-OVZDFEOR.js → chunk-6QAZ5O6X.js} +2 -2
- package/dist/chunk-6QAZ5O6X.js.map +1 -0
- package/dist/{chunk-RTZVQAJ7.js → chunk-6QE4DUYC.js} +19 -4
- package/dist/chunk-6QE4DUYC.js.map +1 -0
- package/dist/{chunk-7Q5PLD5C.js → chunk-7MRT7EPB.js} +3 -3
- package/dist/{chunk-E535SAN4.js → chunk-7PH4OPBZ.js} +4258 -520
- package/dist/chunk-7PH4OPBZ.js.map +1 -0
- package/dist/{chunk-PEULZC6M.js → chunk-A3JMGXPG.js} +8 -1
- package/dist/chunk-A3JMGXPG.js.map +1 -0
- package/dist/{chunk-UMLVJTYV.js → chunk-ADB7GPM3.js} +7 -4
- package/dist/chunk-ADB7GPM3.js.map +1 -0
- package/dist/{chunk-G6FRSBKK.js → chunk-AI4USDRI.js} +4 -4
- package/dist/chunk-BZW5IL43.js +151 -0
- package/dist/chunk-BZW5IL43.js.map +1 -0
- package/dist/chunk-C2RJVZZL.js +123 -0
- package/dist/chunk-C2RJVZZL.js.map +1 -0
- package/dist/{chunk-UND4XIB6.js → chunk-C6W5KVDV.js} +52 -38
- package/dist/chunk-C6W5KVDV.js.map +1 -0
- package/dist/chunk-CQYEDODS.js +125 -0
- package/dist/chunk-CQYEDODS.js.map +1 -0
- package/dist/{chunk-NWZ3I6R6.js → chunk-EYK72OTL.js} +5 -5
- package/dist/{chunk-7BUTTVMR.js → chunk-F5GWNSE2.js} +2 -2
- package/dist/{chunk-AHPFONIL.js → chunk-F5ILTHMU.js} +5 -5
- package/dist/{chunk-Q6W2CMEJ.js → chunk-FRRJIUSI.js} +18 -5
- package/dist/chunk-FRRJIUSI.js.map +1 -0
- package/dist/{chunk-YMYK7US4.js → chunk-GJTKMME7.js} +2 -2
- package/dist/chunk-GJTKMME7.js.map +1 -0
- package/dist/{chunk-EUYOGYGV.js → chunk-HYJMAV53.js} +6 -6
- package/dist/chunk-HYJMAV53.js.map +1 -0
- package/dist/{chunk-QPEXPHJR.js → chunk-I3IYTUUI.js} +4 -4
- package/dist/{chunk-3QAKZ37R.js → chunk-IVZWHIEK.js} +5 -5
- package/dist/{chunk-PLI5TV7N.js → chunk-IW4L4X65.js} +2 -2
- package/dist/chunk-IW4L4X65.js.map +1 -0
- package/dist/{chunk-3Z2TPHC4.js → chunk-IY24WS2P.js} +69 -5
- package/dist/chunk-IY24WS2P.js.map +1 -0
- package/dist/{chunk-HXJXPZRE.js → chunk-J6RGRZOY.js} +10 -3
- package/dist/chunk-J6RGRZOY.js.map +1 -0
- package/dist/{chunk-3S4BJX25.js → chunk-JBBWALNI.js} +2 -2
- package/dist/chunk-JBBWALNI.js.map +1 -0
- package/dist/{chunk-7Z23ZFLV.js → chunk-JDCPRJVS.js} +5 -5
- package/dist/chunk-JDCPRJVS.js.map +1 -0
- package/dist/{chunk-243PNUA6.js → chunk-JOK73NDT.js} +3 -3
- package/dist/chunk-JTI57WRT.js +164 -0
- package/dist/chunk-JTI57WRT.js.map +1 -0
- package/dist/{chunk-VRBCTEKQ.js → chunk-JYNH4FIM.js} +233 -11
- package/dist/chunk-JYNH4FIM.js.map +1 -0
- package/dist/{chunk-TBKOGSYR.js → chunk-KOAJ3TZM.js} +27 -5
- package/dist/chunk-KOAJ3TZM.js.map +1 -0
- package/dist/{chunk-YTXSFG3C.js → chunk-MBXKRHSS.js} +50 -20
- package/dist/chunk-MBXKRHSS.js.map +1 -0
- package/dist/{chunk-MUWOSVEP.js → chunk-NSXNXLYM.js} +10 -2
- package/dist/chunk-NSXNXLYM.js.map +1 -0
- package/dist/{chunk-J4KLMEUL.js → chunk-NV4IHBZS.js} +664 -51
- package/dist/chunk-NV4IHBZS.js.map +1 -0
- package/dist/{chunk-LRAZDV5X.js → chunk-O5XKZCUD.js} +31 -8
- package/dist/chunk-O5XKZCUD.js.map +1 -0
- package/dist/{chunk-W3XXT26A.js → chunk-OTWT6BAJ.js} +358 -3
- package/dist/chunk-OTWT6BAJ.js.map +1 -0
- package/dist/{chunk-XG3PTSCD.js → chunk-PDVP3C2I.js} +1 -1
- package/dist/chunk-PDVP3C2I.js.map +1 -0
- package/dist/{chunk-GIV6DWBG.js → chunk-S45MDEEF.js} +44 -5
- package/dist/chunk-S45MDEEF.js.map +1 -0
- package/dist/{chunk-VK5EER6C.js → chunk-SQKAECUL.js} +2 -2
- package/dist/{chunk-FAQVNJD4.js → chunk-SQOK5UM6.js} +12 -2
- package/dist/{chunk-FAQVNJD4.js.map → chunk-SQOK5UM6.js.map} +1 -1
- package/dist/chunk-STNPB3UM.js +9 -0
- package/dist/chunk-STNPB3UM.js.map +1 -0
- package/dist/{chunk-YS3POABP.js → chunk-TA6HPKWQ.js} +1 -1
- package/dist/chunk-TA6HPKWQ.js.map +1 -0
- package/dist/{chunk-4HIL6AHQ.js → chunk-TAMRU7A2.js} +4 -4
- package/dist/{chunk-QXQRKXCU.js → chunk-TGIJTNM3.js} +2 -2
- package/dist/chunk-TNH5SLCD.js +361 -0
- package/dist/chunk-TNH5SLCD.js.map +1 -0
- package/dist/{chunk-VPSUZLOJ.js → chunk-TYMDCIQM.js} +31 -5
- package/dist/chunk-TYMDCIQM.js.map +1 -0
- package/dist/chunk-U2XSUCDF.js +524 -0
- package/dist/chunk-U2XSUCDF.js.map +1 -0
- package/dist/{chunk-3Y53S2SA.js → chunk-UU6M64HI.js} +4 -4
- package/dist/{chunk-VCGTOS2A.js → chunk-WE2BUQD2.js} +3 -3
- package/dist/chunk-WE2BUQD2.js.map +1 -0
- package/dist/{chunk-JYQTXEIO.js → chunk-WWVJXBOT.js} +449 -29
- package/dist/chunk-WWVJXBOT.js.map +1 -0
- package/dist/chunk-YPIOFSN3.js +129 -0
- package/dist/chunk-YPIOFSN3.js.map +1 -0
- package/dist/chunk-ZC7J6ZYV.js +7 -0
- package/dist/chunk-ZC7J6ZYV.js.map +1 -0
- package/dist/{chunk-5ZGZ6HIZ.js → chunk-ZONKSLF2.js} +30 -7
- package/dist/chunk-ZONKSLF2.js.map +1 -0
- package/dist/consent/index.cjs.map +1 -1
- package/dist/consent/index.d.cts +8 -6
- package/dist/consent/index.d.ts +8 -6
- package/dist/consent/index.js +3 -3
- package/dist/{crypto-5ZDIY3NG.js → crypto-456N7UVX.js} +7 -3
- package/dist/{delegation-QYXZW25W.js → delegation-DP4COTXB.js} +5 -5
- package/dist/derivations/index.cjs +124 -6
- package/dist/derivations/index.cjs.map +1 -1
- package/dist/derivations/index.d.cts +11 -9
- package/dist/derivations/index.d.ts +11 -9
- package/dist/derivations/index.js +8 -6
- package/dist/{dev-unlock-DQCNDfFp.d.cts → dev-unlock-CY0HIZA0.d.cts} +1 -1
- package/dist/{dev-unlock-utkybTKb.d.ts → dev-unlock-CpKSkl2c.d.ts} +1 -1
- package/dist/discriminant-BN9REW3o.d.cts +60 -0
- package/dist/discriminant-BN9REW3o.d.ts +60 -0
- package/dist/errors-Dkc_fi-S.d.cts +1467 -0
- package/dist/errors-Dkc_fi-S.d.ts +1467 -0
- package/dist/executor-4IEW4KG5.js +8 -0
- package/dist/executor-KYJCJCIN.js +12 -0
- package/dist/executor-W7VIBOBZ.js +8 -0
- package/dist/{fanout-sidecar-VJ52RIEY.js → fanout-sidecar-YXNAEZ33.js} +2 -2
- package/dist/fanout-sidecar-YXNAEZ33.js.map +1 -0
- 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 +144 -4
- package/dist/guards/index.cjs.map +1 -1
- package/dist/guards/index.d.cts +16 -8
- package/dist/guards/index.d.ts +16 -8
- package/dist/guards/index.js +13 -7
- package/dist/{hash-jDowCrK2.d.cts → hash-BSd0-_L8.d.cts} +1 -1
- package/dist/{hash-DcoYWfJ_.d.ts → hash-BnBQx39y.d.ts} +1 -1
- package/dist/history/index.cjs +28 -5
- package/dist/history/index.cjs.map +1 -1
- package/dist/history/index.d.cts +9 -7
- package/dist/history/index.d.ts +9 -7
- package/dist/history/index.js +9 -7
- package/dist/history/index.js.map +1 -1
- package/dist/i18n/index.cjs +356 -26
- package/dist/i18n/index.cjs.map +1 -1
- package/dist/i18n/index.d.cts +8 -6
- package/dist/i18n/index.d.ts +8 -6
- package/dist/i18n/index.js +36 -15
- package/dist/i18n/index.js.map +1 -1
- package/dist/index-BMmajblo.d.cts +362 -0
- package/dist/index-BMmajblo.d.ts +362 -0
- package/dist/{index-BCKdioeh.d.ts → index-Bm9hIY7t.d.ts} +169 -1127
- package/dist/{index-BMjrzNZr.d.cts → index-tZqVB9g5.d.cts} +169 -1127
- package/dist/index.cjs +10286 -2168
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +258 -23
- package/dist/index.d.ts +258 -23
- package/dist/index.js +443 -110
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.cjs +97 -32
- package/dist/indexing/index.cjs.map +1 -1
- package/dist/indexing/index.d.cts +3 -3
- package/dist/indexing/index.d.ts +3 -3
- package/dist/indexing/index.js +4 -4
- package/dist/issue-JXC6T2QR.js +12 -0
- package/dist/{lazy-builder-Rpd-V3jP.d.ts → lazy-builder-ChSqcF5t.d.ts} +2 -2
- package/dist/{lazy-builder-C-rPfWG0.d.cts → lazy-builder-eYZzLEL1.d.cts} +2 -2
- package/dist/{ledger-3IU5GMXA.js → ledger-I7JUYP4L.js} +6 -6
- package/dist/materialized-views/index.cjs +687 -13
- package/dist/materialized-views/index.cjs.map +1 -1
- package/dist/materialized-views/index.d.cts +23 -20
- package/dist/materialized-views/index.d.ts +23 -20
- package/dist/materialized-views/index.js +8 -7
- package/dist/mime-magic-BnJCGJzB.d.cts +103 -0
- package/dist/mime-magic-CjSyakO4.d.ts +103 -0
- package/dist/noydb-ZZCRF6TE.js +38 -0
- package/dist/overlay-views/index.cjs +58 -18
- package/dist/overlay-views/index.cjs.map +1 -1
- package/dist/overlay-views/index.d.cts +32 -12
- package/dist/overlay-views/index.d.ts +32 -12
- package/dist/overlay-views/index.js +6 -6
- package/dist/periods/index.cjs.map +1 -1
- package/dist/periods/index.d.cts +8 -6
- package/dist/periods/index.d.ts +8 -6
- package/dist/periods/index.js +6 -6
- package/dist/{predicate-Dnu81tsS.d.cts → predicate-BmhBSPCH.d.cts} +87 -5
- package/dist/{predicate-Dnu81tsS.d.ts → predicate-BmhBSPCH.d.ts} +87 -5
- package/dist/{public-envelope-U3CMEOMV.js → public-envelope-5XRTUNKF.js} +4 -4
- package/dist/query/index.cjs +1438 -130
- package/dist/query/index.cjs.map +1 -1
- package/dist/query/index.d.cts +4 -3
- package/dist/query/index.d.ts +4 -3
- package/dist/query/index.js +13 -6
- package/dist/read-only-facade-EX6WZZBP.js +7 -0
- package/dist/registry-ATRHOG5B.js +8 -0
- package/dist/registry-DKEXOJVO.js +7 -0
- package/dist/registry-LEHB26TY.js +8 -0
- package/dist/{registry-3ALP62P6.js → registry-NWHOLD5M.js} +3 -3
- package/dist/{revoke-KY2GB4KP.js → revoke-5IEK22KT.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 +9 -7
- package/dist/session/index.d.ts +9 -7
- package/dist/session/index.js +3 -3
- package/dist/shadow/index.cjs.map +1 -1
- package/dist/shadow/index.d.cts +8 -6
- package/dist/shadow/index.d.ts +8 -6
- package/dist/shadow/index.js +2 -2
- package/dist/{signer-GRI5TZKH.js → signer-I6YARZQA.js} +5 -5
- package/dist/snapshots/index.cjs +937 -0
- package/dist/snapshots/index.cjs.map +1 -0
- package/dist/snapshots/index.d.cts +30 -0
- package/dist/snapshots/index.d.ts +30 -0
- package/dist/snapshots/index.js +152 -0
- package/dist/snapshots/index.js.map +1 -0
- package/dist/{stale-OTOF3FH7.js → stale-CPESGAPL.js} +2 -2
- package/dist/stale-CPESGAPL.js.map +1 -0
- package/dist/state-vault-JR3CFGNP.js +14 -0
- package/dist/state-vault-JR3CFGNP.js.map +1 -0
- package/dist/store/index.cjs +8 -0
- package/dist/store/index.cjs.map +1 -1
- package/dist/store/index.d.cts +15 -6
- package/dist/store/index.d.ts +15 -6
- package/dist/store/index.js +2 -2
- package/dist/{strategy-DSTrsZ8t.d.ts → strategy-54eIwox5.d.ts} +456 -7
- package/dist/{strategy-DSTrsZ8t.d.cts → strategy-WtB-jXYv.d.cts} +456 -7
- package/dist/sync/index.cjs.map +1 -1
- package/dist/sync/index.d.cts +7 -5
- package/dist/sync/index.d.ts +7 -5
- package/dist/sync/index.js +4 -4
- package/dist/team/index.cjs +1 -1
- package/dist/team/index.cjs.map +1 -1
- package/dist/team/index.d.cts +8 -6
- package/dist/team/index.d.ts +8 -6
- package/dist/team/index.js +8 -8
- package/dist/transition-guard-D4bfIAiW.d.ts +165 -0
- package/dist/transition-guard-Dmpqzg-_.d.cts +165 -0
- package/dist/tx/index.cjs +155 -5
- package/dist/tx/index.cjs.map +1 -1
- package/dist/tx/index.d.cts +27 -9
- package/dist/tx/index.d.ts +27 -9
- package/dist/tx/index.js +61 -4
- package/dist/tx/index.js.map +1 -1
- package/dist/{types-BoFFiskX.d.ts → types-DLfWFr6U.d.ts} +3997 -1262
- package/dist/{types-DJG8HG6F.d.cts → types-DyOI6XZ_.d.cts} +3997 -1262
- package/dist/{ulid-BmBgooGm.d.ts → ulid-B2L_aqVA.d.ts} +19 -19
- package/dist/{ulid-C7ms9oli.d.cts → ulid-LaxfH2tK.d.cts} +19 -19
- package/dist/util/index.cjs +7 -0
- package/dist/util/index.cjs.map +1 -1
- package/dist/util/index.d.cts +2 -0
- package/dist/util/index.d.ts +2 -0
- package/dist/util/index.js +5 -1
- package/dist/util/index.js.map +1 -1
- package/dist/vault-group-BB246VIM.js +804 -0
- package/dist/vault-group-BB246VIM.js.map +1 -0
- package/dist/{with-materialized-view-CqnRwI2S.d.ts → with-materialized-view-CeZYGJVf.d.cts} +2 -2
- package/dist/{with-materialized-view-BbEPFIIJ.d.cts → with-materialized-view-DNULSxoP.d.ts} +2 -2
- package/dist/{with-overlayed-view-Ct1fSJt-.d.ts → with-overlayed-view-C9joG7UZ.d.ts} +2 -2
- package/dist/{with-overlayed-view-bwlmmFjx.d.cts → with-overlayed-view-kdcPGHih.d.cts} +2 -2
- package/dist/with-rollup-DJDbrxjf.d.ts +47 -0
- package/dist/with-rollup-s58XAeWO.d.cts +47 -0
- package/package.json +35 -4
- package/dist/chunk-2PAQNPE3.js.map +0 -1
- package/dist/chunk-3S4BJX25.js.map +0 -1
- package/dist/chunk-3XHOCQK4.js +0 -118
- package/dist/chunk-3XHOCQK4.js.map +0 -1
- package/dist/chunk-3Z2TPHC4.js.map +0 -1
- package/dist/chunk-5ZGZ6HIZ.js.map +0 -1
- package/dist/chunk-7BRE6EUA.js.map +0 -1
- package/dist/chunk-7Z23ZFLV.js.map +0 -1
- package/dist/chunk-CXSCDO5T.js +0 -51
- package/dist/chunk-CXSCDO5T.js.map +0 -1
- package/dist/chunk-E535SAN4.js.map +0 -1
- package/dist/chunk-EUYOGYGV.js.map +0 -1
- package/dist/chunk-GIV6DWBG.js.map +0 -1
- package/dist/chunk-HXJXPZRE.js.map +0 -1
- package/dist/chunk-J4KLMEUL.js.map +0 -1
- package/dist/chunk-JYQTXEIO.js.map +0 -1
- package/dist/chunk-LRAZDV5X.js.map +0 -1
- package/dist/chunk-MRIBLZL3.js +0 -86
- package/dist/chunk-MRIBLZL3.js.map +0 -1
- package/dist/chunk-MUWOSVEP.js.map +0 -1
- package/dist/chunk-OVZDFEOR.js.map +0 -1
- package/dist/chunk-PEULZC6M.js.map +0 -1
- package/dist/chunk-PFSNOPBQ.js.map +0 -1
- package/dist/chunk-PLI5TV7N.js.map +0 -1
- package/dist/chunk-Q6W2CMEJ.js.map +0 -1
- package/dist/chunk-RTZVQAJ7.js.map +0 -1
- package/dist/chunk-TBKOGSYR.js.map +0 -1
- package/dist/chunk-UMLVJTYV.js.map +0 -1
- package/dist/chunk-UND4XIB6.js.map +0 -1
- package/dist/chunk-VCGTOS2A.js.map +0 -1
- package/dist/chunk-VE6YVP32.js +0 -19
- package/dist/chunk-VE6YVP32.js.map +0 -1
- package/dist/chunk-VPSUZLOJ.js.map +0 -1
- package/dist/chunk-VRBCTEKQ.js.map +0 -1
- package/dist/chunk-W3XXT26A.js.map +0 -1
- package/dist/chunk-XG3PTSCD.js.map +0 -1
- package/dist/chunk-Y2RKOPNC.js.map +0 -1
- package/dist/chunk-YMYK7US4.js.map +0 -1
- package/dist/chunk-YS3POABP.js.map +0 -1
- package/dist/chunk-YTXSFG3C.js.map +0 -1
- package/dist/executor-AS2IDHKZ.js +0 -11
- package/dist/executor-HLXFXNFM.js +0 -8
- package/dist/executor-HN6YBHZ5.js +0 -8
- package/dist/fanout-sidecar-VJ52RIEY.js.map +0 -1
- package/dist/issue-ORP37MVW.js +0 -12
- package/dist/mime-magic-CBBSOkjm.d.cts +0 -50
- package/dist/mime-magic-CBBSOkjm.d.ts +0 -50
- package/dist/noydb-5H3C24GG.js +0 -34
- package/dist/read-only-facade-ITU6L7BL.js +0 -7
- package/dist/registry-7HE6VJGC.js +0 -8
- package/dist/registry-PSIPG2QR.js +0 -8
- package/dist/registry-RFGGMVNJ.js +0 -7
- package/dist/with-derivation-BKXXa8Vt.d.ts +0 -13
- package/dist/with-derivation-BjQ7q4NE.d.cts +0 -13
- package/dist/with-guard-C25yNjzd.d.ts +0 -18
- package/dist/with-guard-DQme5DKE.d.cts +0 -18
- /package/dist/{chunk-7Q5PLD5C.js.map → chunk-7MRT7EPB.js.map} +0 -0
- /package/dist/{chunk-G6FRSBKK.js.map → chunk-AI4USDRI.js.map} +0 -0
- /package/dist/{chunk-NWZ3I6R6.js.map → chunk-EYK72OTL.js.map} +0 -0
- /package/dist/{chunk-7BUTTVMR.js.map → chunk-F5GWNSE2.js.map} +0 -0
- /package/dist/{chunk-AHPFONIL.js.map → chunk-F5ILTHMU.js.map} +0 -0
- /package/dist/{chunk-QPEXPHJR.js.map → chunk-I3IYTUUI.js.map} +0 -0
- /package/dist/{chunk-3QAKZ37R.js.map → chunk-IVZWHIEK.js.map} +0 -0
- /package/dist/{chunk-243PNUA6.js.map → chunk-JOK73NDT.js.map} +0 -0
- /package/dist/{chunk-VK5EER6C.js.map → chunk-SQKAECUL.js.map} +0 -0
- /package/dist/{chunk-4HIL6AHQ.js.map → chunk-TAMRU7A2.js.map} +0 -0
- /package/dist/{chunk-QXQRKXCU.js.map → chunk-TGIJTNM3.js.map} +0 -0
- /package/dist/{chunk-3Y53S2SA.js.map → chunk-UU6M64HI.js.map} +0 -0
- /package/dist/{crypto-5ZDIY3NG.js.map → crypto-456N7UVX.js.map} +0 -0
- /package/dist/{delegation-QYXZW25W.js.map → delegation-DP4COTXB.js.map} +0 -0
- /package/dist/{executor-AS2IDHKZ.js.map → executor-4IEW4KG5.js.map} +0 -0
- /package/dist/{executor-HLXFXNFM.js.map → executor-KYJCJCIN.js.map} +0 -0
- /package/dist/{executor-HN6YBHZ5.js.map → executor-W7VIBOBZ.js.map} +0 -0
- /package/dist/{issue-ORP37MVW.js.map → forget/index.js.map} +0 -0
- /package/dist/{ledger-3IU5GMXA.js.map → issue-JXC6T2QR.js.map} +0 -0
- /package/dist/{noydb-5H3C24GG.js.map → ledger-I7JUYP4L.js.map} +0 -0
- /package/dist/{public-envelope-U3CMEOMV.js.map → noydb-ZZCRF6TE.js.map} +0 -0
- /package/dist/{read-only-facade-ITU6L7BL.js.map → public-envelope-5XRTUNKF.js.map} +0 -0
- /package/dist/{registry-3ALP62P6.js.map → read-only-facade-EX6WZZBP.js.map} +0 -0
- /package/dist/{registry-7HE6VJGC.js.map → registry-ATRHOG5B.js.map} +0 -0
- /package/dist/{registry-PSIPG2QR.js.map → registry-DKEXOJVO.js.map} +0 -0
- /package/dist/{registry-RFGGMVNJ.js.map → registry-LEHB26TY.js.map} +0 -0
- /package/dist/{revoke-KY2GB4KP.js.map → registry-NWHOLD5M.js.map} +0 -0
- /package/dist/{signer-GRI5TZKH.js.map → revoke-5IEK22KT.js.map} +0 -0
- /package/dist/{stale-OTOF3FH7.js.map → signer-I6YARZQA.js.map} +0 -0
|
@@ -13,6 +13,13 @@ var GuardRegistry = class {
|
|
|
13
13
|
guardsFor(collection) {
|
|
14
14
|
return this._byCollection.get(collection) ?? [];
|
|
15
15
|
}
|
|
16
|
+
/** Per-collection guard counts, for introspection. */
|
|
17
|
+
summary() {
|
|
18
|
+
return [...this._byCollection.entries()].map(([collection, guards]) => ({
|
|
19
|
+
collection,
|
|
20
|
+
count: guards.length
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
16
23
|
/**
|
|
17
24
|
* Run every guard's `check` for this collection. First throw wins —
|
|
18
25
|
* remaining guards are not invoked. Guards without a `check` skip.
|
|
@@ -115,4 +122,4 @@ var GuardRegistry = class {
|
|
|
115
122
|
export {
|
|
116
123
|
GuardRegistry
|
|
117
124
|
};
|
|
118
|
-
//# sourceMappingURL=chunk-
|
|
125
|
+
//# sourceMappingURL=chunk-A3JMGXPG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/guards/registry.ts"],"sourcesContent":["import type { GuardStrategy, GuardContext, GuardChange } from './types.js'\n\n/**\n * Per-record metadata attached to every entry in an amendment's\n * change-set. Carried in a parallel map alongside `_amendmentChanges`\n * so the public {@link GuardChange} shape (`{ before, after }`) stays\n * clean for invariant authors — the audit ledger reads this side\n * structure to produce the `{ collection, id, vBefore, vAfter }`\n * tuples for the amendment entry.\n *\n * @internal\n */\nexport interface AmendmentChangeMeta {\n readonly id: string\n readonly vBefore: number\n readonly vAfter: number\n}\n\n/**\n * Vault-internal singleton that holds the guard graph and dispatches\n * per-collection guard execution. Owned by `Vault`; not exported.\n *\n * @internal\n */\n// Internal storage alias — guards are heterogeneous in their record type T,\n// so the registry stores them at the upper bound of GuardStrategy's T constraint.\ntype AnyGuard = GuardStrategy<Record<string, unknown>>\ntype AnyChange = GuardChange<Record<string, unknown>>\n\nexport class GuardRegistry {\n private readonly _byCollection = new Map<string, AnyGuard[]>()\n private _amendmentChanges: Map<string, AnyChange[]> | null = null\n private _amendmentMeta: Map<string, AmendmentChangeMeta[]> | null = null\n\n /** Register a guard. Multiple guards per collection are allowed. */\n register<T extends Record<string, unknown>>(spec: GuardStrategy<T>): void {\n const existing = this._byCollection.get(spec.collection)\n if (existing) existing.push(spec as unknown as AnyGuard)\n else this._byCollection.set(spec.collection, [spec as unknown as AnyGuard])\n }\n\n /** All guards registered against `collection` in registration order. */\n guardsFor(collection: string): ReadonlyArray<AnyGuard> {\n return this._byCollection.get(collection) ?? []\n }\n\n /** Per-collection guard counts, for introspection. */\n summary(): { collection: string; count: number }[] {\n return [...this._byCollection.entries()].map(([collection, guards]) => ({\n collection,\n count: guards.length,\n }))\n }\n\n /**\n * Run every guard's `check` for this collection. First throw wins —\n * remaining guards are not invoked. Guards without a `check` skip.\n */\n async runChecks<T>(\n collection: string,\n incoming: T,\n ctx: GuardContext<T>,\n ): Promise<void> {\n const guards = this._byCollection.get(collection)\n if (!guards) return\n for (const g of guards) {\n if (g.check) {\n await g.check(\n incoming as unknown as Record<string, unknown>,\n ctx as unknown as GuardContext<Record<string, unknown>>,\n )\n }\n }\n }\n\n /**\n * Run every guard's `onDelete` for this collection. First throw wins —\n * remaining guards are not invoked. Guards without an `onDelete` skip.\n * Mirrors {@link runChecks} but for the delete path.\n */\n async runOnDelete<T>(\n collection: string,\n existing: T,\n ctx: GuardContext<T>,\n ): Promise<void> {\n const guards = this._byCollection.get(collection)\n if (!guards) return\n for (const g of guards) {\n if (g.onDelete) {\n await g.onDelete(\n existing as unknown as Record<string, unknown>,\n ctx as unknown as GuardContext<Record<string, unknown>>,\n )\n }\n }\n }\n\n /** True if any guard for `collection` declares an `amendment` block. */\n hasAmendment(collection: string): boolean {\n const guards = this._byCollection.get(collection)\n if (!guards) return false\n return guards.some(g => g.amendment !== undefined)\n }\n\n /** Open a new amendment change-collection window. */\n beginAmendment(): void {\n this._amendmentChanges = new Map()\n this._amendmentMeta = new Map()\n }\n\n /** True iff we're currently inside an amendment transaction. */\n isAmendmentActive(): boolean {\n return this._amendmentChanges !== null\n }\n\n /**\n * Record a {before, after} pair for the active amendment. `vBefore`\n * and `vAfter` are stored in a parallel meta structure so the public\n * {@link GuardChange} shape handed to invariant callbacks stays\n * `{ before, after }` only — the audit ledger reads version metadata\n * via {@link consumeMeta}.\n */\n collectChange<T>(\n collection: string,\n id: string,\n before: T | null,\n after: T,\n vBefore = 0,\n vAfter = 0,\n ): void {\n if (this._amendmentChanges === null || this._amendmentMeta === null) {\n throw new Error('GuardRegistry.collectChange called outside an amendment')\n }\n const list = this._amendmentChanges.get(collection)\n const entry = { before, after } as unknown as AnyChange\n if (list) list.push(entry)\n else this._amendmentChanges.set(collection, [entry])\n\n const metaList = this._amendmentMeta.get(collection)\n const metaEntry: AmendmentChangeMeta = { id, vBefore, vAfter }\n if (metaList) metaList.push(metaEntry)\n else this._amendmentMeta.set(collection, [metaEntry])\n }\n\n /**\n * Drain the change-set and close the amendment window. The caller\n * (transaction commit) feeds these to each affected guard's invariant.\n */\n consumeChanges(): ReadonlyMap<string, ReadonlyArray<AnyChange>> {\n const out = this._amendmentChanges ?? new Map()\n this._amendmentChanges = null\n return out\n }\n\n /**\n * Drain the parallel id/version metadata captured during the\n * amendment. Returned as a flat list with `collection` denormalised\n * so the audit ledger can emit one `{ collection, id, vBefore,\n * vAfter }` tuple per record. Must be called AFTER\n * {@link consumeChanges} (or independently) — calling it closes the\n * meta window in the same way.\n */\n consumeMeta(): ReadonlyArray<{ collection: string; id: string; vBefore: number; vAfter: number }> {\n const out: { collection: string; id: string; vBefore: number; vAfter: number }[] = []\n if (this._amendmentMeta) {\n for (const [collection, list] of this._amendmentMeta) {\n for (const m of list) {\n out.push({ collection, id: m.id, vBefore: m.vBefore, vAfter: m.vAfter })\n }\n }\n }\n this._amendmentMeta = null\n return out\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
// src/guards/read-only-facade.ts
|
|
2
2
|
var ReadOnlyVaultFacade = class {
|
|
3
3
|
_vault;
|
|
4
|
-
|
|
4
|
+
_layer;
|
|
5
|
+
constructor(vault, layer = "read") {
|
|
5
6
|
this._vault = vault;
|
|
7
|
+
this._layer = layer;
|
|
6
8
|
}
|
|
7
9
|
collection(name) {
|
|
8
10
|
const c = this._vault.collection(name);
|
|
11
|
+
const layer = this._layer;
|
|
9
12
|
return {
|
|
10
|
-
get: (id) => c.get(id),
|
|
11
|
-
list: () => c.list(),
|
|
13
|
+
get: (id) => c.get(id, { _layer: layer }),
|
|
14
|
+
list: () => c.list({ _layer: layer }),
|
|
12
15
|
query: () => c.query()
|
|
13
16
|
};
|
|
14
17
|
}
|
|
@@ -17,4 +20,4 @@ var ReadOnlyVaultFacade = class {
|
|
|
17
20
|
export {
|
|
18
21
|
ReadOnlyVaultFacade
|
|
19
22
|
};
|
|
20
|
-
//# sourceMappingURL=chunk-
|
|
23
|
+
//# sourceMappingURL=chunk-ADB7GPM3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/guards/read-only-facade.ts"],"sourcesContent":["import type { Vault } from '../vault.js'\nimport type { Query } from '../query/builder.js'\nimport type { Layer } from '../i18n/policy.js'\nimport type { ReadOnlyVaultFacade as ReadOnlyVaultFacadeContract } from './types.js'\n\n/**\n * Minimal read-only wrapper over a `Vault`. Used as `ctx.vault` inside\n * guard / derivation callbacks so they can fetch related records without\n * acquiring any write capability.\n *\n * The `layer` tags every `get`/`list` read with the resolution layer it\n * belongs to (#285). A guard-seeded facade reads at `'guard'`, a\n * derivation-seeded one at `'derivation'`, so i18nText / dictKey fields\n * resolve under that layer's `onMissing` policy instead of the `'read'`\n * policy — e.g. a guard read can `substitute` a missing locale (lenient\n * default) while the same field `throw`s on an ordinary app read.\n *\n * `query()` is left untagged: the query/aggregate pipeline reads raw\n * `{locale}` maps (no resolution call site), so a layer tag there would be\n * inert. Routing `mv`/`join` resolution through the pipeline is tracked\n * separately (#285 D2/D3).\n */\nexport class ReadOnlyVaultFacade implements ReadOnlyVaultFacadeContract {\n private readonly _vault: Vault\n private readonly _layer: Layer\n\n constructor(vault: Vault, layer: Layer = 'read') {\n this._vault = vault\n this._layer = layer\n }\n\n collection<T = unknown>(name: string): {\n get(id: string): Promise<T | null>\n list(): Promise<T[]>\n query(): Query<T>\n } {\n const c = this._vault.collection<T>(name)\n const layer = this._layer\n return {\n get: (id: string) => c.get(id, { _layer: layer }),\n list: () => c.list({ _layer: layer }),\n query: () => c.query(),\n }\n }\n}\n"],"mappings":";AAsBO,IAAM,sBAAN,MAAiE;AAAA,EACrD;AAAA,EACA;AAAA,EAEjB,YAAY,OAAc,QAAe,QAAQ;AAC/C,SAAK,SAAS;AACd,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,WAAwB,MAItB;AACA,UAAM,IAAI,KAAK,OAAO,WAAc,IAAI;AACxC,UAAM,QAAQ,KAAK;AACnB,WAAO;AAAA,MACL,KAAK,CAAC,OAAe,EAAE,IAAI,IAAI,EAAE,QAAQ,MAAM,CAAC;AAAA,MAChD,MAAM,MAAM,EAAE,KAAK,EAAE,QAAQ,MAAM,CAAC;AAAA,MACpC,OAAO,MAAM,EAAE,MAAM;AAAA,IACvB;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
dekKey
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-F5GWNSE2.js";
|
|
4
4
|
import {
|
|
5
5
|
generateULID
|
|
6
6
|
} from "./chunk-FZU343FL.js";
|
|
@@ -9,10 +9,10 @@ import {
|
|
|
9
9
|
encrypt,
|
|
10
10
|
unwrapKey,
|
|
11
11
|
wrapKey
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-37VGJM3T.js";
|
|
13
13
|
import {
|
|
14
14
|
DelegationTargetMissingError
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-OTWT6BAJ.js";
|
|
16
16
|
|
|
17
17
|
// src/team/delegation.ts
|
|
18
18
|
var DELEGATIONS_COLLECTION = "_delegations";
|
|
@@ -94,4 +94,4 @@ export {
|
|
|
94
94
|
loadActiveDelegations,
|
|
95
95
|
revokeDelegation
|
|
96
96
|
};
|
|
97
|
-
//# sourceMappingURL=chunk-
|
|
97
|
+
//# sourceMappingURL=chunk-AI4USDRI.js.map
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NOYDB_FORMAT_VERSION
|
|
3
|
+
} from "./chunk-TA6HPKWQ.js";
|
|
4
|
+
import {
|
|
5
|
+
bufferToBase64,
|
|
6
|
+
decrypt,
|
|
7
|
+
encrypt,
|
|
8
|
+
generateDEK,
|
|
9
|
+
unwrapCek,
|
|
10
|
+
wrapCek
|
|
11
|
+
} from "./chunk-37VGJM3T.js";
|
|
12
|
+
import {
|
|
13
|
+
RecordCekNotFoundError,
|
|
14
|
+
ValidationError
|
|
15
|
+
} from "./chunk-OTWT6BAJ.js";
|
|
16
|
+
|
|
17
|
+
// src/record-keys/tombstone.ts
|
|
18
|
+
function isTombstone(envelope, encrypted) {
|
|
19
|
+
if (!encrypted) return false;
|
|
20
|
+
return !envelope._data && envelope._cek === void 0;
|
|
21
|
+
}
|
|
22
|
+
function buildTombstone(version, actor) {
|
|
23
|
+
return {
|
|
24
|
+
_noydb: NOYDB_FORMAT_VERSION,
|
|
25
|
+
_v: version,
|
|
26
|
+
_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
27
|
+
_iv: "",
|
|
28
|
+
_data: "",
|
|
29
|
+
...actor ? { _by: actor } : {}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/record-keys/lifecycle.ts
|
|
34
|
+
async function resolveStableCek(deps, id) {
|
|
35
|
+
const cached = deps.cache?.get(id);
|
|
36
|
+
if (cached) return cached;
|
|
37
|
+
const live = await deps.getLive(id);
|
|
38
|
+
if (live?._cek !== void 0) {
|
|
39
|
+
const cek = await unwrapCek(live._cek, await deps.getDEK());
|
|
40
|
+
deps.cache?.set(id, cek, 1);
|
|
41
|
+
return cek;
|
|
42
|
+
}
|
|
43
|
+
const fresh = await generateDEK();
|
|
44
|
+
deps.cache?.set(id, fresh, 1);
|
|
45
|
+
return fresh;
|
|
46
|
+
}
|
|
47
|
+
async function rewrapBodyToDek(envelope, fromDek, toDek) {
|
|
48
|
+
if (envelope._cek !== void 0) {
|
|
49
|
+
const cek = await unwrapCek(envelope._cek, fromDek);
|
|
50
|
+
const plaintext2 = await decrypt(envelope._iv, envelope._data, cek);
|
|
51
|
+
const { iv: iv2, data: data2 } = await encrypt(plaintext2, cek);
|
|
52
|
+
return { _iv: iv2, _data: data2, _cek: await wrapCek(cek, toDek), cek };
|
|
53
|
+
}
|
|
54
|
+
const plaintext = await decrypt(envelope._iv, envelope._data, fromDek);
|
|
55
|
+
const { iv, data } = await encrypt(plaintext, toDek);
|
|
56
|
+
return { _iv: iv, _data: data, cek: null };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/record-keys/sealing.ts
|
|
60
|
+
var subtle = globalThis.crypto.subtle;
|
|
61
|
+
var SEALED_CEK_NS = "_sealed_cek";
|
|
62
|
+
async function sealRecordToHost(ctx, collection, id, hostSealer, opts) {
|
|
63
|
+
if (collection.includes("/")) throw new ValidationError(`sealRecordToHost: collection "${collection}" must not contain "/"`);
|
|
64
|
+
if (id.includes("/")) throw new ValidationError(`sealRecordToHost: id "${id}" must not contain "/"`);
|
|
65
|
+
const live = await ctx.adapter.get(ctx.vault, collection, id);
|
|
66
|
+
if (!live || live._cek === void 0) {
|
|
67
|
+
throw new RecordCekNotFoundError(collection, id);
|
|
68
|
+
}
|
|
69
|
+
const dek = await ctx.getDEK(collection);
|
|
70
|
+
const cek = await unwrapCek(live._cek, dek);
|
|
71
|
+
const rawCek = await subtle.exportKey("raw", cek);
|
|
72
|
+
const cekB64 = bufferToBase64(rawCek);
|
|
73
|
+
const hint = await hostSealer.publishRecipientHint();
|
|
74
|
+
if (hint.pid.includes("/")) throw new ValidationError(`sealRecordToHost: recipient pid "${hint.pid}" must not contain "/"`);
|
|
75
|
+
const binding = {
|
|
76
|
+
collection,
|
|
77
|
+
id,
|
|
78
|
+
cek: cekB64,
|
|
79
|
+
expiresAt: opts.expiresAt
|
|
80
|
+
};
|
|
81
|
+
const sealed = await hostSealer.sealForRecipient(
|
|
82
|
+
new TextEncoder().encode(JSON.stringify(binding)),
|
|
83
|
+
hint
|
|
84
|
+
);
|
|
85
|
+
const delivery = {
|
|
86
|
+
v: 1,
|
|
87
|
+
_noydb_sealed_cek: 1,
|
|
88
|
+
pid: hint.pid,
|
|
89
|
+
payload: bufferToBase64(sealed),
|
|
90
|
+
expiresAt: opts.expiresAt
|
|
91
|
+
};
|
|
92
|
+
const envelopeKey = `${collection}/${id}/${hint.pid}`;
|
|
93
|
+
const prior = await ctx.adapter.get(ctx.vault, SEALED_CEK_NS, envelopeKey);
|
|
94
|
+
const env = {
|
|
95
|
+
_noydb: NOYDB_FORMAT_VERSION,
|
|
96
|
+
_v: (prior?._v ?? 0) + 1,
|
|
97
|
+
_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
98
|
+
// AES-GCM bypassed — the sealing layer is the security boundary, exactly
|
|
99
|
+
// like the managed-passphrase `_meta/sealed-passphrase` envelope.
|
|
100
|
+
_iv: "",
|
|
101
|
+
_data: JSON.stringify(delivery),
|
|
102
|
+
...ctx.actor ? { _by: ctx.actor } : {}
|
|
103
|
+
};
|
|
104
|
+
await ctx.adapter.put(ctx.vault, SEALED_CEK_NS, envelopeKey, env);
|
|
105
|
+
return { pid: hint.pid, envelopeKey };
|
|
106
|
+
}
|
|
107
|
+
async function revokeSealedRecord(ctx, collection, id, pid) {
|
|
108
|
+
await ctx.adapter.delete(ctx.vault, SEALED_CEK_NS, `${collection}/${id}/${pid}`);
|
|
109
|
+
}
|
|
110
|
+
async function rotateRecordCek(ctx, collection, id) {
|
|
111
|
+
const live = await ctx.adapter.get(ctx.vault, collection, id);
|
|
112
|
+
if (!live || live._cek === void 0) {
|
|
113
|
+
throw new RecordCekNotFoundError(collection, id);
|
|
114
|
+
}
|
|
115
|
+
const dek = await ctx.getDEK(collection);
|
|
116
|
+
const oldCek = await unwrapCek(live._cek, dek);
|
|
117
|
+
const json = await decrypt(live._iv, live._data, oldCek);
|
|
118
|
+
const newCek = await generateDEK();
|
|
119
|
+
const { iv, data } = await encrypt(json, newCek);
|
|
120
|
+
const env = {
|
|
121
|
+
_noydb: NOYDB_FORMAT_VERSION,
|
|
122
|
+
_v: live._v + 1,
|
|
123
|
+
_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
124
|
+
_iv: iv,
|
|
125
|
+
_data: data,
|
|
126
|
+
_cek: await wrapCek(newCek, dek),
|
|
127
|
+
...ctx.actor ? { _by: ctx.actor } : {},
|
|
128
|
+
...live._tier !== void 0 ? { _tier: live._tier } : {},
|
|
129
|
+
...live._det !== void 0 ? { _det: live._det } : {}
|
|
130
|
+
};
|
|
131
|
+
await ctx.adapter.put(ctx.vault, collection, id, env);
|
|
132
|
+
await ctx.invalidateRecordCaches(collection, id);
|
|
133
|
+
const prefix = `${collection}/${id}/`;
|
|
134
|
+
const keys = await ctx.adapter.list(ctx.vault, SEALED_CEK_NS);
|
|
135
|
+
for (const key of keys) {
|
|
136
|
+
if (key.startsWith(prefix)) {
|
|
137
|
+
await ctx.adapter.delete(ctx.vault, SEALED_CEK_NS, key);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export {
|
|
143
|
+
isTombstone,
|
|
144
|
+
buildTombstone,
|
|
145
|
+
resolveStableCek,
|
|
146
|
+
rewrapBodyToDek,
|
|
147
|
+
sealRecordToHost,
|
|
148
|
+
revokeSealedRecord,
|
|
149
|
+
rotateRecordCek
|
|
150
|
+
};
|
|
151
|
+
//# sourceMappingURL=chunk-BZW5IL43.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/record-keys/tombstone.ts","../src/record-keys/lifecycle.ts","../src/record-keys/sealing.ts"],"sourcesContent":["/**\n * Tombstone shape + predicate for the per-record-key (CEK) layer.\n *\n * A *tombstone* is the residue a GDPR crypto-shred (`vault.forget()`, #304)\n * leaves on disk: the live envelope rewritten to `{ _noydb, _v, _ts, _by,\n * _iv:'', _data:'' }`, dropping `_iv`/`_data`/`_cek`/`_det`. With the wrapped\n * per-record CEK gone, the body — and every history version under the same\n * CEK — is permanently undecryptable, while the record KEY survives so the\n * version counter and \"record existed and was erased\" persist for the audit\n * ledger.\n *\n * These two helpers are the pure, collection-independent core of that\n * contract: {@link buildTombstone} mints the shape, {@link isTombstone}\n * recognises it on the read path. The orchestration that *writes* a tombstone\n * (`_writeTombstone`) and drives `vault.forget()` lives with the collection /\n * vault; it calls into these.\n */\nimport { NOYDB_FORMAT_VERSION, type EncryptedEnvelope } from '../types.js'\n\n/**\n * Is this envelope a crypto-shred tombstone?\n *\n * A tombstone carries no body (`_data` empty) and no wrapped CEK (`_cek`\n * absent) — decrypting it would call `decrypt('', '', dek)` → an AES-GCM\n * throw, so every read choke point must short-circuit to `null` instead.\n *\n * `encrypted` is the collection's encryption flag: on an unencrypted\n * collection there are no envelopes to shred, so nothing is ever a tombstone.\n * (Legacy migration envelopes carry non-empty `_iv`/`_data`, so they are not\n * tombstones and stay readable.)\n */\nexport function isTombstone(envelope: EncryptedEnvelope, encrypted: boolean): boolean {\n if (!encrypted) return false\n return !envelope._data && envelope._cek === undefined\n}\n\n/**\n * Mint a tombstone envelope from a record's live version + actor.\n *\n * Keeps `_v` (the displaced version) so the counter is monotonic across the\n * shred, stamps a fresh `_ts`, and records `_by` when an actor is supplied.\n * Deliberately omits `_iv`/`_data`/`_cek`/`_det` — that omission *is* the\n * erasure.\n */\nexport function buildTombstone(version: number, actor: string): EncryptedEnvelope {\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: version,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: '',\n ...(actor ? { _by: actor } : {}),\n }\n}\n","/**\n * Per-record CEK lifecycle helpers for the WRITE path (#356 foundation).\n *\n * These are the collection-coupled CEK operations, lifted off `Collection`\n * behind narrow deps so the kernel file delegates instead of carrying the\n * crypto inline:\n *\n * - {@link resolveStableCek} — the stable-CEK rule: an update or history\n * snapshot reuses the record's existing CEK so a single CEK delete kills\n * the whole version chain; a genuine insert (or legacy record being\n * migrated) mints a fresh one.\n * - {@link rewrapBodyToDek} — move a record body's wrapping from one DEK to\n * another (tier elevate/demote, bundle re-key) WITHOUT changing the body\n * key: unwrap the CEK under the source DEK, re-encrypt the body under the\n * same CEK, re-wrap the CEK under the destination DEK. History-chain\n * identity is preserved because the body key never changes.\n *\n * Both are pure functions over their deps — the `cekCache` ownership stays on\n * the collection (its lifetime is tied to `load()`), so callers cache the\n * returned key themselves.\n */\nimport { encrypt, decrypt, generateDEK, wrapCek, unwrapCek } from '../crypto.js'\nimport type { EncryptedEnvelope } from '../types.js'\nimport type { Lru } from '../cache/index.js'\n\n/** Dependencies {@link resolveStableCek} needs from its collection. */\nexport interface StableCekDeps {\n /** The collection's per-record CEK cache (`null` → no caching). */\n readonly cache: Lru<string, CryptoKey> | null\n /** Read the record's live envelope (to recover an existing `_cek`). */\n getLive(id: string): Promise<EncryptedEnvelope | null>\n /** The DEK the CEK is wrapped under (the collection DEK on the normal path). */\n getDEK(): Promise<CryptoKey>\n}\n\n/**\n * Resolve the stable CEK for a record on the write path. Caches the resolved\n * key under `id` so an update + its history snapshot share one CEK.\n */\nexport async function resolveStableCek(deps: StableCekDeps, id: string): Promise<CryptoKey> {\n const cached = deps.cache?.get(id)\n if (cached) return cached\n\n const live = await deps.getLive(id)\n if (live?._cek !== undefined) {\n const cek = await unwrapCek(live._cek, await deps.getDEK())\n deps.cache?.set(id, cek, 1)\n return cek\n }\n\n const fresh = await generateDEK()\n deps.cache?.set(id, fresh, 1)\n return fresh\n}\n\n/** Re-wrapped body bytes + the (optional) CEK to cache. */\nexport interface RewrappedBody {\n readonly _iv: string\n readonly _data: string\n /** Present iff the source envelope carried a CEK (per-record-key record). */\n readonly _cek?: string\n /** The body key when one exists, so the caller can cache it; `null` for a legacy record. */\n readonly cek: CryptoKey | null\n}\n\n/**\n * Move a record body from `fromDek` to `toDek`.\n *\n * - Per-record-key record (`_cek` present): unwrap the CEK under `fromDek`,\n * re-encrypt the body under the SAME CEK, re-wrap the CEK under `toDek`. The\n * body key is unchanged → history-chain identity preserved.\n * - Legacy record (no `_cek`): decrypt under `fromDek`, re-encrypt under `toDek`\n * directly — byte-for-byte the pre-CEK behaviour.\n */\nexport async function rewrapBodyToDek(\n envelope: Pick<EncryptedEnvelope, '_iv' | '_data' | '_cek'>,\n fromDek: CryptoKey,\n toDek: CryptoKey,\n): Promise<RewrappedBody> {\n if (envelope._cek !== undefined) {\n const cek = await unwrapCek(envelope._cek, fromDek)\n const plaintext = await decrypt(envelope._iv, envelope._data, cek)\n const { iv, data } = await encrypt(plaintext, cek)\n return { _iv: iv, _data: data, _cek: await wrapCek(cek, toDek), cek }\n }\n const plaintext = await decrypt(envelope._iv, envelope._data, fromDek)\n const { iv, data } = await encrypt(plaintext, toDek)\n return { _iv: iv, _data: data, cek: null }\n}\n","/**\n * Record-scoped CEK sealing — grantor side (#306 slices 2-3).\n *\n * The **grantor** holds the collection DEK and seals ONE record's CEK to an\n * `at-*` host so that host — and only that host — can decrypt exactly that\n * record, with no access to the vault DEK and no ability to read any other\n * record. This is the counterpart to the host-side `openSealedRecord`\n * (`@noy-db/hub/sealed-record`), which holds no DEK.\n *\n * These functions are the orchestration behind `vault.sealRecordToHost` /\n * `vault.revokeSealedRecord` / `vault.rotateRecordCek`, lifted off `Vault`\n * behind a narrow {@link SealingContext} so the kernel file delegates. They\n * live in `record-keys/` (the vault-side CEK policy layer), NOT in\n * `sealed-record/`, so the host-side subpath stays DEK-free — they import only\n * the wire *types* from there.\n */\nimport { encrypt, decrypt, generateDEK, wrapCek, unwrapCek, bufferToBase64 } from '../crypto.js'\nimport { NOYDB_FORMAT_VERSION, type EncryptedEnvelope, type NoydbStore } from '../types.js'\nimport { RecordCekNotFoundError, ValidationError } from '../errors.js'\nimport type { RecipientSealer } from '../team/managed-passphrase.js'\nimport type { SealedCekBinding, SealedCekDeliveryEnvelope } from '../sealed-record/types.js'\n\nconst subtle = globalThis.crypto.subtle\n\n/** The `_sealed_cek` delivery namespace (one envelope per `collection/id/pid`). */\nconst SEALED_CEK_NS = '_sealed_cek'\n\n/** What the grantor functions need from their `Vault`. */\nexport interface SealingContext {\n readonly adapter: NoydbStore\n /** Vault id (the adapter's first coordinate). */\n readonly vault: string\n /** Resolve the DEK a record's CEK is wrapped under. */\n getDEK(collection: string): Promise<CryptoKey>\n /** Actor id stamped on `_by` (empty string → omit). */\n readonly actor: string\n /** Evict the per-record CEK cache + decrypted-record cache after a rotation. */\n invalidateRecordCaches(collection: string, id: string): Promise<void>\n}\n\n/**\n * Seal a record's CEK to a host. See `vault.sealRecordToHost` for the contract.\n * Returns `{ pid, envelopeKey }`.\n */\nexport async function sealRecordToHost(\n ctx: SealingContext,\n collection: string,\n id: string,\n hostSealer: RecipientSealer,\n opts: { expiresAt: string },\n): Promise<{ pid: string; envelopeKey: string }> {\n // Guard separators: the `_sealed_cek` id is `collection/id/pid`, so a `/` in\n // any component would make the prefix-delete in rotateRecordCek() (which\n // matches `${collection}/${id}/`) ambiguous and could over- or under-match.\n if (collection.includes('/')) throw new ValidationError(`sealRecordToHost: collection \"${collection}\" must not contain \"/\"`)\n if (id.includes('/')) throw new ValidationError(`sealRecordToHost: id \"${id}\" must not contain \"/\"`)\n\n const live = await ctx.adapter.get(ctx.vault, collection, id)\n if (!live || live._cek === undefined) {\n throw new RecordCekNotFoundError(collection, id)\n }\n\n const dek = await ctx.getDEK(collection)\n const cek = await unwrapCek(live._cek, dek)\n const rawCek = await subtle.exportKey('raw', cek)\n const cekB64 = bufferToBase64(rawCek)\n\n const hint = await hostSealer.publishRecipientHint()\n if (hint.pid.includes('/')) throw new ValidationError(`sealRecordToHost: recipient pid \"${hint.pid}\" must not contain \"/\"`)\n\n const binding: SealedCekBinding = {\n collection,\n id,\n cek: cekB64,\n expiresAt: opts.expiresAt,\n }\n const sealed = await hostSealer.sealForRecipient(\n new TextEncoder().encode(JSON.stringify(binding)),\n hint,\n )\n\n const delivery: SealedCekDeliveryEnvelope = {\n v: 1,\n _noydb_sealed_cek: 1,\n pid: hint.pid,\n payload: bufferToBase64(sealed),\n expiresAt: opts.expiresAt,\n }\n\n const envelopeKey = `${collection}/${id}/${hint.pid}`\n const prior = await ctx.adapter.get(ctx.vault, SEALED_CEK_NS, envelopeKey)\n const env: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: (prior?._v ?? 0) + 1,\n _ts: new Date().toISOString(),\n // AES-GCM bypassed — the sealing layer is the security boundary, exactly\n // like the managed-passphrase `_meta/sealed-passphrase` envelope.\n _iv: '',\n _data: JSON.stringify(delivery),\n ...(ctx.actor ? { _by: ctx.actor } : {}),\n }\n await ctx.adapter.put(ctx.vault, SEALED_CEK_NS, envelopeKey, env)\n\n return { pid: hint.pid, envelopeKey }\n}\n\n/**\n * Soft-revoke one sealed-CEK delivery envelope (delete it from the store). A\n * host that already fetched the envelope keeps whatever it cached; for a hard\n * revocation use {@link rotateRecordCek}.\n */\nexport async function revokeSealedRecord(\n ctx: SealingContext,\n collection: string,\n id: string,\n pid: string,\n): Promise<void> {\n await ctx.adapter.delete(ctx.vault, SEALED_CEK_NS, `${collection}/${id}/${pid}`)\n}\n\n/**\n * HARD-rotate a record's CEK: re-encrypt the live body under a fresh CEK, evict\n * caches, and delete EVERY sealed-CEK delivery envelope for the record. See\n * `vault.rotateRecordCek` for why this bypasses `Collection.put` (no guards, no\n * history bump — a key rotation must not re-encrypt prior history under the new\n * CEK).\n */\nexport async function rotateRecordCek(\n ctx: SealingContext,\n collection: string,\n id: string,\n): Promise<void> {\n const live = await ctx.adapter.get(ctx.vault, collection, id)\n if (!live || live._cek === undefined) {\n throw new RecordCekNotFoundError(collection, id)\n }\n\n const dek = await ctx.getDEK(collection)\n const oldCek = await unwrapCek(live._cek, dek)\n const json = await decrypt(live._iv, live._data, oldCek)\n\n const newCek = await generateDEK()\n const { iv, data } = await encrypt(json, newCek)\n\n const env: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: live._v + 1,\n _ts: new Date().toISOString(),\n _iv: iv,\n _data: data,\n _cek: await wrapCek(newCek, dek),\n ...(ctx.actor ? { _by: ctx.actor } : {}),\n ...(live._tier !== undefined ? { _tier: live._tier } : {}),\n ...(live._det !== undefined ? { _det: live._det } : {}),\n }\n await ctx.adapter.put(ctx.vault, collection, id, env)\n\n // Evict BOTH caches synchronously so no read returns the stale old-CEK record.\n await ctx.invalidateRecordCaches(collection, id)\n\n // Delete every sealed-CEK delivery envelope for this record — all pids.\n const prefix = `${collection}/${id}/`\n const keys = await ctx.adapter.list(ctx.vault, SEALED_CEK_NS)\n for (const key of keys) {\n if (key.startsWith(prefix)) {\n await ctx.adapter.delete(ctx.vault, SEALED_CEK_NS, key)\n }\n }\n}\n"],"mappings":"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","names":["plaintext","iv","data"]}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IllegalTransitionError,
|
|
3
|
+
RecordLockedError,
|
|
4
|
+
ValidationError
|
|
5
|
+
} from "./chunk-OTWT6BAJ.js";
|
|
6
|
+
|
|
7
|
+
// src/guards/with-guard.ts
|
|
8
|
+
function withGuard(strategy) {
|
|
9
|
+
if (!strategy.collection || strategy.collection.length === 0) {
|
|
10
|
+
throw new ValidationError("withGuard: collection name is required");
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
__noydb_strategy: "guard",
|
|
14
|
+
spec: strategy
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/guards/immutable-guard.ts
|
|
19
|
+
function recordId(record) {
|
|
20
|
+
const id = record?.id;
|
|
21
|
+
return typeof id === "string" ? id : "";
|
|
22
|
+
}
|
|
23
|
+
function immutableGuard(config) {
|
|
24
|
+
const { collection, after, appendOnly, amendmentRoles, amendmentInvariant } = config;
|
|
25
|
+
if (appendOnly && after !== void 0) {
|
|
26
|
+
throw new ValidationError("immutableGuard: `after` and `appendOnly` are mutually exclusive");
|
|
27
|
+
}
|
|
28
|
+
if (!appendOnly && after === void 0) {
|
|
29
|
+
throw new ValidationError("immutableGuard: provide `after` or `appendOnly: true`");
|
|
30
|
+
}
|
|
31
|
+
const isImmutable = appendOnly ? () => true : after;
|
|
32
|
+
const reason = appendOnly ? "append-only collection" : "record is immutable after issue";
|
|
33
|
+
const spec = {
|
|
34
|
+
collection,
|
|
35
|
+
// Block updates to an already-immutable record. Inserts (existing
|
|
36
|
+
// null) and the transition write that first makes the record
|
|
37
|
+
// immutable are allowed — `after` reads the prior state.
|
|
38
|
+
check: (incoming, ctx) => {
|
|
39
|
+
if (ctx.existing !== null && isImmutable(ctx.existing)) {
|
|
40
|
+
throw new RecordLockedError(collection, recordId(incoming), reason);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
// Block deletes of an immutable record.
|
|
44
|
+
onDelete: (existing) => {
|
|
45
|
+
if (isImmutable(existing)) {
|
|
46
|
+
throw new RecordLockedError(collection, recordId(existing), reason);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
// The authorized override: inside an amendment transaction the
|
|
50
|
+
// check/onDelete are skipped and the change is ledgered. By default
|
|
51
|
+
// there is no extra invariant — the amendment itself is the
|
|
52
|
+
// sanctioned exception. Callers may supply `amendmentInvariant` to
|
|
53
|
+
// keep a constraint inviolable even under amendment (e.g. forbid
|
|
54
|
+
// deletes, or preserve a cross-record sum); a throw reverts the
|
|
55
|
+
// amendment and surfaces as `InvariantError`.
|
|
56
|
+
amendment: {
|
|
57
|
+
roles: amendmentRoles ?? ["admin", "owner"],
|
|
58
|
+
invariant: amendmentInvariant ?? (() => {
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
return withGuard(spec);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/guards/transition-guard.ts
|
|
66
|
+
function recordId2(record) {
|
|
67
|
+
const id = record?.id;
|
|
68
|
+
return typeof id === "string" ? id : "";
|
|
69
|
+
}
|
|
70
|
+
function stateOf(record, field) {
|
|
71
|
+
const v = record[field];
|
|
72
|
+
return typeof v === "string" ? v : String(v);
|
|
73
|
+
}
|
|
74
|
+
function transitionGuard(config) {
|
|
75
|
+
const { collection, field, transitions, initial, amendmentRoles, amendmentInvariant } = config;
|
|
76
|
+
const allowIdempotent = config.allowIdempotent ?? true;
|
|
77
|
+
if (!field) {
|
|
78
|
+
throw new ValidationError("transitionGuard: `field` is required");
|
|
79
|
+
}
|
|
80
|
+
if (transitions === void 0 || typeof transitions !== "object") {
|
|
81
|
+
throw new ValidationError("transitionGuard: `transitions` must be a state\u2192states map");
|
|
82
|
+
}
|
|
83
|
+
const spec = {
|
|
84
|
+
collection,
|
|
85
|
+
check: (incoming, ctx) => {
|
|
86
|
+
const rec = incoming;
|
|
87
|
+
const to = stateOf(rec, field);
|
|
88
|
+
if (ctx.existing === null) {
|
|
89
|
+
if (initial !== void 0 && !initial.includes(to)) {
|
|
90
|
+
throw new IllegalTransitionError(collection, recordId2(rec), "(none)", to);
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const from = stateOf(ctx.existing, field);
|
|
95
|
+
if (from === to) {
|
|
96
|
+
if (allowIdempotent) return;
|
|
97
|
+
throw new IllegalTransitionError(collection, recordId2(rec), from, to);
|
|
98
|
+
}
|
|
99
|
+
const allowed = transitions[from] ?? [];
|
|
100
|
+
if (!allowed.includes(to)) {
|
|
101
|
+
throw new IllegalTransitionError(collection, recordId2(rec), from, to);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
// The authorized override: inside an amendment transaction the check
|
|
105
|
+
// is skipped and the change is ledgered. By default no extra invariant
|
|
106
|
+
// — the amendment itself is the sanctioned exception. Callers may
|
|
107
|
+
// supply `amendmentInvariant` to keep a constraint inviolable even
|
|
108
|
+
// under amendment; a throw reverts the amendment as `InvariantError`.
|
|
109
|
+
amendment: {
|
|
110
|
+
roles: amendmentRoles ?? ["admin", "owner"],
|
|
111
|
+
invariant: amendmentInvariant ?? (() => {
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
return withGuard(spec);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export {
|
|
119
|
+
withGuard,
|
|
120
|
+
immutableGuard,
|
|
121
|
+
transitionGuard
|
|
122
|
+
};
|
|
123
|
+
//# sourceMappingURL=chunk-C2RJVZZL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/guards/with-guard.ts","../src/guards/immutable-guard.ts","../src/guards/transition-guard.ts"],"sourcesContent":["import { ValidationError } from '../errors.js'\nimport type { GuardStrategy, GuardStrategyHandle } from './types.js'\n\n/**\n * Register a guard for a collection. Guards run on every `put()` /\n * `delete()` for the named collection (after permissions, before\n * encryption) and may:\n *\n * - `check` — block writes by throwing (typically `RecordLockedError`)\n * - `frozenFields` — freeze specific fields once a condition is true\n * - `amendment` — declare an authorized-override path with invariant\n *\n * Pass the returned handle to `createNoydb({ strategies: [...] })`.\n *\n * @see docs/superpowers/specs/2026-05-18-guards-design.md\n */\nexport function withGuard<T extends Record<string, unknown>>(\n strategy: GuardStrategy<T>,\n): GuardStrategyHandle<T> {\n if (!strategy.collection || strategy.collection.length === 0) {\n throw new ValidationError('withGuard: collection name is required')\n }\n return {\n __noydb_strategy: 'guard',\n spec: strategy,\n }\n}\n","/**\n * `immutableGuard` — declarative WORM / append-only sugar over the guard\n * subsystem.\n *\n * Issued fiscal documents (invoices, DDTs) must be immutable after issue.\n * That is expressible today with a hand-rolled `withGuard` (block on\n * `check`/`onDelete`, allow an admin `amendment`), but the boilerplate is\n * repetitive and easy to get subtly wrong. `immutableGuard` generates\n * exactly that guard from a declarative config, reusing the guard\n * machinery wholesale — `check`/`onDelete` rejection, the ledgered\n * `amendment` override, and composition with `periods`/`history`.\n *\n * ```ts\n * createNoydb({ guardStrategies: [\n * immutableGuard({\n * collection: 'invoices',\n * after: (r) => r.status === 'issued', // immutable once issued\n * }),\n * ] })\n * ```\n *\n * A record is mutable until `after(record)` holds; from then on, updates\n * and deletes throw `RecordLockedError` unless performed inside an\n * `amendment` transaction by an authorized role (the override is\n * ledgered by the guard amendment mechanism). `appendOnly: true` is\n * shorthand for `after: () => true` — immutable from creation.\n */\n\nimport { withGuard } from './with-guard.js'\nimport type { GuardStrategy, GuardStrategyHandle, GuardContext, GuardChange } from './types.js'\nimport { RecordLockedError, ValidationError } from '../errors.js'\n\nexport interface ImmutableGuardConfig<T extends Record<string, unknown>> {\n /** The collection to make WORM. */\n collection: string\n /**\n * A record becomes immutable once this predicate holds. Evaluated on\n * the *existing* (already-persisted) record, so the write that first\n * makes it true is still allowed; subsequent writes are blocked.\n * Mutually exclusive with `appendOnly`.\n */\n after?: (record: T) => boolean\n /** Shorthand for `after: () => true` — immutable from creation. */\n appendOnly?: boolean\n /** Roles permitted to override via an amendment transaction. Default `['admin', 'owner']`. */\n amendmentRoles?: ReadonlyArray<'admin' | 'owner'>\n /**\n * Optional set-level invariant run over the amendment change-set after\n * the writes execute. Signature matches `GuardStrategy.amendment.invariant`\n * exactly: it receives every {before, after} pair touching this\n * collection in the amendment plus the guard context; throwing reverts\n * the whole amendment and surfaces as `InvariantError`.\n *\n * Use this to keep a constraint inviolable EVEN under amendment — e.g.\n * forbid deleting an issued document by re-throwing on any\n * `before !== null && after === null` change, or assert a cross-record\n * sum is preserved. When omitted the amendment is unconditionally\n * allowed (the amendment itself is the sanctioned, ledgered override) —\n * this is the backward-compatible default.\n */\n amendmentInvariant?: (\n changes: ReadonlyArray<GuardChange<T>>,\n ctx: GuardContext<T>,\n ) => Promise<void> | void\n}\n\nfunction recordId(record: Record<string, unknown> | null): string {\n const id = record?.id\n return typeof id === 'string' ? id : ''\n}\n\n/**\n * Build an immutability guard. Pass the returned handle to\n * `createNoydb({ guardStrategies: [...] })`.\n */\nexport function immutableGuard<T extends Record<string, unknown>>(\n config: ImmutableGuardConfig<T>,\n): GuardStrategyHandle<T> {\n const { collection, after, appendOnly, amendmentRoles, amendmentInvariant } = config\n if (appendOnly && after !== undefined) {\n throw new ValidationError('immutableGuard: `after` and `appendOnly` are mutually exclusive')\n }\n if (!appendOnly && after === undefined) {\n throw new ValidationError('immutableGuard: provide `after` or `appendOnly: true`')\n }\n\n const isImmutable: (record: T) => boolean = appendOnly ? () => true : after!\n const reason = appendOnly ? 'append-only collection' : 'record is immutable after issue'\n\n const spec: GuardStrategy<T> = {\n collection,\n // Block updates to an already-immutable record. Inserts (existing\n // null) and the transition write that first makes the record\n // immutable are allowed — `after` reads the prior state.\n check: (incoming: T, ctx: GuardContext<T>) => {\n if (ctx.existing !== null && isImmutable(ctx.existing)) {\n throw new RecordLockedError(collection, recordId(incoming as Record<string, unknown>), reason)\n }\n },\n // Block deletes of an immutable record.\n onDelete: (existing: T) => {\n if (isImmutable(existing)) {\n throw new RecordLockedError(collection, recordId(existing as Record<string, unknown>), reason)\n }\n },\n // The authorized override: inside an amendment transaction the\n // check/onDelete are skipped and the change is ledgered. By default\n // there is no extra invariant — the amendment itself is the\n // sanctioned exception. Callers may supply `amendmentInvariant` to\n // keep a constraint inviolable even under amendment (e.g. forbid\n // deletes, or preserve a cross-record sum); a throw reverts the\n // amendment and surfaces as `InvariantError`.\n amendment: {\n roles: amendmentRoles ?? ['admin', 'owner'],\n invariant: amendmentInvariant ?? (() => {\n /* allow — the amendment is the override, and is ledgered */\n }),\n },\n }\n\n return withGuard<T>(spec)\n}\n","/**\n * `transitionGuard` — declarative state-machine sugar over the guard\n * subsystem.\n *\n * Any record with a lifecycle field (invoice `status`, order state,\n * ticket workflow, subscription phase) needs transition validation: a\n * write may only move the field along a declared arc. That is expressible\n * with a hand-rolled `withGuard({ check })`, but every consumer\n * re-implements the same graph lookup + error. `transitionGuard`\n * generates exactly that guard from a state graph, reusing the guard\n * machinery wholesale — `check` rejection, the ledgered `amendment`\n * override, and composition with `periods`/`history`.\n *\n * It generalizes {@link immutableGuard}: WORM is the special case \"every\n * state has no outgoing arcs\", i.e. `transitions` mapping each state to `[]`.\n *\n * ```ts\n * createNoydb({ guardStrategies: [\n * transitionGuard<Sale>({\n * collection: 'sales', field: 'status',\n * transitions: { // absence of an arc = forbidden\n * draft: ['to_verify', 'cancelled'],\n * to_verify: ['proforma', 'draft', 'cancelled'],\n * proforma: ['invoiced', 'cancelled'],\n * invoiced: ['paid'], paid: [], cancelled: [],\n * },\n * initial: ['draft', 'to_verify'], // allowed status on insert\n * }),\n * ] })\n * ```\n *\n * Semantics:\n * - **Insert** (`ctx.existing === null`): `incoming[field]` must be in\n * `initial`. When `initial` is omitted, any value is allowed on insert.\n * - **Update**: the arc `(existing[field] → incoming[field])` must be\n * listed in `transitions[from]`, else `IllegalTransitionError`. A\n * same-value write (`from === to`) is allowed when `allowIdempotent`\n * (default `true`) — so writes that touch other fields without moving\n * state pass.\n * - **Override**: inside an `amendment` transaction by an authorized role\n * the check is skipped and the change is ledgered (mirrors every guard).\n *\n * The status graph is caller-supplied data — no UI, no domain logic.\n */\n\nimport { withGuard } from './with-guard.js'\nimport type { GuardStrategy, GuardStrategyHandle, GuardContext, GuardChange } from './types.js'\nimport { IllegalTransitionError, ValidationError } from '../errors.js'\n\nexport interface TransitionGuardConfig<T extends Record<string, unknown>> {\n /** The collection whose state field is governed. */\n collection: string\n /** The state field on the record (e.g. `'status'`). */\n field: keyof T & string\n /**\n * The transition graph: each state maps to the states it may move to.\n * A state absent from the map (or mapped to `[]`) is terminal — no\n * outgoing arc, so any non-idempotent write from it is rejected.\n */\n transitions: Readonly<Record<string, readonly string[]>>\n /**\n * States allowed as the initial value on insert (`existing === null`).\n * Omit to allow any value on insert.\n */\n initial?: readonly string[]\n /**\n * Allow a same-value write (`from === to`) on update. Default `true` —\n * lets a put that changes other fields, but not the state, through.\n */\n allowIdempotent?: boolean\n /** Roles permitted to override via an amendment transaction. Default `['admin', 'owner']`. */\n amendmentRoles?: ReadonlyArray<'admin' | 'owner'>\n /**\n * Optional set-level invariant run over the amendment change-set after\n * the writes execute. Signature matches `GuardStrategy.amendment.invariant`\n * exactly. When omitted the amendment is unconditionally allowed (the\n * amendment itself is the sanctioned, ledgered override) — the\n * backward-compatible default. Mirrors {@link immutableGuard}.\n */\n amendmentInvariant?: (\n changes: ReadonlyArray<GuardChange<T>>,\n ctx: GuardContext<T>,\n ) => Promise<void> | void\n}\n\nfunction recordId(record: Record<string, unknown> | null): string {\n const id = record?.id\n return typeof id === 'string' ? id : ''\n}\n\nfunction stateOf(record: Record<string, unknown>, field: string): string {\n const v = record[field]\n return typeof v === 'string' ? v : String(v)\n}\n\n/**\n * Build a state-machine transition guard. Pass the returned handle to\n * `createNoydb({ guardStrategies: [...] })`.\n */\nexport function transitionGuard<T extends Record<string, unknown>>(\n config: TransitionGuardConfig<T>,\n): GuardStrategyHandle<T> {\n const { collection, field, transitions, initial, amendmentRoles, amendmentInvariant } = config\n const allowIdempotent = config.allowIdempotent ?? true\n\n if (!field) {\n throw new ValidationError('transitionGuard: `field` is required')\n }\n if (transitions === undefined || typeof transitions !== 'object') {\n throw new ValidationError('transitionGuard: `transitions` must be a state→states map')\n }\n\n const spec: GuardStrategy<T> = {\n collection,\n check: (incoming: T, ctx: GuardContext<T>) => {\n const rec = incoming as Record<string, unknown>\n const to = stateOf(rec, field)\n\n // Insert — gate on the allowed initial set (any value if unset).\n if (ctx.existing === null) {\n if (initial !== undefined && !initial.includes(to)) {\n throw new IllegalTransitionError(collection, recordId(rec), '(none)', to)\n }\n return\n }\n\n // Update — the arc (from → to) must be a declared edge.\n const from = stateOf(ctx.existing as Record<string, unknown>, field)\n if (from === to) {\n if (allowIdempotent) return\n throw new IllegalTransitionError(collection, recordId(rec), from, to)\n }\n const allowed = transitions[from] ?? []\n if (!allowed.includes(to)) {\n throw new IllegalTransitionError(collection, recordId(rec), from, to)\n }\n },\n // The authorized override: inside an amendment transaction the check\n // is skipped and the change is ledgered. By default no extra invariant\n // — the amendment itself is the sanctioned exception. Callers may\n // supply `amendmentInvariant` to keep a constraint inviolable even\n // under amendment; a throw reverts the amendment as `InvariantError`.\n amendment: {\n roles: amendmentRoles ?? ['admin', 'owner'],\n invariant: amendmentInvariant ?? (() => {\n /* allow — the amendment is the override, and is ledgered */\n }),\n },\n }\n\n return withGuard<T>(spec)\n}\n"],"mappings":"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","names":["recordId"]}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
NOYDB_FORMAT_VERSION
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-TA6HPKWQ.js";
|
|
4
4
|
import {
|
|
5
5
|
decrypt,
|
|
6
6
|
encrypt
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-37VGJM3T.js";
|
|
8
8
|
|
|
9
9
|
// src/persisted-schemas/storage.ts
|
|
10
10
|
var SCHEMAS_COLLECTION = "_schemas";
|
|
@@ -84,6 +84,49 @@ var MemorySealingKeyProvider = class {
|
|
|
84
84
|
return out;
|
|
85
85
|
}
|
|
86
86
|
};
|
|
87
|
+
async function sealRsaOaepTlv(plaintext, publicKeyPem) {
|
|
88
|
+
const b64 = publicKeyPem.replace(/-----BEGIN PUBLIC KEY-----/, "").replace(/-----END PUBLIC KEY-----/, "").replace(/\s+/g, "");
|
|
89
|
+
const spki = base64ToBytes(b64);
|
|
90
|
+
const recipientPub = await crypto.subtle.importKey(
|
|
91
|
+
"spki",
|
|
92
|
+
spki,
|
|
93
|
+
{ name: "RSA-OAEP", hash: "SHA-256" },
|
|
94
|
+
false,
|
|
95
|
+
["encrypt"]
|
|
96
|
+
);
|
|
97
|
+
const cekBytes = crypto.getRandomValues(new Uint8Array(32));
|
|
98
|
+
const cek = await crypto.subtle.importKey("raw", cekBytes, "AES-GCM", false, ["encrypt"]);
|
|
99
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
100
|
+
const ct = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv }, cek, plaintext));
|
|
101
|
+
const wrapped = new Uint8Array(await crypto.subtle.encrypt({ name: "RSA-OAEP" }, recipientPub, cekBytes));
|
|
102
|
+
cekBytes.fill(0);
|
|
103
|
+
if (wrapped.length !== 256) {
|
|
104
|
+
throw new Error(`sealRsaOaepTlv: expected 256-byte RSA-OAEP wrap, got ${wrapped.length}`);
|
|
105
|
+
}
|
|
106
|
+
const out = new Uint8Array(1 + 256 + 12 + ct.length);
|
|
107
|
+
out[0] = 1;
|
|
108
|
+
out.set(wrapped, 1);
|
|
109
|
+
out.set(iv, 1 + 256);
|
|
110
|
+
out.set(ct, 1 + 256 + 12);
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
function parseRsaOaepTlv(bytes) {
|
|
114
|
+
if (bytes.length < 1 + 256 + 12 + 16) {
|
|
115
|
+
throw new Error("parseRsaOaepTlv: sealed input too short");
|
|
116
|
+
}
|
|
117
|
+
if (bytes[0] !== 1) {
|
|
118
|
+
throw new Error(`parseRsaOaepTlv: unknown TLV version ${bytes[0]}`);
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
wrapped: bytes.subarray(1, 1 + 256),
|
|
122
|
+
iv: bytes.subarray(1 + 256, 1 + 256 + 12),
|
|
123
|
+
ct: bytes.subarray(1 + 256 + 12)
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
async function aesGcmOpen(cekBytes, iv, ct) {
|
|
127
|
+
const cek = await crypto.subtle.importKey("raw", cekBytes, "AES-GCM", false, ["decrypt"]);
|
|
128
|
+
return new Uint8Array(await crypto.subtle.decrypt({ name: "AES-GCM", iv }, cek, ct));
|
|
129
|
+
}
|
|
87
130
|
var MemoryRecipientSealer = class {
|
|
88
131
|
id;
|
|
89
132
|
keypair;
|
|
@@ -112,49 +155,17 @@ var MemoryRecipientSealer = class {
|
|
|
112
155
|
if (typeof pem !== "string") {
|
|
113
156
|
throw new Error("MemoryRecipientSealer.sealForRecipient: hint.material.publicKeyPem missing or not a string");
|
|
114
157
|
}
|
|
115
|
-
|
|
116
|
-
const spki = base64ToBytes(b64);
|
|
117
|
-
const recipientPub = await crypto.subtle.importKey(
|
|
118
|
-
"spki",
|
|
119
|
-
spki,
|
|
120
|
-
{ name: "RSA-OAEP", hash: "SHA-256" },
|
|
121
|
-
false,
|
|
122
|
-
["encrypt"]
|
|
123
|
-
);
|
|
124
|
-
const cekBytes = crypto.getRandomValues(new Uint8Array(32));
|
|
125
|
-
const cek = await crypto.subtle.importKey("raw", cekBytes, "AES-GCM", false, ["encrypt"]);
|
|
126
|
-
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
127
|
-
const ct = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv }, cek, plaintext));
|
|
128
|
-
const wrapped = new Uint8Array(await crypto.subtle.encrypt({ name: "RSA-OAEP" }, recipientPub, cekBytes));
|
|
129
|
-
cekBytes.fill(0);
|
|
130
|
-
if (wrapped.length !== 256) {
|
|
131
|
-
throw new Error(`MemoryRecipientSealer.sealForRecipient: expected 256-byte RSA-OAEP wrap, got ${wrapped.length}`);
|
|
132
|
-
}
|
|
133
|
-
const out = new Uint8Array(1 + 256 + 12 + ct.length);
|
|
134
|
-
out[0] = 1;
|
|
135
|
-
out.set(wrapped, 1);
|
|
136
|
-
out.set(iv, 1 + 256);
|
|
137
|
-
out.set(ct, 1 + 256 + 12);
|
|
138
|
-
return out;
|
|
158
|
+
return sealRsaOaepTlv(plaintext, pem);
|
|
139
159
|
}
|
|
140
160
|
async seal(plaintext) {
|
|
141
161
|
const hint = await this.publishRecipientHint();
|
|
142
162
|
return this.sealForRecipient(plaintext, hint);
|
|
143
163
|
}
|
|
144
164
|
async unseal(bytes) {
|
|
145
|
-
|
|
146
|
-
throw new Error("MemoryRecipientSealer.unseal: sealed input too short");
|
|
147
|
-
}
|
|
148
|
-
if (bytes[0] !== 1) {
|
|
149
|
-
throw new Error(`MemoryRecipientSealer.unseal: unknown TLV version ${bytes[0]}`);
|
|
150
|
-
}
|
|
151
|
-
const wrapped = bytes.subarray(1, 1 + 256);
|
|
152
|
-
const iv = bytes.subarray(1 + 256, 1 + 256 + 12);
|
|
153
|
-
const ct = bytes.subarray(1 + 256 + 12);
|
|
165
|
+
const { wrapped, iv, ct } = parseRsaOaepTlv(bytes);
|
|
154
166
|
const { privateKey } = await this.keypair;
|
|
155
167
|
const cekBytes = new Uint8Array(await crypto.subtle.decrypt({ name: "RSA-OAEP" }, privateKey, wrapped));
|
|
156
|
-
const
|
|
157
|
-
const pt = new Uint8Array(await crypto.subtle.decrypt({ name: "AES-GCM", iv }, cek, ct));
|
|
168
|
+
const pt = await aesGcmOpen(cekBytes, iv, ct);
|
|
158
169
|
cekBytes.fill(0);
|
|
159
170
|
return pt;
|
|
160
171
|
}
|
|
@@ -241,6 +252,9 @@ export {
|
|
|
241
252
|
loadPersistedSchema,
|
|
242
253
|
savePersistedSchema,
|
|
243
254
|
MemorySealingKeyProvider,
|
|
255
|
+
sealRsaOaepTlv,
|
|
256
|
+
parseRsaOaepTlv,
|
|
257
|
+
aesGcmOpen,
|
|
244
258
|
MemoryRecipientSealer,
|
|
245
259
|
SEALED_PASSPHRASE_RECORD_ID,
|
|
246
260
|
parseSealedEnvelope,
|
|
@@ -248,4 +262,4 @@ export {
|
|
|
248
262
|
loadSealedPassphrase,
|
|
249
263
|
resolveManagedSecret
|
|
250
264
|
};
|
|
251
|
-
//# sourceMappingURL=chunk-
|
|
265
|
+
//# sourceMappingURL=chunk-C6W5KVDV.js.map
|