@noy-db/hub 0.6.0-pre.9 → 0.7.0-pre.0
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/CHANGELOG.md +7127 -0
- package/README.md +1 -1
- package/codemods/0.4.0-pre.json +24 -0
- package/codemods/0.6.0-pre.json +229 -0
- package/codemods/0.7.0-pre.json +258 -0
- package/dist/api-EQYRTZO2.js +20 -0
- package/dist/as/index.js +146 -0
- package/dist/as/index.js.map +1 -0
- package/dist/at/index.js +23 -0
- package/dist/attestation/index.js +19 -19
- package/dist/{backup-KULFZ2T4.js → backup-Z7ZBTAQ6.js} +36 -18
- package/dist/backup-Z7ZBTAQ6.js.map +1 -0
- package/dist/blobs/index.js +22 -20
- package/dist/blobs/index.js.map +1 -1
- package/dist/broker/index.js +12 -12
- package/dist/by/index.js +11 -0
- package/dist/cargo/index.js +40 -36
- package/dist/{chunk-YAWK6AWF.js → chunk-225O5CXQ.js} +2 -2
- package/dist/{chunk-MSWYQLMQ.js → chunk-2GXEQUEX.js} +2 -2
- package/dist/chunk-2GXEQUEX.js.map +1 -0
- package/dist/{chunk-3URQC7EH.js → chunk-2LPVI4PX.js} +355 -132
- package/dist/chunk-2LPVI4PX.js.map +1 -0
- package/dist/{chunk-JJNOLSG5.js → chunk-3L2MVE36.js} +15 -16
- package/dist/chunk-3L2MVE36.js.map +1 -0
- package/dist/{chunk-OF3L2SD6.js → chunk-3QARDAYW.js} +2 -2
- package/dist/{chunk-WLZ47GSL.js → chunk-467SKJ65.js} +12 -10
- package/dist/{chunk-WLZ47GSL.js.map → chunk-467SKJ65.js.map} +1 -1
- package/dist/{chunk-W3TVWF6N.js → chunk-4A4TYISP.js} +2 -2
- package/dist/{chunk-ZJADGNYF.js → chunk-4AOERLTV.js} +1 -1
- package/dist/chunk-4AOERLTV.js.map +1 -0
- package/dist/{chunk-CCZJJ4I2.js → chunk-4G25SDQL.js} +6 -6
- package/dist/{chunk-4I72F5DF.js → chunk-4LPF226H.js} +11 -14
- package/dist/chunk-4LPF226H.js.map +1 -0
- package/dist/{chunk-7F4DBNSP.js → chunk-4ZPE6SKS.js} +135 -18
- package/dist/chunk-4ZPE6SKS.js.map +1 -0
- package/dist/{chunk-7TSYBD3E.js → chunk-5263OEM3.js} +2 -2
- package/dist/{chunk-WFUIE565.js → chunk-57JTHWLQ.js} +17 -14
- package/dist/chunk-57JTHWLQ.js.map +1 -0
- package/dist/{chunk-FNSMZQS4.js → chunk-5ACAVJUI.js} +2 -2
- package/dist/{chunk-PDLLCOGD.js → chunk-5AFMP532.js} +20 -17
- package/dist/chunk-5AFMP532.js.map +1 -0
- package/dist/{chunk-JRKZTEZZ.js → chunk-5W7YXFV5.js} +444 -73
- package/dist/chunk-5W7YXFV5.js.map +1 -0
- package/dist/chunk-6EW2UCVF.js +190 -0
- package/dist/chunk-6EW2UCVF.js.map +1 -0
- package/dist/{chunk-4VUPLMZ6.js → chunk-6FOGTKBY.js} +68 -73
- package/dist/chunk-6FOGTKBY.js.map +1 -0
- package/dist/{chunk-VMCCGUQB.js → chunk-7LYUJNU3.js} +2 -2
- package/dist/{chunk-52JPHN5N.js → chunk-A2YW5EMU.js} +27 -149
- package/dist/chunk-A2YW5EMU.js.map +1 -0
- package/dist/{chunk-WLYIROCJ.js → chunk-AB5P32RE.js} +2 -2
- package/dist/{chunk-PL2GHSCM.js → chunk-ALB64CZM.js} +157 -90
- package/dist/chunk-ALB64CZM.js.map +1 -0
- package/dist/{chunk-SUFG5E73.js → chunk-ALUNQEBY.js} +2 -2
- package/dist/{chunk-CADUYBQD.js → chunk-ATL3ZVXT.js} +2 -2
- package/dist/{chunk-BWVKEOPE.js → chunk-ATV637J7.js} +7 -11
- package/dist/chunk-ATV637J7.js.map +1 -0
- package/dist/{chunk-DG27IUVI.js → chunk-CBRS2NEK.js} +2 -11
- package/dist/chunk-CBRS2NEK.js.map +1 -0
- package/dist/{chunk-7MNBICB5.js → chunk-CN6B5PEN.js} +16 -14
- package/dist/chunk-CN6B5PEN.js.map +1 -0
- package/dist/{chunk-6FJWSWFT.js → chunk-DBNYPLWY.js} +3 -3
- package/dist/{chunk-5JY5TSVY.js → chunk-DFAUMLD7.js} +25 -31
- package/dist/chunk-DFAUMLD7.js.map +1 -0
- package/dist/{chunk-FFDNKRCY.js → chunk-DKWPJOYH.js} +2 -2
- package/dist/chunk-DKWPJOYH.js.map +1 -0
- package/dist/{chunk-VJFMXYKP.js → chunk-E6YA6UAP.js} +2 -2
- package/dist/{chunk-WASHZV34.js → chunk-F23WQK3G.js} +65 -27
- package/dist/chunk-F23WQK3G.js.map +1 -0
- package/dist/{chunk-JIUVJ4M7.js → chunk-F7ZFRPGQ.js} +3 -3
- package/dist/{chunk-HXGUIPQB.js → chunk-FOG7YEJP.js} +3 -3
- package/dist/{chunk-QULVCV3M.js → chunk-GK4DRVUD.js} +6 -6
- package/dist/chunk-GK4DRVUD.js.map +1 -0
- package/dist/{chunk-BRVW2GS5.js → chunk-H36KSN6X.js} +87 -34
- package/dist/chunk-H36KSN6X.js.map +1 -0
- package/dist/{chunk-O2ERS4UJ.js → chunk-H4M7ADXY.js} +81 -52
- package/dist/chunk-H4M7ADXY.js.map +1 -0
- package/dist/{chunk-FZ6NYJ7L.js → chunk-H7HP56F6.js} +11 -8
- package/dist/chunk-H7HP56F6.js.map +1 -0
- package/dist/chunk-HK67PX4J.js +27 -0
- package/dist/chunk-HK67PX4J.js.map +1 -0
- package/dist/{chunk-X56RTUK3.js → chunk-HM4JSXX2.js} +3 -3
- package/dist/{chunk-AHLJSVRL.js → chunk-HTGRQZFB.js} +5 -7
- package/dist/chunk-HTGRQZFB.js.map +1 -0
- package/dist/{chunk-GQ3BX5EA.js → chunk-I3ISCYT7.js} +2 -2
- package/dist/{chunk-Q6TEJXBX.js → chunk-IESNYJXZ.js} +31 -49
- package/dist/chunk-IESNYJXZ.js.map +1 -0
- package/dist/{chunk-FUHKW252.js → chunk-J4MN6XIU.js} +2 -2
- package/dist/{chunk-ADW22QSB.js → chunk-J4UJ5FRV.js} +4 -4
- package/dist/{chunk-HNGSV2YC.js → chunk-JBIV4FLR.js} +67 -10
- package/dist/chunk-JBIV4FLR.js.map +1 -0
- package/dist/{chunk-HVQGCKY4.js → chunk-JVLSU7S2.js} +2 -2
- package/dist/{chunk-AR2JDFAE.js → chunk-KDJGJR7F.js} +2 -2
- package/dist/{chunk-NPP5R7FF.js → chunk-KN4W2OQ4.js} +22 -18
- package/dist/chunk-KN4W2OQ4.js.map +1 -0
- package/dist/{chunk-G6S5VVHP.js → chunk-L6GTLZRK.js} +10 -10
- package/dist/chunk-L6GTLZRK.js.map +1 -0
- package/dist/{chunk-2BMRJ6LQ.js → chunk-LF6DMX4C.js} +8 -11
- package/dist/{chunk-2BMRJ6LQ.js.map → chunk-LF6DMX4C.js.map} +1 -1
- package/dist/chunk-LFUID7OW.js +163 -0
- package/dist/chunk-LFUID7OW.js.map +1 -0
- package/dist/{chunk-747LRSOO.js → chunk-LKFKXS5K.js} +2 -2
- package/dist/{chunk-U5O6GIWZ.js → chunk-LR4Z47QD.js} +3 -3
- package/dist/{chunk-VZ3XFUQ7.js → chunk-MTBXJAS4.js} +16 -22
- package/dist/chunk-MTBXJAS4.js.map +1 -0
- package/dist/chunk-N4CNVSUF.js +66 -0
- package/dist/chunk-N4CNVSUF.js.map +1 -0
- package/dist/{chunk-RTAMLDWP.js → chunk-NLKLXXTO.js} +2 -2
- package/dist/{chunk-IGJCYUT5.js → chunk-NSITFOCJ.js} +1 -1
- package/dist/chunk-NSITFOCJ.js.map +1 -0
- package/dist/{chunk-FDX7OWWI.js → chunk-OPXR4PB2.js} +2 -2
- package/dist/{chunk-4GZ6XOVW.js → chunk-OWLVBOWO.js} +44 -42
- package/dist/chunk-OWLVBOWO.js.map +1 -0
- package/dist/{chunk-Z45YRGFO.js → chunk-P6ASQOZR.js} +2 -2
- package/dist/{chunk-5O5HRRTV.js → chunk-PCQDY6QN.js} +7 -7
- package/dist/{chunk-5O5HRRTV.js.map → chunk-PCQDY6QN.js.map} +1 -1
- package/dist/{chunk-BEFVYAR5.js → chunk-PGFKIOR3.js} +4 -4
- package/dist/chunk-PGFKIOR3.js.map +1 -0
- package/dist/{chunk-3FSX6LDG.js → chunk-POS53FG6.js} +18 -3
- package/dist/{chunk-3FSX6LDG.js.map → chunk-POS53FG6.js.map} +1 -1
- package/dist/{chunk-3IXIU6FP.js → chunk-PQ6Z2TAP.js} +7 -10
- package/dist/chunk-PQ6Z2TAP.js.map +1 -0
- package/dist/{chunk-CEJWMBXO.js → chunk-PUJTKLPL.js} +6 -2
- package/dist/chunk-PUJTKLPL.js.map +1 -0
- package/dist/{chunk-GAG7QOVO.js → chunk-QGJ4XIA7.js} +53 -18
- package/dist/chunk-QGJ4XIA7.js.map +1 -0
- package/dist/{chunk-2DHSSLOD.js → chunk-QHJKK77B.js} +13 -15
- package/dist/chunk-QHJKK77B.js.map +1 -0
- package/dist/{chunk-2Z3NCLNX.js → chunk-QI5NWLVW.js} +7 -7
- package/dist/{chunk-6SAFL5YE.js → chunk-RC4BROYR.js} +3 -3
- package/dist/{chunk-NJDQ4YSV.js → chunk-RDHUZ674.js} +4 -4
- package/dist/{chunk-YPYHVOXP.js → chunk-RK7N4OQ5.js} +2 -2
- package/dist/chunk-RK7N4OQ5.js.map +1 -0
- package/dist/{chunk-3737A6EA.js → chunk-S2VG3ABP.js} +127 -31
- package/dist/chunk-S2VG3ABP.js.map +1 -0
- package/dist/{chunk-3JVXVJ7L.js → chunk-S35S77YU.js} +10 -10
- package/dist/{chunk-KUDZVJKQ.js → chunk-SHILAQH5.js} +2 -2
- package/dist/{chunk-4UVDI4XE.js → chunk-T3PHCEN4.js} +4 -4
- package/dist/{chunk-55LKVALY.js → chunk-T7IR4N2S.js} +1 -3
- package/dist/chunk-T7IR4N2S.js.map +1 -0
- package/dist/{chunk-RXYYFSBB.js → chunk-TOV6VMHF.js} +6 -6
- package/dist/chunk-TOV6VMHF.js.map +1 -0
- package/dist/{chunk-W77KZHCW.js → chunk-TVIFAHZR.js} +16 -17
- package/dist/chunk-TVIFAHZR.js.map +1 -0
- package/dist/chunk-UBSFPGVJ.js +1 -0
- package/dist/{chunk-FAXZQ5UF.js → chunk-UCUALOSM.js} +2 -2
- package/dist/{chunk-4HDYQL3J.js → chunk-VAG47QHV.js} +2 -2
- package/dist/{chunk-35EBKWQU.js → chunk-VFIWDPXZ.js} +4 -4
- package/dist/{chunk-5ZKS6ZE2.js → chunk-VTG7327U.js} +4 -4
- package/dist/chunk-VTG7327U.js.map +1 -0
- package/dist/{chunk-VCETL77O.js → chunk-W5LHQXZU.js} +36 -7
- package/dist/chunk-W5LHQXZU.js.map +1 -0
- package/dist/{chunk-6NVPFKN3.js → chunk-WK5FSW7T.js} +92 -35
- package/dist/chunk-WK5FSW7T.js.map +1 -0
- package/dist/{chunk-HNHCQ556.js → chunk-WLLUM35R.js} +2 -2
- package/dist/{chunk-YJ6IONGA.js → chunk-X62MEF2O.js} +13 -5
- package/dist/chunk-X62MEF2O.js.map +1 -0
- package/dist/{chunk-7G6VDWXB.js → chunk-X67FCULK.js} +5 -5
- package/dist/chunk-X67FCULK.js.map +1 -0
- package/dist/{chunk-UHXCLPGC.js → chunk-XDVVBETV.js} +2 -2
- package/dist/chunk-XDVVBETV.js.map +1 -0
- package/dist/chunk-XJ3FEKEY.js +15 -0
- package/dist/chunk-XJ3FEKEY.js.map +1 -0
- package/dist/{chunk-E6427JU2.js → chunk-XSN63FUS.js} +19 -3
- package/dist/chunk-XSN63FUS.js.map +1 -0
- package/dist/{chunk-JRCYZEHU.js → chunk-XVU7J43V.js} +2 -2
- package/dist/{chunk-2GAJGUNM.js → chunk-Y3YLJORD.js} +408 -260
- package/dist/chunk-Y3YLJORD.js.map +1 -0
- package/dist/{chunk-6BU3L4UE.js → chunk-Y6GZ2P4M.js} +7 -10
- package/dist/chunk-Y6GZ2P4M.js.map +1 -0
- package/dist/{chunk-DMQCLZ4Z.js → chunk-YCSM3GZU.js} +2 -2
- package/dist/{chunk-RV3ARMPN.js → chunk-YKPGQJFU.js} +30 -180
- package/dist/chunk-YKPGQJFU.js.map +1 -0
- package/dist/{chunk-FMQJZH4R.js → chunk-YMLQBZFJ.js} +6 -6
- package/dist/{chunk-6HFM7EIT.js → chunk-YPSOHDNC.js} +2 -2
- package/dist/{chunk-CDB2HVQW.js → chunk-YXA2TCTZ.js} +14 -15
- package/dist/chunk-YXA2TCTZ.js.map +1 -0
- package/dist/{chunk-N35NC4JH.js → chunk-Z2PWOSCJ.js} +2 -2
- package/dist/{chunk-XWX7GL56.js → chunk-Z2WK53N6.js} +47 -23
- package/dist/chunk-Z2WK53N6.js.map +1 -0
- package/dist/{chunk-HPLWFNG4.js → chunk-Z7ZXZL6Y.js} +2 -2
- package/dist/{chunk-PNKHAKQ7.js → chunk-ZO252Q5S.js} +17 -10
- package/dist/chunk-ZO252Q5S.js.map +1 -0
- package/dist/classified/index.js +5 -5
- package/dist/{classified-marker-2KJAXRRO.js → classified-marker-BSQHU4JT.js} +3 -3
- package/dist/collection-facade-LG3NYZVB.js +45 -0
- package/dist/{computed-6G5M3UPH.js → computed-ASBNTLOK.js} +3 -3
- package/dist/consent/index.js +10 -10
- package/dist/cover/index.js +10 -3
- package/dist/custody/index.js +12 -11
- package/dist/{dead-filter-DCB62VWW.js → dead-filter-QKU56XTQ.js} +2 -2
- package/dist/debug/index.js +1 -1
- package/dist/{delegation-MO7YZYJS.js → delegation-XWRPYODW.js} +24 -23
- package/dist/delegation-XWRPYODW.js.map +1 -0
- package/dist/derivations/index.js +12 -12
- package/dist/derive-CXHCY46K.js +22 -0
- package/dist/directory/index.js +10 -10
- package/dist/{dispatch-MJ6SHB3Y.js → dispatch-4AP4PUZU.js} +18 -18
- package/dist/{dispatch-FZEAWKVM.js → dispatch-VR4LRXZ7.js} +9 -6
- package/dist/dispatch-VR4LRXZ7.js.map +1 -0
- package/dist/{enclave-JU4YIEPB.js → enclave-NYPT7WA7.js} +31 -9
- package/dist/executor-3KFRQETS.js +29 -0
- package/dist/executor-AFHQYRAU.js +9 -0
- package/dist/executor-YYDW6XQI.js +9 -0
- package/dist/export-accessible-BX2ILB4C.js +23 -0
- package/dist/extract-partition-UPOVITFI.js +38 -0
- package/dist/{fanout-sidecar-NNAJQXMX.js → fanout-sidecar-IWP3R4RC.js} +18 -27
- package/dist/fanout-sidecar-IWP3R4RC.js.map +1 -0
- package/dist/{fence-watcher-DHQ775JB.js → fence-watcher-ZVSMLKQD.js} +1 -1
- package/dist/fence-watcher-ZVSMLKQD.js.map +1 -0
- package/dist/find-SZBL2HPB.js +11 -0
- package/dist/forget/index.js +9 -9
- package/dist/guards/index.js +4 -4
- package/dist/history/index.js +23 -20
- package/dist/history/index.js.map +1 -1
- package/dist/i18n/index.js +14 -14
- package/dist/index.d.ts +35 -18
- package/dist/index.js +199 -204
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.js +4 -4
- package/dist/introspection/index.js +12 -11
- package/dist/issue-G53NB6EW.js +19 -0
- package/dist/kernel/collection-config.d.ts +9 -9
- package/dist/kernel/collection.d.ts +9 -3
- package/dist/kernel/constants.d.ts +36 -0
- package/dist/kernel/enclave/crypto.d.ts +52 -4
- package/dist/kernel/enclave/index.d.ts +10 -3
- package/dist/kernel/enclave/record-aad.d.ts +131 -0
- package/dist/kernel/enclave/record-envelope.d.ts +76 -0
- package/dist/kernel/enclave/record-keys/envelope-body.d.ts +34 -2
- package/dist/kernel/enclave/record-keys/lifecycle.d.ts +22 -5
- package/dist/kernel/enclave/record-keys/record-codec.d.ts +47 -14
- package/dist/kernel/enclave/record-keys/rekey-blob.d.ts +88 -0
- package/dist/kernel/enclave/record-keys/rekey.d.ts +91 -0
- package/dist/kernel/enclave/record-keys/tombstone.d.ts +3 -2
- package/dist/kernel/errors.d.ts +134 -14
- package/dist/kernel/merge-authority.d.ts +25 -0
- package/dist/kernel/noydb.d.ts +33 -10
- package/dist/kernel/query/builder.d.ts +51 -4
- package/dist/kernel/query/join.d.ts +27 -0
- package/dist/kernel/query/live.d.ts +31 -9
- package/dist/kernel/query/predicate.d.ts +9 -0
- package/dist/kernel/refs.d.ts +3 -1
- package/dist/kernel/secret-mode.d.ts +1 -1
- package/dist/kernel/sync-policy.d.ts +0 -2
- package/dist/kernel/tier-visibility.d.ts +34 -0
- package/dist/kernel/types.d.ts +145 -28
- package/dist/kernel/vault.d.ts +16 -7
- package/dist/kernel/via/compose.d.ts +3 -3
- package/dist/kernel/via/index.d.ts +15 -6
- package/dist/kernel/via/pipeline.d.ts +6 -6
- package/dist/kernel/via/reconcile.d.ts +14 -2
- package/dist/kernel/via/taint-binding.d.ts +3 -3
- package/dist/{ledger-AETNF4OM.js → ledger-T5K4P2D7.js} +11 -11
- package/dist/liberate-VEOZRESE.js +25 -0
- package/dist/{link-set-E5HNY67I.js → link-set-76JZ6YB4.js} +11 -11
- package/dist/{managed-secret-CWQ6PND5.js → managed-secret-6YC32VXG.js} +13 -5
- package/dist/materialized-views/index.js +27 -27
- package/dist/migrate-cek-LBRKYUZZ.js +19 -0
- package/dist/migrate-cek-LBRKYUZZ.js.map +1 -0
- package/dist/money/index.js +5 -5
- package/dist/noydb-EDJA4JNJ.js +86 -0
- package/dist/on/index.js +1 -0
- package/dist/overlay-views/index.js +4 -4
- package/dist/periods/index.js +12 -12
- package/dist/pod/index.js +79 -88
- package/dist/pod-handle-P6DPBM5U.js +41 -0
- package/dist/pod-handle-P6DPBM5U.js.map +1 -0
- package/dist/policy/index.js +11 -4
- package/dist/port/as/active.d.ts +10 -0
- package/dist/port/as/index.d.ts +32 -0
- package/dist/port/as/types.d.ts +113 -0
- package/dist/port/at/index.d.ts +37 -0
- package/dist/port/by/default-provider.d.ts +3 -3
- package/dist/port/by/index.d.ts +32 -10
- package/dist/port/by/types.d.ts +29 -11
- package/dist/port/on/index.d.ts +50 -0
- package/dist/port/to/index.d.ts +4 -4
- package/dist/port/to/locator.d.ts +54 -8
- package/dist/port/with/computed-strategy.d.ts +1 -1
- package/dist/port/with/device-seal-strategy.d.ts +2 -2
- package/dist/port/with/formats-strategy.d.ts +76 -0
- package/dist/port/with/i18n-strategy.d.ts +4 -1
- package/dist/port/with/lookup-strategy.d.ts +4 -1
- package/dist/port/with/merge-authority.d.ts +52 -0
- package/dist/port/with/service-bus.d.ts +0 -4
- package/dist/port/with/strategies.d.ts +13 -5
- package/dist/port/with/team-strategy.d.ts +5 -1
- package/dist/portability/index.js +8 -8
- package/dist/{post-register-2XSSMGEQ.js → post-register-53O7AJRZ.js} +6 -6
- package/dist/query/index.js +6 -6
- package/dist/reduce/index.js +6 -6
- package/dist/register-6TCBR6XD.js +32 -0
- package/dist/registry-DH2L4O2V.js +18 -0
- package/dist/registry-IWFMXCHX.js +9 -0
- package/dist/registry-Q3FG3OAX.js +9 -0
- package/dist/registry-YC3IFG3A.js +19 -0
- package/dist/request-withdrawal-NM5VYR6F.js +31 -0
- package/dist/{reveal-BW36KCOQ.js → reveal-XBIMHAXR.js} +6 -6
- package/dist/revoke-MJNYWO72.js +24 -0
- package/dist/satellites/index.js +1 -1
- package/dist/schema-update/index.js +2 -2
- package/dist/sealed-record/index.js +20 -13
- package/dist/sealed-record/index.js.map +1 -1
- package/dist/search/index.js +4 -4
- package/dist/{seed-GDV5PT4R.js → seed-NSNIGYXE.js} +20 -22
- package/dist/seed-NSNIGYXE.js.map +1 -0
- package/dist/sequence/index.js +9 -9
- package/dist/session/index.js +10 -10
- package/dist/shadow/index.js +2 -2
- package/dist/share-link/index.js +1 -1
- package/dist/signer-6XVS2AU3.js +25 -0
- package/dist/snapshots/index.js +10 -10
- package/dist/snapshots/index.js.map +1 -1
- package/dist/{stale-ARHFVL5U.js → stale-MCS46TFO.js} +10 -10
- package/dist/storage-ZXUT476I.js +23 -0
- package/dist/storage-ZXUT476I.js.map +1 -0
- package/dist/{store-coordination-provider-GFCUOBR6.js → store-coordination-provider-6FM26VQM.js} +22 -15
- package/dist/store-coordination-provider-6FM26VQM.js.map +1 -0
- package/dist/sync/index.js +11 -11
- package/dist/team/index.js +17 -16
- package/dist/tiers/index.js +11 -10
- package/dist/to/index.js +17 -9
- package/dist/to/index.js.map +1 -1
- package/dist/transactions/index.js +4 -4
- package/dist/{ulid-PTAIMDG4.js → ulid-VQW6ERQN.js} +2 -2
- package/dist/ulid-VQW6ERQN.js.map +1 -0
- package/dist/util/index.js +1 -1
- package/dist/vault-diff-FE5J35NJ.js +9 -0
- package/dist/vault-diff-FE5J35NJ.js.map +1 -0
- package/dist/vault-head/index.js +115 -0
- package/dist/vault-head/index.js.map +1 -0
- package/dist/{verify-RO5RTZOJ.js → verify-WYOFLSNA.js} +5 -5
- package/dist/via/blob/binding.d.ts +6 -6
- package/dist/via/blob/index.d.ts +3 -0
- package/dist/via/classified/binding.d.ts +9 -9
- package/dist/via/computed/binding.d.ts +2 -2
- package/dist/via/i18n/binding.d.ts +4 -4
- package/dist/via/lookup/binding.d.ts +5 -5
- package/dist/via/lookup/handle.d.ts +4 -1
- package/dist/via/lookup/index.d.ts +1 -1
- package/dist/via/lookup/snapshot.d.ts +1 -1
- package/dist/via/money/binding.d.ts +3 -3
- package/dist/via/money/normalize.d.ts +1 -1
- package/dist/via/money/where.d.ts +3 -3
- package/dist/walk-JNEOYCAU.js +21 -0
- package/dist/walk-JNEOYCAU.js.map +1 -0
- package/dist/with-audit/periods/periods.d.ts +1 -1
- package/dist/with-audit/periods/strategy.d.ts +1 -1
- package/dist/with-audit/sealed-record/index.d.ts +13 -0
- package/dist/with-audit/tiers/index.d.ts +17 -2
- package/dist/with-cargo/adopt-partition.d.ts +7 -7
- package/dist/with-cargo/extract-partition.d.ts +1 -1
- package/dist/with-cargo/floor.d.ts +1 -1
- package/dist/with-cargo/index.d.ts +2 -2
- package/dist/with-commit/history/history.d.ts +26 -0
- package/dist/with-commit/history/index.d.ts +2 -1
- package/dist/with-commit/history/ledger/store.d.ts +7 -1
- package/dist/with-commit/history/strategy.d.ts +11 -1
- package/dist/with-commit/numbering/index.d.ts +0 -5
- package/dist/with-commit/vault-head/active.d.ts +2 -0
- package/dist/with-commit/vault-head/head.d.ts +28 -0
- package/dist/with-commit/vault-head/index.d.ts +10 -0
- package/dist/with-commit/vault-head/strategy.d.ts +127 -0
- package/dist/with-commit/vault-head/verify.d.ts +90 -0
- package/dist/with-formula/materialized-views/dependency-analyzer.d.ts +4 -0
- package/dist/with-formula/materialized-views/projection-legs.d.ts +59 -0
- package/dist/with-formula/materialized-views/registry.d.ts +50 -0
- package/dist/with-formula/materialized-views/types.d.ts +68 -10
- package/dist/with-party/directory/cover/storage.d.ts +0 -8
- package/dist/with-party/directory/storage.d.ts +0 -11
- package/dist/with-party/directory/visibility.d.ts +0 -21
- package/dist/with-party/policy/storage.d.ts +0 -10
- package/dist/with-party/team/deed.d.ts +4 -4
- package/dist/with-party/team/device-seal.d.ts +5 -5
- package/dist/with-party/team/echo-ceremony.d.ts +2 -2
- package/dist/with-party/team/echo-secret.d.ts +4 -4
- package/dist/with-party/team/index.d.ts +3 -3
- package/dist/with-party/team/keyring.d.ts +141 -4
- package/dist/with-party/team/managed-secret.d.ts +21 -11
- package/dist/with-party/team/noydb-facade.d.ts +7 -3
- package/dist/with-party/team/{shamir-recovery-provider.d.ts → noydb-shamir.d.ts} +1 -1
- package/dist/with-party/team/recovery.d.ts +3 -3
- package/dist/with-party/team/roster-tag.d.ts +101 -0
- package/dist/with-party/team/rotate-recover.d.ts +5 -5
- package/dist/with-party/team/tiers.d.ts +2 -1
- package/dist/with-pod/format.d.ts +11 -13
- package/dist/with-pod/index.d.ts +10 -10
- package/dist/with-pod/open.d.ts +2 -2
- package/dist/with-pod/pod-handle.d.ts +19 -0
- package/dist/with-pod/pod-store.d.ts +0 -8
- package/dist/with-pod/{bundle.d.ts → pod.d.ts} +17 -25
- package/dist/with-pod/ulid.d.ts +1 -1
- package/dist/with-shape/blobs/blob-compaction.d.ts +13 -0
- package/dist/with-shape/blobs/blob-set.d.ts +83 -11
- package/dist/with-shape/blobs/orphan-report.d.ts +62 -0
- package/dist/with-shape/introspection/describe.d.ts +2 -2
- package/dist/with-shape/schema-update/fence-controller.d.ts +4 -4
- package/dist/with-shape/schema-update/fence-watcher.d.ts +4 -4
- package/dist/with-shape/schema-update/store-coordination-provider.d.ts +7 -6
- package/dist/with-sync/engine.d.ts +54 -5
- package/dist/with-sync/strategy.d.ts +3 -0
- package/dist/withdraw-accessible-ILTSXTIE.js +28 -0
- package/dist/withdraw-accessible-ILTSXTIE.js.map +1 -0
- package/package.json +29 -5
- package/dist/api-RXUMEKT3.js +0 -20
- package/dist/backup-KULFZ2T4.js.map +0 -1
- package/dist/bundle-handle-GFNHT52M.js +0 -36
- package/dist/bundle-handle-GFNHT52M.js.map +0 -1
- package/dist/chunk-2DHSSLOD.js.map +0 -1
- package/dist/chunk-2GAJGUNM.js.map +0 -1
- package/dist/chunk-3737A6EA.js.map +0 -1
- package/dist/chunk-3IXIU6FP.js.map +0 -1
- package/dist/chunk-3URQC7EH.js.map +0 -1
- package/dist/chunk-4GZ6XOVW.js.map +0 -1
- package/dist/chunk-4I72F5DF.js.map +0 -1
- package/dist/chunk-4VUPLMZ6.js.map +0 -1
- package/dist/chunk-52JPHN5N.js.map +0 -1
- package/dist/chunk-55LKVALY.js.map +0 -1
- package/dist/chunk-5JY5TSVY.js.map +0 -1
- package/dist/chunk-5ZKS6ZE2.js.map +0 -1
- package/dist/chunk-6BU3L4UE.js.map +0 -1
- package/dist/chunk-6NVPFKN3.js.map +0 -1
- package/dist/chunk-7F4DBNSP.js.map +0 -1
- package/dist/chunk-7G6VDWXB.js.map +0 -1
- package/dist/chunk-7H73RIE3.js +0 -45
- package/dist/chunk-7H73RIE3.js.map +0 -1
- package/dist/chunk-7MNBICB5.js.map +0 -1
- package/dist/chunk-AFHQATY4.js +0 -47
- package/dist/chunk-AFHQATY4.js.map +0 -1
- package/dist/chunk-AHLJSVRL.js.map +0 -1
- package/dist/chunk-BEFVYAR5.js.map +0 -1
- package/dist/chunk-BRVW2GS5.js.map +0 -1
- package/dist/chunk-BWVKEOPE.js.map +0 -1
- package/dist/chunk-CDB2HVQW.js.map +0 -1
- package/dist/chunk-CEJWMBXO.js.map +0 -1
- package/dist/chunk-DG27IUVI.js.map +0 -1
- package/dist/chunk-E6427JU2.js.map +0 -1
- package/dist/chunk-FFDNKRCY.js.map +0 -1
- package/dist/chunk-FZ6NYJ7L.js.map +0 -1
- package/dist/chunk-G6S5VVHP.js.map +0 -1
- package/dist/chunk-GAG7QOVO.js.map +0 -1
- package/dist/chunk-HNGSV2YC.js.map +0 -1
- package/dist/chunk-IGJCYUT5.js.map +0 -1
- package/dist/chunk-JJNOLSG5.js.map +0 -1
- package/dist/chunk-JRKZTEZZ.js.map +0 -1
- package/dist/chunk-MSWYQLMQ.js.map +0 -1
- package/dist/chunk-NPP5R7FF.js.map +0 -1
- package/dist/chunk-O2ERS4UJ.js.map +0 -1
- package/dist/chunk-PDLLCOGD.js.map +0 -1
- package/dist/chunk-PL2GHSCM.js.map +0 -1
- package/dist/chunk-PNKHAKQ7.js.map +0 -1
- package/dist/chunk-Q6TEJXBX.js.map +0 -1
- package/dist/chunk-QULVCV3M.js.map +0 -1
- package/dist/chunk-RV3ARMPN.js.map +0 -1
- package/dist/chunk-RXYYFSBB.js.map +0 -1
- package/dist/chunk-UHXCLPGC.js.map +0 -1
- package/dist/chunk-VCETL77O.js.map +0 -1
- package/dist/chunk-VZ3XFUQ7.js.map +0 -1
- package/dist/chunk-W77KZHCW.js.map +0 -1
- package/dist/chunk-WASHZV34.js.map +0 -1
- package/dist/chunk-WFUIE565.js.map +0 -1
- package/dist/chunk-XWX7GL56.js.map +0 -1
- package/dist/chunk-YJ6IONGA.js.map +0 -1
- package/dist/chunk-YPYHVOXP.js.map +0 -1
- package/dist/chunk-ZJADGNYF.js.map +0 -1
- package/dist/collection-facade-QEJIM6BC.js +0 -45
- package/dist/delegation-MO7YZYJS.js.map +0 -1
- package/dist/derive-IUA6JFLB.js +0 -22
- package/dist/dispatch-FZEAWKVM.js.map +0 -1
- package/dist/executor-CYJQLKKI.js +0 -9
- package/dist/executor-S4QIKEHW.js +0 -9
- package/dist/executor-YIQB2S6N.js +0 -29
- package/dist/export-accessible-4S36PQNR.js +0 -23
- package/dist/extract-partition-IO7FDZ2P.js +0 -38
- package/dist/fanout-sidecar-NNAJQXMX.js.map +0 -1
- package/dist/fence-watcher-DHQ775JB.js.map +0 -1
- package/dist/find-RBBYKXI7.js +0 -11
- package/dist/issue-RIVHSY2S.js +0 -19
- package/dist/liberate-GFN4C7RR.js +0 -24
- package/dist/noydb-ASEXNJS6.js +0 -83
- package/dist/register-4EFJCVYD.js +0 -32
- package/dist/registry-4SDHHOJ7.js +0 -18
- package/dist/registry-CZVINVQH.js +0 -9
- package/dist/registry-IKUR2MXD.js +0 -9
- package/dist/registry-OYTFGWVK.js +0 -19
- package/dist/request-withdrawal-KGXGDPVQ.js +0 -31
- package/dist/revoke-JFN773KF.js +0 -24
- package/dist/seed-GDV5PT4R.js.map +0 -1
- package/dist/signer-TA55QB6R.js +0 -25
- package/dist/storage-V7MTFAQJ.js +0 -23
- package/dist/store-coordination-provider-GFCUOBR6.js.map +0 -1
- package/dist/walk-3ZECBFDA.js +0 -21
- package/dist/with-pod/bundle-handle.d.ts +0 -9
- package/dist/withdraw-accessible-7PG4DYQK.js +0 -28
- /package/dist/{api-RXUMEKT3.js.map → api-EQYRTZO2.js.map} +0 -0
- /package/dist/{collection-facade-QEJIM6BC.js.map → at/index.js.map} +0 -0
- /package/dist/{computed-6G5M3UPH.js.map → by/index.js.map} +0 -0
- /package/dist/{chunk-YAWK6AWF.js.map → chunk-225O5CXQ.js.map} +0 -0
- /package/dist/{chunk-OF3L2SD6.js.map → chunk-3QARDAYW.js.map} +0 -0
- /package/dist/{chunk-W3TVWF6N.js.map → chunk-4A4TYISP.js.map} +0 -0
- /package/dist/{chunk-CCZJJ4I2.js.map → chunk-4G25SDQL.js.map} +0 -0
- /package/dist/{chunk-7TSYBD3E.js.map → chunk-5263OEM3.js.map} +0 -0
- /package/dist/{chunk-FNSMZQS4.js.map → chunk-5ACAVJUI.js.map} +0 -0
- /package/dist/{chunk-VMCCGUQB.js.map → chunk-7LYUJNU3.js.map} +0 -0
- /package/dist/{chunk-WLYIROCJ.js.map → chunk-AB5P32RE.js.map} +0 -0
- /package/dist/{chunk-SUFG5E73.js.map → chunk-ALUNQEBY.js.map} +0 -0
- /package/dist/{chunk-CADUYBQD.js.map → chunk-ATL3ZVXT.js.map} +0 -0
- /package/dist/{chunk-6FJWSWFT.js.map → chunk-DBNYPLWY.js.map} +0 -0
- /package/dist/{chunk-VJFMXYKP.js.map → chunk-E6YA6UAP.js.map} +0 -0
- /package/dist/{chunk-JIUVJ4M7.js.map → chunk-F7ZFRPGQ.js.map} +0 -0
- /package/dist/{chunk-HXGUIPQB.js.map → chunk-FOG7YEJP.js.map} +0 -0
- /package/dist/{chunk-X56RTUK3.js.map → chunk-HM4JSXX2.js.map} +0 -0
- /package/dist/{chunk-GQ3BX5EA.js.map → chunk-I3ISCYT7.js.map} +0 -0
- /package/dist/{chunk-FUHKW252.js.map → chunk-J4MN6XIU.js.map} +0 -0
- /package/dist/{chunk-ADW22QSB.js.map → chunk-J4UJ5FRV.js.map} +0 -0
- /package/dist/{chunk-HVQGCKY4.js.map → chunk-JVLSU7S2.js.map} +0 -0
- /package/dist/{chunk-AR2JDFAE.js.map → chunk-KDJGJR7F.js.map} +0 -0
- /package/dist/{chunk-747LRSOO.js.map → chunk-LKFKXS5K.js.map} +0 -0
- /package/dist/{chunk-U5O6GIWZ.js.map → chunk-LR4Z47QD.js.map} +0 -0
- /package/dist/{chunk-RTAMLDWP.js.map → chunk-NLKLXXTO.js.map} +0 -0
- /package/dist/{chunk-FDX7OWWI.js.map → chunk-OPXR4PB2.js.map} +0 -0
- /package/dist/{chunk-Z45YRGFO.js.map → chunk-P6ASQOZR.js.map} +0 -0
- /package/dist/{chunk-2Z3NCLNX.js.map → chunk-QI5NWLVW.js.map} +0 -0
- /package/dist/{chunk-6SAFL5YE.js.map → chunk-RC4BROYR.js.map} +0 -0
- /package/dist/{chunk-NJDQ4YSV.js.map → chunk-RDHUZ674.js.map} +0 -0
- /package/dist/{chunk-3JVXVJ7L.js.map → chunk-S35S77YU.js.map} +0 -0
- /package/dist/{chunk-KUDZVJKQ.js.map → chunk-SHILAQH5.js.map} +0 -0
- /package/dist/{chunk-4UVDI4XE.js.map → chunk-T3PHCEN4.js.map} +0 -0
- /package/dist/{derive-IUA6JFLB.js.map → chunk-UBSFPGVJ.js.map} +0 -0
- /package/dist/{chunk-FAXZQ5UF.js.map → chunk-UCUALOSM.js.map} +0 -0
- /package/dist/{chunk-4HDYQL3J.js.map → chunk-VAG47QHV.js.map} +0 -0
- /package/dist/{chunk-35EBKWQU.js.map → chunk-VFIWDPXZ.js.map} +0 -0
- /package/dist/{chunk-HNHCQ556.js.map → chunk-WLLUM35R.js.map} +0 -0
- /package/dist/{chunk-JRCYZEHU.js.map → chunk-XVU7J43V.js.map} +0 -0
- /package/dist/{chunk-DMQCLZ4Z.js.map → chunk-YCSM3GZU.js.map} +0 -0
- /package/dist/{chunk-FMQJZH4R.js.map → chunk-YMLQBZFJ.js.map} +0 -0
- /package/dist/{chunk-6HFM7EIT.js.map → chunk-YPSOHDNC.js.map} +0 -0
- /package/dist/{chunk-N35NC4JH.js.map → chunk-Z2PWOSCJ.js.map} +0 -0
- /package/dist/{chunk-HPLWFNG4.js.map → chunk-Z7ZXZL6Y.js.map} +0 -0
- /package/dist/{classified-marker-2KJAXRRO.js.map → classified-marker-BSQHU4JT.js.map} +0 -0
- /package/dist/{enclave-JU4YIEPB.js.map → collection-facade-LG3NYZVB.js.map} +0 -0
- /package/dist/{executor-CYJQLKKI.js.map → computed-ASBNTLOK.js.map} +0 -0
- /package/dist/{dead-filter-DCB62VWW.js.map → dead-filter-QKU56XTQ.js.map} +0 -0
- /package/dist/{executor-S4QIKEHW.js.map → derive-CXHCY46K.js.map} +0 -0
- /package/dist/{dispatch-MJ6SHB3Y.js.map → dispatch-4AP4PUZU.js.map} +0 -0
- /package/dist/{executor-YIQB2S6N.js.map → enclave-NYPT7WA7.js.map} +0 -0
- /package/dist/{export-accessible-4S36PQNR.js.map → executor-3KFRQETS.js.map} +0 -0
- /package/dist/{extract-partition-IO7FDZ2P.js.map → executor-AFHQYRAU.js.map} +0 -0
- /package/dist/{find-RBBYKXI7.js.map → executor-YYDW6XQI.js.map} +0 -0
- /package/dist/{issue-RIVHSY2S.js.map → export-accessible-BX2ILB4C.js.map} +0 -0
- /package/dist/{ledger-AETNF4OM.js.map → extract-partition-UPOVITFI.js.map} +0 -0
- /package/dist/{liberate-GFN4C7RR.js.map → find-SZBL2HPB.js.map} +0 -0
- /package/dist/{link-set-E5HNY67I.js.map → issue-G53NB6EW.js.map} +0 -0
- /package/dist/{managed-secret-CWQ6PND5.js.map → ledger-T5K4P2D7.js.map} +0 -0
- /package/dist/{noydb-ASEXNJS6.js.map → liberate-VEOZRESE.js.map} +0 -0
- /package/dist/{register-4EFJCVYD.js.map → link-set-76JZ6YB4.js.map} +0 -0
- /package/dist/{registry-4SDHHOJ7.js.map → managed-secret-6YC32VXG.js.map} +0 -0
- /package/dist/{registry-CZVINVQH.js.map → noydb-EDJA4JNJ.js.map} +0 -0
- /package/dist/{registry-IKUR2MXD.js.map → on/index.js.map} +0 -0
- /package/dist/{post-register-2XSSMGEQ.js.map → post-register-53O7AJRZ.js.map} +0 -0
- /package/dist/{registry-OYTFGWVK.js.map → register-6TCBR6XD.js.map} +0 -0
- /package/dist/{request-withdrawal-KGXGDPVQ.js.map → registry-DH2L4O2V.js.map} +0 -0
- /package/dist/{revoke-JFN773KF.js.map → registry-IWFMXCHX.js.map} +0 -0
- /package/dist/{signer-TA55QB6R.js.map → registry-Q3FG3OAX.js.map} +0 -0
- /package/dist/{stale-ARHFVL5U.js.map → registry-YC3IFG3A.js.map} +0 -0
- /package/dist/{storage-V7MTFAQJ.js.map → request-withdrawal-NM5VYR6F.js.map} +0 -0
- /package/dist/{reveal-BW36KCOQ.js.map → reveal-XBIMHAXR.js.map} +0 -0
- /package/dist/{ulid-PTAIMDG4.js.map → revoke-MJNYWO72.js.map} +0 -0
- /package/dist/{walk-3ZECBFDA.js.map → signer-6XVS2AU3.js.map} +0 -0
- /package/dist/{withdraw-accessible-7PG4DYQK.js.map → stale-MCS46TFO.js.map} +0 -0
- /package/dist/{verify-RO5RTZOJ.js.map → verify-WYOFLSNA.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/port/with/i18n-strategy.ts","../src/kernel/paths.ts","../src/via/i18n/policy.ts","../src/via/i18n/script.ts","../src/via/i18n/binding.ts","../src/via/i18n/core.ts"],"sourcesContent":["/**\n * i18n strategy seam (#623 Task 7 — moved out of `via/i18n/strategy.ts`\n * and `via/i18n/dictionary.ts`, precedent: `port/with/lazy-strategy.ts`).\n * Lives on the `/with` port (the one seam the kernel spine may import\n * statically) so `Collection`/`Vault` can hold the `NO_I18N` default and the\n * two reserved-name/brand predicates without a spine→service static import.\n *\n * Core imports `I18nStrategy` type-only + `NO_I18N` stub; real\n * `applyI18nLocale` / `validateI18nTextValue` / `DictionaryHandle` are only\n * reachable via `withI18n()` in `via/i18n/active.ts`.\n *\n * Solo apps that don't use `i18nText()` fields, don't declare `dictKey()`\n * fields, and don't open a `vault.dictionary(...)` handle ship none of the\n * ~854 LOC behind this seam.\n *\n * Behavior under NO_I18N:\n *\n * - **applyI18nLocale** — returns the record unchanged. Apps without\n * any i18n descriptors never observe a difference; apps that\n * *did* declare i18nText/dictKey fields without opting into the\n * strategy still get raw values back (locale resolution silently\n * skipped). The validators below ensure the misconfiguration is\n * caught at write time instead.\n * - **validateI18nTextValue** — throws when called. Only fires when\n * a collection declared `i18nFields`; if you declared the field,\n * you must opt in.\n * - **buildDictionaryHandle** — throws when called. Only fires when\n * user code calls `vault.dictionary(...)`.\n *\n * #650 Task 1 (via-lookup extraction, phase D): the real\n * `buildDictionaryHandle` (in `via/i18n/active.ts`) now delegates to\n * `withLookup().buildLookupHandle` (`via/lookup/active.ts`) — same\n * handle (`LookupHandle`, aliased as `DictionaryHandle`), new home. This\n * interface's shape is unchanged.\n *\n * @internal\n */\n\nimport type { NoydbStore } from '../../kernel/types.js'\nimport type { LedgerStore } from '../../with-commit/history/ledger/store.js'\nimport type { UnlockedKeyring } from '../../with-party/team/keyring.js'\nimport type { NoydbEventEmitter } from '../../kernel/events.js'\nimport type { I18nTextDescriptor } from '../../via/i18n/core.js'\nimport type { Layer } from '../../via/i18n/policy.js'\nimport type { ScriptWarning } from '../../via/i18n/script.js'\nimport type { DictKeyDescriptor, DictionaryHandle, DictionaryOptions, StaticDictDescriptor } from '../../via/i18n/dictionary.js'\nimport type { ViaCryptoCtx } from '../../kernel/via/index.js'\nimport type { EncryptedEnvelope } from '../../kernel/types.js'\n\n/**\n * Options accepted by `I18nStrategy.buildDictionaryHandle`. Mirrors\n * the `DictionaryHandle` constructor verbatim — kept here so core\n * code never imports the dictionary module at runtime.\n *\n * @internal\n */\nexport interface BuildDictionaryHandleOptions<Keys extends string = string> {\n adapter: NoydbStore\n compartmentName: string\n dictionaryName: string\n keyring: UnlockedKeyring\n /**\n * The `reservedEnvelopes('_dict_')` capability (#629 Task 4) — DictionaryHandle's\n * sanctioned crypto door onto its `_dict_<name>` collection. Bound by the\n * Vault to its own `getDEK`, the same per-collection-name DEK resolver\n * every other collection uses.\n */\n reservedEnvelopes: ReturnType<ViaCryptoCtx['reservedEnvelopes']>\n encrypted: boolean\n ledger: LedgerStore | undefined\n options: DictionaryOptions\n findAndUpdateReferences:\n | ((\n dictionaryName: string,\n oldKey: string,\n newKey: string,\n ) => Promise<void>)\n | undefined\n emitter: NoydbEventEmitter\n /**\n * Used by the active strategy to satisfy the generic-key parameter\n * on the returned handle. The NO_I18N stub never reads it.\n */\n // marker generic — runtime sees no value\n _keyMarker?: Keys\n /** #650 Task 4 (#647) — choke-point participation hooks, passed through to `buildLookupHandle`. */\n onDirty?: ((collection: string, id: string, action: 'put' | 'delete', version: number) => Promise<void>) | undefined\n onRecordMutated?: ((collection: string, id: string, action: 'put' | 'delete', version: number) => Promise<void>) | undefined\n /**\n * #647 fix wave 1 — mints a version-ordered delete-marker envelope, passed through to\n * `buildLookupHandle`. See `port/with/lookup-strategy.ts`'s `BuildLookupHandleOptions` doc\n * comment for why this is injected rather than imported by the handle itself.\n */\n buildDeleteMarker: (identity: { collection: string; id: string }, version: number, actor: string) => EncryptedEnvelope\n /** #650 Task 5 (#648) — the real reference check, passed through to `buildLookupHandle`. */\n checkReferencesOnDelete?: ((key: string) => Promise<unknown>) | undefined\n}\n\n/**\n * @internal\n */\nexport interface I18nStrategy {\n /**\n * Resolve `i18nText` fields on a record to the requested locale and\n * return a new object. Returns the input unchanged under\n * `NO_I18N`.\n */\n applyI18nLocale(\n record: Record<string, unknown>,\n fields: Record<string, I18nTextDescriptor>,\n locale: string,\n fallback?: string | readonly string[],\n layer?: Layer,\n ): Record<string, unknown>\n\n /**\n * Validate that an i18nText field's value satisfies its descriptor\n * (required locales present, etc.). Throws under `NO_I18N` —\n * declaring i18nFields without opting in is a misconfiguration.\n */\n validateI18nTextValue(\n value: unknown,\n field: string,\n descriptor: I18nTextDescriptor,\n ): void\n\n /**\n * Enforce per-locale script constraints over an i18nText value map\n * (write-time). Returns the (possibly filtered) value plus any\n * non-fatal warnings. Returns the value unchanged when the field has\n * no `script` option, or under `NO_I18N`.\n */\n enforceScript(\n value: Record<string, unknown>,\n field: string,\n descriptor: I18nTextDescriptor,\n exempt?: ReadonlySet<string>,\n ): { value: Record<string, unknown>; warnings: ScriptWarning[] }\n\n /**\n * Per-field locales that are unchanged round-tripped densify fills (exempt\n * from write-time script enforcement). Empty under NO_I18N.\n */\n computeExemptFills(\n prior: Record<string, unknown> | undefined,\n incoming: Record<string, unknown>,\n fields: Record<string, I18nTextDescriptor>,\n ): Map<string, Set<string>>\n\n /**\n * Eager-fill empty i18n slots from each field's substitute chain and record\n * provenance in `record['_i18nFilled']`. Mutates `record`. No-op under\n * NO_I18N.\n */\n densify(\n record: Record<string, unknown>,\n prior: Record<string, unknown> | undefined,\n fields: Record<string, I18nTextDescriptor>,\n ): void\n\n /**\n * Construct a typed `DictionaryHandle` for the named dictionary.\n * Throws under `NO_I18N`.\n */\n buildDictionaryHandle<Keys extends string = string>(\n opts: BuildDictionaryHandleOptions<Keys>,\n ): DictionaryHandle<Keys>\n}\n\nfunction notEnabled(op: string): Error {\n return new Error(\n `${op} requires the i18n strategy. Import ` +\n '`{ withI18n }` from \"@noy-db/hub/i18n\" and pass it to ' +\n '`createNoydb({ i18nStrategy: withI18n() })`.',\n )\n}\n\n/**\n * No-i18n stub. Locale resolution is the identity; validation and\n * dictionary construction throw with an actionable pointer.\n *\n * @internal\n */\nexport const NO_I18N: I18nStrategy = {\n applyI18nLocale(record) { return record },\n validateI18nTextValue() { throw notEnabled('i18nText field validation') },\n enforceScript(value) { return { value, warnings: [] } },\n computeExemptFills() { return new Map<string, Set<string>>() },\n densify() {},\n buildDictionaryHandle() { throw notEnabled('vault.dictionary()') },\n}\n\n/**\n * Return true when a collection name is a reserved dictionary collection\n * (the `_dict_*` prefix). Mirrors `DICT_COLLECTION_PREFIX` — now on\n * `via/lookup/handle.ts` (#650 Task 1), re-exported from\n * `via/i18n/dictionary.ts` for compat — duplicated here (not\n * imported) so this port has no VALUE dependency back on the feature; keep\n * the two in sync if the prefix ever changes.\n */\nexport function isDictCollectionName(name: string): boolean {\n return name.startsWith('_dict_')\n}\n\n/** Runtime predicate for detecting a StaticDictDescriptor. */\nexport function isStaticDictDescriptor(x: unknown): x is StaticDictDescriptor {\n return (\n typeof x === 'object' &&\n x !== null &&\n (x as { _noydbStaticDict?: unknown })._noydbStaticDict === true\n )\n}\n\n/**\n * Runtime predicate for detecting an I18nTextDescriptor. Pure tag check —\n * moved here (#623 Task 11) alongside `isStaticDictDescriptor` so\n * `kernel/via/compose.ts` (`mergeViaFields`'s descriptor-shape classification)\n * can reach it through the port instead of importing `via/i18n/core.js`\n * directly. Re-exported from that module for compat with existing importers.\n */\nexport function isI18nTextDescriptor(x: unknown): x is I18nTextDescriptor {\n return (\n typeof x === 'object' &&\n x !== null &&\n (x as { _noydbI18nText?: unknown })._noydbI18nText === true\n )\n}\n\n/**\n * Runtime predicate for detecting a DictKeyDescriptor. Pure tag check —\n * moved here (#623 Task 11) for the same reason as `isI18nTextDescriptor`\n * above. Re-exported from `via/i18n/dictionary.js` for compat with\n * existing importers.\n */\nexport function isDictKeyDescriptor(x: unknown): x is DictKeyDescriptor {\n return (\n typeof x === 'object' &&\n x !== null &&\n (x as { _noydbDictKey?: unknown })._noydbDictKey === true\n )\n}\n\n/**\n * Type-only re-exports (#623 Task 8) — the kernel spine (collection.ts,\n * collection-config.ts, vault.ts, types.ts) imports these descriptor/handle\n * types through the port instead of reaching into `via/i18n/*`\n * directly, so `src/kernel/**` carries no via-i18n specifier at all.\n * `isolatedModules: true` erases these at build time — no runtime coupling.\n */\nexport type { I18nTextDescriptor } from '../../via/i18n/core.js'\nexport type { DictKeyDescriptor, DictionaryHandle, DictionaryOptions, StaticDictDescriptor } from '../../via/i18n/dictionary.js'\nexport type { Layer } from '../../via/i18n/policy.js'\nexport type { ScriptWarning } from '../../via/i18n/script.js'\n","/**\n * Generic dotted-path / `[]`-wildcard record helpers. Kernel-owned:\n * these carry zero i18n logic — they were extracted out of\n * `via/i18n/core.ts` (Task 7, #623) so kernel call-sites (put-time\n * validation, densify) can import them without a spine→service edge.\n *\n * @internal\n */\n\n/**\n * Return all leaf values at `path`, expanding `[].` array wildcards.\n *\n * - `'name'` → `[obj.name]`\n * - `'address.lineOne'` → `[obj.address.lineOne]`\n * - `'contacts[].title'` → `[obj.contacts[0].title, obj.contacts[1].title, …]`\n *\n * Returns an empty array when the path does not resolve (missing key,\n * wrong type, etc.). Used by `enforceI18nOnPut` to validate nested fields.\n */\nexport function getAtPath(obj: Record<string, unknown>, path: string): unknown[] {\n const arrayIdx = path.indexOf('[].')\n if (arrayIdx !== -1) {\n const arrayKey = path.slice(0, arrayIdx)\n const restPath = path.slice(arrayIdx + 3)\n const arr = obj[arrayKey]\n if (!Array.isArray(arr)) return []\n return arr.flatMap(item => {\n if (!item || typeof item !== 'object' || Array.isArray(item)) return []\n return getAtPath(item as Record<string, unknown>, restPath)\n })\n }\n const dotIdx = path.indexOf('.')\n if (dotIdx !== -1) {\n const head = path.slice(0, dotIdx)\n const rest = path.slice(dotIdx + 1)\n const nested = obj[head]\n if (!nested || typeof nested !== 'object' || Array.isArray(nested)) return []\n return getAtPath(nested as Record<string, unknown>, rest)\n }\n const val = obj[path]\n return val !== undefined ? [val] : []\n}\n\n/**\n * Mutate `obj` in-place, setting `value` at the nested `path`.\n * Supports dot notation (`'address.lineOne'`) but not array wildcards —\n * auto-translate on `contacts[].title` style paths is not supported.\n */\nexport function setAtPathInPlace(\n obj: Record<string, unknown>,\n path: string,\n value: unknown,\n): void {\n const dotIdx = path.indexOf('.')\n if (dotIdx !== -1) {\n const head = path.slice(0, dotIdx)\n const rest = path.slice(dotIdx + 1)\n const nested = obj[head]\n if (!nested || typeof nested !== 'object' || Array.isArray(nested)) return\n setAtPathInPlace(nested as Record<string, unknown>, rest, value)\n return\n }\n obj[path] = value\n}\n","/**\n * Per-layer i18n resolution policy.\n *\n * `onMissing` governs what happens when a multilingual field is resolved\n * to a target locale that is absent. It may be a single scalar policy or\n * a per-layer map, so a field can be lenient at the app read boundary but\n * strict inside a materialized view.\n *\n * Effective policy for layer `λ`:\n *\n * ```\n * explicit(λ) = typeof onMissing === 'object' ? onMissing[λ] : undefined\n * scalar = typeof onMissing === 'string' ? onMissing : undefined\n * policy(λ) = explicit(λ) ?? layerDefault(λ) ?? scalar ?? 'throw'\n * ```\n *\n * - `layerDefault('guard') = 'substitute'` — guards are lenient unless\n * EXPLICITLY overridden; they never inherit a scalar policy (a guard\n * reading a display value must not hard-fail on a missing locale).\n * - every other layer has no default, so it inherits the scalar, else\n * falls back to `'throw'` (today's behavior — zero breaking change).\n *\n * @public\n */\nexport type OnMissing = 'substitute' | 'null' | 'throw'\n\n/**\n * The contexts in which a multilingual field is resolved. Each can carry\n * its own `onMissing` policy.\n *\n * - `read` — ordinary app reads (`get`/`list`/query projection).\n * - `guard` — a guard callback reading a value.\n * - `join` — a joined record expanded onto a row.\n * - `mv` — materialized-view input.\n * - `derivation` — derivation input.\n * - `export` — bundle/cover export.\n */\nexport type Layer = 'read' | 'guard' | 'join' | 'mv' | 'derivation' | 'export'\n\n/** Field-level policy: a single scalar, or a per-layer map. */\nexport type OnMissingPolicy = OnMissing | Partial<Record<Layer, OnMissing>>\n\n/**\n * Resolve the effective `OnMissing` for a layer from a field's declared\n * policy. See module docs for the resolution rule.\n */\nexport function resolvePolicy(\n onMissing: OnMissingPolicy | undefined,\n layer: Layer,\n): OnMissing {\n const explicit =\n onMissing && typeof onMissing === 'object' ? onMissing[layer] : undefined\n const scalar = typeof onMissing === 'string' ? onMissing : undefined\n const layerDefault: OnMissing | undefined =\n layer === 'guard' ? 'substitute' : undefined\n return explicit ?? layerDefault ?? scalar ?? 'throw'\n}\n","/**\n * Per-locale script enforcement for `i18nText` fields (write-time).\n *\n * Each locale slot's string is validated against an allowed set of\n * Unicode scripts. `'auto'` infers the set from the locale code with\n * **asymmetric Latin tolerance**: every non-Latin-script locale\n * also allows `Latin`, because proper names and addresses in those\n * locales routinely embed Latin brand/building/technical names — while\n * Latin-script locales do NOT allow other scripts, so the common error\n * (e.g. Thai text dumped into an `en` slot) is still caught.\n *\n * The always-on baseline is `Common` (digits, punctuation), `Inherited`\n * and `Mark` (combining diacritics, joiners, harakat, tone marks), and\n * whitespace — so Latin digits and in-script combining marks never\n * false-reject.\n *\n * @public\n */\nimport { ScriptViolationError } from '../../kernel/errors.js'\nimport type { I18nTextDescriptor } from './core.js'\n\n/** Locales whose base language is written in the Latin script. */\nconst LATIN_BASE = new Set([\n 'en', 'fr', 'de', 'es', 'it', 'pt', 'nl', 'sv', 'no', 'da', 'fi', 'is',\n 'pl', 'cs', 'sk', 'hu', 'ro', 'hr', 'sl', 'et', 'lv', 'lt', 'tr', 'vi',\n 'id', 'ms', 'tl', 'sw', 'af', 'ca', 'gl', 'eu', 'cy', 'ga',\n])\n\n/** Base-language → primary (non-Latin) scripts. Latin is appended by inferScripts. */\nconst SCRIPT_TABLE: Record<string, readonly string[]> = {\n th: ['Thai'],\n ko: ['Hangul', 'Han'],\n ja: ['Han', 'Hiragana', 'Katakana'],\n zh: ['Han'],\n ar: ['Arabic'],\n fa: ['Arabic'],\n ur: ['Arabic'],\n ru: ['Cyrillic'],\n uk: ['Cyrillic'],\n bg: ['Cyrillic'],\n sr: ['Cyrillic'],\n he: ['Hebrew'],\n el: ['Greek'],\n hi: ['Devanagari'],\n ta: ['Tamil'],\n km: ['Khmer'],\n lo: ['Lao'],\n my: ['Myanmar'],\n}\n\n/** Map a BCP-47 script subtag (e.g. `Latn`, `Cyrl`) to allowed scripts. */\nconst SUBTAG_SCRIPTS: Record<string, readonly string[]> = {\n Latn: ['Latin'],\n Cyrl: ['Cyrillic', 'Latin'],\n Hans: ['Han', 'Latin'],\n Hant: ['Han', 'Latin'],\n Thai: ['Thai', 'Latin'],\n Arab: ['Arabic', 'Latin'],\n}\n\n/**\n * Infer the allowed Unicode scripts for a BCP-47 locale, with asymmetric\n * Latin tolerance. A script subtag (`th-Latn`) wins over the base\n * language. Unknown locales default to `['Latin']`.\n */\nexport function inferScripts(locale: string): readonly string[] {\n const parts = locale.split('-')\n const subtag = parts.find((t) => /^[A-Z][a-z]{3}$/.test(t))\n if (subtag && SUBTAG_SCRIPTS[subtag]) return SUBTAG_SCRIPTS[subtag]\n\n const base = (parts[0] ?? '').toLowerCase()\n if (LATIN_BASE.has(base)) return ['Latin']\n const primary = SCRIPT_TABLE[base]\n if (primary) return [...primary, 'Latin'] // asymmetric Latin tolerance\n return ['Latin']\n}\n\n/** Resolve the allowed scripts for a field's locale slot. */\nfunction allowedFor(descriptor: I18nTextDescriptor, locale: string): readonly string[] {\n const script = descriptor.options.script\n if (script && script !== 'auto') {\n const explicit = script[locale]\n if (explicit) return explicit\n }\n return inferScripts(locale)\n}\n\n/** Always-allowed baseline character classes (besides the named scripts). */\nconst BASELINE = String.raw`\\p{White_Space}\\p{Script=Common}\\p{Script=Inherited}\\p{Mark}`\n\n/** Build a whole-string matcher for the allowed scripts. */\nfunction fullMatcher(scripts: readonly string[]): RegExp {\n const cls = scripts.map((s) => `\\\\p{Script=${s}}`).join('')\n return new RegExp(`^[${BASELINE}${cls}]*$`, 'u')\n}\n\n/** Build a single-character matcher (for sampling / stripping). */\nfunction charMatcher(scripts: readonly string[]): RegExp {\n const cls = scripts.map((s) => `\\\\p{Script=${s}}`).join('')\n return new RegExp(`[${BASELINE}${cls}]`, 'u')\n}\n\n/** Collect a short sample of characters that violate the allowed scripts. */\nfunction offendingSample(str: string, scripts: readonly string[]): string {\n const ok = charMatcher(scripts)\n const bad: string[] = []\n for (const ch of str) {\n if (!ok.test(ch)) bad.push(ch)\n if (bad.length >= 8) break\n }\n return bad.join('')\n}\n\n/** Remove characters that violate the allowed scripts. */\nfunction stripDisallowed(str: string, scripts: readonly string[]): string {\n const ok = charMatcher(scripts)\n let out = ''\n for (const ch of str) if (ok.test(ch)) out += ch\n return out\n}\n\n/** A non-fatal script violation recorded under `'filter'`/`'warn'` modes. */\nexport interface ScriptWarning {\n readonly field: string\n readonly locale: string\n readonly expected: readonly string[]\n readonly sample: string\n}\n\n/**\n * Enforce a field's script constraint over an i18nText value map.\n *\n * - No `script` option ⇒ returns the value unchanged.\n * - `onScriptViolation: 'reject'` (default) ⇒ throws {@link ScriptViolationError}.\n * - `'filter'` ⇒ returns a copy with disallowed characters stripped + warnings.\n * - `'warn'` ⇒ returns the value unchanged + warnings.\n */\nexport function enforceScript(\n value: Record<string, unknown>,\n field: string,\n descriptor: I18nTextDescriptor,\n exempt?: ReadonlySet<string>,\n): { value: Record<string, unknown>; warnings: ScriptWarning[] } {\n const opt = descriptor.options\n if (!opt.script) return { value, warnings: [] }\n\n const mode = opt.onScriptViolation ?? 'reject'\n const warnings: ScriptWarning[] = []\n let out = value\n\n for (const [locale, raw] of Object.entries(value)) {\n if (exempt?.has(locale)) continue\n if (typeof raw !== 'string') continue\n const allowed = allowedFor(descriptor, locale)\n if (fullMatcher(allowed).test(raw)) continue\n\n const sample = offendingSample(raw, allowed)\n if (mode === 'reject') {\n throw new ScriptViolationError(field, locale, allowed, sample)\n }\n warnings.push({ field, locale, expected: allowed, sample })\n if (mode === 'filter') {\n if (out === value) out = { ...value }\n out[locale] = stripDisallowed(raw, allowed)\n }\n }\n\n return { value: out, warnings }\n}\n","/**\n * The i18n `NoydbVia` — wires the i18n engine (auto-translate/script\n * enforcement/validation/densify on write; locale + dict-label presentation\n * on read) into the kernel's generic Via port. Mirrors\n * `via/money/binding.ts`'s #553 static-link pattern.\n *\n * LIVE (#623 Task 8 wired the cutover): `i18nVia()`/`linkI18nVia()` are\n * the real put/read path for every i18n-declaring collection —\n * `collection.ts`'s `_putInternal`/`applyLocaleToRecord` (the call sites\n * this file's bodies were moved/adapted from) now delegate their i18n\n * stages to `this.via.encodeWrite`/`this.via.present` instead of running\n * the hand-wired i18n logic inline.\n *\n * `i18nText()`, `dictKey()`, `staticDict()` each call {@link linkI18nVia}\n * first — the same #553 pattern `money()` uses.\n */\nimport type { NoydbVia, ViaWriteCtx, ViaReadCtx } from '../../kernel/via/index.js'\nimport { installViaBinder } from '../../kernel/via/index.js'\nimport type { I18nTextDescriptor } from './core.js'\nimport { stripI18nFilled } from './core.js'\nimport type { DictKeyDescriptor, StaticDictDescriptor } from './dictionary.js'\nimport type { I18nStrategy } from '../../port/with/i18n-strategy.js'\nimport { isStaticDictDescriptor } from '../../port/with/i18n-strategy.js'\nimport { resolvePolicy, type Layer } from './policy.js'\nimport { getAtPath, setAtPathInPlace } from '../../kernel/paths.js'\nimport { TranslatorNotConfiguredError, LocaleNotSpecifiedError } from '../../kernel/errors.js'\n\n/**\n * Config a collection's i18n declarations resolve to — the binding's\n * construction input. Fields are loosely typed (`unknown`) at this seam\n * because the kernel's `ViaBinder` contract is opaque-config-in; the\n * concrete i18n types are applied inside the binding's own functions.\n */\nexport interface I18nViaConfig {\n i18nFields?: Record<string, unknown> // I18nTextDescriptor map\n dictKeyFields?: Record<string, unknown> // DictKeyDescriptor | StaticDictDescriptor map\n i18nDensifyFields?: Record<string, unknown> // I18nTextDescriptor subset (densifyOnWrite: true)\n strategy: unknown // I18nStrategy (real or NO_I18N)\n defaultLocale?: string\n autoTranslateHook?: (...args: unknown[]) => Promise<string>\n dictLabelResolver?: (...args: unknown[]) => Promise<string | undefined>\n i18nPutValidator?: (record: unknown) => void // the vault-built closure (unchanged wiring)\n collectionName: string\n}\n\n// ─── write pipeline ─────────────────────────────────────────────────────\n\n/**\n * Auto-translate missing i18nText translations. Runs BEFORE i18n\n * validation so translated values satisfy the required-locale constraint.\n * Throws `TranslatorNotConfiguredError` when a field has\n * `autoTranslate: true` but no hook was configured. Mutates `record`\n * in place (mirrors `collection.ts:1830-1874`).\n */\nasync function runI18nAutoTranslate(\n record: Record<string, unknown>,\n i18nFields: Record<string, I18nTextDescriptor>,\n cfg: I18nViaConfig,\n): Promise<void> {\n for (const [field, descriptor] of Object.entries(i18nFields)) {\n if (!descriptor.options.autoTranslate) continue\n // getAtPath returns [] for array-wildcard paths — auto-translate on\n // 'contacts[].field' style paths is not supported; skip silently.\n const leafValues = getAtPath(record, field)\n if (leafValues.length !== 1) continue\n const value = leafValues[0]\n if (!value || typeof value !== 'object' || Array.isArray(value)) continue\n const map = value as Record<string, string>\n // Determine which locales need translation. For 'all', translate all\n // declared languages that are missing. For 'any', only translate if\n // none are present. For string[], translate the listed required ones.\n const { languages, required } = descriptor.options\n const missing: string[] = languages.filter(\n (lang) => !(lang in map) || map[lang] === '',\n )\n if (missing.length === 0) continue\n // Find a source locale (first present non-empty value)\n const sourceLocale = languages.find((l) => l in map && map[l] !== '')\n if (!sourceLocale) continue\n if (!cfg.autoTranslateHook) {\n throw new TranslatorNotConfiguredError(field, cfg.collectionName)\n }\n // Only translate locales that are actually needed\n const toTranslate =\n required === 'any'\n ? [] // 'any' is already satisfied since sourceLocale exists\n : required === 'all'\n ? missing\n : missing.filter((l) => required.includes(l))\n const translated = { ...map }\n for (const targetLocale of toTranslate) {\n translated[targetLocale] = await cfg.autoTranslateHook(\n map[sourceLocale]!,\n sourceLocale,\n targetLocale,\n field,\n cfg.collectionName,\n )\n }\n setAtPathInPlace(record, field, translated)\n }\n}\n\n/**\n * i18nText script enforcement — runs AFTER auto-translate (so generated\n * values are checked too). Throws `ScriptViolationError` (from inside\n * `strategy.enforceScript`) under the default 'reject'; 'filter' strips\n * disallowed chars in place; 'warn' leaves the value unchanged. Mirrors\n * `collection.ts:1896-1927`.\n */\nfunction runI18nScriptEnforcement(\n record: Record<string, unknown>,\n i18nFields: Record<string, I18nTextDescriptor>,\n strategy: I18nStrategy,\n exemptFills: Map<string, Set<string>> | undefined,\n ctx: ViaWriteCtx,\n cfg: I18nViaConfig,\n): void {\n for (const [field, descriptor] of Object.entries(i18nFields)) {\n if (!descriptor.options.script) continue\n for (const leaf of getAtPath(record, field)) {\n if (!leaf || typeof leaf !== 'object' || Array.isArray(leaf)) continue\n const leafMap = leaf as Record<string, unknown>\n const { value: cleaned, warnings } = strategy.enforceScript(\n leafMap,\n field,\n descriptor,\n exemptFills?.get(field),\n )\n if (cleaned !== leafMap) Object.assign(leafMap, cleaned)\n // enforceScript only returns warnings under 'warn'/'filter' ('reject'\n // throws first), so this guard never fires — it makes that invariant\n // explicit and keeps `mode` off the optional-undefined type.\n const mode = descriptor.options.onScriptViolation\n if (mode === 'warn' || mode === 'filter') {\n for (const w of warnings) {\n // Exact original payload shape (collection.ts:1916-1922 pre-cutover).\n ctx.emit('i18n:script-violation', {\n vault: ctx.vault,\n collection: cfg.collectionName,\n id: ctx.id,\n mode,\n warning: w,\n })\n }\n }\n }\n }\n}\n\n/**\n * `encodeWrite` — translate → script → validate → densify, in the EXACT\n * order `_putInternal` runs them today (seam-map Part 2, stages 7-11).\n */\nasync function runI18nWriteStages(\n record: Record<string, unknown>,\n ctx: ViaWriteCtx,\n cfg: I18nViaConfig,\n): Promise<Record<string, unknown>> {\n const i18nFields = cfg.i18nFields as Record<string, I18nTextDescriptor> | undefined\n const i18nDensifyFields = cfg.i18nDensifyFields as Record<string, I18nTextDescriptor> | undefined\n const strategy = cfg.strategy as I18nStrategy\n\n // 1. Auto-translate.\n if (i18nFields) {\n await runI18nAutoTranslate(record, i18nFields, cfg)\n }\n\n // 2. densifyOnWrite: read prior fills so a round-tripped derived copy is\n // exempt from script enforcement and can be refreshed. Read once here,\n // reused by densify() below. `ctx.prior()` resolves the real prior\n // record (collection.ts wires it to `resolveDensifyPrior`, #623 Task 8).\n let densifyPrior: Record<string, unknown> | undefined\n let exemptFills: Map<string, Set<string>> | undefined\n if (i18nDensifyFields) {\n densifyPrior = (await ctx.prior()) ?? undefined\n exemptFills = strategy.computeExemptFills(densifyPrior, record, i18nDensifyFields)\n }\n\n // 3. Script enforcement.\n if (i18nFields) {\n runI18nScriptEnforcement(record, i18nFields, strategy, exemptFills, ctx, cfg)\n }\n\n // 4. i18nText validation — AFTER auto-translate + script so the record is\n // trustworthy. Throws MissingTranslationError when required translations\n // are absent. The vault-built closure (unchanged wiring).\n cfg.i18nPutValidator?.(record)\n\n // 5. Eager-fill empty slots + record provenance. Runs AFTER the authored\n // gates (required + script) so only authored slots are validated.\n if (i18nDensifyFields) {\n strategy.densify(record, densifyPrior, i18nDensifyFields)\n }\n\n return record\n}\n\n// ─── read pipeline ──────────────────────────────────────────────────────\n\n/**\n * `present` — i18nText locale resolution + dictKey/staticDict label\n * resolution + `_i18nFilled` stripping. Adapted from\n * `applyLocaleToRecord`'s i18n+dict+strip portions (`collection.ts:4184-\n * 4301`) — the money-decode portion of that method is a separate binding\n * (`via/money`) and is NOT this function's concern.\n */\nasync function runI18nPresent(\n record: Record<string, unknown>,\n ctx: ViaReadCtx,\n cfg: I18nViaConfig,\n): Promise<Record<string, unknown>> {\n const i18nFields = cfg.i18nFields as Record<string, I18nTextDescriptor> | undefined\n const dictKeyFields = cfg.dictKeyFields as Record<string, DictKeyDescriptor | StaticDictDescriptor> | undefined\n const strategy = cfg.strategy as I18nStrategy\n const hasI18n = i18nFields !== undefined && Object.keys(i18nFields).length > 0\n const hasDict = dictKeyFields !== undefined && Object.keys(dictKeyFields).length > 0\n if (!hasI18n && !hasDict) return record\n\n const locale = typeof ctx.locale === 'string' ? ctx.locale : undefined\n const fallback = ctx.fallback as string | readonly string[] | undefined\n const layer = ctx.layer as Layer\n\n let result = record\n\n // i18nText / dictKey resolution require an active locale — EXCEPT a\n // static dict declaring a `displayLocale`, which resolves its\n // `<field>Label` even under a locale-less read (the hybrid hinge). Only\n // this second gate relaxes, and ONLY for static-display fields — folding\n // `hasI18n` in here would let an i18nText-only collection fall through to\n // applyI18nLocale(…, undefined) on a locale-less read, breaking the\n // raw-{th,en}-map invariant.\n const hasStaticDisplay =\n hasDict &&\n dictKeyFields !== undefined &&\n Object.values(dictKeyFields).some(\n (d) => isStaticDictDescriptor(d) && d.displayLocale !== undefined,\n )\n\n // Strip the internal densify marker even when no locale is active\n // (applyI18nLocale, which normally strips it, is skipped on this path).\n // Non-mutating: never touches the cached/stored record object.\n if (!locale && !hasStaticDisplay) return stripI18nFilled(result)\n\n // 1. i18nText resolution — guarded on `locale`, because the relaxed gate\n // above can now be entered with `locale === undefined` (static-display).\n // The layer selects the field's per-layer `onMissing` policy inside\n // applyI18nLocale.\n if (locale && hasI18n && i18nFields) {\n result = strategy.applyI18nLocale(result, i18nFields, locale, fallback, layer)\n }\n\n // 2. dictKey / staticDict label resolution\n if (hasDict && dictKeyFields && cfg.dictLabelResolver && locale !== 'raw') {\n const withLabels = { ...result }\n const resolver = cfg.dictLabelResolver\n for (const [field, desc] of Object.entries(dictKeyFields)) {\n // dictKey default policy is 'null' (omit/null on miss) — today's\n // behavior — unless the field declares onMissing. 'substitute'\n // walks the declared substitute chain (passed as the resolver's\n // fallback); 'throw' raises LocaleNotSpecifiedError.\n const policy = desc.onMissing ? resolvePolicy(desc.onMissing, layer) : 'null'\n const fieldFallback =\n policy === 'substitute'\n ? (fallback ?? desc.substitute)\n : fallback\n // Per-field effective locale: a static dict falls back to its\n // `displayLocale` when no locale is active (the hybrid hinge); a\n // plain dictKey with no displayLocale gets `undefined` → its\n // <field>Label is omitted on a locale-less read (today's behavior).\n const effLocale =\n locale ??\n (isStaticDictDescriptor(desc) ? desc.displayLocale : undefined)\n // Resolve one key → label | null, honoring the policy. With no\n // effective locale there is nothing to resolve against.\n const resolveKey = async (key: string): Promise<string | null> => {\n if (!effLocale) {\n if (policy === 'throw') {\n throw new LocaleNotSpecifiedError(\n field,\n `dictKey \"${field}\": no locale active to resolve key \"${key}\".`,\n )\n }\n return null\n }\n const label = await resolver(desc.name, key, effLocale, fieldFallback)\n if (label === undefined) {\n if (policy === 'throw') {\n throw new LocaleNotSpecifiedError(\n field,\n `dictKey \"${field}\": no label for key \"${key}\" in locale \"${effLocale}\".`,\n )\n }\n return null\n }\n return label\n }\n\n if (field.includes('[].')) {\n // Wildcard path `arrayKey[].leaf`: add a per-element sibling\n // `<leaf>Label`. Single level + simple leaf.\n const parts = field.split('[].')\n const arrayKey = parts[0]!\n const leaf = parts[1]\n if (!leaf || leaf.includes('.')) continue\n const arr = (withLabels as Record<string, unknown>)[arrayKey]\n if (!Array.isArray(arr)) continue\n const labelKey = `${leaf}Label`\n ;(withLabels as Record<string, unknown>)[arrayKey] = await Promise.all(\n arr.map(async (el) => {\n if (!el || typeof el !== 'object' || Array.isArray(el)) return el\n const k = (el as Record<string, unknown>)[leaf]\n if (typeof k !== 'string') return el\n return { ...(el as Record<string, unknown>), [labelKey]: await resolveKey(k) }\n }),\n )\n continue\n }\n\n const val = result[field]\n if (Array.isArray(val)) {\n // Array-of-keys → [{ key, label }] pair objects (key preserved).\n withLabels[`${field}Label`] = await Promise.all(\n val.map(async (k) => ({\n key: k,\n label: typeof k === 'string' ? await resolveKey(k) : null,\n })),\n )\n } else if (typeof val === 'string') {\n const label = await resolveKey(val)\n // Scalar under 'null'/default omits the label key (today's\n // behavior); 'substitute' returns a value; 'throw' threw above.\n if (label !== null) withLabels[`${field}Label`] = label\n }\n }\n result = withLabels\n }\n\n // Final guard: the locale-less static-display path skips\n // applyI18nLocale's strip, so ensure the densify marker never leaks here\n // either. Non-mutating (no-op when absent or already stripped above).\n return stripI18nFilled(result)\n}\n\n// ─── introspection ──────────────────────────────────────────────────────\n\nfunction buildI18nDescribeFragment(cfg: I18nViaConfig): Record<string, unknown> {\n return {\n i18nFields: cfg.i18nFields ? Object.keys(cfg.i18nFields) : [],\n dictKeyFields: cfg.dictKeyFields ? Object.keys(cfg.dictKeyFields) : [],\n ...(cfg.defaultLocale !== undefined ? { defaultLocale: cfg.defaultLocale } : {}),\n }\n}\n\n// ─── binding ────────────────────────────────────────────────────────────\n\nexport function i18nVia(cfg: I18nViaConfig): NoydbVia {\n return {\n brand: 'i18n',\n posture: { encryptedAtRest: 'envelope', queryable: 'full', exportable: true, forgettable: true },\n // DictionaryHandle's `_dict_*` reserved collections (#629 Task 4) — the\n // codec boundary's `reservedEnvelopes` door may address any collection\n // name under this prefix.\n reservedPrefixes: ['_dict_'],\n covers: (field) =>\n (cfg.i18nFields !== undefined && field in cfg.i18nFields)\n || (cfg.dictKeyFields !== undefined && field in cfg.dictKeyFields),\n encodeWrite: async (record, ctx) => runI18nWriteStages(record, ctx, cfg),\n present: async (record, ctx) => runI18nPresent(record, ctx, cfg),\n describeFragment: () => buildI18nDescribeFragment(cfg),\n }\n}\n\nexport function linkI18nVia(): void {\n installViaBinder('i18n', (c) => i18nVia(c as I18nViaConfig))\n}\n","/**\n * i18nText schema type —\n *\n * `i18nText({ languages, required })` creates a descriptor for a\n * multi-language content field whose value is stored as a\n * `{ [locale]: string }` map (e.g. `{ en: 'Consulting', th: 'ที่ปรึกษา' }`).\n *\n * On put, the descriptor validates that required languages are present.\n * On read (when a `locale` option is passed), the map is collapsed to the\n * caller's locale string via the fallback chain.\n *\n * Design decisions\n * ────────────────\n *\n * **Descriptor pattern (not a Zod type).**\n * `i18nText()` returns a plain descriptor object used in the collection's\n * `i18nFields` option — same pattern as `ref()` / `dictKey()`. This keeps\n * `@noy-db/core` at zero runtime dependencies and avoids Zod v3 field-type\n * constraints. TypeScript inference is handled via the descriptor's type.\n *\n * **Enforcement at the collection boundary.**\n * The `required` option is checked by `Collection.put()` via the compartment's\n * registered `i18nFields`. Failed validation throws `MissingTranslationError`\n * — a distinct class from `SchemaValidationError` so callers can tell\n * \"wrong shape\" from \"missing translations\".\n *\n * **Resolution is post-decryption.**\n * Locale resolution happens AFTER `decryptRecord()`, as a pure in-memory\n * transform. No additional crypto work is needed. The resolved record is\n * returned in place of the stored one, with i18nText fields replaced by\n * their locale-resolved strings.\n *\n * **`locale: 'raw'`.**\n * Passing `{ locale: 'raw' }` skips resolution and returns the full\n * `{ [locale]: string }` map — useful for bilingual exports, admin UIs,\n * and any context where all translations must be visible at once.\n *\n * **Out of scope.**\n * Pluralization, RTL rendering, date/number formatting, per-locale CRDT\n * merging.\n */\n\nimport { MissingTranslationError, LocaleNotSpecifiedError } from '../../kernel/errors.js'\nimport type { OnMissing, OnMissingPolicy, Layer } from './policy.js'\nimport { resolvePolicy } from './policy.js'\nimport { inferScripts } from './script.js'\nimport { linkI18nVia } from './binding.js'\n\n// ─── I18nMap type helper ───────────────────────────────────────────────\n\n/** Flatten an intersection into a single object literal for nicer hovers. */\ntype Prettify<T> = { [K in keyof T]: T[K] } & {}\n\n/**\n * The stored shape of a multilingual field, inferred from its `required`\n * mode — so the compiler forces you to handle an absent optional locale\n * (`string | undefined`) instead of silently yielding `undefined`.\n *\n * Mirrors `i18nText({ languages, required })`:\n * - `'all'` (default) — every locale required: `{ th: string; en: string }`\n * - `'any'` — every locale optional: `{ th?: string; en?: string }`\n * (the \"at least one present\" guarantee is runtime-only — not expressible\n * in TypeScript — so each key is optional)\n * - `readonly L[]` — listed locales required, the rest optional:\n * `I18nMap<'th'|'en', ['th']>` → `{ th: string; en?: string }`\n *\n * @example\n * ```ts\n * type Lang = 'th' | 'en'\n * interface Contact {\n * name: I18nMap<Lang, 'any'> // { th?: string; en?: string }\n * legalName: I18nMap<Lang, ['th']> // { th: string; en?: string }\n * slug: I18nMap<Lang> // { th: string; en: string }\n * }\n * ```\n *\n * @public\n */\nexport type I18nMap<\n Langs extends string,\n Required extends 'all' | 'any' | readonly Langs[] = 'all',\n> = Required extends 'all'\n ? Record<Langs, string>\n : Required extends 'any'\n ? Partial<Record<Langs, string>>\n : Required extends readonly (infer R extends Langs)[]\n ? Prettify<Record<R, string> & Partial<Record<Exclude<Langs, R>, string>>>\n : never\n\n// ─── i18nText descriptor ───────────────────────────────────────────────\n\n/**\n * Options for `i18nText()`.\n *\n * `languages` declares the full set of supported locales. `required`\n * controls which must be present on every `put()`.\n *\n * `autoTranslate` is the per-field opt-in for the `plaintextTranslator`\n * hook. When `true` and a `plaintextTranslator` is configured\n * on `createNoydb()`, missing translations are generated before `put()`.\n * Default: `false`.\n */\nexport interface I18nTextOptions {\n /** All supported locale codes (BCP 47). */\n readonly languages: readonly string[]\n /**\n * Which locales must be present on every `put()`.\n *\n * - `'all'` — every declared language must be present.\n * - `'any'` — at least one declared language must be present.\n * - `string[]` — listed locales are required; others are optional.\n */\n readonly required: 'all' | 'any' | readonly string[]\n /**\n * Per-field opt-in for the `plaintextTranslator` hook.\n * When `true`, missing required translations are auto-generated\n * before `put()` if a translator is configured. Default: `false`.\n */\n readonly autoTranslate?: boolean\n /**\n * What to do when this field is resolved to a locale that is absent.\n * A single policy, or a per-layer map (read/guard/join/mv/derivation/\n * export). Default `'throw'` — today's behavior, zero breaking change.\n * See {@link OnMissingPolicy}.\n *\n * NOTE (current wiring): ALL layers are enforced — `read` (`get`/`list`),\n * `guard`, `derivation`, `mv`, `join`, `export`. Guard / derivation\n * `ctx.vault` reads resolve under their own layer policy (`guard` defaults to\n * the lenient `'substitute'`). The `mv` layer fires for materialized views\n * that declare `{ i18nLocale, i18nFields }` — UNION (group-key i18n fields\n * resolve before the unified-row bucketing) and query-form (resolved in\n * `GroupedReduction.run` before `groupAndReduce`); grouping a raw i18n field\n * without a locale throws. The `join` layer resolves a joined right-side i18n\n * field to the query locale (`toArray({ locale })` or the vault default; raw\n * when locale-less). The `export` layer fires for\n * `exportStream`/`exportJSON({ resolveLabels })` — records collapse to the\n * export locale.\n */\n readonly onMissing?: OnMissingPolicy\n /**\n * Ordered preferred-substitute locales used when `onMissing` resolves\n * to `'substitute'` and the target locale is absent. `'any'` as an\n * element means \"first non-empty value\". A caller-supplied `fallback`\n * at read time takes precedence over this declared list.\n */\n readonly substitute?: readonly string[]\n /**\n * Smart-substitute. When `true`, a missing-locale `substitute` walk that\n * misses the explicit chain prefers the available locale whose script is\n * nearest the target (same script, then Latin) rather than an arbitrary value\n * — e.g. a missing Thai label prefers another Thai (or Latin) translation over\n * an unreadable script. Default `false` (legacy first-non-empty behavior).\n */\n readonly smartSubstitute?: boolean\n /**\n * Per-locale script enforcement (write-time). `'auto'` infers the\n * allowed Unicode scripts per locale (asymmetric Latin tolerance); an\n * object overrides per slot. Absent ⇒ no check. See `./script.ts`.\n */\n readonly script?: 'auto' | Partial<Record<string, readonly string[]>>\n /**\n * What to do when a slot's value contains characters outside its\n * allowed script set. Default `'reject'`.\n */\n readonly onScriptViolation?: 'reject' | 'filter' | 'warn'\n /**\n * Eager-fill empty locale slots from the substitute chain at\n * write time, recording provenance in the internal `_i18nFilled` marker.\n * Mutually exclusive with an EXPLICIT `'throw'` onMissing policy (densify\n * fills every hole, so a throw would be unreachable). Without an explicit\n * `substitute`, fills from `'any'` (first non-empty). Default absent.\n */\n readonly densifyOnWrite?: boolean\n}\n\n/**\n * Descriptor returned by `i18nText()`. Attach to the collection's\n * `i18nFields` option:\n *\n * ```ts\n * const lineItems = company.collection<LineItem>('line-items', {\n * i18nFields: {\n * description: i18nText({ languages: ['en', 'th'], required: 'all' }),\n * },\n * })\n * ```\n */\nexport interface I18nTextDescriptor {\n readonly _noydbI18nText: true\n /** Via port brand marker — lets an `I18nTextDescriptor` satisfy the kernel's opaque `ViaDescriptor` (#623 Task 9). */\n readonly _viaBrand: 'i18n'\n readonly options: I18nTextOptions\n}\n\n/**\n * Create an `I18nTextDescriptor` for a multi-language content field.\n *\n * @param options Language list + enforcement mode.\n *\n * @example\n * ```ts\n * i18nText({ languages: ['en', 'th'], required: 'all' })\n * i18nText({ languages: ['en', 'th'], required: ['th'], autoTranslate: true })\n * ```\n */\nexport function i18nText(options: I18nTextOptions): I18nTextDescriptor {\n // The declaration is the Via binding's opt-in unit (#553): constructing\n // a descriptor statically links the i18n binding into the kernel's Via\n // port, so every later kernel consultation finds it already installed.\n linkI18nVia()\n if (options.densifyOnWrite === true && hasThrowPolicy(options.onMissing)) {\n throw new Error(\n `i18nText: densifyOnWrite cannot be combined with an explicit onMissing 'throw' ` +\n `policy — densify fills every empty slot, so a 'throw' would be unreachable. ` +\n `Remove the 'throw' policy or disable densifyOnWrite.`,\n )\n }\n return { _noydbI18nText: true, _viaBrand: 'i18n', options }\n}\n\n/** True when `onMissing` declares `'throw'` for any layer (scalar or per-layer). */\nfunction hasThrowPolicy(onMissing: OnMissingPolicy | undefined): boolean {\n if (onMissing === undefined) return false\n if (typeof onMissing === 'string') return onMissing === 'throw'\n return Object.values(onMissing).includes('throw')\n}\n\n// `isI18nTextDescriptor` now lives on the kernel port (#623 Task 11,\n// port/with/i18n-strategy.ts, alongside `isStaticDictDescriptor`) —\n// re-exported here for compat with existing importers of this module.\nexport { isI18nTextDescriptor } from '../../port/with/i18n-strategy.js'\n\n// ─── Validation helpers ────────────────────────────────────────────────\n\n/**\n * Validate that a value is a valid `{ [locale]: string }` map and that\n * all required locales are present. Throws `MissingTranslationError`\n * when the required constraint is violated.\n *\n * Called by `Collection.put()` for each registered `i18nField`.\n *\n * @param value The raw field value from the record being put.\n * @param field The field name (used in the thrown error message).\n * @param descriptor The `i18nText()` descriptor for this field.\n */\nexport function validateI18nTextValue(\n value: unknown,\n field: string,\n descriptor: I18nTextDescriptor,\n): void {\n const { options } = descriptor\n\n // Must be a non-null object\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new MissingTranslationError(\n field,\n options.languages,\n `Field \"${field}\" must be a { [locale]: string } map, got ${typeof value}.`,\n )\n }\n\n const map = value as Record<string, unknown>\n\n // All values must be strings\n for (const [locale, v] of Object.entries(map)) {\n if (typeof v !== 'string') {\n throw new MissingTranslationError(\n field,\n [locale],\n `Field \"${field}\": locale \"${locale}\" must be a string, got ${typeof v}.`,\n )\n }\n }\n\n // Check required constraint\n const { required } = options\n if (required === 'all') {\n const missing = options.languages.filter(\n (lang) => !(lang in map) || map[lang] === '',\n )\n if (missing.length > 0) {\n throw new MissingTranslationError(\n field,\n missing,\n `Field \"${field}\" requires all declared languages. Missing: ${missing.join(', ')}.`,\n )\n }\n } else if (required === 'any') {\n const present = options.languages.some(\n (lang) => lang in map && map[lang] !== '',\n )\n if (!present) {\n throw new MissingTranslationError(\n field,\n options.languages,\n `Field \"${field}\" requires at least one declared language. None present.`,\n )\n }\n } else {\n // string[] — named required locales; TypeScript narrows required to readonly string[]\n const requiredList = required\n const missing = requiredList.filter(\n (lang) => !(lang in map) || map[lang] === '',\n )\n if (missing.length > 0) {\n throw new MissingTranslationError(\n field,\n missing,\n `Field \"${field}\" requires: ${requiredList.join(', ')}. Missing: ${missing.join(', ')}.`,\n )\n }\n }\n}\n\n// ─── Locale resolution ─────────────────────────────────────────────────\n\n/**\n * Resolve an i18nText value (`{ [locale]: string }` map) to a string\n * for the given locale.\n *\n * @param value The stored locale map.\n * @param locale The requested locale code, or `'raw'` to return the map.\n * @param fallback Single locale or ordered list; use `'any'` as the last\n * element to fall back to any available translation.\n * @param field Field name used in `LocaleNotSpecifiedError` messages.\n * @returns The resolved string, OR the original map when `locale === 'raw'`.\n */\n/** Options for the policy-aware form of {@link resolveI18nText}. */\nexport interface ResolveI18nOptions {\n /** Effective policy for the resolution layer. Default `'throw'`. */\n readonly policy?: OnMissing\n /** Declared substitute chain; applied only under policy `'substitute'`. */\n readonly substitute?: readonly string[]\n /**\n * Smart-substitute. When `true` and policy is `'substitute'`, after the\n * explicit chain misses, pick the available locale whose script is nearest the\n * target (same script first, then Latin's broad readability) instead of an\n * arbitrary value. Default `false`.\n */\n readonly smartSubstitute?: boolean\n}\n\n/** Normalize a single-or-list fallback into an array. */\nfunction toChain(fallback: string | readonly string[] | undefined): readonly string[] {\n return Array.isArray(fallback) ? fallback : fallback ? [fallback as string] : []\n}\n\n/** Walk a chain, returning the first non-empty value (or `'any'` match). */\nfunction pickFromChain(\n value: Record<string, string>,\n chain: readonly string[],\n): string | undefined {\n for (const fb of chain) {\n if (fb === 'any') {\n const any = Object.values(value).find((v) => v !== '')\n if (any !== undefined) return any\n } else if (value[fb] !== undefined && value[fb] !== '') {\n return value[fb]\n }\n }\n return undefined\n}\n\n// Legacy 4-arg form: can only throw or return — never null. Keeps every\n// existing call site's type unchanged (default policy is 'throw').\nexport function resolveI18nText(\n value: Record<string, string>,\n locale: string,\n fallback?: string | readonly string[],\n field?: string,\n): string | Record<string, string>\n// Policy-aware form: may return null under 'null'/'substitute' policies.\nexport function resolveI18nText(\n value: Record<string, string>,\n locale: string,\n fallback: string | readonly string[] | undefined,\n field: string | undefined,\n opts: ResolveI18nOptions,\n): string | Record<string, string> | null\nexport function resolveI18nText(\n value: Record<string, string>,\n locale: string,\n fallback?: string | readonly string[],\n field?: string,\n opts?: ResolveI18nOptions,\n): string | Record<string, string> | null {\n if (locale === 'raw') {\n return value\n }\n\n if (!locale) {\n throw new LocaleNotSpecifiedError(field ?? '<unknown>')\n }\n\n // Primary locale\n if (value[locale] !== undefined && value[locale] !== '') {\n return value[locale]\n }\n\n const policy: OnMissing = opts?.policy ?? 'throw'\n\n // Caller-supplied fallback ALWAYS applies first (backward compat +\n // explicit read-time override), regardless of policy.\n const callerChain = toChain(fallback)\n const callerHit = pickFromChain(value, callerChain)\n if (callerHit !== undefined) return callerHit\n\n // Declared substitute applies ONLY under policy 'substitute'.\n if (policy === 'substitute') {\n const subHit = pickFromChain(value, toChain(opts?.substitute))\n if (subHit !== undefined) return subHit\n // Smart-substitute: after the explicit chain, prefer the script-nearest\n // available locale over an arbitrary first-non-empty value.\n if (opts?.smartSubstitute) {\n const smartHit = pickNearestScript(value, locale)\n if (smartHit !== undefined) return smartHit\n }\n }\n\n // Exhausted.\n if (policy === 'throw') {\n throw new LocaleNotSpecifiedError(\n field ?? '<unknown>',\n `No translation available for locale \"${locale}\"` +\n (callerChain.length > 0 ? ` or fallback chain [${callerChain.join(', ')}]` : '') +\n '.',\n )\n }\n return null\n}\n\n/**\n * Smart-substitute: among the non-empty locales in `value`, pick the one\n * whose primary script is nearest the target `locale` — same script first, then\n * Latin (broadly readable), then any other — so a missing Thai label falls back\n * to another Thai (or Latin) value rather than, say, an Arabic one. First-seen\n * wins on ties. Returns the value string, or `undefined` when `value` has no\n * non-empty entry.\n */\nfunction pickNearestScript(value: Record<string, string>, target: string): string | undefined {\n const targetScript = inferScripts(target)[0] ?? 'Latin'\n let best: { score: number; v: string } | undefined\n for (const [loc, v] of Object.entries(value)) {\n if (typeof v !== 'string' || v === '') continue\n const s = inferScripts(loc)[0] ?? 'Latin'\n const score = s === targetScript ? 0 : s === 'Latin' ? 1 : 2\n if (best === undefined || score < best.score) best = { score, v }\n if (score === 0) break // nothing beats a same-script match\n }\n return best?.v\n}\n\n// ─── Path helpers (nested i18nFields like 'address.lineOne') ──────────\n\n// `getAtPath`/`setAtPathInPlace` are generic dotted-path/[]-wildcard record\n// helpers, not i18n logic — moved to `kernel/paths.ts` (Task 7, #623).\n// `getAtPath` re-exported here for compat with existing importers of this\n// module (embeddings/descriptor.ts, search/build-docs.ts); `setAtPathInPlace`\n// is not re-exported — its only consumer (via-i18n/binding.ts) imports it\n// directly from kernel/paths.ts.\nexport { getAtPath } from '../../kernel/paths.js'\n\n/** Recursively resolve i18nText at a single path within a record copy. */\nfunction applyAtPath(\n obj: Record<string, unknown>,\n path: string,\n locale: string,\n fallback: string | readonly string[] | undefined,\n opts: ResolveI18nOptions,\n): Record<string, unknown> {\n const arrayIdx = path.indexOf('[].')\n if (arrayIdx !== -1) {\n const arrayKey = path.slice(0, arrayIdx)\n const restPath = path.slice(arrayIdx + 3)\n const arr = obj[arrayKey]\n if (!Array.isArray(arr)) return obj\n return {\n ...obj,\n [arrayKey]: arr.map(item => {\n if (!item || typeof item !== 'object' || Array.isArray(item)) return item\n return applyAtPath(item as Record<string, unknown>, restPath, locale, fallback, opts)\n }),\n }\n }\n const dotIdx = path.indexOf('.')\n if (dotIdx !== -1) {\n const head = path.slice(0, dotIdx)\n const rest = path.slice(dotIdx + 1)\n const nested = obj[head]\n if (!nested || typeof nested !== 'object' || Array.isArray(nested)) return obj\n return {\n ...obj,\n [head]: applyAtPath(nested as Record<string, unknown>, rest, locale, fallback, opts),\n }\n }\n const raw = obj[path]\n if (raw === undefined || raw === null) return obj\n if (typeof raw !== 'object' || Array.isArray(raw)) return obj\n return {\n ...obj,\n [path]: resolveI18nText(raw as Record<string, string>, locale, fallback, path, opts),\n }\n}\n\n/**\n * Apply locale resolution to a single record, returning a new copy.\n *\n * For each field registered as an `i18nText` descriptor:\n * - If `locale === 'raw'`, the field value is left as the stored map.\n * - Otherwise, the field value is replaced with the resolved string.\n *\n * Field paths support dot notation (`'address.lineOne'`) and array\n * wildcards (`'contacts[].title'`). Top-level fields work as before.\n *\n * @param record The decrypted record.\n * @param i18nFields Map of field path → `I18nTextDescriptor`.\n * @param locale The requested locale (or `'raw'`).\n * @param fallback Fallback chain (optional).\n * @param layer Resolution layer (default `'read'`). Each field's\n * `onMissing` policy is resolved for this layer, so the\n * same record resolves leniently on a get but strictly\n * inside an mv/derivation.\n */\nexport function applyI18nLocale(\n record: Record<string, unknown>,\n i18nFields: Record<string, I18nTextDescriptor>,\n locale: string,\n fallback?: string | readonly string[],\n layer: Layer = 'read',\n): Record<string, unknown> {\n const fieldNames = Object.keys(i18nFields)\n if (fieldNames.length === 0) return record\n\n let result = record\n\n for (const [field, descriptor] of Object.entries(i18nFields)) {\n const { onMissing, substitute, smartSubstitute } = descriptor.options\n const opts: ResolveI18nOptions = {\n policy: resolvePolicy(onMissing, layer),\n ...(substitute !== undefined ? { substitute } : {}),\n ...(smartSubstitute ? { smartSubstitute } : {}),\n }\n result = applyAtPath(result, field, locale, fallback, opts)\n }\n\n // The internal densify provenance marker never leaves the store.\n result = stripI18nFilled(result)\n\n return result\n}\n\n/**\n * Sync i18n-text-only present-for-join dressing — the exact `join`-layer\n * partial application `kernel/query/join.ts` used to import and call\n * directly before #626's retirement (#650 Task 6): fixes `fallback`\n * `undefined` and `layer` `'join'`. `via/lookup/snapshot.ts`'s\n * `buildPresentForJoin` composes this with the lookup-label half to build\n * the `JoinableSource.presentForJoin` hook the join executor now calls\n * instead of reaching into this module.\n */\nexport function presentI18nForJoin(\n record: Record<string, unknown>,\n i18nFields: Record<string, I18nTextDescriptor>,\n locale: string,\n): Record<string, unknown> {\n return applyI18nLocale(record, i18nFields, locale, undefined, 'join')\n}\n\n/**\n * Remove the internal densify provenance marker (`_i18nFilled`) from a\n * read-facing record. NON-mutating: returns the same object when the\n * marker is absent, otherwise a shallow copy without the marker.\n *\n * MUST be applied on every user-facing read return — even locale-less ones,\n * which bypass {@link applyI18nLocale}. MUST NOT be applied on the internal\n * prior-read path (decryptRecord / resolveDensifyPrior), where densify needs\n * the marker.\n */\nexport function stripI18nFilled<T extends Record<string, unknown>>(record: T): T {\n if (!Object.prototype.hasOwnProperty.call(record, '_i18nFilled')) return record\n const rest: T = { ...record }\n delete (rest as Record<string, unknown>)._i18nFilled\n return rest\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
NoydbError
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-JBIV4FLR.js";
|
|
4
4
|
|
|
5
5
|
// src/kernel/via/index.ts
|
|
6
6
|
var binders = /* @__PURE__ */ new Map();
|
|
@@ -26,4 +26,4 @@ export {
|
|
|
26
26
|
isViaInstalled,
|
|
27
27
|
viaBinder
|
|
28
28
|
};
|
|
29
|
-
//# sourceMappingURL=chunk-
|
|
29
|
+
//# sourceMappingURL=chunk-XDVVBETV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/kernel/via/index.ts"],"sourcesContent":["// kernel/via/index.ts — the ONLY kernel-resident via surface.\nimport { NoydbError } from '../errors.js'\nimport type { EncryptedEnvelope } from '../types.js'\n\n/** Awaitable type for potentially async results. */\ntype Awaitable<T> = T | Promise<T>\n\n/** Declared security posture — a property the kernel enforces (enforcement activates in phases B/C). */\nexport interface ViaPosture {\n readonly encryptedAtRest: 'envelope' | 'sealed'\n readonly queryable: 'none' | 'det-exact' | 'ordered' | 'full'\n readonly exportable: boolean\n readonly forgettable: boolean\n}\n\n/** Opaque marker every feature descriptor extends; the kernel never sees concrete descriptor types. */\nexport interface ViaDescriptor { readonly _viaBrand: string }\n\n/** Per-call write context (A: minimal; B/C extend). */\nexport interface ViaWriteCtx {\n readonly id: string\n /** Owning vault name (event payload identity — e.g. i18n:script-violation). */\n readonly vault: string\n /** Prior stored record (decoded), lazily resolved. Null when creating. */\n readonly prior: () => Promise<Record<string, unknown> | null>\n /** Typed event emission (e.g. i18n:script-violation). */\n readonly emit: (event: string, payload: unknown) => void\n}\n\n/** Per-call read context — mirrors LocaleReadOptions loosely; features narrow. */\nexport interface ViaReadCtx {\n readonly locale?: unknown\n readonly fallback?: unknown\n readonly layer: string\n}\n\n/** One sealed slot's ciphertext — matches the existing `iv:data` sealed map entries (seam map §2 step 2). */\nexport interface SealedSlotRef { readonly iv: string; readonly data: string }\n\n/**\n * A scoped crypto capability handed to a `via` feature's `encodeAtRest`/\n * `decodeAtRest`/`erase` hooks — never the keyring, never the enclave.\n * `sealedSlots` is pre-bound to one `(collection, recordId)`; `reservedEnvelopes`\n * is a whole-envelope encrypt/decrypt door scoped to collection names under a\n * declared prefix (e.g. `_dict_`).\n */\nexport interface ViaCryptoCtx {\n readonly sealedSlots: {\n seal(field: string, plaintext: unknown): Promise<SealedSlotRef>\n unseal(field: string, ref: SealedSlotRef): Promise<unknown>\n delete(field: string): Promise<void>\n }\n reservedEnvelopes(prefix: string): {\n /**\n * `id` is the record id the envelope will be stored under. Required\n * (#1051) because this function holds the DEK, so it is where record-identity\n * AAD is applied — a caller that cannot name the id cannot be bound.\n */\n encrypt(\n identity: { readonly collection: string; readonly id: string; readonly by?: string },\n json: string,\n v: number,\n ): Promise<EncryptedEnvelope>\n decrypt(collection: string, id: string, env: EncryptedEnvelope): Promise<string>\n }\n}\n\n/** Per-call erase context — `forget()`'s per-ref participation door (phase C). */\nexport interface ViaEraseCtx { readonly id: string; readonly vault: string; readonly live: unknown /* EncryptedEnvelope */; readonly crypto: ViaCryptoCtx }\n/**\n * What an `erase` hook reports back to `forget()`'s summary ledger entry.\n * `retainedShared` is additive (#629 Task 10) — content NOT erased because\n * still referenced by another live record (blob's shared-chunk dual\n * accounting, `shredAllForRecord()`'s `retainedShared` count). Absent/0 for\n * bindings with no such concept (a sealed field is never \"shared\").\n */\nexport interface ViaEraseReport { readonly shredded: number; readonly residue: readonly unknown[]; readonly retainedShared?: number }\n\n/**\n * A feature bound to one collection's declared config. Record-grain hooks —\n * every hook receives the whole record (matches the real engine signatures,\n * e.g. quantizeMoneyFields(record, moneyFields)). All hooks optional;\n * absent = passthrough. Query-participation hooks are SYNC (#553).\n */\nexport interface NoydbVia {\n readonly brand: string\n readonly posture: ViaPosture\n /** Declared dependencies (field paths / cross-record specs). MANDATORY for any future\n * derive-bearing binding — phase C validates well-formedness (strings, non-empty,\n * reference declared fields) and graph-registers a derived edge for each covered\n * field at declare time, right after `compileVias` builds the bindings\n * (unknown source field throws `ValidationError`). See\n * `kernel/collection-config.ts`'s `resolveViaDepsEdges`. */\n readonly deps?: readonly string[]\n /** Collection-name prefixes this binding's `reservedEnvelopes` capability may address (e.g. `_dict_`). */\n readonly reservedPrefixes?: readonly string[]\n /**\n * Does this binding own `field`? Backs `ViaPipeline.postureFor` (#629 Task\n * 8's posture consumer) — a passive coverage check, independent of\n * `buildClause`/`compareForOrder` (which some bindings, e.g. classified and\n * blob, never define). SYNC, no side effects.\n *\n * Contract (documentation only — not enforced at runtime, future work):\n * any binding declaring a non-default posture value — `queryable: 'none'`\n * (#629 Task 8) or `exportable: false` (#629 Task 9's `redactForExport`)\n * — MUST implement `covers()`. `postureFor`'s consumers only engage for\n * fields a binding actively claims via `covers()`; a binding that omits\n * it would silently fall through to the generic (unrefused/unredacted)\n * path instead.\n */\n covers?(field: string): boolean\n // NOTE: phase C adds a `derive` hook ADDITIVELY — do not stub it now.\n // ── write pipeline ──\n /** Refuse a write before crypto runs (classified step-3 slot: storage:'never' rejection + validators). Throws to refuse. */\n enforceWrite?(record: Record<string, unknown>, ctx: ViaWriteCtx): void | Promise<void>\n /** First pipeline stage (money canonicalizeIncomingMoney). SYNC. */\n ingest?(record: Record<string, unknown>): Record<string, unknown>\n /** Decode STORED form to canonical for internal boundaries (gates, derivations, patch bases). SYNC. */\n canonicalizeStored?(record: Record<string, unknown>): Record<string, unknown>\n /** Post-validation write encoding (money quantize; i18n translate→script→validate→densify). May be async. */\n encodeWrite?(record: Record<string, unknown>, ctx: ViaWriteCtx): Awaitable<Record<string, unknown>>\n /** Final write-pipeline stage: seal/encrypt declared fields via `crypto` before the envelope body is built (classified step-2 slot). */\n encodeAtRest?(record: Record<string, unknown>, crypto: ViaCryptoCtx): Promise<{ record: Record<string, unknown>; sealed?: Record<string, SealedSlotRef> }>\n // ── read pipeline ──\n /** First read-pipeline stage: unseal/decrypt declared fields via `crypto` before `present` runs (classified sealed-handle slot). */\n decodeAtRest?(record: Record<string, unknown>, sealed: Record<string, SealedSlotRef>, crypto: ViaCryptoCtx, opts: { asHandles: boolean }): Promise<Record<string, unknown>>\n /** Read-time presentation (money decode+virtuals; i18n locale/labels/strip). May be async. */\n present?(record: Record<string, unknown>, ctx: ViaReadCtx): Awaitable<Record<string, unknown>>\n /**\n * Late read-time presentation (#669) — runs AFTER every binding's `present()` in the\n * money+computed present-order segment (`ViaPipeline`'s `_presentOrder`, `kernel/via/\n * pipeline.ts`), BEFORE the \"everything else\" segment (i18n/lookup dressing, taint\n * redaction) runs. Exists for a binding that needs to react to a virtual computed\n * field's fresh OWN-field output before anything dresses it (money's MAJOR-UNITS\n * quantize-and-present for a field that is BOTH money AND `mode:'virtual'` computed) —\n * `present()` alone can't express \"run after computed for THESE fields only, before\n * computed for all others\", since it's a per-BINDING fold, not per-field. Folded over\n * `bindings` in declaration order (mirrors every other per-binding fold), so lookup/\n * i18n dressing and taint/redaction (both in the \"everything else\" segment) still see\n * the already-dressed value.\n */\n presentLate?(record: Record<string, unknown>, ctx: ViaReadCtx): Awaitable<Record<string, unknown>>\n // ── query participation (ALL SYNC — #553) ──\n /** Returns an opaque clause payload when this binding covers `field`, else undefined. */\n // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents\n buildClause?(field: string, op: string, value: unknown): unknown | undefined\n /** Evaluate a payload produced by buildClause against a raw stored value. */\n evaluateClause?(actual: unknown, op: string, payload: unknown): boolean\n /**\n * Optional: the STORED-form operand for a direct secondary-index probe\n * against a payload produced by `buildClause` (#625) — lets\n * `candidateRecords()` (`query/builder.ts`) hit `CollectionIndexes.\n * lookupEqual`/`lookupIn` for `==`/`in` clauses instead of falling back\n * to a linear scan + per-record `evaluateClause`. Returning `undefined`\n * means \"no sound probe for this op/payload\" (e.g. a comparison whose\n * stored form isn't a single index-bucketable value, or an op the index\n * can't serve) — the caller falls back to the scan. A binding that\n * never implements this hook always falls back, unchanged from before\n * this hook existed.\n */\n // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents\n indexProbe?(op: string, payload: unknown): unknown | undefined\n /**\n * Optional: canonicalize a raw STORED field value into the bucket key an\n * eager index should use (#672) — lets `CollectionIndexes` group a\n * pre-declaration / non-canonical stored value (e.g. a money field's\n * leftover `'0100'` written before its `money()` declaration) under the\n * SAME key a canonical write produces (`'100'`), so the index-probe fast\n * path (`indexProbe` above) and the fallback scan (`evaluateClause`)\n * agree on which records match. `undefined` means \"not mine / can't\n * canonicalize this value\" — the caller buckets the raw stringified\n * value, unchanged from before this hook existed. MUST agree with what\n * this binding's own `evaluateClause`/`indexProbe` treat as equal, or\n * the fast path and the scan will disagree.\n */\n canonicalizeIndexKey?(field: string, rawValue: unknown): string | undefined\n /** Decode a raw stored record for query/scan results and callback views ('raw' — no virtuals). */\n decodeResults?(record: unknown): unknown\n /** Exact ordering for a covered field; undefined when the field is not covered. */\n compareForOrder?(field: string, a: unknown, b: unknown): number | undefined\n /**\n * Per-key, PER-CALL-locale label resolution for `orderBy(field, dir,\n * { by: 'label' })` (#650 Task 7) — the sibling `compareForOrder` above\n * structurally cannot serve, since it carries no locale parameter.\n * `locale` is the query's per-call locale (`undefined` for a locale-less\n * query — a binding may fall back to its own descriptor-level default,\n * mirroring `present`'s displayLocale hinge). `undefined` return = this\n * binding doesn't resolve a label for `key` at `field` — caller falls\n * back to the raw stored value, same graceful-degrade discipline every\n * other via query hook uses. SYNC (#553 — query participation).\n */\n resolveOrderLabel?(field: string, key: string, locale: string | undefined): string | undefined\n /** Rewrite an aggregate spec (money exact reducers). */\n wrapReducers?(spec: unknown): unknown\n // ── forget participation ──\n /** `forget()`'s per-ref erasure door — shred/report this binding's residue for one record (classified/blob forget participation). */\n erase?(ctx: ViaEraseCtx): Promise<ViaEraseReport>\n // ── introspection ──\n describeFragment?(): Record<string, unknown>\n}\n\n/** Binder: constructs a binding from a collection's declared config. Installed by the feature's declaration factory. */\nexport type ViaBinder = (config: unknown) => NoydbVia\n\nconst binders = new Map<string, ViaBinder>()\n\n/** @internal — called (idempotently, first-wins) by a feature's declaration factory, e.g. money(). */\nexport function installViaBinder(brand: string, binder: ViaBinder): void {\n if (!binders.has(brand)) binders.set(brand, binder)\n}\n\n/** @internal — registry presence check. Used by tests (mirrors isMoneyEngineInstalled) and by vault's i18n validator delegators as a no-i18n-ever-declared fast path. */\nexport function isViaInstalled(brand: string): boolean {\n return binders.has(brand)\n}\n\n/** @internal — resolve a binder; throws when the declaration factory never ran (hand-rolled descriptors). */\nexport function viaBinder(brand: string): ViaBinder {\n const b = binders.get(brand)\n if (!b) {\n throw new NoydbError(\n 'VIA_NOT_LINKED',\n `via feature \"${brand}\" requires descriptors created via its declaration factory from @noy-db/hub`,\n )\n }\n return b\n}\n"],"mappings":";;;;;AA4MA,IAAM,UAAU,oBAAI,IAAuB;AAGpC,SAAS,iBAAiB,OAAe,QAAyB;AACvE,MAAI,CAAC,QAAQ,IAAI,KAAK,EAAG,SAAQ,IAAI,OAAO,MAAM;AACpD;AAGO,SAAS,eAAe,OAAwB;AACrD,SAAO,QAAQ,IAAI,KAAK;AAC1B;AAGO,SAAS,UAAU,OAA0B;AAClD,QAAM,IAAI,QAAQ,IAAI,KAAK;AAC3B,MAAI,CAAC,GAAG;AACN,UAAM,IAAI;AAAA,MACR;AAAA,MACA,gBAAgB,KAAK;AAAA,IACvB;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CargoNotEnabledError
|
|
3
|
+
} from "./chunk-JBIV4FLR.js";
|
|
4
|
+
|
|
5
|
+
// src/with-cargo/strategy.ts
|
|
6
|
+
var NO_CARGO = {
|
|
7
|
+
async extractPartition() {
|
|
8
|
+
throw new CargoNotEnabledError();
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
NO_CARGO
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=chunk-XJ3FEKEY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/with-cargo/strategy.ts"],"sourcesContent":["/**\n * Cargo capability strategy (FR-6/FR-7) — the source-side, owner-level\n * partition operation that requires a live {@link Vault}: extracting a re-keyed\n * transfer-sealed partition ({@link extractPartitionCore}). The active engine\n * ({@link withCargo}) dynamically imports that engine (keeping the extraction\n * crypto out of the floor bundle); {@link NO_CARGO} throws.\n *\n * The public free function `extractPartition` (which takes a `Vault`) delegates\n * here via `vault.cargoStrategy`, so an un-opted-in caller hits `NO_CARGO`'s\n * throw.\n *\n * Carve-out (mirrors `openSealedRecord` / `liberateVault`): the recipient-side\n * free functions `adoptPartition` and `decryptExtractedPartition` operate on\n * raw bundle bytes + a destination store — they carry NO source instance to\n * gate against, so they stay ungated host-side tooling. `diffVault` is likewise\n * ungated: it's shared import/merge infrastructure the `as-*` exporters call\n * internally, not a gated cargo capability.\n * @internal\n */\nimport type { Vault } from '../kernel/vault.js'\nimport type { ExtractPartitionResult, ExtractPartitionOptions } from './extract-partition.js'\nimport { CargoNotEnabledError } from '../kernel/errors.js'\n\nexport interface CargoStrategy {\n extractPartition(vault: Vault, opts: ExtractPartitionOptions): Promise<ExtractPartitionResult>\n}\n\n/**\n * No-op stub — the floor default. The source-side cargo op throws\n * {@link CargoNotEnabledError}; opt in with `cargoStrategy: withCargo()` in\n * createNoydb. @internal\n */\nexport const NO_CARGO: CargoStrategy = {\n async extractPartition() { throw new CargoNotEnabledError() },\n}\n"],"mappings":";;;;;AAgCO,IAAM,WAA0B;AAAA,EACrC,MAAM,mBAAmB;AAAE,UAAM,IAAI,qBAAqB;AAAA,EAAE;AAC9D;","names":[]}
|
|
@@ -1,14 +1,29 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readPath
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-NSITFOCJ.js";
|
|
4
4
|
import {
|
|
5
5
|
DanglingReferenceError,
|
|
6
6
|
JoinTooLargeError
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-JBIV4FLR.js";
|
|
8
8
|
|
|
9
9
|
// src/kernel/query/join.ts
|
|
10
10
|
var DEFAULT_JOIN_MAX_ROWS = 5e4;
|
|
11
11
|
var JOIN_WARN_FRACTION = 0.8;
|
|
12
|
+
function referencesJoinAlias(clause, aliases) {
|
|
13
|
+
if (clause.type !== "field") return false;
|
|
14
|
+
return aliases.has(clause.field.split(".")[0]);
|
|
15
|
+
}
|
|
16
|
+
function splitAroundJoins(clauses, joins) {
|
|
17
|
+
if (joins.length === 0 || clauses.length === 0) return { preJoin: clauses, postJoin: [] };
|
|
18
|
+
const aliases = new Set(joins.map((leg) => leg.as));
|
|
19
|
+
const preJoin = [];
|
|
20
|
+
const postJoin = [];
|
|
21
|
+
for (const clause of clauses) {
|
|
22
|
+
;
|
|
23
|
+
(referencesJoinAlias(clause, aliases) ? postJoin : preJoin).push(clause);
|
|
24
|
+
}
|
|
25
|
+
return { preJoin, postJoin };
|
|
26
|
+
}
|
|
12
27
|
function coerceRefKey(value) {
|
|
13
28
|
if (value === null || value === void 0) return null;
|
|
14
29
|
if (typeof value === "string") return value;
|
|
@@ -167,7 +182,8 @@ function resetJoinWarnings() {
|
|
|
167
182
|
|
|
168
183
|
export {
|
|
169
184
|
DEFAULT_JOIN_MAX_ROWS,
|
|
185
|
+
splitAroundJoins,
|
|
170
186
|
applyJoins,
|
|
171
187
|
resetJoinWarnings
|
|
172
188
|
};
|
|
173
|
-
//# sourceMappingURL=chunk-
|
|
189
|
+
//# sourceMappingURL=chunk-XSN63FUS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/kernel/query/join.ts"],"sourcesContent":["/**\n * Query DSL `.join()` — eager, single-FK, intra-vault joins.\n *\n * resolves a ref()-declared foreign key into an attached\n * right-side record under an alias, using one of two planner paths\n * selected automatically:\n *\n * - **nested-loop** — right-side source exposes `lookupById`, so\n * each left row costs O(1). This is the common path for joins\n * against a Collection, which backs `lookupById` with a Map\n * lookup.\n * - **hash** — right-side has only `snapshot()`. Build a\n * `Map<id, record>` once, probe per left row. Same asymptotic\n * cost for our collections, but the path exists as a fallback\n * for custom QuerySource implementations and as an explicit\n * test-only override via `{ strategy: 'hash' }`.\n *\n * Scope:\n *\n * - Equi-joins on declared `ref()` fields only. Joins on\n * undeclared fields throw at plan time with an actionable error\n * naming the field and collection.\n * - Same-vault only. Cross-vault correlation goes\n * through `queryAcross`; this is an architectural\n * invariant, not a limitation we plan to lift.\n * - Hard row ceiling via `JoinTooLargeError` — default 50k per\n * side, override via `{ maxRows }`. Warns at 80% of the ceiling\n * on the existing warn channel.\n * - Three ref-mode behaviors on dangling refs:\n * strict → `DanglingReferenceError`,\n * warn → attach `null` with a one-shot warning,\n * cascade → attach `null` silently (cascade is a delete-time\n * mode; any dangling refs still present at read time are\n * mid-flight cascades or orphans from earlier, not a DSL error).\n *\n * Partition-awareness seam:\n *\n * Every `JoinLeg` carries a `partitionScope` field that is always\n * `'all'` in. The executor never reads this field.\n * partition-aware joins will start populating it from `where()`\n * predicates on the partition key without changing the planner's\n * external shape — this is the whole reason it exists now.\n *\n * Joins stay OUT of the ledger: reads don't touch `_ledger/`,\n * including joined reads.\n */\n\nimport type { RefDescriptor, RefMode } from '../refs.js'\nimport type { Clause } from './predicate.js'\nimport { readPath } from './predicate.js'\nimport { JoinTooLargeError, DanglingReferenceError } from '../errors.js'\n\n/** Planner strategy for a single join leg. Auto-selected unless overridden. */\nexport type JoinStrategy = 'hash' | 'nested'\n\n/** Default per-side row ceiling before `.join()` throws `JoinTooLargeError`. */\nexport const DEFAULT_JOIN_MAX_ROWS = 50_000\n\n/**\n * Fraction of the row ceiling at which a one-shot warning is emitted.\n * At 80% we warn; at 100% we throw. The warn gives consumers a\n * heads-up before the hard error so they can raise the ceiling or\n * filter further without first hitting a broken query.\n */\nconst JOIN_WARN_FRACTION = 0.8\n\n/**\n * Internal representation of a single join leg in the query plan.\n *\n * This is the primary place where constraint #1 is honored:\n * every leg carries a `partitionScope` field that is always `'all'`\n * in and is never read by the executor. partition-aware\n * joins will start populating it from `where()` predicates on the\n * partition key without changing the planner's external shape.\n */\nexport interface JoinLeg {\n /** Field on the left-side record holding the foreign key value. */\n readonly field: string\n /** Alias key under which the joined right-side record attaches. */\n readonly as: string\n /** Target collection name, resolved from the `ref()` declaration. */\n readonly target: string\n /** Ref mode controlling behavior on dangling refs at read time. */\n readonly mode: RefMode\n /** Manual planner strategy override. `undefined` → auto-select. */\n readonly strategy: JoinStrategy | undefined\n /** Per-side row ceiling override. `undefined` → DEFAULT_JOIN_MAX_ROWS. */\n readonly maxRows: number | undefined\n /**\n * Partition scope for future partition-aware joins. Always `'all'`\n * today — the executor never reads this field. Future versions will\n * populate it from `where()` predicates without breaking the\n * planner's external shape. Do not remove even though it looks\n * unused today — that's the whole point of having it.\n */\n readonly partitionScope: 'all' | readonly string[]\n /**\n * When `true`, this is a dictionary join. The executor\n * resolves the left-field value against the dict snapshot and\n * attaches `{ ...labels, key }` rather than a right-side record.\n * `target` holds the dictionary name (not a collection name).\n */\n readonly isDictJoin?: true\n}\n\n/**\n * Minimal shape of a joinable right-side record source.\n *\n * Collections implement this structurally via their `QuerySource`;\n * sources without `lookupById` force the hash-join fallback. Kept as\n * a thin interface so tests can wire up plain-object sources without\n * pulling in the full Collection class.\n *\n * The optional `subscribe` is used by `Query.live()` to merge\n * right-side change streams into the live re-run trigger. Sources\n * that omit `subscribe` still work for live joins — they just\n * don't drive re-fires when their right side mutates. Collection\n * implements `subscribe` by hooking into the existing per-\n * vault event emitter.\n */\nexport interface JoinableSource {\n snapshot(): readonly unknown[]\n lookupById?(id: string): unknown\n /**\n * Default locale a label-resolving query falls back to when the query\n * itself is locale-less. Set by a `staticDict()`-backed source from its\n * `displayLocale` so `{ by: 'label' }` resolves under a locale-less read.\n * Plain `_dict_*`-backed sources omit it.\n */\n readonly displayLocale?: string\n /**\n * Sync present-for-join dressing (#626 retirement, #650 Task 6) — when\n * present and the query carries a locale, each joined right-side record\n * is passed through this hook (built by the right-side `Collection` from\n * its own i18n-text + lookup-label bindings) BEFORE it is attached under\n * the leg's alias — so a joined `i18nText` field resolves to a string\n * (not a raw `{ locale }` map) and a joined lookup field gains its\n * `<field>Label`. Locale-less queries leave joined fields raw\n * (consistent with a locale-less read). Replaces the old `i18nFields`\n * data field — the join executor no longer resolves i18n locale itself,\n * it just calls this hook.\n */\n readonly presentForJoin?: (record: unknown, locale: string) => unknown\n /**\n * Subscribe to mutations on this source. The callback fires\n * AFTER the underlying record set has been updated. Returns an\n * unsubscribe function. Optional — sources without this method\n * cannot trigger live-join re-fires from their side.\n */\n subscribe?(cb: () => void): () => void\n}\n\n/**\n * Join resolution context attached to a `Query` when it's constructed\n * from a `Collection`. Holds everything the `.join()` method needs to\n * translate a field name into a target collection + ref mode, and\n * everything the executor needs to read the right side.\n *\n * Kept as a structural interface so `Vault` can implement it\n * without `Query` needing to import `Vault` (circular-import\n * avoid). The Collection wires this up in its `query()` method using\n * the `joinResolver` back-reference the Vault passes in.\n */\nexport interface JoinContext {\n /** Name of the left-side (owning) collection. */\n readonly leftCollection: string\n /**\n * The owning collection's default locale. Used to resolve joined\n * i18n fields at the `join` layer when a terminal call doesn't pass an\n * explicit locale — so `openVault({ locale })` flows to joins like it does\n * to `get`/`list`. A per-call `toArray({ locale })` overrides it.\n */\n readonly defaultLocale?: string\n /** Look up a `RefDescriptor` by field name on the left collection. */\n resolveRef(field: string): RefDescriptor | null\n /** Resolve a right-side source by target collection name. */\n resolveSource(collectionName: string): JoinableSource | null\n /**\n * Resolve a dictKey join source. Returns a `JoinableSource`\n * whose snapshot exposes `{ key, ...labels }` records, keyed by the\n * stable dictionary key. `null` when the field is not a dictKey.\n *\n * The source is built from the compartment's in-memory dictionary\n * snapshot — same data as `DictionaryHandle.list()`, O(1) per lookup.\n */\n resolveDictSource?(field: string): JoinableSource | null\n}\n\n/**\n * Does this clause address a field that only exists once join legs are\n * attached — i.e. is its path rooted at a join alias?\n *\n * Only `FieldClause` is inspectable. A `FilterClause` carries an opaque\n * function and a `WherePredicateClause` a named one, so neither can be\n * classified; both stay on the pre-join side, which is where they have\n * always run. See `splitAroundJoins`.\n */\nfunction referencesJoinAlias(clause: Clause, aliases: ReadonlySet<string>): boolean {\n if (clause.type !== 'field') return false\n // `where('client.name', …)` addresses the alias; so does the anti-join\n // form `where('client', '==', null)`, where the path IS the alias.\n return aliases.has(clause.field.split('.')[0]!)\n}\n\n/**\n * Split a plan's clauses into those evaluable before the join legs run and\n * those that need the joined shape (#1030).\n *\n * Join legs attach after `where` so the left set can be narrowed (and\n * index-driven) first. That is the right default, but it silently broke any\n * predicate addressing a joined alias: the field did not exist yet, so\n * `readPath` returned `undefined`, nothing matched, and the query returned an\n * empty result with no error.\n *\n * The split is deliberately narrow. When no clause addresses an alias — every\n * query written against the previous behaviour — `postJoin` is empty and the\n * caller takes its original path unchanged. The reordered pipeline therefore\n * only ever runs for queries that match nothing today.\n *\n * KNOWN RESIDUAL: `.filter(r => r.client?.name === 'Ann')` cannot be\n * classified (the predicate is a closure), so it still runs pre-join and still\n * sees no alias. Prefer `.where()` for anything addressing a joined field.\n *\n * Shared by the eager `Query` and the streaming `ScanBuilder` so the two\n * cannot drift on which side a clause belongs to.\n */\nexport function splitAroundJoins(\n clauses: readonly Clause[],\n joins: readonly JoinLeg[],\n): { readonly preJoin: readonly Clause[]; readonly postJoin: readonly Clause[] } {\n if (joins.length === 0 || clauses.length === 0) return { preJoin: clauses, postJoin: [] }\n const aliases = new Set(joins.map(leg => leg.as))\n const preJoin: Clause[] = []\n const postJoin: Clause[] = []\n for (const clause of clauses) {\n ;(referencesJoinAlias(clause, aliases) ? postJoin : preJoin).push(clause)\n }\n return { preJoin, postJoin }\n}\n\n/**\n * Coerce an unknown FK value into a lookup key string.\n *\n * Legitimate ref values are strings or numbers — the same narrowing\n * the write-time `enforceRefsOnPut` path applies. Anything else\n * (objects, arrays, booleans, null, undefined) is treated as \"no\n * ref\" and returns `null`, so the join attaches `null` instead of\n * running `String({})` and producing `'[object Object]'` as a\n * bucket key. This matches the lint rule guidance and keeps\n * bizarre FK values from producing silently-wrong lookups.\n */\nfunction coerceRefKey(value: unknown): string | null {\n if (value === null || value === undefined) return null\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'bigint') return String(value)\n return null\n}\n\n/**\n * Warn-channel deduplication for dangling-ref `'warn'` mode. Keyed\n * by `field → target:refId` so the same dangling ref only produces\n * one warning even across many rows or repeated queries.\n */\nconst warnedDanglingKeys = new Set<string>()\nfunction warnOnceDangling(field: string, target: string, refId: string): void {\n const key = `${field}→${target}:${refId}`\n if (warnedDanglingKeys.has(key)) return\n warnedDanglingKeys.add(key)\n console.warn(\n `[noy-db] .join() encountered dangling ref in 'warn' mode: ` +\n `field \"${field}\" → \"${target}:${refId}\" not found. Attaching null.`,\n )\n}\n\n/**\n * Track row-ceiling warnings to fire only once per (target, side).\n * Prevents per-query spam when a consumer is running the same query\n * repeatedly (e.g. in a reactive loop).\n */\nconst warnedCeilingKeys = new Set<string>()\nfunction warnCeilingApproaching(\n target: string,\n side: 'left' | 'right',\n rows: number,\n maxRows: number,\n): void {\n const key = `${target}:${side}`\n if (warnedCeilingKeys.has(key)) return\n warnedCeilingKeys.add(key)\n const pct = Math.round((rows / maxRows) * 100)\n console.warn(\n `[noy-db] .join() ${side} side is at ${pct}% of the ${maxRows}-row ` +\n `ceiling for target \"${target}\" (${rows} rows). Streaming joins over ` +\n `scan() are not yet supported for collections that need to exceed this.`,\n )\n}\n\n/**\n * Apply every join leg in the plan against a base set of left-side\n * rows. Called by the query executor after `where` / `orderBy` /\n * `offset` / `limit` have narrowed the left set.\n *\n * Each leg attaches a `leg.as` field to every row. Returns a new\n * array of plain objects — the original left rows are not mutated\n * (structural sharing is fine for the inner fields, but the\n * top-level object is a fresh clone so consumers can further mutate\n * safely).\n *\n * **Ordering:** joins run AFTER orderBy / limit / offset in v1.\n * This keeps the planner simple and means queries like \"top 10\n * invoices with client\" sort and paginate the left side first, then\n * join. Sorting *by* a joined field is out of scope for — users\n * can post-sort the result array in userland or wait for \n * (multi-FK chaining) which can be layered on top.\n *\n * **Multi-FK chaining:** each leg's `maxRows` is enforced\n * against the current left-row count independently. Because\n * joins are equi-joins on the target's primary key (one-to-one or\n * one-to-null), the left row count is constant across legs — no\n * cartesian blowup. The per-leg left-side check is still necessary\n * so that a later leg with a tighter ceiling correctly fires on a\n * query like `.join('a', { maxRows: 100_000 }).join('b', { maxRows: 50 })`,\n * which should throw on the second leg if the left set exceeds 50.\n */\nexport function applyJoins(\n rows: readonly unknown[],\n joins: readonly JoinLeg[],\n context: JoinContext,\n locale?: string,\n): unknown[] {\n if (joins.length === 0) return [...rows]\n\n let result: unknown[] = [...rows]\n for (const leg of joins) {\n result = applyOneJoin(result, leg, context, locale)\n }\n return result\n}\n\nfunction applyOneJoin(\n leftRows: readonly unknown[],\n leg: JoinLeg,\n context: JoinContext,\n locale?: string,\n): unknown[] {\n // Dict join path — resolve left-field value against the\n // dictionary snapshot and attach { key, ...labels } under leg.as.\n if (leg.isDictJoin) {\n const dictSource = context.resolveDictSource?.(leg.field)\n if (!dictSource) {\n throw new Error(\n `.join() field \"${leg.field}\" on \"${context.leftCollection}\" is declared as a ` +\n `dictKey join but the dict source could not be resolved. ` +\n `Ensure the dictionary has at least one entry.`,\n )\n }\n const out: unknown[] = []\n const snapshot = dictSource.snapshot()\n const dictMap = new Map<string, unknown>()\n for (const entry of snapshot) {\n const k = readPath(entry, 'key')\n if (typeof k === 'string') dictMap.set(k, entry)\n }\n for (const left of leftRows) {\n const rawId = readPath(left, leg.field)\n const key = coerceRefKey(rawId)\n const dictEntry = key === null ? undefined : dictMap.get(key)\n out.push({ ...(left as Record<string, unknown>), [leg.as]: dictEntry ?? null })\n }\n return out\n }\n\n const source = context.resolveSource(leg.target)\n if (!source) {\n throw new Error(\n `.join() cannot resolve target collection \"${leg.target}\" ` +\n `(referenced from field \"${leg.field}\" on \"${context.leftCollection}\"). ` +\n `Make sure the target collection has been opened via vault.collection() ` +\n `at least once before running the query.`,\n )\n }\n\n const maxRows = leg.maxRows ?? DEFAULT_JOIN_MAX_ROWS\n\n // Per-leg left-side ceiling check. In a\n // multi-FK chain, each leg's `maxRows` is enforced independently\n // against the current left-row count, so\n // `.join('a', { maxRows: 100_000 }).join('b', { maxRows: 50 })`\n // correctly throws on the second leg if the left set exceeds 50.\n if (leftRows.length > maxRows) {\n throw new JoinTooLargeError({\n leftRows: leftRows.length,\n rightRows: -1,\n maxRows,\n side: 'left',\n message:\n `.join() left side has ${leftRows.length} rows, exceeding the ${maxRows}-row ` +\n `ceiling for target \"${leg.target}\". Filter the left side further with ` +\n `where()/limit() before joining, or raise the ceiling via { maxRows }. ` +\n `Streaming joins over scan() are not yet supported.`,\n })\n }\n if (leftRows.length > maxRows * JOIN_WARN_FRACTION) {\n warnCeilingApproaching(leg.target, 'left', leftRows.length, maxRows)\n }\n\n const rightSnapshot = source.snapshot()\n if (rightSnapshot.length > maxRows) {\n throw new JoinTooLargeError({\n leftRows: leftRows.length,\n rightRows: rightSnapshot.length,\n maxRows,\n side: 'right',\n message:\n `.join() right side \"${leg.target}\" has ${rightSnapshot.length} rows, ` +\n `exceeding the ${maxRows}-row ceiling. Raise the ceiling via { maxRows } ` +\n `if the data genuinely fits in memory, or track for streaming joins.`,\n })\n }\n if (rightSnapshot.length > maxRows * JOIN_WARN_FRACTION) {\n warnCeilingApproaching(leg.target, 'right', rightSnapshot.length, maxRows)\n }\n\n // `join`-layer dressing (#650 Task 6, #626 retirement). When the query\n // carries a locale (per-call or the owning collection's default) and the\n // right side declares a `presentForJoin` hook, resolve each matched right\n // record through it before it's attached. Locale-less → leave raw.\n const effLocale = locale ?? context.defaultLocale\n const presentResolve: ((right: unknown) => unknown) | undefined =\n effLocale !== undefined && source.presentForJoin !== undefined\n ? (right) =>\n right !== null && typeof right === 'object'\n ? source.presentForJoin!(right, effLocale)\n : right\n : undefined\n\n // Strategy selection: explicit override wins; otherwise prefer\n // nested-loop when the source exposes lookupById (O(1) per row),\n // falling back to hash join when it doesn't.\n const strategy: JoinStrategy =\n leg.strategy ?? (source.lookupById ? 'nested' : 'hash')\n\n if (strategy === 'nested' && source.lookupById) {\n // Bind through an arrow so the `this` context of lookupById\n // doesn't drift — same pattern as the existing candidateRecords\n // helper in builder.ts.\n const lookup = (id: string): unknown => source.lookupById?.(id)\n return nestedLoopJoin(leftRows, leg, lookup, presentResolve)\n }\n return hashJoin(leftRows, leg, rightSnapshot, presentResolve)\n}\n\nfunction nestedLoopJoin(\n leftRows: readonly unknown[],\n leg: JoinLeg,\n lookupById: (id: string) => unknown,\n presentResolve?: (right: unknown) => unknown,\n): unknown[] {\n const out: unknown[] = []\n for (const left of leftRows) {\n const rawId = readPath(left, leg.field)\n const key = coerceRefKey(rawId)\n let right = key === null ? undefined : lookupById(key)\n if (presentResolve && right !== undefined) right = presentResolve(right)\n out.push(attachJoin(left, leg, right, rawId))\n }\n return out\n}\n\nfunction hashJoin(\n leftRows: readonly unknown[],\n leg: JoinLeg,\n rightSnapshot: readonly unknown[],\n presentResolve?: (right: unknown) => unknown,\n): unknown[] {\n // Build the right-side hash once per query execution. We key on\n // the `id` field because ref() always points to a target's primary\n // key — non-equi and non-id joins are out of scope for.\n const rightMap = new Map<string, unknown>()\n for (const record of rightSnapshot) {\n const rawId = readPath(record, 'id')\n const key = coerceRefKey(rawId)\n if (key !== null) {\n rightMap.set(key, record)\n }\n }\n const out: unknown[] = []\n for (const left of leftRows) {\n const rawId = readPath(left, leg.field)\n const key = coerceRefKey(rawId)\n let right = key === null ? undefined : rightMap.get(key)\n if (presentResolve && right !== undefined) right = presentResolve(right)\n out.push(attachJoin(left, leg, right, rawId))\n }\n return out\n}\n\n/**\n * Attach the resolved right-side record (or null) to the left row\n * under the alias, applying ref-mode semantics for the dangling\n * case.\n *\n * A left-side record whose FK field is null/undefined is NOT a\n * dangling ref — it's \"no reference at all\", which is always\n * allowed regardless of mode. This matches the write-time\n * `enforceRefsOnPut` behavior: \"Nullish ref values are allowed —\n * treat them as 'no reference'.\"\n *\n * Only non-null FKs pointing at non-existent targets trigger the\n * mode behavior.\n */\nfunction attachJoin(\n left: unknown,\n leg: JoinLeg,\n right: unknown,\n rawId: unknown,\n): unknown {\n if (left === null || typeof left !== 'object') {\n // Pathological input — return as-is. Shouldn't happen in\n // practice because QuerySource yields objects, but defensive\n // because plan execution is untyped at this layer.\n return left\n }\n const merged: Record<string, unknown> = { ...(left as Record<string, unknown>) }\n\n // \"No ref at all\" — null/undefined FK value, or a non-string/non-\n // number FK that coerceRefKey treated as no-ref. Never throws\n // regardless of mode; matches the write-time policy that nullish\n // refs are allowed.\n const refKey = coerceRefKey(rawId)\n if (right === undefined) {\n if (refKey !== null && leg.mode === 'strict') {\n throw new DanglingReferenceError({\n field: leg.field,\n target: leg.target,\n refId: refKey,\n message:\n `.join() strict dangling: record references \"${leg.target}:${refKey}\" ` +\n `via field \"${leg.field}\", but no such record exists. Use ref() mode 'warn' ` +\n `or 'cascade' if dangling refs are acceptable, or run ` +\n `vault.checkIntegrity() to find and fix the orphans.`,\n })\n }\n if (refKey !== null && leg.mode === 'warn') {\n warnOnceDangling(leg.field, leg.target, refKey)\n }\n // For 'cascade' and null refs we attach null silently. Cascade\n // is a delete-time mode; any dangling refs visible at read time\n // are either mid-flight or pre-existing orphans, not a DSL error.\n merged[leg.as] = null\n } else {\n merged[leg.as] = right\n }\n return merged\n}\n\n/**\n * Test-only: reset the join warning deduplication state between\n * tests. Production code never calls this — the dedup state is\n * intentionally process-scoped so a noisy query doesn't spam the\n * console once per component render.\n */\nexport function resetJoinWarnings(): void {\n warnedDanglingKeys.clear()\n warnedCeilingKeys.clear()\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ReadOnlyFrameError
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-JBIV4FLR.js";
|
|
4
4
|
|
|
5
5
|
// src/with-fork/shadow/vault-frame.ts
|
|
6
6
|
var VaultFrame = class {
|
|
@@ -71,4 +71,4 @@ export {
|
|
|
71
71
|
VaultFrame,
|
|
72
72
|
CollectionFrame
|
|
73
73
|
};
|
|
74
|
-
//# sourceMappingURL=chunk-
|
|
74
|
+
//# sourceMappingURL=chunk-XVU7J43V.js.map
|