@noy-db/hub 0.6.0-pre.1 → 0.6.0-pre.11
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/codemods/0.4.0-pre.json +699 -0
- package/dist/{api-67SBPMMH.js → api-RXUMEKT3.js} +6 -6
- package/dist/attestation/index.js +12 -12
- package/dist/attestation/index.js.map +1 -1
- package/dist/{backup-GAYWQAFN.js → backup-AVAGHAJE.js} +25 -6
- package/dist/backup-AVAGHAJE.js.map +1 -0
- package/dist/blobs/index.js +5 -5
- package/dist/broker/index.js +6 -6
- package/dist/{bundle-handle-5OUAI7CV.js → bundle-handle-GFNHT52M.js} +2 -2
- package/dist/cargo/index.js +18 -18
- package/dist/{chunk-CYQOUTMY.js → chunk-2BMRJ6LQ.js} +2 -2
- package/dist/{chunk-HS7HNYBD.js → chunk-2DHSSLOD.js} +6 -6
- package/dist/{chunk-JH3OHQM4.js → chunk-2Z3NCLNX.js} +6 -6
- package/dist/{chunk-NVU7ZMPN.js → chunk-3IXIU6FP.js} +2 -2
- package/dist/{chunk-XBLKM73C.js → chunk-3JVXVJ7L.js} +8 -8
- package/dist/{chunk-FY343IZR.js → chunk-3URQC7EH.js} +3 -3
- package/dist/{chunk-BPTZ4K5L.js → chunk-4GZ6XOVW.js} +7 -7
- package/dist/{chunk-ROWL352P.js → chunk-4I72F5DF.js} +4 -4
- package/dist/{chunk-EK7DT5BT.js → chunk-4VUPLMZ6.js} +39 -3
- package/dist/chunk-4VUPLMZ6.js.map +1 -0
- package/dist/{chunk-LBGPO5RL.js → chunk-52JPHN5N.js} +4 -4
- package/dist/{chunk-27TCYH6G.js → chunk-5JY5TSVY.js} +3 -3
- package/dist/{chunk-27TCYH6G.js.map → chunk-5JY5TSVY.js.map} +1 -1
- package/dist/chunk-5O5HRRTV.js +201 -0
- package/dist/chunk-5O5HRRTV.js.map +1 -0
- package/dist/{chunk-ZXJXDHNK.js → chunk-5ZKS6ZE2.js} +2 -2
- package/dist/{chunk-ZXJXDHNK.js.map → chunk-5ZKS6ZE2.js.map} +1 -1
- package/dist/{chunk-MVOWXQHN.js → chunk-6BU3L4UE.js} +2 -2
- package/dist/{chunk-3IIPWA75.js → chunk-6BVER473.js} +28 -3
- package/dist/chunk-6BVER473.js.map +1 -0
- package/dist/{chunk-OSFPNKY4.js → chunk-6FJWSWFT.js} +2 -2
- package/dist/chunk-6FJWSWFT.js.map +1 -0
- package/dist/{chunk-CJ73OLMW.js → chunk-6HFM7EIT.js} +1 -1
- package/dist/chunk-6HFM7EIT.js.map +1 -0
- package/dist/{chunk-PGAPBZEK.js → chunk-6NVPFKN3.js} +4 -4
- package/dist/{chunk-24UICKTB.js → chunk-6SAFL5YE.js} +2 -2
- package/dist/chunk-6SAFL5YE.js.map +1 -0
- package/dist/{chunk-NQRJ5AJO.js → chunk-7H73RIE3.js} +2 -2
- package/dist/{chunk-NQCNSVGI.js → chunk-7MNBICB5.js} +3 -3
- package/dist/{chunk-T4GPR2IR.js → chunk-7TSYBD3E.js} +1 -1
- package/dist/chunk-7TSYBD3E.js.map +1 -0
- package/dist/{chunk-TLNNHG3A.js → chunk-ADW22QSB.js} +3 -3
- package/dist/{chunk-TLNNHG3A.js.map → chunk-ADW22QSB.js.map} +1 -1
- package/dist/{chunk-3737A6EA.js → chunk-BGQ63MCT.js} +74 -17
- package/dist/chunk-BGQ63MCT.js.map +1 -0
- package/dist/{chunk-TFZ7LLOQ.js → chunk-BRVW2GS5.js} +5 -5
- package/dist/{chunk-3UK6STWO.js → chunk-CBXHHCZT.js} +3 -3
- package/dist/{chunk-MXZ5TSTR.js → chunk-CCVYQFLU.js} +6 -6
- package/dist/{chunk-V5C5NNBR.js → chunk-CDB2HVQW.js} +3 -3
- package/dist/{chunk-3HYWYLXL.js → chunk-CEJWMBXO.js} +1 -1
- package/dist/chunk-CEJWMBXO.js.map +1 -0
- package/dist/{chunk-2KIV7BEK.js → chunk-FDX7OWWI.js} +1 -1
- package/dist/chunk-FDX7OWWI.js.map +1 -0
- package/dist/{chunk-4MQDV3HZ.js → chunk-FFDNKRCY.js} +2 -2
- package/dist/{chunk-PP5CO2PR.js → chunk-FMQJZH4R.js} +2 -2
- package/dist/{chunk-JSRU5W2M.js → chunk-FZ6NYJ7L.js} +3 -3
- package/dist/{chunk-ZGGVOADG.js → chunk-G6S5VVHP.js} +2 -2
- package/dist/{chunk-OMSAULZM.js → chunk-GU7A7MQA.js} +9 -9
- package/dist/{chunk-E6427JU2.js → chunk-H4YQSDOI.js} +17 -1
- package/dist/chunk-H4YQSDOI.js.map +1 -0
- package/dist/{chunk-JXIDGFKS.js → chunk-HPLWFNG4.js} +3 -1
- package/dist/chunk-HPLWFNG4.js.map +1 -0
- package/dist/{chunk-ZWMO5GL5.js → chunk-JJNOLSG5.js} +3 -3
- package/dist/{chunk-RLWY6Z44.js → chunk-JRKZTEZZ.js} +62 -6
- package/dist/chunk-JRKZTEZZ.js.map +1 -0
- package/dist/chunk-N35NC4JH.js +123 -0
- package/dist/chunk-N35NC4JH.js.map +1 -0
- package/dist/{chunk-SRGJET2F.js → chunk-NJDQ4YSV.js} +2 -2
- package/dist/{chunk-JEM2IGS3.js → chunk-NPP5R7FF.js} +5 -5
- package/dist/{chunk-DPJTDNUF.js → chunk-O2ERS4UJ.js} +5 -5
- package/dist/{chunk-F3GVO44K.js → chunk-PDLLCOGD.js} +3 -3
- package/dist/{chunk-VBY6ZLJT.js → chunk-PL2GHSCM.js} +3 -3
- package/dist/{chunk-DCMQR46R.js → chunk-PNKHAKQ7.js} +3 -3
- package/dist/{chunk-ZK4CO2KZ.js → chunk-Q6TEJXBX.js} +4 -4
- package/dist/{chunk-GS5RT2KK.js → chunk-QULVCV3M.js} +2 -2
- package/dist/chunk-QULVCV3M.js.map +1 -0
- package/dist/{chunk-KZ3KMDN3.js → chunk-RTAMLDWP.js} +6 -2
- package/dist/chunk-RTAMLDWP.js.map +1 -0
- package/dist/{chunk-MZ5ZFO2D.js → chunk-RXYYFSBB.js} +82 -110
- package/dist/chunk-RXYYFSBB.js.map +1 -0
- package/dist/{chunk-4VCWI42L.js → chunk-SUFG5E73.js} +2 -2
- package/dist/{chunk-SKONR2OL.js → chunk-U5O6GIWZ.js} +2 -2
- package/dist/{chunk-3CLZY46Y.js → chunk-UV4O7FAL.js} +296 -110
- package/dist/chunk-UV4O7FAL.js.map +1 -0
- package/dist/{chunk-HFKGIBMD.js → chunk-VZ3XFUQ7.js} +2 -2
- package/dist/{chunk-3EO7RZLJ.js → chunk-W77KZHCW.js} +4 -4
- package/dist/{chunk-P7NGLLUF.js → chunk-WASHZV34.js} +2 -2
- package/dist/{chunk-XB7LUDKM.js → chunk-WFUIE565.js} +3 -3
- package/dist/{chunk-RPXL6KSX.js → chunk-WLZ47GSL.js} +3 -3
- package/dist/{chunk-MJWCLSQL.js → chunk-X56RTUK3.js} +3 -3
- package/dist/{chunk-MC2LFTQM.js → chunk-XWX7GL56.js} +2 -2
- package/dist/{chunk-46V67XP5.js → chunk-YJ6IONGA.js} +2 -2
- package/dist/chunk-YJ6IONGA.js.map +1 -0
- package/dist/{chunk-24ZLWWEO.js → chunk-YPYHVOXP.js} +1 -1
- package/dist/chunk-YPYHVOXP.js.map +1 -0
- package/dist/{chunk-2LGDCNFJ.js → chunk-Z45YRGFO.js} +1 -1
- package/dist/chunk-Z45YRGFO.js.map +1 -0
- package/dist/classified/index.js +3 -3
- package/dist/{classified-marker-MH3ON5OL.js → classified-marker-2KJAXRRO.js} +3 -3
- package/dist/{collection-facade-AHT7FATG.js → collection-facade-QEJIM6BC.js} +8 -8
- package/dist/consent/index.js +4 -4
- package/dist/cover/index.js +2 -2
- package/dist/custody/index.js +6 -6
- package/dist/{dead-filter-U575NGSH.js → dead-filter-DCB62VWW.js} +2 -2
- package/dist/{delegation-PDYL6RWT.js → delegation-MO7YZYJS.js} +4 -4
- package/dist/derivations/index.js +6 -6
- package/dist/{derive-AKT4KU36.js → derive-IUA6JFLB.js} +5 -5
- package/dist/directory/index.js +5 -5
- package/dist/{dispatch-4XIJSFTT.js → dispatch-23SKSHPD.js} +5 -5
- package/dist/{dispatch-N5HGVUGU.js → dispatch-MJ6SHB3Y.js} +9 -9
- package/dist/{enclave-OPK24QDR.js → enclave-JU4YIEPB.js} +4 -4
- package/dist/{executor-3JZ4POTW.js → executor-FKZURXVV.js} +10 -9
- package/dist/{export-accessible-CPTP5STN.js → export-accessible-4S36PQNR.js} +7 -7
- package/dist/{extract-partition-TWXPHVU7.js → extract-partition-IO7FDZ2P.js} +10 -10
- package/dist/{fanout-sidecar-T2ZGLQFE.js → fanout-sidecar-NNAJQXMX.js} +4 -4
- package/dist/forget/index.js +4 -4
- package/dist/guards/index.js +1 -1
- package/dist/history/index.js +6 -6
- package/dist/i18n/index.js +6 -6
- package/dist/index.d.ts +3 -2
- package/dist/index.js +71 -65
- package/dist/index.js.map +1 -1
- package/dist/introspection/index.js +6 -6
- package/dist/{issue-N74C55X6.js → issue-RIVHSY2S.js} +6 -6
- package/dist/kernel/query/builder.d.ts +15 -2
- package/dist/kernel/query/join.d.ts +27 -0
- package/dist/kernel/secret-mode.d.ts +1 -1
- package/dist/kernel/types.d.ts +41 -6
- package/dist/kernel/vault.d.ts +19 -15
- package/dist/{ledger-DONWFC5M.js → ledger-AETNF4OM.js} +6 -6
- package/dist/liberate-GFN4C7RR.js +24 -0
- package/dist/{link-set-2TTP4VLO.js → link-set-E5HNY67I.js} +5 -5
- package/dist/{managed-secret-EAOYWRYF.js → managed-secret-CWQ6PND5.js} +3 -3
- package/dist/materialized-views/index.js +11 -10
- package/dist/money/index.js +10 -6
- package/dist/{noydb-6W2XGQCS.js → noydb-FYTCH2GD.js} +30 -30
- package/dist/overlay-views/index.js +1 -1
- package/dist/periods/index.js +17 -7
- package/dist/pod/index.js +33 -33
- package/dist/policy/index.js +3 -3
- package/dist/port/to/index.d.ts +2 -2
- package/dist/port/to/locator.d.ts +54 -8
- package/dist/port/with/collection-options.d.ts +1 -1
- package/dist/portability/index.js +6 -6
- package/dist/{post-register-ORQOQJCS.js → post-register-2XSSMGEQ.js} +4 -4
- package/dist/query/index.js +2 -2
- package/dist/{register-LHETZRJM.js → register-4EFJCVYD.js} +11 -11
- package/dist/{registry-JRIN447B.js → registry-4SDHHOJ7.js} +6 -6
- package/dist/{registry-AMAZ6QRZ.js → registry-OYTFGWVK.js} +4 -4
- package/dist/{request-withdrawal-2DN2RGCO.js → request-withdrawal-KGXGDPVQ.js} +9 -9
- package/dist/{reveal-AG4SCHUQ.js → reveal-BW36KCOQ.js} +3 -3
- package/dist/{revoke-ZX4NM2CJ.js → revoke-JFN773KF.js} +6 -6
- package/dist/sealed-record/index.js +6 -6
- package/dist/search/index.js +1 -1
- package/dist/{seed-IRCDGIRK.js → seed-GDV5PT4R.js} +6 -6
- package/dist/sequence/index.js +4 -4
- package/dist/session/index.js +3 -3
- package/dist/{signer-7KHD4OL2.js → signer-TA55QB6R.js} +5 -5
- package/dist/snapshots/index.js +5 -5
- package/dist/{stale-K734VACF.js → stale-4PUTWI2H.js} +5 -5
- package/dist/{storage-4H2GF6RQ.js → storage-V7MTFAQJ.js} +5 -5
- package/dist/{store-coordination-provider-4SKLSFUG.js → store-coordination-provider-GFCUOBR6.js} +3 -3
- package/dist/sync/index.js +5 -5
- package/dist/team/index.js +8 -8
- package/dist/tiers/index.js +4 -4
- package/dist/to/index.js +16 -6
- package/dist/to/index.js.map +1 -1
- package/dist/via/classified/descriptor.d.ts +1 -1
- package/dist/via/money/exact.d.ts +44 -0
- package/dist/via/money/index.d.ts +2 -0
- package/dist/via/money/normalize.d.ts +20 -0
- package/dist/{walk-MQHYNDLZ.js → walk-3ZECBFDA.js} +7 -7
- package/dist/with-audit/attestation/index.d.ts +1 -1
- package/dist/with-audit/guards/immutable-guard.d.ts +14 -0
- package/dist/with-audit/guards/with-guard.d.ts +1 -1
- package/dist/with-audit/periods/active.d.ts +28 -1
- package/dist/with-audit/periods/index.d.ts +4 -2
- package/dist/with-audit/periods/periods.d.ts +160 -5
- package/dist/with-audit/periods/strategy.d.ts +13 -4
- package/dist/with-audit/periods/vault-facade.d.ts +89 -10
- package/dist/with-audit/periods/window.d.ts +10 -0
- package/dist/with-cargo/index.d.ts +2 -2
- package/dist/with-commit/history/ledger/entry.d.ts +1 -1
- package/dist/with-commit/numbering/descriptor.d.ts +1 -1
- package/dist/with-formula/derivations/with-derivation.d.ts +1 -1
- package/dist/with-formula/materialized-views/types.d.ts +52 -0
- package/dist/with-formula/materialized-views/with-materialized-view.d.ts +3 -3
- package/dist/with-formula/overlay-views/with-overlayed-view.d.ts +1 -1
- package/dist/with-party/custody/index.d.ts +1 -1
- package/dist/with-party/directory/user-envelope/index.d.ts +1 -1
- package/dist/with-party/directory/user-envelope/storage.d.ts +1 -1
- package/dist/with-party/team/echo-ceremony.d.ts +3 -3
- package/dist/with-party/team/echo-secret.d.ts +2 -2
- package/dist/with-party/team/keyring.d.ts +10 -1
- package/dist/with-pod/bundle.d.ts +15 -0
- package/dist/with-shape/introspection/index.d.ts +2 -1
- package/dist/with-shape/introspection/types.d.ts +1 -1
- package/dist/with-shape/persisted-schemas/derive.d.ts +1 -1
- package/dist/with-shape/persisted-schemas/index.d.ts +1 -1
- package/dist/with-shape/persisted-schemas/types.d.ts +1 -1
- package/dist/{withdraw-accessible-JSDQHXHE.js → withdraw-accessible-7PG4DYQK.js} +8 -8
- package/package.json +6 -4
- package/dist/backup-GAYWQAFN.js.map +0 -1
- package/dist/chunk-24UICKTB.js.map +0 -1
- package/dist/chunk-24ZLWWEO.js.map +0 -1
- package/dist/chunk-2KIV7BEK.js.map +0 -1
- package/dist/chunk-2LGDCNFJ.js.map +0 -1
- package/dist/chunk-3737A6EA.js.map +0 -1
- package/dist/chunk-3CLZY46Y.js.map +0 -1
- package/dist/chunk-3HYWYLXL.js.map +0 -1
- package/dist/chunk-3IIPWA75.js.map +0 -1
- package/dist/chunk-46V67XP5.js.map +0 -1
- package/dist/chunk-CJ73OLMW.js.map +0 -1
- package/dist/chunk-E6427JU2.js.map +0 -1
- package/dist/chunk-EK7DT5BT.js.map +0 -1
- package/dist/chunk-GS5RT2KK.js.map +0 -1
- package/dist/chunk-JXIDGFKS.js.map +0 -1
- package/dist/chunk-KZ3KMDN3.js.map +0 -1
- package/dist/chunk-LTU4FLHG.js +0 -131
- package/dist/chunk-LTU4FLHG.js.map +0 -1
- package/dist/chunk-MZ5ZFO2D.js.map +0 -1
- package/dist/chunk-OSFPNKY4.js.map +0 -1
- package/dist/chunk-RLWY6Z44.js.map +0 -1
- package/dist/chunk-T4GPR2IR.js.map +0 -1
- package/dist/liberate-KICI7AJN.js +0 -24
- /package/dist/{api-67SBPMMH.js.map → api-RXUMEKT3.js.map} +0 -0
- /package/dist/{bundle-handle-5OUAI7CV.js.map → bundle-handle-GFNHT52M.js.map} +0 -0
- /package/dist/{chunk-CYQOUTMY.js.map → chunk-2BMRJ6LQ.js.map} +0 -0
- /package/dist/{chunk-HS7HNYBD.js.map → chunk-2DHSSLOD.js.map} +0 -0
- /package/dist/{chunk-JH3OHQM4.js.map → chunk-2Z3NCLNX.js.map} +0 -0
- /package/dist/{chunk-NVU7ZMPN.js.map → chunk-3IXIU6FP.js.map} +0 -0
- /package/dist/{chunk-XBLKM73C.js.map → chunk-3JVXVJ7L.js.map} +0 -0
- /package/dist/{chunk-FY343IZR.js.map → chunk-3URQC7EH.js.map} +0 -0
- /package/dist/{chunk-BPTZ4K5L.js.map → chunk-4GZ6XOVW.js.map} +0 -0
- /package/dist/{chunk-ROWL352P.js.map → chunk-4I72F5DF.js.map} +0 -0
- /package/dist/{chunk-LBGPO5RL.js.map → chunk-52JPHN5N.js.map} +0 -0
- /package/dist/{chunk-MVOWXQHN.js.map → chunk-6BU3L4UE.js.map} +0 -0
- /package/dist/{chunk-PGAPBZEK.js.map → chunk-6NVPFKN3.js.map} +0 -0
- /package/dist/{chunk-NQRJ5AJO.js.map → chunk-7H73RIE3.js.map} +0 -0
- /package/dist/{chunk-NQCNSVGI.js.map → chunk-7MNBICB5.js.map} +0 -0
- /package/dist/{chunk-TFZ7LLOQ.js.map → chunk-BRVW2GS5.js.map} +0 -0
- /package/dist/{chunk-3UK6STWO.js.map → chunk-CBXHHCZT.js.map} +0 -0
- /package/dist/{chunk-MXZ5TSTR.js.map → chunk-CCVYQFLU.js.map} +0 -0
- /package/dist/{chunk-V5C5NNBR.js.map → chunk-CDB2HVQW.js.map} +0 -0
- /package/dist/{chunk-4MQDV3HZ.js.map → chunk-FFDNKRCY.js.map} +0 -0
- /package/dist/{chunk-PP5CO2PR.js.map → chunk-FMQJZH4R.js.map} +0 -0
- /package/dist/{chunk-JSRU5W2M.js.map → chunk-FZ6NYJ7L.js.map} +0 -0
- /package/dist/{chunk-ZGGVOADG.js.map → chunk-G6S5VVHP.js.map} +0 -0
- /package/dist/{chunk-OMSAULZM.js.map → chunk-GU7A7MQA.js.map} +0 -0
- /package/dist/{chunk-ZWMO5GL5.js.map → chunk-JJNOLSG5.js.map} +0 -0
- /package/dist/{chunk-SRGJET2F.js.map → chunk-NJDQ4YSV.js.map} +0 -0
- /package/dist/{chunk-JEM2IGS3.js.map → chunk-NPP5R7FF.js.map} +0 -0
- /package/dist/{chunk-DPJTDNUF.js.map → chunk-O2ERS4UJ.js.map} +0 -0
- /package/dist/{chunk-F3GVO44K.js.map → chunk-PDLLCOGD.js.map} +0 -0
- /package/dist/{chunk-VBY6ZLJT.js.map → chunk-PL2GHSCM.js.map} +0 -0
- /package/dist/{chunk-DCMQR46R.js.map → chunk-PNKHAKQ7.js.map} +0 -0
- /package/dist/{chunk-ZK4CO2KZ.js.map → chunk-Q6TEJXBX.js.map} +0 -0
- /package/dist/{chunk-4VCWI42L.js.map → chunk-SUFG5E73.js.map} +0 -0
- /package/dist/{chunk-SKONR2OL.js.map → chunk-U5O6GIWZ.js.map} +0 -0
- /package/dist/{chunk-HFKGIBMD.js.map → chunk-VZ3XFUQ7.js.map} +0 -0
- /package/dist/{chunk-3EO7RZLJ.js.map → chunk-W77KZHCW.js.map} +0 -0
- /package/dist/{chunk-P7NGLLUF.js.map → chunk-WASHZV34.js.map} +0 -0
- /package/dist/{chunk-XB7LUDKM.js.map → chunk-WFUIE565.js.map} +0 -0
- /package/dist/{chunk-RPXL6KSX.js.map → chunk-WLZ47GSL.js.map} +0 -0
- /package/dist/{chunk-MJWCLSQL.js.map → chunk-X56RTUK3.js.map} +0 -0
- /package/dist/{chunk-MC2LFTQM.js.map → chunk-XWX7GL56.js.map} +0 -0
- /package/dist/{classified-marker-MH3ON5OL.js.map → classified-marker-2KJAXRRO.js.map} +0 -0
- /package/dist/{collection-facade-AHT7FATG.js.map → collection-facade-QEJIM6BC.js.map} +0 -0
- /package/dist/{dead-filter-U575NGSH.js.map → dead-filter-DCB62VWW.js.map} +0 -0
- /package/dist/{delegation-PDYL6RWT.js.map → delegation-MO7YZYJS.js.map} +0 -0
- /package/dist/{derive-AKT4KU36.js.map → derive-IUA6JFLB.js.map} +0 -0
- /package/dist/{dispatch-4XIJSFTT.js.map → dispatch-23SKSHPD.js.map} +0 -0
- /package/dist/{dispatch-N5HGVUGU.js.map → dispatch-MJ6SHB3Y.js.map} +0 -0
- /package/dist/{enclave-OPK24QDR.js.map → enclave-JU4YIEPB.js.map} +0 -0
- /package/dist/{executor-3JZ4POTW.js.map → executor-FKZURXVV.js.map} +0 -0
- /package/dist/{export-accessible-CPTP5STN.js.map → export-accessible-4S36PQNR.js.map} +0 -0
- /package/dist/{extract-partition-TWXPHVU7.js.map → extract-partition-IO7FDZ2P.js.map} +0 -0
- /package/dist/{fanout-sidecar-T2ZGLQFE.js.map → fanout-sidecar-NNAJQXMX.js.map} +0 -0
- /package/dist/{issue-N74C55X6.js.map → issue-RIVHSY2S.js.map} +0 -0
- /package/dist/{ledger-DONWFC5M.js.map → ledger-AETNF4OM.js.map} +0 -0
- /package/dist/{liberate-KICI7AJN.js.map → liberate-GFN4C7RR.js.map} +0 -0
- /package/dist/{link-set-2TTP4VLO.js.map → link-set-E5HNY67I.js.map} +0 -0
- /package/dist/{managed-secret-EAOYWRYF.js.map → managed-secret-CWQ6PND5.js.map} +0 -0
- /package/dist/{noydb-6W2XGQCS.js.map → noydb-FYTCH2GD.js.map} +0 -0
- /package/dist/{post-register-ORQOQJCS.js.map → post-register-2XSSMGEQ.js.map} +0 -0
- /package/dist/{register-LHETZRJM.js.map → register-4EFJCVYD.js.map} +0 -0
- /package/dist/{registry-AMAZ6QRZ.js.map → registry-4SDHHOJ7.js.map} +0 -0
- /package/dist/{registry-JRIN447B.js.map → registry-OYTFGWVK.js.map} +0 -0
- /package/dist/{request-withdrawal-2DN2RGCO.js.map → request-withdrawal-KGXGDPVQ.js.map} +0 -0
- /package/dist/{reveal-AG4SCHUQ.js.map → reveal-BW36KCOQ.js.map} +0 -0
- /package/dist/{revoke-ZX4NM2CJ.js.map → revoke-JFN773KF.js.map} +0 -0
- /package/dist/{seed-IRCDGIRK.js.map → seed-GDV5PT4R.js.map} +0 -0
- /package/dist/{signer-7KHD4OL2.js.map → signer-TA55QB6R.js.map} +0 -0
- /package/dist/{stale-K734VACF.js.map → stale-4PUTWI2H.js.map} +0 -0
- /package/dist/{storage-4H2GF6RQ.js.map → storage-V7MTFAQJ.js.map} +0 -0
- /package/dist/{store-coordination-provider-4SKLSFUG.js.map → store-coordination-provider-GFCUOBR6.js.map} +0 -0
- /package/dist/{walk-MQHYNDLZ.js.map → walk-3ZECBFDA.js.map} +0 -0
- /package/dist/{withdraw-accessible-JSDQHXHE.js.map → withdraw-accessible-7PG4DYQK.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/with-party/directory/user-envelope/api.ts"],"sourcesContent":["/**\n * Public `vault.user.*` API surface.\n *\n * Three families:\n * - Write-self: `me` / `updateMe` / `setMe` — always target the writer's\n * own keyringId. **Own-only write rule** is structural — no method\n * exists to write someone else's envelope.\n * - Read-anyone: `get` / `list` — read other principals' envelopes\n * (subject to `view-team-profiles` policy gate).\n * - Reactive: `subscribe` / `live` — in-process event emission on local\n * writes. Cross-instance updates land via the team/sync engine and\n * surface to subscribers when the sync diff replays through this API.\n *\n * @see design-history/2026-05-05-user-envelope-design.md\n *\n * @module\n */\nimport { PolicyDeniedError } from '../../../kernel/errors.js'\nimport {\n loadUserEnvelope,\n saveUserEnvelope,\n listUserEnvelopeIds,\n} from './storage.js'\nimport type {\n UserEnvelope,\n DeepPartialOrNull,\n Unsubscribe,\n UserEnvelopePresented,\n LiveUserEnvelope,\n VaultUserApi,\n UserApiDeps,\n UserApiFactory,\n} from '../../../kernel/types.js'\nimport {\n persistUserVisibility,\n readUserVisibility,\n} from '../visibility.js'\nimport type { UserVisibility } from '../types.js'\nimport type { ExportAccessibleOptions } from '../../../with-audit/portability/export-accessible.js'\nimport type { WithdrawAccessibleOptions, WithdrawResult } from '../../../with-audit/portability/withdraw-accessible.js'\nimport type {\n RequestWithdrawalOptions,\n RequestWithdrawalResult,\n WithdrawalRequest,\n WithdrawalRequestStatus,\n ApproveWithdrawalOptions,\n RejectWithdrawalOptions,\n} from '../../../with-audit/portability/request-withdrawal.js'\n\ninterface ChangeListener<T = unknown> {\n (env: UserEnvelope<T> | null): void\n}\n\n/**\n * Implementation behind `vault.user`. Constructed once per Vault via\n * {@link createUserApi}, holds the writer's keyringId in closure so\n * `updateMe`/`setMe` cannot target any other principal — the own-only\n * rule is enforced at the type level (no `set(otherKeyringId, …)`\n * method) AND at runtime (the keyringId argument simply doesn't exist\n * on the write path).\n */\nexport class UserApi implements VaultUserApi {\n /** keyringId → set of listeners. Wildcard '*' fires on every change. */\n private readonly listeners = new Map<string, Set<ChangeListener>>()\n\n private readonly adapter: UserApiDeps['adapter']\n private readonly vaultName: UserApiDeps['vaultName']\n /** The writer's own keyringId. Frozen at construction time. */\n private readonly writerKeyringId: UserApiDeps['writerKeyringId']\n private readonly getDek: UserApiDeps['getDek']\n /**\n * Policy-gate validator. When omitted, gates are skipped — useful\n * for low-level tests that exercise the storage layer directly.\n * Production paths always wire the Noydb-backed implementation.\n */\n private readonly checkGate?: UserApiDeps['checkGate']\n /**\n * Noydb-backed `exportMyAccessibleData`, injected by the Vault\n * (which holds the keyring + bundle machinery). Omitted in low-level tests.\n */\n private readonly exportAccessible?: UserApiDeps['exportAccessible']\n /**\n * Noydb-backed `unilateralWithdrawal`, injected by the Vault.\n * Destructive — extract + dispose (delete | freeze). Omitted in low-level tests.\n */\n private readonly unilateralWithdraw?: UserApiDeps['unilateralWithdraw']\n /**\n * Noydb-backed two-party withdrawal ceremony, injected by the\n * Vault. requestWithdraw = requester side; the rest = owner side.\n */\n private readonly requestWithdraw?: UserApiDeps['requestWithdraw']\n private readonly listWithdrawals?: UserApiDeps['listWithdrawals']\n private readonly approveWithdraw?: UserApiDeps['approveWithdraw']\n private readonly rejectWithdraw?: UserApiDeps['rejectWithdraw']\n\n constructor(deps: UserApiDeps) {\n this.adapter = deps.adapter\n this.vaultName = deps.vaultName\n this.writerKeyringId = deps.writerKeyringId\n this.getDek = deps.getDek\n this.checkGate = deps.checkGate\n this.exportAccessible = deps.exportAccessible\n this.unilateralWithdraw = deps.unilateralWithdraw\n this.requestWithdraw = deps.requestWithdraw\n this.listWithdrawals = deps.listWithdrawals\n this.approveWithdraw = deps.approveWithdraw\n this.rejectWithdraw = deps.rejectWithdraw\n }\n\n /**\n * File a two-party withdrawal request for the caller's accessible\n * scope. Non-destructive (writes a pending request); an owner later approves\n * or rejects. This is the path for read-only roles (`client`/`viewer`) that\n * cannot self-serve a destructive `unilateralWithdrawal`. Gated by\n * `user-request-withdrawal` (enabled by default).\n */\n async requestWithdrawal(opts: RequestWithdrawalOptions = {}): Promise<RequestWithdrawalResult> {\n if (this.checkGate) await this.checkGate('user-request-withdrawal')\n if (!this.requestWithdraw) {\n throw new Error('requestWithdrawal requires a Noydb-backed vault (not a bare UserApi)')\n }\n return this.requestWithdraw(opts)\n }\n\n /** Owner side: list filed withdrawal requests (optionally by status). */\n async listWithdrawalRequests(opts: { status?: WithdrawalRequestStatus } = {}): Promise<WithdrawalRequest[]> {\n if (!this.listWithdrawals) {\n throw new Error('listWithdrawalRequests requires a Noydb-backed vault (not a bare UserApi)')\n }\n return this.listWithdrawals(opts)\n }\n\n /**\n * Owner side: approve a pending request. Extracts the requester's\n * recorded scope under firm authority, disposes of the source per the\n * request's disposition, and returns the re-keyed bundle to hand back. Gated\n * by `approve-user-withdrawal` (tier-2 default) + owner/admin role.\n */\n async approveWithdrawal(requestId: string, opts: ApproveWithdrawalOptions = {}): Promise<WithdrawResult> {\n if (this.checkGate) await this.checkGate('approve-user-withdrawal')\n if (!this.approveWithdraw) {\n throw new Error('approveWithdrawal requires a Noydb-backed vault (not a bare UserApi)')\n }\n return this.approveWithdraw(requestId, opts)\n }\n\n /** Owner side: reject a pending request (no data is touched). */\n async rejectWithdrawal(requestId: string, opts: RejectWithdrawalOptions = {}): Promise<WithdrawalRequest> {\n if (this.checkGate) await this.checkGate('approve-user-withdrawal')\n if (!this.rejectWithdraw) {\n throw new Error('rejectWithdrawal requires a Noydb-backed vault (not a bare UserApi)')\n }\n return this.rejectWithdraw(requestId, opts)\n }\n\n /**\n * Single-party withdrawal: export the caller's accessible scope\n * (re-keyed) and dispose of the source (`delete` or `freeze`). Gated by the\n * fail-closed built-in `client-unilateral-withdraw` policy — undefined or\n * disabled → throws (use `requestWithdrawal`). The firm enables it at vault\n * creation.\n */\n async unilateralWithdrawal(opts: WithdrawAccessibleOptions): Promise<WithdrawResult> {\n if (this.checkGate) await this.checkGate('client-unilateral-withdraw')\n if (!this.unilateralWithdraw) {\n throw new Error('unilateralWithdrawal requires a Noydb-backed vault (not a bare UserApi)')\n }\n return this.unilateralWithdraw(opts)\n }\n\n /**\n * Export the calling user's accessible scope as a portable, re-keyed\n * `.noydb` bundle. Non-destructive and **always allowed** (data sovereignty\n * by construction) but audited. Scope = the caller's DEK access set.\n */\n async exportMyAccessibleData(opts: ExportAccessibleOptions = {}): Promise<Uint8Array> {\n if (!this.exportAccessible) {\n throw new Error('exportMyAccessibleData requires a Noydb-backed vault (not a bare UserApi)')\n }\n return this.exportAccessible(opts)\n }\n\n // ─── Write-self ──────────────────────────────────────────────────────\n\n /** Read the writer's own envelope. Returns null if never written. */\n async me<T = unknown>(): Promise<UserEnvelope<T> | null> {\n const dek = await this.getDek()\n return loadUserEnvelope<T>(this.adapter, this.vaultName, this.writerKeyringId, dek)\n }\n\n /**\n * Deep-merge a partial patch into the writer's own envelope. Creates\n * the envelope on first call. Optimistic-concurrency safe — a stale\n * `_v` (parallel writer on another device) throws `ConflictError`.\n *\n * Patch semantics:\n * - `undefined` (or omitted key) — skip; existing value preserved\n * - `null` — delete the field from the merged result\n * - any other value — overwrite (deep-merge for plain objects,\n * replace for primitives / arrays)\n *\n * To clear a field, pass `null` rather than `undefined`. Callers\n * with shape `T = string | null` where `null` is a meaningful value\n * should use `setMe` for that specific field instead — `null` here\n * always means delete.\n *\n * Gated by the `edit-own-profile` policy gate (default `minTier: 3`).\n * Pass `presented` to satisfy tightened policies that require a\n * factor proof (e.g. STRICT_POLICY's TOTP requirement).\n */\n async updateMe<T extends object = Record<string, unknown>>(\n patch: DeepPartialOrNull<T>,\n presented?: UserEnvelopePresented,\n ): Promise<UserEnvelope<T>> {\n if (this.checkGate) await this.checkGate('edit-own-profile', presented)\n const dek = await this.getDek()\n const current = await loadUserEnvelope<T>(\n this.adapter,\n this.vaultName,\n this.writerKeyringId,\n dek,\n )\n const merged: T = current ? deepMerge(current.data, patch) : (patch as unknown as T)\n const written = await saveUserEnvelope<T>(\n this.adapter,\n this.vaultName,\n this.writerKeyringId,\n merged,\n dek,\n current?._v ?? 0,\n )\n this.fireChange(this.writerKeyringId, written)\n return written\n }\n\n /**\n * Replace the writer's own envelope with `payload`. Use sparingly —\n * `updateMe` is the canonical mutation. No `expectedVersion` check;\n * callers explicitly take last-write-wins semantics.\n *\n * Gated by `edit-own-profile`. See `updateMe` for `presented` usage.\n */\n async setMe<T = unknown>(\n payload: T,\n presented?: UserEnvelopePresented,\n ): Promise<UserEnvelope<T>> {\n if (this.checkGate) await this.checkGate('edit-own-profile', presented)\n const dek = await this.getDek()\n const written = await saveUserEnvelope<T>(\n this.adapter,\n this.vaultName,\n this.writerKeyringId,\n payload,\n dek,\n )\n this.fireChange(this.writerKeyringId, written)\n return written\n }\n\n // ─── Visibility ──────────────────────────────────────────────────────\n\n /**\n * Read the current user's visibility flag from\n * `_meta/visibility/<keyringId>`. Returns `{ hidden: false }` when no\n * document has been persisted (the default-visible case).\n */\n async getMyVisibility(): Promise<UserVisibility> {\n const persisted = await readUserVisibility(this.adapter, this.vaultName, this.writerKeyringId)\n return persisted ?? { hidden: false }\n }\n\n /**\n * Update the current user's visibility in the team directory.\n *\n * - `hidden: true` — opt out of the default `listUsersWithEnvelopes`\n * listing. `owner`/`admin` callers can still see the user by passing\n * `{ includeHidden: true }`.\n * - `hidden: false` — opt back in.\n *\n * Own-only by construction: the keyringId argument doesn't exist on\n * this method, so no caller can hide or unhide another principal.\n *\n * Honest caveat: this is a UX flag, not a privacy guarantee. The\n * envelope ciphertext at `_users/<keyringId>` and the keyring file at\n * `_keyring/<userId>` are both still observable to anyone with direct\n * store read access. See `https://github.com/vLannaAi/noy-db-docs/blob/main/content/docs/services/user-envelope.md` →\n * \"Directory visibility\".\n */\n async setMyVisibility(visibility: UserVisibility): Promise<void> {\n await persistUserVisibility(\n this.adapter,\n this.vaultName,\n this.writerKeyringId,\n { hidden: visibility.hidden },\n )\n }\n\n // ─── Read-anyone ─────────────────────────────────────────────────────\n\n /**\n * Read another principal's envelope by their keyringId. Returns null\n * if the principal exists but has no envelope yet, or if the\n * keyringId does not exist at all.\n *\n * Gated by `view-team-profiles` (default `minTier: 2`) — but ONLY for\n * cross-principal reads. Reading your own envelope (`keyringId ===\n * self`) is never gated; that's just `me()` written long-form.\n */\n async get<T = unknown>(\n keyringId: string,\n presented?: UserEnvelopePresented,\n ): Promise<UserEnvelope<T> | null> {\n if (this.checkGate && keyringId !== this.writerKeyringId) {\n await this.checkGate('view-team-profiles', presented)\n }\n const dek = await this.getDek()\n return loadUserEnvelope<T>(this.adapter, this.vaultName, keyringId, dek)\n }\n\n /**\n * Read every persisted envelope in the vault. Order is store-defined.\n *\n * Gated by `view-team-profiles`. Default policy (`minTier: 2`) lets\n * any authenticated session read all envelopes. Two privacy-strict\n * opt-outs:\n *\n * - `view-team-profiles.enabled: false` → list() returns only the\n * caller's own envelope (silent self-fallback, no thrown error).\n * - `view-team-profiles.minTier: 1` + insufficient tier → throws\n * `PolicyDeniedError` with `reason: 'insufficient-tier'`. The\n * caller is expected to elevate, not silently degrade.\n *\n * The asymmetry is deliberate: `enabled: false` is a deliberate\n * design choice (\"nobody sees teammate profiles in this app\");\n * `insufficient-tier` is \"you need to authenticate further\". Different\n * UX prompts for different intents.\n */\n async list<T = unknown>(presented?: UserEnvelopePresented): Promise<UserEnvelope<T>[]> {\n if (this.checkGate) {\n try {\n await this.checkGate('view-team-profiles', presented)\n } catch (err) {\n if (err instanceof PolicyDeniedError && err.reason === 'disabled') {\n // Privacy-strict opt-out: quietly return only self.\n const me = await this.me<T>()\n return me ? [me] : []\n }\n throw err\n }\n }\n const dek = await this.getDek()\n const ids = await listUserEnvelopeIds(this.adapter, this.vaultName)\n const envelopes = await Promise.all(\n ids.map((id) => loadUserEnvelope<T>(this.adapter, this.vaultName, id, dek)),\n )\n return envelopes.filter((e): e is UserEnvelope<T> => e !== null)\n }\n\n // ─── Reactive ────────────────────────────────────────────────────────\n\n /**\n * Listen for changes to a specific keyringId's envelope. The callback\n * fires synchronously after every successful local `updateMe` /\n * `setMe` for that principal.\n *\n * Cross-instance changes (a teammate edits their profile on their\n * device, the sync engine pulls the diff onto this device) will fire\n * subscribers when the sync layer replays the write through this API.\n * In v1, subscribers do NOT fire on raw store changes — wire your sync\n * layer to call back through `vault.user.setMe` / `updateMe` if you\n * need that.\n *\n * Pass keyringId `'*'` to fire on every change in the vault.\n */\n subscribe<T = unknown>(\n keyringId: string,\n cb: (env: UserEnvelope<T> | null) => void,\n ): Unsubscribe {\n let listeners = this.listeners.get(keyringId)\n if (!listeners) {\n listeners = new Set()\n this.listeners.set(keyringId, listeners)\n }\n const wrapped: ChangeListener = cb as ChangeListener\n listeners.add(wrapped)\n return () => {\n listeners?.delete(wrapped)\n if (listeners && listeners.size === 0) {\n this.listeners.delete(keyringId)\n }\n }\n }\n\n /**\n * Reactive handle that caches the current value and re-reads on every\n * change for the given keyringId. Convenient for framework bindings:\n *\n * const live = vault.user.live<UserShape>(vault.userId)\n * live.subscribe(env => render(env?.data))\n *\n * Initial value is `null` until the first `current()` call materializes\n * it via `vault.user.get()`. Call `stop()` when done to release the\n * subscription.\n */\n live<T = unknown>(keyringId: string): LiveUserEnvelope<T> {\n let value: UserEnvelope<T> | null = null\n let primed = false\n const unsubscribe = this.subscribe<T>(keyringId, (env) => {\n value = env\n })\n\n return {\n current(): UserEnvelope<T> | null {\n if (!primed) {\n primed = true\n // First call: kick off a read but return synchronously. The\n // subscriber will be re-fired by the next write or the caller\n // can await `vault.user.get()` directly for an immediate read.\n }\n return value\n },\n subscribe: (cb) => this.subscribe<T>(keyringId, cb),\n stop: unsubscribe,\n }\n }\n\n // ─── Internal: change emission ───────────────────────────────────────\n\n private fireChange<T>(keyringId: string, env: UserEnvelope<T> | null): void {\n const targeted = this.listeners.get(keyringId)\n if (targeted) for (const l of targeted) l(env)\n const wildcard = this.listeners.get('*')\n if (wildcard) for (const l of wildcard) l(env)\n }\n}\n\n/**\n * Build the `vault.user` API implementation from its dependencies. This is\n * the real {@link UserApiFactory} — `createNoydb()` dynamically imports it\n * and stashes it on the `Noydb` instance so `Vault`'s constructor can call\n * it synchronously.\n */\nexport const createUserApi: UserApiFactory = (deps: UserApiDeps): VaultUserApi => new UserApi(deps)\n\n/**\n * Recursive plain-object deep merge with delete intent.\n *\n * Patch semantics:\n * - `undefined` — skip the key; source value preserved\n * - `null` — delete the key from output (lodash `_.merge` /\n * Firestore `FieldValue.delete()` semantics)\n * - plain object — recurse (deep merge)\n * - any other value — replace (arrays are replaced, not concatenated)\n *\n * Safe against the JS quirk where an own property explicitly set to\n * `undefined` is iterated by `Object.entries`. We dispatch on the value\n * BEFORE writing, so `{ k: undefined }` triggers the skip branch rather\n * than overwriting `out[k]` with undefined.\n */\nfunction deepMerge<T>(source: T, patch: DeepPartialOrNull<T>): T {\n if (!isPlainObject(source) || !isPlainObject(patch)) {\n // Top-level non-object replace. `null` patch at the leaf level\n // would have been caught by the parent recursion's branch table;\n // at the top level it means \"set the whole envelope to null,\"\n // which the type system already prevents (T extends object).\n return patch as unknown as T\n }\n const out: Record<string, unknown> = { ...(source as Record<string, unknown>) }\n for (const [key, patchVal] of Object.entries(patch as Record<string, unknown>)) {\n if (patchVal === undefined) {\n // Skip — preserve the source value at this key. Matches the\n // pre-existing behavior so callers who never used `null` see no diff.\n continue\n }\n if (patchVal === null) {\n // Delete intent. `delete` rather than `out[key] = undefined`\n // because JSON.stringify drops undefined fields silently and\n // we want the deletion to be visible to consumers iterating\n // the merged object (e.g. `Object.keys(merged.profile)`).\n delete out[key]\n continue\n }\n const sourceVal = (source as Record<string, unknown>)[key]\n if (isPlainObject(patchVal)) {\n // Recurse for any plain-object patch — including the \"source is\n // missing this key\" case. Without recursing through a synthetic\n // empty source, nested `null` deletions in the patch would land\n // as literal `null` values instead of triggering the delete\n // branch (e.g. `{ app: { signature: null } }` against a missing\n // `app` would emit `{ app: { signature: null } }` instead of\n // `{ app: {} }`).\n const recurseSource = isPlainObject(sourceVal) ? sourceVal : {}\n out[key] = deepMerge(recurseSource, patchVal as DeepPartialOrNull<typeof recurseSource>)\n } else {\n out[key] = patchVal\n }\n }\n return out as T\n}\n\nfunction isPlainObject(x: unknown): x is Record<string, unknown> {\n if (x === null || typeof x !== 'object') return false\n if (Array.isArray(x)) return false\n const proto = Object.getPrototypeOf(x) as object | null\n return proto === Object.prototype || proto === null\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/with-formula/derivations/with-derivation.ts","../src/with-formula/derivations/with-rollup.ts"],"sourcesContent":["import { ValidationError } from '../../kernel/errors.js'\nimport type { DerivationSpec, DerivationStrategy } from './types.js'\n\n/**\n * Register a deterministic derivation: one source collection → one or\n * more typed outputs, computed by the user's `derive` function on\n * plaintext after DEK unwrap. Outputs are encrypted with the same DEK\n * as the source and written via the standard `Collection.put` path.\n *\n * See design-history/2026-05-01-dim14-derivation-v1-design.md.\n */\nexport function withDerivation<\n TSource extends Record<string, unknown>,\n TOutputs extends Record<string, Record<string, unknown> | ReadonlyArray<Record<string, unknown>>>,\n>(spec: DerivationSpec<TSource, TOutputs>): DerivationStrategy {\n if (!spec.source || spec.source.length === 0) {\n throw new ValidationError('withDerivation: source collection name is required')\n }\n if (!spec.outputs || Object.keys(spec.outputs).length === 0) {\n throw new ValidationError('withDerivation: outputs map must declare at least one output')\n }\n if (spec.deterministic !== true) {\n throw new ValidationError('withDerivation: v1 only supports deterministic derivations')\n }\n if (typeof spec.derive !== 'function') {\n throw new ValidationError('withDerivation: derive must be a function')\n }\n\n // Validate declared sibling sources. Each must be a non-empty\n // string and must differ from the primary source — a self-reference\n // would double-register the strategy under the same `_bySource` key.\n if (spec.sources !== undefined) {\n for (const extra of spec.sources) {\n if (typeof extra !== 'string' || extra.length === 0) {\n throw new ValidationError('withDerivation: each entry in sources[] must be a non-empty string')\n }\n if (extra === spec.source) {\n throw new ValidationError(\n `withDerivation: sources[] must not contain the primary source \"${spec.source}\"`,\n )\n }\n }\n }\n\n // Validate FK triggers. Each `collection` must be a non-empty\n // string differing from the primary source, and `on` a non-empty field.\n if (spec.triggerBy !== undefined) {\n for (const t of spec.triggerBy) {\n if (typeof t?.collection !== 'string' || t.collection.length === 0) {\n throw new ValidationError('withDerivation: each triggerBy entry needs a non-empty `collection`')\n }\n if (t.collection === spec.source) {\n throw new ValidationError(\n `withDerivation: triggerBy.collection must not equal the source \"${spec.source}\" (use sources[] for same-id triggers)`,\n )\n }\n if (typeof t.on !== 'string' || t.on.length === 0) {\n throw new ValidationError(\n `withDerivation: triggerBy on \"${t.collection}\" needs a non-empty \\`on\\` (the FK field on the source)`,\n )\n }\n if (t.maxFanout !== undefined && (!Number.isInteger(t.maxFanout) || t.maxFanout < 1)) {\n throw new ValidationError(\n `withDerivation: triggerBy maxFanout on \"${t.collection}\" must be a positive integer (got ${String(t.maxFanout)}).`,\n )\n }\n }\n }\n\n // Validate array-shape outputs.\n const lifecycleMode = typeof spec.lifecycle === 'string' ? spec.lifecycle : spec.lifecycle.mode\n for (const [outputKey, outputSpec] of Object.entries(spec.outputs)) {\n // Self-write output (collection === source): reverse-denorm must declare\n // `denorm` (the fields it owns) — field-level provenance.\n if (outputSpec.shape === 'record' && outputSpec.collection === spec.source) {\n if (!outputSpec.denorm || outputSpec.denorm.length === 0) {\n throw new ValidationError(\n `withDerivation: self-write output \"${outputKey}\" (collection === source \"${spec.source}\") `\n + 'must declare `denorm: [...]` naming the fields it maintains.',\n )\n }\n }\n if (outputSpec.shape === 'array') {\n if (lifecycleMode !== 'eager') {\n throw new ValidationError(\n `withDerivation: shape 'array' supports lifecycle 'eager' only in this release `\n + `Output \"${outputKey}\" declared lifecycle '${lifecycleMode}'. `\n + 'Switch to `lifecycle: \"eager\"` or use shape: \"record\".',\n )\n }\n if (typeof outputSpec.key !== 'function') {\n throw new ValidationError(\n `withDerivation: shape 'array' output \"${outputKey}\" requires \\`key: (out) => string\\`.`,\n )\n }\n if (outputSpec.maxFanout !== undefined) {\n if (!Number.isInteger(outputSpec.maxFanout) || outputSpec.maxFanout < 1) {\n throw new ValidationError(\n `withDerivation: maxFanout for output \"${outputKey}\" must be a positive integer `\n + `(got ${String(outputSpec.maxFanout)}).`,\n )\n }\n }\n }\n }\n\n return {\n __noydb_strategy: 'derivation',\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n spec: spec as DerivationSpec<any, any>,\n }\n}\n","import { ValidationError } from '../../kernel/errors.js'\nimport type { DerivationSpec, DerivationStrategy } from './types.js'\n\n/** Options for {@link withRollup} (#844b — was an inline literal, so unnameable). */\nexport interface WithRollupOptions<\n TChild extends Record<string, unknown> = Record<string, unknown>,\n TParent extends Record<string, unknown> = Record<string, unknown>,\n> {\n /** Child collection — the trigger. */\n readonly from: string\n /** FK field on the child pointing at the parent id. */\n readonly key: keyof TChild & string\n /** Parent collection carrying the maintained summary. */\n readonly into: string\n /** Field on the parent to maintain. */\n readonly field: keyof TParent & string\n /** Reduces the matching children to the value written at `field`. */\n readonly compute: (children: TChild[]) => unknown\n}\n\n/**\n * `withRollup` — aggregate many child records onto a single field of their\n * parent. The reverse of a join: instead of reading children\n * on demand, the parent carries a maintained summary.\n *\n * ```ts\n * withRollup<Sale, Buyer>({\n * from: 'sales', // child collection (the trigger)\n * key: 'buyerId', // FK on the child → parent id\n * into: 'buyers', // parent collection\n * field: 'revenueByYear', // field on the parent to maintain\n * compute: (sales) => groupSumByYear(sales, 'total'),\n * })\n * ```\n *\n * On every write OR delete of a `from` record, the parent at id `child[key]`\n * is recomputed: `compute(allChildren where child[key] === parentId)` is\n * patched onto `parent[field]`. A parent write also recomputes its own\n * aggregate (so a parent created after its children still fills in). Only the\n * `field` is touched — the rest of the parent record is never clobbered — and\n * a value-equality guard suppresses no-op writes. The aggregate is gap-free\n * with respect to child inserts, updates, and deletes.\n *\n * Desugars to a `withDerivation` strategy carrying a `rollup` marker; dispatch\n * handles it without invoking the executor. Eager-only in this slice.\n */\nexport function withRollup<\n TChild extends Record<string, unknown> = Record<string, unknown>,\n TParent extends Record<string, unknown> = Record<string, unknown>,\n>(opts: WithRollupOptions<TChild, TParent>): DerivationStrategy {\n const { from, key, into, field, compute } = opts\n if (!from || from.length === 0) {\n throw new ValidationError('withRollup: `from` (child collection) is required')\n }\n if (!into || into.length === 0) {\n throw new ValidationError('withRollup: `into` (parent collection) is required')\n }\n if (from === into) {\n throw new ValidationError('withRollup: `from` and `into` must be different collections')\n }\n if (!key || key.length === 0) {\n throw new ValidationError('withRollup: `key` (FK field on the child) is required')\n }\n if (!field || field.length === 0) {\n throw new ValidationError('withRollup: `field` (target field on the parent) is required')\n }\n if (typeof compute !== 'function') {\n throw new ValidationError('withRollup: `compute` must be a function')\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const spec: DerivationSpec<any, any> = {\n source: into, // the parent record is what carries the rolled-up field\n deterministic: true,\n rollup: { from, key, field, compute: compute as (children: unknown[]) => unknown },\n // Synthetic self-write output for registry / cycle bookkeeping. Dispatch\n // patches `field` directly (value-equality guarded); the executor is not run.\n outputs: { value: { shape: 'record', collection: into, denorm: [field] } },\n derive: () => ({ value: {} }), // never invoked for a rollup strategy\n lifecycle: 'eager',\n }\n\n return { __noydb_strategy: 'derivation', spec }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
applyRewrappedBody,
|
|
7
7
|
rewrapBodyToDek
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-3URQC7EH.js";
|
|
9
9
|
import {
|
|
10
10
|
isDeleteMarker,
|
|
11
11
|
isTombstoneShape
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-7H73RIE3.js";
|
|
13
13
|
import {
|
|
14
14
|
decrypt,
|
|
15
15
|
encrypt,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
} from "./chunk-VCETL77O.js";
|
|
18
18
|
import {
|
|
19
19
|
NOYDB_FORMAT_VERSION
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-YPYHVOXP.js";
|
|
21
21
|
import {
|
|
22
22
|
PersistedIndexCompensationError,
|
|
23
23
|
TierDemoteDeniedError,
|
|
@@ -378,4 +378,4 @@ export {
|
|
|
378
378
|
demote,
|
|
379
379
|
classifySealedShred
|
|
380
380
|
};
|
|
381
|
-
//# sourceMappingURL=chunk-
|
|
381
|
+
//# sourceMappingURL=chunk-6NVPFKN3.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
envelopeBodyForHash
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-3URQC7EH.js";
|
|
4
4
|
import {
|
|
5
5
|
sha256Hex
|
|
6
6
|
} from "./chunk-VCETL77O.js";
|
|
@@ -67,4 +67,4 @@ export {
|
|
|
67
67
|
parseIndex,
|
|
68
68
|
envelopePayloadHash
|
|
69
69
|
};
|
|
70
|
-
//# sourceMappingURL=chunk-
|
|
70
|
+
//# sourceMappingURL=chunk-6SAFL5YE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/with-commit/history/ledger/entry.ts","../src/with-commit/history/ledger/hash.ts"],"sourcesContent":["/**\n * Ledger entry shape + canonical JSON + sha256 helpers.\n *\n * This file holds the PURE primitives used by the hash-chained ledger:\n * the entry type, the deterministic (sort-stable) JSON encoder, and\n * the sha256 hasher that produces `prevHash` and `ledger.head()`.\n *\n * Everything here is validator-free and side-effect free — the only\n * runtime dep is Web Crypto's `subtle.digest` for the sha256 call,\n * which we already use for every other hashing operation in the core.\n *\n * The hash chain property works like this:\n *\n * hash(entry[i]) = sha256(canonicalJSON(entry[i]))\n * entry[i+1].prevHash = hash(entry[i])\n *\n * Any modification to `entry[i]` (field values, field order, whitespace)\n * produces a different `hash(entry[i])`, which means `entry[i+1]`'s\n * stored `prevHash` no longer matches the recomputed hash, which means\n * `verify()` returns `{ ok: false, divergedAt: i + 1 }`. The chain is\n * append-only and tamper-evident without external anchoring.\n */\n\n/**\n * A single ledger entry in its plaintext form — what gets serialized,\n * hashed, and then encrypted with the ledger DEK before being written\n * to the `_ledger/` adapter collection.\n *\n * ## Why hash the ciphertext, not the plaintext?\n *\n * `payloadHash` is the sha256 of the record's ENCRYPTED envelope bytes,\n * not its plaintext. This matters:\n *\n * 1. **Zero-knowledge preserved.** A user (or a third party) can\n * verify the ledger against the stored envelopes without any\n * decryption keys. The adapter layer already holds only\n * ciphertext, so hashing the ciphertext keeps the ledger at the\n * same privacy level as the adapter.\n *\n * 2. **Determinism.** Plaintext → ciphertext is randomized by the\n * fresh per-write IV, so `hash(plaintext)` would need extra\n * normalization. `hash(ciphertext)` is already deterministic and\n * unique per write.\n *\n * 3. **Detection property.** If an attacker modifies even one byte of\n * the stored ciphertext (trying to flip a record), the hash\n * changes, the ledger's recorded `payloadHash` no longer matches,\n * and a data-integrity check fails. We don't do that check in\n * `verify()` today, but the\n * hook is there for a future `verifyIntegrity()` follow-up.\n *\n * Fields marked `op`, `collection`, `id`, `version`, `ts`, `actor` are\n * plaintext METADATA about the operation — NOT the record itself. The\n * entry is still encrypted at rest via the ledger DEK, but adapters\n * could theoretically infer operation patterns from the sizes and\n * timestamps. This is an accepted trade-off for the tamper-evidence\n * property; full ORAM-level privacy is out of scope for noy-db.\n */\nimport { sha256Hex as sha256HexBytes } from '../../../kernel/enclave/index.js'\n\nexport interface LedgerEntry {\n /**\n * Zero-based sequential position of this entry in the chain. The\n * canonical adapter key is this number zero-padded to 10 digits\n * (`\"0000000001\"`) so lexicographic ordering matches numeric order.\n */\n readonly index: number\n\n /**\n * Hex-encoded sha256 of the canonical JSON of the PREVIOUS entry.\n * The genesis entry (index 0) has `prevHash === ''` — the first\n * entry in a fresh vault has nothing to point back to.\n */\n readonly prevHash: string\n\n /**\n * Which kind of mutation this entry records. only supports\n * data operations (`put`, `delete`, `amendment`). Access-control\n * operations (`grant`, `revoke`, `rotate`) will be added in a\n * follow-up once the keyring write path is instrumented — that's\n * tracked in the epic issue.\n *\n * `'amendment'` is the multi-record audit entry written by the\n * guards service when an admin/owner uses `withTransactions(...)`\n * to repair a constraint-violating state. See `amendment` field\n * below for the structured payload.\n *\n * `'lifecycle'` records a non-data audit event (e.g. partition\n * handover) — `collection`/`id` are empty and the event detail\n * lives in `reason` (e.g. `'partition-handed-over:<sealId>'`). Like\n * `amendment`, it carries no data envelope, so `verifyBackupIntegrity`\n * skips it in the data cross-check (it still participates in the\n * tamper-evident chain).\n *\n * `'forget'` is the single summary entry written by `vault.forget()`\n * (GDPR crypto-shred). `collection`/`id` are empty and `version`\n * is 0 — a forget is not scoped to one record. `payloadHash` carries\n * `sha256Hex(subjectId)` so the ledger PROVES \"subject X existed and\n * was erased on date D\" without retaining the subject id or any\n * plaintext; `reason` holds a JSON summary of the shred counts. Like\n * `amendment`/`lifecycle` it carries no data envelope and is skipped\n * by the reconstruct walker (it still participates in the chain, so\n * `verify()` passes after a shred).\n */\n readonly op: 'put' | 'delete' | 'amendment' | 'lifecycle' | 'migration' | 'forget'\n\n /** The collection the mutation targeted. */\n readonly collection: string\n\n /** The record id the mutation targeted. */\n readonly id: string\n\n /**\n * The record version AFTER this mutation. For `put` this is the\n * newly assigned version; for `delete` this is the version that\n * was deleted (the last version visible to reads).\n */\n readonly version: number\n\n /** ISO timestamp of the mutation. */\n readonly ts: string\n\n /** User id of the actor who performed the mutation. */\n readonly actor: string\n\n /**\n * Hex-encoded sha256 over the encrypted envelope's `_data` field, plus its\n * sealed-field ciphertext map (`_sealed`) when the record carries one —\n * so the ledger attests to both the open body and every sealed\n * value. A record with no `_sealed` hashes `_data` alone.\n * For `put`, this is the hash of the new ciphertext. For `delete`,\n * it's the hash of the last visible ciphertext at deletion time, or the empty\n * string if nothing was there to delete. Hashing the ciphertext (not the\n * plaintext) preserves zero-knowledge — see the file docstring. The exact\n * recipe lives in `envelopePayloadHash` (`hash.ts`).\n */\n readonly payloadHash: string\n\n /**\n * Optional human-readable tag describing why this mutation happened.\n * Threaded through `collection.put(_, _, { reason })`. Common\n * values include `'import:csv'`, `'import:json'`, `'import:xlsx'` from\n * `as-*` ImportPlan.apply(), but consumers can use any string for\n * domain-specific audit filtering. Auto-strip via `canonicalJson` —\n * absent on the wire, never serialized as `null`.\n *\n * Audit consumers filter: `entries.filter(e => e.reason?.startsWith('import:'))`.\n */\n readonly reason?: string\n\n /**\n * Optional hex-encoded sha256 of the encrypted JSON Patch delta\n * blob stored alongside this entry in `_ledger_deltas/`. Present\n * only for `put` operations that had a previous version — the\n * genesis put of a new record, and every `delete`, leave this\n * field undefined.\n *\n * The delta payload itself lives in a sibling internal collection\n * (`_ledger_deltas/<paddedIndex>`) and is encrypted with the\n * ledger DEK. Callers use `ledger.loadDelta(index)` to decrypt and\n * deserialize it when reconstructing a historical version.\n *\n * Why optional instead of always-present: the first put of a\n * record has no previous version to diff against, so storing an\n * empty patch would be noise. For deletes there's no \"next\" state\n * to describe with a delta. Both cases set this field to undefined.\n *\n * Note: the canonical-JSON hasher treats `undefined` as invalid\n * (it's one of the guard rails), so on the wire this field is\n * either `{ deltaHash: '<hex>' }` or absent from the JSON\n * entirely — never `{ deltaHash: undefined }`.\n */\n readonly deltaHash?: string\n\n /**\n * Present only when `op === 'amendment'`. Records the human reason,\n * the role of the actor, the (collection, id, vBefore, vAfter) tuple\n * for every record touched, and which guard invariants passed.\n *\n * See design-history/2026-05-18-guards-design.md.\n */\n readonly amendment?: {\n readonly reason: string\n readonly role: 'admin' | 'owner'\n readonly changes: ReadonlyArray<{\n readonly collection: string\n readonly id: string\n readonly vBefore: number\n readonly vAfter: number\n }>\n readonly invariantsPassed: ReadonlyArray<string>\n }\n}\n\n/**\n * Canonical (sort-stable) JSON encoder.\n *\n * This function is the load-bearing primitive of the hash chain:\n * `sha256(canonicalJSON(entry))` must produce the same hex string\n * every time, on every machine, for the same logical entry — otherwise\n * `verify()` would return `{ ok: false }` on cross-platform reads.\n *\n * JavaScript's `JSON.stringify` is almost canonical, but NOT quite:\n * it preserves the insertion order of object keys, which means\n * `{a:1,b:2}` and `{b:2,a:1}` serialize differently. We fix this by\n * recursively walking objects and sorting their keys before\n * concatenation.\n *\n * Arrays keep their original order (reordering them would change\n * semantics). Numbers, strings, booleans, and `null` use the default\n * JSON encoding. `undefined` and functions are rejected — ledger\n * entries are plain data, and silently dropping `undefined` would\n * break the \"same input → same hash\" property if a caller forgot to\n * omit a field.\n *\n * Performance: one pass per nesting level; O(n log n) for key sorting\n * at each object. Entries are small (< 1 KB) so this is negligible\n * compared to the sha256 call.\n */\nexport function canonicalJson(value: unknown): string {\n if (value === null) return 'null'\n if (typeof value === 'boolean') return value ? 'true' : 'false'\n if (typeof value === 'number') {\n if (!Number.isFinite(value)) {\n throw new Error(\n `canonicalJson: refusing to encode non-finite number ${String(value)}`,\n )\n }\n return JSON.stringify(value)\n }\n if (typeof value === 'string') return JSON.stringify(value)\n if (typeof value === 'bigint') {\n throw new Error('canonicalJson: BigInt is not JSON-serializable')\n }\n if (typeof value === 'undefined' || typeof value === 'function') {\n throw new Error(\n `canonicalJson: refusing to encode ${typeof value} — include all fields explicitly`,\n )\n }\n if (Array.isArray(value)) {\n return '[' + value.map((v) => canonicalJson(v)).join(',') + ']'\n }\n if (typeof value === 'object') {\n const obj = value as Record<string, unknown>\n const keys = Object.keys(obj).sort()\n const parts: string[] = []\n for (const key of keys) {\n parts.push(JSON.stringify(key) + ':' + canonicalJson(obj[key]))\n }\n return '{' + parts.join(',') + '}'\n }\n throw new Error(`canonicalJson: unexpected value type: ${typeof value}`)\n}\n\n/**\n * Compute a hex-encoded sha256 of a string via Web Crypto's subtle API.\n *\n * We use hex (not base64) for hashes because hex is case-insensitive,\n * fixed-length (64 chars), and easier to compare visually in debug\n * output. Base64 would save a few bytes in storage but every encrypted\n * ledger entry is already much larger than the hash itself.\n */\nexport async function sha256Hex(input: string): Promise<string> {\n return sha256HexBytes(new TextEncoder().encode(input))\n}\n\n/**\n * Compute the canonical hash of a ledger entry. Short wrapper around\n * `canonicalJson` + `sha256Hex`; callers use this instead of composing\n * the two functions every time, so any future change to the hashing\n * pipeline (e.g., adding a domain-separation prefix) lives in one place.\n */\nexport async function hashEntry(entry: LedgerEntry): Promise<string> {\n return sha256Hex(canonicalJson(entry))\n}\n\n/**\n * Pad an index to the canonical 10-digit form used as the adapter key.\n * Ten digits is enough for ~10 billion ledger entries per vault\n * — far beyond any realistic use case, but cheap enough that the extra\n * digits don't hurt storage.\n */\nexport function paddedIndex(index: number): string {\n return String(index).padStart(10, '0')\n}\n\n/** Parse a padded adapter key back into a number. Returns NaN on malformed input. */\nexport function parseIndex(key: string): number {\n return Number.parseInt(key, 10)\n}\n","/**\n * Envelope payload hash — pinned in its own leaf module so consumers\n * (DictionaryHandle, the active history strategy) can import it\n * without dragging in the `LedgerStore` class.\n *\n * see `constants.ts` for the broader rationale.\n *\n * @internal\n */\n\nimport type { EncryptedEnvelope } from '../../../kernel/types.js'\nimport { sha256Hex } from './entry.js'\nimport { envelopeBodyForHash } from '../../../kernel/enclave/index.js'\n\n/**\n * Compute the `payloadHash` value for an encrypted envelope. Used by\n * `LedgerStore.append` for both put (hash the new envelope) and\n * delete (hash the previous envelope) paths, and by\n * `DictionaryHandle` so its ledger entries match the same contract.\n *\n * Hashes the open `_data` ciphertext, plus the sealed-field ciphertext\n * map (`_sealed`) when a record carries one — so the ledger attests to\n * both the open body AND every sealed value … and the verify-digest\n * ciphertext map (`_vdig`), each bound only when present — the `_vdig`\n * binding is the temporal-rollback detector for the C1 splice class.\n * `rotateRecordCek` rewrites `_vdig` with no ledger entry, the same\n * pre-existing rotation property `_sealed`/`_cek` already have\n * (`verifyBackupIntegrity` flags rotated records until re-anchored).\n *\n * Returns the empty string when there is no envelope (delete of a\n * never-existed record). The empty string tolerated by the ledger\n * entry's `payloadHash` field as the canonical \"nothing here\" value.\n */\nexport async function envelopePayloadHash(\n envelope: EncryptedEnvelope | null,\n): Promise<string> {\n if (!envelope) return ''\n // Back-compat: a record with NO sealed fields hashes as sha256 of\n // `_data` alone — so every pre-existing ledger entry\n // and every non-sealed backup verifies byte-identically. A record WITH\n // `_sealed` widens the hash to also bind the sealed-field ciphertext, so the\n // ledger attests to sealed-value tamper/erasure (a dropped or swapped\n // `_sealed[field]` now diverges `verifyBackupIntegrity`'s data cross-check).\n // `envelopeBodyForHash` (the enclave barrel's C1 protected-body access\n // contract) derives this exact string — see its own doc for the\n // canonicalization recipe (sorted keys, independent of `_sealed`'s\n // field-insertion / store-serialization order).\n //\n // `_cek` is deliberately NOT bound: a tampered wrapped-CEK already self-\n // detects (it fails to unwrap), and `rotateRecordCek` rewrites `_cek` with no\n // ledger entry — binding it would make every legitimate rotation fail verify.\n //\n // RESIDUAL: a backup containing sealed records created BEFORE this change\n // stored `payloadHash` over `_data` only, so it will fail the data cross-check\n // and must be re-anchored (re-`put`/re-baseline). Sealed fields are new, so\n // this is expected to affect ~no real vault.\n //\n // The two branches are NOT domain-separated — D1 pins the no-`_sealed` branch\n // to exactly `sha256(_data)`, so it cannot carry a discriminator tag. An actor\n // with raw-store write access could therefore set a non-sealed record's `_data`\n // equal to the canonical-JSON string below and collide a sealed record's hash —\n // but doing so overwrites `_data` itself, which then no longer GCM-decrypts, so\n // it only suppresses the verify signal on an already-destroyed record; it cannot\n // silently erase one sealed field while leaving the record readable.\n return sha256Hex(envelopeBodyForHash(envelope))\n}\n"],"mappings":";;;;;;;;AA2NO,SAAS,cAAc,OAAwB;AACpD,MAAI,UAAU,KAAM,QAAO;AAC3B,MAAI,OAAO,UAAU,UAAW,QAAO,QAAQ,SAAS;AACxD,MAAI,OAAO,UAAU,UAAU;AAC7B,QAAI,CAAC,OAAO,SAAS,KAAK,GAAG;AAC3B,YAAM,IAAI;AAAA,QACR,uDAAuD,OAAO,KAAK,CAAC;AAAA,MACtE;AAAA,IACF;AACA,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B;AACA,MAAI,OAAO,UAAU,SAAU,QAAO,KAAK,UAAU,KAAK;AAC1D,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,IAAI,MAAM,gDAAgD;AAAA,EAClE;AACA,MAAI,OAAO,UAAU,eAAe,OAAO,UAAU,YAAY;AAC/D,UAAM,IAAI;AAAA,MACR,qCAAqC,OAAO,KAAK;AAAA,IACnD;AAAA,EACF;AACA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,MAAM,IAAI,CAAC,MAAM,cAAc,CAAC,CAAC,EAAE,KAAK,GAAG,IAAI;AAAA,EAC9D;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,MAAM;AACZ,UAAM,OAAO,OAAO,KAAK,GAAG,EAAE,KAAK;AACnC,UAAM,QAAkB,CAAC;AACzB,eAAW,OAAO,MAAM;AACtB,YAAM,KAAK,KAAK,UAAU,GAAG,IAAI,MAAM,cAAc,IAAI,GAAG,CAAC,CAAC;AAAA,IAChE;AACA,WAAO,MAAM,MAAM,KAAK,GAAG,IAAI;AAAA,EACjC;AACA,QAAM,IAAI,MAAM,yCAAyC,OAAO,KAAK,EAAE;AACzE;AAUA,eAAsBA,WAAU,OAAgC;AAC9D,SAAO,UAAe,IAAI,YAAY,EAAE,OAAO,KAAK,CAAC;AACvD;AAQA,eAAsB,UAAU,OAAqC;AACnE,SAAOA,WAAU,cAAc,KAAK,CAAC;AACvC;AAQO,SAAS,YAAY,OAAuB;AACjD,SAAO,OAAO,KAAK,EAAE,SAAS,IAAI,GAAG;AACvC;AAGO,SAAS,WAAW,KAAqB;AAC9C,SAAO,OAAO,SAAS,KAAK,EAAE;AAChC;;;AChQA,eAAsB,oBACpB,UACiB;AACjB,MAAI,CAAC,SAAU,QAAO;AA4BtB,SAAOC,WAAU,oBAAoB,QAAQ,CAAC;AAChD;","names":["sha256Hex","sha256Hex"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
NOYDB_FORMAT_VERSION
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-YPYHVOXP.js";
|
|
4
4
|
|
|
5
5
|
// src/kernel/enclave/record-keys/tombstone.ts
|
|
6
6
|
function isTombstoneShape(envelope) {
|
|
@@ -42,4 +42,4 @@ export {
|
|
|
42
42
|
isDeleteMarker,
|
|
43
43
|
buildDeleteMarker
|
|
44
44
|
};
|
|
45
|
-
//# sourceMappingURL=chunk-
|
|
45
|
+
//# sourceMappingURL=chunk-7H73RIE3.js.map
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
openEnvelopeJson
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-3URQC7EH.js";
|
|
4
4
|
import {
|
|
5
5
|
encrypt
|
|
6
6
|
} from "./chunk-VCETL77O.js";
|
|
7
7
|
import {
|
|
8
8
|
NOYDB_FORMAT_VERSION
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-YPYHVOXP.js";
|
|
10
10
|
import {
|
|
11
11
|
SequenceContentionError,
|
|
12
12
|
SequenceNotEnabledError,
|
|
@@ -199,4 +199,4 @@ export {
|
|
|
199
199
|
resolveSequenceKey,
|
|
200
200
|
SequenceStore
|
|
201
201
|
};
|
|
202
|
-
//# sourceMappingURL=chunk-
|
|
202
|
+
//# sourceMappingURL=chunk-7MNBICB5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/kernel/via/graph.ts"],"sourcesContent":["// kernel/via/graph.ts — the per-vault ViaGraph dependency model (Via port phase C, #638).\n//\n// Metadata-only dependency graph over (collection, field) nodes: source-field\n// postures, derived-field edges, and the taint (effective-posture) algebra.\n// NEVER stores record values or key material — see\n// design-history/2026-07-11-via-phase-c-design.md §1/§2.\n\nimport { DerivationCycleError, MaterializedViewCycleError } from '../errors.js'\nimport type { ViaPosture } from './index.js'\n\n/** A (collection, field) node. Artifact-grain targets (rollup field, MV row-class,\n * overlay output) are modelled as a field node whose `field` is the artifact key. */\nexport interface FieldRef { readonly collection: string; readonly field: string }\n\nexport type EdgeKind = 'computed' | 'derivation' | 'rollup' | 'mv' | 'overlay' | 'ref'\n/** `'virtual'` (#638 Task 7) marks a `computed(fn, { mode: 'virtual' })` field's edge —\n * never stored (rides the `present` phase), so it can never be sealed at rest\n * ({@link ViaGraph.taintSealedFields} excludes it) and is always non-queryable\n * regardless of source posture ({@link ViaGraph.effectivePosture} clamps it). */\nexport type Grain = 'record' | 'aggregate' | 'virtual'\n\n/** Delete/forget-time referential policy for a `'ref'` edge (#650 Task 5, spec §4) — duplicated\n * (not imported) from `via/lookup/descriptor.ts`'s `OnDelete`: the kernel spine may not\n * statically import `via/**` (Check 14, `via-layering`). Keep the two in sync by hand. */\nexport type OnDelete = 'restrict' | 'cascade' | 'nullify'\n\n/** Plain (non-via) field baseline — max-permissive; taint only ever tightens. */\nexport const DEFAULT_POSTURE: ViaPosture =\n { encryptedAtRest: 'envelope', queryable: 'full', exportable: true, forgettable: false }\n\nconst QUERYABLE_RANK: Record<ViaPosture['queryable'], number> = {\n none: 0,\n 'det-exact': 1,\n ordered: 2,\n full: 3,\n}\n\n/** The per-axis \"most restrictive\" fold — pure, exported for direct unit testing. */\nexport function foldPosture(a: ViaPosture, b: ViaPosture): ViaPosture {\n return {\n encryptedAtRest: a.encryptedAtRest === 'sealed' || b.encryptedAtRest === 'sealed' ? 'sealed' : 'envelope',\n queryable: QUERYABLE_RANK[a.queryable] <= QUERYABLE_RANK[b.queryable] ? a.queryable : b.queryable,\n exportable: a.exportable && b.exportable,\n forgettable: a.forgettable || b.forgettable,\n }\n}\n\n/** Security-axes-only fold for a wildcard collection node (#642): sealed-wins encryptedAtRest,\n * AND exportable, OR forgettable — queryable is NOT folded (stays base.queryable; the sealed\n * clamp to 'none' is buildTaintOverlay's job, so inheriting `sealed` never over-restricts a\n * blob-adjacent output to unqueryable). Pure; exported for unit testing. Distinct from\n * {@link foldPosture} (which folds all four axes) — do NOT reuse foldPosture here. */\nexport function foldWildcardSecurity(base: ViaPosture, contributor: ViaPosture): ViaPosture {\n return {\n encryptedAtRest: base.encryptedAtRest === 'sealed' || contributor.encryptedAtRest === 'sealed' ? 'sealed' : 'envelope',\n queryable: base.queryable,\n exportable: base.exportable && contributor.exportable,\n forgettable: base.forgettable || contributor.forgettable,\n }\n}\n\n// the folded formula kinds a '*' source contributes its collection-fold to (ref keeps identity):\ntype FoldedKind = 'derivation' | 'rollup' | 'mv' | 'overlay'\nconst FOLDED_KINDS: ReadonlySet<EdgeKind> = new Set<FoldedKind>(['derivation', 'rollup', 'mv', 'overlay'])\n\n/** Whether `p` is exactly the plain, non-taint baseline (Task 3 — `taintProvenance`). */\nfunction isDefaultPosture(p: ViaPosture): boolean {\n return p.encryptedAtRest === DEFAULT_POSTURE.encryptedAtRest && p.queryable === DEFAULT_POSTURE.queryable &&\n p.exportable === DEFAULT_POSTURE.exportable && p.forgettable === DEFAULT_POSTURE.forgettable\n}\n\n/** One `_out` entry — a `source -> target` derived edge, carrying its OWN registration's\n * `kind` (#678). `kind` must be edge-local, not re-derived from `_in`: `_in` is single-slot\n * per target and can be overwritten by a LATER `registerDerived` call on the SAME target\n * (a dual-role target, e.g. #631's exempt {computed, lookup} field composition) — asking\n * `_in` for \"this target's current kind\" answers the wrong question for a specific edge. */\ninterface OutEdge { readonly target: FieldRef; readonly kind: EdgeKind }\n\n/** One registered `registerDerived` call — a target's whole in-edge set. */\ninterface DerivedEdge {\n readonly target: FieldRef\n readonly sources: readonly FieldRef[]\n readonly kind: EdgeKind\n readonly grain: Grain\n /** `'ref'` edges only — the referencing field's delete/forget policy (#650 Task 5). */\n readonly onDelete?: OnDelete\n /** `'ref'` edges only — the backing dimension's canonical-key FIELD NAME on its own row (matrix\n * tier's `lookup(dim, {key})`; always `'id'` for reserved/static tiers). Defaults to `'id'`\n * when absent — a referencing field always stores this field's VALUE, never the backing row's\n * PUT-id when the two differ (#650 Task 5, mirrors Task 3's membership review fix). */\n readonly keyField?: string\n}\n\nconst SEP = '\\0'\nconst EMPTY_SET: ReadonlySet<string> = new Set()\n\nfunction nodeId(ref: FieldRef): string {\n return `${ref.collection}${SEP}${ref.field}`\n}\n\n/** Inverse of `nodeId`, for building a human-readable cycle path. */\nfunction displayNodeId(id: string): string {\n const sep = id.indexOf(SEP)\n return `${id.slice(0, sep)}.${id.slice(sep + 1)}`\n}\n\n/** Metadata-only dependency graph, ONE per vault. Never stores values or key material. */\nexport class ViaGraph {\n /** Declared source-field postures (registerField), keyed by node id. */\n private readonly _posture = new Map<string, ViaPosture>()\n /** Every derived target's registration, keyed by the target's node id. */\n private readonly _in = new Map<string, DerivedEdge>()\n /** source node id -> derived edges that depend on it (dispatch/erasure), each carrying\n * its own registration's `kind` (#678 — see {@link OutEdge}). */\n private readonly _out = new Map<string, OutEdge[]>()\n /** Memoized effective posture per derived target node id. */\n private readonly _effectiveCache = new Map<string, ViaPosture>()\n /** Memoized whole-collection security-axes fold for `'*'` wildcard LEAF nodes (#642),\n * keyed by collection name — see {@link _wildcardContribution}. Cleared alongside\n * `_effectiveCache` on every `registerField`/`registerDerived` (a later-registered\n * field must re-fold on the next read — registration ordering is free). */\n private readonly _wildcardCache = new Map<string, ViaPosture>()\n /** Collections with at least one classified field (#638 Task 2 fix wave 2,\n * Finding I1 — the reconcile path's combined-state leak guard memory). */\n private readonly _classifiedCollections = new Set<string>()\n /** Computed field names declared with no `deps` entry, per collection\n * (Finding I1 — such a field registers no edge when no classified field is\n * present yet, so a LATER, separate reconcile call attaching classifiedFields\n * still needs to see it regardless of declaration order). */\n private readonly _depslessComputed = new Map<string, Set<string>>()\n\n /** Declare a source field's posture (money/i18n/classified/plain). Plain fields\n * default to `DEFAULT_POSTURE`; a later declaration for the same node wins-first (idempotent). */\n registerField(collection: string, field: string, posture: ViaPosture): void {\n const id = nodeId({ collection, field })\n if (this._posture.has(id)) return\n this._posture.set(id, posture)\n this._effectiveCache.clear()\n this._wildcardCache.clear()\n }\n\n /** A derived target depends on `sources` (may be cross-collection). `kind`/`grain`\n * drive dispatch + erasure semantics; sources drive taint. Re-registering the same\n * target replaces `_in` but does not prune stale `_out` edges — callers must\n * register each target at most once per graph lifetime. `onDelete`/`keyField` (#650 Task 5)\n * are meaningful only for `kind === 'ref'` edges — {@link referencingEdgesOf} reads them back. */\n registerDerived(target: FieldRef, sources: readonly FieldRef[], kind: EdgeKind, grain: Grain, onDelete?: OnDelete, keyField?: string): void {\n const id = nodeId(target)\n this._in.set(id, {\n target, sources, kind, grain,\n ...(onDelete !== undefined ? { onDelete } : {}),\n ...(keyField !== undefined ? { keyField } : {}),\n })\n for (const source of sources) {\n const sourceId = nodeId(source)\n const outEdge: OutEdge = { target, kind }\n const dependents = this._out.get(sourceId)\n if (dependents) dependents.push(outEdge)\n else this._out.set(sourceId, [outEdge])\n }\n this._effectiveCache.clear()\n this._wildcardCache.clear()\n }\n\n /** Whether `target` already has a registered in-edge — lets a caller skip\n * re-registering the same target (#638 Task 2 fix wave 2, Finding I2i:\n * `registerDerived`'s at-most-once contract must hold across repeated\n * identical `vault.collection()` calls, not just within a single one). */\n hasDerived(target: FieldRef): boolean {\n return this._in.has(nodeId(target))\n }\n\n /** Mark/query a collection as having declared at least one classified field. */\n markClassified(collection: string): void {\n this._classifiedCollections.add(collection)\n }\n\n isClassified(collection: string): boolean {\n return this._classifiedCollections.has(collection)\n }\n\n /** Mark/query a collection's depsless (no declared `deps`) computed\n * field names — see `_depslessComputed`'s doc comment above. */\n markDepslessComputed(collection: string, field: string): void {\n let set = this._depslessComputed.get(collection)\n if (!set) { set = new Set(); this._depslessComputed.set(collection, set) }\n set.add(field)\n }\n\n depslessComputedFields(collection: string): ReadonlySet<string> {\n return this._depslessComputed.get(collection) ?? EMPTY_SET\n }\n\n /** Every field name the graph already has memory of for `collection` — from an\n * EARLIER, separate `vault.collection()` call (registered postures, derived\n * targets, depsless-computed names) — regardless of which via feature declared\n * it. #645 — `via/graph-wiring.ts`'s reconcile-path `knownFields` universe\n * (`collectKnownFieldNames`) is scoped to THIS call's own options only, so a\n * computed field attached in a LATER call whose `deps` correctly names a field\n * declared in an EARLIER call was spuriously refused as \"unknown\"; this method\n * lets that call site union in the graph's cross-call memory. Excludes the `'*'`\n * wildcard target (an overlay/derivation whole-record fold node, never a real\n * record field — see {@link registerDerived}'s doc comment). */\n knownFieldNames(collection: string): ReadonlySet<string> {\n const out = new Set<string>()\n const prefix = `${collection}${SEP}`\n for (const id of this._posture.keys()) {\n if (id.startsWith(prefix)) out.add(id.slice(prefix.length))\n }\n for (const edge of this._in.values()) {\n if (edge.target.collection === collection && edge.target.field !== '*') out.add(edge.target.field)\n }\n const depsless = this._depslessComputed.get(collection)\n if (depsless) for (const field of depsless) out.add(field)\n return out\n }\n\n /** Reject cycles at declare time (vault open). Throws `DerivationCycleError` for a\n * derivation/rollup/computed cycle, `MaterializedViewCycleError` for an MV cycle —\n * same classes + message shape the registries throw today (behavior lock). */\n assertAcyclic(): void {\n const visited = new Set<string>()\n const stack: string[] = []\n\n /** #639 — containment expansion: writing a real field `f` on collection `C` is\n * a write to `C`, so it must ALSO reach every whole-record (`'*'`) dependent of\n * `C` — the missing reachability step that made mutual/rotating rollup cycles\n * invisible (a rollup target is always a real field node and never itself a\n * graph SOURCE, so the old `_out.get(id)`-only walk dead-ended on it).\n *\n * TRAVERSAL-LOCAL ONLY (law, #642): this is a virtual adjacency computed\n * during the DFS walk, reading `_out` only. It must NEVER be materialized via\n * `registerDerived` (no new `(C,f)→(C,'*')` edge) and must NEVER touch `_in` —\n * putting `(C,'*')` into `_in` would flip `_contribution('C\\0*')` from the\n * `_wildcardContribution` fold (`#642`) onto the `_computeEffective` path,\n * bleeding cycle-reachability taint into `foldWildcardSecurity`'s posture\n * fold. Posture folding (`_contribution`/`_computeEffective`/\n * `_wildcardContribution`) never reads `_out`, so an expansion that reads\n * `_out` only is provably unable to perturb it. */\n // #671 item 5 — exclude `kind:'ref'` consuming edges: mutual FK lookups (two\n // collections each referencing the other) are legal and must not be treated as a\n // derivation cycle. Ref edges exist for cascade/rename machinery\n // (`referencingEdgesOf`/delete-time restrict/cascade/nullify), never derivation\n // ordering, so they carry no ordering constraint here. #678 — `kind` is read off\n // THIS `_out` entry (edge-local), NOT re-derefed from `_in`: a dual-role target\n // (e.g. #631's exempt {computed, lookup} composition) can have its `_in` entry's\n // `kind` overwritten by a LATER `registerDerived` call for the SAME target, so\n // asking `_in` \"what kind is `t` registered as NOW\" wrongly excluded a real\n // computed edge from the DFS as if it were a ref edge, hiding a genuine\n // derivation cycle. Asking \"what kind is THIS specific edge\" is correct\n // regardless of what else was later registered for the target.\n const notRefEdge = (entry: OutEdge): boolean => entry.kind !== 'ref'\n const neighboursOf = (id: string): readonly FieldRef[] => {\n const own = (this._out.get(id) ?? []).filter(notRefEdge).map((entry) => entry.target)\n const sep = id.indexOf(SEP)\n if (id.slice(sep + 1) === '*') return own\n const wildcard = (this._out.get(`${id.slice(0, sep)}${SEP}*`) ?? []).filter(notRefEdge).map((entry) => entry.target)\n if (wildcard.length === 0) return own\n return [...own, ...wildcard]\n }\n\n const visit = (id: string): void => {\n const idx = stack.indexOf(id)\n if (idx !== -1) {\n const cycle = stack.slice(idx).concat(id)\n const isMv = cycle.some(cycleId => this._in.get(cycleId)?.kind === 'mv')\n const path = cycle.map(displayNodeId)\n if (isMv) throw new MaterializedViewCycleError(path)\n throw new DerivationCycleError(path)\n }\n if (visited.has(id)) return\n stack.push(id)\n for (const dependent of neighboursOf(id)) visit(nodeId(dependent))\n stack.pop()\n visited.add(id)\n }\n\n for (const id of this._out.keys()) visit(id)\n }\n\n /** A leaf (non-derived) node's own posture contribution: its declared posture,\n * or `DEFAULT_POSTURE` if never declared via `registerField`. */\n private _declaredPosture(id: string): ViaPosture {\n return this._posture.get(id) ?? DEFAULT_POSTURE\n }\n\n /** A node's posture contribution when folded into a dependent: recurse if it is itself\n * derived; else its declared/default posture — UNLESS it is a `'*'` LEAF node consumed\n * by a folded formula kind (#642, `derivation|rollup|mv|overlay`), in which case it\n * contributes its collection's whole-record security fold ({@link _wildcardContribution}).\n * `kind` is the CONSUMING edge's kind, threaded from {@link _computeEffective}. `'ref'`\n * edges (and the provenance-only untyped call, `kind` left `undefined`) keep `'*'` =\n * identity — the phase-D lookup reliance (`via/lookup/registry.ts:392-397`) that a\n * referencing field must not seal just because its dimension collection has a classified\n * column. */\n private _contribution(id: string, kind?: EdgeKind): ViaPosture {\n const edge = this._in.get(id)\n if (edge) return this._computeEffective(id, edge)\n if (kind !== undefined && FOLDED_KINDS.has(kind) && id.endsWith(`${SEP}*`)) {\n return this._wildcardContribution(id.slice(0, -2))\n }\n return this._declaredPosture(id)\n }\n\n /** Lazy fold (#642) of a collection's REGISTERED field postures on the security axes only\n * ({@link foldWildcardSecurity} — sealed-wins encryptedAtRest, AND exportable, OR\n * forgettable; queryable stays at DEFAULT_POSTURE's 'full', never folded), seeded at\n * DEFAULT_POSTURE. This is the collection-level posture a `'*'` LEAF node contributes to a\n * folded-kind formula edge (derivation/rollup/mv/overlay) — see {@link _contribution}. A\n * plain (unregistered) field contributes nothing (the identity), so the fold is strictly\n * more conservative than deps-based taint and never hits the KNOWN-LIMIT wall (it names no\n * fields — seam map §1d). Memoized per collection in `_wildcardCache`. */\n private _wildcardContribution(collection: string): ViaPosture {\n const cached = this._wildcardCache.get(collection)\n if (cached) return cached\n let result = DEFAULT_POSTURE\n const prefix = `${collection}${SEP}`\n for (const [id, posture] of this._posture) {\n if (!id.startsWith(prefix) || id === `${prefix}*`) continue\n result = foldWildcardSecurity(result, posture)\n }\n this._wildcardCache.set(collection, result)\n return result\n }\n\n private _computeEffective(id: string, edge: DerivedEdge): ViaPosture {\n const cached = this._effectiveCache.get(id)\n if (cached) return cached\n let result = DEFAULT_POSTURE\n for (const source of edge.sources) {\n result = foldPosture(result, this._contribution(nodeId(source), edge.kind))\n }\n // #638 Task 7 — a virtual field is computed fresh on every read and never\n // stored, so it is structurally unqueryable regardless of what its\n // sources' fold would otherwise permit (e.g. a money-sourced virtual\n // field must NOT inherit money's 'ordered' queryability — there is no\n // stored/indexed form to query against). Every OTHER axis (exportable/\n // forgettable/encryptedAtRest) still folds normally — the taint rule is\n // identical to a materialized field; only queryability is a fixed,\n // grain-level property, not a source-derived one.\n if (edge.grain === 'virtual' && result.queryable !== 'none') {\n result = { ...result, queryable: 'none' }\n }\n this._effectiveCache.set(id, result)\n return result\n }\n\n /** Strictest source posture on every axis, per §2. `undefined` when `target` has no\n * in-edges (not a derived field). Transitive: folds through chained derivations. */\n effectivePosture(target: FieldRef): ViaPosture | undefined {\n const id = nodeId(target)\n const edge = this._in.get(id)\n if (!edge) return undefined\n return this._computeEffective(id, edge)\n }\n\n /** Per-collection { field → effectivePosture } overlay the pipeline consumes (Task 3). */\n taintedPostures(collection: string): ReadonlyMap<string, ViaPosture> {\n const out = new Map<string, ViaPosture>()\n for (const edge of this._in.values()) {\n if (edge.target.collection !== collection) continue\n out.set(edge.target.field, this._computeEffective(nodeId(edge.target), edge))\n }\n return out\n }\n\n /** Materialized (`grain !== 'virtual'`) derived fields on `collection` whose effective\n * encryptedAtRest resolves to 'sealed' — the taint-seal set (Task 3). #638 Task 7\n * makes the `grain !== 'virtual'` filter meaningful: a `computed(fn, { mode: 'virtual' })`\n * field is never stored (rides the `present` phase, seam map Part 4's money-Formatted/\n * i18n-Label precedent), so it is EXCLUDED here even when its effective `encryptedAtRest`\n * folds to `'sealed'` — there is no envelope slot to seal. It still surfaces in\n * {@link taintedPostures} (query refusal via `postureFor`'s `queryable` clamp above, and\n * export refusal via `exportable`) — only the AT-REST SEALING ACTION is skipped. */\n taintSealedFields(collection: string): ReadonlySet<string> {\n const out = new Set<string>()\n for (const edge of this._in.values()) {\n if (edge.target.collection !== collection || edge.grain === 'virtual') continue\n const posture = this._computeEffective(nodeId(edge.target), edge)\n if (posture.encryptedAtRest === 'sealed') out.add(edge.target.field)\n }\n return out\n }\n\n /** Fields on `collection` whose registered edge is `grain === 'virtual'` (#638 Task 7) —\n * `via/graph-wiring.ts#applyTaintOverlay` intersects this with `exportable === false`\n * postures to know which virtual fields need PRESENT-TIME (not just export-time)\n * redaction, since a virtual field's value is only ever materialized inside `present()`. */\n virtualFields(collection: string): ReadonlySet<string> {\n const out = new Set<string>()\n for (const edge of this._in.values()) {\n if (edge.target.collection === collection && edge.grain === 'virtual') out.add(edge.target.field)\n }\n return out\n }\n\n /** Per-collection { field → immediate source field names that forced its\n * effective posture away from `DEFAULT_POSTURE` } — `describe()`'s\n * provenance (Task 3). Only the DIRECT declared sources are named (not the\n * ultimate origin several hops up a transitive chain); a source absent\n * from the result contributed nothing restrictive. */\n taintProvenance(collection: string): ReadonlyMap<string, readonly string[]> {\n const out = new Map<string, readonly string[]>()\n for (const edge of this._in.values()) {\n if (edge.target.collection !== collection) continue\n const forcedBy = edge.sources\n .filter((source) => !isDefaultPosture(this._contribution(nodeId(source))))\n .map((source) => source.field)\n if (forcedBy.length > 0) out.set(edge.target.field, forcedBy)\n }\n return out\n }\n\n private _targetsDependingOn(collection: string): ReadonlyArray<{ readonly target: FieldRef; readonly kind: EdgeKind; readonly grain: Grain }> {\n const out: Array<{ target: FieldRef; kind: EdgeKind; grain: Grain }> = []\n for (const edge of this._in.values()) {\n if (edge.sources.some(source => source.collection === collection)) {\n out.push({ target: edge.target, kind: edge.kind, grain: edge.grain })\n }\n }\n return out\n }\n\n /** Dispatch (Task 4): every derived target triggered by a write to `collection`. Excludes\n * `'ref'` edges (#650 Task 5 review, folded Minor) — a `'ref'` edge is consulted only by the\n * delete/forget-time restrict/cascade/nullify path ({@link referencingEdgesOf}), never by the\n * sync/cutover/restore dispatch wave (`runGraphDispatchWave`); counting it here defeated that\n * wave's zero-cost early-continue, costing every write to a referenced backing collection an\n * unconditional decrypt + two no-op dispatch passes. {@link derivedArtifactsOf} (erasure\n * fanout) still includes `'ref'` edges — the forget path genuinely needs them. */\n dependentsOf(collection: string): ReadonlyArray<{ readonly target: FieldRef; readonly kind: EdgeKind; readonly grain: Grain }> {\n return this._targetsDependingOn(collection).filter((edge) => edge.kind !== 'ref')\n }\n\n /** Erasure (Task 6): derived artifacts of a forgotten record whose source is `collection`. */\n derivedArtifactsOf(collection: string): ReadonlyArray<{ readonly target: FieldRef; readonly kind: EdgeKind; readonly grain: Grain }> {\n return this._targetsDependingOn(collection)\n }\n\n /** Referencing edges pointing AT a backing dimension (delete/forget-time restrict/cascade/\n * nullify, #650 Task 5) — an O(1) reverse lookup via the existing `_out` map (keyed at the\n * dimension's wildcard `field:'*'` node, the same convention every whole-collection source\n * already uses), NEVER a scan across collections or records. `backing` is the collection name\n * a lookup field's edge was registered against (a reserved dimension's `_dict_<name>`/\n * `_lookup_<name>` collection name, or a matrix dimension's own collection name). */\n referencingEdgesOf(backing: string): ReadonlyArray<{ readonly referencing: FieldRef; readonly onDelete: OnDelete; readonly keyField: string }> {\n const targets = this._out.get(nodeId({ collection: backing, field: '*' }))\n if (!targets) return []\n const out: Array<{ referencing: FieldRef; onDelete: OnDelete; keyField: string }> = []\n for (const entry of targets) {\n // #678 — `kind` is read directly off THIS `_out` entry (edge-local) instead of\n // re-derefing `_in`. This also fixes the symmetric ref-vanishes hazard: under a\n // registration order where a LATER `registerDerived` call for the same target\n // overwrites `_in`'s kind away from `'ref'`, the old `_in`-derefing check would\n // silently drop a genuine referencing edge from cascade/nullify delete machinery\n // even though this specific edge was truly registered as `'ref'`.\n if (entry.kind !== 'ref') continue\n const edge = this._in.get(nodeId(entry.target))\n if (edge?.onDelete !== undefined) {\n out.push({ referencing: entry.target, onDelete: edge.onDelete, keyField: edge.keyField ?? 'id' })\n }\n }\n return out\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
grant,
|
|
3
3
|
revoke
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-JRKZTEZZ.js";
|
|
5
5
|
import {
|
|
6
6
|
CustodyNotEnabledError
|
|
7
7
|
} from "./chunk-HNGSV2YC.js";
|
|
@@ -16,7 +16,7 @@ function withCustody() {
|
|
|
16
16
|
return host._revokeCustodianImpl(revoke, vault, options, factors);
|
|
17
17
|
},
|
|
18
18
|
async liberate(vault, opts) {
|
|
19
|
-
const { liberateVault } = await import("./liberate-
|
|
19
|
+
const { liberateVault } = await import("./liberate-GFN4C7RR.js");
|
|
20
20
|
return liberateVault(vault, opts);
|
|
21
21
|
}
|
|
22
22
|
};
|
|
@@ -74,4 +74,4 @@ export {
|
|
|
74
74
|
withCustody,
|
|
75
75
|
CustodyApi
|
|
76
76
|
};
|
|
77
|
-
//# sourceMappingURL=chunk-
|
|
77
|
+
//# sourceMappingURL=chunk-ADW22QSB.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/with-party/custody/active.ts","../src/with-party/custody/strategy.ts","../src/with-party/custody/index.ts"],"sourcesContent":["/**\n * Enable the sovereign-custody (FR-6) capability.\n * Pass to `createNoydb({ custodyStrategy: withCustody() })` to make\n * `db.grantCustodian` / `db.revokeCustodian` (and the `vault.custody.*` facade\n * that delegates to them) plus `vault.custody.liberate()` live. The heavy\n * `liberateVault` ceremony is dynamically imported here, so it is reached only\n * via opt-in; grant/revoke run the host's own gate + keyring engine.\n */\nimport type { CustodyStrategy } from './strategy.js'\nimport { grant as grantEngine, revoke as revokeEngine } from '../team/keyring.js'\n\nexport function withCustody(): CustodyStrategy {\n return {\n async grantCustodian(host, vault, options, factors) {\n return host._grantCustodianImpl(grantEngine, vault, options, factors)\n },\n async revokeCustodian(host, vault, options, factors) {\n return host._revokeCustodianImpl(revokeEngine, vault, options, factors)\n },\n async liberate(vault, opts) {\n const { liberateVault } = await import('./liberate.js')\n return liberateVault(vault, opts)\n },\n }\n}\n","/**\n * Sovereign-custody (FR-6) capability strategy — the three on-demand\n * operations the custody surface routes through: minting a custodian\n * (`grantCustodian`), removing one (`revokeCustodian`), and the audited\n * ownership-claim ceremony (`liberate`). The active engine ({@link withCustody})\n * runs the grant/revoke impls the host exposes and dynamically imports the\n * heavy `liberateVault` ceremony (keeping it out of the floor bundle);\n * {@link NO_CUSTODY} throws.\n *\n * `Noydb.grantCustodian` / `Noydb.revokeCustodian` delegate here (passing\n * themselves as the {@link CustodyHost}), and `vault.custody.liberate` delegates\n * here with its {@link Vault}. An un-opted-in caller hits `NO_CUSTODY`'s throw.\n *\n * Note: the lower-level `liberateVault` FREE FUNCTION (exported from\n * `custody/liberate.ts`) stays ungated — it has no `createNoydb` instance to\n * gate against (the same carve-out as the `openSealedRecord` host opener). Only\n * the instance surfaces (`db.grantCustodian` / `db.revokeCustodian` /\n * `vault.custody.*`) are the capability.\n * @internal\n */\nimport type { GrantOptions, RevokeOptions, FactorProofBundle, NoydbStore } from '../../kernel/types.js'\nimport type { Vault } from '../../kernel/vault.js'\nimport type { UnlockedKeyring } from '../team/keyring.js'\nimport type { GrantCustodianOptions } from './index.js'\nimport type { LiberateOptions, LiberateResult } from './liberate.js'\nimport { CustodyNotEnabledError } from '../../kernel/errors.js'\n\n/**\n * The grant/revoke engine the strategy runs when opted in — implemented by\n * `Noydb` (`_grantCustodianImpl` / `_revokeCustodianImpl` hold the gate +\n * keyring logic; the public `grantCustodian` / `revokeCustodian` route through\n * the strategy). Since the #267 team split the keyring grant/revoke engines\n * are passed IN by `withCustody()` (statically linked there, not in the\n * kernel) so the single-user floor never carries them.\n */\nexport interface CustodyHost {\n _grantCustodianImpl(\n engine: (adapter: NoydbStore, vault: string, callerKeyring: UnlockedKeyring, options: GrantOptions) => Promise<void>,\n vault: string,\n options: GrantCustodianOptions,\n factors?: FactorProofBundle,\n ): Promise<void>\n _revokeCustodianImpl(\n engine: (adapter: NoydbStore, vault: string, callerKeyring: UnlockedKeyring, options: RevokeOptions) => Promise<void>,\n vault: string,\n options: RevokeOptions,\n factors?: FactorProofBundle,\n ): Promise<void>\n}\n\nexport interface CustodyStrategy {\n grantCustodian(host: CustodyHost, vault: string, options: GrantCustodianOptions, factors?: FactorProofBundle): Promise<void>\n revokeCustodian(host: CustodyHost, vault: string, options: RevokeOptions, factors?: FactorProofBundle): Promise<void>\n liberate(vault: Vault, opts: LiberateOptions): Promise<LiberateResult>\n}\n\n/**\n * No-op stub — the floor default. Every custody operation throws\n * {@link CustodyNotEnabledError}; opt in with `custodyStrategy: withCustody()`\n * in createNoydb. @internal\n */\nexport const NO_CUSTODY: CustodyStrategy = {\n async grantCustodian() { throw new CustodyNotEnabledError() },\n async revokeCustodian() { throw new CustodyNotEnabledError() },\n async liberate() { throw new CustodyNotEnabledError() },\n}\n","/**\n * Public `vault.custody.*` API surface (FR-6).\n *\n * The custody namespace is the vault-instance face of the FR-6 sovereign-custody\n * model — it mirrors `vault.user.*` exactly: a thin delegation shell with NO\n * business logic. The Vault constructs one `CustodyApi` per session, injecting\n * closures that bind the vault name / keyring into the genuinely-core\n * implementations (`Noydb.grantCustodian` / `Noydb.revokeCustodian` and the\n * `liberateVault` ceremony). Each method just forwards to its injected callback.\n *\n * Three operations:\n * - `grantCustodian(opts)` — owner-only: mint a `custodian` who operates the\n * vault fully but can never grant / rotate / sever / extract.\n * - `revokeCustodian(opts)` — owner-only: remove a custodian.\n * - `liberate(opts)` — custodian-only: audited claim of ownership over a\n * sealed-owner (Deed) vault (mints a DISTINCT new owner; ledger-audited).\n *\n * Provisioning a Deed (`createDeedOwner`) is deliberately NOT on this class: it\n * is a store-level operation that mints the vault's first owner, so there is no\n * vault instance (and thus no custody namespace) yet — it stays the exported\n * `team/deed.ts` function.\n *\n * @see
|
|
1
|
+
{"version":3,"sources":["../src/with-party/custody/active.ts","../src/with-party/custody/strategy.ts","../src/with-party/custody/index.ts"],"sourcesContent":["/**\n * Enable the sovereign-custody (FR-6) capability.\n * Pass to `createNoydb({ custodyStrategy: withCustody() })` to make\n * `db.grantCustodian` / `db.revokeCustodian` (and the `vault.custody.*` facade\n * that delegates to them) plus `vault.custody.liberate()` live. The heavy\n * `liberateVault` ceremony is dynamically imported here, so it is reached only\n * via opt-in; grant/revoke run the host's own gate + keyring engine.\n */\nimport type { CustodyStrategy } from './strategy.js'\nimport { grant as grantEngine, revoke as revokeEngine } from '../team/keyring.js'\n\nexport function withCustody(): CustodyStrategy {\n return {\n async grantCustodian(host, vault, options, factors) {\n return host._grantCustodianImpl(grantEngine, vault, options, factors)\n },\n async revokeCustodian(host, vault, options, factors) {\n return host._revokeCustodianImpl(revokeEngine, vault, options, factors)\n },\n async liberate(vault, opts) {\n const { liberateVault } = await import('./liberate.js')\n return liberateVault(vault, opts)\n },\n }\n}\n","/**\n * Sovereign-custody (FR-6) capability strategy — the three on-demand\n * operations the custody surface routes through: minting a custodian\n * (`grantCustodian`), removing one (`revokeCustodian`), and the audited\n * ownership-claim ceremony (`liberate`). The active engine ({@link withCustody})\n * runs the grant/revoke impls the host exposes and dynamically imports the\n * heavy `liberateVault` ceremony (keeping it out of the floor bundle);\n * {@link NO_CUSTODY} throws.\n *\n * `Noydb.grantCustodian` / `Noydb.revokeCustodian` delegate here (passing\n * themselves as the {@link CustodyHost}), and `vault.custody.liberate` delegates\n * here with its {@link Vault}. An un-opted-in caller hits `NO_CUSTODY`'s throw.\n *\n * Note: the lower-level `liberateVault` FREE FUNCTION (exported from\n * `custody/liberate.ts`) stays ungated — it has no `createNoydb` instance to\n * gate against (the same carve-out as the `openSealedRecord` host opener). Only\n * the instance surfaces (`db.grantCustodian` / `db.revokeCustodian` /\n * `vault.custody.*`) are the capability.\n * @internal\n */\nimport type { GrantOptions, RevokeOptions, FactorProofBundle, NoydbStore } from '../../kernel/types.js'\nimport type { Vault } from '../../kernel/vault.js'\nimport type { UnlockedKeyring } from '../team/keyring.js'\nimport type { GrantCustodianOptions } from './index.js'\nimport type { LiberateOptions, LiberateResult } from './liberate.js'\nimport { CustodyNotEnabledError } from '../../kernel/errors.js'\n\n/**\n * The grant/revoke engine the strategy runs when opted in — implemented by\n * `Noydb` (`_grantCustodianImpl` / `_revokeCustodianImpl` hold the gate +\n * keyring logic; the public `grantCustodian` / `revokeCustodian` route through\n * the strategy). Since the #267 team split the keyring grant/revoke engines\n * are passed IN by `withCustody()` (statically linked there, not in the\n * kernel) so the single-user floor never carries them.\n */\nexport interface CustodyHost {\n _grantCustodianImpl(\n engine: (adapter: NoydbStore, vault: string, callerKeyring: UnlockedKeyring, options: GrantOptions) => Promise<void>,\n vault: string,\n options: GrantCustodianOptions,\n factors?: FactorProofBundle,\n ): Promise<void>\n _revokeCustodianImpl(\n engine: (adapter: NoydbStore, vault: string, callerKeyring: UnlockedKeyring, options: RevokeOptions) => Promise<void>,\n vault: string,\n options: RevokeOptions,\n factors?: FactorProofBundle,\n ): Promise<void>\n}\n\nexport interface CustodyStrategy {\n grantCustodian(host: CustodyHost, vault: string, options: GrantCustodianOptions, factors?: FactorProofBundle): Promise<void>\n revokeCustodian(host: CustodyHost, vault: string, options: RevokeOptions, factors?: FactorProofBundle): Promise<void>\n liberate(vault: Vault, opts: LiberateOptions): Promise<LiberateResult>\n}\n\n/**\n * No-op stub — the floor default. Every custody operation throws\n * {@link CustodyNotEnabledError}; opt in with `custodyStrategy: withCustody()`\n * in createNoydb. @internal\n */\nexport const NO_CUSTODY: CustodyStrategy = {\n async grantCustodian() { throw new CustodyNotEnabledError() },\n async revokeCustodian() { throw new CustodyNotEnabledError() },\n async liberate() { throw new CustodyNotEnabledError() },\n}\n","/**\n * Public `vault.custody.*` API surface (FR-6).\n *\n * The custody namespace is the vault-instance face of the FR-6 sovereign-custody\n * model — it mirrors `vault.user.*` exactly: a thin delegation shell with NO\n * business logic. The Vault constructs one `CustodyApi` per session, injecting\n * closures that bind the vault name / keyring into the genuinely-core\n * implementations (`Noydb.grantCustodian` / `Noydb.revokeCustodian` and the\n * `liberateVault` ceremony). Each method just forwards to its injected callback.\n *\n * Three operations:\n * - `grantCustodian(opts)` — owner-only: mint a `custodian` who operates the\n * vault fully but can never grant / rotate / sever / extract.\n * - `revokeCustodian(opts)` — owner-only: remove a custodian.\n * - `liberate(opts)` — custodian-only: audited claim of ownership over a\n * sealed-owner (Deed) vault (mints a DISTINCT new owner; ledger-audited).\n *\n * Provisioning a Deed (`createDeedOwner`) is deliberately NOT on this class: it\n * is a store-level operation that mints the vault's first owner, so there is no\n * vault instance (and thus no custody namespace) yet — it stays the exported\n * `team/deed.ts` function.\n *\n * @see design-history/2026-06-17-fr6-deed-custodian-liberate-design.md\n * @module\n */\nimport type { GrantOptions, RevokeOptions, FactorProofBundle } from '../../kernel/types.js'\nimport type { LiberateOptions, LiberateResult } from './liberate.js'\n\n/** Options for `vault.custody.grantCustodian` — a grant with the role fixed to `custodian`. */\nexport type GrantCustodianOptions = Omit<GrantOptions, 'role'>\n\n// Capability opt-in seam (S4): grant/revoke custodian + liberate throw\n// CustodyNotEnabledError unless `custodyStrategy: withCustody()` is opted in.\nexport { withCustody } from './active.js'\nexport { NO_CUSTODY, type CustodyStrategy, type CustodyHost } from './strategy.js'\nexport { CustodyNotEnabledError } from '../../kernel/errors.js'\n\n/**\n * Implementation behind `vault.custody`. Constructed once per Vault. Holds the\n * injected, vault-bound implementations in closure; every method delegates with\n * no added logic (the owner-only / custodian-only / gate checks all live in the\n * injected implementations — `Noydb.grantCustodian` etc. and `liberateVault`).\n */\nexport class CustodyApi {\n constructor(\n /** Bound `Noydb.grantCustodian(this.name, ...)` — owner-only, gated. */\n private readonly _grantCustodian: (options: GrantCustodianOptions, factors?: FactorProofBundle) => Promise<void>,\n /** Bound `Noydb.revokeCustodian(this.name, ...)` — owner-only, gated. */\n private readonly _revokeCustodian: (options: RevokeOptions, factors?: FactorProofBundle) => Promise<void>,\n /** Bound `liberateVault(this, ...)` — custodian-only audited ownership claim. */\n private readonly _liberate: (opts: LiberateOptions) => Promise<LiberateResult>,\n ) {}\n\n /**\n * Owner-only: grant the FR-6 `custodian` role. The custodian operates every\n * collection (rw + access) but is provably unable to grant / revoke / rotate /\n * extract-and-sever. Defended in depth (gate + owner-only role check) inside\n * the injected `Noydb.grantCustodian`.\n */\n async grantCustodian(options: GrantCustodianOptions, factors?: FactorProofBundle): Promise<void> {\n return this._grantCustodian(options, factors)\n }\n\n /** Owner-only: revoke a custodian. */\n async revokeCustodian(options: RevokeOptions, factors?: FactorProofBundle): Promise<void> {\n return this._revokeCustodian(options, factors)\n }\n\n /**\n * Custodian-only: the audited claim of ownership over a sealed-owner (Deed)\n * vault. Mints a DISTINCT new owner re-wrapping the incumbent DEKs under a\n * fresh KEK (the latent owner is never impersonated), ledger-audited. See\n * {@link liberateVault}.\n */\n async liberate(opts: LiberateOptions): Promise<LiberateResult> {\n return this._liberate(opts)\n }\n}\n"],"mappings":";;;;;;;;;AAWO,SAAS,cAA+B;AAC7C,SAAO;AAAA,IACL,MAAM,eAAe,MAAM,OAAO,SAAS,SAAS;AAClD,aAAO,KAAK,oBAAoB,OAAa,OAAO,SAAS,OAAO;AAAA,IACtE;AAAA,IACA,MAAM,gBAAgB,MAAM,OAAO,SAAS,SAAS;AACnD,aAAO,KAAK,qBAAqB,QAAc,OAAO,SAAS,OAAO;AAAA,IACxE;AAAA,IACA,MAAM,SAAS,OAAO,MAAM;AAC1B,YAAM,EAAE,cAAc,IAAI,MAAM,OAAO,wBAAe;AACtD,aAAO,cAAc,OAAO,IAAI;AAAA,IAClC;AAAA,EACF;AACF;;;ACqCO,IAAM,aAA8B;AAAA,EACzC,MAAM,iBAAiB;AAAE,UAAM,IAAI,uBAAuB;AAAA,EAAE;AAAA,EAC5D,MAAM,kBAAkB;AAAE,UAAM,IAAI,uBAAuB;AAAA,EAAE;AAAA,EAC7D,MAAM,WAAW;AAAE,UAAM,IAAI,uBAAuB;AAAA,EAAE;AACxD;;;ACtBO,IAAM,aAAN,MAAiB;AAAA,EACtB,YAEmB,iBAEA,kBAEA,WACjB;AALiB;AAEA;AAEA;AAAA,EAChB;AAAA,EALgB;AAAA,EAEA;AAAA,EAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASnB,MAAM,eAAe,SAAgC,SAA4C;AAC/F,WAAO,KAAK,gBAAgB,SAAS,OAAO;AAAA,EAC9C;AAAA;AAAA,EAGA,MAAM,gBAAgB,SAAwB,SAA4C;AACxF,WAAO,KAAK,iBAAiB,SAAS,OAAO;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,SAAS,MAAgD;AAC7D,WAAO,KAAK,UAAU,IAAI;AAAA,EAC5B;AACF;","names":[]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
applyJoins
|
|
3
|
-
|
|
2
|
+
applyJoins,
|
|
3
|
+
splitAroundJoins
|
|
4
|
+
} from "./chunk-H4YQSDOI.js";
|
|
4
5
|
import {
|
|
5
6
|
NO_REDUCE
|
|
6
7
|
} from "./chunk-YMUFFJCO.js";
|
|
@@ -99,6 +100,17 @@ var EMPTY_PLAN = {
|
|
|
99
100
|
joins: []
|
|
100
101
|
};
|
|
101
102
|
var DEFAULT_CROSS_JOIN_MAX_ROWS = 5e4;
|
|
103
|
+
function assertNoJoinAliasField(fields, joins, terminal) {
|
|
104
|
+
if (joins.length === 0) return;
|
|
105
|
+
const aliases = new Set(joins.map((leg) => leg.as));
|
|
106
|
+
for (const field of fields) {
|
|
107
|
+
const head = field.split(".")[0];
|
|
108
|
+
if (!aliases.has(head)) continue;
|
|
109
|
+
throw new Error(
|
|
110
|
+
`Query.${terminal}(): field "${field}" addresses the join alias "${head}", but ${terminal}() does not apply join legs \u2014 it would silently group every row under undefined. Joined aggregation is not supported. Either aggregate over the left collection's own fields, or use .crossJoin("${head}", { as: \u2026 }), whose expansion IS visible to ${terminal}().`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
102
114
|
var Query = class _Query {
|
|
103
115
|
source;
|
|
104
116
|
plan;
|
|
@@ -532,14 +544,37 @@ var Query = class _Query {
|
|
|
532
544
|
* (Left/base i18n fields are resolved by `get`/`list`, not here.)
|
|
533
545
|
*/
|
|
534
546
|
toArray(opts) {
|
|
535
|
-
const
|
|
536
|
-
if (
|
|
547
|
+
const { preJoin, postJoin } = splitAroundJoins(this.plan.clauses, this.plan.joins);
|
|
548
|
+
if (postJoin.length === 0) {
|
|
549
|
+
const base = this.decodeVia(executePlanWithSource(this.source, this.plan, this.joinContext, opts?.locale));
|
|
550
|
+
if (this.plan.joins.length === 0) return base;
|
|
551
|
+
return applyJoins(base, this.plan.joins, this.requireJoinContext("toArray"), opts?.locale);
|
|
552
|
+
}
|
|
553
|
+
const joinContext = this.requireJoinContext("toArray");
|
|
554
|
+
const narrowed = this.decodeVia(
|
|
555
|
+
executePlanWithSource(
|
|
556
|
+
this.source,
|
|
557
|
+
{ ...this.plan, clauses: preJoin, orderBy: [], limit: void 0, offset: 0 },
|
|
558
|
+
joinContext,
|
|
559
|
+
opts?.locale
|
|
560
|
+
)
|
|
561
|
+
);
|
|
562
|
+
const joined = applyJoins(narrowed, this.plan.joins, joinContext, opts?.locale);
|
|
563
|
+
const filtered = filterRecords(joined, postJoin, fnViewDecoder(this.source));
|
|
564
|
+
return applyOrderAndPage(filtered, this.plan, this.source, joinContext, opts?.locale);
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Joins need a `JoinContext`. Unreachable in practice — `.join()` throws
|
|
568
|
+
* when one is missing — but belt-and-braces for a plan built through the
|
|
569
|
+
* raw `Query` constructor with joins pre-populated.
|
|
570
|
+
*/
|
|
571
|
+
requireJoinContext(terminal) {
|
|
537
572
|
if (!this.joinContext) {
|
|
538
573
|
throw new Error(
|
|
539
|
-
`Query
|
|
574
|
+
`Query.${terminal}(): plan carries ${this.plan.joins.length} join leg(s) but no JoinContext is attached. This usually means the Query was constructed via the raw Query constructor with a plan that had joins pre-populated. Use collection.query().join(...) instead.`
|
|
540
575
|
);
|
|
541
576
|
}
|
|
542
|
-
return
|
|
577
|
+
return this.joinContext;
|
|
543
578
|
}
|
|
544
579
|
/**
|
|
545
580
|
* Decode this source's Via-covered fields on read (e.g. money: stored
|
|
@@ -567,12 +602,19 @@ var Query = class _Query {
|
|
|
567
602
|
}
|
|
568
603
|
/**
|
|
569
604
|
* Return the number of matching records (after where/filter,
|
|
570
|
-
* before limit). **Joins are NOT applied** — count() reports the
|
|
571
|
-
* left-side cardinality, because joins
|
|
605
|
+
* before limit). **Joins are normally NOT applied** — count() reports the
|
|
606
|
+
* left-side cardinality, because joins are projection-only
|
|
572
607
|
* (they attach an aliased field; they never filter). Running joins
|
|
573
608
|
* here just to discard the aliases would be wasteful, and in strict
|
|
574
609
|
* mode it could throw `DanglingReferenceError` for a call whose
|
|
575
610
|
* intent is purely to count.
|
|
611
|
+
*
|
|
612
|
+
* The exception (#1030) is a `where` clause addressing a join alias. Then
|
|
613
|
+
* the legs must run, because the predicate is part of what is being
|
|
614
|
+
* counted — skipping them would report the unfiltered left cardinality.
|
|
615
|
+
* A strict-mode `DanglingReferenceError` becomes reachable from `count()`
|
|
616
|
+
* in exactly that case, which is acceptable: the caller asked to filter on
|
|
617
|
+
* the joined side, so they asked for the join.
|
|
576
618
|
*/
|
|
577
619
|
count() {
|
|
578
620
|
if (this.plan.clauses.some((c) => c.type === "crossJoin")) {
|
|
@@ -583,12 +625,20 @@ var Query = class _Query {
|
|
|
583
625
|
}
|
|
584
626
|
return executeClausePipeline(this.source, this.plan.clauses, this.joinContext).length;
|
|
585
627
|
}
|
|
586
|
-
const {
|
|
587
|
-
|
|
588
|
-
|
|
628
|
+
const { preJoin, postJoin } = splitAroundJoins(this.plan.clauses, this.plan.joins);
|
|
629
|
+
const { candidates, remainingClauses } = candidateRecords(this.source, preJoin);
|
|
630
|
+
const narrowed = remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses, fnViewDecoder(this.source));
|
|
631
|
+
if (postJoin.length === 0) return narrowed.length;
|
|
632
|
+
const joined = applyJoins(narrowed, this.plan.joins, this.requireJoinContext("count"));
|
|
633
|
+
return filterRecords(joined, postJoin, fnViewDecoder(this.source)).length;
|
|
589
634
|
}
|
|
590
635
|
aggregate(specOrBuild) {
|
|
591
636
|
let spec = typeof specOrBuild === "function" ? specOrBuild(reducerBuilder) : specOrBuild;
|
|
637
|
+
assertNoJoinAliasField(
|
|
638
|
+
Object.values(spec).map((r) => r.field).filter((f) => typeof f === "string"),
|
|
639
|
+
this.plan.joins,
|
|
640
|
+
"aggregate"
|
|
641
|
+
);
|
|
592
642
|
spec = this.source.via?.wrapReducers(spec) ?? spec;
|
|
593
643
|
const source = this.source;
|
|
594
644
|
const clauses = this.plan.clauses;
|
|
@@ -613,6 +663,7 @@ var Query = class _Query {
|
|
|
613
663
|
if (fields.length === 0) {
|
|
614
664
|
throw new Error(".groupBy() requires at least one field");
|
|
615
665
|
}
|
|
666
|
+
assertNoJoinAliasField(fields, this.plan.joins, "groupBy");
|
|
616
667
|
const source = this.source;
|
|
617
668
|
const clauses = this.plan.clauses;
|
|
618
669
|
const joinCtx = this.joinContext;
|
|
@@ -777,6 +828,10 @@ function executePlanWithSource(source, plan, joinContext, locale) {
|
|
|
777
828
|
const { candidates, remainingClauses } = candidateRecords(source, plan.clauses);
|
|
778
829
|
result = remainingClauses.length === 0 ? [...candidates] : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
|
|
779
830
|
}
|
|
831
|
+
return applyOrderAndPage(result, plan, source, joinContext, locale);
|
|
832
|
+
}
|
|
833
|
+
function applyOrderAndPage(rows, plan, source, joinContext, locale) {
|
|
834
|
+
let result = rows;
|
|
780
835
|
if (plan.orderBy.length > 0) {
|
|
781
836
|
const labelMaps = buildOrderLabelMaps(plan.orderBy, joinContext, locale, source.via);
|
|
782
837
|
result = sortRecords(result, plan.orderBy, source.via, labelMaps);
|
|
@@ -1336,10 +1391,11 @@ var ScanBuilder = class _ScanBuilder {
|
|
|
1336
1391
|
*/
|
|
1337
1392
|
async *[Symbol.asyncIterator]() {
|
|
1338
1393
|
const joinResolvers = this.joins.length === 0 ? null : this.buildJoinResolvers();
|
|
1394
|
+
const { preJoin, postJoin } = splitAroundJoins(this.clauses, this.joins);
|
|
1339
1395
|
let page = await this.pageProvider.listPage({ limit: this.pageSize });
|
|
1340
1396
|
while (true) {
|
|
1341
1397
|
for (const record of page.items) {
|
|
1342
|
-
if (!this.recordMatches(record)) continue;
|
|
1398
|
+
if (!this.recordMatches(record, preJoin)) continue;
|
|
1343
1399
|
const decoded = this.decodeVia(record);
|
|
1344
1400
|
if (joinResolvers === null) {
|
|
1345
1401
|
yield decoded;
|
|
@@ -1348,6 +1404,7 @@ var ScanBuilder = class _ScanBuilder {
|
|
|
1348
1404
|
for (const resolver of joinResolvers) {
|
|
1349
1405
|
attached = this.applyOneJoinStreaming(attached, resolver);
|
|
1350
1406
|
}
|
|
1407
|
+
if (postJoin.length > 0 && !this.recordMatches(attached, postJoin)) continue;
|
|
1351
1408
|
yield attached;
|
|
1352
1409
|
}
|
|
1353
1410
|
}
|
|
@@ -1494,10 +1551,10 @@ var ScanBuilder = class _ScanBuilder {
|
|
|
1494
1551
|
* path, because the stream sources records from the adapter
|
|
1495
1552
|
* paginator, not from the in-memory cache where indexes live.
|
|
1496
1553
|
*/
|
|
1497
|
-
recordMatches(record) {
|
|
1498
|
-
if (
|
|
1499
|
-
const fnView = this.via?.hasResultDecode && hasFnClause(
|
|
1500
|
-
for (const clause of
|
|
1554
|
+
recordMatches(record, clauses = this.clauses) {
|
|
1555
|
+
if (clauses.length === 0) return true;
|
|
1556
|
+
const fnView = this.via?.hasResultDecode && hasFnClause(clauses) ? this.decodeVia(record) : void 0;
|
|
1557
|
+
for (const clause of clauses) {
|
|
1501
1558
|
if (!evaluateClause(record, clause, fnView)) return false;
|
|
1502
1559
|
}
|
|
1503
1560
|
return true;
|
|
@@ -1517,4 +1574,4 @@ export {
|
|
|
1517
1574
|
executePlan,
|
|
1518
1575
|
ScanBuilder
|
|
1519
1576
|
};
|
|
1520
|
-
//# sourceMappingURL=chunk-
|
|
1577
|
+
//# sourceMappingURL=chunk-BGQ63MCT.js.map
|