@noy-db/hub 0.3.0-pre.7 → 0.3.0-pre.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/index.d.ts +2 -3
- package/dist/adapter/index.js +1 -1
- package/dist/aggregate/index.d.ts +3 -4
- package/dist/aggregate/index.js +9 -8
- package/dist/aggregate/index.js.map +1 -1
- package/dist/api-HV4PDUWF.js +20 -0
- package/dist/as/index.d.ts +4 -5
- package/dist/as/index.js +11 -11
- package/dist/at/index.d.ts +2 -3
- package/dist/attestation/index.d.ts +3 -4
- package/dist/attestation/index.js +18 -18
- package/dist/{backup-7PX6NDKT.js → backup-ODXO7OGA.js} +12 -12
- package/dist/blobs/index.d.ts +4 -5
- package/dist/blobs/index.js +11 -11
- package/dist/blobs/index.js.map +1 -1
- package/dist/bundle/index.d.ts +5 -6
- package/dist/bundle/index.js +20 -20
- package/dist/{bundle-BN_VSq5L.d.ts → bundle-DlSxo31R.d.ts} +1 -1
- package/dist/by/index.js +2 -2
- package/dist/cargo/index.d.ts +4 -5
- package/dist/cargo/index.js +23 -22
- package/dist/{chunk-QYNBPDGO.js → chunk-2644KEGW.js} +18 -10
- package/dist/chunk-2644KEGW.js.map +1 -0
- package/dist/{chunk-XW5LZYV4.js → chunk-2HVUKRNE.js} +2 -2
- package/dist/{chunk-VFQOE76I.js → chunk-2KOSJTMU.js} +2 -2
- package/dist/{chunk-7AERMJP6.js → chunk-2UQ4RMHV.js} +4 -4
- package/dist/{chunk-5GQE5IDB.js → chunk-2XE6LIXT.js} +13 -11
- package/dist/chunk-2XE6LIXT.js.map +1 -0
- package/dist/{chunk-VIWOGJ2O.js → chunk-34RRRIC3.js} +2 -2
- package/dist/{chunk-BASCPHNM.js → chunk-3C5CKXFI.js} +289 -48
- package/dist/chunk-3C5CKXFI.js.map +1 -0
- package/dist/{chunk-LFGJG4XZ.js → chunk-3I4KYWNP.js} +2 -2
- package/dist/{chunk-RUWEHFRB.js → chunk-42KVY3OC.js} +4 -4
- package/dist/{chunk-5K7KZMXG.js → chunk-4C4WBS4V.js} +2048 -948
- package/dist/chunk-4C4WBS4V.js.map +1 -0
- package/dist/{chunk-J54DC5IC.js → chunk-4G3IT7J7.js} +7 -7
- package/dist/{chunk-WJEMUO32.js → chunk-4RQ62VSR.js} +2 -2
- package/dist/{chunk-QAVFI23O.js → chunk-4ZSGSRBI.js} +8 -8
- package/dist/{chunk-C4NOLA5X.js → chunk-5GAX2QOH.js} +2 -2
- package/dist/{chunk-IEKK2XI4.js → chunk-5SAZMBW7.js} +7 -7
- package/dist/{chunk-II6DLDZM.js → chunk-5TZZUL5X.js} +2 -2
- package/dist/{chunk-GMZFHDSG.js → chunk-6CU3FN74.js} +8 -8
- package/dist/chunk-6G2SCQYS.js +29 -0
- package/dist/chunk-6G2SCQYS.js.map +1 -0
- package/dist/{chunk-UWZ7O4NR.js → chunk-6MWUANRF.js} +2 -2
- package/dist/{chunk-U7LV2DUN.js → chunk-6ULCYCMU.js} +2 -2
- package/dist/{chunk-6Z435B6N.js → chunk-7HAPHVRJ.js} +131 -92
- package/dist/chunk-7HAPHVRJ.js.map +1 -0
- package/dist/{chunk-VFG5YXWM.js → chunk-7IZQTCTR.js} +3 -3
- package/dist/chunk-7ZANJ6JZ.js +265 -0
- package/dist/chunk-7ZANJ6JZ.js.map +1 -0
- package/dist/{chunk-VMRXI3IF.js → chunk-A2APWS32.js} +6 -6
- package/dist/chunk-A2APWS32.js.map +1 -0
- package/dist/{chunk-U5IJHUHZ.js → chunk-A4MAD6PO.js} +5 -5
- package/dist/chunk-ANDBBA2P.js +36 -0
- package/dist/chunk-ANDBBA2P.js.map +1 -0
- package/dist/{chunk-5HZULLHE.js → chunk-AUR7MRHA.js} +2 -2
- package/dist/{chunk-YSJG4L4A.js → chunk-AYGZMQP5.js} +3 -3
- package/dist/chunk-AYGZMQP5.js.map +1 -0
- package/dist/{chunk-H7WQMGXH.js → chunk-CJEMA2Z6.js} +2 -2
- package/dist/{chunk-WRLPV6SI.js → chunk-CTSFKDGA.js} +2 -2
- package/dist/{chunk-OJ64XMPJ.js → chunk-CU62BBRB.js} +32 -1
- package/dist/chunk-CU62BBRB.js.map +1 -0
- package/dist/{chunk-OFYY5Q4R.js → chunk-CWCCYH34.js} +2 -2
- package/dist/{chunk-WGH3YV6Y.js → chunk-DEYAO3N6.js} +10 -10
- package/dist/{chunk-7L6EH6S7.js → chunk-DFIBOSOQ.js} +8 -8
- package/dist/{chunk-3CHNKEFI.js → chunk-E5AHO65E.js} +7 -7
- package/dist/{chunk-AG2PLGQ2.js → chunk-FC3XK5JT.js} +8 -8
- package/dist/{chunk-CP3GUIAN.js → chunk-FGOGS7CE.js} +10 -12
- package/dist/chunk-FGOGS7CE.js.map +1 -0
- package/dist/{chunk-DJ4TNF4N.js → chunk-GX2BYNCT.js} +7 -7
- package/dist/{chunk-YTCCC6NG.js → chunk-GZ2MOPVX.js} +34 -63
- package/dist/chunk-GZ2MOPVX.js.map +1 -0
- package/dist/chunk-H57P2SJB.js +126 -0
- package/dist/chunk-H57P2SJB.js.map +1 -0
- package/dist/{chunk-55LSDABW.js → chunk-HBTT7YQX.js} +2 -2
- package/dist/{chunk-PYIUDUGS.js → chunk-HOCEGVGQ.js} +2 -2
- package/dist/{chunk-5JQMX2EH.js → chunk-ICV32FPK.js} +2 -2
- package/dist/{chunk-WFBZRXS5.js → chunk-IIP5WFVT.js} +138 -92
- package/dist/chunk-IIP5WFVT.js.map +1 -0
- package/dist/{chunk-YSU2E4GZ.js → chunk-IUKGIB2Q.js} +19 -6
- package/dist/chunk-IUKGIB2Q.js.map +1 -0
- package/dist/{chunk-OWGAXPRE.js → chunk-IWTYFS3D.js} +2 -2
- package/dist/{chunk-FCOZ7DEC.js → chunk-JC5R7P44.js} +3 -3
- package/dist/{chunk-K6WHZBCO.js → chunk-KBBWY46Y.js} +2 -2
- package/dist/{chunk-RNYDHIQL.js → chunk-KBF5GSGX.js} +3 -3
- package/dist/{chunk-6P5AJ2ZS.js → chunk-KCG3BHFG.js} +2 -23
- package/dist/chunk-KCG3BHFG.js.map +1 -0
- package/dist/{chunk-T4NQMPBN.js → chunk-KNXN47MO.js} +3 -3
- package/dist/{chunk-VK6OC22R.js → chunk-KWLD2D75.js} +4 -3
- package/dist/chunk-KWLD2D75.js.map +1 -0
- package/dist/{chunk-7F2P2NHX.js → chunk-KXKLKJW4.js} +6 -6
- package/dist/{chunk-PAJCRV7O.js → chunk-KYC4LIEU.js} +2 -2
- package/dist/{chunk-AZPGB4OD.js → chunk-LEOXZY36.js} +8 -8
- package/dist/{chunk-2L7PPF2C.js → chunk-M5I354EE.js} +9 -9
- package/dist/{chunk-26FSVNBL.js → chunk-MK2BSS44.js} +2 -2
- package/dist/{chunk-G5CFMITA.js → chunk-MSVY7ULZ.js} +3 -3
- package/dist/{chunk-PE25C4QL.js → chunk-NNWK3PSY.js} +9 -9
- package/dist/{chunk-JT4A6OAJ.js → chunk-NSZ6J5DF.js} +7 -7
- package/dist/{chunk-RHB7BOEO.js → chunk-NUEFXLZU.js} +7 -7
- package/dist/{chunk-I6Q7YECW.js → chunk-NYVQCZ54.js} +5 -5
- package/dist/chunk-NYVQCZ54.js.map +1 -0
- package/dist/{chunk-DKGZIX6S.js → chunk-OQAODTYR.js} +2 -2
- package/dist/{chunk-HV3VNAYI.js → chunk-OV4CMJGR.js} +5 -5
- package/dist/{chunk-V76PHWLE.js → chunk-PEDZFFKW.js} +2 -2
- package/dist/chunk-PF3FQCCF.js +164 -0
- package/dist/chunk-PF3FQCCF.js.map +1 -0
- package/dist/{chunk-CND5WQLO.js → chunk-Q2KVKTC2.js} +5 -5
- package/dist/{chunk-RUIMKQTA.js → chunk-QJR3W3MF.js} +1 -1
- package/dist/chunk-QJR3W3MF.js.map +1 -0
- package/dist/{chunk-HBVEGUON.js → chunk-QTXQ6RNZ.js} +3 -3
- package/dist/{chunk-J5YDMJHG.js → chunk-RJ5522XX.js} +12 -12
- package/dist/{chunk-H3MPSYCH.js → chunk-RMUXPHIH.js} +56 -7
- package/dist/chunk-RMUXPHIH.js.map +1 -0
- package/dist/{chunk-CHK5VVMI.js → chunk-S6AQXDN4.js} +2 -2
- package/dist/{chunk-E3LURVGQ.js → chunk-SSFC3IYA.js} +9 -9
- package/dist/chunk-SSFC3IYA.js.map +1 -0
- package/dist/{chunk-MOQ5M46U.js → chunk-SZCHEDML.js} +2 -2
- package/dist/{chunk-UB3XHGFG.js → chunk-TDPEJINP.js} +14 -7
- package/dist/chunk-TDPEJINP.js.map +1 -0
- package/dist/{chunk-HCK2DFEK.js → chunk-UNJU345Q.js} +1 -1
- package/dist/chunk-UNJU345Q.js.map +1 -0
- package/dist/chunk-V7RNIND6.js +265 -0
- package/dist/chunk-V7RNIND6.js.map +1 -0
- package/dist/{chunk-VUHXLL32.js → chunk-VJ3NQK4K.js} +5 -5
- package/dist/{chunk-62TTFKU4.js → chunk-WNA6T3MA.js} +8 -8
- package/dist/{chunk-7CZ6YXMO.js → chunk-WVCQ75OK.js} +2 -2
- package/dist/{chunk-NO53W6O5.js → chunk-X66MXMR5.js} +2 -2
- package/dist/{chunk-JIW3Q5JN.js → chunk-XHD3L3JO.js} +199 -35
- package/dist/chunk-XHD3L3JO.js.map +1 -0
- package/dist/{chunk-6PALJTC2.js → chunk-XJK2WIYN.js} +3 -3
- package/dist/{chunk-5UOPIPZC.js → chunk-XQ7UYPVN.js} +3 -3
- package/dist/{chunk-HU55NPHZ.js → chunk-Y2OCKFZ3.js} +2 -2
- package/dist/{chunk-UJJJPKJO.js → chunk-YBFOBRTO.js} +3 -3
- package/dist/{chunk-I6JJ6VJS.js → chunk-YLN33ADH.js} +2 -2
- package/dist/{chunk-HGQ6UNHE.js → chunk-Z5YFRMG5.js} +2 -2
- package/dist/{chunk-EU7HDMT3.js → chunk-ZWQFOXBR.js} +2 -2
- package/dist/classified/index.d.ts +4 -5
- package/dist/classified/index.js +5 -3
- package/dist/{config-drift-LRKCC3VZ.js → classified-marker-IO5EBW7Q.js} +4 -4
- package/dist/classified-marker-IO5EBW7Q.js.map +1 -0
- package/dist/collection-facade-3RAXG2OU.js +38 -0
- package/dist/{computed-6FOFXQPE.js → computed-R62HGCO4.js} +3 -3
- package/dist/consent/index.d.ts +3 -4
- package/dist/consent/index.js +9 -9
- package/dist/crdt/index.d.ts +3 -4
- package/dist/{dead-filter-NZORV45S.js → dead-filter-VNA4M3GP.js} +2 -2
- package/dist/delegation-ZKII5UBI.js +25 -0
- package/dist/derivations/index.d.ts +4 -5
- package/dist/derivations/index.js +12 -11
- package/dist/derive-5D2C3WG4.js +21 -0
- package/dist/describe/index.d.ts +4 -1
- package/dist/{dev-unlock-CsOqX7O2.d.ts → dev-unlock-DDTafVO6.d.ts} +1 -1
- package/dist/{enclave-7X5BUAMS.js → enclave-EUBEJGPG.js} +11 -9
- package/dist/executor-IL7SRNUZ.js +23 -0
- package/dist/executor-OUA2Y6YC.js +9 -0
- package/dist/executor-WAEI6PDV.js +9 -0
- package/dist/export-accessible-O55RKAGC.js +23 -0
- package/dist/extract-partition-VBGCBA6Q.js +36 -0
- package/dist/{fanout-sidecar-BDKLP5GG.js → fanout-sidecar-CI5BCMQM.js} +11 -11
- package/dist/find-FISGUWSK.js +11 -0
- package/dist/forget/index.d.ts +2 -2
- package/dist/forget/index.js +9 -9
- package/dist/guards/index.d.ts +4 -5
- package/dist/guards/index.js +4 -4
- package/dist/{hash-BPzm02Za.d.ts → hash-DBkZfIBW.d.ts} +1 -1
- package/dist/history/index.d.ts +4 -5
- package/dist/history/index.js +11 -11
- package/dist/i18n/index.d.ts +24 -5
- package/dist/i18n/index.js +45 -34
- package/dist/i18n/index.js.map +1 -1
- package/dist/in/index.d.ts +2 -3
- package/dist/{index-CLucFLeN.d.ts → index-CyclmyDr.d.ts} +2 -3
- package/dist/{index-BJ3L-XcV.d.ts → index-DzhNI2vE.d.ts} +6378 -5022
- package/dist/index.d.ts +64 -23
- package/dist/index.js +411 -132
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.d.ts +3 -4
- package/dist/indexing/index.js +4 -4
- package/dist/issue-EQAWIYFQ.js +19 -0
- package/dist/kernel/index.d.ts +3 -4
- package/dist/kernel/index.js +13 -12
- package/dist/lazy/index.d.ts +3 -4
- package/dist/{ledger-EMDHRJJN.js → ledger-AJA2SR3C.js} +11 -11
- package/dist/liberate-SEGR4JLY.js +24 -0
- package/dist/{link-set-62Y36TOG.js → link-set-YBAC2DQT.js} +11 -11
- package/dist/materialized-views/index.d.ts +14 -5
- package/dist/materialized-views/index.js +17 -15
- package/dist/{mime-magic-BlE9fzn2.d.ts → mime-magic-CbV1quyp.d.ts} +1 -1
- package/dist/noydb-AMEOU456.js +65 -0
- package/dist/on/index.d.ts +2 -3
- package/dist/on/index.js +9 -9
- package/dist/overlay-views/index.d.ts +4 -5
- package/dist/overlay-views/index.js +4 -4
- package/dist/periods/index.d.ts +3 -4
- package/dist/periods/index.js +20 -12
- package/dist/pod/index.d.ts +3 -4
- package/dist/pod/index.js +11 -11
- package/dist/{policy-SXPLTQS4.js → policy-SDGRWVIL.js} +5 -5
- package/dist/portability/index.d.ts +3 -4
- package/dist/portability/index.js +8 -8
- package/dist/{post-register-DCJ2SRVG.js → post-register-MROIUA5O.js} +6 -6
- package/dist/{public-envelope-CBRIJORQ.js → public-envelope-J5PLQOW2.js} +4 -4
- package/dist/query/index.d.ts +2 -3
- package/dist/query/index.js +9 -6
- package/dist/{read-only-facade-5ADRJVTM.js → read-only-facade-UCYNRE56.js} +2 -2
- package/dist/register-OVJKW7BS.js +23 -0
- package/dist/registry-5RJSK5AJ.js +9 -0
- package/dist/registry-DQRNTWRL.js +18 -0
- package/dist/registry-LTGLERS7.js +19 -0
- package/dist/request-withdrawal-27O77D2V.js +31 -0
- package/dist/{reveal-R5N7JKA5.js → reveal-LT3I2K4P.js} +6 -6
- package/dist/revoke-4N45G7D2.js +24 -0
- package/dist/satellites/index.d.ts +2 -3
- package/dist/satellites/index.js +1 -1
- package/dist/sealed-record/index.d.ts +3 -4
- package/dist/sealed-record/index.js +12 -12
- package/dist/session/index.d.ts +4 -5
- package/dist/session/index.js +9 -9
- package/dist/shadow/index.d.ts +3 -4
- package/dist/shadow/index.js +2 -2
- package/dist/signer-XJCOV7JD.js +25 -0
- package/dist/snapshots/index.d.ts +3 -4
- package/dist/snapshots/index.js +10 -10
- package/dist/{stale-DP66HFT3.js → stale-IYYXEH3U.js} +2 -2
- package/dist/storage-Y44CAPE6.js +23 -0
- package/dist/{store-coordination-provider-F5N5ASDF.js → store-coordination-provider-WKGAE2WI.js} +3 -3
- package/dist/{subject-index-O75wKshW.d.ts → subject-index-CyOgGMJL.d.ts} +18 -0
- package/dist/sync/index.d.ts +2 -3
- package/dist/sync/index.js +9 -9
- package/dist/team/index.d.ts +3 -4
- package/dist/team/index.js +15 -15
- package/dist/tiers/index.d.ts +2 -3
- package/dist/tiers/index.js +10 -10
- package/dist/to/index.d.ts +2 -3
- package/dist/to/index.js +1 -1
- package/dist/{transition-guard-BzsAQEd-.d.ts → transition-guard-BOUkzQU4.d.ts} +1 -1
- package/dist/tx/index.d.ts +3 -4
- package/dist/tx/index.js +3 -3
- package/dist/ui/index.d.ts +4 -1
- package/dist/util/index.js +1 -1
- package/dist/{validators-DGmYFP51.d.ts → validators-B3SalxlJ.d.ts} +1 -1
- package/dist/{vault-diff-DmBBatkx.d.ts → vault-diff-Dt9NkLzw.d.ts} +1 -1
- package/dist/{verify-CAYGXYNO.js → verify-GH6CWVSW.js} +5 -5
- package/dist/{walk-3SB25HF2.js → walk-76UCFXYV.js} +11 -11
- package/dist/with/index.d.ts +3 -4
- package/dist/with/index.js +11 -11
- package/dist/{with-materialized-view-DPEVoGoB.d.ts → with-materialized-view-BP33v1Z6.d.ts} +1 -1
- package/dist/{with-overlayed-view-DGpKFSv1.d.ts → with-overlayed-view-DntFpOKN.d.ts} +1 -1
- package/dist/{with-rollup-B2RwnTq0.d.ts → with-rollup-ClCHO6-4.d.ts} +1 -1
- package/dist/withdraw-accessible-6J2UH3IN.js +28 -0
- package/package.json +3 -3
- package/dist/api-Z66JMGOU.js +0 -20
- package/dist/chunk-47C4CI7R.js +0 -108
- package/dist/chunk-47C4CI7R.js.map +0 -1
- package/dist/chunk-5GQE5IDB.js.map +0 -1
- package/dist/chunk-5K7KZMXG.js.map +0 -1
- package/dist/chunk-6P5AJ2ZS.js.map +0 -1
- package/dist/chunk-6Z435B6N.js.map +0 -1
- package/dist/chunk-7KTL4RHZ.js +0 -120
- package/dist/chunk-7KTL4RHZ.js.map +0 -1
- package/dist/chunk-BASCPHNM.js.map +0 -1
- package/dist/chunk-CP3GUIAN.js.map +0 -1
- package/dist/chunk-E3LURVGQ.js.map +0 -1
- package/dist/chunk-H3MPSYCH.js.map +0 -1
- package/dist/chunk-HCK2DFEK.js.map +0 -1
- package/dist/chunk-I6Q7YECW.js.map +0 -1
- package/dist/chunk-JIW3Q5JN.js.map +0 -1
- package/dist/chunk-OJ64XMPJ.js.map +0 -1
- package/dist/chunk-QYNBPDGO.js.map +0 -1
- package/dist/chunk-RUIMKQTA.js.map +0 -1
- package/dist/chunk-UB3XHGFG.js.map +0 -1
- package/dist/chunk-VK6OC22R.js.map +0 -1
- package/dist/chunk-VMRXI3IF.js.map +0 -1
- package/dist/chunk-WFBZRXS5.js.map +0 -1
- package/dist/chunk-YSJG4L4A.js.map +0 -1
- package/dist/chunk-YSU2E4GZ.js.map +0 -1
- package/dist/chunk-YTCCC6NG.js.map +0 -1
- package/dist/collection-facade-JP6N7USE.js +0 -39
- package/dist/config-drift-LRKCC3VZ.js.map +0 -1
- package/dist/delegation-FDOXXHNG.js +0 -25
- package/dist/derive-JR2C5JFG.js +0 -21
- package/dist/describe-C6iHNlqJ.d.ts +0 -162
- package/dist/executor-6LHOV7MN.js +0 -21
- package/dist/executor-EUPYHCLU.js +0 -9
- package/dist/executor-KZ4B3PRT.js +0 -9
- package/dist/export-accessible-7XLSZBGJ.js +0 -23
- package/dist/extract-partition-RM6EROS5.js +0 -36
- package/dist/find-OH4BEULF.js +0 -11
- package/dist/issue-FJE2IHUI.js +0 -19
- package/dist/liberate-HFIPHJGR.js +0 -24
- package/dist/noydb-4ZASGII5.js +0 -62
- package/dist/register-5NRE7HBZ.js +0 -23
- package/dist/registry-5QLETK3Q.js +0 -17
- package/dist/registry-DYTNLOJC.js +0 -19
- package/dist/registry-HSNQ4NBP.js +0 -9
- package/dist/request-withdrawal-5APB7RKN.js +0 -31
- package/dist/revoke-VELFCK7S.js +0 -24
- package/dist/signer-IVU3B6DP.js +0 -25
- package/dist/storage-RH5VBLLL.js +0 -23
- package/dist/withdraw-accessible-LN4AOJDD.js +0 -28
- /package/dist/{api-Z66JMGOU.js.map → api-HV4PDUWF.js.map} +0 -0
- /package/dist/{backup-7PX6NDKT.js.map → backup-ODXO7OGA.js.map} +0 -0
- /package/dist/{chunk-XW5LZYV4.js.map → chunk-2HVUKRNE.js.map} +0 -0
- /package/dist/{chunk-VFQOE76I.js.map → chunk-2KOSJTMU.js.map} +0 -0
- /package/dist/{chunk-7AERMJP6.js.map → chunk-2UQ4RMHV.js.map} +0 -0
- /package/dist/{chunk-VIWOGJ2O.js.map → chunk-34RRRIC3.js.map} +0 -0
- /package/dist/{chunk-LFGJG4XZ.js.map → chunk-3I4KYWNP.js.map} +0 -0
- /package/dist/{chunk-RUWEHFRB.js.map → chunk-42KVY3OC.js.map} +0 -0
- /package/dist/{chunk-J54DC5IC.js.map → chunk-4G3IT7J7.js.map} +0 -0
- /package/dist/{chunk-WJEMUO32.js.map → chunk-4RQ62VSR.js.map} +0 -0
- /package/dist/{chunk-QAVFI23O.js.map → chunk-4ZSGSRBI.js.map} +0 -0
- /package/dist/{chunk-C4NOLA5X.js.map → chunk-5GAX2QOH.js.map} +0 -0
- /package/dist/{chunk-IEKK2XI4.js.map → chunk-5SAZMBW7.js.map} +0 -0
- /package/dist/{chunk-II6DLDZM.js.map → chunk-5TZZUL5X.js.map} +0 -0
- /package/dist/{chunk-GMZFHDSG.js.map → chunk-6CU3FN74.js.map} +0 -0
- /package/dist/{chunk-UWZ7O4NR.js.map → chunk-6MWUANRF.js.map} +0 -0
- /package/dist/{chunk-U7LV2DUN.js.map → chunk-6ULCYCMU.js.map} +0 -0
- /package/dist/{chunk-VFG5YXWM.js.map → chunk-7IZQTCTR.js.map} +0 -0
- /package/dist/{chunk-U5IJHUHZ.js.map → chunk-A4MAD6PO.js.map} +0 -0
- /package/dist/{chunk-5HZULLHE.js.map → chunk-AUR7MRHA.js.map} +0 -0
- /package/dist/{chunk-H7WQMGXH.js.map → chunk-CJEMA2Z6.js.map} +0 -0
- /package/dist/{chunk-WRLPV6SI.js.map → chunk-CTSFKDGA.js.map} +0 -0
- /package/dist/{chunk-OFYY5Q4R.js.map → chunk-CWCCYH34.js.map} +0 -0
- /package/dist/{chunk-WGH3YV6Y.js.map → chunk-DEYAO3N6.js.map} +0 -0
- /package/dist/{chunk-7L6EH6S7.js.map → chunk-DFIBOSOQ.js.map} +0 -0
- /package/dist/{chunk-3CHNKEFI.js.map → chunk-E5AHO65E.js.map} +0 -0
- /package/dist/{chunk-AG2PLGQ2.js.map → chunk-FC3XK5JT.js.map} +0 -0
- /package/dist/{chunk-DJ4TNF4N.js.map → chunk-GX2BYNCT.js.map} +0 -0
- /package/dist/{chunk-55LSDABW.js.map → chunk-HBTT7YQX.js.map} +0 -0
- /package/dist/{chunk-PYIUDUGS.js.map → chunk-HOCEGVGQ.js.map} +0 -0
- /package/dist/{chunk-5JQMX2EH.js.map → chunk-ICV32FPK.js.map} +0 -0
- /package/dist/{chunk-OWGAXPRE.js.map → chunk-IWTYFS3D.js.map} +0 -0
- /package/dist/{chunk-FCOZ7DEC.js.map → chunk-JC5R7P44.js.map} +0 -0
- /package/dist/{chunk-K6WHZBCO.js.map → chunk-KBBWY46Y.js.map} +0 -0
- /package/dist/{chunk-RNYDHIQL.js.map → chunk-KBF5GSGX.js.map} +0 -0
- /package/dist/{chunk-T4NQMPBN.js.map → chunk-KNXN47MO.js.map} +0 -0
- /package/dist/{chunk-7F2P2NHX.js.map → chunk-KXKLKJW4.js.map} +0 -0
- /package/dist/{chunk-PAJCRV7O.js.map → chunk-KYC4LIEU.js.map} +0 -0
- /package/dist/{chunk-AZPGB4OD.js.map → chunk-LEOXZY36.js.map} +0 -0
- /package/dist/{chunk-2L7PPF2C.js.map → chunk-M5I354EE.js.map} +0 -0
- /package/dist/{chunk-26FSVNBL.js.map → chunk-MK2BSS44.js.map} +0 -0
- /package/dist/{chunk-G5CFMITA.js.map → chunk-MSVY7ULZ.js.map} +0 -0
- /package/dist/{chunk-PE25C4QL.js.map → chunk-NNWK3PSY.js.map} +0 -0
- /package/dist/{chunk-JT4A6OAJ.js.map → chunk-NSZ6J5DF.js.map} +0 -0
- /package/dist/{chunk-RHB7BOEO.js.map → chunk-NUEFXLZU.js.map} +0 -0
- /package/dist/{chunk-DKGZIX6S.js.map → chunk-OQAODTYR.js.map} +0 -0
- /package/dist/{chunk-HV3VNAYI.js.map → chunk-OV4CMJGR.js.map} +0 -0
- /package/dist/{chunk-V76PHWLE.js.map → chunk-PEDZFFKW.js.map} +0 -0
- /package/dist/{chunk-CND5WQLO.js.map → chunk-Q2KVKTC2.js.map} +0 -0
- /package/dist/{chunk-HBVEGUON.js.map → chunk-QTXQ6RNZ.js.map} +0 -0
- /package/dist/{chunk-J5YDMJHG.js.map → chunk-RJ5522XX.js.map} +0 -0
- /package/dist/{chunk-CHK5VVMI.js.map → chunk-S6AQXDN4.js.map} +0 -0
- /package/dist/{chunk-MOQ5M46U.js.map → chunk-SZCHEDML.js.map} +0 -0
- /package/dist/{chunk-VUHXLL32.js.map → chunk-VJ3NQK4K.js.map} +0 -0
- /package/dist/{chunk-62TTFKU4.js.map → chunk-WNA6T3MA.js.map} +0 -0
- /package/dist/{chunk-7CZ6YXMO.js.map → chunk-WVCQ75OK.js.map} +0 -0
- /package/dist/{chunk-NO53W6O5.js.map → chunk-X66MXMR5.js.map} +0 -0
- /package/dist/{chunk-6PALJTC2.js.map → chunk-XJK2WIYN.js.map} +0 -0
- /package/dist/{chunk-5UOPIPZC.js.map → chunk-XQ7UYPVN.js.map} +0 -0
- /package/dist/{chunk-HU55NPHZ.js.map → chunk-Y2OCKFZ3.js.map} +0 -0
- /package/dist/{chunk-UJJJPKJO.js.map → chunk-YBFOBRTO.js.map} +0 -0
- /package/dist/{chunk-I6JJ6VJS.js.map → chunk-YLN33ADH.js.map} +0 -0
- /package/dist/{chunk-HGQ6UNHE.js.map → chunk-Z5YFRMG5.js.map} +0 -0
- /package/dist/{chunk-EU7HDMT3.js.map → chunk-ZWQFOXBR.js.map} +0 -0
- /package/dist/{collection-facade-JP6N7USE.js.map → collection-facade-3RAXG2OU.js.map} +0 -0
- /package/dist/{computed-6FOFXQPE.js.map → computed-R62HGCO4.js.map} +0 -0
- /package/dist/{dead-filter-NZORV45S.js.map → dead-filter-VNA4M3GP.js.map} +0 -0
- /package/dist/{delegation-FDOXXHNG.js.map → delegation-ZKII5UBI.js.map} +0 -0
- /package/dist/{derive-JR2C5JFG.js.map → derive-5D2C3WG4.js.map} +0 -0
- /package/dist/{enclave-7X5BUAMS.js.map → enclave-EUBEJGPG.js.map} +0 -0
- /package/dist/{executor-6LHOV7MN.js.map → executor-IL7SRNUZ.js.map} +0 -0
- /package/dist/{executor-EUPYHCLU.js.map → executor-OUA2Y6YC.js.map} +0 -0
- /package/dist/{executor-KZ4B3PRT.js.map → executor-WAEI6PDV.js.map} +0 -0
- /package/dist/{export-accessible-7XLSZBGJ.js.map → export-accessible-O55RKAGC.js.map} +0 -0
- /package/dist/{extract-partition-RM6EROS5.js.map → extract-partition-VBGCBA6Q.js.map} +0 -0
- /package/dist/{fanout-sidecar-BDKLP5GG.js.map → fanout-sidecar-CI5BCMQM.js.map} +0 -0
- /package/dist/{find-OH4BEULF.js.map → find-FISGUWSK.js.map} +0 -0
- /package/dist/{issue-FJE2IHUI.js.map → issue-EQAWIYFQ.js.map} +0 -0
- /package/dist/{ledger-EMDHRJJN.js.map → ledger-AJA2SR3C.js.map} +0 -0
- /package/dist/{liberate-HFIPHJGR.js.map → liberate-SEGR4JLY.js.map} +0 -0
- /package/dist/{link-set-62Y36TOG.js.map → link-set-YBAC2DQT.js.map} +0 -0
- /package/dist/{noydb-4ZASGII5.js.map → noydb-AMEOU456.js.map} +0 -0
- /package/dist/{policy-SXPLTQS4.js.map → policy-SDGRWVIL.js.map} +0 -0
- /package/dist/{post-register-DCJ2SRVG.js.map → post-register-MROIUA5O.js.map} +0 -0
- /package/dist/{public-envelope-CBRIJORQ.js.map → public-envelope-J5PLQOW2.js.map} +0 -0
- /package/dist/{read-only-facade-5ADRJVTM.js.map → read-only-facade-UCYNRE56.js.map} +0 -0
- /package/dist/{register-5NRE7HBZ.js.map → register-OVJKW7BS.js.map} +0 -0
- /package/dist/{registry-5QLETK3Q.js.map → registry-5RJSK5AJ.js.map} +0 -0
- /package/dist/{registry-DYTNLOJC.js.map → registry-DQRNTWRL.js.map} +0 -0
- /package/dist/{registry-HSNQ4NBP.js.map → registry-LTGLERS7.js.map} +0 -0
- /package/dist/{request-withdrawal-5APB7RKN.js.map → request-withdrawal-27O77D2V.js.map} +0 -0
- /package/dist/{reveal-R5N7JKA5.js.map → reveal-LT3I2K4P.js.map} +0 -0
- /package/dist/{revoke-VELFCK7S.js.map → revoke-4N45G7D2.js.map} +0 -0
- /package/dist/{signer-IVU3B6DP.js.map → signer-XJCOV7JD.js.map} +0 -0
- /package/dist/{stale-DP66HFT3.js.map → stale-IYYXEH3U.js.map} +0 -0
- /package/dist/{storage-RH5VBLLL.js.map → storage-Y44CAPE6.js.map} +0 -0
- /package/dist/{store-coordination-provider-F5N5ASDF.js.map → store-coordination-provider-WKGAE2WI.js.map} +0 -0
- /package/dist/{verify-CAYGXYNO.js.map → verify-GH6CWVSW.js.map} +0 -0
- /package/dist/{walk-3SB25HF2.js.map → walk-76UCFXYV.js.map} +0 -0
- /package/dist/{withdraw-accessible-LN4AOJDD.js.map → withdraw-accessible-6J2UH3IN.js.map} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadVaultPolicy
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-X66MXMR5.js";
|
|
4
4
|
import {
|
|
5
5
|
jsonSchemaToFields
|
|
6
6
|
} from "./chunk-TQ3REHVF.js";
|
|
@@ -9,31 +9,31 @@ import {
|
|
|
9
9
|
SEQUENCE_COLLECTION,
|
|
10
10
|
compileSequenceFormat,
|
|
11
11
|
resolveSequenceKey
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-5SAZMBW7.js";
|
|
13
13
|
import {
|
|
14
14
|
LinkIntegrityError,
|
|
15
15
|
isLinkCollectionName,
|
|
16
16
|
linkCollectionName,
|
|
17
17
|
linkRowKey
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-PEDZFFKW.js";
|
|
19
19
|
import {
|
|
20
20
|
hashFields,
|
|
21
21
|
validateSatelliteDeclaration
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-ICV32FPK.js";
|
|
23
23
|
import {
|
|
24
24
|
loadFence
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-CTSFKDGA.js";
|
|
26
26
|
import {
|
|
27
27
|
InvertedIndex,
|
|
28
28
|
MemoryIndexStore,
|
|
29
29
|
NO_SEARCH,
|
|
30
30
|
VectorSet,
|
|
31
31
|
buildPersistedIndexCallbacks
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-FGOGS7CE.js";
|
|
33
33
|
import {
|
|
34
34
|
derivePersistedSchema,
|
|
35
35
|
isZod4Schema
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-HBTT7YQX.js";
|
|
37
37
|
import {
|
|
38
38
|
isBaseLive,
|
|
39
39
|
liveBaseIdSet
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
CustodyApi,
|
|
43
43
|
NO_CARGO,
|
|
44
44
|
NO_CUSTODY
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-42KVY3OC.js";
|
|
46
46
|
import {
|
|
47
47
|
ServiceBus,
|
|
48
48
|
WriteHookRegistry,
|
|
@@ -50,45 +50,59 @@ import {
|
|
|
50
50
|
assertCanImport,
|
|
51
51
|
canExport,
|
|
52
52
|
canImport
|
|
53
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-QTXQ6RNZ.js";
|
|
54
54
|
import {
|
|
55
55
|
NO_ATTESTATION
|
|
56
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-3I4KYWNP.js";
|
|
57
57
|
import {
|
|
58
58
|
NO_CLASSIFIED,
|
|
59
|
-
|
|
59
|
+
guardClassifiedCompat,
|
|
60
60
|
resolveClassifiedFields
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-V7RNIND6.js";
|
|
62
62
|
import {
|
|
63
63
|
NO_TIERS,
|
|
64
64
|
classifySealedShred
|
|
65
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-SSFC3IYA.js";
|
|
66
66
|
import {
|
|
67
67
|
NO_PORTABILITY
|
|
68
|
-
} from "./chunk-
|
|
68
|
+
} from "./chunk-5TZZUL5X.js";
|
|
69
69
|
import {
|
|
70
70
|
runDrainBarrier
|
|
71
|
-
} from "./chunk-
|
|
71
|
+
} from "./chunk-ZWQFOXBR.js";
|
|
72
72
|
import {
|
|
73
|
-
PERIODS_COLLECTION
|
|
74
|
-
|
|
73
|
+
PERIODS_COLLECTION,
|
|
74
|
+
PERIOD_ARCHIVES_COLLECTION,
|
|
75
|
+
PERIOD_FREEZES_COLLECTION,
|
|
76
|
+
PERIOD_TARGET_PURGES_COLLECTION,
|
|
77
|
+
periodExclusiveUpperBound
|
|
78
|
+
} from "./chunk-IUKGIB2Q.js";
|
|
75
79
|
import {
|
|
76
80
|
TxContext,
|
|
77
81
|
revertExecuted
|
|
78
|
-
} from "./chunk-
|
|
82
|
+
} from "./chunk-JC5R7P44.js";
|
|
83
|
+
import {
|
|
84
|
+
DEFAULT_POSTURE,
|
|
85
|
+
ViaGraph
|
|
86
|
+
} from "./chunk-7ZANJ6JZ.js";
|
|
87
|
+
import {
|
|
88
|
+
forgetDerivedFanout,
|
|
89
|
+
ledgerAuditHook,
|
|
90
|
+
putDerivedOutput,
|
|
91
|
+
runGraphDispatchWave
|
|
92
|
+
} from "./chunk-H57P2SJB.js";
|
|
79
93
|
import {
|
|
80
94
|
OverlayedCollection
|
|
81
|
-
} from "./chunk-
|
|
95
|
+
} from "./chunk-SZCHEDML.js";
|
|
82
96
|
import {
|
|
83
97
|
EXPORT_AUDIT_COLLECTION,
|
|
84
98
|
createExportBlobsHandle,
|
|
85
99
|
runCompaction
|
|
86
|
-
} from "./chunk-
|
|
100
|
+
} from "./chunk-NYVQCZ54.js";
|
|
87
101
|
import {
|
|
88
102
|
LazyQuery,
|
|
89
103
|
decodeIdxId,
|
|
90
104
|
encodeIdxId
|
|
91
|
-
} from "./chunk-
|
|
105
|
+
} from "./chunk-XJK2WIYN.js";
|
|
92
106
|
import {
|
|
93
107
|
IMPLICIT_LAZY,
|
|
94
108
|
Lru,
|
|
@@ -97,11 +111,10 @@ import {
|
|
|
97
111
|
import {
|
|
98
112
|
resolveManagedSecret,
|
|
99
113
|
saveSealedPassphrase
|
|
100
|
-
} from "./chunk-
|
|
114
|
+
} from "./chunk-CJEMA2Z6.js";
|
|
101
115
|
import {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
} from "./chunk-WFBZRXS5.js";
|
|
116
|
+
dictCollectionName
|
|
117
|
+
} from "./chunk-IIP5WFVT.js";
|
|
105
118
|
import {
|
|
106
119
|
NO_TEAM,
|
|
107
120
|
enrollAuthenticator,
|
|
@@ -112,7 +125,7 @@ import {
|
|
|
112
125
|
rotatePassphrase,
|
|
113
126
|
updateAuthenticator,
|
|
114
127
|
writeMagicLinkGrant
|
|
115
|
-
} from "./chunk-
|
|
128
|
+
} from "./chunk-DEYAO3N6.js";
|
|
116
129
|
import {
|
|
117
130
|
hasRecoveryEnrolled,
|
|
118
131
|
hasStrongRecoveryEnrolled,
|
|
@@ -122,7 +135,7 @@ import {
|
|
|
122
135
|
mintShamirRecoveryEntry,
|
|
123
136
|
savePaperRecoveryEntries,
|
|
124
137
|
saveShamirRecoveryEntries
|
|
125
|
-
} from "./chunk-
|
|
138
|
+
} from "./chunk-VJ3NQK4K.js";
|
|
126
139
|
import {
|
|
127
140
|
INDEXED_STORE_POLICY
|
|
128
141
|
} from "./chunk-VQNJ7UZL.js";
|
|
@@ -138,17 +151,17 @@ import {
|
|
|
138
151
|
listUsers,
|
|
139
152
|
loadKeyring,
|
|
140
153
|
updateKeyringIdentity
|
|
141
|
-
} from "./chunk-
|
|
154
|
+
} from "./chunk-6CU3FN74.js";
|
|
142
155
|
import {
|
|
143
156
|
persistDirectoryConfig,
|
|
144
157
|
readDirectoryConfig
|
|
145
|
-
} from "./chunk-
|
|
158
|
+
} from "./chunk-E5AHO65E.js";
|
|
146
159
|
import {
|
|
147
160
|
generateULID
|
|
148
161
|
} from "./chunk-ZJADGNYF.js";
|
|
149
162
|
import {
|
|
150
163
|
sha256Hex
|
|
151
|
-
} from "./chunk-
|
|
164
|
+
} from "./chunk-MSVY7ULZ.js";
|
|
152
165
|
import {
|
|
153
166
|
NO_FORGET,
|
|
154
167
|
addSubjectRef,
|
|
@@ -157,50 +170,59 @@ import {
|
|
|
157
170
|
readDottedPath,
|
|
158
171
|
rebuildSubjectIndex,
|
|
159
172
|
removeSubjectRef
|
|
160
|
-
} from "./chunk-
|
|
173
|
+
} from "./chunk-A2APWS32.js";
|
|
161
174
|
import {
|
|
162
175
|
NO_SEALED_RECORD
|
|
163
|
-
} from "./chunk-
|
|
176
|
+
} from "./chunk-2HVUKRNE.js";
|
|
164
177
|
import {
|
|
165
178
|
RecordCodec,
|
|
166
179
|
SEALED_CEK_NS,
|
|
167
180
|
findByDet,
|
|
168
181
|
hasPerRecordKey,
|
|
182
|
+
makeReservedEnvelopes,
|
|
169
183
|
openEnvelopeJson,
|
|
170
184
|
queryByDet,
|
|
171
185
|
resolveStableCek,
|
|
172
186
|
validateSchemaInput
|
|
173
|
-
} from "./chunk-
|
|
187
|
+
} from "./chunk-XHD3L3JO.js";
|
|
174
188
|
import {
|
|
175
189
|
buildDeleteMarker,
|
|
176
190
|
buildTombstone,
|
|
177
191
|
isDeleteMarker,
|
|
178
192
|
isTombstone
|
|
179
|
-
} from "./chunk-
|
|
180
|
-
import {
|
|
181
|
-
NOYDB_FORMAT_VERSION
|
|
182
|
-
} from "./chunk-HCK2DFEK.js";
|
|
193
|
+
} from "./chunk-Z5YFRMG5.js";
|
|
183
194
|
import {
|
|
184
195
|
encrypt
|
|
185
|
-
} from "./chunk-
|
|
196
|
+
} from "./chunk-AUR7MRHA.js";
|
|
197
|
+
import {
|
|
198
|
+
NOYDB_FORMAT_VERSION,
|
|
199
|
+
SealedHandle
|
|
200
|
+
} from "./chunk-UNJU345Q.js";
|
|
186
201
|
import {
|
|
187
202
|
NO_AGGREGATE,
|
|
188
203
|
Query,
|
|
189
204
|
ScanBuilder
|
|
190
|
-
} from "./chunk-
|
|
205
|
+
} from "./chunk-7HAPHVRJ.js";
|
|
191
206
|
import {
|
|
192
207
|
canonicalGroupKey
|
|
193
|
-
} from "./chunk-
|
|
208
|
+
} from "./chunk-2XE6LIXT.js";
|
|
194
209
|
import {
|
|
195
|
-
moneyRuntime,
|
|
196
210
|
readPath
|
|
197
|
-
} from "./chunk-
|
|
211
|
+
} from "./chunk-KCG3BHFG.js";
|
|
198
212
|
import {
|
|
213
|
+
NO_I18N,
|
|
199
214
|
getAtPath,
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
215
|
+
isDictCollectionName,
|
|
216
|
+
isDictKeyDescriptor,
|
|
217
|
+
isI18nTextDescriptor,
|
|
218
|
+
isStaticDictDescriptor,
|
|
219
|
+
presentI18nForJoin
|
|
220
|
+
} from "./chunk-3C5CKXFI.js";
|
|
221
|
+
import {
|
|
222
|
+
installViaBinder,
|
|
223
|
+
isViaInstalled,
|
|
224
|
+
viaBinder
|
|
225
|
+
} from "./chunk-6G2SCQYS.js";
|
|
204
226
|
import {
|
|
205
227
|
AlreadyElevatedError,
|
|
206
228
|
AttestationError,
|
|
@@ -210,12 +232,13 @@ import {
|
|
|
210
232
|
ConflictError,
|
|
211
233
|
DebugPlaintextError,
|
|
212
234
|
DerivationCapExceededError,
|
|
235
|
+
DictKeyInUseError,
|
|
213
236
|
ElevationExpiredError,
|
|
237
|
+
FieldNotQueryableError,
|
|
214
238
|
ForgetStrategyNotConfiguredError,
|
|
215
239
|
IndexWriteFailureError,
|
|
216
240
|
InvalidKeyError,
|
|
217
241
|
KeyringCorruptError,
|
|
218
|
-
LocaleNotSpecifiedError,
|
|
219
242
|
ManagedRecoveryNotEnrolledError,
|
|
220
243
|
MigrationRequiredError,
|
|
221
244
|
NoAccessError,
|
|
@@ -232,85 +255,281 @@ import {
|
|
|
232
255
|
StaticDictReadonlyError,
|
|
233
256
|
StoreCapabilityError,
|
|
234
257
|
TierNotGrantedError,
|
|
235
|
-
TranslatorNotConfiguredError,
|
|
236
258
|
USER_ENVELOPE_COLLECTION,
|
|
237
259
|
UniqueConstraintError,
|
|
238
260
|
UnknownDictCodeError,
|
|
239
261
|
UnsupportedIndexOptionError,
|
|
240
262
|
ValidationError
|
|
241
|
-
} from "./chunk-
|
|
263
|
+
} from "./chunk-CU62BBRB.js";
|
|
242
264
|
|
|
243
265
|
// src/port/by/default-provider.ts
|
|
244
266
|
async function createDefaultCoordinationProvider(store) {
|
|
245
|
-
const { StoreCoordinationProvider } = await import("./store-coordination-provider-
|
|
267
|
+
const { StoreCoordinationProvider } = await import("./store-coordination-provider-WKGAE2WI.js");
|
|
246
268
|
return new StoreCoordinationProvider(store);
|
|
247
269
|
}
|
|
248
270
|
|
|
249
|
-
// src/
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
271
|
+
// src/kernel/via-pipeline.ts
|
|
272
|
+
var ViaPipeline = class _ViaPipeline {
|
|
273
|
+
constructor(bindings, taint) {
|
|
274
|
+
this.bindings = bindings;
|
|
275
|
+
this.taint = taint;
|
|
276
|
+
}
|
|
277
|
+
bindings;
|
|
278
|
+
taint;
|
|
279
|
+
/** undefined when there is nothing to enforce — the zero-via fast path is
|
|
280
|
+
* `this.via === undefined` (#553: keeps an all-plain collection sync). */
|
|
281
|
+
static build(bindings, taint) {
|
|
282
|
+
if (bindings.length === 0 && (!taint || taint.postures.size === 0)) return void 0;
|
|
283
|
+
return new _ViaPipeline(bindings, taint);
|
|
284
|
+
}
|
|
285
|
+
/** Refuse a write before crypto runs: awaits each binding's `enforceWrite` in order — first throw wins. */
|
|
286
|
+
async enforceWrite(record, ctx) {
|
|
287
|
+
for (const b of this.bindings) if (b.enforceWrite) await b.enforceWrite(record, ctx);
|
|
288
|
+
}
|
|
289
|
+
ingest(record) {
|
|
290
|
+
let r = record;
|
|
291
|
+
for (const b of this.bindings) if (b.ingest) r = b.ingest(r);
|
|
292
|
+
return r;
|
|
293
|
+
}
|
|
294
|
+
canonicalizeStored(record) {
|
|
295
|
+
let r = record;
|
|
296
|
+
for (const b of this.bindings) if (b.canonicalizeStored) r = b.canonicalizeStored(r);
|
|
297
|
+
return r;
|
|
298
|
+
}
|
|
299
|
+
async encodeWrite(record, ctx) {
|
|
300
|
+
let r = record;
|
|
301
|
+
for (const b of this.bindings) if (b.encodeWrite) r = await b.encodeWrite(r, ctx);
|
|
302
|
+
return r;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Final write-pipeline stage: folds each binding's `encodeAtRest` in
|
|
306
|
+
* order, threading the record and accumulating the sealed-slot map. A
|
|
307
|
+
* field belongs to exactly one via feature — two bindings sealing the
|
|
308
|
+
* same field is a brand-keyed collision and throws.
|
|
309
|
+
*/
|
|
310
|
+
async encodeAtRest(record, crypto) {
|
|
311
|
+
let r = record;
|
|
312
|
+
let sealed;
|
|
313
|
+
const sealedBy = /* @__PURE__ */ new Map();
|
|
314
|
+
for (const b of this.bindings) {
|
|
315
|
+
if (!b.encodeAtRest) continue;
|
|
316
|
+
const result = await b.encodeAtRest(r, crypto);
|
|
317
|
+
r = result.record;
|
|
318
|
+
if (!result.sealed) continue;
|
|
319
|
+
for (const [field, ref2] of Object.entries(result.sealed)) {
|
|
320
|
+
const priorBrand = sealedBy.get(field);
|
|
321
|
+
if (priorBrand !== void 0) {
|
|
322
|
+
throw new ValidationError(
|
|
323
|
+
`via encodeAtRest: field "${field}" sealed by binding "${priorBrand}" is also sealed by binding "${b.brand}"`
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
sealedBy.set(field, b.brand);
|
|
327
|
+
sealed ??= {};
|
|
328
|
+
sealed[field] = ref2;
|
|
329
|
+
}
|
|
265
330
|
}
|
|
331
|
+
return sealed ? { record: r, sealed } : { record: r };
|
|
266
332
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
);
|
|
333
|
+
/** First read-pipeline stage: folds each binding's `decodeAtRest` in order. */
|
|
334
|
+
async decodeAtRest(record, sealed, crypto, opts) {
|
|
335
|
+
let r = record;
|
|
336
|
+
for (const b of this.bindings) if (b.decodeAtRest) r = await b.decodeAtRest(r, sealed, crypto, opts);
|
|
337
|
+
return r;
|
|
273
338
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
339
|
+
async present(record, ctx) {
|
|
340
|
+
let r = record;
|
|
341
|
+
for (const b of this.bindings) if (b.present) r = await b.present(r, ctx);
|
|
342
|
+
return r;
|
|
343
|
+
}
|
|
344
|
+
buildClause(field, op, value) {
|
|
345
|
+
for (const b of this.bindings) {
|
|
346
|
+
const payload = b.buildClause?.(field, op, value);
|
|
347
|
+
if (payload !== void 0) return { brand: b.brand, payload };
|
|
348
|
+
}
|
|
349
|
+
return void 0;
|
|
350
|
+
}
|
|
351
|
+
evaluateClause(clause, actual, op) {
|
|
352
|
+
const b = this.bindings.find((x) => x.brand === clause.brand);
|
|
353
|
+
return b?.evaluateClause ? b.evaluateClause(actual, op, clause.payload) : false;
|
|
354
|
+
}
|
|
355
|
+
decodeResults(record) {
|
|
356
|
+
let r = record;
|
|
357
|
+
for (const b of this.bindings) if (b.decodeResults) r = b.decodeResults(r);
|
|
358
|
+
return r;
|
|
280
359
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
360
|
+
/** undefined = no binding covers the field → caller falls back to generic compare. */
|
|
361
|
+
compareForOrder(field, a, b) {
|
|
362
|
+
for (const bind of this.bindings) {
|
|
363
|
+
const c = bind.compareForOrder?.(field, a, b);
|
|
364
|
+
if (c !== void 0) return c;
|
|
365
|
+
}
|
|
366
|
+
return void 0;
|
|
367
|
+
}
|
|
368
|
+
/** undefined = no binding resolves an order-sort label for `key` at `field`/`locale` (#650 Task 7 —
|
|
369
|
+
* the `orderBy(..., {by:'label'})` per-call-locale channel `compareForOrder` above can't serve). */
|
|
370
|
+
resolveOrderLabel(field, key, locale) {
|
|
371
|
+
for (const bind of this.bindings) {
|
|
372
|
+
const label = bind.resolveOrderLabel?.(field, key, locale);
|
|
373
|
+
if (label !== void 0) return label;
|
|
374
|
+
}
|
|
375
|
+
return void 0;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Which posture governs `field`, if any binding covers it — `undefined`
|
|
379
|
+
* means no binding declares `field` (the generic, non-via query path
|
|
380
|
+
* applies). #629 Task 8's posture consumer: `.where()`/`.orderBy()`
|
|
381
|
+
* consult this before building/evaluating a clause, refusing fields whose
|
|
382
|
+
* posture is `queryable: 'none'` (e.g. blob); every other posture
|
|
383
|
+
* ('det-exact'/'ordered'/'full') is left to the existing per-binding
|
|
384
|
+
* buildClause/evaluateClause/compareForOrder machinery, unchanged.
|
|
385
|
+
*
|
|
386
|
+
* #638 Task 3: the graph-computed taint overlay is consulted FIRST — a
|
|
387
|
+
* derived field's assigned (most-restrictive-of-sources) posture wins over
|
|
388
|
+
* whatever a binding would otherwise report for that field name (no
|
|
389
|
+
* binding covers a computed/derived field today, so this never actually
|
|
390
|
+
* shadows one — see `via-taint-binding.ts`'s `taintBinding.covers`, which
|
|
391
|
+
* only claims the sealed subset for `encodeAtRest`/`decodeAtRest`, not for
|
|
392
|
+
* this lookup).
|
|
393
|
+
*/
|
|
394
|
+
postureFor(field) {
|
|
395
|
+
const t = this.taint?.postures.get(field);
|
|
396
|
+
if (t) return t;
|
|
397
|
+
for (const b of this.bindings) {
|
|
398
|
+
if (b.covers?.(field)) return b.posture;
|
|
399
|
+
}
|
|
400
|
+
return void 0;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Refuse any field-based reducer over a `queryable: 'none'` field (blob) —
|
|
404
|
+
* metadata-only: walks each reducer's `.field` and checks posture, no
|
|
405
|
+
* rewriting. Reducers with no `.field` (e.g. `count()`) are skipped.
|
|
406
|
+
*
|
|
407
|
+
* Shared by `wrapReducers` below (which ALSO applies each binding's
|
|
408
|
+
* rewrite, e.g. money's exact-BigInt reducer swap) and by
|
|
409
|
+
* `ScanBuilder.aggregate()` (#629 Task 8 review fix wave 1), which must
|
|
410
|
+
* call THIS, not `wrapReducers` — wiring full `wrapReducers` into
|
|
411
|
+
* `ScanBuilder.aggregate()` would newly activate money/i18n reducer
|
|
412
|
+
* wrapping on a path that has never run it, a parity-breaking behavior
|
|
413
|
+
* change for existing brands.
|
|
414
|
+
*/
|
|
415
|
+
refuseUnqueryableReducers(spec) {
|
|
416
|
+
for (const reducer of Object.values(spec)) {
|
|
417
|
+
const field = reducer?.field;
|
|
418
|
+
if (field !== void 0 && this.postureFor(field)?.queryable === "none") {
|
|
419
|
+
throw new FieldNotQueryableError(field);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Rewrite an aggregate spec (money exact reducers), then refuse any
|
|
425
|
+
* field-based reducer over a `queryable: 'none'` field (blob) — the same
|
|
426
|
+
* posture gate `.where()`/`.orderBy()` apply, extended to `.aggregate()`
|
|
427
|
+
* (both the bare-spec and builder forms funnel through here).
|
|
428
|
+
*/
|
|
429
|
+
wrapReducers(spec) {
|
|
430
|
+
this.refuseUnqueryableReducers(spec);
|
|
431
|
+
let s = spec;
|
|
432
|
+
for (const b of this.bindings) if (b.wrapReducers) s = b.wrapReducers(s);
|
|
433
|
+
return s;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Deliberate export-layer redaction (#629 Task 9's posture consumer):
|
|
437
|
+
* for every OWN field the decoded record carries, if the covering
|
|
438
|
+
* binding's posture declares `exportable: false` (classified today;
|
|
439
|
+
* money/i18n/blob are all `true`), replace the value with
|
|
440
|
+
* {@link EXPORT_REDACTION_MARKER} — the same `'[sealed]'` string
|
|
441
|
+
* `SealedHandle.toJSON()` (`kernel/types.ts`) already emits, so this is
|
|
442
|
+
* byte-parity with today's accidental redaction, now independent of the
|
|
443
|
+
* field's runtime shape. `SealedHandle.toJSON()` stays untouched as
|
|
444
|
+
* defense-in-depth: a record read via `collection.get()` and
|
|
445
|
+
* JSON.stringify'd outside `exportStream()` still gets the accident's
|
|
446
|
+
* redaction on its own. Never mutates the input; returns a new object
|
|
447
|
+
* only when a field was actually redacted.
|
|
448
|
+
*/
|
|
449
|
+
redactForExport(record) {
|
|
450
|
+
let out;
|
|
451
|
+
for (const field of Object.keys(record)) {
|
|
452
|
+
if (this.postureFor(field)?.exportable === false) {
|
|
453
|
+
out ??= { ...record };
|
|
454
|
+
out[field] = EXPORT_REDACTION_MARKER;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return out ?? record;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Shared fold over a subset of `this.bindings`: runs each binding's
|
|
461
|
+
* `erase`, concatenating shredded/retainedShared counts and residue.
|
|
462
|
+
* Consults `posture.forgettable` (#629 Task 10) — a binding declaring
|
|
463
|
+
* `forgettable: false` is skipped even if it defines `erase` (none does
|
|
464
|
+
* today; a future non-forgettable binding is supported without a brand check).
|
|
465
|
+
*/
|
|
466
|
+
async foldErase(ctx, bindings) {
|
|
467
|
+
let shredded = 0;
|
|
468
|
+
let retainedShared = 0;
|
|
469
|
+
const residue = [];
|
|
470
|
+
for (const b of bindings) {
|
|
471
|
+
if (!b.erase || b.posture.forgettable === false) continue;
|
|
472
|
+
const report = await b.erase(ctx);
|
|
473
|
+
shredded += report.shredded;
|
|
474
|
+
retainedShared += report.retainedShared ?? 0;
|
|
475
|
+
residue.push(...report.residue);
|
|
476
|
+
}
|
|
477
|
+
return retainedShared > 0 ? { shredded, residue, retainedShared } : { shredded, residue };
|
|
478
|
+
}
|
|
479
|
+
/** `forget()`'s per-ref erasure door: folds every binding's `erase`. */
|
|
480
|
+
async erase(ctx) {
|
|
481
|
+
return this.foldErase(ctx, this.bindings);
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* `forget()`'s SEALED-posture-only erase fold (#629 Task 10, `Collection._onViaErase`'s
|
|
485
|
+
* one caller) — classified today; metadata-filtered on
|
|
486
|
+
* `posture.encryptedAtRest`, not brand-checked. `undefined` when no
|
|
487
|
+
* sealed-posture binding is compiled in — `vault.ts` then falls back to
|
|
488
|
+
* its own bare-`sensitive` classification, which no via binding covers.
|
|
489
|
+
*/
|
|
490
|
+
async eraseSealed(ctx) {
|
|
491
|
+
const sealed = this.bindings.filter((b) => b.posture.encryptedAtRest === "sealed");
|
|
492
|
+
return sealed.length === 0 ? void 0 : this.foldErase(ctx, sealed);
|
|
286
493
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
494
|
+
/** True iff any binding implements decodeResults (query fast-path gating). */
|
|
495
|
+
get hasResultDecode() {
|
|
496
|
+
return this.bindings.some((b) => b.decodeResults !== void 0);
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* True iff any binding declares an at-rest hook (`encodeAtRest`/
|
|
500
|
+
* `decodeAtRest`) — the codec boundary's async-stack gate (#629 Task 3
|
|
501
|
+
* consults this to choose between the hook path and today's inline
|
|
502
|
+
* sealed-slot path; a stack with only sync hooks, e.g. money-only, stays
|
|
503
|
+
* `false`).
|
|
504
|
+
*/
|
|
505
|
+
get hasAtRestHooks() {
|
|
506
|
+
return this.bindings.some((b) => b.encodeAtRest !== void 0 || b.decodeAtRest !== void 0);
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Fold every binding's `describeFragment()` into one `brand -> fragment`
|
|
510
|
+
* map (#650 Task 7 — the first-ever consumer; `describeFragment` was
|
|
511
|
+
* declared at `via.ts:136` since #623 with zero callers until this task).
|
|
512
|
+
* `undefined` when no compiled binding implements it. Consumed by
|
|
513
|
+
* `Collection.describe()`/`describeAsync()`, threaded to `buildDescription`
|
|
514
|
+
* as `BuildDescriptionInput.viaFragments`.
|
|
515
|
+
*/
|
|
516
|
+
describeFragments() {
|
|
517
|
+
let out;
|
|
518
|
+
for (const b of this.bindings) {
|
|
519
|
+
const f = b.describeFragment?.();
|
|
520
|
+
if (f !== void 0) {
|
|
521
|
+
out ??= {};
|
|
522
|
+
out[b.brand] = f;
|
|
523
|
+
}
|
|
312
524
|
}
|
|
525
|
+
return out;
|
|
313
526
|
}
|
|
527
|
+
};
|
|
528
|
+
var EXPORT_REDACTION_MARKER = "[sealed]";
|
|
529
|
+
function exportRedact(coll, record) {
|
|
530
|
+
if (record === null || typeof record !== "object") return record;
|
|
531
|
+
const via2 = coll.via;
|
|
532
|
+
return via2 ? via2.redactForExport(record) : record;
|
|
314
533
|
}
|
|
315
534
|
|
|
316
535
|
// src/with-lookup/indexing/collection-facade.ts
|
|
@@ -398,7 +617,7 @@ async function rebuildIndexes(ctx) {
|
|
|
398
617
|
for (const recordId of canonicalIds) {
|
|
399
618
|
const envelope = await ctx.adapter.get(ctx.vault, ctx.name, recordId);
|
|
400
619
|
if (!envelope) continue;
|
|
401
|
-
const record = await ctx.codec.decryptRecord(envelope, { skipValidation: true });
|
|
620
|
+
const record = await ctx.codec.decryptRecord(envelope, { skipValidation: true, id: recordId });
|
|
402
621
|
if (record === null) continue;
|
|
403
622
|
await maintainPersistedIndexesOnPut(ctx, recordId, record, null, envelope._v);
|
|
404
623
|
}
|
|
@@ -451,7 +670,7 @@ async function reconcileIndex(ctx, field, opts = {}) {
|
|
|
451
670
|
if (id.startsWith("_")) continue;
|
|
452
671
|
const env = await ctx.adapter.get(ctx.vault, ctx.name, id);
|
|
453
672
|
if (!env) continue;
|
|
454
|
-
const record = await ctx.codec.decryptRecord(env, { skipValidation: true });
|
|
673
|
+
const record = await ctx.codec.decryptRecord(env, { skipValidation: true, id });
|
|
455
674
|
if (record === null) continue;
|
|
456
675
|
const live = readPersistedValue(record, field);
|
|
457
676
|
const stored = sidecar.get(id);
|
|
@@ -875,13 +1094,14 @@ function deriveWidget(opts) {
|
|
|
875
1094
|
case "percent":
|
|
876
1095
|
return "number";
|
|
877
1096
|
}
|
|
878
|
-
if (opts.dict !== void 0) return "select";
|
|
1097
|
+
if (opts.dict !== void 0 || opts.lookup !== void 0) return "select";
|
|
879
1098
|
if (opts.type === "boolean") return "checkbox";
|
|
880
1099
|
if (opts.type === "number") return "number";
|
|
881
1100
|
return "text";
|
|
882
1101
|
}
|
|
883
1102
|
function buildDescription(input) {
|
|
884
|
-
const { collection, fieldMeta, moneyFields, dictKeyFields, computed, refs, zodFields, dictLabels, meta, i18nFields, classified } = input;
|
|
1103
|
+
const { collection, fieldMeta, moneyFields, dictKeyFields, lookupFields, computed, refs, zodFields, dictLabels, meta, i18nFields, classified, taint } = input;
|
|
1104
|
+
const lookupFragments = input.viaFragments?.["lookup"]?.lookupFields;
|
|
885
1105
|
if (zodFields !== void 0 && Object.keys(zodFields).length > 0 && fieldMeta !== void 0) {
|
|
886
1106
|
const knownFields = /* @__PURE__ */ new Set([
|
|
887
1107
|
...Object.keys(moneyFields ?? {}),
|
|
@@ -889,13 +1109,15 @@ function buildDescription(input) {
|
|
|
889
1109
|
...Object.keys(refs),
|
|
890
1110
|
...Object.keys(computed ?? {}),
|
|
891
1111
|
...Object.keys(zodFields),
|
|
892
|
-
...Object.keys(i18nFields ?? {})
|
|
1112
|
+
...Object.keys(i18nFields ?? {}),
|
|
1113
|
+
...Object.keys(lookupFields ?? {})
|
|
893
1114
|
]);
|
|
894
1115
|
validateFieldMetaKeys(collection, fieldMeta, knownFields);
|
|
895
1116
|
}
|
|
896
1117
|
const allKeys = /* @__PURE__ */ new Set([
|
|
897
1118
|
...Object.keys(moneyFields ?? {}),
|
|
898
1119
|
...Object.keys(dictKeyFields ?? {}),
|
|
1120
|
+
...Object.keys(lookupFields ?? {}),
|
|
899
1121
|
...Object.keys(refs),
|
|
900
1122
|
...Object.keys(computed ?? {}),
|
|
901
1123
|
...Object.keys(fieldMeta ?? {}),
|
|
@@ -908,15 +1130,18 @@ function buildDescription(input) {
|
|
|
908
1130
|
const zod = zodFields?.[key];
|
|
909
1131
|
const money = moneyFields?.[key];
|
|
910
1132
|
const dict = dictKeyFields?.[key];
|
|
1133
|
+
const lookupDesc = lookupFields?.[key];
|
|
911
1134
|
const refDesc = refs[key];
|
|
912
1135
|
const isComputed = computed !== void 0 && key in computed;
|
|
913
1136
|
const i18nDesc = i18nFields?.[key];
|
|
914
1137
|
const cls = classified?.[key];
|
|
1138
|
+
const taintPosture = taint?.postures.get(key);
|
|
915
1139
|
let type = zod?.type ?? "unknown";
|
|
916
1140
|
const inferred = {};
|
|
917
1141
|
let moneyBlock;
|
|
918
1142
|
let dictBlock;
|
|
919
1143
|
let refBlock;
|
|
1144
|
+
let lookupBlock;
|
|
920
1145
|
if (money) {
|
|
921
1146
|
type = "number";
|
|
922
1147
|
inferred.semanticType = "currency";
|
|
@@ -963,10 +1188,49 @@ function buildDescription(input) {
|
|
|
963
1188
|
dictBlock = { name: dict.name, static: false };
|
|
964
1189
|
}
|
|
965
1190
|
}
|
|
1191
|
+
} else if (lookupDesc) {
|
|
1192
|
+
type = "enum";
|
|
1193
|
+
const lookupLabelMap = dictLabels?.[lookupDesc.dimension];
|
|
1194
|
+
const lookupLabelMapHasEntries = lookupLabelMap !== void 0 && Object.keys(lookupLabelMap).length > 0;
|
|
1195
|
+
if (lookupDesc.table !== void 0) {
|
|
1196
|
+
const displayLocale = lookupDesc.displayLocale;
|
|
1197
|
+
const table = lookupDesc.table;
|
|
1198
|
+
const values = (lookupDesc.keys ?? Object.keys(table)).map((k) => {
|
|
1199
|
+
const localeMap = table[k];
|
|
1200
|
+
const label = displayLocale !== void 0 ? localeMap?.[displayLocale] ?? void 0 : void 0;
|
|
1201
|
+
return label !== void 0 ? { value: k, label } : { value: k };
|
|
1202
|
+
});
|
|
1203
|
+
dictBlock = { name: lookupDesc.dimension, static: true, values };
|
|
1204
|
+
} else if (lookupLabelMapHasEntries) {
|
|
1205
|
+
const values = Object.entries(lookupLabelMap).map(([value, label]) => ({ value, label }));
|
|
1206
|
+
dictBlock = { name: lookupDesc.dimension, static: false, values };
|
|
1207
|
+
} else if (lookupDesc.keys !== void 0) {
|
|
1208
|
+
const values = lookupDesc.keys.map((k) => {
|
|
1209
|
+
const label = lookupDesc.labels?.[k];
|
|
1210
|
+
return label !== void 0 ? { value: k, label } : { value: k };
|
|
1211
|
+
});
|
|
1212
|
+
dictBlock = { name: lookupDesc.dimension, static: false, values };
|
|
1213
|
+
} else {
|
|
1214
|
+
dictBlock = { name: lookupDesc.dimension, static: false };
|
|
1215
|
+
}
|
|
966
1216
|
} else if (i18nDesc) {
|
|
967
1217
|
type = "string";
|
|
968
1218
|
} else if (isComputed) {
|
|
969
1219
|
}
|
|
1220
|
+
const lookupFragmentEntry = lookupFragments?.[key];
|
|
1221
|
+
if (lookupFragmentEntry !== void 0) {
|
|
1222
|
+
lookupBlock = {
|
|
1223
|
+
...lookupFragmentEntry.dimension !== void 0 ? { dimension: lookupFragmentEntry.dimension } : {},
|
|
1224
|
+
backing: lookupFragmentEntry.backing,
|
|
1225
|
+
vocabulary: lookupFragmentEntry.vocabulary,
|
|
1226
|
+
key: lookupFragmentEntry.key,
|
|
1227
|
+
onDelete: lookupFragmentEntry.onDelete,
|
|
1228
|
+
...lookupFragmentEntry.altKeys !== void 0 ? { altKeys: lookupFragmentEntry.altKeys } : {},
|
|
1229
|
+
...lookupFragmentEntry.present !== void 0 ? { present: lookupFragmentEntry.present } : {},
|
|
1230
|
+
...lookupFragmentEntry.sortBy !== void 0 ? { sortBy: lookupFragmentEntry.sortBy } : {},
|
|
1231
|
+
...lookupFragmentEntry.keys !== void 0 ? { keys: lookupFragmentEntry.keys } : {}
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
970
1234
|
if (cls !== void 0) inferred.sensitivity = cls.sensitivity;
|
|
971
1235
|
const channelEntry = fieldMeta?.[key];
|
|
972
1236
|
const zodMeta = zod?.meta;
|
|
@@ -988,6 +1252,7 @@ function buildDescription(input) {
|
|
|
988
1252
|
type,
|
|
989
1253
|
...resolved.semanticType !== void 0 ? { semanticType: resolved.semanticType } : {},
|
|
990
1254
|
...dictBlock !== void 0 ? { dict: dictBlock } : {},
|
|
1255
|
+
...lookupBlock !== void 0 ? { lookup: lookupBlock } : {},
|
|
991
1256
|
...resolved.widget !== void 0 ? { resolvedWidget: resolved.widget } : {}
|
|
992
1257
|
});
|
|
993
1258
|
const editable = !isComputed && key !== "id";
|
|
@@ -1009,6 +1274,7 @@ function buildDescription(input) {
|
|
|
1009
1274
|
...refBlock !== void 0 ? { ref: refBlock } : {},
|
|
1010
1275
|
...moneyBlock !== void 0 ? { money: moneyBlock } : {},
|
|
1011
1276
|
...dictBlock !== void 0 ? { dict: dictBlock } : {},
|
|
1277
|
+
...lookupBlock !== void 0 ? { lookup: lookupBlock } : {},
|
|
1012
1278
|
...isComputed ? { computed: true } : {},
|
|
1013
1279
|
...i18nBlock !== void 0 ? { i18n: i18nBlock } : {},
|
|
1014
1280
|
widget,
|
|
@@ -1020,7 +1286,8 @@ function buildDescription(input) {
|
|
|
1020
1286
|
list: cls.list.kind === "omit" ? "omit" : cls.list.kind === "mask" ? { mask: cls.list.pattern } : { rider: cls.list.rider },
|
|
1021
1287
|
...cls.equatable === true ? { equatable: true } : {}
|
|
1022
1288
|
}
|
|
1023
|
-
} : {}
|
|
1289
|
+
} : {},
|
|
1290
|
+
...taintPosture !== void 0 ? { taint: { posture: taintPosture, forcedBy: taint?.provenance?.get(key) ?? [] } } : {}
|
|
1024
1291
|
};
|
|
1025
1292
|
fields.push(field);
|
|
1026
1293
|
}
|
|
@@ -1071,7 +1338,7 @@ async function resolveStaleOnRead(accessor, outputCollection, id) {
|
|
|
1071
1338
|
}
|
|
1072
1339
|
const sourceWithId = { ...source, id };
|
|
1073
1340
|
if (DerivationExecutor === null) {
|
|
1074
|
-
({ DerivationExecutor } = await import("./executor-
|
|
1341
|
+
({ DerivationExecutor } = await import("./executor-WAEI6PDV.js"));
|
|
1075
1342
|
}
|
|
1076
1343
|
const ctx = { vault: accessor.getReadOnlyFacade() };
|
|
1077
1344
|
const result = await DerivationExecutor.run(spec, sourceWithId, 0, strategyHash, ctx);
|
|
@@ -1168,51 +1435,64 @@ var NO_HISTORY = {
|
|
|
1168
1435
|
}
|
|
1169
1436
|
};
|
|
1170
1437
|
|
|
1171
|
-
// src/with-shape/i18n/strategy.ts
|
|
1172
|
-
function notEnabled2(op) {
|
|
1173
|
-
return new Error(
|
|
1174
|
-
`${op} requires the i18n strategy. Import \`{ withI18n }\` from "@noy-db/hub/i18n" and pass it to \`createNoydb({ i18nStrategy: withI18n() })\`.`
|
|
1175
|
-
);
|
|
1176
|
-
}
|
|
1177
|
-
var NO_I18N = {
|
|
1178
|
-
applyI18nLocale(record) {
|
|
1179
|
-
return record;
|
|
1180
|
-
},
|
|
1181
|
-
validateI18nTextValue() {
|
|
1182
|
-
throw notEnabled2("i18nText field validation");
|
|
1183
|
-
},
|
|
1184
|
-
enforceScript(value) {
|
|
1185
|
-
return { value, warnings: [] };
|
|
1186
|
-
},
|
|
1187
|
-
computeExemptFills() {
|
|
1188
|
-
return /* @__PURE__ */ new Map();
|
|
1189
|
-
},
|
|
1190
|
-
densify() {
|
|
1191
|
-
},
|
|
1192
|
-
buildDictionaryHandle() {
|
|
1193
|
-
throw notEnabled2("vault.dictionary()");
|
|
1194
|
-
}
|
|
1195
|
-
};
|
|
1196
|
-
|
|
1197
1438
|
// src/with-party/team/sync-strategy.ts
|
|
1198
|
-
function
|
|
1439
|
+
function notEnabled2(op) {
|
|
1199
1440
|
return new Error(
|
|
1200
1441
|
`${op} requires the sync strategy. Import \`{ withSync }\` from "@noy-db/hub/sync" and pass it to \`createNoydb({ syncStrategy: withSync() })\`.`
|
|
1201
1442
|
);
|
|
1202
1443
|
}
|
|
1203
1444
|
var NO_SYNC = {
|
|
1204
1445
|
buildSyncEngine() {
|
|
1205
|
-
throw
|
|
1446
|
+
throw notEnabled2("SyncEngine");
|
|
1206
1447
|
},
|
|
1207
1448
|
buildSyncTransaction() {
|
|
1208
|
-
throw
|
|
1449
|
+
throw notEnabled2("SyncTransaction");
|
|
1209
1450
|
},
|
|
1210
1451
|
buildPresence() {
|
|
1211
|
-
throw
|
|
1452
|
+
throw notEnabled2("collection.presence()");
|
|
1212
1453
|
}
|
|
1213
1454
|
};
|
|
1214
1455
|
|
|
1215
|
-
// src/
|
|
1456
|
+
// src/shape/via-blob/binding.ts
|
|
1457
|
+
async function eraseBlobs(ctx, cfg) {
|
|
1458
|
+
if (cfg.purgeBlobsForRecord) return cfg.purgeBlobsForRecord(ctx.id);
|
|
1459
|
+
return { shredded: 0, residue: [] };
|
|
1460
|
+
}
|
|
1461
|
+
function buildBlobDescribeFragment(fields) {
|
|
1462
|
+
return {
|
|
1463
|
+
blobFields: Object.fromEntries(
|
|
1464
|
+
Object.entries(fields).map(([field, p]) => [field, {
|
|
1465
|
+
...p.retainDays !== void 0 ? { retainDays: p.retainDays } : {},
|
|
1466
|
+
...p.evictWhen !== void 0 ? { evictWhen: true } : {},
|
|
1467
|
+
...p.legalHold !== void 0 ? { legalHold: true } : {},
|
|
1468
|
+
...p.retainUntil !== void 0 ? { retainUntil: true } : {},
|
|
1469
|
+
...p.external === true ? { external: true } : {},
|
|
1470
|
+
...p.public === true ? { public: true } : {},
|
|
1471
|
+
...p.backlink !== void 0 ? { backlink: p.backlink } : {}
|
|
1472
|
+
}])
|
|
1473
|
+
)
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
function blobBinding(cfg) {
|
|
1477
|
+
return {
|
|
1478
|
+
brand: "blob",
|
|
1479
|
+
// encryptedAtRest: 'envelope' — blob chunks are AEAD-encrypted envelopes
|
|
1480
|
+
// in `_blob_chunks` (service-side), not sealed record fields; queryable:
|
|
1481
|
+
// 'none' — nothing indexes blob content; exportable — vault.exportBlobs()
|
|
1482
|
+
// is a first-class door; forgettable — shredAllForRecord participates in
|
|
1483
|
+
// vault.forget().
|
|
1484
|
+
posture: { encryptedAtRest: "envelope", queryable: "none", exportable: true, forgettable: true },
|
|
1485
|
+
covers: (field) => field in cfg.fields,
|
|
1486
|
+
erase: (ctx) => eraseBlobs(ctx, cfg),
|
|
1487
|
+
describeFragment: () => buildBlobDescribeFragment(cfg.fields)
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
function linkBlobVia() {
|
|
1491
|
+
installViaBinder("blob", (c) => blobBinding(c));
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
// src/port/with/blob-strategy.ts
|
|
1495
|
+
linkBlobVia();
|
|
1216
1496
|
var NO_BLOBS = {
|
|
1217
1497
|
openSlot() {
|
|
1218
1498
|
throw new Error(
|
|
@@ -1221,23 +1501,502 @@ var NO_BLOBS = {
|
|
|
1221
1501
|
}
|
|
1222
1502
|
};
|
|
1223
1503
|
|
|
1504
|
+
// src/shape/via-lookup/registry.ts
|
|
1505
|
+
function enforceStaticDictOnPut(staticFields, record) {
|
|
1506
|
+
if (!staticFields || Object.keys(staticFields).length === 0) return;
|
|
1507
|
+
if (!record || typeof record !== "object") return;
|
|
1508
|
+
const obj = record;
|
|
1509
|
+
for (const [field, desc] of Object.entries(staticFields)) {
|
|
1510
|
+
if (desc.validateCodes === false) continue;
|
|
1511
|
+
const known = new Set(desc.keys);
|
|
1512
|
+
const values = getAtPath(obj, field);
|
|
1513
|
+
for (const value of values) {
|
|
1514
|
+
if (value === void 0 || value === null) continue;
|
|
1515
|
+
const codes = Array.isArray(value) ? value : [value];
|
|
1516
|
+
for (const code of codes) {
|
|
1517
|
+
if (typeof code !== "string") continue;
|
|
1518
|
+
if (!known.has(code)) {
|
|
1519
|
+
throw new UnknownDictCodeError(desc.name, field, code);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
function resolveDictSource(leftCollection, field, staticDescriptorByField, dictKeyFieldRegistry, getDictionaryHandle) {
|
|
1526
|
+
const staticFields = staticDescriptorByField.get(leftCollection);
|
|
1527
|
+
if (staticFields && field in staticFields) {
|
|
1528
|
+
const desc = staticFields[field];
|
|
1529
|
+
const rows = Object.entries(desc.table).map(
|
|
1530
|
+
([key, labels]) => ({ key, labels, ...labels })
|
|
1531
|
+
);
|
|
1532
|
+
const source = {
|
|
1533
|
+
snapshot() {
|
|
1534
|
+
return rows;
|
|
1535
|
+
},
|
|
1536
|
+
lookupById(id) {
|
|
1537
|
+
return rows.find((e) => e["key"] === id);
|
|
1538
|
+
}
|
|
1539
|
+
};
|
|
1540
|
+
if (desc.displayLocale !== void 0) {
|
|
1541
|
+
;
|
|
1542
|
+
source.displayLocale = desc.displayLocale;
|
|
1543
|
+
}
|
|
1544
|
+
return source;
|
|
1545
|
+
}
|
|
1546
|
+
const dictFields = dictKeyFieldRegistry.get(leftCollection);
|
|
1547
|
+
if (!dictFields || !(field in dictFields)) return null;
|
|
1548
|
+
const dictName = dictFields[field];
|
|
1549
|
+
if (!dictName) return null;
|
|
1550
|
+
const handle = getDictionaryHandle(dictName);
|
|
1551
|
+
return {
|
|
1552
|
+
snapshot() {
|
|
1553
|
+
return handle.snapshotEntries();
|
|
1554
|
+
},
|
|
1555
|
+
lookupById(id) {
|
|
1556
|
+
const entries = handle.snapshotEntries();
|
|
1557
|
+
return entries.find((e) => e["key"] === id);
|
|
1558
|
+
}
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
async function updateReferencingRecords(registry, getCollection, name, oldKey, newKey) {
|
|
1562
|
+
for (const [collectionName, dictFields] of registry) {
|
|
1563
|
+
const fields = Object.entries(dictFields).filter(([, dn]) => dn === name).map(([field]) => field);
|
|
1564
|
+
if (fields.length === 0) continue;
|
|
1565
|
+
const coll = getCollection(collectionName);
|
|
1566
|
+
const records = await coll.list();
|
|
1567
|
+
for (const record of records) {
|
|
1568
|
+
let changed = false;
|
|
1569
|
+
const updated = { ...record };
|
|
1570
|
+
for (const field of fields) {
|
|
1571
|
+
if (updated[field] === oldKey) {
|
|
1572
|
+
updated[field] = newKey;
|
|
1573
|
+
changed = true;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
if (changed) {
|
|
1577
|
+
const id = record["id"];
|
|
1578
|
+
if (id !== void 0) {
|
|
1579
|
+
await coll.put(id, updated);
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
function resolveLabelFromMap(labels, locale, fallback) {
|
|
1586
|
+
if (labels[locale] !== void 0) return labels[locale];
|
|
1587
|
+
const chain = Array.isArray(fallback) ? fallback : fallback ? [fallback] : [];
|
|
1588
|
+
for (const fb of chain) {
|
|
1589
|
+
if (fb === "any") {
|
|
1590
|
+
const any = Object.values(labels)[0];
|
|
1591
|
+
if (any !== void 0) return any;
|
|
1592
|
+
} else if (labels[fb] !== void 0) {
|
|
1593
|
+
return labels[fb];
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
return void 0;
|
|
1597
|
+
}
|
|
1598
|
+
function lookupToStaticDictCompat(desc) {
|
|
1599
|
+
if (desc.backing !== "static" || desc.table === void 0) return void 0;
|
|
1600
|
+
return {
|
|
1601
|
+
_noydbStaticDict: true,
|
|
1602
|
+
_viaBrand: "i18n",
|
|
1603
|
+
name: desc.dimension,
|
|
1604
|
+
table: desc.table,
|
|
1605
|
+
keys: desc.keys ?? Object.keys(desc.table),
|
|
1606
|
+
...desc.displayLocale !== void 0 ? { displayLocale: desc.displayLocale } : {},
|
|
1607
|
+
...desc.onMissing !== void 0 ? { onMissing: desc.onMissing } : {},
|
|
1608
|
+
...desc.substitute !== void 0 ? { substitute: desc.substitute } : {},
|
|
1609
|
+
validateCodes: desc.vocabulary === "closed"
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
function collectLookupDictCompat(lookupFields) {
|
|
1613
|
+
const dictFieldMap = {};
|
|
1614
|
+
const staticEntries = [];
|
|
1615
|
+
for (const [field, desc] of Object.entries(lookupFields ?? {})) {
|
|
1616
|
+
if (desc.backing === "reserved") {
|
|
1617
|
+
dictFieldMap[field] = desc.dimension;
|
|
1618
|
+
} else {
|
|
1619
|
+
const compat = lookupToStaticDictCompat(desc);
|
|
1620
|
+
if (compat) staticEntries.push([field, compat]);
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
return { dictFieldMap, staticEntries };
|
|
1624
|
+
}
|
|
1625
|
+
function materializeBackingTable(descriptor, rows) {
|
|
1626
|
+
const keys = new Set(rows.keys());
|
|
1627
|
+
const owner = /* @__PURE__ */ new Map();
|
|
1628
|
+
for (const key of keys) owner.set(key, key);
|
|
1629
|
+
const altIndex = /* @__PURE__ */ new Map();
|
|
1630
|
+
const altFields = descriptor.altKeys ?? [];
|
|
1631
|
+
for (const [canonicalKey, row] of rows) {
|
|
1632
|
+
for (const altField of altFields) {
|
|
1633
|
+
const value = row[altField];
|
|
1634
|
+
if (typeof value !== "string" || value === "") continue;
|
|
1635
|
+
const existingOwner = owner.get(value);
|
|
1636
|
+
if (existingOwner !== void 0 && existingOwner !== canonicalKey) {
|
|
1637
|
+
throw new ValidationError(
|
|
1638
|
+
`lookup "${descriptor.dimension}": altKey field "${altField}" value "${value}" is claimed by both "${existingOwner}" and "${canonicalKey}" \u2014 key/altKey values must be unique across the dimension.`
|
|
1639
|
+
);
|
|
1640
|
+
}
|
|
1641
|
+
owner.set(value, canonicalKey);
|
|
1642
|
+
altIndex.set(value, canonicalKey);
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
return { keys, altIndex };
|
|
1646
|
+
}
|
|
1647
|
+
function checkLookupMembership(descriptor, key, getDictionary, getCollection) {
|
|
1648
|
+
if (descriptor.backing === "static") {
|
|
1649
|
+
const known = descriptor.keys ?? (descriptor.table ? Object.keys(descriptor.table) : []);
|
|
1650
|
+
return known.includes(key);
|
|
1651
|
+
}
|
|
1652
|
+
if (descriptor.backing === "reserved") {
|
|
1653
|
+
if ((descriptor.keys ?? []).includes(key)) return true;
|
|
1654
|
+
return getDictionary(descriptor.dimension).snapshotEntries().some((e) => e["key"] === key);
|
|
1655
|
+
}
|
|
1656
|
+
return buildLookupAltIndex(descriptor, getDictionary, getCollection).keys.has(key);
|
|
1657
|
+
}
|
|
1658
|
+
function buildLookupAltIndex(descriptor, getDictionary, getCollection) {
|
|
1659
|
+
if (descriptor.backing === "static") {
|
|
1660
|
+
return materializeBackingTable(descriptor, new Map(Object.entries(descriptor.table ?? {})));
|
|
1661
|
+
}
|
|
1662
|
+
if (descriptor.backing === "reserved") {
|
|
1663
|
+
const entries = getDictionary(descriptor.dimension).snapshotEntries();
|
|
1664
|
+
return materializeBackingTable(descriptor, new Map(entries.map((e) => [String(e["key"]), e])));
|
|
1665
|
+
}
|
|
1666
|
+
const rows = getCollection(descriptor.dimension).querySourceForJoin().snapshot();
|
|
1667
|
+
return materializeBackingTable(
|
|
1668
|
+
descriptor,
|
|
1669
|
+
new Map(
|
|
1670
|
+
rows.map((r) => {
|
|
1671
|
+
const row = r;
|
|
1672
|
+
return [String(row[descriptor.key]), row];
|
|
1673
|
+
})
|
|
1674
|
+
)
|
|
1675
|
+
);
|
|
1676
|
+
}
|
|
1677
|
+
function collectLookupRefEdges(collectionName, lookupFields) {
|
|
1678
|
+
const edges = [];
|
|
1679
|
+
for (const [field, desc] of Object.entries(lookupFields ?? {})) {
|
|
1680
|
+
if (desc.backing === "static") continue;
|
|
1681
|
+
const backing = desc.backing === "reserved" ? dictCollectionName(desc.dimension) : desc.dimension;
|
|
1682
|
+
edges.push({
|
|
1683
|
+
referencing: { collection: collectionName, field },
|
|
1684
|
+
sources: [
|
|
1685
|
+
{ collection: backing, field: "*" },
|
|
1686
|
+
...desc.present?.label !== void 0 ? [{ collection: backing, field: desc.present.label }] : []
|
|
1687
|
+
],
|
|
1688
|
+
onDelete: desc.onDelete,
|
|
1689
|
+
keyField: desc.key
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1692
|
+
return edges;
|
|
1693
|
+
}
|
|
1694
|
+
function registerLookupRefEdges(graph, collectionName, lookupFields) {
|
|
1695
|
+
for (const e of collectLookupRefEdges(collectionName, lookupFields)) {
|
|
1696
|
+
graph.registerDerived(e.referencing, e.sources, "ref", "record", e.onDelete, e.keyField);
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
function buildLookupSnapshotRows(descriptor, isReservedDimension, getDictionary, getCollection) {
|
|
1700
|
+
const dimension = descriptor.dimension;
|
|
1701
|
+
if (isReservedDimension(dimension)) {
|
|
1702
|
+
const rows = /* @__PURE__ */ new Map();
|
|
1703
|
+
for (const entry of getDictionary(dimension).snapshotEntries()) {
|
|
1704
|
+
const key = entry["key"];
|
|
1705
|
+
if (typeof key === "string") rows.set(key, entry);
|
|
1706
|
+
}
|
|
1707
|
+
return rows;
|
|
1708
|
+
}
|
|
1709
|
+
if (descriptor.backing === "collection") {
|
|
1710
|
+
const rawRows = getCollection(dimension).querySourceForJoin().snapshot();
|
|
1711
|
+
const rows = /* @__PURE__ */ new Map();
|
|
1712
|
+
for (const r of rawRows) {
|
|
1713
|
+
const row = r;
|
|
1714
|
+
rows.set(String(row[descriptor.key]), row);
|
|
1715
|
+
}
|
|
1716
|
+
return rows;
|
|
1717
|
+
}
|
|
1718
|
+
return void 0;
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
// src/shape/via-lookup/snapshot.ts
|
|
1722
|
+
function readRowField(row, field, by, locale, fallback) {
|
|
1723
|
+
if (!row || field === void 0) return void 0;
|
|
1724
|
+
const raw = row[field];
|
|
1725
|
+
if (by === void 0) return typeof raw === "string" ? raw : void 0;
|
|
1726
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
1727
|
+
const map = raw;
|
|
1728
|
+
const val = map[locale];
|
|
1729
|
+
if (typeof val === "string") return val;
|
|
1730
|
+
if (typeof fallback === "string" && typeof map[fallback] === "string") return map[fallback];
|
|
1731
|
+
return void 0;
|
|
1732
|
+
}
|
|
1733
|
+
function buildLookupSnapshot(dimension, rows, descriptor) {
|
|
1734
|
+
void dimension;
|
|
1735
|
+
return {
|
|
1736
|
+
row: (key) => rows.get(key),
|
|
1737
|
+
label: (key, locale, fallback) => readRowField(rows.get(key), descriptor.present?.label, descriptor.present?.by, locale, fallback),
|
|
1738
|
+
compareKeys: (a, b, locale) => {
|
|
1739
|
+
const sortField = descriptor.sortBy ?? descriptor.present?.label;
|
|
1740
|
+
const av = readRowField(rows.get(a), sortField, descriptor.present?.by, locale) ?? a;
|
|
1741
|
+
const bv = readRowField(rows.get(b), sortField, descriptor.present?.by, locale) ?? b;
|
|
1742
|
+
return av < bv ? -1 : av > bv ? 1 : 0;
|
|
1743
|
+
}
|
|
1744
|
+
};
|
|
1745
|
+
}
|
|
1746
|
+
function presentLookupForJoin(record, locale, lookupFields, getSnapshotRows) {
|
|
1747
|
+
let result = record;
|
|
1748
|
+
for (const [field, desc] of Object.entries(lookupFields)) {
|
|
1749
|
+
if (desc.present === void 0) continue;
|
|
1750
|
+
const raw = record[field];
|
|
1751
|
+
if (typeof raw !== "string") continue;
|
|
1752
|
+
const rows = desc.backing === "static" ? desc.table ? new Map(Object.entries(desc.table)) : void 0 : getSnapshotRows(desc);
|
|
1753
|
+
if (!rows) continue;
|
|
1754
|
+
const label = buildLookupSnapshot(desc.dimension, rows, desc).label(raw, locale);
|
|
1755
|
+
if (label === void 0) continue;
|
|
1756
|
+
if (result === record) result = { ...record };
|
|
1757
|
+
result[`${field}Label`] = label;
|
|
1758
|
+
}
|
|
1759
|
+
return result;
|
|
1760
|
+
}
|
|
1761
|
+
function buildPresentForJoin(i18nFields, lookupFields, getSnapshotRows) {
|
|
1762
|
+
const hasI18n = i18nFields !== void 0 && Object.keys(i18nFields).length > 0;
|
|
1763
|
+
const hasLookup = lookupFields !== void 0 && Object.keys(lookupFields).length > 0;
|
|
1764
|
+
if (!hasI18n && !hasLookup) return void 0;
|
|
1765
|
+
const resolveRows = getSnapshotRows ?? (() => void 0);
|
|
1766
|
+
return (record, locale) => {
|
|
1767
|
+
if (record === null || typeof record !== "object") return record;
|
|
1768
|
+
let result = record;
|
|
1769
|
+
if (hasI18n) result = presentI18nForJoin(result, i18nFields, locale);
|
|
1770
|
+
if (hasLookup) result = presentLookupForJoin(result, locale, lookupFields, resolveRows);
|
|
1771
|
+
return result;
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
// src/port/with/lookup-strategy.ts
|
|
1776
|
+
function isLookupDescriptor(x) {
|
|
1777
|
+
return typeof x === "object" && x !== null && x._viaBrand === "lookup";
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
// src/kernel/via-compose.ts
|
|
1781
|
+
function via(...descriptors) {
|
|
1782
|
+
if (descriptors.length === 0) {
|
|
1783
|
+
throw new ValidationError("via(): at least one descriptor is required, e.g. via(money({ currency: 'EUR' }))");
|
|
1784
|
+
}
|
|
1785
|
+
return { _noydbVia: true, descriptors };
|
|
1786
|
+
}
|
|
1787
|
+
function isViaFieldSpec(x) {
|
|
1788
|
+
return typeof x === "object" && x !== null && x._noydbVia === true;
|
|
1789
|
+
}
|
|
1790
|
+
function mergeViaFields(sources) {
|
|
1791
|
+
for (const field of Object.keys(sources.dictKeyFields ?? {})) {
|
|
1792
|
+
if (sources.lookupFields && field in sources.lookupFields) {
|
|
1793
|
+
throw new ValidationError(
|
|
1794
|
+
`mergeViaFields(): field "${field}" is declared in both \`dictKeyFields\` and \`lookupFields\` \u2014 declare it in one place only.`
|
|
1795
|
+
);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
if (!sources.viaFields || Object.keys(sources.viaFields).length === 0) {
|
|
1799
|
+
return {
|
|
1800
|
+
moneyFields: sources.moneyFields,
|
|
1801
|
+
i18nFields: sources.i18nFields,
|
|
1802
|
+
dictKeyFields: sources.dictKeyFields,
|
|
1803
|
+
computedFields: void 0,
|
|
1804
|
+
lookupFields: sources.lookupFields
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
const sugarFieldNames = /* @__PURE__ */ new Set([
|
|
1808
|
+
...Object.keys(sources.moneyFields ?? {}),
|
|
1809
|
+
...Object.keys(sources.i18nFields ?? {}),
|
|
1810
|
+
...Object.keys(sources.dictKeyFields ?? {}),
|
|
1811
|
+
...Object.keys(sources.lookupFields ?? {})
|
|
1812
|
+
]);
|
|
1813
|
+
const viaMoney = {};
|
|
1814
|
+
const viaI18nText = {};
|
|
1815
|
+
const viaDictKey = {};
|
|
1816
|
+
const viaComputed = {};
|
|
1817
|
+
const viaLookup = {};
|
|
1818
|
+
for (const [field, spec] of Object.entries(sources.viaFields)) {
|
|
1819
|
+
if (sugarFieldNames.has(field)) {
|
|
1820
|
+
throw new ValidationError(
|
|
1821
|
+
`via(): field "${field}" is declared via both a sugar key and \`viaFields\` \u2014 declare it in one place only.`
|
|
1822
|
+
);
|
|
1823
|
+
}
|
|
1824
|
+
for (const descriptor of spec.descriptors) {
|
|
1825
|
+
if (descriptor._viaBrand === "money") {
|
|
1826
|
+
viaMoney[field] = descriptor;
|
|
1827
|
+
} else if (descriptor._viaBrand === "i18n") {
|
|
1828
|
+
if (isI18nTextDescriptor(descriptor)) viaI18nText[field] = descriptor;
|
|
1829
|
+
else if (isDictKeyDescriptor(descriptor) || isStaticDictDescriptor(descriptor)) viaDictKey[field] = descriptor;
|
|
1830
|
+
} else if (descriptor._viaBrand === "computed") {
|
|
1831
|
+
viaComputed[field] = descriptor;
|
|
1832
|
+
} else if (descriptor._viaBrand === "lookup" && isLookupDescriptor(descriptor)) {
|
|
1833
|
+
viaLookup[field] = descriptor;
|
|
1834
|
+
} else {
|
|
1835
|
+
throw new ValidationError(`via(): field "${field}" has a descriptor with unrecognized _viaBrand "${descriptor._viaBrand}" \u2014 via() only supports money/i18n/computed/lookup descriptors today.`);
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
return {
|
|
1840
|
+
moneyFields: Object.keys(viaMoney).length > 0 ? { ...sources.moneyFields ?? {}, ...viaMoney } : sources.moneyFields,
|
|
1841
|
+
i18nFields: Object.keys(viaI18nText).length > 0 ? { ...sources.i18nFields ?? {}, ...viaI18nText } : sources.i18nFields,
|
|
1842
|
+
dictKeyFields: Object.keys(viaDictKey).length > 0 ? { ...sources.dictKeyFields ?? {}, ...viaDictKey } : sources.dictKeyFields,
|
|
1843
|
+
computedFields: Object.keys(viaComputed).length > 0 ? viaComputed : void 0,
|
|
1844
|
+
lookupFields: Object.keys(viaLookup).length > 0 ? { ...sources.lookupFields ?? {}, ...viaLookup } : sources.lookupFields
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1224
1848
|
// src/kernel/collection-config.ts
|
|
1849
|
+
function computedEntryParts(entry) {
|
|
1850
|
+
if (typeof entry === "function") return { fn: entry, mode: "materialized" };
|
|
1851
|
+
return { fn: entry.fn, ...entry.deps !== void 0 ? { deps: entry.deps } : {}, mode: entry.mode ?? "materialized" };
|
|
1852
|
+
}
|
|
1853
|
+
function unifyComputedFields(opts, viaComputedFields) {
|
|
1854
|
+
return { ...opts.computed ?? {}, ...viaComputedFields ?? {} };
|
|
1855
|
+
}
|
|
1856
|
+
function compileViaBindings(opts, classifiedGuardCtx, eraseCfgOut) {
|
|
1857
|
+
const { moneyFields, i18nFields, dictKeyFields, computedFields, lookupFields } = mergeViaFields(opts);
|
|
1858
|
+
const bindings = [];
|
|
1859
|
+
if (moneyFields) bindings.push(viaBinder("money")(moneyFields));
|
|
1860
|
+
if (i18nFields || dictKeyFields) {
|
|
1861
|
+
const densify = i18nFields ? Object.fromEntries(
|
|
1862
|
+
Object.entries(i18nFields).filter(([, d]) => d.options.densifyOnWrite === true)
|
|
1863
|
+
) : {};
|
|
1864
|
+
const i18nDensifyFields = Object.keys(densify).length > 0 ? densify : void 0;
|
|
1865
|
+
bindings.push(viaBinder("i18n")({
|
|
1866
|
+
...i18nFields !== void 0 ? { i18nFields } : {},
|
|
1867
|
+
...dictKeyFields !== void 0 ? { dictKeyFields } : {},
|
|
1868
|
+
...i18nDensifyFields !== void 0 ? { i18nDensifyFields } : {},
|
|
1869
|
+
strategy: opts.i18nStrategy ?? NO_I18N,
|
|
1870
|
+
...opts.defaultLocale !== void 0 ? { defaultLocale: opts.defaultLocale } : {},
|
|
1871
|
+
...opts.autoTranslateHook !== void 0 ? { autoTranslateHook: opts.autoTranslateHook } : {},
|
|
1872
|
+
...opts.dictLabelResolver !== void 0 ? { dictLabelResolver: opts.dictLabelResolver } : {},
|
|
1873
|
+
...opts.i18nPutValidator !== void 0 ? { i18nPutValidator: opts.i18nPutValidator } : {},
|
|
1874
|
+
collectionName: opts.name
|
|
1875
|
+
}));
|
|
1876
|
+
}
|
|
1877
|
+
if (lookupFields !== void 0) {
|
|
1878
|
+
bindings.push(viaBinder("lookup")({
|
|
1879
|
+
lookupFields,
|
|
1880
|
+
...opts.lookupLabelResolver !== void 0 ? { lookupLabelResolver: opts.lookupLabelResolver } : {},
|
|
1881
|
+
...opts.getLookupBacking !== void 0 ? { getLookupBacking: opts.getLookupBacking } : {},
|
|
1882
|
+
...opts.membership !== void 0 ? { membership: opts.membership } : {},
|
|
1883
|
+
...opts.getAltIndex !== void 0 ? { getAltIndex: opts.getAltIndex } : {},
|
|
1884
|
+
...opts.snapshotFor !== void 0 ? { snapshotFor: opts.snapshotFor } : {},
|
|
1885
|
+
collectionName: opts.name
|
|
1886
|
+
}));
|
|
1887
|
+
}
|
|
1888
|
+
if (opts.classifiedFields !== void 0) {
|
|
1889
|
+
const classifiedCfg = {
|
|
1890
|
+
entries: opts.classifiedFields,
|
|
1891
|
+
collectionName: opts.name,
|
|
1892
|
+
guardCtx: classifiedGuardCtx
|
|
1893
|
+
};
|
|
1894
|
+
if (eraseCfgOut) eraseCfgOut.classified = classifiedCfg;
|
|
1895
|
+
bindings.push(viaBinder("classified")(classifiedCfg));
|
|
1896
|
+
}
|
|
1897
|
+
if (opts.blobFields !== void 0) {
|
|
1898
|
+
bindings.push(viaBinder("blob")({
|
|
1899
|
+
fields: opts.blobFields,
|
|
1900
|
+
collectionName: opts.name
|
|
1901
|
+
}));
|
|
1902
|
+
}
|
|
1903
|
+
const virtualFields = /* @__PURE__ */ new Map();
|
|
1904
|
+
for (const [field, entry] of Object.entries(unifyComputedFields(opts, computedFields))) {
|
|
1905
|
+
const parts = computedEntryParts(entry);
|
|
1906
|
+
if (parts.mode === "virtual") {
|
|
1907
|
+
virtualFields.set(field, { _viaBrand: "computed", fn: parts.fn, mode: "virtual", ...parts.deps !== void 0 ? { deps: parts.deps } : {} });
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
if (virtualFields.size > 0) {
|
|
1911
|
+
bindings.push(viaBinder("computed")({ virtualFields }));
|
|
1912
|
+
}
|
|
1913
|
+
return bindings;
|
|
1914
|
+
}
|
|
1915
|
+
function collectKnownFieldNames(parts) {
|
|
1916
|
+
return /* @__PURE__ */ new Set([
|
|
1917
|
+
...Object.keys(parts.moneyFields ?? {}),
|
|
1918
|
+
...Object.keys(parts.i18nFields ?? {}),
|
|
1919
|
+
...Object.keys(parts.dictKeyFields ?? {}),
|
|
1920
|
+
...Object.keys(parts.classifiedFields ?? {}),
|
|
1921
|
+
...Object.keys(parts.computed ?? {}),
|
|
1922
|
+
...Object.keys(parts.lookupFields ?? {})
|
|
1923
|
+
]);
|
|
1924
|
+
}
|
|
1925
|
+
var EMPTY_KNOWN_FIELDS = /* @__PURE__ */ new Set();
|
|
1926
|
+
function resolveComputedEdges(collectionName, computed, hasClassifiedFields, knownFields = EMPTY_KNOWN_FIELDS) {
|
|
1927
|
+
if (!computed) return [];
|
|
1928
|
+
const edges = [];
|
|
1929
|
+
for (const [field, entry] of Object.entries(computed)) {
|
|
1930
|
+
const parts = computedEntryParts(entry);
|
|
1931
|
+
const deps = parts.deps;
|
|
1932
|
+
if (deps === void 0) {
|
|
1933
|
+
if (hasClassifiedFields) {
|
|
1934
|
+
throw new ValidationError(
|
|
1935
|
+
`Collection "${collectionName}": computed field "${field}" has no declared \`deps\` and the collection declares classified fields \u2014 an opaque computed function could silently copy a classified field's plaintext into an ordinary, unredacted field. Declare \`deps\` naming the source fields it reads, e.g. computed: { ${field}: { fn, deps: [...] } } or via(computed(fn, { deps: [...] })).`
|
|
1936
|
+
);
|
|
1937
|
+
}
|
|
1938
|
+
continue;
|
|
1939
|
+
}
|
|
1940
|
+
if (deps.length === 0) {
|
|
1941
|
+
throw new ValidationError(`Collection "${collectionName}": computed field "${field}"'s \`deps\` must be non-empty.`);
|
|
1942
|
+
}
|
|
1943
|
+
for (const dep of deps) {
|
|
1944
|
+
if (typeof dep !== "string" || dep.length === 0) {
|
|
1945
|
+
throw new ValidationError(`Collection "${collectionName}": computed field "${field}"'s \`deps\` entries must be non-empty strings.`);
|
|
1946
|
+
}
|
|
1947
|
+
if (hasClassifiedFields && !knownFields.has(dep)) {
|
|
1948
|
+
throw new ValidationError(
|
|
1949
|
+
`Collection "${collectionName}": computed field "${field}"'s \`deps\` entry "${dep}" does not name a declared field, and the collection declares classified fields \u2014 an opaque computed function could silently copy a classified field's plaintext into an ordinary, unredacted field via a mistyped/unknown dep name. Declare \`deps\` naming only known fields (money/i18n/dictKey/classified/computed) \u2014 check "${dep}" for a typo.`
|
|
1950
|
+
);
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
edges.push({
|
|
1954
|
+
target: { collection: collectionName, field },
|
|
1955
|
+
sources: deps.map((d) => ({ collection: collectionName, field: d })),
|
|
1956
|
+
grain: parts.mode === "virtual" ? "virtual" : "record"
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
return edges;
|
|
1960
|
+
}
|
|
1961
|
+
function resolveViaBindingDepsEdges(collectionName, bindings, knownFields) {
|
|
1962
|
+
const edges = [];
|
|
1963
|
+
for (const binding of bindings) {
|
|
1964
|
+
if (!binding.deps || binding.deps.length === 0) continue;
|
|
1965
|
+
for (const dep of binding.deps) {
|
|
1966
|
+
if (!knownFields.has(dep)) {
|
|
1967
|
+
throw new ValidationError(
|
|
1968
|
+
`Collection "${collectionName}": via binding "${binding.brand}" deps references undeclared field "${dep}".`
|
|
1969
|
+
);
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
const sources = binding.deps.map((d) => ({ collection: collectionName, field: d }));
|
|
1973
|
+
for (const field of knownFields) {
|
|
1974
|
+
if (binding.covers?.(field)) edges.push({ target: { collection: collectionName, field }, sources });
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
return edges;
|
|
1978
|
+
}
|
|
1225
1979
|
function resolveCollectionConfig(opts) {
|
|
1226
1980
|
if (opts.embeddings && opts.crdt) {
|
|
1227
1981
|
throw new Error(
|
|
1228
1982
|
`Collection "${opts.name}": embeddings are not supported on CRDT collections (L2). Use a non-CRDT collection for semantic search.`
|
|
1229
1983
|
);
|
|
1230
1984
|
}
|
|
1231
|
-
|
|
1985
|
+
const effectiveViaFields = mergeViaFields(opts);
|
|
1232
1986
|
const resolvedClassified = opts.classifiedFields !== void 0 ? resolveClassifiedFields(opts.name, opts.classifiedFields) : void 0;
|
|
1233
|
-
|
|
1987
|
+
const allComputed = unifyComputedFields(opts, effectiveViaFields.computedFields);
|
|
1988
|
+
const materializedComputed = {};
|
|
1989
|
+
for (const [field, entry] of Object.entries(allComputed)) {
|
|
1990
|
+
if (computedEntryParts(entry).mode !== "virtual") materializedComputed[field] = entry;
|
|
1991
|
+
}
|
|
1992
|
+
let mergedComputed = Object.keys(materializedComputed).length > 0 ? materializedComputed : void 0;
|
|
1234
1993
|
if (resolvedClassified !== void 0) {
|
|
1235
|
-
for (const key of Object.keys(
|
|
1994
|
+
for (const key of Object.keys(allComputed)) {
|
|
1236
1995
|
if (resolvedClassified.riderComputed[key] !== void 0) {
|
|
1237
1996
|
throw new ClassifiedConfigError(opts.name, `computed field "${key}" collides with a rider companion`);
|
|
1238
1997
|
}
|
|
1239
1998
|
}
|
|
1240
|
-
mergedComputed = { ...resolvedClassified.riderComputed, ...
|
|
1999
|
+
mergedComputed = { ...resolvedClassified.riderComputed, ...materializedComputed };
|
|
1241
2000
|
}
|
|
1242
2001
|
let deterministicFields;
|
|
1243
2002
|
if (opts.deterministicFields && opts.deterministicFields.length > 0) {
|
|
@@ -1250,10 +2009,6 @@ function resolveCollectionConfig(opts) {
|
|
|
1250
2009
|
} else {
|
|
1251
2010
|
deterministicFields = null;
|
|
1252
2011
|
}
|
|
1253
|
-
const densifyFields = opts.i18nFields ? Object.fromEntries(
|
|
1254
|
-
Object.entries(opts.i18nFields).filter(([, d]) => d.options.densifyOnWrite === true)
|
|
1255
|
-
) : {};
|
|
1256
|
-
const i18nDensifyFields = Object.keys(densifyFields).length > 0 ? densifyFields : void 0;
|
|
1257
2012
|
const guardIndexedFields = /* @__PURE__ */ new Set();
|
|
1258
2013
|
for (const ix of opts.indexes ?? []) {
|
|
1259
2014
|
if (typeof ix === "string") guardIndexedFields.add(ix);
|
|
@@ -1289,6 +2044,18 @@ function resolveCollectionConfig(opts) {
|
|
|
1289
2044
|
const vdigFields = vdigEntries.length > 0 ? new Map(vdigEntries) : null;
|
|
1290
2045
|
const perRecordCek = opts.perRecordKeys === true;
|
|
1291
2046
|
const cekCache = perRecordCek ? new Lru({ maxRecords: 4096 }) : null;
|
|
2047
|
+
const viaEraseCfgOut = {};
|
|
2048
|
+
const via2 = ViaPipeline.build(compileViaBindings(opts, classifiedGuardCtx, viaEraseCfgOut));
|
|
2049
|
+
const knownFields = collectKnownFieldNames({
|
|
2050
|
+
moneyFields: effectiveViaFields.moneyFields,
|
|
2051
|
+
i18nFields: effectiveViaFields.i18nFields,
|
|
2052
|
+
dictKeyFields: effectiveViaFields.dictKeyFields,
|
|
2053
|
+
classifiedFields: resolvedClassified?.byField,
|
|
2054
|
+
computed: allComputed,
|
|
2055
|
+
lookupFields: effectiveViaFields.lookupFields
|
|
2056
|
+
});
|
|
2057
|
+
const computedEdges = resolveComputedEdges(opts.name, allComputed, resolvedClassified !== void 0, knownFields);
|
|
2058
|
+
const viaDepsEdges = resolveViaBindingDepsEdges(opts.name, via2?.bindings ?? [], knownFields);
|
|
1292
2059
|
return {
|
|
1293
2060
|
adapter: opts.adapter,
|
|
1294
2061
|
vault: opts.vault,
|
|
@@ -1320,20 +2087,34 @@ function resolveCollectionConfig(opts) {
|
|
|
1320
2087
|
ledger: opts.ledger,
|
|
1321
2088
|
refEnforcer: opts.refEnforcer,
|
|
1322
2089
|
joinResolver: opts.joinResolver,
|
|
1323
|
-
i18nFields:
|
|
2090
|
+
i18nFields: effectiveViaFields.i18nFields,
|
|
1324
2091
|
textIndexes: opts.textIndexes,
|
|
1325
|
-
i18nDensifyFields,
|
|
1326
2092
|
embeddings: opts.embeddings,
|
|
1327
2093
|
vectorSet: opts.embeddings ? new VectorSet() : void 0,
|
|
1328
|
-
dictKeyFields:
|
|
2094
|
+
dictKeyFields: effectiveViaFields.dictKeyFields,
|
|
2095
|
+
lookupFields: effectiveViaFields.lookupFields,
|
|
2096
|
+
// #650 Task 6 — the sync join-dressing hook (#626 retirement, spec §5);
|
|
2097
|
+
// `undefined` when this collection declares neither i18nText nor lookup
|
|
2098
|
+
// fields, matching today's `i18nFields`-absent JoinableSource shape.
|
|
2099
|
+
presentForJoin: buildPresentForJoin(effectiveViaFields.i18nFields, effectiveViaFields.lookupFields, opts.snapshotFor),
|
|
1329
2100
|
fieldMeta: opts.fieldMeta,
|
|
1330
2101
|
meta: opts.meta,
|
|
1331
2102
|
_refs: opts.declaredRefs ?? {},
|
|
1332
|
-
|
|
2103
|
+
via: via2,
|
|
2104
|
+
classifiedEraseCfg: viaEraseCfgOut.classified,
|
|
2105
|
+
moneyFields: effectiveViaFields.moneyFields,
|
|
1333
2106
|
classified: resolvedClassified,
|
|
1334
2107
|
classifiedGuardCtx,
|
|
1335
2108
|
classifiedStrategy: opts.classifiedStrategy ?? NO_CLASSIFIED,
|
|
1336
2109
|
computed: mergedComputed,
|
|
2110
|
+
computedEdges,
|
|
2111
|
+
viaDepsEdges,
|
|
2112
|
+
// #638 Task 7 — every declared computed field name (both modes, both surfaces); the
|
|
2113
|
+
// reconcile path's cross-call depsless-computed leak-guard memory
|
|
2114
|
+
// (`via-graph-wiring.ts#registerCollectionGraphSources`) reads this instead of
|
|
2115
|
+
// `Object.keys(opts.computed ?? {})` alone, so a via(computed(...))-declared field
|
|
2116
|
+
// participates in that guard identically to a sugar-declared one.
|
|
2117
|
+
computedFieldNames: Object.keys(allComputed),
|
|
1337
2118
|
dictLabelResolver: opts.dictLabelResolver,
|
|
1338
2119
|
getDictionary: opts.getDictionary,
|
|
1339
2120
|
i18nPutValidator: opts.i18nPutValidator,
|
|
@@ -1344,6 +2125,7 @@ function resolveCollectionConfig(opts) {
|
|
|
1344
2125
|
onAccess: opts.onAccess,
|
|
1345
2126
|
derivationSource: opts.derivationSource,
|
|
1346
2127
|
materializedViewSource: opts.materializedViewSource,
|
|
2128
|
+
graphDispatch: opts.graphDispatch,
|
|
1347
2129
|
tiers: opts.tiers && opts.tiers.length > 0 ? new Set(opts.tiers) : null,
|
|
1348
2130
|
tiersStrategy: opts.tiersStrategy ?? NO_TIERS,
|
|
1349
2131
|
searchStrategy: opts.searchStrategy ?? NO_SEARCH,
|
|
@@ -1361,7 +2143,7 @@ function resolveCollectionConfig(opts) {
|
|
|
1361
2143
|
// src/with-formula/computed/lazy.ts
|
|
1362
2144
|
var fn = null;
|
|
1363
2145
|
async function loadEvalComputedFields() {
|
|
1364
|
-
fn ??= (await import("./computed-
|
|
2146
|
+
fn ??= (await import("./computed-R62HGCO4.js")).evalComputedFields;
|
|
1365
2147
|
return fn;
|
|
1366
2148
|
}
|
|
1367
2149
|
|
|
@@ -1420,7 +2202,6 @@ var Collection = class {
|
|
|
1420
2202
|
tiersStrategy;
|
|
1421
2203
|
searchStrategy;
|
|
1422
2204
|
historyStrategy;
|
|
1423
|
-
i18nStrategy;
|
|
1424
2205
|
syncStrategy;
|
|
1425
2206
|
// In-memory cache of decrypted records (eager mode only). Lazy mode
|
|
1426
2207
|
// uses `lru` instead. Both fields exist so a single Collection instance
|
|
@@ -1522,70 +2303,38 @@ var Collection = class {
|
|
|
1522
2303
|
* throw `LocaleNotSpecifiedError` unless a per-call locale is passed.
|
|
1523
2304
|
*/
|
|
1524
2305
|
defaultLocale;
|
|
1525
|
-
/**
|
|
1526
|
-
*
|
|
1527
|
-
*
|
|
1528
|
-
* - `put()` via `i18nPutValidator` to enforce required translations
|
|
1529
|
-
* - `get()`/`list()` to apply locale resolution after decryption
|
|
1530
|
-
*
|
|
1531
|
-
* Declared via the `i18nFields` collection option.
|
|
1532
|
-
*/
|
|
2306
|
+
/** Field name → `I18nTextDescriptor` for `i18nText()` fields (`i18nFields` option); write/read
|
|
2307
|
+
* runs through the compiled `via` i18n binding — this remains for `describe()`
|
|
2308
|
+
* and the search-index build path. */
|
|
1533
2309
|
i18nFields;
|
|
1534
|
-
/**
|
|
1535
|
-
* The configured string fields exposed to `retrieve()`. `undefined`
|
|
1536
|
-
* for ordinary collections, so the search path costs nothing when unused.
|
|
1537
|
-
*/
|
|
2310
|
+
/** The configured string fields exposed to `retrieve()`; `undefined` for ordinary collections (zero-cost). */
|
|
1538
2311
|
textIndexes;
|
|
1539
|
-
/**
|
|
1540
|
-
* The session-scoped lexical index store. `undefined` (so the dirty
|
|
1541
|
-
* poke + retrieve are zero-cost) unless `textIndexes` is non-empty.
|
|
1542
|
-
*/
|
|
2312
|
+
/** Session-scoped lexical index store; `undefined` (zero-cost) unless `textIndexes` is non-empty. */
|
|
1543
2313
|
searchIndexStore;
|
|
1544
|
-
/**
|
|
1545
|
-
*
|
|
1546
|
-
* descriptor opts in via `densifyOnWrite: true`). `undefined` when none opt
|
|
1547
|
-
* in, so the write path skips all densify work for ordinary collections.
|
|
1548
|
-
*/
|
|
1549
|
-
i18nDensifyFields;
|
|
1550
|
-
/**
|
|
1551
|
-
* Embedding config for write-time vector derivation. `undefined`
|
|
1552
|
-
* for ordinary collections (zero cost). When set, `put()` encodes the
|
|
1553
|
-
* source field(s) and stores an encrypted `_vec` sidecar.
|
|
1554
|
-
*/
|
|
2314
|
+
/** Embedding config for write-time vector derivation; `undefined` (zero-cost) for ordinary
|
|
2315
|
+
* collections. When set, `put()` encodes the source field(s) and stores an encrypted `_vec` sidecar. */
|
|
1555
2316
|
embeddings;
|
|
1556
|
-
/**
|
|
1557
|
-
* In-memory vector set, populated lazily from `_vec` sidecars.
|
|
1558
|
-
* `undefined` when no embedding config is declared.
|
|
1559
|
-
*/
|
|
2317
|
+
/** In-memory vector set, populated lazily from `_vec` sidecars; `undefined` when no embedding config is declared. */
|
|
1560
2318
|
vectorSet;
|
|
1561
|
-
/**
|
|
1562
|
-
*
|
|
1563
|
-
* `dictKey()`. Used by `get()`/`list()` to add `<field>Label` virtual
|
|
1564
|
-
* fields when a locale is requested.
|
|
1565
|
-
*/
|
|
2319
|
+
/** Field name → `DictKeyDescriptor` for `dictKey()` fields; used by `get()`/`list()` to add
|
|
2320
|
+
* `<field>Label` virtual fields when a locale is requested. */
|
|
1566
2321
|
dictKeyFields;
|
|
1567
|
-
/**
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
2322
|
+
/** Field name → `LookupDescriptor` for native `lookup()`/`enumOf()`/`dict()` fields (#650 Task 2) — describe()-only in this task. */
|
|
2323
|
+
lookupFields;
|
|
2324
|
+
/** Sync join-dressing hook (#650 Task 6, #626 retirement) — `querySourceForJoin()`'s `presentForJoin`. */
|
|
2325
|
+
presentForJoin;
|
|
2326
|
+
/** Consumer-neutral per-field descriptors declared via `fieldMeta`; read by `getFieldMeta()`, merged by `describe()`. */
|
|
1571
2327
|
fieldMeta;
|
|
1572
|
-
/**
|
|
1573
|
-
* Collection-level descriptive metadata declared via the `meta` collection
|
|
1574
|
-
* option. Read by `getMeta()`; surfaced in `collection.describe()`.
|
|
1575
|
-
*/
|
|
2328
|
+
/** Collection-level descriptive metadata declared via `meta`; read by `getMeta()`, surfaced in `describe()`. */
|
|
1576
2329
|
meta;
|
|
1577
|
-
/**
|
|
1578
|
-
* Outbound ref declarations for this collection (snapshot from vault
|
|
1579
|
-
* refRegistry at construction time). Used by `describe()` (sync, config-only).
|
|
1580
|
-
*/
|
|
2330
|
+
/** Outbound ref declarations (snapshot from vault refRegistry at construction time); used by `describe()`. */
|
|
1581
2331
|
_refs;
|
|
1582
|
-
/**
|
|
1583
|
-
*
|
|
1584
|
-
*
|
|
1585
|
-
* string, `get()`/`list()` decode back. Mutable so {@link _applyMoneyFields}
|
|
1586
|
-
* can attach descriptors to a collection MV-analysis pre-created.
|
|
1587
|
-
*/
|
|
2332
|
+
/** Money field descriptors keyed by field path, typed as the opaque {@link ViaDescriptor} marker
|
|
2333
|
+
* (the kernel never inspects the concrete shape); `put()` quantizes to a scaled-int string,
|
|
2334
|
+
* `get()`/`list()` decode back. Mutable so {@link _applyMoneyFields} can attach. */
|
|
1588
2335
|
moneyFields;
|
|
2336
|
+
via;
|
|
2337
|
+
// compiled Via pipeline (money, i18n); rebuilt by {@link _applyMoneyFields}
|
|
1589
2338
|
/**
|
|
1590
2339
|
* Computed scalar fields, evaluated first on every `put()`. Mutable for
|
|
1591
2340
|
* the same MV-pre-creation reconcile as {@link moneyFields}.
|
|
@@ -1620,27 +2369,12 @@ var Collection = class {
|
|
|
1620
2369
|
* `createNoydb()` option (opt in with `withClassified()`).
|
|
1621
2370
|
*/
|
|
1622
2371
|
classifiedStrategy;
|
|
1623
|
-
/**
|
|
1624
|
-
* Async callback provided by the Vault that resolves a dict key
|
|
1625
|
-
* to its label for a given locale. Used by the locale-read path for
|
|
1626
|
-
* dictKey fields.
|
|
1627
|
-
*
|
|
1628
|
-
* Signature: `(dictName, key, locale, fallback?) => Promise<string | undefined>`
|
|
1629
|
-
*/
|
|
1630
|
-
dictLabelResolver;
|
|
1631
2372
|
/**
|
|
1632
2373
|
* Async callback provided by the Vault to open a dynamic
|
|
1633
2374
|
* dictionary handle (for label-map pre-computation in the search index).
|
|
1634
2375
|
* Only used in `resolveDictLabelMaps()`; static dicts bypass this entirely.
|
|
1635
2376
|
*/
|
|
1636
2377
|
getDictionary;
|
|
1637
|
-
/**
|
|
1638
|
-
* Synchronous callback provided by the Vault that validates
|
|
1639
|
-
* i18nText fields on `put()`. Throws `MissingTranslationError` when
|
|
1640
|
-
* a required translation is absent. Called after schema validation,
|
|
1641
|
-
* before encryption.
|
|
1642
|
-
*/
|
|
1643
|
-
i18nPutValidator;
|
|
1644
2378
|
/**
|
|
1645
2379
|
* declared deterministic fields. `null` when the feature
|
|
1646
2380
|
* is inactive for this collection; a frozen `Set` otherwise.
|
|
@@ -1695,14 +2429,6 @@ var Collection = class {
|
|
|
1695
2429
|
tiers;
|
|
1696
2430
|
tierMode;
|
|
1697
2431
|
onCrossTierAccess;
|
|
1698
|
-
/**
|
|
1699
|
-
* Async translator callback provided by Noydb via Vault for
|
|
1700
|
-
* `i18nText` fields with `autoTranslate: true`. Called
|
|
1701
|
-
* before i18n validation so translated values are present when the
|
|
1702
|
-
* validator runs. `undefined` when no `plaintextTranslator` was
|
|
1703
|
-
* configured on `createNoydb()`.
|
|
1704
|
-
*/
|
|
1705
|
-
autoTranslateHook;
|
|
1706
2432
|
/**
|
|
1707
2433
|
* Optional reference to the vault-level hash-chained audit
|
|
1708
2434
|
* log. When present, every successful `put()` and `delete()` appends
|
|
@@ -1740,6 +2466,8 @@ var Collection = class {
|
|
|
1740
2466
|
* commits + after `dispatchDerivations` has run.
|
|
1741
2467
|
*/
|
|
1742
2468
|
materializedViewSource;
|
|
2469
|
+
/** #638 Task 4 — `Vault._collectGraphTouch`; a no-op absent an open sync/cutover/restore batch. */
|
|
2470
|
+
graphDispatch;
|
|
1743
2471
|
/**
|
|
1744
2472
|
* Optional back-reference to the owning compartment's ref
|
|
1745
2473
|
* enforcer. When present, `Collection.put` calls
|
|
@@ -1802,7 +2530,6 @@ var Collection = class {
|
|
|
1802
2530
|
this.tiersStrategy = cfg.tiersStrategy;
|
|
1803
2531
|
this.searchStrategy = cfg.searchStrategy;
|
|
1804
2532
|
this.historyStrategy = cfg.historyStrategy;
|
|
1805
|
-
this.i18nStrategy = cfg.i18nStrategy;
|
|
1806
2533
|
this.syncStrategy = cfg.syncStrategy;
|
|
1807
2534
|
this.reconcileOnOpen = cfg.reconcileOnOpen;
|
|
1808
2535
|
this.getDEK = cfg.getDEK;
|
|
@@ -1816,29 +2543,29 @@ var Collection = class {
|
|
|
1816
2543
|
this.i18nFields = cfg.i18nFields;
|
|
1817
2544
|
this.textIndexes = cfg.textIndexes;
|
|
1818
2545
|
this.searchIndexStore = opts.textIndexes && opts.textIndexes.length > 0 ? opts.textIndexPersist ? new PersistedIndexStore(buildPersistedIndexCallbacks(() => this.searchContext())) : new MemoryIndexStore() : void 0;
|
|
1819
|
-
this.i18nDensifyFields = cfg.i18nDensifyFields;
|
|
1820
2546
|
this.embeddings = cfg.embeddings;
|
|
1821
2547
|
this.vectorSet = cfg.vectorSet;
|
|
1822
2548
|
this.dictKeyFields = cfg.dictKeyFields;
|
|
2549
|
+
this.lookupFields = cfg.lookupFields;
|
|
2550
|
+
this.presentForJoin = cfg.presentForJoin;
|
|
1823
2551
|
this.fieldMeta = cfg.fieldMeta;
|
|
1824
2552
|
this.meta = cfg.meta;
|
|
1825
2553
|
this._refs = cfg._refs;
|
|
1826
2554
|
this.moneyFields = cfg.moneyFields;
|
|
2555
|
+
this.via = cfg.via;
|
|
1827
2556
|
this.classified = cfg.classified;
|
|
1828
2557
|
this.classifiedGuardCtx = cfg.classifiedGuardCtx;
|
|
1829
2558
|
this.vdigFields = cfg.vdigFields;
|
|
1830
2559
|
this.classifiedStrategy = cfg.classifiedStrategy;
|
|
1831
2560
|
this.computed = cfg.computed;
|
|
1832
|
-
this.dictLabelResolver = cfg.dictLabelResolver;
|
|
1833
2561
|
this.getDictionary = cfg.getDictionary;
|
|
1834
|
-
this.i18nPutValidator = cfg.i18nPutValidator;
|
|
1835
|
-
this.autoTranslateHook = cfg.autoTranslateHook;
|
|
1836
2562
|
this.defaultLocale = cfg.defaultLocale;
|
|
1837
2563
|
this.crdtMode = cfg.crdtMode;
|
|
1838
2564
|
this.syncAdapter = cfg.syncAdapter;
|
|
1839
2565
|
this.onAccess = cfg.onAccess;
|
|
1840
2566
|
this.derivationSource = cfg.derivationSource;
|
|
1841
2567
|
this.materializedViewSource = cfg.materializedViewSource;
|
|
2568
|
+
this.graphDispatch = cfg.graphDispatch;
|
|
1842
2569
|
this.tiers = cfg.tiers;
|
|
1843
2570
|
this.tierMode = cfg.tierMode;
|
|
1844
2571
|
this.onCrossTierAccess = cfg.onCrossTierAccess;
|
|
@@ -1878,10 +2605,12 @@ var Collection = class {
|
|
|
1878
2605
|
crdtMode: this.crdtMode,
|
|
1879
2606
|
crdtStrategy: this.crdtStrategy,
|
|
1880
2607
|
schema: this.schema,
|
|
1881
|
-
getDEK: () => this.getDEK(this.name),
|
|
2608
|
+
getDEK: (collection) => this.getDEK(collection ?? this.name),
|
|
1882
2609
|
cekCache: this.cekCache,
|
|
1883
|
-
classifiedMarkerDigestOnly: () => this._classifiedMarkerDigestOnly()
|
|
2610
|
+
classifiedMarkerDigestOnly: () => this._classifiedMarkerDigestOnly(),
|
|
2611
|
+
via: this.via
|
|
1884
2612
|
});
|
|
2613
|
+
if (cfg.classifiedEraseCfg) cfg.classifiedEraseCfg.classifySealedShred = (live) => this.codec.classifySealedShred(live);
|
|
1885
2614
|
if (opts.crdt && opts.onRegisterConflictResolver) {
|
|
1886
2615
|
const crdtMode = opts.crdt;
|
|
1887
2616
|
const crdtResolver = async (id, local, remote) => {
|
|
@@ -1945,7 +2674,7 @@ var Collection = class {
|
|
|
1945
2674
|
const merged = mergeFn(localRecord, remoteRecord);
|
|
1946
2675
|
const mergedVersion = Math.max(local._v, remote._v) + 1;
|
|
1947
2676
|
const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
|
|
1948
|
-
return this.codec.encryptRecord(merged, mergedVersion, cek);
|
|
2677
|
+
return this.codec.encryptRecord(merged, mergedVersion, cek, void 0, void 0, void 0, id);
|
|
1949
2678
|
};
|
|
1950
2679
|
}
|
|
1951
2680
|
opts.onRegisterConflictResolver(collectionName, resolver);
|
|
@@ -2035,35 +2764,41 @@ var Collection = class {
|
|
|
2035
2764
|
fieldMeta: this.fieldMeta,
|
|
2036
2765
|
moneyFields: this.moneyFields,
|
|
2037
2766
|
dictKeyFields: this.dictKeyFields,
|
|
2767
|
+
...this.lookupFields !== void 0 ? { lookupFields: this.lookupFields } : {},
|
|
2038
2768
|
computed: this.computed,
|
|
2039
2769
|
refs: this._refs,
|
|
2040
2770
|
zodFields: void 0,
|
|
2041
2771
|
...this.meta !== void 0 ? { meta: this.meta } : {},
|
|
2042
2772
|
...this.i18nFields !== void 0 ? { i18nFields: this.i18nFields } : {},
|
|
2043
|
-
...this.classified !== void 0 ? { classified: this.classified.byField } : {}
|
|
2773
|
+
...this.classified !== void 0 ? { classified: this.classified.byField } : {},
|
|
2774
|
+
...this.via?.taint !== void 0 ? { taint: this.via.taint } : {},
|
|
2775
|
+
...this.via ? { viaFragments: this.via.describeFragments() } : {}
|
|
2776
|
+
// #650 Task 7
|
|
2044
2777
|
});
|
|
2045
2778
|
}
|
|
2046
2779
|
/**
|
|
2047
|
-
* Async describe implementation.
|
|
2048
|
-
*
|
|
2049
|
-
*
|
|
2050
|
-
* then delegates to buildDescription which also runs fieldMeta key-validation.
|
|
2780
|
+
* Async describe implementation. Derives validator-exact types via deriveZodFields
|
|
2781
|
+
* (lazy, no static zod import), optionally resolves dynamic-dict labels from vault.dictionary(name).list(),
|
|
2782
|
+
* then delegates to buildDescription (which also runs fieldMeta key-validation).
|
|
2051
2783
|
*/
|
|
2052
2784
|
async describeAsync(opts) {
|
|
2053
2785
|
const zodFields = this.schema !== void 0 ? await deriveZodFields(this.schema) : void 0;
|
|
2054
2786
|
let dictLabels;
|
|
2055
|
-
if (opts.resolveDictLabels === true && this.
|
|
2056
|
-
|
|
2057
|
-
for (const
|
|
2058
|
-
|
|
2059
|
-
|
|
2787
|
+
if (opts.resolveDictLabels === true && this.getDictionary !== void 0) {
|
|
2788
|
+
const names = /* @__PURE__ */ new Set();
|
|
2789
|
+
for (const desc of Object.values(this.dictKeyFields ?? {})) if (!isStaticDictDescriptor(desc)) names.add(desc.name);
|
|
2790
|
+
for (const desc of Object.values(this.lookupFields ?? {})) if (desc.backing === "reserved") names.add(desc.dimension);
|
|
2791
|
+
if (names.size > 0) {
|
|
2792
|
+
dictLabels = {};
|
|
2793
|
+
for (const name of names) {
|
|
2794
|
+
const handle = await this.getDictionary(name);
|
|
2060
2795
|
const entries = await handle.list();
|
|
2061
2796
|
const valueToLabel = {};
|
|
2062
2797
|
for (const entry of entries) {
|
|
2063
2798
|
const label = Object.values(entry.labels)[0];
|
|
2064
2799
|
if (label !== void 0) valueToLabel[entry.key] = label;
|
|
2065
2800
|
}
|
|
2066
|
-
dictLabels[
|
|
2801
|
+
dictLabels[name] = valueToLabel;
|
|
2067
2802
|
}
|
|
2068
2803
|
}
|
|
2069
2804
|
}
|
|
@@ -2072,13 +2807,17 @@ var Collection = class {
|
|
|
2072
2807
|
fieldMeta: this.fieldMeta,
|
|
2073
2808
|
moneyFields: this.moneyFields,
|
|
2074
2809
|
dictKeyFields: this.dictKeyFields,
|
|
2810
|
+
...this.lookupFields !== void 0 ? { lookupFields: this.lookupFields } : {},
|
|
2075
2811
|
computed: this.computed,
|
|
2076
2812
|
refs: this._refs,
|
|
2077
2813
|
zodFields,
|
|
2078
2814
|
...dictLabels !== void 0 ? { dictLabels } : {},
|
|
2079
2815
|
...this.meta !== void 0 ? { meta: this.meta } : {},
|
|
2080
2816
|
...this.i18nFields !== void 0 ? { i18nFields: this.i18nFields } : {},
|
|
2081
|
-
...this.classified !== void 0 ? { classified: this.classified.byField } : {}
|
|
2817
|
+
...this.classified !== void 0 ? { classified: this.classified.byField } : {},
|
|
2818
|
+
...this.via?.taint !== void 0 ? { taint: this.via.taint } : {},
|
|
2819
|
+
...this.via ? { viaFragments: this.via.describeFragments() } : {}
|
|
2820
|
+
// #650 Task 7
|
|
2082
2821
|
});
|
|
2083
2822
|
}
|
|
2084
2823
|
/** JSON Schema for this collection with describe() metadata as x- extensions. */
|
|
@@ -2199,7 +2938,7 @@ var Collection = class {
|
|
|
2199
2938
|
}
|
|
2200
2939
|
await this.onAccess?.("find", "*");
|
|
2201
2940
|
if (hits.length === 0) return [];
|
|
2202
|
-
const { verifyDigestField } = await import("./verify-
|
|
2941
|
+
const { verifyDigestField } = await import("./verify-GH6CWVSW.js");
|
|
2203
2942
|
const policy = {
|
|
2204
2943
|
normalize: spec.verifyNormalize ?? "password",
|
|
2205
2944
|
notLastN: spec.notLastN ?? 0,
|
|
@@ -2286,10 +3025,17 @@ var Collection = class {
|
|
|
2286
3025
|
* analysis auto-creates a source collection (without options) during
|
|
2287
3026
|
* `openVault`, before the user's `collection(name, { moneyFields })`
|
|
2288
3027
|
* declaration; this reconciles that ordering. First-wins. Not public.
|
|
3028
|
+
*
|
|
3029
|
+
* PREPENDS money rather than appending: {@link compileViaBindings} always
|
|
3030
|
+
* compiles money before i18n (money-first pipeline order — see its
|
|
3031
|
+
* docstring), and by the time this reconcile runs an i18n binding may
|
|
3032
|
+
* already be sitting in `this.via.bindings` (declared at construction).
|
|
3033
|
+
* Appending here would yield `[i18n, money]` on this path vs `[money,
|
|
3034
|
+
* i18n]` from compile — prepending keeps both paths money-first.
|
|
2289
3035
|
*/
|
|
2290
3036
|
_applyMoneyFields(moneyFields) {
|
|
2291
3037
|
if (this.moneyFields !== void 0) return;
|
|
2292
|
-
|
|
3038
|
+
this.via = ViaPipeline.build([viaBinder("money")(moneyFields), ...this.via?.bindings ?? []]);
|
|
2293
3039
|
this.moneyFields = moneyFields;
|
|
2294
3040
|
}
|
|
2295
3041
|
/** @internal — attach computed fields post-construction. See {@link _applyMoneyFields}. */
|
|
@@ -2349,6 +3095,13 @@ var Collection = class {
|
|
|
2349
3095
|
}
|
|
2350
3096
|
this.classified = resolved;
|
|
2351
3097
|
this.computed = { ...resolved.riderComputed, ...this.computed ?? {} };
|
|
3098
|
+
this.via = ViaPipeline.build([...this.via?.bindings ?? [], viaBinder("classified")({
|
|
3099
|
+
entries: classifiedFields,
|
|
3100
|
+
collectionName: this.name,
|
|
3101
|
+
guardCtx: this.classifiedGuardCtx,
|
|
3102
|
+
classifySealedShred: (live) => this.codec.classifySealedShred(live)
|
|
3103
|
+
// #629 T10
|
|
3104
|
+
})]);
|
|
2352
3105
|
}
|
|
2353
3106
|
/** @internal — used only in tests; do not read in production code. */
|
|
2354
3107
|
get _ramCiphertext() {
|
|
@@ -2375,7 +3128,7 @@ var Collection = class {
|
|
|
2375
3128
|
}
|
|
2376
3129
|
}
|
|
2377
3130
|
if (this.materializedViewSource !== void 0) {
|
|
2378
|
-
const { resolveStaleMVOnRead } = await import("./stale-
|
|
3131
|
+
const { resolveStaleMVOnRead } = await import("./stale-IYYXEH3U.js");
|
|
2379
3132
|
await resolveStaleMVOnRead(this.materializedViewSource, this.name);
|
|
2380
3133
|
}
|
|
2381
3134
|
let record;
|
|
@@ -2531,7 +3284,7 @@ var Collection = class {
|
|
|
2531
3284
|
if (cached) return cached.record;
|
|
2532
3285
|
const env = await this.adapter.get(this.vault, this.name, id);
|
|
2533
3286
|
if (!env) return void 0;
|
|
2534
|
-
const rec = await this.codec.decryptRecord(env);
|
|
3287
|
+
const rec = await this.codec.decryptRecord(env, { id });
|
|
2535
3288
|
return rec === null ? void 0 : rec;
|
|
2536
3289
|
}
|
|
2537
3290
|
await this.ensureHydrated();
|
|
@@ -2579,7 +3332,7 @@ var Collection = class {
|
|
|
2579
3332
|
if (cached2) return { record: cached2.record, version: cached2.version };
|
|
2580
3333
|
const env = await this.adapter.get(this.vault, this.name, id);
|
|
2581
3334
|
if (!env) return { record: null, version: 0 };
|
|
2582
|
-
return { record: await this.codec.decryptRecord(env, { skipValidation: true }) ?? null, version: env._v };
|
|
3335
|
+
return { record: await this.codec.decryptRecord(env, { skipValidation: true, id }) ?? null, version: env._v };
|
|
2583
3336
|
}
|
|
2584
3337
|
await this.ensureHydrated();
|
|
2585
3338
|
const cached = this.cache.get(id);
|
|
@@ -2613,7 +3366,7 @@ var Collection = class {
|
|
|
2613
3366
|
const env = await this.adapter.get(this.vault, this.name, id);
|
|
2614
3367
|
if (!env) return { env: null, record: null, elided: false };
|
|
2615
3368
|
try {
|
|
2616
|
-
return { env, record: await this.codec.decryptRecord(env, { skipValidation: true }), elided: false };
|
|
3369
|
+
return { env, record: await this.codec.decryptRecord(env, { skipValidation: true, id }), elided: false };
|
|
2617
3370
|
} catch {
|
|
2618
3371
|
return { env, record: null, elided: false };
|
|
2619
3372
|
}
|
|
@@ -2638,7 +3391,7 @@ var Collection = class {
|
|
|
2638
3391
|
if (!hasWritePermission(this.keyring, this.name)) {
|
|
2639
3392
|
throw new ReadOnlyError();
|
|
2640
3393
|
}
|
|
2641
|
-
if (this.
|
|
3394
|
+
if (this.via) record = this.via.ingest(record);
|
|
2642
3395
|
if (this.subsystemBus?.hasGateHandlers("beforePut")) {
|
|
2643
3396
|
const { env: existingEnv, record: existingRecord } = await this.resolveGatePrior("beforePut", id);
|
|
2644
3397
|
const gateEvent = {
|
|
@@ -2647,7 +3400,7 @@ var Collection = class {
|
|
|
2647
3400
|
collection: this.name,
|
|
2648
3401
|
docId: id,
|
|
2649
3402
|
incoming: record,
|
|
2650
|
-
existing: this.
|
|
3403
|
+
existing: this.via ? this.via.canonicalizeStored(existingRecord) : existingRecord,
|
|
2651
3404
|
existingVersion: existingEnv?._v ?? 0,
|
|
2652
3405
|
existingTs: existingEnv?._ts,
|
|
2653
3406
|
userId: this.keyring.userId,
|
|
@@ -2656,99 +3409,21 @@ var Collection = class {
|
|
|
2656
3409
|
};
|
|
2657
3410
|
await this.subsystemBus.dispatchGate("beforePut", gateEvent);
|
|
2658
3411
|
}
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
3412
|
+
const viaWriteCtx = {
|
|
3413
|
+
id,
|
|
3414
|
+
vault: this.vault,
|
|
3415
|
+
prior: async () => await this.resolveDensifyPrior(id) ?? null,
|
|
3416
|
+
emit: (e, p) => this.emitter.emit(e, p)
|
|
3417
|
+
};
|
|
3418
|
+
if (this.via) await this.via.enforceWrite(record, viaWriteCtx);
|
|
2662
3419
|
if (this.computed !== void 0) {
|
|
2663
3420
|
record = (await loadEvalComputedFields())(record, this.computed, id);
|
|
2664
3421
|
}
|
|
2665
3422
|
if (this.schema !== void 0) {
|
|
2666
3423
|
record = await validateSchemaInput(this.schema, record, `put(${id})`);
|
|
2667
3424
|
}
|
|
2668
|
-
if (this.
|
|
2669
|
-
record =
|
|
2670
|
-
}
|
|
2671
|
-
if (this.i18nFields) {
|
|
2672
|
-
const obj = record;
|
|
2673
|
-
for (const [field, descriptor] of Object.entries(this.i18nFields)) {
|
|
2674
|
-
if (!descriptor.options.autoTranslate) continue;
|
|
2675
|
-
const leafValues = getAtPath(obj, field);
|
|
2676
|
-
if (leafValues.length !== 1) continue;
|
|
2677
|
-
const value = leafValues[0];
|
|
2678
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
2679
|
-
const map = value;
|
|
2680
|
-
const { languages, required } = descriptor.options;
|
|
2681
|
-
const missing = languages.filter(
|
|
2682
|
-
(lang) => !(lang in map) || map[lang] === ""
|
|
2683
|
-
);
|
|
2684
|
-
if (missing.length === 0) continue;
|
|
2685
|
-
const sourceLocale = languages.find((l) => l in map && map[l] !== "");
|
|
2686
|
-
if (!sourceLocale) continue;
|
|
2687
|
-
if (!this.autoTranslateHook) {
|
|
2688
|
-
throw new TranslatorNotConfiguredError(field, this.name);
|
|
2689
|
-
}
|
|
2690
|
-
const toTranslate = required === "any" ? [] : required === "all" ? missing : missing.filter((l) => required.includes(l));
|
|
2691
|
-
const translated = { ...map };
|
|
2692
|
-
for (const targetLocale of toTranslate) {
|
|
2693
|
-
translated[targetLocale] = await this.autoTranslateHook(
|
|
2694
|
-
map[sourceLocale],
|
|
2695
|
-
sourceLocale,
|
|
2696
|
-
targetLocale,
|
|
2697
|
-
field,
|
|
2698
|
-
this.name
|
|
2699
|
-
);
|
|
2700
|
-
}
|
|
2701
|
-
setAtPathInPlace(obj, field, translated);
|
|
2702
|
-
}
|
|
2703
|
-
}
|
|
2704
|
-
let densifyPrior;
|
|
2705
|
-
let exemptFills;
|
|
2706
|
-
if (this.i18nDensifyFields) {
|
|
2707
|
-
densifyPrior = await this.resolveDensifyPrior(id);
|
|
2708
|
-
exemptFills = this.i18nStrategy.computeExemptFills(
|
|
2709
|
-
densifyPrior,
|
|
2710
|
-
record,
|
|
2711
|
-
this.i18nDensifyFields
|
|
2712
|
-
);
|
|
2713
|
-
}
|
|
2714
|
-
if (this.i18nFields) {
|
|
2715
|
-
const obj = record;
|
|
2716
|
-
for (const [field, descriptor] of Object.entries(this.i18nFields)) {
|
|
2717
|
-
if (!descriptor.options.script) continue;
|
|
2718
|
-
for (const leaf of getAtPath(obj, field)) {
|
|
2719
|
-
if (!leaf || typeof leaf !== "object" || Array.isArray(leaf)) continue;
|
|
2720
|
-
const leafMap = leaf;
|
|
2721
|
-
const { value: cleaned, warnings } = this.i18nStrategy.enforceScript(
|
|
2722
|
-
leafMap,
|
|
2723
|
-
field,
|
|
2724
|
-
descriptor,
|
|
2725
|
-
exemptFills?.get(field)
|
|
2726
|
-
);
|
|
2727
|
-
if (cleaned !== leafMap) Object.assign(leafMap, cleaned);
|
|
2728
|
-
const mode = descriptor.options.onScriptViolation;
|
|
2729
|
-
if (mode === "warn" || mode === "filter") {
|
|
2730
|
-
for (const w of warnings) {
|
|
2731
|
-
this.emitter.emit("i18n:script-violation", {
|
|
2732
|
-
vault: this.vault,
|
|
2733
|
-
collection: this.name,
|
|
2734
|
-
id,
|
|
2735
|
-
mode,
|
|
2736
|
-
warning: w
|
|
2737
|
-
});
|
|
2738
|
-
}
|
|
2739
|
-
}
|
|
2740
|
-
}
|
|
2741
|
-
}
|
|
2742
|
-
}
|
|
2743
|
-
if (this.i18nPutValidator !== void 0) {
|
|
2744
|
-
this.i18nPutValidator(record);
|
|
2745
|
-
}
|
|
2746
|
-
if (this.i18nDensifyFields) {
|
|
2747
|
-
this.i18nStrategy.densify(
|
|
2748
|
-
record,
|
|
2749
|
-
densifyPrior,
|
|
2750
|
-
this.i18nDensifyFields
|
|
2751
|
-
);
|
|
3425
|
+
if (this.via) {
|
|
3426
|
+
record = await this.via.encodeWrite(record, viaWriteCtx);
|
|
2752
3427
|
}
|
|
2753
3428
|
if (this.refEnforcer !== void 0) {
|
|
2754
3429
|
await this.refEnforcer.enforceRefsOnPut(this.name, record);
|
|
@@ -2795,7 +3470,7 @@ var Collection = class {
|
|
|
2795
3470
|
const existingResolved = existingResolvedRecord !== null ? { record: existingResolvedRecord, version: existingVersion } : void 0;
|
|
2796
3471
|
if (existingResolved && this.historyConfig.enabled !== false) {
|
|
2797
3472
|
const vdigCtx2 = this.vdigFields !== null ? { id, prev: existingEnvelope } : void 0;
|
|
2798
|
-
const histEnvelope = await this.codec.encryptRecord(existingResolved.record, existingResolved.version, cek2, void 0, void 0, vdigCtx2);
|
|
3473
|
+
const histEnvelope = await this.codec.encryptRecord(existingResolved.record, existingResolved.version, cek2, void 0, void 0, vdigCtx2, id);
|
|
2799
3474
|
await this.historyStrategy.saveHistory(this.adapter, this.vault, this.name, id, histEnvelope);
|
|
2800
3475
|
this.emitter.emit("history:save", { vault: this.vault, collection: this.name, id, version: existingResolved.version });
|
|
2801
3476
|
if (this.historyConfig.maxVersions) {
|
|
@@ -2827,12 +3502,7 @@ var Collection = class {
|
|
|
2827
3502
|
this.cache.set(id, { record: resolvedRecord, version: version2 });
|
|
2828
3503
|
this.indexes?.upsert(id, resolvedRecord, existingResolved ? existingResolved.record : null);
|
|
2829
3504
|
}
|
|
2830
|
-
await this.
|
|
2831
|
-
this.emitter.emit("change", { vault: this.vault, collection: this.name, id, action: "put" });
|
|
2832
|
-
this.searchIndexStore?.markDirty();
|
|
2833
|
-
await this.onAccess?.("put", id);
|
|
2834
|
-
await this.dispatchDerivations(id, record, version2);
|
|
2835
|
-
await this.dispatchMaterializedViews(id, record);
|
|
3505
|
+
await this._onRecordMutated(id, "put", "local-write", { record, version: version2 });
|
|
2836
3506
|
return;
|
|
2837
3507
|
}
|
|
2838
3508
|
let existing;
|
|
@@ -2842,7 +3512,7 @@ var Collection = class {
|
|
|
2842
3512
|
if (!existing) {
|
|
2843
3513
|
priorRaw = await this.adapter.get(this.vault, this.name, id);
|
|
2844
3514
|
if (priorRaw) {
|
|
2845
|
-
const previousRecord = await this.codec.decryptRecord(priorRaw);
|
|
3515
|
+
const previousRecord = await this.codec.decryptRecord(priorRaw, { id });
|
|
2846
3516
|
if (previousRecord !== null) {
|
|
2847
3517
|
existing = { record: previousRecord, version: priorRaw._v };
|
|
2848
3518
|
}
|
|
@@ -2861,7 +3531,7 @@ var Collection = class {
|
|
|
2861
3531
|
const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
|
|
2862
3532
|
const vdigCtx = this.vdigFields !== null ? { id, prev: await this.adapter.get(this.vault, this.name, id) } : void 0;
|
|
2863
3533
|
if (existing && this.historyConfig.enabled !== false) {
|
|
2864
|
-
const historyEnvelope = await this.codec.encryptRecord(existing.record, existing.version, cek, void 0, void 0, vdigCtx);
|
|
3534
|
+
const historyEnvelope = await this.codec.encryptRecord(existing.record, existing.version, cek, void 0, void 0, vdigCtx, id);
|
|
2865
3535
|
await this.historyStrategy.saveHistory(this.adapter, this.vault, this.name, id, historyEnvelope);
|
|
2866
3536
|
this.emitter.emit("history:save", {
|
|
2867
3537
|
vault: this.vault,
|
|
@@ -2875,7 +3545,7 @@ var Collection = class {
|
|
|
2875
3545
|
});
|
|
2876
3546
|
}
|
|
2877
3547
|
}
|
|
2878
|
-
const envelope = await this.codec.encryptRecord(record, version, cek, options?.source, options?.sourceTs, vdigCtx);
|
|
3548
|
+
const envelope = await this.codec.encryptRecord(record, version, cek, options?.source, options?.sourceTs, vdigCtx, id);
|
|
2879
3549
|
await this.adapter.put(this.vault, this.name, id, envelope);
|
|
2880
3550
|
await this._ensureClassifiedMarker();
|
|
2881
3551
|
if (this.embeddings) {
|
|
@@ -2904,17 +3574,7 @@ var Collection = class {
|
|
|
2904
3574
|
this.indexes?.upsert(id, record, existing ? existing.record : null);
|
|
2905
3575
|
this.uniqueConstraints?.upsert(id, record, existing?.record);
|
|
2906
3576
|
}
|
|
2907
|
-
await this.
|
|
2908
|
-
this.emitter.emit("change", {
|
|
2909
|
-
vault: this.vault,
|
|
2910
|
-
collection: this.name,
|
|
2911
|
-
id,
|
|
2912
|
-
action: "put"
|
|
2913
|
-
});
|
|
2914
|
-
this.searchIndexStore?.markDirty();
|
|
2915
|
-
await this.onAccess?.("put", id);
|
|
2916
|
-
await this.dispatchDerivations(id, record, version);
|
|
2917
|
-
await this.dispatchMaterializedViews(id, record);
|
|
3577
|
+
await this._onRecordMutated(id, "put", "local-write", { record, version });
|
|
2918
3578
|
}
|
|
2919
3579
|
/**
|
|
2920
3580
|
* Fire registered MV strategies whose dependency set includes this
|
|
@@ -2926,10 +3586,10 @@ var Collection = class {
|
|
|
2926
3586
|
* MV-emitted row (carries `_materializedFrom`) — defensive guard
|
|
2927
3587
|
* against missed cycle detection.
|
|
2928
3588
|
*
|
|
2929
|
-
* @internal
|
|
3589
|
+
* @internal `wave` (#638 Task 4): when present (the sync/cutover/restore dispatch wave),
|
|
3590
|
+
* an eager MV already refreshed this wave is skipped (per-target dedup, keyed on spec name).
|
|
2930
3591
|
*/
|
|
2931
|
-
async dispatchMaterializedViews(id, record) {
|
|
2932
|
-
void id;
|
|
3592
|
+
async dispatchMaterializedViews(id, record, wave) {
|
|
2933
3593
|
if (this.materializedViewSource === void 0) return;
|
|
2934
3594
|
const incoming = record;
|
|
2935
3595
|
if (incoming && typeof incoming === "object" && "_materializedFrom" in incoming) return;
|
|
@@ -2941,18 +3601,20 @@ var Collection = class {
|
|
|
2941
3601
|
for (const reg of mvs) {
|
|
2942
3602
|
const mode = reg.spec.refresh;
|
|
2943
3603
|
if (mode === "eager") {
|
|
3604
|
+
if (wave?.seen(`mv\0${reg.spec.name}`)) continue;
|
|
2944
3605
|
if (executor === null) {
|
|
2945
3606
|
;
|
|
2946
|
-
({ MaterializedViewExecutor: executor } = await import("./executor-
|
|
3607
|
+
({ MaterializedViewExecutor: executor } = await import("./executor-IL7SRNUZ.js"));
|
|
2947
3608
|
}
|
|
2948
3609
|
await executor.refresh(reg, {
|
|
2949
3610
|
getCollection: (name) => this.materializedViewSource.getCollection(name),
|
|
2950
3611
|
getActiveTxContext: () => this.materializedViewSource.getActiveTxContext(),
|
|
2951
|
-
getQueryContext: () => this.materializedViewSource.getQueryContext()
|
|
3612
|
+
getQueryContext: () => this.materializedViewSource.getQueryContext(),
|
|
3613
|
+
dispatchCtx: this.#dispatchCtx({ collection: this.name, id })
|
|
2952
3614
|
});
|
|
2953
3615
|
} else if (mode === "lazy") {
|
|
2954
3616
|
if (staleHelpers === null) {
|
|
2955
|
-
staleHelpers = await import("./stale-
|
|
3617
|
+
staleHelpers = await import("./stale-IYYXEH3U.js");
|
|
2956
3618
|
}
|
|
2957
3619
|
staleHelpers.markMVStale(registry, reg.spec.name);
|
|
2958
3620
|
}
|
|
@@ -3004,7 +3666,15 @@ var Collection = class {
|
|
|
3004
3666
|
raw = (await this.resolvePriorValues(id))?.record ?? null;
|
|
3005
3667
|
}
|
|
3006
3668
|
if (raw === null) return null;
|
|
3007
|
-
return this.
|
|
3669
|
+
return this.via ? this.via.canonicalizeStored(raw) : raw;
|
|
3670
|
+
}
|
|
3671
|
+
/** @internal #638 Task 4 — decrypted STORED-form record + envelope version for the sync/cutover/
|
|
3672
|
+
* restore dispatch wave (id threaded into decrypt, matching `_invalidateCacheEntry`'s contract). */
|
|
3673
|
+
async _getStoredRecordForDispatch(id) {
|
|
3674
|
+
const env = await this.adapter.get(this.vault, this.name, id);
|
|
3675
|
+
if (!env || isTombstone(env, this.storeCiphertext) || isDeleteMarker(env)) return null;
|
|
3676
|
+
const record = await this.codec.decryptRecord(env, { id });
|
|
3677
|
+
return record === null ? null : { record, version: env._v };
|
|
3008
3678
|
}
|
|
3009
3679
|
/**
|
|
3010
3680
|
* @internal Ids of records whose top-level `field` equals `value`.
|
|
@@ -3035,19 +3705,22 @@ var Collection = class {
|
|
|
3035
3705
|
}
|
|
3036
3706
|
return out;
|
|
3037
3707
|
}
|
|
3038
|
-
/**
|
|
3039
|
-
*
|
|
3040
|
-
*
|
|
3041
|
-
*
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3708
|
+
/** @internal Recompute a rollup aggregate onto the parent from `parentId`'s current children
|
|
3709
|
+
* (value-equality guarded; no-op absent parent). `wave` (#638 T4): per-target dedup. Returns
|
|
3710
|
+
* the `putDerivedOutput` outcome, or `'noop'` when nothing needed recomputing (#638 T6 —
|
|
3711
|
+
* `dispatchRollupsOnDelete`'s forget-fanout caller needs this to fill the forget report). */
|
|
3712
|
+
/** @internal — ctx for `putDerivedOutput`'s frozen-period skip+audit (#638 Task 5). */
|
|
3713
|
+
#dispatchCtx(source) {
|
|
3714
|
+
return { emit: (e, p) => this.emitter.emit(e, p), source, audit: ledgerAuditHook(this.ledger, this.keyring.userId) };
|
|
3715
|
+
}
|
|
3716
|
+
async recomputeRollup(spec, parentId, source, wave) {
|
|
3717
|
+
if (this.derivationSource === void 0 || spec.rollup === void 0) return "noop";
|
|
3046
3718
|
const { from, key, field, compute } = spec.rollup;
|
|
3047
3719
|
const into = spec.source;
|
|
3720
|
+
if (wave?.seen(`rollup\0${into}\0${parentId}\0${field}`)) return "noop";
|
|
3048
3721
|
const intoColl = this.derivationSource.getCollection(into);
|
|
3049
3722
|
const base = await intoColl._getStoredRecord(parentId);
|
|
3050
|
-
if (base === null) return;
|
|
3723
|
+
if (base === null) return "noop";
|
|
3051
3724
|
const fromColl = this.derivationSource.getCollection(from);
|
|
3052
3725
|
const childIds = await fromColl._findMatchingIds(key, parentId);
|
|
3053
3726
|
const children = [];
|
|
@@ -3056,7 +3729,7 @@ var Collection = class {
|
|
|
3056
3729
|
if (c !== null && c !== void 0) children.push(c);
|
|
3057
3730
|
}
|
|
3058
3731
|
const newValue = compute(children);
|
|
3059
|
-
if (selfWriteFieldEqual(base[field], newValue)) return;
|
|
3732
|
+
if (selfWriteFieldEqual(base[field], newValue)) return "noop";
|
|
3060
3733
|
const patched = { ...base, [field]: newValue };
|
|
3061
3734
|
const txCtx = this.derivationSource.getActiveTxContext();
|
|
3062
3735
|
if (txCtx !== null) {
|
|
@@ -3066,28 +3739,32 @@ var Collection = class {
|
|
|
3066
3739
|
priorEnvelope: prior
|
|
3067
3740
|
});
|
|
3068
3741
|
}
|
|
3069
|
-
|
|
3742
|
+
return putDerivedOutput(intoColl, parentId, patched, this.#dispatchCtx(source));
|
|
3070
3743
|
}
|
|
3071
3744
|
/**
|
|
3072
|
-
* @internal Fire any rollups for which THIS collection is the
|
|
3073
|
-
*
|
|
3074
|
-
* from
|
|
3075
|
-
*
|
|
3745
|
+
* @internal Fire any rollups for which THIS collection is the child `from`, recomputing the
|
|
3746
|
+
* affected parent after a child delete/forget. Called from the delete path (return discarded)
|
|
3747
|
+
* and from `forgetDerivedFanout` (#638 Task 6), which needs the per-target outcome to fill
|
|
3748
|
+
* `ForgetResult.derivedAggregatesRecomputed`/`derivedResidueFrozen`.
|
|
3076
3749
|
*/
|
|
3077
|
-
async dispatchRollupsOnDelete(deleted) {
|
|
3078
|
-
if (this.derivationSource === void 0) return;
|
|
3750
|
+
async dispatchRollupsOnDelete(id, deleted) {
|
|
3751
|
+
if (this.derivationSource === void 0) return [];
|
|
3079
3752
|
const registry = this.derivationSource.registry();
|
|
3080
3753
|
const rec = deleted;
|
|
3754
|
+
const results = [];
|
|
3081
3755
|
for (const { spec } of registry.strategiesForSource(this.name)) {
|
|
3082
3756
|
if (!spec.rollup || spec.rollup.from !== this.name) continue;
|
|
3083
3757
|
const kv = rec[spec.rollup.key];
|
|
3084
3758
|
if (typeof kv !== "string" && typeof kv !== "number") continue;
|
|
3085
|
-
await this.recomputeRollup(spec, String(kv));
|
|
3759
|
+
results.push({ into: spec.source, parentId: String(kv), outcome: await this.recomputeRollup(spec, String(kv), { collection: this.name, id }) });
|
|
3086
3760
|
}
|
|
3761
|
+
return results;
|
|
3087
3762
|
}
|
|
3088
|
-
|
|
3763
|
+
/** @internal `wave` (#638 Task 4) — threaded to `recomputeRollup` for the sync/cutover/restore
|
|
3764
|
+
* dispatch wave's per-target dedup; `undefined` on the local-write path (byte-identical). */
|
|
3765
|
+
async dispatchDerivations(id, record, version, wave) {
|
|
3089
3766
|
if (this.derivationSource === void 0) return;
|
|
3090
|
-
const incoming = this.
|
|
3767
|
+
const incoming = this.via ? this.via.canonicalizeStored(record) : record;
|
|
3091
3768
|
if (incoming && typeof incoming === "object" && "_derivedFrom" in incoming) return;
|
|
3092
3769
|
const registry = this.derivationSource.registry();
|
|
3093
3770
|
const strategies = registry.strategiesForSource(this.name);
|
|
@@ -3104,7 +3781,7 @@ var Collection = class {
|
|
|
3104
3781
|
} else {
|
|
3105
3782
|
parentId = id;
|
|
3106
3783
|
}
|
|
3107
|
-
if (parentId !== null) await this.recomputeRollup(spec, parentId);
|
|
3784
|
+
if (parentId !== null) await this.recomputeRollup(spec, parentId, { collection: this.name, id }, wave);
|
|
3108
3785
|
continue;
|
|
3109
3786
|
}
|
|
3110
3787
|
const isSource = spec.source === this.name;
|
|
@@ -3137,10 +3814,11 @@ var Collection = class {
|
|
|
3137
3814
|
continue;
|
|
3138
3815
|
}
|
|
3139
3816
|
if (DerivationExecutor === null) {
|
|
3140
|
-
({ DerivationExecutor } = await import("./executor-
|
|
3817
|
+
({ DerivationExecutor } = await import("./executor-WAEI6PDV.js"));
|
|
3141
3818
|
}
|
|
3142
3819
|
for (const run of runs) {
|
|
3143
3820
|
const ctx = { vault: this.derivationSource.getReadOnlyFacade() };
|
|
3821
|
+
const outCtx = this.#dispatchCtx({ collection: spec.source, id: run.runId });
|
|
3144
3822
|
const result = await DerivationExecutor.run(spec, run.input, run.version, strategyHash, ctx);
|
|
3145
3823
|
for (const key of Object.keys(spec.outputs)) {
|
|
3146
3824
|
const out = result.outputs[key];
|
|
@@ -3156,7 +3834,7 @@ var Collection = class {
|
|
|
3156
3834
|
const outputCollection = this.derivationSource.getCollection(outSpec.collection);
|
|
3157
3835
|
const txCtx = this.derivationSource.getActiveTxContext();
|
|
3158
3836
|
if (out.kind === "array") {
|
|
3159
|
-
const { loadFanoutSidecar, saveFanoutSidecar } = await import("./fanout-sidecar-
|
|
3837
|
+
const { loadFanoutSidecar, saveFanoutSidecar } = await import("./fanout-sidecar-CI5BCMQM.js");
|
|
3160
3838
|
const prior = await loadFanoutSidecar(this.adapter, this.vault, spec.source, run.runId, key, this.getDEK, this.storeCiphertext);
|
|
3161
3839
|
const prevKeys = new Set(prior?.keys ?? []);
|
|
3162
3840
|
const newKeysList = out.entries.map((e) => e.key);
|
|
@@ -3178,7 +3856,7 @@ var Collection = class {
|
|
|
3178
3856
|
priorEnvelope
|
|
3179
3857
|
});
|
|
3180
3858
|
}
|
|
3181
|
-
await outputCollection
|
|
3859
|
+
await putDerivedOutput(outputCollection, entry.key, entry.value, outCtx, { source: "derived" });
|
|
3182
3860
|
}
|
|
3183
3861
|
await saveFanoutSidecar(this.adapter, this.vault, {
|
|
3184
3862
|
source: spec.source,
|
|
@@ -3211,7 +3889,7 @@ var Collection = class {
|
|
|
3211
3889
|
priorEnvelope: prior
|
|
3212
3890
|
});
|
|
3213
3891
|
}
|
|
3214
|
-
await outputCollection
|
|
3892
|
+
await putDerivedOutput(outputCollection, run.runId, patched, outCtx, { source: "derived" });
|
|
3215
3893
|
continue;
|
|
3216
3894
|
}
|
|
3217
3895
|
if (txCtx !== null) {
|
|
@@ -3226,7 +3904,7 @@ var Collection = class {
|
|
|
3226
3904
|
priorEnvelope: prior
|
|
3227
3905
|
});
|
|
3228
3906
|
}
|
|
3229
|
-
await outputCollection
|
|
3907
|
+
await putDerivedOutput(outputCollection, run.runId, out.value, outCtx, { source: "derived" });
|
|
3230
3908
|
}
|
|
3231
3909
|
}
|
|
3232
3910
|
}
|
|
@@ -3283,9 +3961,9 @@ var Collection = class {
|
|
|
3283
3961
|
const next = transform(record);
|
|
3284
3962
|
const nextVersion = (env._v ?? 0) + 1;
|
|
3285
3963
|
const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
|
|
3286
|
-
const newEnv = await this.codec.encryptRecord(next, nextVersion, cek, void 0, void 0, this.vdigFields !== null ? { id, prev: env } : void 0);
|
|
3964
|
+
const newEnv = await this.codec.encryptRecord(next, nextVersion, cek, void 0, void 0, this.vdigFields !== null ? { id, prev: env } : void 0, id);
|
|
3287
3965
|
await this.adapter.put(this.vault, this.name, id, newEnv);
|
|
3288
|
-
await this.
|
|
3966
|
+
await this._onRecordMutated(id, "put", "cutover");
|
|
3289
3967
|
if (this.ledger) {
|
|
3290
3968
|
await this.ledger.append({
|
|
3291
3969
|
op: "migration",
|
|
@@ -3314,9 +3992,8 @@ var Collection = class {
|
|
|
3314
3992
|
* history snapshot still fire so backup integrity and time-travel
|
|
3315
3993
|
* reconstruction stay consistent.
|
|
3316
3994
|
*
|
|
3317
|
-
* Returns
|
|
3318
|
-
*
|
|
3319
|
-
* envelope and short-circuits before the ledger/event side-effects).
|
|
3995
|
+
* Returns `true` when it erased a live record, `false` for delete-of-absent (idempotent
|
|
3996
|
+
* contract — both txCtx-aware and txCtx===null callers honour this, short-circuiting before any side-effect).
|
|
3320
3997
|
*
|
|
3321
3998
|
* When a `txCtx` is supplied, the prior envelope is captured and
|
|
3322
3999
|
* pushed onto `txCtx._executed` BEFORE the delete fires — mirrors
|
|
@@ -3345,7 +4022,7 @@ var Collection = class {
|
|
|
3345
4022
|
*/
|
|
3346
4023
|
async _internalDelete(id, txCtx = null) {
|
|
3347
4024
|
const prior = await this.adapter.get(this.vault, this.name, id);
|
|
3348
|
-
if (prior === null) return;
|
|
4025
|
+
if (prior === null) return false;
|
|
3349
4026
|
if (txCtx !== null) {
|
|
3350
4027
|
txCtx._executed.push({
|
|
3351
4028
|
op: {
|
|
@@ -3358,6 +4035,7 @@ var Collection = class {
|
|
|
3358
4035
|
});
|
|
3359
4036
|
}
|
|
3360
4037
|
await this._doDelete(id, true);
|
|
4038
|
+
return true;
|
|
3361
4039
|
}
|
|
3362
4040
|
async _doDelete(id, internal) {
|
|
3363
4041
|
if (!hasWritePermission(this.keyring, this.name)) {
|
|
@@ -3370,7 +4048,7 @@ var Collection = class {
|
|
|
3370
4048
|
vault: this.vault,
|
|
3371
4049
|
collection: this.name,
|
|
3372
4050
|
docId: id,
|
|
3373
|
-
existing: this.
|
|
4051
|
+
existing: this.via ? this.via.canonicalizeStored(existingRecord) : existingRecord,
|
|
3374
4052
|
existingVersion: existingEnv?._v ?? 0,
|
|
3375
4053
|
existingTs: existingEnv?._ts,
|
|
3376
4054
|
internal,
|
|
@@ -3388,7 +4066,7 @@ var Collection = class {
|
|
|
3388
4066
|
if (!existing && this.historyConfig.enabled !== false) {
|
|
3389
4067
|
const previousEnvelope2 = await this.adapter.get(this.vault, this.name, id);
|
|
3390
4068
|
if (previousEnvelope2) {
|
|
3391
|
-
const previousRecord = await this.codec.decryptRecord(previousEnvelope2);
|
|
4069
|
+
const previousRecord = await this.codec.decryptRecord(previousEnvelope2, { id });
|
|
3392
4070
|
if (previousRecord !== null) {
|
|
3393
4071
|
existing = { record: previousRecord, version: previousEnvelope2._v };
|
|
3394
4072
|
}
|
|
@@ -3400,7 +4078,7 @@ var Collection = class {
|
|
|
3400
4078
|
if (existing && this.historyConfig.enabled !== false) {
|
|
3401
4079
|
const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
|
|
3402
4080
|
const prevForVdig = this.vdigFields !== null ? await this.adapter.get(this.vault, this.name, id) : null;
|
|
3403
|
-
const historyEnvelope = await this.codec.encryptRecord(existing.record, existing.version, cek, void 0, void 0, this.vdigFields !== null ? { id, prev: prevForVdig } : void 0);
|
|
4081
|
+
const historyEnvelope = await this.codec.encryptRecord(existing.record, existing.version, cek, void 0, void 0, this.vdigFields !== null ? { id, prev: prevForVdig } : void 0, id);
|
|
3404
4082
|
await this.historyStrategy.saveHistory(this.adapter, this.vault, this.name, id, historyEnvelope);
|
|
3405
4083
|
}
|
|
3406
4084
|
const previousEnvelope = await this.adapter.get(this.vault, this.name, id);
|
|
@@ -3436,19 +4114,11 @@ var Collection = class {
|
|
|
3436
4114
|
this.uniqueConstraints?.remove(id, existing.record);
|
|
3437
4115
|
}
|
|
3438
4116
|
}
|
|
3439
|
-
await this.
|
|
3440
|
-
this.emitter.emit("change", {
|
|
3441
|
-
vault: this.vault,
|
|
3442
|
-
collection: this.name,
|
|
3443
|
-
id,
|
|
3444
|
-
action: "delete"
|
|
3445
|
-
});
|
|
3446
|
-
this.searchIndexStore?.markDirty();
|
|
3447
|
-
await this.onAccess?.("delete", id);
|
|
4117
|
+
await this._onRecordMutated(id, "delete", "local-delete", { version: markerVersion ?? (existing?.version ?? 0) + 1 });
|
|
3448
4118
|
if (!internal) {
|
|
3449
4119
|
await this.dispatchMaterializedViewsOnDelete(id);
|
|
3450
4120
|
await this.dispatchArrayDerivationsOnDelete(id);
|
|
3451
|
-
if (existing) await this.dispatchRollupsOnDelete(existing.record);
|
|
4121
|
+
if (existing) await this.dispatchRollupsOnDelete(id, existing.record);
|
|
3452
4122
|
}
|
|
3453
4123
|
}
|
|
3454
4124
|
/**
|
|
@@ -3497,75 +4167,81 @@ var Collection = class {
|
|
|
3497
4167
|
return { previousVersion: live._v };
|
|
3498
4168
|
}
|
|
3499
4169
|
/**
|
|
3500
|
-
* Cascade deletes of array-shape derived rows when a source row is
|
|
3501
|
-
*
|
|
3502
|
-
*
|
|
3503
|
-
* deletes the sidecar itself.
|
|
3504
|
-
*
|
|
3505
|
-
* Record-shape derivations are skipped — see _doDelete's comment
|
|
3506
|
-
* for why the asymmetry is correct.
|
|
4170
|
+
* Cascade deletes of array-shape derived rows when a source row is deleted. Reads each
|
|
4171
|
+
* registered strategy's fanout sidecar for this source id, deletes every listed derived
|
|
4172
|
+
* row, then deletes the sidecar itself. Returns the REAL erased count (#622 review Finding 1).
|
|
3507
4173
|
*
|
|
4174
|
+
* Record-shape derivations are skipped on the ordinary delete path (see _doDelete's comment).
|
|
4175
|
+
* `eraseRecordShapeToo` (#638 T6, default `false`) opts a same-id record-shape copy into
|
|
4176
|
+
* erasure too — forget()'s fanout, GDPR residue. A delete-of-absent contributes 0 either way.
|
|
3508
4177
|
* @internal
|
|
3509
4178
|
*/
|
|
3510
|
-
async dispatchArrayDerivationsOnDelete(id) {
|
|
3511
|
-
if (this.derivationSource === void 0) return;
|
|
4179
|
+
async dispatchArrayDerivationsOnDelete(id, eraseRecordShapeToo = false) {
|
|
4180
|
+
if (this.derivationSource === void 0) return 0;
|
|
3512
4181
|
const registry = this.derivationSource.registry();
|
|
3513
4182
|
const strategies = registry.strategiesForSource(this.name);
|
|
3514
|
-
if (strategies.length === 0) return;
|
|
4183
|
+
if (strategies.length === 0) return 0;
|
|
3515
4184
|
let helpers = null;
|
|
3516
4185
|
const txCtx = this.derivationSource.getActiveTxContext();
|
|
4186
|
+
let erased = 0;
|
|
3517
4187
|
for (const { spec } of strategies) {
|
|
3518
4188
|
for (const [outputKey, outSpec] of Object.entries(spec.outputs)) {
|
|
3519
|
-
if (outSpec.shape
|
|
4189
|
+
if (outSpec.shape === "record") {
|
|
4190
|
+
if (eraseRecordShapeToo && spec.source === this.name && outSpec.collection !== this.name) {
|
|
4191
|
+
if (await this.derivationSource.getCollection(outSpec.collection)._internalDelete(id, txCtx)) erased += 1;
|
|
4192
|
+
}
|
|
4193
|
+
continue;
|
|
4194
|
+
}
|
|
3520
4195
|
if (helpers === null) {
|
|
3521
|
-
helpers = await import("./fanout-sidecar-
|
|
4196
|
+
helpers = await import("./fanout-sidecar-CI5BCMQM.js");
|
|
3522
4197
|
}
|
|
3523
4198
|
const sidecar = await helpers.loadFanoutSidecar(this.adapter, this.vault, spec.source, id, outputKey, this.getDEK, this.storeCiphertext);
|
|
3524
4199
|
if (!sidecar) continue;
|
|
3525
4200
|
const outputCollection = this.derivationSource.getCollection(outSpec.collection);
|
|
3526
4201
|
for (const derivedId of sidecar.keys) {
|
|
3527
|
-
await outputCollection._internalDelete(derivedId, txCtx);
|
|
4202
|
+
if (await outputCollection._internalDelete(derivedId, txCtx)) erased += 1;
|
|
3528
4203
|
}
|
|
3529
4204
|
await helpers.deleteFanoutSidecar(this.adapter, this.vault, spec.source, id, outputKey);
|
|
3530
4205
|
}
|
|
3531
4206
|
}
|
|
4207
|
+
return erased;
|
|
3532
4208
|
}
|
|
3533
4209
|
/**
|
|
3534
|
-
* Mirror of {@link dispatchMaterializedViews} for the delete path
|
|
3535
|
-
*
|
|
3536
|
-
*
|
|
3537
|
-
*
|
|
3538
|
-
* at the `_doDelete` call site above.
|
|
3539
|
-
*
|
|
4210
|
+
* Mirror of {@link dispatchMaterializedViews} for the delete/forget path — no `_materializedFrom`
|
|
4211
|
+
* skip (record's gone); the `internal` gate at `_doDelete` is the recursion guard. Returns the
|
|
4212
|
+
* row count TOMBSTONED across every eager MV sourced here (#638 T6 — `forgetDerivedFanout`'s
|
|
4213
|
+
* `derivedRecordsErased`); a lazy MV only flips its stale bit (0 contribution — resolved on read).
|
|
3540
4214
|
* @internal
|
|
3541
4215
|
*/
|
|
3542
4216
|
async dispatchMaterializedViewsOnDelete(id) {
|
|
3543
|
-
void
|
|
3544
|
-
if (this.materializedViewSource === void 0) return;
|
|
4217
|
+
if (this.materializedViewSource === void 0) return 0;
|
|
3545
4218
|
const registry = this.materializedViewSource.registry();
|
|
3546
4219
|
const mvs = registry.mvsForSource(this.name);
|
|
3547
|
-
if (mvs.length === 0) return;
|
|
4220
|
+
if (mvs.length === 0) return 0;
|
|
3548
4221
|
let executor = null;
|
|
3549
4222
|
let staleHelpers = null;
|
|
4223
|
+
let deleted = 0;
|
|
3550
4224
|
for (const reg of mvs) {
|
|
3551
4225
|
const mode = reg.spec.refresh;
|
|
3552
4226
|
if (mode === "eager") {
|
|
3553
4227
|
if (executor === null) {
|
|
3554
4228
|
;
|
|
3555
|
-
({ MaterializedViewExecutor: executor } = await import("./executor-
|
|
4229
|
+
({ MaterializedViewExecutor: executor } = await import("./executor-IL7SRNUZ.js"));
|
|
3556
4230
|
}
|
|
3557
|
-
await executor.refresh(reg, {
|
|
4231
|
+
deleted += (await executor.refresh(reg, {
|
|
3558
4232
|
getCollection: (name) => this.materializedViewSource.getCollection(name),
|
|
3559
4233
|
getActiveTxContext: () => this.materializedViewSource.getActiveTxContext(),
|
|
3560
|
-
getQueryContext: () => this.materializedViewSource.getQueryContext()
|
|
3561
|
-
|
|
4234
|
+
getQueryContext: () => this.materializedViewSource.getQueryContext(),
|
|
4235
|
+
dispatchCtx: this.#dispatchCtx({ collection: this.name, id })
|
|
4236
|
+
})).deleted;
|
|
3562
4237
|
} else if (mode === "lazy") {
|
|
3563
4238
|
if (staleHelpers === null) {
|
|
3564
|
-
staleHelpers = await import("./stale-
|
|
4239
|
+
staleHelpers = await import("./stale-IYYXEH3U.js");
|
|
3565
4240
|
}
|
|
3566
4241
|
staleHelpers.markMVStale(registry, reg.spec.name);
|
|
3567
4242
|
}
|
|
3568
4243
|
}
|
|
4244
|
+
return deleted;
|
|
3569
4245
|
}
|
|
3570
4246
|
/**
|
|
3571
4247
|
* List all records in the collection.
|
|
@@ -3584,22 +4260,14 @@ var Collection = class {
|
|
|
3584
4260
|
);
|
|
3585
4261
|
}
|
|
3586
4262
|
if (this.materializedViewSource !== void 0) {
|
|
3587
|
-
const { resolveStaleMVOnRead } = await import("./stale-
|
|
4263
|
+
const { resolveStaleMVOnRead } = await import("./stale-IYYXEH3U.js");
|
|
3588
4264
|
await resolveStaleMVOnRead(this.materializedViewSource, this.name);
|
|
3589
4265
|
}
|
|
3590
4266
|
await this.ensureHydrated();
|
|
3591
4267
|
const records = [...this.cache.values()].map((e) => e.record);
|
|
3592
|
-
if (!this.
|
|
4268
|
+
if (!this.via) return records;
|
|
3593
4269
|
return Promise.all(records.map((r) => this.applyLocaleToRecord(r, locale)));
|
|
3594
4270
|
}
|
|
3595
|
-
/**
|
|
3596
|
-
* @internal — whether any read-side record transform is registered
|
|
3597
|
-
* (money decode, i18nText resolution, dictKey labels). Gates the
|
|
3598
|
-
* no-transform fast path in {@link list}.
|
|
3599
|
-
*/
|
|
3600
|
-
hasReadTransforms() {
|
|
3601
|
-
return this.moneyFields !== void 0 && Object.keys(this.moneyFields).length > 0 || this.i18nFields !== void 0 && Object.keys(this.i18nFields).length > 0 || this.dictKeyFields !== void 0 && Object.keys(this.dictKeyFields).length > 0;
|
|
3602
|
-
}
|
|
3603
4271
|
/**
|
|
3604
4272
|
* Scan-mode full-text search over a plain-text `field`. Decrypts the
|
|
3605
4273
|
* collection in memory and ranks records by BM25 against the tokenized query.
|
|
@@ -3826,7 +4494,7 @@ var Collection = class {
|
|
|
3826
4494
|
getIndexes: () => this.getIndexes(),
|
|
3827
4495
|
lookupById: (id) => this.cache.get(id)?.record,
|
|
3828
4496
|
snapshotEntries: () => [...this.cache.entries()].map(([id, e]) => ({ id, record: e.record })),
|
|
3829
|
-
...this.
|
|
4497
|
+
...this.via ? { via: this.via } : {}
|
|
3830
4498
|
};
|
|
3831
4499
|
const resolver = this.joinResolver;
|
|
3832
4500
|
const leftCollection = this.name;
|
|
@@ -3906,8 +4574,7 @@ var Collection = class {
|
|
|
3906
4574
|
* silently miss records — consistent with the `query()` /
|
|
3907
4575
|
* `list()` lazy-mode policy. If this becomes a blocker for a
|
|
3908
4576
|
* real consumer, the fix is to add an async `scan()`-backed
|
|
3909
|
-
* variant of this method, which is exactly what
|
|
3910
|
-
* joins will need anyway.
|
|
4577
|
+
* variant of this method, which is exactly what streaming joins will need anyway.
|
|
3911
4578
|
*/
|
|
3912
4579
|
querySourceForJoin() {
|
|
3913
4580
|
if (this.lazy) {
|
|
@@ -3927,9 +4594,8 @@ var Collection = class {
|
|
|
3927
4594
|
this.emitter.on("change", handler);
|
|
3928
4595
|
return () => this.emitter.off("change", handler);
|
|
3929
4596
|
},
|
|
3930
|
-
//
|
|
3931
|
-
|
|
3932
|
-
...this.i18nFields !== void 0 ? { i18nFields: this.i18nFields } : {}
|
|
4597
|
+
// Sync join dressing (#650 Task 6, #626 retirement) — i18n-text + lookup-label, from this collection's own bindings.
|
|
4598
|
+
...this.presentForJoin !== void 0 ? { presentForJoin: this.presentForJoin } : {}
|
|
3933
4599
|
};
|
|
3934
4600
|
}
|
|
3935
4601
|
/**
|
|
@@ -3965,7 +4631,7 @@ var Collection = class {
|
|
|
3965
4631
|
);
|
|
3966
4632
|
const entries = [];
|
|
3967
4633
|
for (const env of envelopes) {
|
|
3968
|
-
const record = await this.codec.decryptRecord(env, { skipValidation: true });
|
|
4634
|
+
const record = await this.codec.decryptRecord(env, { skipValidation: true, id });
|
|
3969
4635
|
if (record === null) continue;
|
|
3970
4636
|
entries.push({
|
|
3971
4637
|
version: env._v,
|
|
@@ -3994,7 +4660,7 @@ var Collection = class {
|
|
|
3994
4660
|
version
|
|
3995
4661
|
);
|
|
3996
4662
|
if (!envelope) return null;
|
|
3997
|
-
return this.codec.decryptRecord(envelope, { skipValidation: true });
|
|
4663
|
+
return this.codec.decryptRecord(envelope, { skipValidation: true, id });
|
|
3998
4664
|
}
|
|
3999
4665
|
/** Revert a record to a past version. Creates a new version with the old content. */
|
|
4000
4666
|
async revert(id, version) {
|
|
@@ -4102,7 +4768,7 @@ var Collection = class {
|
|
|
4102
4768
|
const id = ids[i];
|
|
4103
4769
|
const envelope = await this.adapter.get(this.vault, this.name, id);
|
|
4104
4770
|
if (envelope) {
|
|
4105
|
-
const record = await this.codec.decryptRecord(envelope, { sealedAsHandles: true });
|
|
4771
|
+
const record = await this.codec.decryptRecord(envelope, { id, sealedAsHandles: true });
|
|
4106
4772
|
if (record === null) continue;
|
|
4107
4773
|
items.push(record);
|
|
4108
4774
|
if (!this.lazy && !this.cache.has(id)) {
|
|
@@ -4176,14 +4842,14 @@ var Collection = class {
|
|
|
4176
4842
|
[],
|
|
4177
4843
|
[],
|
|
4178
4844
|
joinContext,
|
|
4179
|
-
this.
|
|
4845
|
+
this.via
|
|
4180
4846
|
);
|
|
4181
4847
|
}
|
|
4182
4848
|
/** Decrypt a page of envelopes returned by `adapter.listPage`. */
|
|
4183
4849
|
async decryptPage(items) {
|
|
4184
4850
|
const out = [];
|
|
4185
4851
|
for (const { id, envelope } of items) {
|
|
4186
|
-
const record = await this.codec.decryptRecord(envelope, { sealedAsHandles: true });
|
|
4852
|
+
const record = await this.codec.decryptRecord(envelope, { id, sealedAsHandles: true });
|
|
4187
4853
|
if (record === null) continue;
|
|
4188
4854
|
out.push({ id, record, version: envelope._v });
|
|
4189
4855
|
}
|
|
@@ -4243,7 +4909,7 @@ var Collection = class {
|
|
|
4243
4909
|
}
|
|
4244
4910
|
return;
|
|
4245
4911
|
}
|
|
4246
|
-
const record = await this.codec.decryptRecord(envelope, { sealedAsHandles: true });
|
|
4912
|
+
const record = await this.codec.decryptRecord(envelope, { id, sealedAsHandles: true });
|
|
4247
4913
|
if (record === null) {
|
|
4248
4914
|
this.cache.delete(id);
|
|
4249
4915
|
if (previous) {
|
|
@@ -4263,9 +4929,63 @@ var Collection = class {
|
|
|
4263
4929
|
* Never writes to the store and never fires write hooks, so it cannot loop.
|
|
4264
4930
|
*/
|
|
4265
4931
|
async _applyRemoteChange(id, action) {
|
|
4266
|
-
await this.
|
|
4267
|
-
|
|
4268
|
-
|
|
4932
|
+
await this._onRecordMutated(id, action, "tab-mirror");
|
|
4933
|
+
}
|
|
4934
|
+
/**
|
|
4935
|
+
* Origin-tagged mutation choke point (#623 task 10) — the single dispatch
|
|
4936
|
+
* point every put/delete path funnels through, AFTER its own store write,
|
|
4937
|
+
* to fire the side-effects that path performs today. Each case below
|
|
4938
|
+
* performs EXACTLY the side-effect set the seam-map Part-3 table
|
|
4939
|
+
* (`.superpowers/sdd/seam-map-i18n-pipeline.md`) records for `origin` —
|
|
4940
|
+
* this is a pure parity extraction of existing tails, not a behavior
|
|
4941
|
+
* change. Phase C (#621, #622) plugs the dependency-graph dispatch into
|
|
4942
|
+
* this socket, keyed off `origin`, without every call site having to
|
|
4943
|
+
* learn the graph.
|
|
4944
|
+
*
|
|
4945
|
+
* `restore` is never reached: `Vault.load` / `backup.ts#loadVault` drops
|
|
4946
|
+
* the whole `collectionCache` instead of dispatching per record — the
|
|
4947
|
+
* origin is reserved for phase C.
|
|
4948
|
+
*
|
|
4949
|
+
* @internal
|
|
4950
|
+
*/
|
|
4951
|
+
async _onRecordMutated(id, action, origin, ctx) {
|
|
4952
|
+
switch (origin) {
|
|
4953
|
+
case "local-write": {
|
|
4954
|
+
const record = ctx.record;
|
|
4955
|
+
const version = ctx.version;
|
|
4956
|
+
await this.onDirty?.(this.name, id, "put", version);
|
|
4957
|
+
this.emitter.emit("change", { vault: this.vault, collection: this.name, id, action: "put" });
|
|
4958
|
+
this.searchIndexStore?.markDirty();
|
|
4959
|
+
await this.onAccess?.("put", id);
|
|
4960
|
+
await this.dispatchDerivations(id, record, version);
|
|
4961
|
+
await this.dispatchMaterializedViews(id, record);
|
|
4962
|
+
return;
|
|
4963
|
+
}
|
|
4964
|
+
case "local-delete": {
|
|
4965
|
+
await this.onDirty?.(this.name, id, "put", ctx.version);
|
|
4966
|
+
this.emitter.emit("change", { vault: this.vault, collection: this.name, id, action: "delete" });
|
|
4967
|
+
this.searchIndexStore?.markDirty();
|
|
4968
|
+
await this.onAccess?.("delete", id);
|
|
4969
|
+
return;
|
|
4970
|
+
}
|
|
4971
|
+
case "tab-mirror":
|
|
4972
|
+
await this._invalidateCacheEntry(id);
|
|
4973
|
+
this.emitter.emit("change", { vault: this.vault, collection: this.name, id, action });
|
|
4974
|
+
this.searchIndexStore?.markDirty();
|
|
4975
|
+
return;
|
|
4976
|
+
case "sync-apply":
|
|
4977
|
+
this._invalidateCekCacheEntry(id);
|
|
4978
|
+
await this._invalidateCacheEntry(id);
|
|
4979
|
+
this.graphDispatch?.collect(this.name, id);
|
|
4980
|
+
return;
|
|
4981
|
+
case "cutover":
|
|
4982
|
+
await this._invalidateCacheEntry(id);
|
|
4983
|
+
this.graphDispatch?.collect(this.name, id);
|
|
4984
|
+
return;
|
|
4985
|
+
case "restore":
|
|
4986
|
+
this.graphDispatch?.collect(this.name, id);
|
|
4987
|
+
return;
|
|
4988
|
+
}
|
|
4269
4989
|
}
|
|
4270
4990
|
/** @internal — the current in-memory record without a store read (for conflict capture). */
|
|
4271
4991
|
_peekCached(id) {
|
|
@@ -4423,7 +5143,7 @@ var Collection = class {
|
|
|
4423
5143
|
for (const [id, entry] of this.cache) {
|
|
4424
5144
|
const cek = this.perRecordCek ? await this.resolveRecordCek(id) : void 0;
|
|
4425
5145
|
const prevForVdig = this.vdigFields !== null ? await this.adapter.get(this.vault, this.name, id) : null;
|
|
4426
|
-
result[id] = await this.codec.encryptRecord(entry.record, entry.version, cek, void 0, void 0, this.vdigFields !== null ? { id, prev: prevForVdig } : void 0);
|
|
5146
|
+
result[id] = await this.codec.encryptRecord(entry.record, entry.version, cek, void 0, void 0, this.vdigFields !== null ? { id, prev: prevForVdig } : void 0, id);
|
|
4427
5147
|
}
|
|
4428
5148
|
return result;
|
|
4429
5149
|
}
|
|
@@ -4435,93 +5155,22 @@ var Collection = class {
|
|
|
4435
5155
|
* `this.defaultLocale`.
|
|
4436
5156
|
*
|
|
4437
5157
|
* - i18nText fields: replaced with the resolved string (or the full
|
|
4438
|
-
* map when `locale === 'raw'`).
|
|
4439
|
-
* - dictKey fields: `<field>Label` virtual fields added.
|
|
4440
|
-
*
|
|
4441
|
-
* Returns the record unchanged when no
|
|
4442
|
-
*
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
if (!
|
|
4449
|
-
const locale = localeOpts?.locale ?? this.defaultLocale;
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
const hasStaticDisplay = hasDict && this.dictKeyFields !== void 0 && Object.values(this.dictKeyFields).some(
|
|
4455
|
-
(d) => isStaticDictDescriptor(d) && d.displayLocale !== void 0
|
|
4456
|
-
);
|
|
4457
|
-
if (!locale && !hasStaticDisplay) return stripI18nFilled(result);
|
|
4458
|
-
const layer = localeOpts?._layer ?? "read";
|
|
4459
|
-
if (locale && hasI18n && this.i18nFields) {
|
|
4460
|
-
result = this.i18nStrategy.applyI18nLocale(result, this.i18nFields, locale, localeOpts?.fallback, layer);
|
|
4461
|
-
}
|
|
4462
|
-
if (hasDict && this.dictKeyFields && this.dictLabelResolver && locale !== "raw") {
|
|
4463
|
-
const withLabels = { ...result };
|
|
4464
|
-
const resolver = this.dictLabelResolver;
|
|
4465
|
-
for (const [field, desc] of Object.entries(this.dictKeyFields)) {
|
|
4466
|
-
const policy = desc.onMissing ? resolvePolicy(desc.onMissing, layer) : "null";
|
|
4467
|
-
const fallback = policy === "substitute" ? localeOpts?.fallback ?? desc.substitute : localeOpts?.fallback;
|
|
4468
|
-
const effLocale = locale ?? (isStaticDictDescriptor(desc) ? desc.displayLocale : void 0);
|
|
4469
|
-
const resolveKey = async (key) => {
|
|
4470
|
-
if (!effLocale) {
|
|
4471
|
-
if (policy === "throw") {
|
|
4472
|
-
throw new LocaleNotSpecifiedError(
|
|
4473
|
-
field,
|
|
4474
|
-
`dictKey "${field}": no locale active to resolve key "${key}".`
|
|
4475
|
-
);
|
|
4476
|
-
}
|
|
4477
|
-
return null;
|
|
4478
|
-
}
|
|
4479
|
-
const label = await resolver(desc.name, key, effLocale, fallback);
|
|
4480
|
-
if (label === void 0) {
|
|
4481
|
-
if (policy === "throw") {
|
|
4482
|
-
throw new LocaleNotSpecifiedError(
|
|
4483
|
-
field,
|
|
4484
|
-
`dictKey "${field}": no label for key "${key}" in locale "${effLocale}".`
|
|
4485
|
-
);
|
|
4486
|
-
}
|
|
4487
|
-
return null;
|
|
4488
|
-
}
|
|
4489
|
-
return label;
|
|
4490
|
-
};
|
|
4491
|
-
if (field.includes("[].")) {
|
|
4492
|
-
const parts = field.split("[].");
|
|
4493
|
-
const arrayKey = parts[0];
|
|
4494
|
-
const leaf = parts[1];
|
|
4495
|
-
if (!leaf || leaf.includes(".")) continue;
|
|
4496
|
-
const arr = withLabels[arrayKey];
|
|
4497
|
-
if (!Array.isArray(arr)) continue;
|
|
4498
|
-
const labelKey = `${leaf}Label`;
|
|
4499
|
-
withLabels[arrayKey] = await Promise.all(
|
|
4500
|
-
arr.map(async (el) => {
|
|
4501
|
-
if (!el || typeof el !== "object" || Array.isArray(el)) return el;
|
|
4502
|
-
const k = el[leaf];
|
|
4503
|
-
if (typeof k !== "string") return el;
|
|
4504
|
-
return { ...el, [labelKey]: await resolveKey(k) };
|
|
4505
|
-
})
|
|
4506
|
-
);
|
|
4507
|
-
continue;
|
|
4508
|
-
}
|
|
4509
|
-
const val = result[field];
|
|
4510
|
-
if (Array.isArray(val)) {
|
|
4511
|
-
withLabels[`${field}Label`] = await Promise.all(
|
|
4512
|
-
val.map(async (k) => ({
|
|
4513
|
-
key: k,
|
|
4514
|
-
label: typeof k === "string" ? await resolveKey(k) : null
|
|
4515
|
-
}))
|
|
4516
|
-
);
|
|
4517
|
-
} else if (typeof val === "string") {
|
|
4518
|
-
const label = await resolveKey(val);
|
|
4519
|
-
if (label !== null) withLabels[`${field}Label`] = label;
|
|
4520
|
-
}
|
|
4521
|
-
}
|
|
4522
|
-
result = withLabels;
|
|
4523
|
-
}
|
|
4524
|
-
return stripI18nFilled(result);
|
|
5158
|
+
* map when `locale === 'raw'`).
|
|
5159
|
+
* - dictKey fields: `<field>Label` virtual fields added.
|
|
5160
|
+
*
|
|
5161
|
+
* Returns the record unchanged when no Via pipeline is compiled (#638 Task 7:
|
|
5162
|
+
* was money/i18n/dictKey-flag-gated — missed any OTHER binding's `present`
|
|
5163
|
+
* hook, e.g. the `computed` via-binding's virtual-mode read-time compute;
|
|
5164
|
+
* `this.via` truthiness is the general condition every binding's presence
|
|
5165
|
+
* already implies).
|
|
5166
|
+
*/
|
|
5167
|
+
async applyLocaleToRecord(record, localeOpts) {
|
|
5168
|
+
if (!this.via) return record;
|
|
5169
|
+
const locale = localeOpts?.locale ?? this.defaultLocale;
|
|
5170
|
+
const layer = localeOpts?._layer ?? "read";
|
|
5171
|
+
let result = record;
|
|
5172
|
+
result = await this.via.present(result, { locale, ...localeOpts?.fallback !== void 0 ? { fallback: localeOpts.fallback } : {}, layer });
|
|
5173
|
+
return result;
|
|
4525
5174
|
}
|
|
4526
5175
|
/**
|
|
4527
5176
|
* Low-level: encrypt a pre-serialised JSON string into an EncryptedEnvelope.
|
|
@@ -4738,14 +5387,14 @@ var Collection = class {
|
|
|
4738
5387
|
/** C-A/R10: persist the classified marker once per classified handle (store I/O in config-drift.ts). */
|
|
4739
5388
|
async _ensureClassifiedMarker() {
|
|
4740
5389
|
if (this._markerPersisted || this.vdigFields === null) return;
|
|
4741
|
-
const { persistClassifiedMarkerForFields } = await import("./
|
|
5390
|
+
const { persistClassifiedMarkerForFields } = await import("./classified-marker-IO5EBW7Q.js");
|
|
4742
5391
|
await persistClassifiedMarkerForFields(this.adapter, this.vault, this.name, this.vdigFields, await this.getDEK(this.name));
|
|
4743
5392
|
this._markerPersisted = true;
|
|
4744
5393
|
}
|
|
4745
5394
|
/** C-A/R10 drift signal — the marker's declared digest-only set, memoized to one store read per handle (see config-drift.ts). */
|
|
4746
5395
|
async _classifiedMarkerDigestOnly() {
|
|
4747
5396
|
if (this._markerDigestOnlyCache !== void 0) return this._markerDigestOnlyCache;
|
|
4748
|
-
const { readClassifiedMarkerDigestOnly } = await import("./
|
|
5397
|
+
const { readClassifiedMarkerDigestOnly } = await import("./classified-marker-IO5EBW7Q.js");
|
|
4749
5398
|
this._markerDigestOnlyCache = await readClassifiedMarkerDigestOnly(this.adapter, this.vault, this.name, await this.getDEK(this.name));
|
|
4750
5399
|
return this._markerDigestOnlyCache;
|
|
4751
5400
|
}
|
|
@@ -4824,6 +5473,10 @@ var Collection = class {
|
|
|
4824
5473
|
_classifySealedShred(live) {
|
|
4825
5474
|
return classifySealedShred(this.tiersContext(), live);
|
|
4826
5475
|
}
|
|
5476
|
+
async _onViaErase(id, live) {
|
|
5477
|
+
return this.via ? this.via.eraseSealed({ id, vault: this.vault, live, crypto: await this.codec.eraseCryptoCtx(id, live) }) : void 0;
|
|
5478
|
+
}
|
|
5479
|
+
// @internal forget()'s per-ref via-erase fold (#629 T10)
|
|
4827
5480
|
/**
|
|
4828
5481
|
* Bind the {@link TiersContext} the tier ops need. The `cekCache` is passed
|
|
4829
5482
|
* by reference (the SAME `Lru` the kernel's read/write path owns) so an
|
|
@@ -5224,7 +5877,7 @@ var VaultPeriods = class {
|
|
|
5224
5877
|
...anchor.priorPeriodName !== void 0 && { priorPeriodName: anchor.priorPeriodName },
|
|
5225
5878
|
...options.dateField !== void 0 && { dateField: options.dateField }
|
|
5226
5879
|
};
|
|
5227
|
-
const envelope = await this.
|
|
5880
|
+
const envelope = await this.writeReserved(PERIODS_COLLECTION, record.name, record);
|
|
5228
5881
|
await this.deps.strategy.appendPeriodLedgerEntry(this.deps.getLedgerOrNull(), this.deps.userId(), envelope, record.name);
|
|
5229
5882
|
existing.push(record);
|
|
5230
5883
|
this.periodCache = existing;
|
|
@@ -5279,20 +5932,208 @@ var VaultPeriods = class {
|
|
|
5279
5932
|
priorPeriodName: anchor.priorPeriodName ?? prior.name,
|
|
5280
5933
|
...openingCollections.length > 0 && { openingCollections }
|
|
5281
5934
|
};
|
|
5282
|
-
const envelope = await this.
|
|
5935
|
+
const envelope = await this.writeReserved(PERIODS_COLLECTION, record.name, record);
|
|
5283
5936
|
await this.deps.strategy.appendPeriodLedgerEntry(this.deps.getLedgerOrNull(), this.deps.userId(), envelope, record.name);
|
|
5284
5937
|
existing.push(record);
|
|
5285
5938
|
this.periodCache = existing;
|
|
5286
5939
|
return record;
|
|
5287
5940
|
}
|
|
5288
|
-
/**
|
|
5941
|
+
/**
|
|
5942
|
+
* Freeze a closed period: physically purges delete markers whose `_ts`
|
|
5943
|
+
* falls inside the period's window (#604) and records the fact in a
|
|
5944
|
+
* companion `_period_freezes/<name>` record. NEVER mutates the
|
|
5945
|
+
* hash-chained `_periods/<name>` record's stored bytes — `frozenAt` /
|
|
5946
|
+
* `frozenBy` / `purgedMarkerCount` are merged into `PeriodRecord`s on
|
|
5947
|
+
* read only. Idempotent: a second call is a no-op that returns the
|
|
5948
|
+
* same merged record without re-purging or re-appending a ledger entry.
|
|
5949
|
+
*/
|
|
5950
|
+
async freezePeriod(name) {
|
|
5951
|
+
const existing = await this.loadPeriodsCache();
|
|
5952
|
+
const period = existing.find((p) => p.name === name);
|
|
5953
|
+
if (!period) throw new ValidationError(`freezePeriod: no period named "${name}".`);
|
|
5954
|
+
if (period.kind !== "closed") {
|
|
5955
|
+
throw new ValidationError(
|
|
5956
|
+
`freezePeriod: period "${name}" is "${period.kind}"; only a closed period can be frozen.`
|
|
5957
|
+
);
|
|
5958
|
+
}
|
|
5959
|
+
const before = periodExclusiveUpperBound(period.endDate);
|
|
5960
|
+
if (Date.parse(before) > Date.now()) {
|
|
5961
|
+
throw new ValidationError(
|
|
5962
|
+
`freezePeriod: period "${name}" purge window ends at ${before}, in the future; only a period whose window is fully in the past can be frozen (recent delete markers may not have converged).`
|
|
5963
|
+
);
|
|
5964
|
+
}
|
|
5965
|
+
const prior = await this.readReserved(PERIOD_FREEZES_COLLECTION, name);
|
|
5966
|
+
if (prior) return this.mergeFreeze(period, prior);
|
|
5967
|
+
const purgedMarkerCount = await this.deps.purgeDeleteMarkers(before);
|
|
5968
|
+
const freeze = {
|
|
5969
|
+
period: name,
|
|
5970
|
+
frozenAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5971
|
+
frozenBy: this.deps.userId(),
|
|
5972
|
+
purgedMarkerCount
|
|
5973
|
+
};
|
|
5974
|
+
const envelope = await this.writeReserved(PERIOD_FREEZES_COLLECTION, name, freeze);
|
|
5975
|
+
await this.deps.strategy.appendPeriodLedgerEntry(
|
|
5976
|
+
this.deps.getLedgerOrNull(),
|
|
5977
|
+
this.deps.userId(),
|
|
5978
|
+
envelope,
|
|
5979
|
+
name,
|
|
5980
|
+
PERIOD_FREEZES_COLLECTION
|
|
5981
|
+
);
|
|
5982
|
+
return this.mergeFreeze(period, freeze);
|
|
5983
|
+
}
|
|
5984
|
+
/**
|
|
5985
|
+
* Archive a closed period (#613): physically relocates its in-window
|
|
5986
|
+
* records (those with `_ts < periodExclusiveUpperBound(endDate)`) from the
|
|
5987
|
+
* hot store to the configured cold tier via `archiveRecords`, and records
|
|
5988
|
+
* the fact in a companion `_period_archives/<name>` record. NEVER mutates
|
|
5989
|
+
* the hash-chained `_periods/<name>` record. Non-destructive (reads fall
|
|
5990
|
+
* through to cold) and idempotent: a second call is a no-op returning the
|
|
5991
|
+
* same merged record.
|
|
5992
|
+
*/
|
|
5993
|
+
async archivePeriod(name) {
|
|
5994
|
+
const existing = await this.loadPeriodsCache();
|
|
5995
|
+
const period = existing.find((p) => p.name === name);
|
|
5996
|
+
if (!period) throw new ValidationError(`archivePeriod: no period named "${name}".`);
|
|
5997
|
+
if (period.kind !== "closed") {
|
|
5998
|
+
throw new ValidationError(
|
|
5999
|
+
`archivePeriod: period "${name}" is "${period.kind}"; only a closed period can be archived.`
|
|
6000
|
+
);
|
|
6001
|
+
}
|
|
6002
|
+
const prior = await this.readReserved(PERIOD_ARCHIVES_COLLECTION, name);
|
|
6003
|
+
if (prior) return this.mergeArchive(period, prior);
|
|
6004
|
+
const before = periodExclusiveUpperBound(period.endDate);
|
|
6005
|
+
const archivedRecordCount = await this.deps.archiveRecords(before);
|
|
6006
|
+
const archive = {
|
|
6007
|
+
period: name,
|
|
6008
|
+
archivedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6009
|
+
archivedBy: this.deps.userId(),
|
|
6010
|
+
archivedRecordCount
|
|
6011
|
+
};
|
|
6012
|
+
const envelope = await this.writeReserved(PERIOD_ARCHIVES_COLLECTION, name, archive);
|
|
6013
|
+
await this.deps.strategy.appendPeriodLedgerEntry(
|
|
6014
|
+
this.deps.getLedgerOrNull(),
|
|
6015
|
+
this.deps.userId(),
|
|
6016
|
+
envelope,
|
|
6017
|
+
name,
|
|
6018
|
+
PERIOD_ARCHIVES_COLLECTION
|
|
6019
|
+
);
|
|
6020
|
+
return this.mergeArchive(period, archive);
|
|
6021
|
+
}
|
|
6022
|
+
/**
|
|
6023
|
+
* Target-purge a closed period (#615): sweeps delete markers off the vault's
|
|
6024
|
+
* PUSH-ONLY sync targets (`backup`/`archive`) via `purgeTargets`, recording a
|
|
6025
|
+
* companion `_period_target_purges/<name>` record. `sync-peer` targets are
|
|
6026
|
+
* skipped (resurrection risk). NEVER mutates the chained `_periods/<name>`
|
|
6027
|
+
* record. Requires the period be frozen first (closed → frozen → target-purged).
|
|
6028
|
+
* Idempotent once run; with no push-only targets it writes no companion and
|
|
6029
|
+
* is re-runnable.
|
|
6030
|
+
*/
|
|
6031
|
+
async purgePeriodTargets(name) {
|
|
6032
|
+
const existing = await this.loadPeriodsCache();
|
|
6033
|
+
const period = existing.find((p) => p.name === name);
|
|
6034
|
+
if (!period) throw new ValidationError(`purgePeriodTargets: no period named "${name}".`);
|
|
6035
|
+
if (period.kind !== "closed") {
|
|
6036
|
+
throw new ValidationError(
|
|
6037
|
+
`purgePeriodTargets: period "${name}" is "${period.kind}"; only a closed period can be target-purged.`
|
|
6038
|
+
);
|
|
6039
|
+
}
|
|
6040
|
+
const frozen = await this.readReserved(PERIOD_FREEZES_COLLECTION, name);
|
|
6041
|
+
if (!frozen) {
|
|
6042
|
+
throw new ValidationError(
|
|
6043
|
+
`purgePeriodTargets: period "${name}" must be frozen first (closed \u2192 frozen \u2192 target-purged).`
|
|
6044
|
+
);
|
|
6045
|
+
}
|
|
6046
|
+
const prior = await this.readReserved(PERIOD_TARGET_PURGES_COLLECTION, name);
|
|
6047
|
+
if (prior) return this.mergeTargetPurge(period, prior);
|
|
6048
|
+
const before = periodExclusiveUpperBound(period.endDate);
|
|
6049
|
+
const targets = await this.deps.purgeTargets(before);
|
|
6050
|
+
if (targets.length === 0) return period;
|
|
6051
|
+
const record = {
|
|
6052
|
+
period: name,
|
|
6053
|
+
purgedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6054
|
+
purgedBy: this.deps.userId(),
|
|
6055
|
+
targets
|
|
6056
|
+
};
|
|
6057
|
+
const envelope = await this.writeReserved(PERIOD_TARGET_PURGES_COLLECTION, name, record);
|
|
6058
|
+
await this.deps.strategy.appendPeriodLedgerEntry(
|
|
6059
|
+
this.deps.getLedgerOrNull(),
|
|
6060
|
+
this.deps.userId(),
|
|
6061
|
+
envelope,
|
|
6062
|
+
name,
|
|
6063
|
+
PERIOD_TARGET_PURGES_COLLECTION
|
|
6064
|
+
);
|
|
6065
|
+
return this.mergeTargetPurge(period, record);
|
|
6066
|
+
}
|
|
6067
|
+
/** Merge target-purge companion fields into a fresh `PeriodRecord` copy — never mutates `periodCache`. */
|
|
6068
|
+
mergeTargetPurge(period, record) {
|
|
6069
|
+
return {
|
|
6070
|
+
...period,
|
|
6071
|
+
targetsPurgedAt: record.purgedAt,
|
|
6072
|
+
targetsPurgedBy: record.purgedBy,
|
|
6073
|
+
targetsPurged: record.targets
|
|
6074
|
+
};
|
|
6075
|
+
}
|
|
6076
|
+
/** Merge archive companion fields into a fresh `PeriodRecord` copy — never mutates `periodCache`. */
|
|
6077
|
+
mergeArchive(period, archive) {
|
|
6078
|
+
return {
|
|
6079
|
+
...period,
|
|
6080
|
+
archivedAt: archive.archivedAt,
|
|
6081
|
+
archivedBy: archive.archivedBy,
|
|
6082
|
+
archivedRecordCount: archive.archivedRecordCount
|
|
6083
|
+
};
|
|
6084
|
+
}
|
|
6085
|
+
/** Merge freeze companion fields into a fresh `PeriodRecord` copy — never mutates `periodCache`. */
|
|
6086
|
+
mergeFreeze(period, freeze) {
|
|
6087
|
+
return {
|
|
6088
|
+
...period,
|
|
6089
|
+
frozenAt: freeze.frozenAt,
|
|
6090
|
+
frozenBy: freeze.frozenBy,
|
|
6091
|
+
purgedMarkerCount: freeze.purgedMarkerCount
|
|
6092
|
+
};
|
|
6093
|
+
}
|
|
6094
|
+
/** Return every closed / opened period in `closedAt` order, merged with any freeze + archive companions. */
|
|
5289
6095
|
async listPeriods() {
|
|
5290
|
-
|
|
6096
|
+
const all = await this.loadPeriodsCache();
|
|
6097
|
+
const freezeIds = await this.deps.adapter.list(this.deps.vault, PERIOD_FREEZES_COLLECTION);
|
|
6098
|
+
const freezes = /* @__PURE__ */ new Map();
|
|
6099
|
+
for (const id of freezeIds) {
|
|
6100
|
+
const f = await this.readReserved(PERIOD_FREEZES_COLLECTION, id);
|
|
6101
|
+
if (f) freezes.set(f.period, f);
|
|
6102
|
+
}
|
|
6103
|
+
const archiveIds = await this.deps.adapter.list(this.deps.vault, PERIOD_ARCHIVES_COLLECTION);
|
|
6104
|
+
const archives = /* @__PURE__ */ new Map();
|
|
6105
|
+
for (const id of archiveIds) {
|
|
6106
|
+
const a = await this.readReserved(PERIOD_ARCHIVES_COLLECTION, id);
|
|
6107
|
+
if (a) archives.set(a.period, a);
|
|
6108
|
+
}
|
|
6109
|
+
const targetPurgeIds = await this.deps.adapter.list(this.deps.vault, PERIOD_TARGET_PURGES_COLLECTION);
|
|
6110
|
+
const targetPurges = /* @__PURE__ */ new Map();
|
|
6111
|
+
for (const id of targetPurgeIds) {
|
|
6112
|
+
const tp = await this.readReserved(PERIOD_TARGET_PURGES_COLLECTION, id);
|
|
6113
|
+
if (tp) targetPurges.set(tp.period, tp);
|
|
6114
|
+
}
|
|
6115
|
+
return all.map((p) => {
|
|
6116
|
+
const f = freezes.get(p.name);
|
|
6117
|
+
let merged = f ? this.mergeFreeze(p, f) : p;
|
|
6118
|
+
const a = archives.get(p.name);
|
|
6119
|
+
if (a) merged = this.mergeArchive(merged, a);
|
|
6120
|
+
const tp = targetPurges.get(p.name);
|
|
6121
|
+
if (tp) merged = this.mergeTargetPurge(merged, tp);
|
|
6122
|
+
return merged;
|
|
6123
|
+
});
|
|
5291
6124
|
}
|
|
5292
|
-
/** Look up a single period by name. Returns `null` if not found. */
|
|
6125
|
+
/** Look up a single period by name, merged with its freeze + archive + target-purge companions if any. Returns `null` if not found. */
|
|
5293
6126
|
async getPeriod(name) {
|
|
5294
6127
|
const all = await this.loadPeriodsCache();
|
|
5295
|
-
|
|
6128
|
+
const period = all.find((p) => p.name === name);
|
|
6129
|
+
if (!period) return null;
|
|
6130
|
+
const freeze = await this.readReserved(PERIOD_FREEZES_COLLECTION, name);
|
|
6131
|
+
const archive = await this.readReserved(PERIOD_ARCHIVES_COLLECTION, name);
|
|
6132
|
+
let merged = freeze ? this.mergeFreeze(period, freeze) : period;
|
|
6133
|
+
if (archive) merged = this.mergeArchive(merged, archive);
|
|
6134
|
+
const targetPurge = await this.readReserved(PERIOD_TARGET_PURGES_COLLECTION, name);
|
|
6135
|
+
if (targetPurge) merged = this.mergeTargetPurge(merged, targetPurge);
|
|
6136
|
+
return merged;
|
|
5296
6137
|
}
|
|
5297
6138
|
/** Called by the gate bus before put/delete. */
|
|
5298
6139
|
async assertTsWritable(existing, incoming) {
|
|
@@ -5317,11 +6158,12 @@ var VaultPeriods = class {
|
|
|
5317
6158
|
this.periodCache = loaded;
|
|
5318
6159
|
return loaded;
|
|
5319
6160
|
}
|
|
5320
|
-
|
|
5321
|
-
|
|
6161
|
+
/** Generic reserved-collection writer — serves `_periods` and `_period_freezes` alike. */
|
|
6162
|
+
async writeReserved(collection, key, value) {
|
|
6163
|
+
const json = JSON.stringify(value);
|
|
5322
6164
|
let envelope;
|
|
5323
6165
|
if (this.deps.encrypted) {
|
|
5324
|
-
const dek = await this.deps.getDEK(
|
|
6166
|
+
const dek = await this.deps.getDEK(collection);
|
|
5325
6167
|
const { iv, data } = await encrypt(json, dek);
|
|
5326
6168
|
envelope = {
|
|
5327
6169
|
_noydb: NOYDB_FORMAT_VERSION,
|
|
@@ -5341,9 +6183,16 @@ var VaultPeriods = class {
|
|
|
5341
6183
|
_by: this.deps.userId()
|
|
5342
6184
|
};
|
|
5343
6185
|
}
|
|
5344
|
-
await this.deps.adapter.put(this.deps.vault,
|
|
6186
|
+
await this.deps.adapter.put(this.deps.vault, collection, key, envelope);
|
|
5345
6187
|
return envelope;
|
|
5346
6188
|
}
|
|
6189
|
+
/** Generic reserved-collection reader — serves `_period_freezes` companion reads. */
|
|
6190
|
+
async readReserved(collection, key) {
|
|
6191
|
+
const env = await this.deps.adapter.get(this.deps.vault, collection, key);
|
|
6192
|
+
if (!env) return null;
|
|
6193
|
+
const json = this.deps.encrypted ? await openEnvelopeJson(env, await this.deps.getDEK(collection)) : env._data;
|
|
6194
|
+
return JSON.parse(json);
|
|
6195
|
+
}
|
|
5347
6196
|
async decryptPeriodRecord(envelope) {
|
|
5348
6197
|
let json;
|
|
5349
6198
|
if (this.deps.encrypted) {
|
|
@@ -5577,11 +6426,12 @@ var VaultLinks = class {
|
|
|
5577
6426
|
* for the same (collection, id) returns immediately so two
|
|
5578
6427
|
* mutually-cascading collections don't recurse forever.
|
|
5579
6428
|
*/
|
|
5580
|
-
async enforceRefsOnDelete(collectionName, id) {
|
|
6429
|
+
async enforceRefsOnDelete(graph, collectionName, id) {
|
|
5581
6430
|
const key = `${collectionName}/${id}`;
|
|
5582
6431
|
if (this.cascadeInProgress.has(key)) return;
|
|
5583
6432
|
this.cascadeInProgress.add(key);
|
|
5584
6433
|
try {
|
|
6434
|
+
await this.enforceLookupRefsOnDelete(graph, collectionName, collectionName, id);
|
|
5585
6435
|
const inbound = this.deps.refRegistry.getInbound(collectionName);
|
|
5586
6436
|
for (const rule of inbound) {
|
|
5587
6437
|
const fromCollection = this.deps.collection(rule.collection);
|
|
@@ -5636,6 +6486,109 @@ var VaultLinks = class {
|
|
|
5636
6486
|
this.cascadeInProgress.delete(key);
|
|
5637
6487
|
}
|
|
5638
6488
|
}
|
|
6489
|
+
/**
|
|
6490
|
+
* Find the records in `collection` whose `field` currently equals `key` — the SAME
|
|
6491
|
+
* list()+filter machinery `enforceRefsOnDelete`'s classic ref-restrict/cascade loop above
|
|
6492
|
+
* already uses (bounded by the referencing collection's size, never by the whole vault or by
|
|
6493
|
+
* "which collections reference this" — that part is the graph's O(1) `_out` reverse index).
|
|
6494
|
+
*/
|
|
6495
|
+
async findLookupReferencingRecords(collection, field, key) {
|
|
6496
|
+
const all = await collection.list();
|
|
6497
|
+
return all.filter((rec) => String(rec[field]) === key);
|
|
6498
|
+
}
|
|
6499
|
+
/**
|
|
6500
|
+
* Resolve the VALUE a referencing field actually stores for the row being deleted (`rawKey`,
|
|
6501
|
+
* the backing row's PUT-id) under a given edge's `keyField` — a referencing field always stores
|
|
6502
|
+
* `row[keyField]`, never the PUT-id when the two differ (matrix tier's `lookup(dim, {key})`;
|
|
6503
|
+
* mirrors Task 3's `checkLookupMembership` review fix, Important 1). `keyField === 'id'`
|
|
6504
|
+
* (reserved/static tiers, and the matrix default) needs no row read: `rawKey` IS the value.
|
|
6505
|
+
* Reads the backing row AT MOST ONCE per distinct non-`'id'` `keyField`, even across multiple
|
|
6506
|
+
* edges — `backingCollection` is only `.get()`-able for the matrix tier, which is exactly the
|
|
6507
|
+
* only tier that can ever have `keyField !== 'id'`.
|
|
6508
|
+
*/
|
|
6509
|
+
async resolveLookupCompareKey(backingCollection, rawKey, keyField, cache) {
|
|
6510
|
+
if (keyField === "id") return rawKey;
|
|
6511
|
+
if (cache.has(keyField)) return cache.get(keyField);
|
|
6512
|
+
const row = await this.deps.collection(backingCollection).get(rawKey);
|
|
6513
|
+
const raw = row?.[keyField];
|
|
6514
|
+
const resolved = typeof raw === "string" || typeof raw === "number" ? String(raw) : void 0;
|
|
6515
|
+
cache.set(keyField, resolved);
|
|
6516
|
+
return resolved;
|
|
6517
|
+
}
|
|
6518
|
+
/**
|
|
6519
|
+
* Phase 1 (#650 Task 5, #648) — restrict check, never mutates. Throws
|
|
6520
|
+
* `DictKeyInUseError(dimension, key, usedBy, count)` naming the FIRST referencing collection
|
|
6521
|
+
* still holding a matching record, for every `onDelete:'restrict'` edge the graph's O(1)
|
|
6522
|
+
* `referencingEdgesOf(backingCollection)` reverse lookup finds pointing at this dimension. A
|
|
6523
|
+
* dimension with no declared lookup-referencing edges is a no-op — undeclared refs keep
|
|
6524
|
+
* dangling (today's behavior, unaffected). Called by `Vault.forget()` BEFORE any shred (the
|
|
6525
|
+
* spec §4 "restrict refuses before any shred" design decision) — cascade/nullify propagation
|
|
6526
|
+
* for the SAME edges happens separately, after the shred, via `forgetDerivedFanout`.
|
|
6527
|
+
*
|
|
6528
|
+
* Also resolves (and RETURNS) the live compare-key for EVERY referencing edge, restrict AND
|
|
6529
|
+
* cascade/nullify alike (#650 Task 5 review, Important fix): `forgetDerivedFanout`'s cascade/
|
|
6530
|
+
* nullify propagation runs AFTER `_writeTombstone`, when a non-`'id'` `keyField`'s value can no
|
|
6531
|
+
* longer be read off the (now-shredded) row — resolving it HERE, at the one point every
|
|
6532
|
+
* referencing edge's backing row is still guaranteed live, eliminates that post-shred
|
|
6533
|
+
* dependency. A `keyField` this can't resolve (row unreadable) maps to `undefined` in the
|
|
6534
|
+
* returned map so the caller can report it as residue instead of silently skipping the edge.
|
|
6535
|
+
*/
|
|
6536
|
+
async checkLookupRefsRestrict(graph, dimension, backingCollection, key) {
|
|
6537
|
+
const keyCache = /* @__PURE__ */ new Map();
|
|
6538
|
+
for (const { referencing, onDelete, keyField } of graph.referencingEdgesOf(backingCollection)) {
|
|
6539
|
+
const compareKey = await this.resolveLookupCompareKey(backingCollection, key, keyField, keyCache);
|
|
6540
|
+
if (onDelete !== "restrict" || compareKey === void 0) continue;
|
|
6541
|
+
const coll = this.deps.collection(referencing.collection);
|
|
6542
|
+
const matches = await this.findLookupReferencingRecords(coll, referencing.field, compareKey);
|
|
6543
|
+
if (matches.length > 0) {
|
|
6544
|
+
throw new DictKeyInUseError(dimension, key, referencing.collection, matches.length);
|
|
6545
|
+
}
|
|
6546
|
+
}
|
|
6547
|
+
return keyCache;
|
|
6548
|
+
}
|
|
6549
|
+
/**
|
|
6550
|
+
* Phase 2 (#650 Task 5, #648) — apply `cascade`/`nullify` propagation for this dimension's
|
|
6551
|
+
* non-restrict referencing edges (restrict edges are skipped — phase 1 above already refused
|
|
6552
|
+
* or the caller already checked). `cascade` deletes each matching record through its ordinary
|
|
6553
|
+
* `collection.delete()` (an origin-tagged, fanout-visible delete — participates in sync/history/
|
|
6554
|
+
* the graph dispatch wave exactly like any other delete); `nullify` clears the referencing field
|
|
6555
|
+
* via an ordinary `collection.put()`. Returns the counts so callers (forget's `ForgetFanoutStats`)
|
|
6556
|
+
* can report the propagation additively.
|
|
6557
|
+
*/
|
|
6558
|
+
async applyLookupRefsPropagation(graph, backingCollection, key) {
|
|
6559
|
+
let cascaded = 0;
|
|
6560
|
+
let nullified = 0;
|
|
6561
|
+
const keyCache = /* @__PURE__ */ new Map();
|
|
6562
|
+
for (const { referencing, onDelete, keyField } of graph.referencingEdgesOf(backingCollection)) {
|
|
6563
|
+
if (onDelete === "restrict") continue;
|
|
6564
|
+
const compareKey = await this.resolveLookupCompareKey(backingCollection, key, keyField, keyCache);
|
|
6565
|
+
if (compareKey === void 0) continue;
|
|
6566
|
+
const coll = this.deps.collection(referencing.collection);
|
|
6567
|
+
const matches = await this.findLookupReferencingRecords(coll, referencing.field, compareKey);
|
|
6568
|
+
for (const rec of matches) {
|
|
6569
|
+
const id = rec["id"];
|
|
6570
|
+
if (id === void 0) continue;
|
|
6571
|
+
if (onDelete === "cascade") {
|
|
6572
|
+
await coll.delete(id);
|
|
6573
|
+
cascaded++;
|
|
6574
|
+
} else if (onDelete === "nullify") {
|
|
6575
|
+
await coll.put(id, { ...rec, [referencing.field]: null });
|
|
6576
|
+
nullified++;
|
|
6577
|
+
}
|
|
6578
|
+
}
|
|
6579
|
+
}
|
|
6580
|
+
return { cascaded, nullified };
|
|
6581
|
+
}
|
|
6582
|
+
/**
|
|
6583
|
+
* Convenience: the full restrict-then-propagate sequence for an ORDINARY (non-forget) delete of
|
|
6584
|
+
* a lookup-referenced row — `checkLookupRefsRestrict` then `applyLookupRefsPropagation`. Used by
|
|
6585
|
+
* `enforceRefsOnDelete` above (matrix-tier: the backing row IS an ordinary `Collection` record)
|
|
6586
|
+
* and by `LookupHandle.delete()`'s injected `checkReferencesOnDelete` callback (reserved tier).
|
|
6587
|
+
*/
|
|
6588
|
+
async enforceLookupRefsOnDelete(graph, dimension, backingCollection, key) {
|
|
6589
|
+
await this.checkLookupRefsRestrict(graph, dimension, backingCollection, key);
|
|
6590
|
+
return this.applyLookupRefsPropagation(graph, backingCollection, key);
|
|
6591
|
+
}
|
|
5639
6592
|
/**
|
|
5640
6593
|
* @internal — apply link `onDelete` policy when an endpoint record is
|
|
5641
6594
|
* deleted. `'strict'` throws (blocks the delete), `'cascade'`
|
|
@@ -5792,7 +6745,7 @@ var VaultLinks = class {
|
|
|
5792
6745
|
// src/with-shape/links/lazy-handle.ts
|
|
5793
6746
|
function makeLazyLinkSetHandle(d) {
|
|
5794
6747
|
let engine = null;
|
|
5795
|
-
const load = () => engine ??= import("./link-set-
|
|
6748
|
+
const load = () => engine ??= import("./link-set-YBAC2DQT.js").then(
|
|
5796
6749
|
({ LinkSet }) => new LinkSet(d.adapter, d.vault, d.name, d.spec, d.encrypted, d.getDEK, d.actor, d.emitter, d.endpointExists)
|
|
5797
6750
|
);
|
|
5798
6751
|
return {
|
|
@@ -5806,6 +6759,166 @@ function makeLazyLinkSetHandle(d) {
|
|
|
5806
6759
|
};
|
|
5807
6760
|
}
|
|
5808
6761
|
|
|
6762
|
+
// src/kernel/via-taint-binding.ts
|
|
6763
|
+
var EMPTY_STRING_SET = /* @__PURE__ */ new Set();
|
|
6764
|
+
function isDefaultPosture(p) {
|
|
6765
|
+
return p.encryptedAtRest === DEFAULT_POSTURE.encryptedAtRest && p.queryable === DEFAULT_POSTURE.queryable && p.exportable === DEFAULT_POSTURE.exportable && p.forgettable === DEFAULT_POSTURE.forgettable;
|
|
6766
|
+
}
|
|
6767
|
+
function buildTaintOverlay(rawPostures, sealFields) {
|
|
6768
|
+
let out;
|
|
6769
|
+
for (const [field, posture] of rawPostures) {
|
|
6770
|
+
if (isDefaultPosture(posture)) continue;
|
|
6771
|
+
out ??= /* @__PURE__ */ new Map();
|
|
6772
|
+
out.set(field, sealFields.has(field) ? { ...posture, queryable: "none" } : posture);
|
|
6773
|
+
}
|
|
6774
|
+
return out;
|
|
6775
|
+
}
|
|
6776
|
+
async function encodeTaintAtRest(record, crypto, fields) {
|
|
6777
|
+
let open = record;
|
|
6778
|
+
let sealed;
|
|
6779
|
+
for (const field of fields) {
|
|
6780
|
+
if (!(field in record)) continue;
|
|
6781
|
+
const value = record[field];
|
|
6782
|
+
if (value === void 0) continue;
|
|
6783
|
+
const ref2 = await crypto.sealedSlots.seal(field, value);
|
|
6784
|
+
if (open === record) open = { ...record };
|
|
6785
|
+
delete open[field];
|
|
6786
|
+
sealed ??= {};
|
|
6787
|
+
sealed[field] = ref2;
|
|
6788
|
+
}
|
|
6789
|
+
return sealed ? { record: open, sealed } : { record };
|
|
6790
|
+
}
|
|
6791
|
+
async function decodeTaintAtRest(record, sealed, crypto, opts, fields) {
|
|
6792
|
+
let out = record;
|
|
6793
|
+
for (const field of fields) {
|
|
6794
|
+
const ref2 = sealed[field];
|
|
6795
|
+
if (ref2 === void 0) continue;
|
|
6796
|
+
if (out === record) out = { ...record };
|
|
6797
|
+
out[field] = opts.asHandles ? new SealedHandle(() => crypto.sealedSlots.unseal(field, ref2)) : await crypto.sealedSlots.unseal(field, ref2);
|
|
6798
|
+
}
|
|
6799
|
+
return out;
|
|
6800
|
+
}
|
|
6801
|
+
function taintBinding(sealFields, presentRedactFields = EMPTY_STRING_SET) {
|
|
6802
|
+
const fields = [...sealFields];
|
|
6803
|
+
const redactFields = [...presentRedactFields];
|
|
6804
|
+
return {
|
|
6805
|
+
brand: "taint",
|
|
6806
|
+
posture: { encryptedAtRest: "sealed", queryable: "none", exportable: false, forgettable: true },
|
|
6807
|
+
covers: (field) => sealFields.has(field) || presentRedactFields.has(field),
|
|
6808
|
+
...fields.length > 0 ? {
|
|
6809
|
+
encodeAtRest: (record, crypto) => encodeTaintAtRest(record, crypto, fields),
|
|
6810
|
+
decodeAtRest: (record, sealed, crypto, opts) => decodeTaintAtRest(record, sealed, crypto, opts, fields)
|
|
6811
|
+
} : {},
|
|
6812
|
+
...redactFields.length > 0 ? {
|
|
6813
|
+
present: (record) => {
|
|
6814
|
+
let out = record;
|
|
6815
|
+
for (const field of redactFields) {
|
|
6816
|
+
if (field in out) {
|
|
6817
|
+
if (out === record) out = { ...record };
|
|
6818
|
+
out[field] = EXPORT_REDACTION_MARKER;
|
|
6819
|
+
}
|
|
6820
|
+
}
|
|
6821
|
+
return out;
|
|
6822
|
+
}
|
|
6823
|
+
} : {}
|
|
6824
|
+
};
|
|
6825
|
+
}
|
|
6826
|
+
|
|
6827
|
+
// src/kernel/via-graph-wiring.ts
|
|
6828
|
+
var CLASSIFIED_POSTURE = { encryptedAtRest: "sealed", queryable: "det-exact", exportable: false, forgettable: true };
|
|
6829
|
+
function registerCollectionGraphSources(graph, name, opts) {
|
|
6830
|
+
const cfg = resolveCollectionConfig(opts);
|
|
6831
|
+
const bindings = cfg.via?.bindings ?? [];
|
|
6832
|
+
const knownFields = /* @__PURE__ */ new Set([
|
|
6833
|
+
...Object.keys(cfg.moneyFields ?? {}),
|
|
6834
|
+
...Object.keys(cfg.i18nFields ?? {}),
|
|
6835
|
+
...Object.keys(cfg.dictKeyFields ?? {}),
|
|
6836
|
+
// #650 Task 2 — native lookup()/enumOf()/dict() fields register their
|
|
6837
|
+
// posture (`lookupBinding().posture`) the same generic way every other
|
|
6838
|
+
// binding does; no `'ref'` EdgeKind / graph edge yet (Task 5).
|
|
6839
|
+
...Object.keys(cfg.lookupFields ?? {}),
|
|
6840
|
+
...cfg.classified !== void 0 ? Object.keys(cfg.classified.byField) : []
|
|
6841
|
+
]);
|
|
6842
|
+
for (const field of knownFields) {
|
|
6843
|
+
for (const binding of bindings) {
|
|
6844
|
+
if (binding.covers?.(field)) {
|
|
6845
|
+
graph.registerField(name, field, binding.posture);
|
|
6846
|
+
break;
|
|
6847
|
+
}
|
|
6848
|
+
}
|
|
6849
|
+
}
|
|
6850
|
+
for (const edge of cfg.computedEdges) graph.registerDerived(edge.target, edge.sources, "computed", edge.grain ?? "record");
|
|
6851
|
+
for (const edge of cfg.viaDepsEdges) graph.registerDerived(edge.target, edge.sources, "computed", edge.grain ?? "record");
|
|
6852
|
+
if (cfg.classified !== void 0) graph.markClassified(name);
|
|
6853
|
+
const depFields = new Set(cfg.computedEdges.map((edge) => edge.target.field));
|
|
6854
|
+
for (const field of cfg.computedFieldNames) {
|
|
6855
|
+
if (!depFields.has(field)) graph.markDepslessComputed(name, field);
|
|
6856
|
+
}
|
|
6857
|
+
}
|
|
6858
|
+
function validateReconcileGraphEdges(graph, name, options) {
|
|
6859
|
+
const resolvedClassified = options.classifiedFields !== void 0 ? resolveClassifiedFields(name, options.classifiedFields) : void 0;
|
|
6860
|
+
if (resolvedClassified !== void 0 && Object.values(resolvedClassified.byField).some((spec) => spec.storage !== "never")) {
|
|
6861
|
+
const riderNames = new Set(Object.keys(resolvedClassified.riderComputed));
|
|
6862
|
+
const leaking = [...graph.depslessComputedFields(name)].filter((field) => !riderNames.has(field));
|
|
6863
|
+
if (leaking.length > 0) {
|
|
6864
|
+
const field = leaking[0];
|
|
6865
|
+
throw new ValidationError(
|
|
6866
|
+
`Collection "${name}": computed field "${field}" has no declared \`deps\` and the collection declares classified fields \u2014 an opaque computed function could silently copy a classified field's plaintext into an ordinary, unredacted field. Declare \`deps\` naming the source fields it reads, e.g. computed: { ${field}: { fn, deps: [...] } }.`
|
|
6867
|
+
);
|
|
6868
|
+
}
|
|
6869
|
+
}
|
|
6870
|
+
const combinedHasClassified = graph.isClassified(name) || resolvedClassified !== void 0;
|
|
6871
|
+
let edges = [];
|
|
6872
|
+
let depslessComputedFields = [];
|
|
6873
|
+
if (options.computed !== void 0) {
|
|
6874
|
+
for (const [field, entry] of Object.entries(options.computed)) {
|
|
6875
|
+
if (computedEntryParts(entry).mode === "virtual") {
|
|
6876
|
+
throw new ValidationError(
|
|
6877
|
+
`Collection "${name}": computed field "${field}" declares mode: 'virtual' on a reconcile call \u2014 virtual computed fields are construction-only (no late-attach reconcile door); declare them in the collection's first vault.collection("${name}", { ... }) call.`
|
|
6878
|
+
);
|
|
6879
|
+
}
|
|
6880
|
+
}
|
|
6881
|
+
const knownFields = collectKnownFieldNames({
|
|
6882
|
+
moneyFields: options.moneyFields,
|
|
6883
|
+
classifiedFields: resolvedClassified?.byField,
|
|
6884
|
+
computed: options.computed
|
|
6885
|
+
});
|
|
6886
|
+
edges = resolveComputedEdges(name, options.computed, combinedHasClassified, knownFields);
|
|
6887
|
+
const depFields = new Set(edges.map((edge) => edge.target.field));
|
|
6888
|
+
depslessComputedFields = Object.keys(options.computed).filter((field) => !depFields.has(field));
|
|
6889
|
+
}
|
|
6890
|
+
return {
|
|
6891
|
+
edges,
|
|
6892
|
+
depslessComputedFields,
|
|
6893
|
+
classifiedFieldNames: resolvedClassified !== void 0 ? Object.keys(resolvedClassified.byField) : []
|
|
6894
|
+
};
|
|
6895
|
+
}
|
|
6896
|
+
function commitReconcileGraphEdges(graph, name, plan) {
|
|
6897
|
+
for (const edge of plan.edges) {
|
|
6898
|
+
if (!graph.hasDerived(edge.target)) graph.registerDerived(edge.target, edge.sources, "computed", edge.grain ?? "record");
|
|
6899
|
+
}
|
|
6900
|
+
for (const field of plan.depslessComputedFields) graph.markDepslessComputed(name, field);
|
|
6901
|
+
for (const field of plan.classifiedFieldNames) graph.registerField(name, field, CLASSIFIED_POSTURE);
|
|
6902
|
+
if (plan.classifiedFieldNames.length > 0) graph.markClassified(name);
|
|
6903
|
+
}
|
|
6904
|
+
function applyTaintOverlay(coll, graph, name) {
|
|
6905
|
+
const postures = buildTaintOverlay(graph.taintedPostures(name), graph.taintSealedFields(name));
|
|
6906
|
+
if (!postures) return;
|
|
6907
|
+
const sealFields = graph.taintSealedFields(name);
|
|
6908
|
+
const provenance = graph.taintProvenance(name);
|
|
6909
|
+
const virtualFields = graph.virtualFields(name);
|
|
6910
|
+
const virtualExportRedact = /* @__PURE__ */ new Set();
|
|
6911
|
+
for (const [field, posture] of postures) {
|
|
6912
|
+
if (posture.exportable === false && virtualFields.has(field)) virtualExportRedact.add(field);
|
|
6913
|
+
}
|
|
6914
|
+
const c = coll;
|
|
6915
|
+
const needsTaintBinding = sealFields.size > 0 || virtualExportRedact.size > 0;
|
|
6916
|
+
const bindings = needsTaintBinding ? [...c.via?.bindings ?? [], taintBinding(sealFields, virtualExportRedact)] : c.via?.bindings ?? [];
|
|
6917
|
+
const taint = { postures, sealFields, provenance };
|
|
6918
|
+
c.via = ViaPipeline.build(bindings, taint);
|
|
6919
|
+
c.codec.setVia(c.via);
|
|
6920
|
+
}
|
|
6921
|
+
|
|
5809
6922
|
// src/with-shape/satellites/registry.ts
|
|
5810
6923
|
var SatelliteRegistry = class {
|
|
5811
6924
|
byBase = /* @__PURE__ */ new Map();
|
|
@@ -5916,7 +7029,7 @@ function declareSatellite(ctx, collectionName, options, existingRegistry) {
|
|
|
5916
7029
|
ctx.forEachSyncEngine((e) => e.remirrorPairResolvers([spec.base, spec.satellite]));
|
|
5917
7030
|
const registry = reg;
|
|
5918
7031
|
const baseSchema = ctx.getBaseSchema(spec.base);
|
|
5919
|
-
void import("./post-register-
|
|
7032
|
+
void import("./post-register-MROIUA5O.js").then((m) => m.postRegister(ctx.adapter, ctx.vaultName, spec, ctx.getDEK, baseSchema, registry)).catch(() => {
|
|
5920
7033
|
});
|
|
5921
7034
|
return reg;
|
|
5922
7035
|
}
|
|
@@ -6409,50 +7522,25 @@ var VaultAttestation = class {
|
|
|
6409
7522
|
};
|
|
6410
7523
|
|
|
6411
7524
|
// src/kernel/vault.ts
|
|
6412
|
-
function resolveLabelFromMap(labels, locale, fallback) {
|
|
6413
|
-
if (labels[locale] !== void 0) return labels[locale];
|
|
6414
|
-
const chain = Array.isArray(fallback) ? fallback : fallback ? [fallback] : [];
|
|
6415
|
-
for (const fb of chain) {
|
|
6416
|
-
if (fb === "any") {
|
|
6417
|
-
const any = Object.values(labels)[0];
|
|
6418
|
-
if (any !== void 0) return any;
|
|
6419
|
-
} else if (labels[fb] !== void 0) {
|
|
6420
|
-
return labels[fb];
|
|
6421
|
-
}
|
|
6422
|
-
}
|
|
6423
|
-
return void 0;
|
|
6424
|
-
}
|
|
6425
7525
|
var Vault = class {
|
|
6426
7526
|
adapter;
|
|
6427
7527
|
/** The vault's name as passed to `openVault()`. Stable for the instance lifetime. */
|
|
6428
7528
|
name;
|
|
6429
|
-
/**
|
|
6430
|
-
*
|
|
6431
|
-
* (e.g. `as-*` reader `apply()` paths gating on `withTransactions()`)
|
|
6432
|
-
* reach the strategy seam without threading `db` through every API.
|
|
6433
|
-
*
|
|
6434
|
-
* Type-only Noydb import keeps the module graph acyclic at runtime.
|
|
6435
|
-
*/
|
|
7529
|
+
/** Backreference to the parent `Noydb` — lets vault-scoped services reach the strategy seam
|
|
7530
|
+
* without threading `db` through every API (type-only import keeps the module graph acyclic). */
|
|
6436
7531
|
noydb;
|
|
6437
|
-
/**
|
|
6438
|
-
*
|
|
6439
|
-
* needs to refresh it after restoring a different keyring file —
|
|
6440
|
-
* otherwise the in-memory DEKs (from the pre-load session) and
|
|
6441
|
-
* the on-disk wrapped DEKs (from the loaded backup) drift apart
|
|
6442
|
-
* and every subsequent decrypt fails with TamperedError.
|
|
6443
|
-
*/
|
|
7532
|
+
/** The active in-memory keyring. NOT readonly — `load()` refreshes it after restoring a
|
|
7533
|
+
* different keyring file, or in-memory/on-disk DEKs drift apart and decrypt fails with TamperedError. */
|
|
6444
7534
|
keyring;
|
|
6445
7535
|
encrypted;
|
|
6446
7536
|
emitter;
|
|
6447
7537
|
onDirty;
|
|
6448
7538
|
onRegisterConflictResolver;
|
|
6449
7539
|
syncAdapter;
|
|
7540
|
+
getPurgeableTargets;
|
|
6450
7541
|
historyConfig;
|
|
6451
|
-
/**
|
|
6452
|
-
*
|
|
6453
|
-
* means "blobs are off for this vault"; every `collection.blob(id)`
|
|
6454
|
-
* call throws with a pointer at `@noy-db/hub/blobs`.
|
|
6455
|
-
*/
|
|
7542
|
+
/** tree-shake seam for the optional blob service; `undefined` means "blobs are off for this
|
|
7543
|
+
* vault" — every `collection.blob(id)` call throws with a pointer at `@noy-db/hub/blobs`. */
|
|
6456
7544
|
blobStrategy;
|
|
6457
7545
|
objectStore;
|
|
6458
7546
|
/** Cold-storage archival strategy (the archive target store). */
|
|
@@ -6465,11 +7553,8 @@ var Vault = class {
|
|
|
6465
7553
|
crdtStrategy;
|
|
6466
7554
|
tiersStrategy;
|
|
6467
7555
|
searchStrategy;
|
|
6468
|
-
/**
|
|
6469
|
-
*
|
|
6470
|
-
* `withCargo()` was passed. Public so the `extractPartition` free function
|
|
6471
|
-
* (which takes a `Vault`) routes through it.
|
|
6472
|
-
*/
|
|
7556
|
+
/** Cargo (partition extraction) strategy — `NO_CARGO` (throwing) unless `withCargo()` was passed.
|
|
7557
|
+
* Public so the `extractPartition` free function (which takes a `Vault`) routes through it. */
|
|
6473
7558
|
cargoStrategy;
|
|
6474
7559
|
sealedRecordStrategy;
|
|
6475
7560
|
portabilityStrategy;
|
|
@@ -6483,42 +7568,26 @@ var Vault = class {
|
|
|
6483
7568
|
i18nStrategy;
|
|
6484
7569
|
syncStrategy;
|
|
6485
7570
|
classifiedStrategy;
|
|
6486
|
-
/**
|
|
6487
|
-
*
|
|
6488
|
-
* `null` for vaults that never register any guard strategy. The
|
|
6489
|
-
* runtime class is dynamic-imported on demand so consumers that
|
|
6490
|
-
* never use guards don't pull `GuardRegistry`/`GuardExecutor` into
|
|
6491
|
-
* their bundle.
|
|
6492
|
-
*/
|
|
7571
|
+
/** Per-vault guard registry; `null` until `_initGuards()` runs (or for vaults that never register
|
|
7572
|
+
* one) — the runtime class is dynamic-imported on demand to keep it out of the floor bundle. */
|
|
6493
7573
|
guardRegistry = null;
|
|
6494
|
-
/**
|
|
6495
|
-
*
|
|
6496
|
-
*
|
|
6497
|
-
* least one strategy handle.
|
|
6498
|
-
*/
|
|
7574
|
+
/** Per-vault derivation/MV/overlay registries — same lazy-load contract as
|
|
7575
|
+
* `guardRegistry`: each stays `null` until its `_init*()` runs with at
|
|
7576
|
+
* least one strategy/MV/overlay handle. */
|
|
6499
7577
|
derivationRegistry = null;
|
|
6500
|
-
/**
|
|
6501
|
-
* Per-vault materialized-view registry. Same lazy-load
|
|
6502
|
-
* contract as `derivationRegistry` — `null` until
|
|
6503
|
-
* `_initMaterializedViews()` runs with at least one MV handle.
|
|
6504
|
-
*/
|
|
6505
7578
|
materializedViewRegistry = null;
|
|
6506
|
-
/**
|
|
6507
|
-
* Per-vault overlay registry. Same lazy-load contract as
|
|
6508
|
-
* `materializedViewRegistry` — `null` until `_initOverlayedViews()`
|
|
6509
|
-
* runs with at least one handle.
|
|
6510
|
-
*/
|
|
6511
7579
|
overlayedViewRegistry = null;
|
|
6512
|
-
/**
|
|
6513
|
-
*
|
|
6514
|
-
*
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
*
|
|
6520
|
-
*
|
|
6521
|
-
|
|
7580
|
+
/** Per-vault dependency graph (#638) — field postures + derivation/MV/overlay/
|
|
7581
|
+
* computed/via-deps edges. Metadata-only (field names, postures, grains —
|
|
7582
|
+
* never values or key material); always present, unlike the registries above. */
|
|
7583
|
+
graph;
|
|
7584
|
+
/** #638 Task 4 — open sync/cutover/restore touch collector, or `undefined` between batches. */
|
|
7585
|
+
_graphBatch;
|
|
7586
|
+
/** Cached read-only facades for guard/derivation callbacks — split by
|
|
7587
|
+
* resolution layer (`layer:'guard'` vs `'derivation'`, each field's
|
|
7588
|
+
* `onMissing` policy differs). Allocated eagerly inside `_initGuards()`/
|
|
7589
|
+
* `_initDerivations()` so read accessors stay synchronous; `null` for
|
|
7590
|
+
* vaults without that service. */
|
|
6522
7591
|
guardFacade = null;
|
|
6523
7592
|
derivationFacade = null;
|
|
6524
7593
|
getDEK;
|
|
@@ -6547,18 +7616,9 @@ var Vault = class {
|
|
|
6547
7616
|
* @see docs/superpowers/specs/2026-06-17-fr6-deed-custodian-liberate-design.md
|
|
6548
7617
|
*/
|
|
6549
7618
|
custody;
|
|
6550
|
-
/**
|
|
6551
|
-
*
|
|
6552
|
-
*
|
|
6553
|
-
* after the on-disk keyring file has been replaced — refreshes
|
|
6554
|
-
* `this.keyring` so the next DEK access uses the loaded wrapped
|
|
6555
|
-
* DEKs instead of the stale pre-load ones.
|
|
6556
|
-
*
|
|
6557
|
-
* Provided by Noydb at openVault() time. Tests that
|
|
6558
|
-
* construct Vault directly can pass `undefined`; load()
|
|
6559
|
-
* skips the refresh in that case (which is fine for plaintext
|
|
6560
|
-
* compartments — there's nothing to re-unwrap).
|
|
6561
|
-
*/
|
|
7619
|
+
/** Re-derives an UnlockedKeyring from the adapter using the active user's passphrase; called by
|
|
7620
|
+
* `load()` after the on-disk keyring file has been replaced. Provided by Noydb at openVault()
|
|
7621
|
+
* time; `undefined` in tests that construct Vault directly (load() then skips the refresh). */
|
|
6562
7622
|
reloadKeyring;
|
|
6563
7623
|
collectionCache = /* @__PURE__ */ new Map();
|
|
6564
7624
|
satelliteRegistry = null;
|
|
@@ -6640,46 +7700,22 @@ var Vault = class {
|
|
|
6640
7700
|
* should use separate Vault instances.
|
|
6641
7701
|
*/
|
|
6642
7702
|
consentContext = null;
|
|
6643
|
-
/**
|
|
6644
|
-
* Registry of dictKey fields declared across all collections in this
|
|
6645
|
-
* vault. Keyed by collection name → field name → dictionary name.
|
|
6646
|
-
* Used by `DictionaryHandle.rename()` to find and update all records
|
|
6647
|
-
* referencing a renamed key.
|
|
6648
|
-
*
|
|
6649
|
-
* Populated by `collection()` when the `dictKeyFields` option is passed.
|
|
6650
|
-
*/
|
|
7703
|
+
/** dictKeyField registry: collection name → field name → dictionary name — `DictionaryHandle.rename()`'s reference-update source; populated by `collection()` from `dictKeyFields`. */
|
|
6651
7704
|
dictKeyFieldRegistry = /* @__PURE__ */ new Map();
|
|
6652
|
-
/**
|
|
6653
|
-
* Names of dictionaries backed by a `staticDict()` descriptor.
|
|
6654
|
-
* A static dict skips the `dictKeyFieldRegistry` rename machinery, but the
|
|
6655
|
-
* vault must still *know* a name is static so `vault.dictionary(name)` can
|
|
6656
|
-
* refuse mutation (`StaticDictReadonlyError`). Populated at `collection()`
|
|
6657
|
-
* config time whenever a `StaticDictDescriptor` is seen.
|
|
6658
|
-
*/
|
|
7705
|
+
/** Names of `staticDict()`-backed dictionaries (skip rename tracking; `vault.dictionary(name)` refuses mutation on these via `StaticDictReadonlyError`). Populated at `collection()` config time. */
|
|
6659
7706
|
staticDictNames = /* @__PURE__ */ new Set();
|
|
6660
|
-
/**
|
|
6661
|
-
* Static-dict descriptors keyed by dictionary name. Backs the
|
|
6662
|
-
* read-path label resolver (resolve from the in-memory table) and the
|
|
6663
|
-
* query-seam `resolveDictSource` snapshot. Last writer wins when the same
|
|
6664
|
-
* name is registered by multiple collections (identical-across-vaults by
|
|
6665
|
-
* construction, so the tables match).
|
|
6666
|
-
*/
|
|
7707
|
+
/** Static-dict descriptors by dictionary name — backs the read-path label resolver and the `resolveDictSource` snapshot. Last writer wins across collections (tables match by construction). */
|
|
6667
7708
|
staticByName = /* @__PURE__ */ new Map();
|
|
6668
|
-
/**
|
|
6669
|
-
* Per-collection map of field name → StaticDictDescriptor. Used by
|
|
6670
|
-
* `enforceStaticDictOnPut` to validate stored codes against `desc.keys`.
|
|
6671
|
-
*/
|
|
7709
|
+
/** Per-collection field name → StaticDictDescriptor, validated by `enforceStaticDictOnPut`. */
|
|
6672
7710
|
staticDescriptorByField = /* @__PURE__ */ new Map();
|
|
6673
|
-
/**
|
|
6674
|
-
* Registry of i18nText fields declared across all collections. Keyed
|
|
6675
|
-
* by collection name → field name → I18nTextDescriptor. Used by
|
|
6676
|
-
* `applyI18nLocale` on reads and by `validateI18nTextValue` on puts.
|
|
6677
|
-
*
|
|
6678
|
-
* Populated by `collection()` when the `i18nFields` option is passed.
|
|
6679
|
-
*/
|
|
7711
|
+
/** i18nText fields: collection name → field name → descriptor. Used by `applyI18nLocale`/`validateI18nTextValue`; populated by `collection()` from `i18nFields`. */
|
|
6680
7712
|
i18nFieldRegistry = /* @__PURE__ */ new Map();
|
|
6681
7713
|
/** Cache of DictionaryHandle instances, one per dictionary name. */
|
|
6682
7714
|
dictionaryCache = /* @__PURE__ */ new Map();
|
|
7715
|
+
/** #650 Task 4 (#647) — declared reserved-lookup collection name → dimension name, for dimensions
|
|
7716
|
+
* with `backing:'reserved'` (dict()/dictKey()). The explicit sync-pull registry: NOT a blanket
|
|
7717
|
+
* underscore-glob — populated at `collection()`-declare time and at `dictionary()`-call time. */
|
|
7718
|
+
reservedLookupCollections = /* @__PURE__ */ new Map();
|
|
6683
7719
|
/** Registered link specs, keyed by link name; set by `vault.link()`. */
|
|
6684
7720
|
linkRegistry = /* @__PURE__ */ new Map();
|
|
6685
7721
|
/** Cache of link-set handles, one per link name (lazy -- see links()). */
|
|
@@ -6712,6 +7748,7 @@ var Vault = class {
|
|
|
6712
7748
|
this.onDirty = opts.onDirty;
|
|
6713
7749
|
this.onRegisterConflictResolver = opts.onRegisterConflictResolver;
|
|
6714
7750
|
this.syncAdapter = opts.syncAdapter;
|
|
7751
|
+
this.getPurgeableTargets = opts.getPurgeableTargets ?? (() => []);
|
|
6715
7752
|
this.blobStrategy = opts.blobStrategy;
|
|
6716
7753
|
this.objectStore = opts.objectStore;
|
|
6717
7754
|
this.archiveStrategy = opts.archiveStrategy;
|
|
@@ -6734,7 +7771,10 @@ var Vault = class {
|
|
|
6734
7771
|
userId: () => this.keyring.userId,
|
|
6735
7772
|
getDEK: (collection) => this.getDEK(collection),
|
|
6736
7773
|
getLedgerOrNull: () => this.getLedgerOrNull(),
|
|
6737
|
-
collection: (name) => this.collection(name)
|
|
7774
|
+
collection: (name) => this.collection(name),
|
|
7775
|
+
purgeDeleteMarkers: (before) => this._purgeDeleteMarkers(before),
|
|
7776
|
+
archiveRecords: (before) => this._archiveClosedPeriod(before),
|
|
7777
|
+
purgeTargets: (before) => this._purgePeriodTargets(before)
|
|
6738
7778
|
});
|
|
6739
7779
|
this.linksEnforcer = new VaultLinks({
|
|
6740
7780
|
refRegistry: this.refRegistry,
|
|
@@ -6752,6 +7792,7 @@ var Vault = class {
|
|
|
6752
7792
|
this.i18nStrategy = opts.i18nStrategy ?? NO_I18N;
|
|
6753
7793
|
this.syncStrategy = opts.syncStrategy ?? NO_SYNC;
|
|
6754
7794
|
this.classifiedStrategy = opts.classifiedStrategy ?? NO_CLASSIFIED;
|
|
7795
|
+
this.graph = new ViaGraph();
|
|
6755
7796
|
void opts.guardStrategies;
|
|
6756
7797
|
this.historyConfig = opts.historyConfig ?? { enabled: true };
|
|
6757
7798
|
this.reloadKeyring = opts.reloadKeyring;
|
|
@@ -6878,6 +7919,7 @@ var Vault = class {
|
|
|
6878
7919
|
}, collectionName, { ...options, satelliteOf: options.satelliteOf }, this.satelliteRegistry);
|
|
6879
7920
|
}
|
|
6880
7921
|
let coll = this.collectionCache.get(collectionName);
|
|
7922
|
+
const reconcilePlan = coll && (options?.computed || options?.classifiedFields) ? validateReconcileGraphEdges(this.graph, collectionName, options) : void 0;
|
|
6881
7923
|
if (coll && options?.moneyFields) {
|
|
6882
7924
|
coll._applyMoneyFields(options.moneyFields);
|
|
6883
7925
|
}
|
|
@@ -6893,12 +7935,17 @@ var Vault = class {
|
|
|
6893
7935
|
if (coll && options?.classifiedFields) {
|
|
6894
7936
|
coll._applyClassifiedFields(options.classifiedFields);
|
|
6895
7937
|
}
|
|
7938
|
+
if (reconcilePlan) {
|
|
7939
|
+
commitReconcileGraphEdges(this.graph, collectionName, reconcilePlan);
|
|
7940
|
+
applyTaintOverlay(coll, this.graph, collectionName);
|
|
7941
|
+
}
|
|
6896
7942
|
if (!coll) {
|
|
7943
|
+
const effectiveViaFields = mergeViaFields({ moneyFields: options?.moneyFields, i18nFields: options?.i18nFields, dictKeyFields: options?.dictKeyFields, lookupFields: options?.lookupFields, viaFields: options?.viaFields });
|
|
6897
7944
|
if (options?.refs) {
|
|
6898
7945
|
this.refRegistry.register(collectionName, options.refs);
|
|
6899
7946
|
}
|
|
6900
|
-
if (
|
|
6901
|
-
this.i18nFieldRegistry.set(collectionName,
|
|
7947
|
+
if (effectiveViaFields.i18nFields) {
|
|
7948
|
+
this.i18nFieldRegistry.set(collectionName, effectiveViaFields.i18nFields);
|
|
6902
7949
|
}
|
|
6903
7950
|
if (options?.blobFields) {
|
|
6904
7951
|
this.blobFieldsRegistry.set(collectionName, options.blobFields);
|
|
@@ -6909,10 +7956,10 @@ var Vault = class {
|
|
|
6909
7956
|
if (options?.attestation !== void 0) {
|
|
6910
7957
|
this.attestation.register(collectionName, options.attestation);
|
|
6911
7958
|
}
|
|
6912
|
-
if (
|
|
7959
|
+
if (effectiveViaFields.dictKeyFields || effectiveViaFields.lookupFields) {
|
|
6913
7960
|
const dictFieldMap = {};
|
|
6914
7961
|
const staticFieldMap = {};
|
|
6915
|
-
for (const [field, desc] of Object.entries(
|
|
7962
|
+
for (const [field, desc] of Object.entries(effectiveViaFields.dictKeyFields ?? {})) {
|
|
6916
7963
|
if (isStaticDictDescriptor(desc)) {
|
|
6917
7964
|
staticFieldMap[field] = desc;
|
|
6918
7965
|
this.staticDictNames.add(desc.name);
|
|
@@ -6921,8 +7968,18 @@ var Vault = class {
|
|
|
6921
7968
|
dictFieldMap[field] = desc.name;
|
|
6922
7969
|
}
|
|
6923
7970
|
}
|
|
7971
|
+
const lookupCompat = collectLookupDictCompat(effectiveViaFields.lookupFields);
|
|
7972
|
+
Object.assign(dictFieldMap, lookupCompat.dictFieldMap);
|
|
7973
|
+
for (const [field, desc] of lookupCompat.staticEntries) {
|
|
7974
|
+
staticFieldMap[field] = desc;
|
|
7975
|
+
this.staticDictNames.add(desc.name);
|
|
7976
|
+
this.staticByName.set(desc.name, desc);
|
|
7977
|
+
}
|
|
6924
7978
|
if (Object.keys(dictFieldMap).length > 0) {
|
|
6925
7979
|
this.dictKeyFieldRegistry.set(collectionName, dictFieldMap);
|
|
7980
|
+
for (const dictName of new Set(Object.values(dictFieldMap))) {
|
|
7981
|
+
this.reservedLookupCollections.set(dictCollectionName(dictName), dictName);
|
|
7982
|
+
}
|
|
6926
7983
|
}
|
|
6927
7984
|
if (Object.keys(staticFieldMap).length > 0) {
|
|
6928
7985
|
this.staticDescriptorByField.set(collectionName, staticFieldMap);
|
|
@@ -6937,7 +7994,7 @@ var Vault = class {
|
|
|
6937
7994
|
const strategies = options.schemaUpdate ?? [];
|
|
6938
7995
|
const work = (async () => {
|
|
6939
7996
|
const dek = await this.getDEK(collectionName);
|
|
6940
|
-
const { persistSchemaIfNeeded } = await import("./register-
|
|
7997
|
+
const { persistSchemaIfNeeded } = await import("./register-OVJKW7BS.js");
|
|
6941
7998
|
const result = await persistSchemaIfNeeded({
|
|
6942
7999
|
store: this.adapter,
|
|
6943
8000
|
vault: this.name,
|
|
@@ -7002,6 +8059,7 @@ var Vault = class {
|
|
|
7002
8059
|
defaultLocale: this.locale,
|
|
7003
8060
|
onRegisterConflictResolver: this.onRegisterConflictResolver,
|
|
7004
8061
|
onAccess: (op, id) => this._logConsent(op, collectionName, id),
|
|
8062
|
+
graphDispatch: { collect: (collection, id) => this._collectGraphTouch(collection, id) },
|
|
7005
8063
|
// Derivation source is only wired when the corresponding registry
|
|
7006
8064
|
// has been initialised. Guard source was removed in Track A slice 3b
|
|
7007
8065
|
// — guards now run via the gate bus in Noydb.#registerGuardGate.
|
|
@@ -7072,9 +8130,10 @@ var Vault = class {
|
|
|
7072
8130
|
if (options?.warmIndexOnOpen !== void 0) collOpts.warmIndexOnOpen = options.warmIndexOnOpen;
|
|
7073
8131
|
if (options?.textIndexPersist !== void 0) collOpts.textIndexPersist = options.textIndexPersist;
|
|
7074
8132
|
if (options?.moneyFields !== void 0) collOpts.moneyFields = options.moneyFields;
|
|
8133
|
+
if (options?.viaFields !== void 0) collOpts.viaFields = options.viaFields;
|
|
7075
8134
|
if (options?.computed !== void 0) collOpts.computed = options.computed;
|
|
7076
8135
|
if (options?.classifiedFields !== void 0) collOpts.classifiedFields = options.classifiedFields;
|
|
7077
|
-
if (
|
|
8136
|
+
if (effectiveViaFields.dictKeyFields !== void 0 || effectiveViaFields.lookupFields !== void 0) {
|
|
7078
8137
|
collOpts.dictLabelResolver = async (dictName, key, locale, fallback) => {
|
|
7079
8138
|
const stat = this.staticByName.get(dictName);
|
|
7080
8139
|
if (stat) {
|
|
@@ -7085,15 +8144,23 @@ var Vault = class {
|
|
|
7085
8144
|
return handle.resolveLabel(key, locale, fallback);
|
|
7086
8145
|
};
|
|
7087
8146
|
collOpts.getDictionary = async (name) => this.dictionary(name);
|
|
7088
|
-
collOpts.dictKeyFields = options
|
|
8147
|
+
collOpts.dictKeyFields = options?.dictKeyFields;
|
|
8148
|
+
}
|
|
8149
|
+
if (effectiveViaFields.lookupFields !== void 0) {
|
|
8150
|
+
collOpts.lookupLabelResolver = collOpts.dictLabelResolver;
|
|
8151
|
+
collOpts.getLookupBacking = (dimension) => async (key) => await this.collection(dimension).get(key) ?? void 0;
|
|
8152
|
+
collOpts.lookupFields = options?.lookupFields;
|
|
8153
|
+
collOpts.membership = (field, key) => checkLookupMembership(effectiveViaFields.lookupFields[field], key, (n) => this.dictionary(n), (n) => this.collection(n));
|
|
8154
|
+
collOpts.getAltIndex = (d) => buildLookupAltIndex(d, (n) => this.dictionary(n), (n) => this.collection(n));
|
|
8155
|
+
collOpts.snapshotFor = (d) => buildLookupSnapshotRows(d, (n) => this.reservedLookupCollections.has(dictCollectionName(n)), (n) => this.dictionary(n), (n) => this.collection(n));
|
|
7089
8156
|
}
|
|
7090
|
-
if (
|
|
8157
|
+
if (effectiveViaFields.i18nFields !== void 0 || effectiveViaFields.dictKeyFields !== void 0) {
|
|
7091
8158
|
collOpts.i18nPutValidator = (record) => {
|
|
7092
8159
|
this.enforceI18nOnPut(collectionName, record);
|
|
7093
8160
|
this.enforceStaticDictOnPut(collectionName, record);
|
|
7094
8161
|
};
|
|
7095
8162
|
}
|
|
7096
|
-
if (
|
|
8163
|
+
if (effectiveViaFields.i18nFields !== void 0 && this.translateText) {
|
|
7097
8164
|
collOpts.autoTranslateHook = this.translateText;
|
|
7098
8165
|
}
|
|
7099
8166
|
if (options?.fieldMeta !== void 0) {
|
|
@@ -7107,6 +8174,9 @@ var Vault = class {
|
|
|
7107
8174
|
}
|
|
7108
8175
|
coll = new Collection(collOpts);
|
|
7109
8176
|
this.collectionCache.set(collectionName, coll);
|
|
8177
|
+
registerCollectionGraphSources(this.graph, collectionName, collOpts);
|
|
8178
|
+
registerLookupRefEdges(this.graph, collectionName, effectiveViaFields.lookupFields);
|
|
8179
|
+
applyTaintOverlay(coll, this.graph, collectionName);
|
|
7110
8180
|
if (options?.warmIndexOnOpen === true && options.prefetch !== false) {
|
|
7111
8181
|
void coll.warmIndex().catch(() => {
|
|
7112
8182
|
});
|
|
@@ -7116,7 +8186,7 @@ var Vault = class {
|
|
|
7116
8186
|
const work = (async () => {
|
|
7117
8187
|
try {
|
|
7118
8188
|
const dek = await this.getDEK(collectionName);
|
|
7119
|
-
await (await import("./register-
|
|
8189
|
+
await (await import("./register-OVJKW7BS.js")).persistSchemaIfNeeded({
|
|
7120
8190
|
store: this.adapter,
|
|
7121
8191
|
vault: this.name,
|
|
7122
8192
|
collectionName,
|
|
@@ -7179,7 +8249,9 @@ var Vault = class {
|
|
|
7179
8249
|
async #runCutoverTransform(collectionName, transform) {
|
|
7180
8250
|
const coll = this.collectionCache.get(collectionName);
|
|
7181
8251
|
if (!coll) return;
|
|
8252
|
+
this._beginGraphBatch();
|
|
7182
8253
|
await coll._applyCutoverTransform(transform);
|
|
8254
|
+
await this._flushGraphBatch();
|
|
7183
8255
|
}
|
|
7184
8256
|
/**
|
|
7185
8257
|
* Refresh a loaded collection's view of one document from a peer
|
|
@@ -7191,12 +8263,45 @@ var Vault = class {
|
|
|
7191
8263
|
if (!coll) return;
|
|
7192
8264
|
await coll._applyRemoteChange(docId, action);
|
|
7193
8265
|
}
|
|
7194
|
-
/** @internal #598: refresh cache entries a sync-applied write rewrote underneath us. No-op if not loaded this session.
|
|
8266
|
+
/** @internal #598: refresh cache entries a sync-applied write rewrote underneath us. No-op if not loaded this session.
|
|
8267
|
+
* #650 Task 4 (#647): a reserved-lookup collection has no `Collection` instance — instead, refresh
|
|
8268
|
+
* its already-warmed `LookupHandle._syncCache` (membership/altIndex/snapshot reads never see a
|
|
8269
|
+
* stale verdict for a pulled vocabulary edit) and collect the touch into any open graph batch
|
|
8270
|
+
* (the sync-apply wave-reachability seam Task 5's ref edges will use). No-op if never warmed. */
|
|
7195
8271
|
async _invalidateSyncApplied(collection, id) {
|
|
7196
8272
|
const coll = this.collectionCache.get(collection);
|
|
7197
|
-
if (
|
|
7198
|
-
|
|
7199
|
-
|
|
8273
|
+
if (coll) {
|
|
8274
|
+
await coll._onRecordMutated(id, "put", "sync-apply");
|
|
8275
|
+
return;
|
|
8276
|
+
}
|
|
8277
|
+
const dimName = this.reservedLookupCollections.get(collection);
|
|
8278
|
+
if (dimName === void 0) return;
|
|
8279
|
+
await this.dictionaryCache.get(dimName)?._refreshSyncCache(id);
|
|
8280
|
+
this._collectGraphTouch(collection, id);
|
|
8281
|
+
}
|
|
8282
|
+
/** @internal #650 Task 4 (#647) — declared reserved-lookup collection names, for `SyncEngine.pull()`'s explicit prefix registry. */
|
|
8283
|
+
_reservedLookupCollectionNames() {
|
|
8284
|
+
return [...this.reservedLookupCollections.keys()];
|
|
8285
|
+
}
|
|
8286
|
+
/** @internal #638 Task 4 — open a graph-dispatch touch batch (sync pull()/push()/cutover). */
|
|
8287
|
+
_beginGraphBatch() {
|
|
8288
|
+
this._graphBatch = /* @__PURE__ */ new Map();
|
|
8289
|
+
}
|
|
8290
|
+
/** @internal #638 Task 4 — `Collection._onRecordMutated`'s sync-apply/cutover/restore hook; a
|
|
8291
|
+
* no-op when no batch is open (`_beginGraphBatch()` was never called for this call chain). */
|
|
8292
|
+
_collectGraphTouch(collection, id) {
|
|
8293
|
+
if (!this._graphBatch) return;
|
|
8294
|
+
this._graphBatch.set(collection, (this._graphBatch.get(collection) ?? /* @__PURE__ */ new Set()).add(id));
|
|
8295
|
+
}
|
|
8296
|
+
/** @internal #638 Task 4 — close the open batch and run ONE dispatch wave over it. */
|
|
8297
|
+
async _flushGraphBatch() {
|
|
8298
|
+
const batch = this._graphBatch;
|
|
8299
|
+
this._graphBatch = void 0;
|
|
8300
|
+
if (batch && batch.size > 0) await runGraphDispatchWave(this, batch);
|
|
8301
|
+
}
|
|
8302
|
+
/** @internal #638 Task 4 — cached-collection lookup for `runGraphDispatchWave` (`VaultLike`); never constructs. */
|
|
8303
|
+
_getCollection(name) {
|
|
8304
|
+
return this.collectionCache.get(name);
|
|
7200
8305
|
}
|
|
7201
8306
|
/**
|
|
7202
8307
|
* @internal #589 → #604. Physically remove delete markers with `_ts` strictly
|
|
@@ -7209,27 +8314,65 @@ var Vault = class {
|
|
|
7209
8314
|
* ONLY; #604's period-close lifecycle is what earns that assertion. Do not call
|
|
7210
8315
|
* it on live/unsettled data.
|
|
7211
8316
|
*
|
|
7212
|
-
* Emits no ledger/event
|
|
7213
|
-
* the audit record.
|
|
8317
|
+
* Emits no ledger/event itself — its callers (#604's `freezePeriod`, #615's
|
|
8318
|
+
* `purgePeriodTargets`) own the audit record. The sweep body is shared via
|
|
8319
|
+
* {@link _purgeMarkersOn}, which #615 also aims at push-only sync targets.
|
|
7214
8320
|
*
|
|
7215
|
-
* Purges the local adapter only
|
|
7216
|
-
*
|
|
7217
|
-
*
|
|
8321
|
+
* Purges the local adapter only. Markers already pushed to the vault's
|
|
8322
|
+
* push-only (`backup`/`archive`) sync targets are reclaimed by #615's
|
|
8323
|
+
* `vault.purgePeriodTargets`; `sync-peer` targets are left untouched (purging
|
|
8324
|
+
* them would re-open the resurrection window — the deferred half of #611).
|
|
7218
8325
|
*/
|
|
7219
8326
|
async _purgeDeleteMarkers(before, collections) {
|
|
7220
|
-
|
|
8327
|
+
return this._purgeMarkersOn(this.adapter, before, collections);
|
|
8328
|
+
}
|
|
8329
|
+
/**
|
|
8330
|
+
* @internal #615. Sweep delete markers with `_ts < before` off ANY store
|
|
8331
|
+
* (local adapter or a push-only sync target). Returns the count removed.
|
|
8332
|
+
*/
|
|
8333
|
+
async _purgeMarkersOn(store, before, collections) {
|
|
8334
|
+
const snapshot = await store.loadAll(this.name);
|
|
7221
8335
|
let removed = 0;
|
|
7222
8336
|
for (const [coll, records] of Object.entries(snapshot)) {
|
|
7223
8337
|
if (collections && !collections.includes(coll)) continue;
|
|
7224
8338
|
for (const [id, env] of Object.entries(records)) {
|
|
7225
8339
|
if (isDeleteMarker(env) && env._ts < before) {
|
|
7226
|
-
await
|
|
8340
|
+
await store.delete(this.name, coll, id);
|
|
7227
8341
|
removed++;
|
|
7228
8342
|
}
|
|
7229
8343
|
}
|
|
7230
8344
|
}
|
|
7231
8345
|
return removed;
|
|
7232
8346
|
}
|
|
8347
|
+
/**
|
|
8348
|
+
* @internal #613. Relocate this closed period's in-window records (those
|
|
8349
|
+
* with `_ts < before`) from the hot store to the configured cold tier,
|
|
8350
|
+
* via the routeStore's `compact({ before })`. Returns the count moved.
|
|
8351
|
+
* Requires a `routeStore` with a cold route (the `coldArchival`
|
|
8352
|
+
* capability); throws a clear error otherwise. Non-destructive — reads
|
|
8353
|
+
* fall through to cold, so this does NOT re-open the #589 window.
|
|
8354
|
+
*/
|
|
8355
|
+
async _archiveClosedPeriod(before) {
|
|
8356
|
+
const store = this.adapter;
|
|
8357
|
+
if (store.capabilities?.coldArchival !== true || typeof store.compact !== "function") {
|
|
8358
|
+
throw new ValidationError("archivePeriod: cold archival requires a routeStore with a cold route.");
|
|
8359
|
+
}
|
|
8360
|
+
return store.compact(this.name, { before });
|
|
8361
|
+
}
|
|
8362
|
+
/**
|
|
8363
|
+
* @internal #615. Sweep delete markers with `_ts < before` off each of the
|
|
8364
|
+
* vault's push-only sync targets, returning a per-target count. Skips
|
|
8365
|
+
* `sync-peer` targets by construction (getPurgeableTargets yields only
|
|
8366
|
+
* backup/archive). Never touches the local adapter.
|
|
8367
|
+
*/
|
|
8368
|
+
async _purgePeriodTargets(before) {
|
|
8369
|
+
const out = [];
|
|
8370
|
+
for (const t of this.getPurgeableTargets()) {
|
|
8371
|
+
const purgedCount = await this._purgeMarkersOn(t.store, before);
|
|
8372
|
+
out.push({ ...t.label !== void 0 ? { label: t.label } : {}, role: t.role, purgedCount });
|
|
8373
|
+
}
|
|
8374
|
+
return out;
|
|
8375
|
+
}
|
|
7233
8376
|
/**
|
|
7234
8377
|
* For a detected conflict: capture this tab's clobbered record,
|
|
7235
8378
|
* read the common ancestor from history, converge the cache to the store's
|
|
@@ -7300,8 +8443,12 @@ var Vault = class {
|
|
|
7300
8443
|
* Validate i18nText fields on a `put()`. Called by Collection just
|
|
7301
8444
|
* before the adapter write, after schema validation. Throws
|
|
7302
8445
|
* `MissingTranslationError` when a required translation is absent.
|
|
8446
|
+
*
|
|
8447
|
+
* Delegates through the Via registry (#623) — a no-op only when i18n was
|
|
8448
|
+
* never declared (mirrors the registry check below).
|
|
7303
8449
|
*/
|
|
7304
8450
|
enforceI18nOnPut(collectionName, record) {
|
|
8451
|
+
if (!isViaInstalled("i18n")) return;
|
|
7305
8452
|
const i18nFields = this.i18nFieldRegistry.get(collectionName);
|
|
7306
8453
|
if (!i18nFields || Object.keys(i18nFields).length === 0) return;
|
|
7307
8454
|
if (!record || typeof record !== "object") return;
|
|
@@ -7318,79 +8465,15 @@ var Vault = class {
|
|
|
7318
8465
|
* Validate staticDict codes on a `put()`. For each `staticDict()`
|
|
7319
8466
|
* field, every stored code must be a declared key of the descriptor's
|
|
7320
8467
|
* table, else `UnknownDictCodeError`. Opt out per descriptor with
|
|
7321
|
-
* `{ validateCodes: false }`.
|
|
7322
|
-
* field paths via `getAtPath` (same path support as i18n validation).
|
|
7323
|
-
*/
|
|
7324
|
-
enforceStaticDictOnPut(collectionName, record) {
|
|
7325
|
-
const staticFields = this.staticDescriptorByField.get(collectionName);
|
|
7326
|
-
if (!staticFields || Object.keys(staticFields).length === 0) return;
|
|
7327
|
-
if (!record || typeof record !== "object") return;
|
|
7328
|
-
const obj = record;
|
|
7329
|
-
for (const [field, desc] of Object.entries(staticFields)) {
|
|
7330
|
-
if (desc.validateCodes === false) continue;
|
|
7331
|
-
const known = new Set(desc.keys);
|
|
7332
|
-
const values = getAtPath(obj, field);
|
|
7333
|
-
for (const value of values) {
|
|
7334
|
-
if (value === void 0 || value === null) continue;
|
|
7335
|
-
const codes = Array.isArray(value) ? value : [value];
|
|
7336
|
-
for (const code of codes) {
|
|
7337
|
-
if (typeof code !== "string") continue;
|
|
7338
|
-
if (!known.has(code)) {
|
|
7339
|
-
throw new UnknownDictCodeError(desc.name, field, code);
|
|
7340
|
-
}
|
|
7341
|
-
}
|
|
7342
|
-
}
|
|
7343
|
-
}
|
|
7344
|
-
}
|
|
7345
|
-
/**
|
|
7346
|
-
* Apply locale resolution to a record for the given collection.
|
|
8468
|
+
* `{ validateCodes: false }`.
|
|
7347
8469
|
*
|
|
7348
|
-
*
|
|
7349
|
-
*
|
|
8470
|
+
* Delegates through the Via registry — see {@link enforceI18nOnPut}. The
|
|
8471
|
+
* per-field validation itself lives in `shape/via-lookup/registry.ts`
|
|
8472
|
+
* (#650 Task 1 extraction), reached via `port/with/lookup-strategy.ts`.
|
|
7350
8473
|
*/
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
|
|
7354
|
-
const hasStaticDisplay = staticFields !== void 0 && Object.values(staticFields).some((d) => d.displayLocale !== void 0);
|
|
7355
|
-
if (!locale && !hasStaticDisplay) return record;
|
|
7356
|
-
let result = record;
|
|
7357
|
-
if (locale) {
|
|
7358
|
-
const i18nFields = this.i18nFieldRegistry.get(collectionName);
|
|
7359
|
-
if (i18nFields && Object.keys(i18nFields).length > 0) {
|
|
7360
|
-
result = this.i18nStrategy.applyI18nLocale(result, i18nFields, locale, localeOpts.fallback);
|
|
7361
|
-
}
|
|
7362
|
-
}
|
|
7363
|
-
const dictFields = this.dictKeyFieldRegistry.get(collectionName);
|
|
7364
|
-
if (locale && dictFields && Object.keys(dictFields).length > 0 && locale !== "raw") {
|
|
7365
|
-
const withLabels = { ...result };
|
|
7366
|
-
for (const [field, dictName] of Object.entries(dictFields)) {
|
|
7367
|
-
const key = result[field];
|
|
7368
|
-
if (typeof key !== "string") continue;
|
|
7369
|
-
const handle = this.dictionary(dictName);
|
|
7370
|
-
const label = await handle.resolveLabel(key, locale, localeOpts.fallback);
|
|
7371
|
-
if (label !== void 0) {
|
|
7372
|
-
withLabels[`${field}Label`] = label;
|
|
7373
|
-
}
|
|
7374
|
-
}
|
|
7375
|
-
result = withLabels;
|
|
7376
|
-
}
|
|
7377
|
-
if (staticFields && Object.keys(staticFields).length > 0 && locale !== "raw") {
|
|
7378
|
-
const withLabels = { ...result };
|
|
7379
|
-
for (const [field, desc] of Object.entries(staticFields)) {
|
|
7380
|
-
const effLocale = locale ?? desc.displayLocale;
|
|
7381
|
-
if (!effLocale) continue;
|
|
7382
|
-
const key = result[field];
|
|
7383
|
-
if (typeof key !== "string") continue;
|
|
7384
|
-
const labels = desc.table[key];
|
|
7385
|
-
if (!labels) continue;
|
|
7386
|
-
const label = resolveLabelFromMap(labels, effLocale, localeOpts.fallback ?? desc.substitute);
|
|
7387
|
-
if (label !== void 0) {
|
|
7388
|
-
withLabels[`${field}Label`] = label;
|
|
7389
|
-
}
|
|
7390
|
-
}
|
|
7391
|
-
result = withLabels;
|
|
7392
|
-
}
|
|
7393
|
-
return result;
|
|
8474
|
+
enforceStaticDictOnPut(collectionName, record) {
|
|
8475
|
+
if (!isViaInstalled("i18n")) return;
|
|
8476
|
+
enforceStaticDictOnPut(this.staticDescriptorByField.get(collectionName), record);
|
|
7394
8477
|
}
|
|
7395
8478
|
/**
|
|
7396
8479
|
* Open a dictionary by name. Returns a `DictionaryHandle` for CRUD
|
|
@@ -7414,6 +8497,7 @@ var Vault = class {
|
|
|
7414
8497
|
if (this.staticDictNames.has(name)) {
|
|
7415
8498
|
throw new StaticDictReadonlyError(name);
|
|
7416
8499
|
}
|
|
8500
|
+
this.reservedLookupCollections.set(dictCollectionName(name), name);
|
|
7417
8501
|
let handle = this.dictionaryCache.get(name);
|
|
7418
8502
|
if (!handle) {
|
|
7419
8503
|
handle = this.i18nStrategy.buildDictionaryHandle({
|
|
@@ -7421,37 +8505,36 @@ var Vault = class {
|
|
|
7421
8505
|
compartmentName: this.name,
|
|
7422
8506
|
dictionaryName: name,
|
|
7423
8507
|
keyring: this.keyring,
|
|
7424
|
-
|
|
8508
|
+
reservedEnvelopes: makeReservedEnvelopes((collection) => this.getDEK(collection), ["_dict_"])("_dict_"),
|
|
7425
8509
|
encrypted: this.encrypted,
|
|
7426
8510
|
ledger: this.getLedgerOrNull() ?? void 0,
|
|
7427
8511
|
options,
|
|
7428
8512
|
// findAndUpdateReferences: rewrite dictKey fields in all
|
|
7429
|
-
// registered collections when rename() is called
|
|
8513
|
+
// registered collections when rename() is called. Body extracted to
|
|
8514
|
+
// shape/via-lookup/registry.ts (#650 Task 1) — reached via the
|
|
8515
|
+
// port/with/lookup-strategy.ts seam.
|
|
7430
8516
|
findAndUpdateReferences: async (dictionaryName, oldKey, newKey) => {
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
}
|
|
7450
|
-
}
|
|
7451
|
-
}
|
|
7452
|
-
}
|
|
8517
|
+
await updateReferencingRecords(
|
|
8518
|
+
this.dictKeyFieldRegistry,
|
|
8519
|
+
(collectionName) => this.collection(collectionName),
|
|
8520
|
+
dictionaryName,
|
|
8521
|
+
oldKey,
|
|
8522
|
+
newKey
|
|
8523
|
+
);
|
|
8524
|
+
},
|
|
8525
|
+
emitter: this.emitter,
|
|
8526
|
+
buildDeleteMarker,
|
|
8527
|
+
// #647 fix wave 1 — real kernel/enclave builder; LookupHandle can't import it directly
|
|
8528
|
+
// #650 Task 4 (#647) — choke-point participation for LOCAL writes: dirty-log tracking + a
|
|
8529
|
+
// one-shot graph-dispatch wave (local-write's `graphDispatch.collect`-equivalent; Task 5's ref edges give it dependents).
|
|
8530
|
+
onDirty: this.onDirty,
|
|
8531
|
+
onRecordMutated: async (collection, id) => {
|
|
8532
|
+
this._beginGraphBatch();
|
|
8533
|
+
this._collectGraphTouch(collection, id);
|
|
8534
|
+
await this._flushGraphBatch();
|
|
7453
8535
|
},
|
|
7454
|
-
|
|
8536
|
+
checkReferencesOnDelete: (key) => this.linksEnforcer.enforceLookupRefsOnDelete(this.graph, name, dictCollectionName(name), key)
|
|
8537
|
+
// #650 Task 5
|
|
7455
8538
|
});
|
|
7456
8539
|
this.dictionaryCache.set(name, handle);
|
|
7457
8540
|
}
|
|
@@ -7520,65 +8603,24 @@ var Vault = class {
|
|
|
7520
8603
|
return handle;
|
|
7521
8604
|
}
|
|
7522
8605
|
/**
|
|
7523
|
-
* Build a `JoinableSource` for a dictKey field, for use in dict joins
|
|
7524
|
-
|
|
7525
|
-
* records — one per dictionary entry — keyed by the stable key.
|
|
7526
|
-
*
|
|
7527
|
-
* Returns `null` when `field` is not a dictKey in `leftCollection`.
|
|
7528
|
-
*
|
|
7529
|
-
* The snapshot is built synchronously from whatever the dictionary
|
|
7530
|
-
* handle has in its cached state. For empty dictionaries this returns
|
|
7531
|
-
* an empty snapshot rather than `null`.
|
|
7532
|
-
*/
|
|
7533
|
-
/**
|
|
7534
|
-
* Build a `JoinableSource` for a dictKey field, for use in dict joins
|
|
7535
|
-
*. Returns a source whose snapshot contains
|
|
7536
|
-
* `{ key, labels, ...labels }` records — one per dictionary entry —
|
|
7537
|
-
* keyed by the stable key.
|
|
7538
|
-
*
|
|
7539
|
-
* The snapshot is built synchronously from the DictionaryHandle's
|
|
7540
|
-
* write-through cache, which is populated on every `put()`, `rename()`,
|
|
7541
|
-
* `delete()`, and `list()` call. For pre-existing data not yet touched
|
|
7542
|
-
* this session, call `await vault.dictionary(name).list()` first
|
|
7543
|
-
* to warm the cache.
|
|
8606
|
+
* Build a `JoinableSource` for a dictKey field, for use in dict joins.
|
|
8607
|
+
* Returns a source whose snapshot contains `{ key, labels, ...labels }`
|
|
8608
|
+
* records — one per dictionary entry — keyed by the stable key. Returns
|
|
8609
|
+
* `null` when `field` is not a dictKey in `leftCollection`.
|
|
7544
8610
|
*
|
|
7545
|
-
*
|
|
8611
|
+
* Body extracted to `shape/via-lookup/registry.ts` (#650 Task 1) —
|
|
8612
|
+
* reached via the `port/with/lookup-strategy.ts` seam. See that
|
|
8613
|
+
* function's doc comment for the static-vs-dynamic-dict split and the
|
|
8614
|
+
* cache-warming caveat.
|
|
7546
8615
|
*/
|
|
7547
8616
|
resolveDictSource(leftCollection, field) {
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
)
|
|
7554
|
-
|
|
7555
|
-
snapshot() {
|
|
7556
|
-
return rows;
|
|
7557
|
-
},
|
|
7558
|
-
lookupById(id) {
|
|
7559
|
-
return rows.find((e) => e["key"] === id);
|
|
7560
|
-
}
|
|
7561
|
-
};
|
|
7562
|
-
if (desc.displayLocale !== void 0) {
|
|
7563
|
-
;
|
|
7564
|
-
source.displayLocale = desc.displayLocale;
|
|
7565
|
-
}
|
|
7566
|
-
return source;
|
|
7567
|
-
}
|
|
7568
|
-
const dictFields = this.dictKeyFieldRegistry.get(leftCollection);
|
|
7569
|
-
if (!dictFields || !(field in dictFields)) return null;
|
|
7570
|
-
const dictName = dictFields[field];
|
|
7571
|
-
if (!dictName) return null;
|
|
7572
|
-
const handle = this.dictionary(dictName);
|
|
7573
|
-
return {
|
|
7574
|
-
snapshot() {
|
|
7575
|
-
return handle.snapshotEntries();
|
|
7576
|
-
},
|
|
7577
|
-
lookupById(id) {
|
|
7578
|
-
const entries = handle.snapshotEntries();
|
|
7579
|
-
return entries.find((e) => e["key"] === id);
|
|
7580
|
-
}
|
|
7581
|
-
};
|
|
8617
|
+
return resolveDictSource(
|
|
8618
|
+
leftCollection,
|
|
8619
|
+
field,
|
|
8620
|
+
this.staticDescriptorByField,
|
|
8621
|
+
this.dictKeyFieldRegistry,
|
|
8622
|
+
(name) => this.dictionary(name)
|
|
8623
|
+
);
|
|
7582
8624
|
}
|
|
7583
8625
|
/**
|
|
7584
8626
|
* Set or update the vault-default locale at runtime.
|
|
@@ -7782,7 +8824,9 @@ var Vault = class {
|
|
|
7782
8824
|
listRecordIds: (c) => this.adapter.list(this.name, c),
|
|
7783
8825
|
getRecord: async (c, id) => await this.collection(c).get(id, { locale: "raw" }),
|
|
7784
8826
|
getEnvelope: (c, id) => this.adapter.get(this.name, c, id),
|
|
7785
|
-
removeFromPrimary: (c, id) =>
|
|
8827
|
+
removeFromPrimary: async (c, id) => {
|
|
8828
|
+
await this.collection(c)._internalDelete(id);
|
|
8829
|
+
},
|
|
7786
8830
|
restoreToPrimary: async (c, id, env) => {
|
|
7787
8831
|
await this.adapter.put(this.name, c, id, env);
|
|
7788
8832
|
await this.collection(c)._invalidateCacheEntry(id);
|
|
@@ -7869,7 +8913,7 @@ var Vault = class {
|
|
|
7869
8913
|
* cascade guard. See `with-shape/links/vault-facade.ts`.
|
|
7870
8914
|
*/
|
|
7871
8915
|
async enforceRefsOnDelete(collectionName, id) {
|
|
7872
|
-
return this.linksEnforcer.enforceRefsOnDelete(collectionName, id);
|
|
8916
|
+
return this.linksEnforcer.enforceRefsOnDelete(this.graph, collectionName, id);
|
|
7873
8917
|
}
|
|
7874
8918
|
// ─── Join resolver) ────────────────────
|
|
7875
8919
|
/**
|
|
@@ -7973,23 +9017,19 @@ var Vault = class {
|
|
|
7973
9017
|
);
|
|
7974
9018
|
}
|
|
7975
9019
|
/**
|
|
7976
|
-
* GDPR crypto-shred of a data subject. Consults the encrypted subject
|
|
7977
|
-
*
|
|
7978
|
-
*
|
|
7979
|
-
*
|
|
7980
|
-
*
|
|
7981
|
-
* the collection DEK and every OTHER record stay intact. Then appends ONE
|
|
7982
|
-
* `op:'forget'` ledger entry whose `payloadHash` is `sha256Hex(subjectId)` —
|
|
7983
|
-
*
|
|
7984
|
-
*
|
|
7985
|
-
*
|
|
7986
|
-
*
|
|
7987
|
-
*
|
|
7988
|
-
*
|
|
7989
|
-
* is still tombstoned, but its pre-shred ciphertext (if leaked to a
|
|
7990
|
-
* backup before migration) stays decryptable. Migrate, then re-forget.
|
|
7991
|
-
* - `blobResidueCollections`: a shredded record still has blob attachments,
|
|
7992
|
-
* which are keyed off a separate `_blob` DEK and are out of scope here.
|
|
9020
|
+
* GDPR crypto-shred of a data subject. Consults the encrypted subject index and, per matching
|
|
9021
|
+
* record: rewrites the LIVE envelope to a tombstone (drops `_iv`/`_data`/`_cek`/`_det`), tombstones
|
|
9022
|
+
* every `_history` version, and fans out to derived residue via the graph (#622) — record-grain
|
|
9023
|
+
* artifacts (MV rows, derivation copies) ERASED, aggregate-grain rollups RECOMPUTED without the
|
|
9024
|
+
* forgotten contribution (skip+audit in a frozen period). The body/history become permanently
|
|
9025
|
+
* undecryptable while the collection DEK and every OTHER record stay intact. Then appends ONE
|
|
9026
|
+
* `op:'forget'` ledger entry whose `payloadHash` is `sha256Hex(subjectId)` — the chain still
|
|
9027
|
+
* `verify()`s, PROVING the subject existed and was erased without retaining any plaintext.
|
|
9028
|
+
*
|
|
9029
|
+
* Reports — but does not silently swallow — two completeness gaps: `unmigratedRecords` (a record
|
|
9030
|
+
* whose body was NOT yet migrated to a per-record CEK — still tombstoned, but pre-shred backups
|
|
9031
|
+
* stay decryptable; migrate, then re-forget) and `blobResidueCollections` (blob attachments, keyed
|
|
9032
|
+
* off a separate `_blob` DEK, out of scope here).
|
|
7993
9033
|
*
|
|
7994
9034
|
* @throws ForgetStrategyNotConfiguredError when no `withForgetCascade` was set.
|
|
7995
9035
|
*/
|
|
@@ -8014,23 +9054,29 @@ var Vault = class {
|
|
|
8014
9054
|
const indexResidue = [];
|
|
8015
9055
|
const blobsEnabled = this.blobStrategy !== void 0;
|
|
8016
9056
|
const actor = this.keyring.userId;
|
|
9057
|
+
const fanoutStats = { recordsErased: 0, aggregatesRecomputed: 0, residueFrozen: [], lookupReferencesCascaded: 0, lookupReferencesNullified: 0, lookupReferencesResidue: [] };
|
|
8017
9058
|
for (const ref2 of allRefs) {
|
|
8018
9059
|
const coll = this.collection(ref2.collection);
|
|
8019
9060
|
const satelliteOf = ref2.satelliteOf;
|
|
8020
9061
|
const perRecordKeys = this.forgetStrategy.subjects[satelliteOf ?? ref2.collection] !== void 0;
|
|
9062
|
+
const lookupCompareKeys = await this.linksEnforcer.checkLookupRefsRestrict(this.graph, ref2.collection, ref2.collection, ref2.id);
|
|
8021
9063
|
const live = await this.adapter.get(this.name, ref2.collection, ref2.id);
|
|
8022
9064
|
if (perRecordKeys && live && live._data && !hasPerRecordKey(live)) {
|
|
8023
9065
|
unmigratedRecords.push(`${ref2.collection}:${ref2.id}`);
|
|
8024
9066
|
}
|
|
8025
9067
|
if (live?._sealed !== void 0) {
|
|
8026
9068
|
try {
|
|
8027
|
-
const
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
|
|
9069
|
+
const viaReport = await coll._onViaErase(ref2.id, live);
|
|
9070
|
+
if (viaReport) {
|
|
9071
|
+
sealedFieldsShredded += viaReport.shredded;
|
|
9072
|
+
for (const entry of viaReport.residue) {
|
|
9073
|
+
if (entry.kind === "classified-sealed-dek-residue" && entry.field !== void 0) sealedResidue.push(`${ref2.collection}:${ref2.id}:${entry.field}`);
|
|
8031
9074
|
}
|
|
8032
|
-
|
|
8033
|
-
|
|
9075
|
+
} else {
|
|
9076
|
+
const cls = await coll._classifySealedShred(live);
|
|
9077
|
+
for (const slot of cls.slots) {
|
|
9078
|
+
if (slot.class === "shreddable" || slot.class === "live-shreddable+dekResidue-in-backups") sealedFieldsShredded += 1;
|
|
9079
|
+
if (slot.class === "dekResidue" || slot.class === "live-shreddable+dekResidue-in-backups") sealedResidue.push(`${ref2.collection}:${ref2.id}:${slot.field}`);
|
|
8034
9080
|
}
|
|
8035
9081
|
}
|
|
8036
9082
|
} catch {
|
|
@@ -8063,6 +9109,7 @@ var Vault = class {
|
|
|
8063
9109
|
if (shred !== null) {
|
|
8064
9110
|
recordsShredded++;
|
|
8065
9111
|
collections.add(ref2.collection);
|
|
9112
|
+
await forgetDerivedFanout(this, { collection: ref2.collection, id: ref2.id }, live, fanoutStats, lookupCompareKeys);
|
|
8066
9113
|
}
|
|
8067
9114
|
historyVersionsShredded += await this.historyStrategy.tombstoneHistory(
|
|
8068
9115
|
this.adapter,
|
|
@@ -8146,7 +9193,14 @@ var Vault = class {
|
|
|
8146
9193
|
sealedCekEnvelopesPurged,
|
|
8147
9194
|
sealedCekResidue,
|
|
8148
9195
|
sealedResidue,
|
|
8149
|
-
ledgerEntry
|
|
9196
|
+
ledgerEntry,
|
|
9197
|
+
derivedRecordsErased: fanoutStats.recordsErased,
|
|
9198
|
+
derivedAggregatesRecomputed: fanoutStats.aggregatesRecomputed,
|
|
9199
|
+
derivedResidueFrozen: fanoutStats.residueFrozen,
|
|
9200
|
+
lookupReferencesCascaded: fanoutStats.lookupReferencesCascaded,
|
|
9201
|
+
lookupReferencesNullified: fanoutStats.lookupReferencesNullified,
|
|
9202
|
+
lookupReferencesResidue: fanoutStats.lookupReferencesResidue
|
|
9203
|
+
// #650 Task 5 (+ review Important fix: residue)
|
|
8150
9204
|
};
|
|
8151
9205
|
}
|
|
8152
9206
|
// ─── Record-scoped CEK sealing ─────────────────────────────────────
|
|
@@ -8242,7 +9296,7 @@ var Vault = class {
|
|
|
8242
9296
|
if (handles.length === 0) return;
|
|
8243
9297
|
const [{ GuardRegistry }, { ReadOnlyVaultFacade }] = await Promise.all([
|
|
8244
9298
|
import("./registry-VW6P6A3J.js"),
|
|
8245
|
-
import("./read-only-facade-
|
|
9299
|
+
import("./read-only-facade-UCYNRE56.js")
|
|
8246
9300
|
]);
|
|
8247
9301
|
const registry = new GuardRegistry();
|
|
8248
9302
|
for (const h of handles) registry.register(h.spec);
|
|
@@ -8269,14 +9323,15 @@ var Vault = class {
|
|
|
8269
9323
|
async _initDerivations(handles) {
|
|
8270
9324
|
if (handles.length === 0) return;
|
|
8271
9325
|
const [{ DerivationRegistry }, { ReadOnlyVaultFacade }] = await Promise.all([
|
|
8272
|
-
import("./registry-
|
|
8273
|
-
import("./read-only-facade-
|
|
9326
|
+
import("./registry-DQRNTWRL.js"),
|
|
9327
|
+
import("./read-only-facade-UCYNRE56.js")
|
|
8274
9328
|
]);
|
|
8275
9329
|
const registry = new DerivationRegistry();
|
|
8276
9330
|
for (const h of handles) {
|
|
8277
9331
|
await registry.register(h.spec);
|
|
8278
9332
|
}
|
|
8279
|
-
registry.
|
|
9333
|
+
for (const edge of registry.edges()) this.graph.registerDerived(edge.target, edge.sources, edge.kind, edge.grain);
|
|
9334
|
+
registry.validate(this.graph);
|
|
8280
9335
|
this.derivationRegistry = registry;
|
|
8281
9336
|
if (this.derivationFacade === null) {
|
|
8282
9337
|
this.derivationFacade = new ReadOnlyVaultFacade(this, "derivation");
|
|
@@ -8300,14 +9355,15 @@ var Vault = class {
|
|
|
8300
9355
|
*/
|
|
8301
9356
|
async _initMaterializedViews(handles) {
|
|
8302
9357
|
if (handles.length === 0) return;
|
|
8303
|
-
const { MaterializedViewRegistry } = await import("./registry-
|
|
9358
|
+
const { MaterializedViewRegistry } = await import("./registry-LTGLERS7.js");
|
|
8304
9359
|
const registry = new MaterializedViewRegistry();
|
|
8305
9360
|
this.materializedViewRegistry = registry;
|
|
8306
9361
|
const db = this;
|
|
8307
9362
|
for (const h of handles) {
|
|
8308
9363
|
await registry.register(h.spec, db);
|
|
8309
9364
|
}
|
|
8310
|
-
registry.
|
|
9365
|
+
for (const edge of registry.edges()) this.graph.registerDerived(edge.target, edge.sources, edge.kind, edge.grain);
|
|
9366
|
+
registry.validate(this.graph);
|
|
8311
9367
|
}
|
|
8312
9368
|
/**
|
|
8313
9369
|
* @internal — consumed by `Collection.put` at write-time. Returns
|
|
@@ -8324,7 +9380,7 @@ var Vault = class {
|
|
|
8324
9380
|
*/
|
|
8325
9381
|
async _initOverlayedViews(handles) {
|
|
8326
9382
|
if (handles.length === 0) return;
|
|
8327
|
-
const { OverlayedViewRegistry } = await import("./registry-
|
|
9383
|
+
const { OverlayedViewRegistry } = await import("./registry-5RJSK5AJ.js");
|
|
8328
9384
|
const registry = new OverlayedViewRegistry();
|
|
8329
9385
|
const mvRegistry = this.materializedViewRegistry;
|
|
8330
9386
|
const overlayNames = /* @__PURE__ */ new Set();
|
|
@@ -8341,6 +9397,7 @@ var Vault = class {
|
|
|
8341
9397
|
isOverlayName: (n) => overlayNames.has(n) && n !== h.spec.name,
|
|
8342
9398
|
isMVOutput
|
|
8343
9399
|
});
|
|
9400
|
+
this.graph.registerDerived({ collection: h.spec.name, field: "*" }, [{ collection: h.spec.base, field: "*" }], "overlay", "record");
|
|
8344
9401
|
}
|
|
8345
9402
|
this.overlayedViewRegistry = registry;
|
|
8346
9403
|
}
|
|
@@ -8351,16 +9408,15 @@ var Vault = class {
|
|
|
8351
9408
|
_getOverlayedViewRegistry() {
|
|
8352
9409
|
return this.overlayedViewRegistry;
|
|
8353
9410
|
}
|
|
9411
|
+
/** @internal — ctx for `putDerivedOutput`'s frozen-period skip+audit (#638 Task 5). */
|
|
9412
|
+
_dispatchCtx(source) {
|
|
9413
|
+
return { emit: (e, p) => this.emitter.emit(e, p), source, audit: ledgerAuditHook(this.getLedgerOrNull() ?? void 0, this.keyring.userId) };
|
|
9414
|
+
}
|
|
8354
9415
|
/**
|
|
8355
|
-
* Manual re-materialize for a single registered MV
|
|
8356
|
-
*
|
|
8357
|
-
*
|
|
8358
|
-
*
|
|
8359
|
-
*
|
|
8360
|
-
* Returns `{ written, deleted, failed }`. `deleted` is always 0
|
|
8361
|
-
* when tombstoning is not enabled.
|
|
8362
|
-
*
|
|
8363
|
-
* Throws if `name` is not a registered MV.
|
|
9416
|
+
* Manual re-materialize for a single registered MV (`refresh: 'manual'` consumers,
|
|
9417
|
+
* stale-bit recovery on vault reopen, bulk-recompute escape hatch after a strategy
|
|
9418
|
+
* change). Returns `{ written, deleted, failed }` (`deleted` always 0 without
|
|
9419
|
+
* tombstoning). Throws if `name` is not a registered MV.
|
|
8364
9420
|
*/
|
|
8365
9421
|
async refreshView(name) {
|
|
8366
9422
|
const registry = this.materializedViewRegistry;
|
|
@@ -8371,42 +9427,42 @@ var Vault = class {
|
|
|
8371
9427
|
if (!reg) {
|
|
8372
9428
|
throw new Error(`refreshView: no MV registered with name "${name}"`);
|
|
8373
9429
|
}
|
|
8374
|
-
const { MaterializedViewExecutor } = await import("./executor-
|
|
9430
|
+
const { MaterializedViewExecutor } = await import("./executor-IL7SRNUZ.js");
|
|
8375
9431
|
const result = await MaterializedViewExecutor.refresh(reg, {
|
|
8376
9432
|
getCollection: (n) => this.collection(n),
|
|
8377
9433
|
getActiveTxContext: () => this.noydb._activeTxContextOrNull,
|
|
8378
|
-
getQueryContext: () => this
|
|
9434
|
+
getQueryContext: () => this,
|
|
9435
|
+
dispatchCtx: this._dispatchCtx({ collection: name, id: "refreshView" })
|
|
8379
9436
|
});
|
|
8380
|
-
const { clearMVStale } = await import("./stale-
|
|
9437
|
+
const { clearMVStale } = await import("./stale-IYYXEH3U.js");
|
|
8381
9438
|
clearMVStale(registry, name);
|
|
8382
9439
|
return result;
|
|
8383
9440
|
}
|
|
8384
9441
|
/**
|
|
8385
|
-
* Re-derive every record in the named source collection
|
|
8386
|
-
*
|
|
8387
|
-
* up-to-date.
|
|
8388
|
-
*
|
|
8389
|
-
* Sequential in v1; parallelisation deferred to v2.
|
|
9442
|
+
* Re-derive every record in the named source collection (useful after a strategy
|
|
9443
|
+
* change to bring previously-derived records up-to-date). Sequential in v1.
|
|
8390
9444
|
*/
|
|
8391
9445
|
async deriveAll(sourceCollection) {
|
|
8392
9446
|
const registry = this._getDerivationRegistry();
|
|
8393
|
-
if (registry === null) return { derived: 0, failed: 0 };
|
|
9447
|
+
if (registry === null) return { derived: 0, failed: 0, skippedFrozen: 0 };
|
|
8394
9448
|
const strategies = registry.strategiesForSource(sourceCollection);
|
|
8395
|
-
if (strategies.length === 0) return { derived: 0, failed: 0 };
|
|
8396
|
-
const { DerivationExecutor } = await import("./executor-
|
|
9449
|
+
if (strategies.length === 0) return { derived: 0, failed: 0, skippedFrozen: 0 };
|
|
9450
|
+
const { DerivationExecutor } = await import("./executor-WAEI6PDV.js");
|
|
8397
9451
|
const sourceColl = this.collection(sourceCollection);
|
|
8398
9452
|
const records = await sourceColl.list();
|
|
8399
|
-
const ctx = { vault: this.derivationFacade ?? new (await import("./read-only-facade-
|
|
9453
|
+
const ctx = { vault: this.derivationFacade ?? new (await import("./read-only-facade-UCYNRE56.js")).ReadOnlyVaultFacade(this, "derivation") };
|
|
8400
9454
|
let derived = 0;
|
|
8401
9455
|
let failed = 0;
|
|
9456
|
+
let skippedFrozen = 0;
|
|
8402
9457
|
for (const record of records) {
|
|
8403
9458
|
if (typeof record !== "object" || record === null) continue;
|
|
8404
9459
|
const id = record.id;
|
|
8405
9460
|
if (typeof id !== "string") continue;
|
|
9461
|
+
const dispatchCtx = this._dispatchCtx({ collection: sourceCollection, id });
|
|
8406
9462
|
for (const { spec, strategyHash } of strategies) {
|
|
8407
9463
|
const sourceWithId = { ...record, id };
|
|
8408
9464
|
const result = await DerivationExecutor.run(spec, sourceWithId, 0, strategyHash, ctx);
|
|
8409
|
-
let anyFailed = false;
|
|
9465
|
+
let anyFailed = false, anyFrozenSkip = false;
|
|
8410
9466
|
for (const key of Object.keys(spec.outputs)) {
|
|
8411
9467
|
const out = result.outputs[key];
|
|
8412
9468
|
if (!out) continue;
|
|
@@ -8418,7 +9474,7 @@ var Vault = class {
|
|
|
8418
9474
|
if (!outSpec) continue;
|
|
8419
9475
|
const outputColl = this.collection(outSpec.collection);
|
|
8420
9476
|
if (out.kind === "array") {
|
|
8421
|
-
const { loadFanoutSidecar, saveFanoutSidecar } = await import("./fanout-sidecar-
|
|
9477
|
+
const { loadFanoutSidecar, saveFanoutSidecar } = await import("./fanout-sidecar-CI5BCMQM.js");
|
|
8422
9478
|
const prior = await loadFanoutSidecar(this.adapter, this.name, spec.source, id, key, this.getDEK, this.encrypted);
|
|
8423
9479
|
const prevKeys = new Set(prior?.keys ?? []);
|
|
8424
9480
|
const newKeysList = out.entries.map((e) => e.key);
|
|
@@ -8428,7 +9484,7 @@ var Vault = class {
|
|
|
8428
9484
|
await outputColl._internalDelete(k);
|
|
8429
9485
|
}
|
|
8430
9486
|
for (const entry of out.entries) {
|
|
8431
|
-
await outputColl
|
|
9487
|
+
if (await putDerivedOutput(outputColl, entry.key, entry.value, dispatchCtx) === "skipped-frozen") anyFrozenSkip = true;
|
|
8432
9488
|
}
|
|
8433
9489
|
await saveFanoutSidecar(this.adapter, this.name, {
|
|
8434
9490
|
source: spec.source,
|
|
@@ -8443,13 +9499,14 @@ var Vault = class {
|
|
|
8443
9499
|
await outputColl._internalDelete(id);
|
|
8444
9500
|
continue;
|
|
8445
9501
|
}
|
|
8446
|
-
await outputColl
|
|
9502
|
+
if (await putDerivedOutput(outputColl, id, out.value, dispatchCtx) === "skipped-frozen") anyFrozenSkip = true;
|
|
8447
9503
|
}
|
|
8448
9504
|
if (anyFailed) failed++;
|
|
9505
|
+
else if (anyFrozenSkip) skippedFrozen++;
|
|
8449
9506
|
else derived++;
|
|
8450
9507
|
}
|
|
8451
9508
|
}
|
|
8452
|
-
return { derived, failed };
|
|
9509
|
+
return { derived, failed, skippedFrozen };
|
|
8453
9510
|
}
|
|
8454
9511
|
/**
|
|
8455
9512
|
* @internal — exposed for `runTransaction({ amendment: true })` so
|
|
@@ -8640,7 +9697,7 @@ var Vault = class {
|
|
|
8640
9697
|
* collection.
|
|
8641
9698
|
*/
|
|
8642
9699
|
async delegate(opts) {
|
|
8643
|
-
const { issueDelegation, DELEGATIONS_COLLECTION } = await import("./delegation-
|
|
9700
|
+
const { issueDelegation, DELEGATIONS_COLLECTION } = await import("./delegation-ZKII5UBI.js");
|
|
8644
9701
|
if (!this.keyring.kek) {
|
|
8645
9702
|
throw new ValidationError(
|
|
8646
9703
|
"issueDelegation: keyring.kek is null \u2014 issuing a delegation requires a tier-1 unlock. Re-authenticate at tier 1 (passphrase) first."
|
|
@@ -8662,7 +9719,7 @@ var Vault = class {
|
|
|
8662
9719
|
* if the id does not exist.
|
|
8663
9720
|
*/
|
|
8664
9721
|
async revokeDelegation(id) {
|
|
8665
|
-
const { revokeDelegation, DELEGATIONS_COLLECTION } = await import("./delegation-
|
|
9722
|
+
const { revokeDelegation, DELEGATIONS_COLLECTION } = await import("./delegation-ZKII5UBI.js");
|
|
8666
9723
|
await revokeDelegation(this.adapter, this.name, id);
|
|
8667
9724
|
void DELEGATIONS_COLLECTION;
|
|
8668
9725
|
}
|
|
@@ -8849,6 +9906,38 @@ var Vault = class {
|
|
|
8849
9906
|
async getPeriod(name) {
|
|
8850
9907
|
return this.periods.getPeriod(name);
|
|
8851
9908
|
}
|
|
9909
|
+
/**
|
|
9910
|
+
* Freeze a closed period (#604): purges in-window delete markers and
|
|
9911
|
+
* records a `_period_freezes` companion, never mutating the chained
|
|
9912
|
+
* `_periods` record. Idempotent. Purges the LOCAL adapter only — a
|
|
9913
|
+
* synced target's markers survive there and re-import on pull (benign),
|
|
9914
|
+
* and purging re-opens the #589 resurrection window for a peer offline
|
|
9915
|
+
* since before the cutoff (see periods.ts's "Freeze" section).
|
|
9916
|
+
*/
|
|
9917
|
+
async freezePeriod(name) {
|
|
9918
|
+
return this.periods.freezePeriod(name);
|
|
9919
|
+
}
|
|
9920
|
+
/**
|
|
9921
|
+
* Archive a closed period (#613): relocates its in-window records from the
|
|
9922
|
+
* hot store to the configured cold tier (via the routeStore's cold route),
|
|
9923
|
+
* recording a `_period_archives` companion + ledger entry, never mutating
|
|
9924
|
+
* the chained `_periods` record. Non-destructive (reads fall through to
|
|
9925
|
+
* cold) and idempotent. Requires a routeStore with a cold route.
|
|
9926
|
+
*/
|
|
9927
|
+
async archivePeriod(name) {
|
|
9928
|
+
return this.periods.archivePeriod(name);
|
|
9929
|
+
}
|
|
9930
|
+
/**
|
|
9931
|
+
* Target-purge a closed+frozen period (#615): sweeps delete markers off the
|
|
9932
|
+
* vault's push-only sync targets (`backup`/`archive`), recording a
|
|
9933
|
+
* `_period_target_purges` companion + ledger entry, never mutating the chained
|
|
9934
|
+
* `_periods` record. `sync-peer` targets are skipped. Requires the period be
|
|
9935
|
+
* frozen first. Idempotent; a vault with no push-only targets writes no
|
|
9936
|
+
* companion and is re-runnable.
|
|
9937
|
+
*/
|
|
9938
|
+
async purgePeriodTargets(name) {
|
|
9939
|
+
return this.periods.purgePeriodTargets(name);
|
|
9940
|
+
}
|
|
8852
9941
|
/** @internal — called by the gate bus before put/delete. */
|
|
8853
9942
|
async _assertTsWritable(existing, incoming) {
|
|
8854
9943
|
return this.periods.assertTsWritable(existing, incoming);
|
|
@@ -8878,7 +9967,7 @@ var Vault = class {
|
|
|
8878
9967
|
* @see docs/superpowers/specs/2026-05-22-schema-dump-design.md
|
|
8879
9968
|
*/
|
|
8880
9969
|
async dumpSchema(opts = {}) {
|
|
8881
|
-
const { dumpVaultSchema } = await import("./walk-
|
|
9970
|
+
const { dumpVaultSchema } = await import("./walk-76UCFXYV.js");
|
|
8882
9971
|
return dumpVaultSchema(this, opts);
|
|
8883
9972
|
}
|
|
8884
9973
|
/**
|
|
@@ -9013,7 +10102,7 @@ var Vault = class {
|
|
|
9013
10102
|
* @see https://github.com/vLannaAi/noy-db-docs/blob/main/content/docs/services/public-envelope.md
|
|
9014
10103
|
*/
|
|
9015
10104
|
async getPublicEnvelope(opts = {}) {
|
|
9016
|
-
const { readPublicEnvelope } = await import("./public-envelope-
|
|
10105
|
+
const { readPublicEnvelope } = await import("./public-envelope-J5PLQOW2.js");
|
|
9017
10106
|
return readPublicEnvelope(this.adapter, this.name, opts);
|
|
9018
10107
|
}
|
|
9019
10108
|
/**
|
|
@@ -9028,7 +10117,7 @@ var Vault = class {
|
|
|
9028
10117
|
* both modes round-trip cleanly.
|
|
9029
10118
|
*/
|
|
9030
10119
|
async dump() {
|
|
9031
|
-
const { dumpVault } = await import("./backup-
|
|
10120
|
+
const { dumpVault } = await import("./backup-ODXO7OGA.js");
|
|
9032
10121
|
return dumpVault(this.backupContext());
|
|
9033
10122
|
}
|
|
9034
10123
|
/**
|
|
@@ -9080,7 +10169,7 @@ var Vault = class {
|
|
|
9080
10169
|
* — there's no chain to verify against.
|
|
9081
10170
|
*/
|
|
9082
10171
|
async load(backupJson) {
|
|
9083
|
-
const { loadVault } = await import("./backup-
|
|
10172
|
+
const { loadVault } = await import("./backup-ODXO7OGA.js");
|
|
9084
10173
|
return loadVault(this.backupContext(), backupJson);
|
|
9085
10174
|
}
|
|
9086
10175
|
/**
|
|
@@ -9112,7 +10201,7 @@ var Vault = class {
|
|
|
9112
10201
|
* way to detect tampering of an in-place vault.
|
|
9113
10202
|
*/
|
|
9114
10203
|
async verifyBackupIntegrity() {
|
|
9115
|
-
const { verifyBackupIntegrity } = await import("./backup-
|
|
10204
|
+
const { verifyBackupIntegrity } = await import("./backup-ODXO7OGA.js");
|
|
9116
10205
|
return verifyBackupIntegrity(this.backupContext());
|
|
9117
10206
|
}
|
|
9118
10207
|
/**
|
|
@@ -9218,7 +10307,7 @@ var Vault = class {
|
|
|
9218
10307
|
const records = [];
|
|
9219
10308
|
for (const id of ids) {
|
|
9220
10309
|
const record = await coll.get(id, localeOpts);
|
|
9221
|
-
if (record !== null) records.push(record);
|
|
10310
|
+
if (record !== null) records.push(exportRedact(coll, record));
|
|
9222
10311
|
}
|
|
9223
10312
|
const chunk = {
|
|
9224
10313
|
collection: collectionName,
|
|
@@ -9237,7 +10326,7 @@ var Vault = class {
|
|
|
9237
10326
|
collection: collectionName,
|
|
9238
10327
|
schema,
|
|
9239
10328
|
refs,
|
|
9240
|
-
records: [record],
|
|
10329
|
+
records: [exportRedact(coll, record)],
|
|
9241
10330
|
...dictionaries !== void 0 ? { dictionaries } : {},
|
|
9242
10331
|
...ledgerHead ? { ledgerHead } : {}
|
|
9243
10332
|
};
|
|
@@ -9330,7 +10419,7 @@ var Vault = class {
|
|
|
9330
10419
|
* is the live validator object, not a serialization of it).
|
|
9331
10420
|
*/
|
|
9332
10421
|
async exportJSON(opts = {}) {
|
|
9333
|
-
const { exportVaultJSON } = await import("./backup-
|
|
10422
|
+
const { exportVaultJSON } = await import("./backup-ODXO7OGA.js");
|
|
9334
10423
|
return exportVaultJSON(this.backupContext(), opts);
|
|
9335
10424
|
}
|
|
9336
10425
|
};
|
|
@@ -10044,17 +11133,17 @@ function memoryStore() {
|
|
|
10044
11133
|
}
|
|
10045
11134
|
|
|
10046
11135
|
// src/with-party/session/strategy.ts
|
|
10047
|
-
function
|
|
11136
|
+
function notEnabled3(op) {
|
|
10048
11137
|
return new Error(
|
|
10049
11138
|
`${op} requires the session strategy. Import \`{ withSession }\` from "@noy-db/hub/session" and pass it to \`createNoydb({ sessionStrategy: withSession() })\`.`
|
|
10050
11139
|
);
|
|
10051
11140
|
}
|
|
10052
11141
|
var NO_SESSION = {
|
|
10053
11142
|
validateSessionPolicy() {
|
|
10054
|
-
throw
|
|
11143
|
+
throw notEnabled3("sessionPolicy");
|
|
10055
11144
|
},
|
|
10056
11145
|
createEnforcer() {
|
|
10057
|
-
throw
|
|
11146
|
+
throw notEnabled3("session policy enforcement");
|
|
10058
11147
|
},
|
|
10059
11148
|
revokeAllSessions() {
|
|
10060
11149
|
}
|
|
@@ -10958,6 +12047,7 @@ function createPlaintextKeyring(userId, debugPlaintext = false) {
|
|
|
10958
12047
|
...debugPlaintext ? { debugPlaintext } : {}
|
|
10959
12048
|
};
|
|
10960
12049
|
}
|
|
12050
|
+
var emptyPullResult = () => ({ pulled: 0, conflicts: [], errors: [] });
|
|
10961
12051
|
var Noydb = class {
|
|
10962
12052
|
options;
|
|
10963
12053
|
emitter = new NoydbEventEmitter();
|
|
@@ -11202,7 +12292,7 @@ var Noydb = class {
|
|
|
11202
12292
|
if (!facade) return;
|
|
11203
12293
|
const ctx = { existing, vault: facade, userId: e.userId, role: e.role };
|
|
11204
12294
|
await registry.runChecks(e.collection, incoming, ctx);
|
|
11205
|
-
const { GuardExecutor } = await import("./executor-
|
|
12295
|
+
const { GuardExecutor } = await import("./executor-OUA2Y6YC.js");
|
|
11206
12296
|
for (const g of guards) {
|
|
11207
12297
|
await GuardExecutor.checkFrozenFields(g, e.docId, existing, incoming, e.computedFieldNames);
|
|
11208
12298
|
}
|
|
@@ -11374,6 +12464,7 @@ var Noydb = class {
|
|
|
11374
12464
|
} : void 0,
|
|
11375
12465
|
onRegisterConflictResolver: syncEngine ? (resolverName, resolver) => syncEngine.registerConflictResolver(resolverName, resolver) : void 0,
|
|
11376
12466
|
syncAdapter: targets.length > 0 ? targets[0].store : void 0,
|
|
12467
|
+
getPurgeableTargets: () => targets.filter((t) => t.role === "backup" || t.role === "archive").map((t) => ({ store: t.store, role: t.role, ...t.label !== void 0 ? { label: t.label } : {} })),
|
|
11377
12468
|
historyConfig: this.options.history,
|
|
11378
12469
|
...this.options.blobStrategy !== void 0 ? { blobStrategy: this.options.blobStrategy } : {},
|
|
11379
12470
|
...this.options.objectStore !== void 0 ? { objectStore: this.options.objectStore } : {},
|
|
@@ -11422,6 +12513,8 @@ var Noydb = class {
|
|
|
11422
12513
|
});
|
|
11423
12514
|
this._forEachSyncEngine(name, (engine) => {
|
|
11424
12515
|
engine.setCacheInvalidator((collection, id) => comp._invalidateSyncApplied(collection, id));
|
|
12516
|
+
engine.setGraphBatchController({ begin: () => comp._beginGraphBatch(), flush: () => comp._flushGraphBatch() });
|
|
12517
|
+
engine.setReservedLookupSource({ collections: () => comp._reservedLookupCollectionNames() });
|
|
11425
12518
|
});
|
|
11426
12519
|
await comp._initGuards(this.options.guardStrategies ?? []);
|
|
11427
12520
|
await comp._initDerivations(this.options.derivationStrategies ?? []);
|
|
@@ -11894,9 +12987,13 @@ var Noydb = class {
|
|
|
11894
12987
|
const engine = this.getSyncEngine(vault);
|
|
11895
12988
|
return engine.push(options);
|
|
11896
12989
|
}
|
|
11897
|
-
/**
|
|
12990
|
+
/**
|
|
12991
|
+
* Pull remote changes to local for a vault. A `backup`/`archive` primary is a
|
|
12992
|
+
* push-only sink and is never pulled from (#616) — returns an empty result.
|
|
12993
|
+
*/
|
|
11898
12994
|
async pull(vault, options) {
|
|
11899
12995
|
const engine = this.getSyncEngine(vault);
|
|
12996
|
+
if (engine.role !== "sync-peer") return emptyPullResult();
|
|
11900
12997
|
return engine.pull(options);
|
|
11901
12998
|
}
|
|
11902
12999
|
/**
|
|
@@ -11905,7 +13002,7 @@ var Noydb = class {
|
|
|
11905
13002
|
*/
|
|
11906
13003
|
async sync(vault, options) {
|
|
11907
13004
|
const primary = this.getSyncEngine(vault);
|
|
11908
|
-
const result = await primary.sync(options);
|
|
13005
|
+
const result = primary.role === "sync-peer" ? await primary.sync(options) : { pull: emptyPullResult(), push: await primary.push(options?.push) };
|
|
11909
13006
|
for (const [key, engine] of this.syncEngines) {
|
|
11910
13007
|
if (key === vault) continue;
|
|
11911
13008
|
if (!key.startsWith(`${vault}::`)) continue;
|
|
@@ -12462,7 +13559,7 @@ var Noydb = class {
|
|
|
12462
13559
|
"setPublicEnvelope: the public-envelope feature is not enabled. Pass `publicEnvelope: true` (or a schema object) to `createNoydb`."
|
|
12463
13560
|
);
|
|
12464
13561
|
}
|
|
12465
|
-
const { loadPublicEnvelope, savePublicEnvelope, resolveSchema, validatePublicEnvelopeInput } = await import("./public-envelope-
|
|
13562
|
+
const { loadPublicEnvelope, savePublicEnvelope, resolveSchema, validatePublicEnvelopeInput } = await import("./public-envelope-J5PLQOW2.js");
|
|
12466
13563
|
const schema = resolveSchema(this.options.publicEnvelope);
|
|
12467
13564
|
validatePublicEnvelopeInput(input, schema);
|
|
12468
13565
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -12491,7 +13588,7 @@ var Noydb = class {
|
|
|
12491
13588
|
* regardless).
|
|
12492
13589
|
*/
|
|
12493
13590
|
async getPublicEnvelope(vault, opts = {}) {
|
|
12494
|
-
const { readPublicEnvelope } = await import("./public-envelope-
|
|
13591
|
+
const { readPublicEnvelope } = await import("./public-envelope-J5PLQOW2.js");
|
|
12495
13592
|
return readPublicEnvelope(this.options.store, vault, opts);
|
|
12496
13593
|
}
|
|
12497
13594
|
// ─── Auth introspection ─────────────────────────────────────────
|
|
@@ -12682,9 +13779,9 @@ async function createNoydb(options) {
|
|
|
12682
13779
|
throw new ValidationError("A secret (passphrase) or getKeyring callback is required when encryption is enabled");
|
|
12683
13780
|
}
|
|
12684
13781
|
if (!options.coordinationStrategy) options = { ...options, coordinationStrategy: await createDefaultCoordinationProvider(options.store) };
|
|
12685
|
-
if (!options.userApiFactory) options = { ...options, userApiFactory: (await import("./api-
|
|
13782
|
+
if (!options.userApiFactory) options = { ...options, userApiFactory: (await import("./api-HV4PDUWF.js")).createUserApi };
|
|
12686
13783
|
if (!options.policyFactory) {
|
|
12687
|
-
const policyModule = await import("./policy-
|
|
13784
|
+
const policyModule = await import("./policy-SDGRWVIL.js");
|
|
12688
13785
|
options = { ...options, policyFactory: policyModule.createNoydbPolicy, policyCheckGateFn: policyModule.checkGate };
|
|
12689
13786
|
}
|
|
12690
13787
|
return new Noydb(options);
|
|
@@ -12701,6 +13798,9 @@ function normalizeSyncTargets(sync) {
|
|
|
12701
13798
|
export {
|
|
12702
13799
|
memoryStore,
|
|
12703
13800
|
withArchive,
|
|
13801
|
+
buildLookupSnapshot,
|
|
13802
|
+
via,
|
|
13803
|
+
isViaFieldSpec,
|
|
12704
13804
|
Collection,
|
|
12705
13805
|
ELEVATION_AUDIT_COLLECTION,
|
|
12706
13806
|
ElevatedHandle,
|
|
@@ -12719,4 +13819,4 @@ export {
|
|
|
12719
13819
|
Noydb,
|
|
12720
13820
|
createNoydb
|
|
12721
13821
|
};
|
|
12722
|
-
//# sourceMappingURL=chunk-
|
|
13822
|
+
//# sourceMappingURL=chunk-4C4WBS4V.js.map
|