@noy-db/hub 0.3.0-pre.7 → 0.3.0-pre.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/index.d.ts +2 -3
- package/dist/adapter/index.js +1 -1
- package/dist/aggregate/index.d.ts +3 -4
- package/dist/aggregate/index.js +9 -8
- package/dist/aggregate/index.js.map +1 -1
- package/dist/api-HV4PDUWF.js +20 -0
- package/dist/as/index.d.ts +4 -5
- package/dist/as/index.js +11 -11
- package/dist/at/index.d.ts +2 -3
- package/dist/attestation/index.d.ts +3 -4
- package/dist/attestation/index.js +18 -18
- package/dist/{backup-7PX6NDKT.js → backup-ODXO7OGA.js} +12 -12
- package/dist/blobs/index.d.ts +4 -5
- package/dist/blobs/index.js +11 -11
- package/dist/blobs/index.js.map +1 -1
- package/dist/bundle/index.d.ts +5 -6
- package/dist/bundle/index.js +20 -20
- package/dist/{bundle-BN_VSq5L.d.ts → bundle-DlSxo31R.d.ts} +1 -1
- package/dist/by/index.js +2 -2
- package/dist/cargo/index.d.ts +4 -5
- package/dist/cargo/index.js +23 -22
- package/dist/{chunk-QYNBPDGO.js → chunk-2644KEGW.js} +18 -10
- package/dist/chunk-2644KEGW.js.map +1 -0
- package/dist/{chunk-XW5LZYV4.js → chunk-2HVUKRNE.js} +2 -2
- package/dist/{chunk-VFQOE76I.js → chunk-2KOSJTMU.js} +2 -2
- package/dist/{chunk-7AERMJP6.js → chunk-2UQ4RMHV.js} +4 -4
- package/dist/{chunk-5GQE5IDB.js → chunk-2XE6LIXT.js} +13 -11
- package/dist/chunk-2XE6LIXT.js.map +1 -0
- package/dist/{chunk-VIWOGJ2O.js → chunk-34RRRIC3.js} +2 -2
- package/dist/{chunk-BASCPHNM.js → chunk-3C5CKXFI.js} +289 -48
- package/dist/chunk-3C5CKXFI.js.map +1 -0
- package/dist/{chunk-LFGJG4XZ.js → chunk-3I4KYWNP.js} +2 -2
- package/dist/{chunk-RUWEHFRB.js → chunk-42KVY3OC.js} +4 -4
- package/dist/{chunk-5K7KZMXG.js → chunk-4C4WBS4V.js} +2048 -948
- package/dist/chunk-4C4WBS4V.js.map +1 -0
- package/dist/{chunk-J54DC5IC.js → chunk-4G3IT7J7.js} +7 -7
- package/dist/{chunk-WJEMUO32.js → chunk-4RQ62VSR.js} +2 -2
- package/dist/{chunk-QAVFI23O.js → chunk-4ZSGSRBI.js} +8 -8
- package/dist/{chunk-C4NOLA5X.js → chunk-5GAX2QOH.js} +2 -2
- package/dist/{chunk-IEKK2XI4.js → chunk-5SAZMBW7.js} +7 -7
- package/dist/{chunk-II6DLDZM.js → chunk-5TZZUL5X.js} +2 -2
- package/dist/{chunk-GMZFHDSG.js → chunk-6CU3FN74.js} +8 -8
- package/dist/chunk-6G2SCQYS.js +29 -0
- package/dist/chunk-6G2SCQYS.js.map +1 -0
- package/dist/{chunk-UWZ7O4NR.js → chunk-6MWUANRF.js} +2 -2
- package/dist/{chunk-U7LV2DUN.js → chunk-6ULCYCMU.js} +2 -2
- package/dist/{chunk-6Z435B6N.js → chunk-7HAPHVRJ.js} +131 -92
- package/dist/chunk-7HAPHVRJ.js.map +1 -0
- package/dist/{chunk-VFG5YXWM.js → chunk-7IZQTCTR.js} +3 -3
- package/dist/chunk-7ZANJ6JZ.js +265 -0
- package/dist/chunk-7ZANJ6JZ.js.map +1 -0
- package/dist/{chunk-VMRXI3IF.js → chunk-A2APWS32.js} +6 -6
- package/dist/chunk-A2APWS32.js.map +1 -0
- package/dist/{chunk-U5IJHUHZ.js → chunk-A4MAD6PO.js} +5 -5
- package/dist/chunk-ANDBBA2P.js +36 -0
- package/dist/chunk-ANDBBA2P.js.map +1 -0
- package/dist/{chunk-5HZULLHE.js → chunk-AUR7MRHA.js} +2 -2
- package/dist/{chunk-YSJG4L4A.js → chunk-AYGZMQP5.js} +3 -3
- package/dist/chunk-AYGZMQP5.js.map +1 -0
- package/dist/{chunk-H7WQMGXH.js → chunk-CJEMA2Z6.js} +2 -2
- package/dist/{chunk-WRLPV6SI.js → chunk-CTSFKDGA.js} +2 -2
- package/dist/{chunk-OJ64XMPJ.js → chunk-CU62BBRB.js} +32 -1
- package/dist/chunk-CU62BBRB.js.map +1 -0
- package/dist/{chunk-OFYY5Q4R.js → chunk-CWCCYH34.js} +2 -2
- package/dist/{chunk-WGH3YV6Y.js → chunk-DEYAO3N6.js} +10 -10
- package/dist/{chunk-7L6EH6S7.js → chunk-DFIBOSOQ.js} +8 -8
- package/dist/{chunk-3CHNKEFI.js → chunk-E5AHO65E.js} +7 -7
- package/dist/{chunk-AG2PLGQ2.js → chunk-FC3XK5JT.js} +8 -8
- package/dist/{chunk-CP3GUIAN.js → chunk-FGOGS7CE.js} +10 -12
- package/dist/chunk-FGOGS7CE.js.map +1 -0
- package/dist/{chunk-DJ4TNF4N.js → chunk-GX2BYNCT.js} +7 -7
- package/dist/{chunk-YTCCC6NG.js → chunk-GZ2MOPVX.js} +34 -63
- package/dist/chunk-GZ2MOPVX.js.map +1 -0
- package/dist/chunk-H57P2SJB.js +126 -0
- package/dist/chunk-H57P2SJB.js.map +1 -0
- package/dist/{chunk-55LSDABW.js → chunk-HBTT7YQX.js} +2 -2
- package/dist/{chunk-PYIUDUGS.js → chunk-HOCEGVGQ.js} +2 -2
- package/dist/{chunk-5JQMX2EH.js → chunk-ICV32FPK.js} +2 -2
- package/dist/{chunk-WFBZRXS5.js → chunk-IIP5WFVT.js} +138 -92
- package/dist/chunk-IIP5WFVT.js.map +1 -0
- package/dist/{chunk-YSU2E4GZ.js → chunk-IUKGIB2Q.js} +19 -6
- package/dist/chunk-IUKGIB2Q.js.map +1 -0
- package/dist/{chunk-OWGAXPRE.js → chunk-IWTYFS3D.js} +2 -2
- package/dist/{chunk-FCOZ7DEC.js → chunk-JC5R7P44.js} +3 -3
- package/dist/{chunk-K6WHZBCO.js → chunk-KBBWY46Y.js} +2 -2
- package/dist/{chunk-RNYDHIQL.js → chunk-KBF5GSGX.js} +3 -3
- package/dist/{chunk-6P5AJ2ZS.js → chunk-KCG3BHFG.js} +2 -23
- package/dist/chunk-KCG3BHFG.js.map +1 -0
- package/dist/{chunk-T4NQMPBN.js → chunk-KNXN47MO.js} +3 -3
- package/dist/{chunk-VK6OC22R.js → chunk-KWLD2D75.js} +4 -3
- package/dist/chunk-KWLD2D75.js.map +1 -0
- package/dist/{chunk-7F2P2NHX.js → chunk-KXKLKJW4.js} +6 -6
- package/dist/{chunk-PAJCRV7O.js → chunk-KYC4LIEU.js} +2 -2
- package/dist/{chunk-AZPGB4OD.js → chunk-LEOXZY36.js} +8 -8
- package/dist/{chunk-2L7PPF2C.js → chunk-M5I354EE.js} +9 -9
- package/dist/{chunk-26FSVNBL.js → chunk-MK2BSS44.js} +2 -2
- package/dist/{chunk-G5CFMITA.js → chunk-MSVY7ULZ.js} +3 -3
- package/dist/{chunk-PE25C4QL.js → chunk-NNWK3PSY.js} +9 -9
- package/dist/{chunk-JT4A6OAJ.js → chunk-NSZ6J5DF.js} +7 -7
- package/dist/{chunk-RHB7BOEO.js → chunk-NUEFXLZU.js} +7 -7
- package/dist/{chunk-I6Q7YECW.js → chunk-NYVQCZ54.js} +5 -5
- package/dist/chunk-NYVQCZ54.js.map +1 -0
- package/dist/{chunk-DKGZIX6S.js → chunk-OQAODTYR.js} +2 -2
- package/dist/{chunk-HV3VNAYI.js → chunk-OV4CMJGR.js} +5 -5
- package/dist/{chunk-V76PHWLE.js → chunk-PEDZFFKW.js} +2 -2
- package/dist/chunk-PF3FQCCF.js +164 -0
- package/dist/chunk-PF3FQCCF.js.map +1 -0
- package/dist/{chunk-CND5WQLO.js → chunk-Q2KVKTC2.js} +5 -5
- package/dist/{chunk-RUIMKQTA.js → chunk-QJR3W3MF.js} +1 -1
- package/dist/chunk-QJR3W3MF.js.map +1 -0
- package/dist/{chunk-HBVEGUON.js → chunk-QTXQ6RNZ.js} +3 -3
- package/dist/{chunk-J5YDMJHG.js → chunk-RJ5522XX.js} +12 -12
- package/dist/{chunk-H3MPSYCH.js → chunk-RMUXPHIH.js} +56 -7
- package/dist/chunk-RMUXPHIH.js.map +1 -0
- package/dist/{chunk-CHK5VVMI.js → chunk-S6AQXDN4.js} +2 -2
- package/dist/{chunk-E3LURVGQ.js → chunk-SSFC3IYA.js} +9 -9
- package/dist/chunk-SSFC3IYA.js.map +1 -0
- package/dist/{chunk-MOQ5M46U.js → chunk-SZCHEDML.js} +2 -2
- package/dist/{chunk-UB3XHGFG.js → chunk-TDPEJINP.js} +14 -7
- package/dist/chunk-TDPEJINP.js.map +1 -0
- package/dist/{chunk-HCK2DFEK.js → chunk-UNJU345Q.js} +1 -1
- package/dist/chunk-UNJU345Q.js.map +1 -0
- package/dist/chunk-V7RNIND6.js +265 -0
- package/dist/chunk-V7RNIND6.js.map +1 -0
- package/dist/{chunk-VUHXLL32.js → chunk-VJ3NQK4K.js} +5 -5
- package/dist/{chunk-62TTFKU4.js → chunk-WNA6T3MA.js} +8 -8
- package/dist/{chunk-7CZ6YXMO.js → chunk-WVCQ75OK.js} +2 -2
- package/dist/{chunk-NO53W6O5.js → chunk-X66MXMR5.js} +2 -2
- package/dist/{chunk-JIW3Q5JN.js → chunk-XHD3L3JO.js} +199 -35
- package/dist/chunk-XHD3L3JO.js.map +1 -0
- package/dist/{chunk-6PALJTC2.js → chunk-XJK2WIYN.js} +3 -3
- package/dist/{chunk-5UOPIPZC.js → chunk-XQ7UYPVN.js} +3 -3
- package/dist/{chunk-HU55NPHZ.js → chunk-Y2OCKFZ3.js} +2 -2
- package/dist/{chunk-UJJJPKJO.js → chunk-YBFOBRTO.js} +3 -3
- package/dist/{chunk-I6JJ6VJS.js → chunk-YLN33ADH.js} +2 -2
- package/dist/{chunk-HGQ6UNHE.js → chunk-Z5YFRMG5.js} +2 -2
- package/dist/{chunk-EU7HDMT3.js → chunk-ZWQFOXBR.js} +2 -2
- package/dist/classified/index.d.ts +4 -5
- package/dist/classified/index.js +5 -3
- package/dist/{config-drift-LRKCC3VZ.js → classified-marker-IO5EBW7Q.js} +4 -4
- package/dist/classified-marker-IO5EBW7Q.js.map +1 -0
- package/dist/collection-facade-3RAXG2OU.js +38 -0
- package/dist/{computed-6FOFXQPE.js → computed-R62HGCO4.js} +3 -3
- package/dist/consent/index.d.ts +3 -4
- package/dist/consent/index.js +9 -9
- package/dist/crdt/index.d.ts +3 -4
- package/dist/{dead-filter-NZORV45S.js → dead-filter-VNA4M3GP.js} +2 -2
- package/dist/delegation-ZKII5UBI.js +25 -0
- package/dist/derivations/index.d.ts +4 -5
- package/dist/derivations/index.js +12 -11
- package/dist/derive-5D2C3WG4.js +21 -0
- package/dist/describe/index.d.ts +4 -1
- package/dist/{dev-unlock-CsOqX7O2.d.ts → dev-unlock-DDTafVO6.d.ts} +1 -1
- package/dist/{enclave-7X5BUAMS.js → enclave-EUBEJGPG.js} +11 -9
- package/dist/executor-IL7SRNUZ.js +23 -0
- package/dist/executor-OUA2Y6YC.js +9 -0
- package/dist/executor-WAEI6PDV.js +9 -0
- package/dist/export-accessible-O55RKAGC.js +23 -0
- package/dist/extract-partition-VBGCBA6Q.js +36 -0
- package/dist/{fanout-sidecar-BDKLP5GG.js → fanout-sidecar-CI5BCMQM.js} +11 -11
- package/dist/find-FISGUWSK.js +11 -0
- package/dist/forget/index.d.ts +2 -2
- package/dist/forget/index.js +9 -9
- package/dist/guards/index.d.ts +4 -5
- package/dist/guards/index.js +4 -4
- package/dist/{hash-BPzm02Za.d.ts → hash-DBkZfIBW.d.ts} +1 -1
- package/dist/history/index.d.ts +4 -5
- package/dist/history/index.js +11 -11
- package/dist/i18n/index.d.ts +24 -5
- package/dist/i18n/index.js +45 -34
- package/dist/i18n/index.js.map +1 -1
- package/dist/in/index.d.ts +2 -3
- package/dist/{index-CLucFLeN.d.ts → index-CyclmyDr.d.ts} +2 -3
- package/dist/{index-BJ3L-XcV.d.ts → index-DzhNI2vE.d.ts} +6378 -5022
- package/dist/index.d.ts +64 -23
- package/dist/index.js +411 -132
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.d.ts +3 -4
- package/dist/indexing/index.js +4 -4
- package/dist/issue-EQAWIYFQ.js +19 -0
- package/dist/kernel/index.d.ts +3 -4
- package/dist/kernel/index.js +13 -12
- package/dist/lazy/index.d.ts +3 -4
- package/dist/{ledger-EMDHRJJN.js → ledger-AJA2SR3C.js} +11 -11
- package/dist/liberate-SEGR4JLY.js +24 -0
- package/dist/{link-set-62Y36TOG.js → link-set-YBAC2DQT.js} +11 -11
- package/dist/materialized-views/index.d.ts +14 -5
- package/dist/materialized-views/index.js +17 -15
- package/dist/{mime-magic-BlE9fzn2.d.ts → mime-magic-CbV1quyp.d.ts} +1 -1
- package/dist/noydb-AMEOU456.js +65 -0
- package/dist/on/index.d.ts +2 -3
- package/dist/on/index.js +9 -9
- package/dist/overlay-views/index.d.ts +4 -5
- package/dist/overlay-views/index.js +4 -4
- package/dist/periods/index.d.ts +3 -4
- package/dist/periods/index.js +20 -12
- package/dist/pod/index.d.ts +3 -4
- package/dist/pod/index.js +11 -11
- package/dist/{policy-SXPLTQS4.js → policy-SDGRWVIL.js} +5 -5
- package/dist/portability/index.d.ts +3 -4
- package/dist/portability/index.js +8 -8
- package/dist/{post-register-DCJ2SRVG.js → post-register-MROIUA5O.js} +6 -6
- package/dist/{public-envelope-CBRIJORQ.js → public-envelope-J5PLQOW2.js} +4 -4
- package/dist/query/index.d.ts +2 -3
- package/dist/query/index.js +9 -6
- package/dist/{read-only-facade-5ADRJVTM.js → read-only-facade-UCYNRE56.js} +2 -2
- package/dist/register-OVJKW7BS.js +23 -0
- package/dist/registry-5RJSK5AJ.js +9 -0
- package/dist/registry-DQRNTWRL.js +18 -0
- package/dist/registry-LTGLERS7.js +19 -0
- package/dist/request-withdrawal-27O77D2V.js +31 -0
- package/dist/{reveal-R5N7JKA5.js → reveal-LT3I2K4P.js} +6 -6
- package/dist/revoke-4N45G7D2.js +24 -0
- package/dist/satellites/index.d.ts +2 -3
- package/dist/satellites/index.js +1 -1
- package/dist/sealed-record/index.d.ts +3 -4
- package/dist/sealed-record/index.js +12 -12
- package/dist/session/index.d.ts +4 -5
- package/dist/session/index.js +9 -9
- package/dist/shadow/index.d.ts +3 -4
- package/dist/shadow/index.js +2 -2
- package/dist/signer-XJCOV7JD.js +25 -0
- package/dist/snapshots/index.d.ts +3 -4
- package/dist/snapshots/index.js +10 -10
- package/dist/{stale-DP66HFT3.js → stale-IYYXEH3U.js} +2 -2
- package/dist/storage-Y44CAPE6.js +23 -0
- package/dist/{store-coordination-provider-F5N5ASDF.js → store-coordination-provider-WKGAE2WI.js} +3 -3
- package/dist/{subject-index-O75wKshW.d.ts → subject-index-CyOgGMJL.d.ts} +18 -0
- package/dist/sync/index.d.ts +2 -3
- package/dist/sync/index.js +9 -9
- package/dist/team/index.d.ts +3 -4
- package/dist/team/index.js +15 -15
- package/dist/tiers/index.d.ts +2 -3
- package/dist/tiers/index.js +10 -10
- package/dist/to/index.d.ts +2 -3
- package/dist/to/index.js +1 -1
- package/dist/{transition-guard-BzsAQEd-.d.ts → transition-guard-BOUkzQU4.d.ts} +1 -1
- package/dist/tx/index.d.ts +3 -4
- package/dist/tx/index.js +3 -3
- package/dist/ui/index.d.ts +4 -1
- package/dist/util/index.js +1 -1
- package/dist/{validators-DGmYFP51.d.ts → validators-B3SalxlJ.d.ts} +1 -1
- package/dist/{vault-diff-DmBBatkx.d.ts → vault-diff-Dt9NkLzw.d.ts} +1 -1
- package/dist/{verify-CAYGXYNO.js → verify-GH6CWVSW.js} +5 -5
- package/dist/{walk-3SB25HF2.js → walk-76UCFXYV.js} +11 -11
- package/dist/with/index.d.ts +3 -4
- package/dist/with/index.js +11 -11
- package/dist/{with-materialized-view-DPEVoGoB.d.ts → with-materialized-view-BP33v1Z6.d.ts} +1 -1
- package/dist/{with-overlayed-view-DGpKFSv1.d.ts → with-overlayed-view-DntFpOKN.d.ts} +1 -1
- package/dist/{with-rollup-B2RwnTq0.d.ts → with-rollup-ClCHO6-4.d.ts} +1 -1
- package/dist/withdraw-accessible-6J2UH3IN.js +28 -0
- package/package.json +3 -3
- package/dist/api-Z66JMGOU.js +0 -20
- package/dist/chunk-47C4CI7R.js +0 -108
- package/dist/chunk-47C4CI7R.js.map +0 -1
- package/dist/chunk-5GQE5IDB.js.map +0 -1
- package/dist/chunk-5K7KZMXG.js.map +0 -1
- package/dist/chunk-6P5AJ2ZS.js.map +0 -1
- package/dist/chunk-6Z435B6N.js.map +0 -1
- package/dist/chunk-7KTL4RHZ.js +0 -120
- package/dist/chunk-7KTL4RHZ.js.map +0 -1
- package/dist/chunk-BASCPHNM.js.map +0 -1
- package/dist/chunk-CP3GUIAN.js.map +0 -1
- package/dist/chunk-E3LURVGQ.js.map +0 -1
- package/dist/chunk-H3MPSYCH.js.map +0 -1
- package/dist/chunk-HCK2DFEK.js.map +0 -1
- package/dist/chunk-I6Q7YECW.js.map +0 -1
- package/dist/chunk-JIW3Q5JN.js.map +0 -1
- package/dist/chunk-OJ64XMPJ.js.map +0 -1
- package/dist/chunk-QYNBPDGO.js.map +0 -1
- package/dist/chunk-RUIMKQTA.js.map +0 -1
- package/dist/chunk-UB3XHGFG.js.map +0 -1
- package/dist/chunk-VK6OC22R.js.map +0 -1
- package/dist/chunk-VMRXI3IF.js.map +0 -1
- package/dist/chunk-WFBZRXS5.js.map +0 -1
- package/dist/chunk-YSJG4L4A.js.map +0 -1
- package/dist/chunk-YSU2E4GZ.js.map +0 -1
- package/dist/chunk-YTCCC6NG.js.map +0 -1
- package/dist/collection-facade-JP6N7USE.js +0 -39
- package/dist/config-drift-LRKCC3VZ.js.map +0 -1
- package/dist/delegation-FDOXXHNG.js +0 -25
- package/dist/derive-JR2C5JFG.js +0 -21
- package/dist/describe-C6iHNlqJ.d.ts +0 -162
- package/dist/executor-6LHOV7MN.js +0 -21
- package/dist/executor-EUPYHCLU.js +0 -9
- package/dist/executor-KZ4B3PRT.js +0 -9
- package/dist/export-accessible-7XLSZBGJ.js +0 -23
- package/dist/extract-partition-RM6EROS5.js +0 -36
- package/dist/find-OH4BEULF.js +0 -11
- package/dist/issue-FJE2IHUI.js +0 -19
- package/dist/liberate-HFIPHJGR.js +0 -24
- package/dist/noydb-4ZASGII5.js +0 -62
- package/dist/register-5NRE7HBZ.js +0 -23
- package/dist/registry-5QLETK3Q.js +0 -17
- package/dist/registry-DYTNLOJC.js +0 -19
- package/dist/registry-HSNQ4NBP.js +0 -9
- package/dist/request-withdrawal-5APB7RKN.js +0 -31
- package/dist/revoke-VELFCK7S.js +0 -24
- package/dist/signer-IVU3B6DP.js +0 -25
- package/dist/storage-RH5VBLLL.js +0 -23
- package/dist/withdraw-accessible-LN4AOJDD.js +0 -28
- /package/dist/{api-Z66JMGOU.js.map → api-HV4PDUWF.js.map} +0 -0
- /package/dist/{backup-7PX6NDKT.js.map → backup-ODXO7OGA.js.map} +0 -0
- /package/dist/{chunk-XW5LZYV4.js.map → chunk-2HVUKRNE.js.map} +0 -0
- /package/dist/{chunk-VFQOE76I.js.map → chunk-2KOSJTMU.js.map} +0 -0
- /package/dist/{chunk-7AERMJP6.js.map → chunk-2UQ4RMHV.js.map} +0 -0
- /package/dist/{chunk-VIWOGJ2O.js.map → chunk-34RRRIC3.js.map} +0 -0
- /package/dist/{chunk-LFGJG4XZ.js.map → chunk-3I4KYWNP.js.map} +0 -0
- /package/dist/{chunk-RUWEHFRB.js.map → chunk-42KVY3OC.js.map} +0 -0
- /package/dist/{chunk-J54DC5IC.js.map → chunk-4G3IT7J7.js.map} +0 -0
- /package/dist/{chunk-WJEMUO32.js.map → chunk-4RQ62VSR.js.map} +0 -0
- /package/dist/{chunk-QAVFI23O.js.map → chunk-4ZSGSRBI.js.map} +0 -0
- /package/dist/{chunk-C4NOLA5X.js.map → chunk-5GAX2QOH.js.map} +0 -0
- /package/dist/{chunk-IEKK2XI4.js.map → chunk-5SAZMBW7.js.map} +0 -0
- /package/dist/{chunk-II6DLDZM.js.map → chunk-5TZZUL5X.js.map} +0 -0
- /package/dist/{chunk-GMZFHDSG.js.map → chunk-6CU3FN74.js.map} +0 -0
- /package/dist/{chunk-UWZ7O4NR.js.map → chunk-6MWUANRF.js.map} +0 -0
- /package/dist/{chunk-U7LV2DUN.js.map → chunk-6ULCYCMU.js.map} +0 -0
- /package/dist/{chunk-VFG5YXWM.js.map → chunk-7IZQTCTR.js.map} +0 -0
- /package/dist/{chunk-U5IJHUHZ.js.map → chunk-A4MAD6PO.js.map} +0 -0
- /package/dist/{chunk-5HZULLHE.js.map → chunk-AUR7MRHA.js.map} +0 -0
- /package/dist/{chunk-H7WQMGXH.js.map → chunk-CJEMA2Z6.js.map} +0 -0
- /package/dist/{chunk-WRLPV6SI.js.map → chunk-CTSFKDGA.js.map} +0 -0
- /package/dist/{chunk-OFYY5Q4R.js.map → chunk-CWCCYH34.js.map} +0 -0
- /package/dist/{chunk-WGH3YV6Y.js.map → chunk-DEYAO3N6.js.map} +0 -0
- /package/dist/{chunk-7L6EH6S7.js.map → chunk-DFIBOSOQ.js.map} +0 -0
- /package/dist/{chunk-3CHNKEFI.js.map → chunk-E5AHO65E.js.map} +0 -0
- /package/dist/{chunk-AG2PLGQ2.js.map → chunk-FC3XK5JT.js.map} +0 -0
- /package/dist/{chunk-DJ4TNF4N.js.map → chunk-GX2BYNCT.js.map} +0 -0
- /package/dist/{chunk-55LSDABW.js.map → chunk-HBTT7YQX.js.map} +0 -0
- /package/dist/{chunk-PYIUDUGS.js.map → chunk-HOCEGVGQ.js.map} +0 -0
- /package/dist/{chunk-5JQMX2EH.js.map → chunk-ICV32FPK.js.map} +0 -0
- /package/dist/{chunk-OWGAXPRE.js.map → chunk-IWTYFS3D.js.map} +0 -0
- /package/dist/{chunk-FCOZ7DEC.js.map → chunk-JC5R7P44.js.map} +0 -0
- /package/dist/{chunk-K6WHZBCO.js.map → chunk-KBBWY46Y.js.map} +0 -0
- /package/dist/{chunk-RNYDHIQL.js.map → chunk-KBF5GSGX.js.map} +0 -0
- /package/dist/{chunk-T4NQMPBN.js.map → chunk-KNXN47MO.js.map} +0 -0
- /package/dist/{chunk-7F2P2NHX.js.map → chunk-KXKLKJW4.js.map} +0 -0
- /package/dist/{chunk-PAJCRV7O.js.map → chunk-KYC4LIEU.js.map} +0 -0
- /package/dist/{chunk-AZPGB4OD.js.map → chunk-LEOXZY36.js.map} +0 -0
- /package/dist/{chunk-2L7PPF2C.js.map → chunk-M5I354EE.js.map} +0 -0
- /package/dist/{chunk-26FSVNBL.js.map → chunk-MK2BSS44.js.map} +0 -0
- /package/dist/{chunk-G5CFMITA.js.map → chunk-MSVY7ULZ.js.map} +0 -0
- /package/dist/{chunk-PE25C4QL.js.map → chunk-NNWK3PSY.js.map} +0 -0
- /package/dist/{chunk-JT4A6OAJ.js.map → chunk-NSZ6J5DF.js.map} +0 -0
- /package/dist/{chunk-RHB7BOEO.js.map → chunk-NUEFXLZU.js.map} +0 -0
- /package/dist/{chunk-DKGZIX6S.js.map → chunk-OQAODTYR.js.map} +0 -0
- /package/dist/{chunk-HV3VNAYI.js.map → chunk-OV4CMJGR.js.map} +0 -0
- /package/dist/{chunk-V76PHWLE.js.map → chunk-PEDZFFKW.js.map} +0 -0
- /package/dist/{chunk-CND5WQLO.js.map → chunk-Q2KVKTC2.js.map} +0 -0
- /package/dist/{chunk-HBVEGUON.js.map → chunk-QTXQ6RNZ.js.map} +0 -0
- /package/dist/{chunk-J5YDMJHG.js.map → chunk-RJ5522XX.js.map} +0 -0
- /package/dist/{chunk-CHK5VVMI.js.map → chunk-S6AQXDN4.js.map} +0 -0
- /package/dist/{chunk-MOQ5M46U.js.map → chunk-SZCHEDML.js.map} +0 -0
- /package/dist/{chunk-VUHXLL32.js.map → chunk-VJ3NQK4K.js.map} +0 -0
- /package/dist/{chunk-62TTFKU4.js.map → chunk-WNA6T3MA.js.map} +0 -0
- /package/dist/{chunk-7CZ6YXMO.js.map → chunk-WVCQ75OK.js.map} +0 -0
- /package/dist/{chunk-NO53W6O5.js.map → chunk-X66MXMR5.js.map} +0 -0
- /package/dist/{chunk-6PALJTC2.js.map → chunk-XJK2WIYN.js.map} +0 -0
- /package/dist/{chunk-5UOPIPZC.js.map → chunk-XQ7UYPVN.js.map} +0 -0
- /package/dist/{chunk-HU55NPHZ.js.map → chunk-Y2OCKFZ3.js.map} +0 -0
- /package/dist/{chunk-UJJJPKJO.js.map → chunk-YBFOBRTO.js.map} +0 -0
- /package/dist/{chunk-I6JJ6VJS.js.map → chunk-YLN33ADH.js.map} +0 -0
- /package/dist/{chunk-HGQ6UNHE.js.map → chunk-Z5YFRMG5.js.map} +0 -0
- /package/dist/{chunk-EU7HDMT3.js.map → chunk-ZWQFOXBR.js.map} +0 -0
- /package/dist/{collection-facade-JP6N7USE.js.map → collection-facade-3RAXG2OU.js.map} +0 -0
- /package/dist/{computed-6FOFXQPE.js.map → computed-R62HGCO4.js.map} +0 -0
- /package/dist/{dead-filter-NZORV45S.js.map → dead-filter-VNA4M3GP.js.map} +0 -0
- /package/dist/{delegation-FDOXXHNG.js.map → delegation-ZKII5UBI.js.map} +0 -0
- /package/dist/{derive-JR2C5JFG.js.map → derive-5D2C3WG4.js.map} +0 -0
- /package/dist/{enclave-7X5BUAMS.js.map → enclave-EUBEJGPG.js.map} +0 -0
- /package/dist/{executor-6LHOV7MN.js.map → executor-IL7SRNUZ.js.map} +0 -0
- /package/dist/{executor-EUPYHCLU.js.map → executor-OUA2Y6YC.js.map} +0 -0
- /package/dist/{executor-KZ4B3PRT.js.map → executor-WAEI6PDV.js.map} +0 -0
- /package/dist/{export-accessible-7XLSZBGJ.js.map → export-accessible-O55RKAGC.js.map} +0 -0
- /package/dist/{extract-partition-RM6EROS5.js.map → extract-partition-VBGCBA6Q.js.map} +0 -0
- /package/dist/{fanout-sidecar-BDKLP5GG.js.map → fanout-sidecar-CI5BCMQM.js.map} +0 -0
- /package/dist/{find-OH4BEULF.js.map → find-FISGUWSK.js.map} +0 -0
- /package/dist/{issue-FJE2IHUI.js.map → issue-EQAWIYFQ.js.map} +0 -0
- /package/dist/{ledger-EMDHRJJN.js.map → ledger-AJA2SR3C.js.map} +0 -0
- /package/dist/{liberate-HFIPHJGR.js.map → liberate-SEGR4JLY.js.map} +0 -0
- /package/dist/{link-set-62Y36TOG.js.map → link-set-YBAC2DQT.js.map} +0 -0
- /package/dist/{noydb-4ZASGII5.js.map → noydb-AMEOU456.js.map} +0 -0
- /package/dist/{policy-SXPLTQS4.js.map → policy-SDGRWVIL.js.map} +0 -0
- /package/dist/{post-register-DCJ2SRVG.js.map → post-register-MROIUA5O.js.map} +0 -0
- /package/dist/{public-envelope-CBRIJORQ.js.map → public-envelope-J5PLQOW2.js.map} +0 -0
- /package/dist/{read-only-facade-5ADRJVTM.js.map → read-only-facade-UCYNRE56.js.map} +0 -0
- /package/dist/{register-5NRE7HBZ.js.map → register-OVJKW7BS.js.map} +0 -0
- /package/dist/{registry-5QLETK3Q.js.map → registry-5RJSK5AJ.js.map} +0 -0
- /package/dist/{registry-DYTNLOJC.js.map → registry-DQRNTWRL.js.map} +0 -0
- /package/dist/{registry-HSNQ4NBP.js.map → registry-LTGLERS7.js.map} +0 -0
- /package/dist/{request-withdrawal-5APB7RKN.js.map → request-withdrawal-27O77D2V.js.map} +0 -0
- /package/dist/{reveal-R5N7JKA5.js.map → reveal-LT3I2K4P.js.map} +0 -0
- /package/dist/{revoke-VELFCK7S.js.map → revoke-4N45G7D2.js.map} +0 -0
- /package/dist/{signer-IVU3B6DP.js.map → signer-XJCOV7JD.js.map} +0 -0
- /package/dist/{stale-DP66HFT3.js.map → stale-IYYXEH3U.js.map} +0 -0
- /package/dist/{storage-RH5VBLLL.js.map → storage-Y44CAPE6.js.map} +0 -0
- /package/dist/{store-coordination-provider-F5N5ASDF.js.map → store-coordination-provider-WKGAE2WI.js.map} +0 -0
- /package/dist/{verify-CAYGXYNO.js.map → verify-GH6CWVSW.js.map} +0 -0
- /package/dist/{walk-3SB25HF2.js.map → walk-76UCFXYV.js.map} +0 -0
- /package/dist/{withdraw-accessible-LN4AOJDD.js.map → withdraw-accessible-6J2UH3IN.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/with-shape/i18n/policy.ts","../src/with-shape/i18n/script.ts","../src/with-shape/i18n/core.ts"],"sourcesContent":["/**\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/public-envelope 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 * 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'\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 * `GroupedAggregation.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 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 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, 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/** Runtime predicate for detecting an `I18nTextDescriptor`. */\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// ─── 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/**\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/** 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 * 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 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/with-lookup/embeddings/cosine.ts","../src/with-lookup/embeddings/descriptor.ts","../src/with-lookup/embeddings/vector-set.ts","../src/with-lookup/search/tokenize.ts","../src/with-lookup/search/scan.ts","../src/with-lookup/search/segment.ts","../src/with-lookup/search/inverted-index.ts","../src/with-lookup/search/snippet.ts","../src/with-lookup/search/index-store.ts","../src/with-lookup/search/build-docs.ts","../src/with-lookup/search/active.ts","../src/with-lookup/search/strategy.ts","../src/with-lookup/search/collection-facade.ts"],"sourcesContent":["/** Cosine similarity in [-1,1]; 0 on zero-norm or length mismatch (no NaN). (L2) */\nexport function cosine(a: Float32Array | number[], b: Float32Array | number[]): number {\n if (a.length !== b.length) return 0\n let dot = 0, na = 0, nb = 0\n for (let i = 0; i < a.length; i++) {\n const x = a[i]!, y = b[i]!\n dot += x * y; na += x * x; nb += y * y\n }\n if (na === 0 || nb === 0) return 0\n return dot / (Math.sqrt(na) * Math.sqrt(nb))\n}\n","/** Per-collection embedding config (L2). The encode hook is host/remote — no bundled model. */\nimport { getAtPath } from '../../with-shape/i18n/core.js'\n\nexport interface EmbeddingDescriptor {\n readonly source: string | readonly string[]\n readonly encode: (text: string) => Promise<Float32Array>\n readonly dim: number\n readonly model: string\n}\n\n/** Concatenate the record's source-field text (skips empties; supports nested/[]-wildcard paths). */\nexport function embeddingSourceText(record: Record<string, unknown>, source: string | readonly string[]): string {\n const fields = typeof source === 'string' ? [source] : source\n const parts: string[] = []\n for (const f of fields) {\n for (const leaf of getAtPath(record, f)) {\n if (typeof leaf === 'string' && leaf !== '') parts.push(leaf)\n }\n }\n return parts.join(' ')\n}\n","/** In-memory vector set for L2 semantic retrieval. Loaded once per session from\n * decrypted _vec sidecars (injected loader), brute-force cosine kNN, model-guarded. */\nimport { cosine } from './cosine.js'\nimport { EmbeddingModelMismatchError } from '../../kernel/errors.js'\n\nexport interface StoredVector { readonly id: string; readonly vec: Float32Array; readonly model: string }\nexport interface VectorHit { readonly id: string; readonly score: number }\n\nexport class VectorSet {\n private vectors: StoredVector[] | undefined\n get loaded(): boolean { return this.vectors !== undefined }\n\n async ensureLoaded(load: () => Promise<StoredVector[]>): Promise<void> {\n if (this.vectors === undefined) this.vectors = await load()\n }\n\n markDirty(): void { this.vectors = undefined }\n\n cosineTopK(query: Float32Array, k: number, opts: { minScore?: number; expectModel?: string } = {}): VectorHit[] {\n const all = this.vectors ?? []\n const minScore = opts.minScore ?? -Infinity\n const hits: VectorHit[] = []\n for (const v of all) {\n if (opts.expectModel !== undefined && v.model !== opts.expectModel) {\n throw new EmbeddingModelMismatchError(opts.expectModel, v.model)\n }\n const score = cosine(query, v.vec)\n if (score >= minScore) hits.push({ id: v.id, score })\n }\n hits.sort((a, b) => b.score - a.score)\n return hits.slice(0, k)\n }\n}\n","/**\n * Default text tokenizer for scan-mode search.\n *\n * NFKC-normalize → lowercase → split on Unicode letter/number runs. This is a\n * word-boundary tokenizer: it does **not** segment scripts written without\n * inter-word spaces (Thai, Lao, Khmer, CJK) — those collapse to one token per\n * run. For such data, a dictionary/ICU segmenter is the right `tokenizer`\n * (the engine takes one as a parameter); substring/`contains` matching is the\n * pragmatic fallback until then.\n */\nexport type Tokenizer = (text: string) => string[]\n\nconst WORD = /[\\p{L}\\p{N}]+/gu\n\nexport const tokenize: Tokenizer = (text: string): string[] => {\n if (!text) return []\n return text.normalize('NFKC').toLowerCase().match(WORD) ?? []\n}\n","/**\n * Scan-mode full-text search — ranks **already-decrypted** records by\n * BM25 against a tokenized query. Pure client-side: nothing is written to the\n * store, so this adds **zero** leakage (unlike a store-usable blind index,\n * which is a separate, gated opt-in).\n *\n * O(n) over the collection; intended for small/medium collections (the pilot's\n * scale) where moving the existing userland scan into the DB is the win.\n */\nimport { tokenize, type Tokenizer } from './tokenize.js'\n\nexport interface SearchOptions {\n /** Top-N by score (default: all matches). */\n readonly limit?: number\n /** `'any'` (default) = OR of query terms; `'all'` = every term must match. */\n readonly match?: 'any' | 'all'\n /** Treat the LAST query term as a prefix (typeahead). */\n readonly prefix?: boolean\n}\n\nexport interface SearchResult<T> {\n readonly id: string\n readonly score: number\n readonly record: T\n}\n\nexport interface SearchEntry<T> {\n readonly id: string\n readonly record: T\n}\n\nconst K1 = 1.2\nconst B = 0.75\n\n/** Coerce a field value to searchable text (numbers/booleans stringified). */\nfunction fieldText(record: unknown, field: string): string {\n const v = (record as Record<string, unknown>)[field]\n if (typeof v === 'string') return v\n if (v === null || v === undefined) return ''\n if (typeof v === 'number' || typeof v === 'boolean') return String(v)\n return ''\n}\n\nexport function searchScan<T>(\n entries: ReadonlyArray<SearchEntry<T>>,\n field: string,\n query: string,\n opts: SearchOptions = {},\n tokenizer: Tokenizer = tokenize,\n): SearchResult<T>[] {\n const queryTerms = tokenizer(query)\n if (queryTerms.length === 0) return []\n\n const match = opts.match ?? 'any'\n const usePrefix = opts.prefix ?? false\n const exactTerms = usePrefix ? queryTerms.slice(0, -1) : queryTerms\n const prefixTerm = usePrefix ? queryTerms[queryTerms.length - 1] : undefined\n\n // One tokenize pass per doc; collect corpus stats (df, avg length).\n const docs = entries.map((e) => ({ id: e.id, record: e.record, terms: tokenizer(fieldText(e.record, field)) }))\n const N = docs.length || 1\n const df = new Map<string, number>()\n let totalLen = 0\n for (const d of docs) {\n totalLen += d.terms.length\n for (const t of new Set(d.terms)) df.set(t, (df.get(t) ?? 0) + 1)\n }\n const avgdl = totalLen / N || 1\n\n // Document frequency for the prefix term: docs containing ANY term with it.\n let prefixDf = 0\n if (prefixTerm !== undefined) {\n for (const d of docs) {\n if (d.terms.some((t) => t.startsWith(prefixTerm))) prefixDf++\n }\n }\n\n const requiredCount = exactTerms.length + (prefixTerm !== undefined ? 1 : 0)\n const results: SearchResult<T>[] = []\n\n for (const d of docs) {\n const tf = new Map<string, number>()\n for (const t of d.terms) tf.set(t, (tf.get(t) ?? 0) + 1)\n\n const matched: { tf: number; df: number }[] = []\n for (const qt of exactTerms) {\n const c = tf.get(qt) ?? 0\n if (c > 0) matched.push({ tf: c, df: df.get(qt) ?? 0 })\n }\n if (prefixTerm !== undefined) {\n let ptf = 0\n for (const [t, c] of tf) if (t.startsWith(prefixTerm)) ptf += c\n if (ptf > 0) matched.push({ tf: ptf, df: prefixDf })\n }\n\n if (matched.length === 0) continue\n if (match === 'all' && matched.length < requiredCount) continue\n\n let score = 0\n for (const m of matched) {\n const idf = Math.log(1 + (N - m.df + 0.5) / (m.df + 0.5))\n const denom = m.tf + K1 * (1 - B + B * (d.terms.length / avgdl))\n score += idf * ((m.tf * (K1 + 1)) / (denom || 1))\n }\n results.push({ id: d.id, score, record: d.record })\n }\n\n results.sort((a, b) => b.score - a.score)\n return opts.limit !== undefined ? results.slice(0, opts.limit) : results\n}\n","/**\n * i18n word tokenizer for the L1 lexical index. Uses Intl.Segmenter\n * (standard ECMAScript — hub-portable) to dictionary-segment Thai/Lao/Khmer/CJK,\n * which the word-run `tokenize` cannot. Terms are matched in NFKC-lowercased form;\n * offsets index the ORIGINAL text so snippets slice the user's text.\n */\nimport type { Tokenizer } from './tokenize.js'\n\nconst SEGMENTER = new Intl.Segmenter(undefined, { granularity: 'word' })\n\nexport interface Token {\n readonly term: string\n readonly offset: number\n}\n\n/** Word-like tokens with NFKC-lowercased terms + original-text char offsets. */\nexport const segmentTokens = (text: string): Token[] => {\n const out: Token[] = []\n if (!text) return out\n for (const s of SEGMENTER.segment(text)) {\n if (s.isWordLike) out.push({ term: s.segment.normalize('NFKC').toLowerCase(), offset: s.index })\n }\n return out\n}\n\n/** Term-only tokenizer (the public `Tokenizer` shape) — for queries. */\nexport const segmentTokenizer: Tokenizer = (text: string): string[] =>\n segmentTokens(text).map((t) => t.term)\n","/**\n * In-memory inverted index for the L1 lexical retrieval layer. Built\n * client-side from already-decrypted records; nothing touches the store. BM25\n * mirrors src/search/scan.ts; multi-field with max-field combination so a record\n * ranks by its strongest field, which also supplies the snippet location.\n */\nimport { segmentTokens, segmentTokenizer } from './segment.js'\n\nconst K1 = 1.2\nconst B = 0.75\n\nexport interface IndexDoc {\n readonly id: string\n readonly fields: ReadonlyArray<{ readonly field: string; readonly locale?: string; readonly text: string }>\n}\n\nexport interface IndexHit {\n readonly id: string\n readonly score: number\n readonly field: string\n readonly locale?: string\n readonly text: string\n readonly offset: number\n}\n\nexport interface QueryOptions {\n readonly limit?: number\n readonly match?: 'any' | 'all'\n readonly prefix?: boolean\n readonly fields?: readonly string[]\n}\n\nexport interface IndexSnapshot {\n readonly v: 1\n readonly fieldStats: ReadonlyArray<[string, { df: [string, number][]; n: number; totalLen: number }]>\n readonly docs: ReadonlyArray<{\n id: string; field: string; locale?: string; text: string; len: number\n tf: [string, number][]; firstOffset: [string, number][]\n }>\n}\n\ninterface Doc {\n id: string\n field: string\n locale?: string\n text: string\n len: number\n tf: Map<string, number>\n firstOffset: Map<string, number>\n}\n\nexport class InvertedIndex {\n // per field: df (term -> #docs), N (#docs), totalLen\n private readonly fieldStats = new Map<string, { df: Map<string, number>; n: number; totalLen: number }>()\n private readonly docs: Doc[] = []\n\n static build(docs: ReadonlyArray<IndexDoc>): InvertedIndex {\n const idx = new InvertedIndex()\n for (const d of docs) {\n for (const f of d.fields) {\n const tokens = segmentTokens(f.text)\n const tf = new Map<string, number>()\n const firstOffset = new Map<string, number>()\n for (const t of tokens) {\n tf.set(t.term, (tf.get(t.term) ?? 0) + 1)\n if (!firstOffset.has(t.term)) firstOffset.set(t.term, t.offset)\n }\n const doc: Doc = { id: d.id, field: f.field, ...(f.locale !== undefined ? { locale: f.locale } : {}), text: f.text, len: tokens.length, tf, firstOffset }\n idx.docs.push(doc)\n let s = idx.fieldStats.get(f.field)\n if (!s) { s = { df: new Map(), n: 0, totalLen: 0 }; idx.fieldStats.set(f.field, s) }\n s.n += 1\n s.totalLen += doc.len\n for (const term of tf.keys()) s.df.set(term, (s.df.get(term) ?? 0) + 1)\n }\n }\n return idx\n }\n\n query(query: string, opts: QueryOptions = {}): IndexHit[] {\n const terms = segmentTokenizer(query)\n if (terms.length === 0) return []\n const usePrefix = opts.prefix ?? false\n const exact = usePrefix ? terms.slice(0, -1) : terms\n const prefix = usePrefix ? terms[terms.length - 1] : undefined\n const match = opts.match ?? 'any'\n const required = exact.length + (prefix !== undefined ? 1 : 0)\n\n // best (max-score) doc per record\n const best = new Map<string, IndexHit>()\n for (const doc of this.docs) {\n if (opts.fields !== undefined && !opts.fields.includes(doc.field)) continue\n const stats = this.fieldStats.get(doc.field)!\n const avgdl = stats.totalLen / stats.n || 1\n let score = 0\n let matchedCount = 0\n let snippetOffset = -1\n\n const scoreTerm = (tf: number, df: number, offset: number): void => {\n if (tf <= 0) return\n matchedCount += 1\n if (snippetOffset < 0 && offset >= 0) snippetOffset = offset\n const idf = Math.log(1 + (stats.n - df + 0.5) / (df + 0.5))\n const denom = tf + K1 * (1 - B + B * (doc.len / avgdl))\n score += idf * ((tf * (K1 + 1)) / (denom || 1))\n }\n\n for (const qt of exact) scoreTerm(doc.tf.get(qt) ?? 0, stats.df.get(qt) ?? 0, doc.firstOffset.get(qt) ?? -1)\n\n if (prefix !== undefined) {\n // Build a set of exact query terms so we don't double-count a term that\n // is both an exact match and starts with the prefix (defect 2 fix).\n const exactSet = new Set(exact)\n let ptf = 0\n let poff = -1\n for (const [term, c] of doc.tf) {\n if (term.startsWith(prefix) && !exactSet.has(term)) {\n ptf += c\n if (poff < 0) poff = doc.firstOffset.get(term) ?? -1\n }\n }\n if (ptf > 0) {\n // df for the prefix: count DISTINCT docs in this field that have any\n // prefix-matching term — mirrors scan.ts semantics (defect 1 fix).\n let pdf = 0\n for (const d of this.docs) {\n if (d.field === doc.field && [...d.tf.keys()].some((t) => t.startsWith(prefix))) pdf++\n }\n scoreTerm(ptf, pdf || 1, poff)\n }\n }\n\n if (matchedCount === 0) continue\n if (match === 'all' && matchedCount < required) continue\n\n const hit: IndexHit = {\n id: doc.id, score, field: doc.field, text: doc.text,\n offset: snippetOffset < 0 ? 0 : snippetOffset,\n ...(doc.locale !== undefined ? { locale: doc.locale } : {}),\n }\n const prev = best.get(doc.id)\n if (!prev || hit.score > prev.score) best.set(doc.id, hit)\n }\n\n const results = [...best.values()].sort((a, b) => b.score - a.score)\n return opts.limit !== undefined ? results.slice(0, opts.limit) : results\n }\n\n toSnapshot(): IndexSnapshot {\n return {\n v: 1,\n fieldStats: [...this.fieldStats].map(([f, s]) => [f, { df: [...s.df], n: s.n, totalLen: s.totalLen }]),\n docs: this.docs.map((d) => ({\n id: d.id, field: d.field, text: d.text, len: d.len,\n tf: [...d.tf], firstOffset: [...d.firstOffset],\n ...(d.locale !== undefined ? { locale: d.locale } : {}),\n })),\n }\n }\n\n static fromSnapshot(s: IndexSnapshot): InvertedIndex {\n const idx = new InvertedIndex()\n for (const [f, st] of s.fieldStats) idx.fieldStats.set(f, { df: new Map(st.df), n: st.n, totalLen: st.totalLen })\n for (const d of s.docs) {\n idx.docs.push({\n id: d.id, field: d.field, text: d.text, len: d.len,\n tf: new Map(d.tf), firstOffset: new Map(d.firstOffset),\n ...(d.locale !== undefined ? { locale: d.locale } : {}),\n })\n }\n return idx\n }\n}\n","/**\n * Minimal-disclosure snippet: a char-window around a match offset (L1).\n * Unicode-safe (operates on code points). `…` marks each truncated end.\n */\nexport function extractSnippet(text: string, offset: number, window = 80): string {\n const chars = Array.from(text)\n if (chars.length <= window) return text\n // offset is a UTF-16 index into `text`; map to a code-point index.\n const cpOffset = Array.from(text.slice(0, Math.max(0, offset))).length\n const half = Math.floor(window / 2)\n let start = Math.max(0, cpOffset - half)\n const end = Math.min(chars.length, start + window)\n start = Math.max(0, end - window)\n const body = chars.slice(start, end).join('')\n return `${start > 0 ? '…' : ''}${body}${end < chars.length ? '…' : ''}`\n}\n","/**\n * The index persistence seam (L1.5). MemoryIndexStore is session-scoped and\n * lazy; an opaque-blob backend implements the same interface so the\n * collection call-site is unchanged.\n */\nimport { InvertedIndex, type IndexDoc } from './inverted-index.js'\n\nexport interface IndexStore {\n ensureBuilt(build: () => ReadonlyArray<IndexDoc>): Promise<InvertedIndex>\n markDirty(): void\n flush(): Promise<void>\n readonly built: boolean\n}\n\nexport class MemoryIndexStore implements IndexStore {\n private index: InvertedIndex | undefined\n\n get built(): boolean { return this.index !== undefined }\n\n async ensureBuilt(build: () => ReadonlyArray<IndexDoc>): Promise<InvertedIndex> {\n if (this.index === undefined) this.index = InvertedIndex.build(build())\n return this.index\n }\n\n markDirty(): void { this.index = undefined }\n\n async flush(): Promise<void> { /* in-memory: nothing to persist */ }\n}\n","/**\n * Map a decrypted record's configured text fields → IndexDoc field entries\n * (L1). String, i18nText, dictKey, and blob-filename expansion. Uses\n * getAtPath for nested/[]-wildcard paths. Blob filenames are NOT inline on the\n * record — the collection pre-resolves them (one listSlots() per record, the\n * heaviest source) and feeds them to {@link buildBlobFieldEntries}.\n */\nimport { getAtPath } from '../../with-shape/i18n/core.js'\nimport type { I18nTextDescriptor } from '../../with-shape/i18n/core.js'\nimport type { DictKeyDescriptor, StaticDictDescriptor } from '../../with-shape/i18n/dictionary.js'\nimport type { IndexDoc } from './inverted-index.js'\n\ntype FieldEntry = IndexDoc['fields'][number]\n\nexport function buildStringFieldEntries(\n record: Record<string, unknown>,\n textIndexes: readonly string[],\n only?: readonly string[],\n): FieldEntry[] {\n const fields = only ? textIndexes.filter((f) => only.includes(f)) : textIndexes\n const out: FieldEntry[] = []\n for (const field of fields) {\n for (const leaf of getAtPath(record, field)) {\n if (typeof leaf === 'string' && leaf !== '') out.push({ field, text: leaf })\n }\n }\n return out\n}\n\nexport function buildI18nFieldEntries(\n record: Record<string, unknown>,\n i18nFields: Record<string, I18nTextDescriptor>,\n textIndexes: readonly string[],\n only?: readonly string[],\n): FieldEntry[] {\n const fields = (only ? textIndexes.filter((f) => only.includes(f)) : textIndexes).filter((f) => f in i18nFields)\n const out: FieldEntry[] = []\n for (const field of fields) {\n for (const leaf of getAtPath(record, field)) {\n if (!leaf || typeof leaf !== 'object' || Array.isArray(leaf)) continue\n for (const [locale, val] of Object.entries(leaf as Record<string, unknown>)) {\n if (typeof val === 'string' && val !== '') out.push({ field, locale, text: val })\n }\n }\n }\n return out\n}\n\n/** label map: field -> (key -> { locale -> label }). Precomputed in the collection (async for dynamic dicts). */\nexport function buildDictKeyFieldEntries(\n record: Record<string, unknown>,\n dictKeyFields: Record<string, DictKeyDescriptor | StaticDictDescriptor>,\n labelMaps: Map<string, Map<string, Record<string, string>>>,\n textIndexes: readonly string[],\n only?: readonly string[],\n): FieldEntry[] {\n const fields = (only ? textIndexes.filter((f) => only.includes(f)) : textIndexes).filter((f) => f in dictKeyFields)\n const out: FieldEntry[] = []\n for (const field of fields) {\n const map = labelMaps.get(field)\n if (!map) continue\n for (const leaf of getAtPath(record, field)) {\n const keys = typeof leaf === 'string' ? [leaf] : Array.isArray(leaf) ? leaf.filter((k): k is string => typeof k === 'string') : []\n for (const key of keys) {\n const labels = map.get(key)\n if (!labels) continue\n for (const [locale, label] of Object.entries(labels)) {\n if (label !== '') out.push({ field, locale, text: label })\n }\n }\n }\n }\n return out\n}\n\n/**\n * Blob filenames precomputed in the collection (`field -> filenames[]`, from one\n * async `listSlots()` per record — the heaviest source, see {@link buildBlobFieldEntries}'s\n * caller). Bytes are never tokenized; only the slot `filename` is indexed.\n */\nexport function buildBlobFieldEntries(filenamesByField: Map<string, string[]>): FieldEntry[] {\n const out: FieldEntry[] = []\n for (const [field, names] of filenamesByField) {\n for (const name of names) if (name !== '') out.push({ field, text: name })\n }\n return out\n}\n","/**\n * Enable the search / retrieval capability.\n * Pass to `createNoydb({ searchStrategy: withSearch() })` to make a collection's\n * `search` / `retrieve` / `similarTo` / `warmIndex` / `flushIndex` methods live,\n * and to enable the put()-time embedding-vector compute for collections that\n * declare an `embeddings` config. The search/retrieval engine is dynamically\n * imported here, so it stays out of the floor bundle until opted in.\n */\nimport type { SearchStrategy } from './strategy.js'\n\nexport function withSearch(): SearchStrategy {\n return {\n async search(ctx, field, query, opts) {\n const { search } = await import('./collection-facade.js')\n return search(ctx, field, query, opts)\n },\n async retrieve(ctx, query, opts) {\n const { retrieve } = await import('./collection-facade.js')\n return retrieve(ctx, query, opts)\n },\n async similarTo(ctx, vector, opts) {\n const { similarTo } = await import('./collection-facade.js')\n return similarTo(ctx, vector, opts)\n },\n async warmIndex(ctx) {\n const { warmIndex } = await import('./collection-facade.js')\n return warmIndex(ctx)\n },\n async flushIndex(ctx) {\n const { flushIndex } = await import('./collection-facade.js')\n return flushIndex(ctx)\n },\n async embedOnWrite(ctx, id, record, version) {\n const { embedOnWrite } = await import('./collection-facade.js')\n return embedOnWrite(ctx, id, record, version)\n },\n }\n}\n","/**\n * Search capability strategy — the on-demand collection-level retrieval\n * operations the `Collection` routes through: scan-mode full-text `search`,\n * unified `retrieve` (lexical | semantic | hybrid), raw-vector `similarTo`, and\n * the lexical-index lifecycle (`warmIndex` / `flushIndex`). The active engine\n * ({@link withSearch}) dynamically imports the search/retrieval facade (keeping\n * it out of the floor bundle); {@link NO_SEARCH} throws.\n *\n * The embedding write-hook (`embedOnWrite`) is paired here too: opting into\n * `withSearch()` enables both the query surface AND the put()-time vector\n * compute — a vector that no gated `similarTo` / semantic `retrieve` could read\n * is dead weight, so embedding compute is not separately gatable. `Collection`\n * always assembles the per-call {@link SearchContext} and delegates here, so an\n * un-opted-in caller hits `NO_SEARCH`'s throw.\n * @internal\n */\nimport type { SearchContext } from './collection-facade.js'\nimport type { SearchOptions, SearchResult } from './index.js'\nimport type { RetrieveOptions, RetrieveHit } from './retrieve-types.js'\nimport { SearchNotEnabledError } from '../../kernel/errors.js'\n\nexport interface SearchStrategy {\n search<T>(ctx: SearchContext<T>, field: string, query: string, opts?: SearchOptions): Promise<SearchResult<T>[]>\n retrieve<T>(ctx: SearchContext<T>, query: string, opts?: RetrieveOptions<T>): Promise<RetrieveHit<T>[]>\n similarTo<T>(ctx: SearchContext<T>, vector: Float32Array, opts?: { k?: number; minScore?: number; includeRecord?: boolean }): Promise<RetrieveHit<T>[]>\n warmIndex<T>(ctx: SearchContext<T>): Promise<void>\n flushIndex<T>(ctx: SearchContext<T>): Promise<void>\n embedOnWrite<T>(ctx: SearchContext<T>, id: string, record: T, version: number): Promise<void>\n}\n\n/**\n * No-op stub — the floor default. Every capability method (and the embedding\n * write-hook) throws {@link SearchNotEnabledError}; opt in with\n * `searchStrategy: withSearch()` in createNoydb. @internal\n */\nexport const NO_SEARCH: SearchStrategy = {\n async search() { throw new SearchNotEnabledError() },\n async retrieve() { throw new SearchNotEnabledError() },\n async similarTo() { throw new SearchNotEnabledError() },\n async warmIndex() { throw new SearchNotEnabledError() },\n async flushIndex() { throw new SearchNotEnabledError() },\n async embedOnWrite() { throw new SearchNotEnabledError() },\n}\n","/**\n * Collection-level search / retrieval (`search` / `retrieve` / `similarTo` +\n * the lexical-index build/flush/warm machinery).\n *\n * This is the retrieval surface: a pure client-side lexical scan\n * (`search`), the persisted-or-in-memory inverted-index lifecycle\n * (`flushIndex` / `warmIndex` / `buildRetrievalDocs` + the dict-label / blob-\n * filename resolvers that feed it), and the lexical / semantic / hybrid\n * `retrieve` fan-out plus raw-vector kNN (`similarTo`). None of it touches the\n * write path — it reads the live eager cache and the encrypted `_vec` / `_ftindex`\n * side-cars.\n *\n * Every function takes a small {@link SearchContext} instead of `this`,\n * mirroring the `record-keys/` siblings.\n *\n * The `cache` is the SAME `Map` reference `Collection` owns (passed by\n * reference, never copied) so the index always builds over the live working\n * set. {@link buildPersistedIndexCallbacks} is special: it is invoked from the\n * `Collection` constructor *before* `this.codec` exists, so it takes a context\n * THUNK and resolves the context lazily at each callback invocation.\n *\n * Internal service — not exported as a `@noy-db/hub/*` subpath.\n */\nimport type { NoydbStore } from '../../kernel/types.js'\nimport type { RecordCodec } from '../../kernel/enclave/index.js'\nimport { stripI18nFilled, type I18nTextDescriptor } from '../../with-shape/i18n/core.js'\nimport { isStaticDictDescriptor, type DictKeyDescriptor, type StaticDictDescriptor, type DictionaryHandle } from '../../with-shape/i18n/dictionary.js'\nimport type { BlobSet } from '../../with-shape/blobs/blob-set.js'\nimport type { BlobFieldsConfig } from '../../with-shape/blobs/blob-compaction.js'\nimport type { Query } from '../../kernel/query/index.js'\nimport { embeddingSourceText, type VectorSet, type EmbeddingDescriptor, type StoredVector } from '../embeddings/index.js'\nimport { EmbeddingDimMismatchError } from '../../kernel/errors.js'\nimport { searchScan, fuseRetrieval, type SearchOptions, type SearchResult } from './index.js'\nimport type { IndexStore } from './index-store.js'\nimport type { PersistedIndexCallbacks } from './persisted-index-store.js'\nimport { extractSnippet } from './snippet.js'\nimport { buildStringFieldEntries, buildI18nFieldEntries, buildDictKeyFieldEntries, buildBlobFieldEntries } from './build-docs.js'\nimport type { IndexDoc, IndexHit } from './inverted-index.js'\nimport type { RetrieveOptions, RetrieveHit } from './retrieve-types.js'\n\n/** Everything the moving search/retrieval methods touched on `this.*`. */\nexport interface SearchContext<T> {\n /** Collection name — error context + `_ftindex` row key. */\n readonly name: string\n /** Vault namespace the side-cars (`_vec`, `_ftindex`) live under. */\n readonly vault: string\n /** The ciphertext store. */\n readonly adapter: NoydbStore\n /** The record codec — decrypts the `_vec` / `_ftindex` side-car bodies. */\n readonly codec: RecordCodec<T>\n /** The eager working-set cache (SHARED `Map` reference, never copied). */\n readonly cache: Map<string, { record: T; version: number }>\n /** True in lazy mode — search/retrieve/warm require eager mode. */\n readonly lazy: boolean\n /** Declared text-index fields, or undefined. */\n readonly textIndexes: readonly string[] | undefined\n /** Declared i18n fields, or undefined. */\n readonly i18nFields: Record<string, I18nTextDescriptor> | undefined\n /** Declared dictionary-key fields, or undefined. */\n readonly dictKeyFields: Record<string, DictKeyDescriptor | StaticDictDescriptor> | undefined\n /** Declared blob fields, or undefined. */\n readonly blobFields: BlobFieldsConfig | undefined\n /** Dictionary handle resolver (dynamic dict-key labels), or undefined. */\n readonly getDictionary: ((name: string) => Promise<DictionaryHandle>) | undefined\n /** The lexical inverted-index store (in-memory or persisted), or undefined. */\n readonly searchIndexStore: IndexStore | undefined\n /** The vector set for semantic retrieval, or undefined. */\n readonly vectorSet: VectorSet | undefined\n /** The embeddings descriptor, or undefined. */\n readonly embeddings: EmbeddingDescriptor | undefined\n /** Hydrate the eager cache before reading it. */\n ensureHydrated(): Promise<void>\n /** Open the blob facade for a record (used to list indexed blob filenames). */\n blob(id: string): BlobSet\n}\n\n/**\n * Client-side lexical scan over the live eager cache. Eager mode only;\n * nothing searchable is written to the store.\n */\nexport async function search<T>(ctx: SearchContext<T>, field: string, query: string, opts: SearchOptions = {}): Promise<SearchResult<T>[]> {\n if (ctx.lazy) {\n throw new Error(\n `Collection \"${ctx.name}\": search() (scan mode) requires eager mode (prefetch: true). ` +\n `A store-usable blind index for lazy / at-scale search is a separate gated opt-in (#308).`,\n )\n }\n await ctx.ensureHydrated()\n const entries: { id: string; record: T }[] = []\n // Strip the internal densify marker from the user-facing records.\n // Non-mutating: never touches the cached record object. The search index\n // is built over the same (marker-free) record, which is fine — the marker\n // is never a searchable field.\n for (const [id, e] of ctx.cache) entries.push({ id, record: stripI18nFilled(e.record as Record<string, unknown>) as T })\n return searchScan(entries, field, query, opts)\n}\n\n/** L1 — build IndexDoc[] for the configured text fields over the live cache. */\nexport function buildRetrievalDocs<T>(\n ctx: SearchContext<T>,\n labelMaps: Map<string, Map<string, Record<string, string>>>,\n blobFilenames: Map<string, Map<string, string[]>>,\n only?: readonly string[],\n): IndexDoc[] {\n const docs: IndexDoc[] = []\n for (const [id, e] of ctx.cache) {\n const rec = stripI18nFilled(e.record as Record<string, unknown>)\n const fields = buildStringFieldEntries(rec, ctx.textIndexes ?? [], only)\n if (ctx.i18nFields) fields.push(...buildI18nFieldEntries(rec, ctx.i18nFields, ctx.textIndexes ?? [], only))\n if (ctx.dictKeyFields) fields.push(...buildDictKeyFieldEntries(rec, ctx.dictKeyFields, labelMaps, ctx.textIndexes ?? [], only))\n const blobNames = blobFilenames.get(id)\n if (blobNames) fields.push(...buildBlobFieldEntries(blobNames))\n if (fields.length > 0) docs.push({ id, fields })\n }\n return docs\n}\n\n/** L1 — true iff any configured text index is also a blob field (gates ALL slot I/O). */\nexport function hasIndexedBlobFields<T>(ctx: SearchContext<T>, only?: readonly string[]): boolean {\n if (!ctx.blobFields || !ctx.textIndexes) return false\n const fields = only ? ctx.textIndexes.filter((f) => only.includes(f)) : ctx.textIndexes\n return fields.some((f) => f in ctx.blobFields!)\n}\n\n/**\n * L1 — resolve `recordId -> (blobField -> filenames[])` by listing slots\n * for the configured blob fields of each cached record. Blob slot metadata is\n * NOT inline on the record: it lives in a separate `_blob_slots_*` collection,\n * so this costs ONE `blob(id).list()` (a `listSlots`) per record at build time\n * — the heaviest indexing source. Fully gated by {@link hasIndexedBlobFields};\n * non-blob (and blob-but-not-indexed) collections do ZERO slot I/O.\n */\nasync function resolveBlobFilenames<T>(ctx: SearchContext<T>, only?: readonly string[]): Promise<Map<string, Map<string, string[]>>> {\n const out = new Map<string, Map<string, string[]>>()\n if (!hasIndexedBlobFields(ctx, only)) return out\n const indexed = (only ? ctx.textIndexes!.filter((f) => only.includes(f)) : ctx.textIndexes!)\n .filter((f) => f in ctx.blobFields!)\n const indexedSet = new Set(indexed)\n for (const id of ctx.cache.keys()) {\n let slots\n try {\n slots = await ctx.blob(id).list()\n } catch {\n continue\n }\n let byField: Map<string, string[]> | undefined\n for (const slot of slots) {\n if (!indexedSet.has(slot.name) || !slot.filename) continue\n if (!byField) { byField = new Map(); out.set(id, byField) }\n const names = byField.get(slot.name)\n if (names) names.push(slot.filename)\n else byField.set(slot.name, [slot.filename])\n }\n }\n return out\n}\n\n/** L1 — field -> (key -> {locale->label}) for dictKey fields; static from table, dynamic via getDictionary().list(). */\nasync function resolveDictLabelMaps<T>(ctx: SearchContext<T>): Promise<Map<string, Map<string, Record<string, string>>>> {\n const maps = new Map<string, Map<string, Record<string, string>>>()\n if (!ctx.dictKeyFields || !ctx.textIndexes) return maps\n for (const field of ctx.textIndexes) {\n const desc = ctx.dictKeyFields[field]\n if (!desc) continue\n const m = new Map<string, Record<string, string>>()\n if (isStaticDictDescriptor(desc)) {\n for (const [key, labels] of Object.entries(desc.table)) m.set(key, labels as Record<string, string>)\n } else {\n if (ctx.getDictionary) {\n const handle = await ctx.getDictionary(desc.name)\n for (const e of await handle.list()) m.set(e.key, e.labels)\n }\n }\n maps.set(field, m)\n }\n return maps\n}\n\n/** L1.5 — force-persist the lexical index now (e.g. on save/idle). Persists only when textIndexPersist is enabled; a no-op otherwise. */\nexport async function flushIndex<T>(ctx: SearchContext<T>): Promise<void> {\n if (!ctx.searchIndexStore) return\n await ctx.ensureHydrated()\n const labelMaps = await resolveDictLabelMaps(ctx)\n const blobFilenames = await resolveBlobFilenames(ctx)\n await ctx.searchIndexStore.ensureBuilt(() => buildRetrievalDocs(ctx, labelMaps, blobFilenames))\n await ctx.searchIndexStore.flush?.()\n}\n\n/** L2 — load + decrypt all _vec sidecars into StoredVector[] for the VectorSet. */\nfunction buildVectorLoad<T>(ctx: SearchContext<T>): () => Promise<StoredVector[]> {\n return async () => {\n const ids = await ctx.adapter.list(ctx.vault, '_vec')\n const out: StoredVector[] = []\n for (const id of ids) {\n const env = await ctx.adapter.get(ctx.vault, '_vec', id)\n if (!env) continue\n const body = await ctx.codec.decryptJsonString(env)\n if (body === null) continue\n const parsed = JSON.parse(body) as { vec: number[]; model: string }\n out.push({ id, vec: new Float32Array(parsed.vec), model: parsed.model })\n }\n return out\n }\n}\n\n/**\n * L1.5 — build the PersistedIndexCallbacks bridge: crypto lives here\n * (collection has getDEK / encryptJsonString / decryptJsonString / adapter),\n * the index store itself is crypto-free.\n *\n * Fingerprint encoding: body-wrap approach — save(json, fp) stores\n * JSON.stringify({ fp, idx: json }) as the encrypted body so the standard\n * EncryptedEnvelope shape is never extended. load() decrypts and JSON.parses\n * the wrapper back out.\n *\n * Cache shape: ctx.cache stores { record, version } — currentFingerprint()\n * iterates over e.version.\n *\n * NOTE: this factory is invoked from the `Collection` constructor *before*\n * `this.codec` is assigned, so it takes a context THUNK and resolves the\n * context lazily inside each callback — by the time a callback actually runs,\n * the codec and cache are fully wired.\n */\nexport function buildPersistedIndexCallbacks<T>(provideCtx: () => SearchContext<T>): PersistedIndexCallbacks {\n const FT = '_ftindex'\n return {\n load: async () => {\n const ctx = provideCtx()\n const env = await ctx.adapter.get(ctx.vault, FT, ctx.name)\n if (!env) return null\n const body = await ctx.codec.decryptJsonString(env)\n if (body === null) return null\n try {\n const wrapped = JSON.parse(body) as { fp: { count: number; maxVersion: number }; idx: string }\n return { json: wrapped.idx, fingerprint: wrapped.fp }\n } catch {\n return null\n }\n },\n save: async (json, fp) => {\n const ctx = provideCtx()\n const body = JSON.stringify({ fp, idx: json })\n const env = await ctx.codec.encryptJsonString(body, fp.count)\n await ctx.adapter.put(ctx.vault, FT, ctx.name, env)\n },\n remove: async () => { const ctx = provideCtx(); await ctx.adapter.delete(ctx.vault, FT, ctx.name) },\n currentFingerprint: () => {\n const ctx = provideCtx()\n let maxVersion = 0\n for (const e of ctx.cache.values()) if (e.version > maxVersion) maxVersion = e.version\n return { count: ctx.cache.size, maxVersion }\n },\n }\n}\n\n/** L1 — pre-build the lexical index (e.g. on open) so the first retrieve() pays no build scan. */\nexport async function warmIndex<T>(ctx: SearchContext<T>): Promise<void> {\n if (!ctx.searchIndexStore) return\n if (ctx.lazy) {\n throw new Error(\n `Collection \"${ctx.name}\": warmIndex() requires eager mode (prefetch: true).`,\n )\n }\n await ctx.ensureHydrated()\n const built = ctx.searchIndexStore.built\n const labelMaps = built ? new Map() : await resolveDictLabelMaps(ctx)\n const blobFilenames = built ? new Map() : await resolveBlobFilenames(ctx)\n await ctx.searchIndexStore.ensureBuilt(() => buildRetrievalDocs(ctx, labelMaps, blobFilenames))\n}\n\n/** Retrieval. mode: 'lexical' (default) | 'semantic' (L2) | 'hybrid' (L3). */\nexport async function retrieve<T>(ctx: SearchContext<T>, query: string, opts: RetrieveOptions<T> = {}): Promise<RetrieveHit<T>[]> {\n const hits =\n opts.mode === 'semantic' ? await retrieveSemantic(ctx, query, opts)\n : opts.mode === 'hybrid' ? await retrieveHybrid(ctx, query, opts)\n : await retrieveLexical(ctx, query, opts)\n return opts.within ? applyWithin(hits, opts.within) : hits\n}\n\n/** L3 — keep only hits whose id matches the structured query, re-rank 1-based. */\nfunction applyWithin<T>(hits: RetrieveHit<T>[], within: Query<T>): RetrieveHit<T>[] {\n const ids = new Set(within._idArray())\n return hits.filter(h => ids.has(h.id)).map((h, i) => ({ ...h, rank: i + 1 }))\n}\n\n/** L1 — client-side lexical retrieval; ranked { id, score, field, snippet, locale? }. */\nasync function retrieveLexical<T>(ctx: SearchContext<T>, query: string, opts: RetrieveOptions<T>): Promise<RetrieveHit<T>[]> {\n if (!ctx.searchIndexStore) {\n throw new Error(`Collection \"${ctx.name}\": retrieve() requires a textIndexes config.`)\n }\n if (ctx.lazy) {\n throw new Error(\n `Collection \"${ctx.name}\": retrieve() requires eager mode (prefetch: true).`,\n )\n }\n await ctx.ensureHydrated()\n const built = ctx.searchIndexStore.built\n const labelMaps = built ? new Map() : await resolveDictLabelMaps(ctx)\n const blobFilenames = built ? new Map() : await resolveBlobFilenames(ctx)\n const index = await ctx.searchIndexStore.ensureBuilt(() => buildRetrievalDocs(ctx, labelMaps, blobFilenames))\n const hits = index.query(query, {\n ...(opts.limit !== undefined ? { limit: opts.limit } : {}),\n ...(opts.match ? { match: opts.match } : {}),\n ...(opts.prefix ? { prefix: opts.prefix } : {}),\n ...(opts.fields ? { fields: opts.fields } : {}),\n })\n const window = opts.snippetWindow ?? 80\n return hits.map((h: IndexHit, i: number) => {\n const base: RetrieveHit<T> = {\n id: h.id,\n score: h.score,\n rank: i + 1,\n field: h.field,\n snippet: extractSnippet(h.text, h.offset, window),\n ...(h.locale !== undefined ? { locale: h.locale } : {}),\n ...(opts.includeRecord\n ? (() => {\n const e = ctx.cache.get(h.id)\n return e ? { record: stripI18nFilled(e.record as Record<string, unknown>) as T } : {}\n })()\n : {}),\n }\n return base\n })\n}\n\n/** L3 — hybrid: fuse lexical (L1) + semantic (L2) by RRF. Requires embeddings. */\nasync function retrieveHybrid<T>(ctx: SearchContext<T>, query: string, opts: RetrieveOptions<T>): Promise<RetrieveHit<T>[]> {\n if (!ctx.embeddings) {\n throw new Error(`Collection \"${ctx.name}\": retrieve({mode:'hybrid'}) requires an embeddings config.`)\n }\n const [lex, sem] = await Promise.all([\n retrieveLexical(ctx, query, opts),\n retrieveSemantic(ctx, query, opts),\n ])\n return fuseRetrieval([lex, sem], opts.limit !== undefined ? { limit: opts.limit } : {})\n}\n\n/** L2 — semantic branch of retrieve(): encode query → similarTo(). */\nasync function retrieveSemantic<T>(ctx: SearchContext<T>, query: string, opts: RetrieveOptions<T>): Promise<RetrieveHit<T>[]> {\n if (!ctx.embeddings) throw new Error(`Collection \"${ctx.name}\": retrieve({mode:'semantic'}) requires an embeddings config.`)\n if (ctx.lazy) throw new Error(`Collection \"${ctx.name}\": retrieve() requires eager mode (prefetch: true).`)\n const qVec = await ctx.embeddings.encode(query)\n return similarTo(ctx, qVec, {\n ...(opts.limit !== undefined ? { k: opts.limit } : {}),\n ...(opts.minScore !== undefined ? { minScore: opts.minScore } : {}),\n ...(opts.includeRecord ? { includeRecord: true } : {}),\n })\n}\n\n/** L2 — raw-vector kNN over the encrypted vector set (decrypted in the trusted tier).\n * Snippet is '' for vector hits in v1 (semantic match isn't span-located). */\nexport async function similarTo<T>(ctx: SearchContext<T>, vector: Float32Array, opts: { k?: number; minScore?: number; includeRecord?: boolean } = {}): Promise<RetrieveHit<T>[]> {\n if (!ctx.embeddings || !ctx.vectorSet) throw new Error(`Collection \"${ctx.name}\": similarTo() requires an embeddings config.`)\n if (ctx.lazy) throw new Error(`Collection \"${ctx.name}\": similarTo() requires eager mode (prefetch: true).`)\n await ctx.ensureHydrated()\n await ctx.vectorSet.ensureLoaded(buildVectorLoad(ctx))\n const hits = ctx.vectorSet.cosineTopK(vector, opts.k ?? 10, {\n ...(opts.minScore !== undefined ? { minScore: opts.minScore } : {}),\n expectModel: ctx.embeddings.model,\n })\n return hits.map((h, i) => {\n const base: RetrieveHit<T> = { id: h.id, score: h.score, rank: i + 1, field: '(vector)', snippet: '' }\n if (opts.includeRecord) { const e = ctx.cache.get(h.id); if (e) (base as { record?: T }).record = stripI18nFilled(e.record as Record<string, unknown>) as T }\n return base\n })\n}\n\n/**\n * L2 — the embedding write-hook: derive a record's embedding vector at\n * write and persist it as the encrypted `_vec` side-car. Routed through the\n * search strategy so it runs only when `withSearch()` is opted in — computing\n * vectors that no gated `similarTo` / semantic `retrieve` could query would be\n * dead weight, so the compute is paired with the search capability. The caller\n * (`Collection.put`) only invokes this when `embeddings` is declared.\n */\nexport async function embedOnWrite<T>(ctx: SearchContext<T>, id: string, record: T, version: number): Promise<void> {\n if (!ctx.embeddings) return\n const text = embeddingSourceText(record as Record<string, unknown>, ctx.embeddings.source)\n const vec = await ctx.embeddings.encode(text)\n if (vec.length !== ctx.embeddings.dim) throw new EmbeddingDimMismatchError('embeddings', ctx.embeddings.dim, vec.length)\n const body = JSON.stringify({ vec: Array.from(vec), model: ctx.embeddings.model, dim: ctx.embeddings.dim })\n const vecEnv = await ctx.codec.encryptJsonString(body, version)\n await ctx.adapter.put(ctx.vault, '_vec', id, vecEnv)\n ctx.vectorSet?.markDirty()\n}\n"],"mappings":"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","names":["K1","B","search","retrieve","similarTo","warmIndex","flushIndex","embedOnWrite"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/with-audit/tiers/active.ts","../src/with-audit/tiers/strategy.ts","../src/with-audit/tiers/index.ts"],"sourcesContent":["/**\n * Enable the hierarchical-tiers capability.\n * Pass to `createNoydb({ tiersStrategy: withTiers() })` to make a collection's\n * `putAtTier` / `getAtTier` / `listAtTier` / `elevate` / `demote` methods live.\n * The tier read/write/re-key engine is dynamically imported here, so it stays\n * out of the floor bundle until opted in.\n */\nimport type { TiersStrategy } from './strategy.js'\n\nexport function withTiers(): TiersStrategy {\n return {\n async putAtTier(ctx, id, record, tier, opts) {\n const { putAtTier } = await import('./index.js')\n return putAtTier(ctx, id, record, tier, opts)\n },\n async getAtTier(ctx, id) {\n const { getAtTier } = await import('./index.js')\n return getAtTier(ctx, id)\n },\n async listAtTier(ctx) {\n const { listAtTier } = await import('./index.js')\n return listAtTier(ctx)\n },\n async elevate(ctx, id, toTier) {\n const { elevate } = await import('./index.js')\n return elevate(ctx, id, toTier)\n },\n async demote(ctx, id, toTier) {\n const { demote } = await import('./index.js')\n return demote(ctx, id, toTier)\n },\n }\n}\n","/**\n * Tiers capability strategy — the five on-demand collection-level tier\n * operations the `Collection` routes through. The active engine\n * ({@link withTiers}) dynamically imports the tier cores (keeping them out of\n * the floor bundle); {@link NO_TIERS} throws. Collection always builds its\n * per-call {@link TiersContext} and delegates here, so an un-opted-in caller\n * hits `NO_TIERS`'s throw.\n * @internal\n */\nimport type { GhostRecord } from '../../kernel/types.js'\nimport type { TiersContext } from './index.js'\nimport { TiersNotEnabledError } from '../../kernel/errors.js'\n\nexport interface TiersStrategy {\n putAtTier<T>(\n ctx: TiersContext<T>,\n id: string,\n record: T,\n tier: number,\n opts?: { elevation?: { reason: string; fromTier: number }; source?: string; sourceTs?: string },\n ): Promise<void>\n getAtTier<T>(ctx: TiersContext<T>, id: string): Promise<T | GhostRecord | null>\n listAtTier<T>(ctx: TiersContext<T>): Promise<Array<{ id: string; tier: number; readable: boolean }>>\n elevate<T>(ctx: TiersContext<T>, id: string, toTier: number): Promise<void>\n demote<T>(ctx: TiersContext<T>, id: string, toTier: number): Promise<void>\n}\n\n/**\n * No-op stub — the floor default. Every capability method throws\n * {@link TiersNotEnabledError}; opt in with `tiersStrategy: withTiers()` in\n * createNoydb. @internal\n */\nexport const NO_TIERS: TiersStrategy = {\n async putAtTier() { throw new TiersNotEnabledError() },\n async getAtTier() { throw new TiersNotEnabledError() },\n async listAtTier() { throw new TiersNotEnabledError() },\n async elevate() { throw new TiersNotEnabledError() },\n async demote() { throw new TiersNotEnabledError() },\n}\n","/**\n * Hierarchical access — the collection-level tier operations\n * (`putAtTier` / `getAtTier` / `listAtTier` / `elevate` / `demote`).\n *\n * A collection opted into `{ tiers: [...] }` stamps `_tier: N` on each envelope\n * and encrypts its body under the tier-N DEK. These helpers are the read/write\n * surface for that scheme: write at a tier, read with tier-aware visibility\n * (invisibility vs. ghost), and move a record up (`elevate`) or down (`demote`)\n * the tier ladder by re-wrapping its body key.\n *\n * Each function takes a small {@link TiersContext} (the exact `this.*` the\n * moving methods touched) instead of `this`, mirroring the `record-keys/`\n * siblings. Behaviour is byte-identical to the inline code it replaced.\n *\n * The crux is `cekCache`: `elevate`/`demote`/`getAtTier` write the SAME `Lru`\n * reference `Collection` owns (never a copy) so a per-record CEK re-wrap stays\n * synchronous with the cache the kernel's write/read path also mutates. The\n * cross-tier event sink stays collection-resident and is reached via the\n * `emitCrossTierEvent` callback.\n *\n * Internal service — not exported as a `@noy-db/hub/*` subpath.\n */\nexport { withTiers } from './active.js'\nexport { NO_TIERS, type TiersStrategy } from './strategy.js'\nexport { TiersNotEnabledError } from '../../kernel/errors.js'\nimport { encrypt, decrypt, unwrapCek, rewrapBodyToDek, type RecordCodec, type EnclaveKey, type SealedShredSlot } from '../../kernel/enclave/index.js'\nimport { TierDemoteDeniedError } from '../../kernel/errors.js'\nimport { dekKey, assertTierAccess } from '../../with-party/team/tiers.js'\nimport type { UnlockedKeyring } from '../../with-party/team/keyring.js'\nimport type { Lru } from '../../kernel/cache/index.js'\nimport {\n NOYDB_FORMAT_VERSION,\n type NoydbStore,\n type EncryptedEnvelope,\n type GhostRecord,\n type TierMode,\n type CrossTierAccessEvent,\n} from '../../kernel/types.js'\n\n/** Everything the moving tier methods touched on `this.*`, as a flat context. */\nexport interface TiersContext<T> {\n /** Collection name — the crypto AAD scope and tier-DEK key prefix. */\n readonly name: string\n /** Vault namespace the records live under. */\n readonly vault: string\n /** The ciphertext store. */\n readonly adapter: NoydbStore\n /** The caller's unlocked keyring (tier-DEK holdings + role + userId). */\n readonly keyring: UnlockedKeyring\n /** The record codec — decrypts a tier-0 envelope to T. */\n readonly codec: RecordCodec<T>\n /**\n * The collection's per-record CEK cache (SHARED reference, not a copy).\n * `elevate`/`demote`/`getAtTier` re-wrap a record's CEK and `set` it here so\n * the move stays synchronous with the cache the kernel's read/write path\n * owns. `null` → no caching.\n */\n readonly cekCache: Lru<string, EnclaveKey> | null\n /** Emit `_source`/`_sourceTs` provenance fields when a source is supplied. */\n readonly provenance: boolean\n /** Declared tiers, or null when the feature is off. */\n readonly tiers: ReadonlySet<number> | null\n /** Above-tier read visibility mode (`'invisibility'` | `'ghost'`). */\n readonly tierMode: TierMode\n /** Resolve a tier DEK by its `dekKey(name, tier)`. */\n getDEK(key: string): Promise<EnclaveKey>\n /** Fire a cross-tier access event (sink stays collection-resident). */\n emitCrossTierEvent(event: CrossTierAccessEvent): void\n}\n\nexport function assertTiersEnabled<T>(ctx: TiersContext<T>): void {\n if (!ctx.tiers) {\n throw new Error(\n `Collection \"${ctx.name}\": hierarchical tiers are not enabled. ` +\n `Pass { tiers: [0, 1, 2, …] } to vault.collection() to opt in.`,\n )\n }\n}\n\nexport function assertDeclaredTier<T>(ctx: TiersContext<T>, tier: number): void {\n if (tier < 0 || !Number.isInteger(tier)) {\n throw new Error(`Collection \"${ctx.name}\": tier must be a non-negative integer, got ${tier}`)\n }\n if (tier === 0) return\n if (!ctx.tiers || !ctx.tiers.has(tier)) {\n throw new Error(\n `Collection \"${ctx.name}\": tier ${tier} is not declared in { tiers: [...] }`,\n )\n }\n}\n\nfunction isElevatorOrOwner<T>(ctx: TiersContext<T>): boolean {\n return ctx.keyring.role === 'owner' || ctx.keyring.role === 'admin'\n}\n\n/**\n * tier-aware put. Encrypts the record with the collection's tier-N DEK and\n * stamps `_tier: N` on the envelope. The caller's keyring must hold the tier-N\n * DEK (directly, by delegation, or by virtue of being the grantor); otherwise\n * throws `TierNotGrantedError`.\n *\n * accepts an optional `elevation` context. When present, the emitted cross-tier\n * event is stamped with `authorization: 'elevation'`, the elevation's reason,\n * and the caller's pre-elevation tier. `vault.elevate(...).collection().put`\n * threads this through; direct `putAtTier` calls leave it undefined and fall\n * back to the inherent-write event shape.\n */\nexport async function putAtTier<T>(\n ctx: TiersContext<T>,\n id: string,\n record: T,\n tier: number,\n opts?: { elevation?: { reason: string; fromTier: number }; source?: string; sourceTs?: string },\n): Promise<void> {\n assertTiersEnabled(ctx)\n assertDeclaredTier(ctx, tier)\n assertTierAccess(ctx.keyring, ctx.name, tier)\n\n const key = dekKey(ctx.name, tier)\n const dek = await ctx.getDEK(key)\n\n const existing = await ctx.adapter.get(ctx.vault, ctx.name, id)\n const version = existing ? existing._v + 1 : 1\n const json = JSON.stringify(record)\n const { iv, data } = await encrypt(json, dek)\n const envelope: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: version,\n _ts: new Date().toISOString(),\n _iv: iv,\n _data: data,\n _by: ctx.keyring.userId,\n ...(tier > 0 && { _tier: tier }),\n ...(ctx.provenance && opts?.source !== undefined ? { _source: opts.source, _sourceTs: opts.sourceTs ?? new Date().toISOString() } : {}),\n }\n\n await ctx.adapter.put(ctx.vault, ctx.name, id, envelope)\n\n if (tier > 0) {\n ctx.emitCrossTierEvent({\n actor: ctx.keyring.userId,\n collection: ctx.name,\n id,\n tier,\n authorization: opts?.elevation ? 'elevation' : 'inherent',\n op: 'put',\n ts: envelope._ts,\n ...(opts?.elevation && {\n reason: opts.elevation.reason,\n elevatedFrom: opts.elevation.fromTier,\n }),\n })\n }\n}\n\n/**\n * tier-aware get. When the stored record is at a tier the caller cannot\n * decrypt:\n * - `'invisibility'` mode (default) → returns `null`.\n * - `'ghost'` mode → returns a `GhostRecord` placeholder with the tier and\n * the record id (the record exists but contents are withheld).\n *\n * Fully-cleared reads return the plaintext record and fire a cross-tier event\n * when `_tier > 0`.\n */\nexport async function getAtTier<T>(ctx: TiersContext<T>, id: string): Promise<T | GhostRecord | null> {\n assertTiersEnabled(ctx)\n const envelope = await ctx.adapter.get(ctx.vault, ctx.name, id)\n if (!envelope) return null\n const tier = envelope._tier ?? 0\n if (tier === 0) {\n return ctx.codec.decryptRecord(envelope)\n }\n\n const key = dekKey(ctx.name, tier)\n if (!ctx.keyring.deks.has(key)) {\n if (ctx.tierMode === 'ghost') {\n return { _ghost: true, _tier: tier } as GhostRecord\n }\n return null\n }\n\n const dek = await ctx.getDEK(key)\n // A tiered record may carry a per-record CEK (e.g. a CEK record\n // elevated via `elevate()`): the CEK is wrapped under the TIER DEK, so\n // unwrap under the tier DEK then decrypt the body under the CEK. Legacy\n // tiered records decrypt directly under the tier DEK.\n let plaintext: string\n if (envelope._cek !== undefined) {\n const cek = await unwrapCek(envelope._cek, dek)\n ctx.cekCache?.set(id, cek, 1)\n plaintext = await decrypt(envelope._iv, envelope._data, cek)\n } else {\n plaintext = await decrypt(envelope._iv, envelope._data, dek)\n }\n const record = JSON.parse(plaintext) as T\n\n ctx.emitCrossTierEvent({\n actor: ctx.keyring.userId,\n collection: ctx.name,\n id,\n tier,\n authorization: isElevatorOrOwner(ctx) ? 'inherent' : 'delegation',\n op: 'get',\n ts: new Date().toISOString(),\n })\n\n return record\n}\n\n/**\n * list ids grouped by the caller's readability. Returns only ids whose tier the\n * caller can read. Above-tier ids are omitted in `'invisibility'` mode and\n * included (with tier metadata) in `'ghost'` mode.\n */\nexport async function listAtTier<T>(ctx: TiersContext<T>): Promise<Array<{ id: string; tier: number; readable: boolean }>> {\n assertTiersEnabled(ctx)\n const ids = await ctx.adapter.list(ctx.vault, ctx.name)\n const out: Array<{ id: string; tier: number; readable: boolean }> = []\n for (const id of ids) {\n const env = await ctx.adapter.get(ctx.vault, ctx.name, id)\n if (!env) continue\n const tier = env._tier ?? 0\n const readable = tier === 0 || ctx.keyring.deks.has(dekKey(ctx.name, tier))\n if (!readable && ctx.tierMode === 'invisibility') continue\n out.push({ id, tier, readable })\n }\n return out\n}\n\n/**\n * elevate a record to a higher tier. Re-encrypts with the target tier's DEK.\n * The caller must hold DEKs for both the current tier (to decrypt) and the\n * target tier (to re-encrypt). Stamps `_elevatedBy` with the caller id so\n * `demote()` can check the reverse operation.\n */\nexport async function elevate<T>(ctx: TiersContext<T>, id: string, toTier: number): Promise<void> {\n assertTiersEnabled(ctx)\n assertDeclaredTier(ctx, toTier)\n assertTierAccess(ctx.keyring, ctx.name, toTier)\n\n const envelope = await ctx.adapter.get(ctx.vault, ctx.name, id)\n if (!envelope) throw new Error(`Record \"${id}\" not found in collection \"${ctx.name}\"`)\n const fromTier = envelope._tier ?? 0\n if (toTier === fromTier) return\n if (toTier < fromTier) {\n throw new Error(`Use demote() to lower the tier of \"${id}\" from ${fromTier} to ${toTier}`)\n }\n // Caller must have access at the existing tier to decrypt.\n if (fromTier > 0) assertTierAccess(ctx.keyring, ctx.name, fromTier)\n\n const fromKey = dekKey(ctx.name, fromTier)\n const toKey = dekKey(ctx.name, toTier)\n const fromDek = await ctx.getDEK(fromKey)\n const toDek = await ctx.getDEK(toKey)\n\n // Per-record CEK composes with tiers: the body key is unchanged (history\n // chain identity preserved); only the wrapping key moves with the tier.\n // Legacy (no `_cek`) records take the direct-DEK path unchanged.\n const now = new Date().toISOString()\n const body = await rewrapBodyToDek(envelope, fromDek, toDek)\n if (body.cek) ctx.cekCache?.set(id, body.cek, 1)\n const next: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: envelope._v + 1,\n _ts: now,\n _iv: body._iv,\n _data: body._data,\n _by: ctx.keyring.userId,\n _tier: toTier,\n _elevatedBy: ctx.keyring.userId,\n ...(body._cek !== undefined ? { _cek: body._cek } : {}),\n }\n await ctx.adapter.put(ctx.vault, ctx.name, id, next)\n\n ctx.emitCrossTierEvent({\n actor: ctx.keyring.userId,\n collection: ctx.name,\n id,\n tier: toTier,\n authorization: 'elevation',\n op: 'elevate',\n ts: now,\n })\n}\n\n/**\n * demote a record to a lower tier. Allowed only for the user who performed the\n * last elevation or an owner.\n */\nexport async function demote<T>(ctx: TiersContext<T>, id: string, toTier: number): Promise<void> {\n assertTiersEnabled(ctx)\n if (toTier < 0) throw new Error(`Cannot demote to negative tier ${toTier}`)\n\n const envelope = await ctx.adapter.get(ctx.vault, ctx.name, id)\n if (!envelope) throw new Error(`Record \"${id}\" not found in collection \"${ctx.name}\"`)\n const fromTier = envelope._tier ?? 0\n if (toTier === fromTier) return\n if (toTier > fromTier) {\n throw new Error(`Use elevate() to raise the tier of \"${id}\" from ${fromTier} to ${toTier}`)\n }\n const isOwner = ctx.keyring.role === 'owner'\n const isOriginalElevator = envelope._elevatedBy === ctx.keyring.userId\n if (!isOwner && !isOriginalElevator) {\n throw new TierDemoteDeniedError(id, fromTier)\n }\n // Caller must still hold the DEK of the current tier to decrypt.\n assertTierAccess(ctx.keyring, ctx.name, fromTier)\n if (toTier > 0) assertDeclaredTier(ctx, toTier)\n\n const fromDek = await ctx.getDEK(dekKey(ctx.name, fromTier))\n const toDek = await ctx.getDEK(dekKey(ctx.name, toTier))\n\n // CEK re-wrap on demote — same body key, moved from the source tier\n // DEK to the target tier DEK. Legacy records take the direct-DEK path.\n const now = new Date().toISOString()\n const body = await rewrapBodyToDek(envelope, fromDek, toDek)\n if (body.cek) ctx.cekCache?.set(id, body.cek, 1)\n const next: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: envelope._v + 1,\n _ts: now,\n _iv: body._iv,\n _data: body._data,\n _by: ctx.keyring.userId,\n ...(toTier > 0 && { _tier: toTier }),\n ...(body._cek !== undefined ? { _cek: body._cek } : {}),\n }\n await ctx.adapter.put(ctx.vault, ctx.name, id, next)\n\n ctx.emitCrossTierEvent({\n actor: ctx.keyring.userId,\n collection: ctx.name,\n id,\n tier: fromTier,\n authorization: 'elevation',\n op: 'demote',\n ts: now,\n })\n}\n\n/**\n * Classify a live envelope's `_sealed` slots for crypto-shred completeness\n * (#M-1). Thin delegate to {@link RecordCodec.classifySealedShred}; surfaced\n * here because `vault.ts` forget() reaches in via `collection._classifySealedShred`.\n */\nexport function classifySealedShred<T>(\n ctx: TiersContext<T>,\n live: EncryptedEnvelope,\n): Promise<{ readonly slots: readonly SealedShredSlot[] }> {\n return ctx.codec.classifySealedShred(live)\n}\n"],"mappings":"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","names":["putAtTier","getAtTier","listAtTier","elevate","demote"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/with-party/team/presence.ts","../src/with-party/team/sync.ts","../src/with-party/team/sync-transaction.ts"],"sourcesContent":["/**\n * Presence handle — real-time awareness of who is viewing/editing a collection.\n * encrypted ephemeral channel keyed by collection DEK via HKDF.\n *\n * The presence key is derived from the collection DEK so:\n * - The adapter never learns user identities from presence payloads.\n * - Presence rotates automatically when the DEK rotates (revoked users\n * can no longer participate after a DEK rotation).\n *\n * Two transport strategies:\n * 1. **Pub/sub** (real-time): used when the adapter implements\n * `presencePublish` and `presenceSubscribe`.\n * 2. **Storage-poll** (fallback): presence records are written to a\n * reserved `_presence_<collection>` collection on the sync adapter\n * (if available) or local adapter, and polled periodically.\n */\n\nimport type { NoydbStore, PresencePeer } from '../../kernel/types.js'\nimport { encrypt, decrypt, generateIV, bufferToBase64, derivePresenceKey, type EnclaveKey } from '../../kernel/enclave/index.js'\n\n/** Options for constructing a PresenceHandle. @internal */\nexport interface PresenceHandleOpts {\n /** Local adapter for storage-poll fallback. */\n adapter: NoydbStore\n /** Remote (sync) adapter — preferred for broadcasting presence if available. */\n syncAdapter?: NoydbStore\n /** Vault name — used as part of the channel and storage key. */\n vault: string\n /** Collection name — used as HKDF `info` and channel suffix. */\n collectionName: string\n /** Calling user's ID, embedded unencrypted in storage records. */\n userId: string\n /** Whether encryption is active. When false, presence payloads are stored as JSON. */\n encrypted: boolean\n /** Callback that resolves the collection DEK (used to derive the presence key). */\n getDEK: (collectionName: string) => Promise<EnclaveKey>\n /** How long (ms) before a peer's presence is considered stale. Default: 30_000. */\n staleMs?: number\n /** Poll interval (ms) for the storage-poll fallback. Default: 5_000. */\n pollIntervalMs?: number\n}\n\n/**\n * Internal storage envelope for the storage-poll fallback.\n * Written to `_presence_<collection>` as `{ userId, lastSeen, iv, data }`.\n */\ninterface StoragePresenceRecord {\n userId: string\n lastSeen: string\n iv: string // base64 AES-GCM IV (empty when not encrypted)\n data: string // base64 ciphertext or JSON string when not encrypted\n}\n\n/** Presence handle for a single collection. */\nexport class PresenceHandle<P> {\n private readonly adapter: NoydbStore\n private readonly syncAdapter: NoydbStore | undefined\n private readonly vault: string\n private readonly collectionName: string\n private readonly userId: string\n private readonly encrypted: boolean\n private readonly getDEK: (collectionName: string) => Promise<EnclaveKey>\n private readonly staleMs: number\n private readonly pollIntervalMs: number\n private readonly channel: string\n private readonly storageCollection: string\n\n private presenceKey: EnclaveKey | null = null\n private subscribers: Array<(peers: PresencePeer<P>[]) => void> = []\n private unsubscribePubSub: (() => void) | null = null\n private pollTimer: ReturnType<typeof setInterval> | null = null\n private stopped = false\n\n constructor(opts: PresenceHandleOpts) {\n this.adapter = opts.adapter\n this.syncAdapter = opts.syncAdapter\n this.vault = opts.vault\n this.collectionName = opts.collectionName\n this.userId = opts.userId\n this.encrypted = opts.encrypted\n this.getDEK = opts.getDEK\n this.staleMs = opts.staleMs ?? 30_000\n this.pollIntervalMs = opts.pollIntervalMs ?? 5_000\n // Channel used by pub/sub adapters — vault-scoped so two collections\n // in the same vault don't bleed into each other's presence channels.\n this.channel = `${opts.vault}:${opts.collectionName}:presence`\n // Reserved collection name for the storage-poll fallback.\n this.storageCollection = `_presence_${opts.collectionName}`\n }\n\n /**\n * Announce yourself (or update your cursor/status).\n * Encrypts `payload` with the presence key and publishes it.\n */\n async update(payload: P): Promise<void> {\n if (this.stopped) return\n\n const key = await this.getPresenceKey()\n const now = new Date().toISOString()\n const plaintext = JSON.stringify({ userId: this.userId, lastSeen: now, payload })\n let encryptedPayload: string\n\n if (this.encrypted && key) {\n const iv = generateIV()\n const ivB64 = bufferToBase64(iv)\n const { data } = await encrypt(plaintext, key)\n encryptedPayload = JSON.stringify({ iv: ivB64, data })\n } else {\n encryptedPayload = plaintext\n }\n\n // Pub/sub path — publish to any adapter that supports it\n const pubAdapter = this.getPubSubAdapter()\n if (pubAdapter?.presencePublish) {\n await pubAdapter.presencePublish(this.channel, encryptedPayload)\n }\n\n // Storage-poll path — write a record to the storage adapter\n await this.writeStorageRecord(payload, now)\n }\n\n /**\n * Subscribe to presence updates. The callback receives a filtered, decrypted\n * list of all currently-active peers (excluding yourself, excluding stale).\n *\n * Returns an unsubscribe function. Also call `stop()` to release all resources.\n */\n subscribe(cb: (peers: PresencePeer<P>[]) => void): () => void {\n if (this.stopped) return () => {}\n\n this.subscribers.push(cb)\n\n // Start pub/sub listener on first subscriber\n if (this.subscribers.length === 1) {\n this.startListening()\n }\n\n return () => {\n this.subscribers = this.subscribers.filter(s => s !== cb)\n if (this.subscribers.length === 0) this.stopListening()\n }\n }\n\n /** Stop all listening and clear resources. */\n stop(): void {\n this.stopped = true\n this.stopListening()\n this.subscribers = []\n }\n\n // ─── Private ────────────────────────────────────────────────────────\n\n private async getPresenceKey(): Promise<EnclaveKey | null> {\n if (!this.encrypted) return null\n if (!this.presenceKey) {\n try {\n const dek = await this.getDEK(this.collectionName)\n this.presenceKey = await derivePresenceKey(dek, this.collectionName)\n } catch {\n // no-op — presence degrades gracefully if crypto fails\n }\n }\n return this.presenceKey\n }\n\n private getPubSubAdapter(): NoydbStore | undefined {\n // Prefer the sync adapter (it broadcasts to other devices)\n if (this.syncAdapter?.presencePublish) return this.syncAdapter\n if (this.adapter.presencePublish) return this.adapter\n return undefined\n }\n\n private startListening(): void {\n const pubAdapter = this.getPubSubAdapter()\n\n if (pubAdapter?.presenceSubscribe) {\n // Real-time pub/sub path\n this.unsubscribePubSub = pubAdapter.presenceSubscribe(\n this.channel,\n (encryptedPayload) => { void this.handlePubSubMessage(encryptedPayload) },\n )\n } else {\n // Storage-poll fallback\n this.pollTimer = setInterval(\n () => { void this.pollStoragePresence() },\n this.pollIntervalMs,\n )\n // Kick off an immediate poll\n void this.pollStoragePresence()\n }\n }\n\n private stopListening(): void {\n if (this.unsubscribePubSub) {\n this.unsubscribePubSub()\n this.unsubscribePubSub = null\n }\n if (this.pollTimer) {\n clearInterval(this.pollTimer)\n this.pollTimer = null\n }\n }\n\n private async handlePubSubMessage(encryptedPayload: string): Promise<void> {\n try {\n const peer = await this.decryptPresencePayload(encryptedPayload)\n if (!peer || peer.userId === this.userId) return\n\n const cutoff = new Date(Date.now() - this.staleMs).toISOString()\n if (peer.lastSeen < cutoff) return\n\n // Deliver only this new peer to subscribers; a full snapshot poll follows\n // on next interval. For pub/sub, we could maintain a map of active peers,\n // but for simplicity: emit a snapshot read from storage.\n await this.pollStoragePresence()\n } catch {\n // Decrypt failure — stale key or tampered payload, ignore\n }\n }\n\n private async decryptPresencePayload(\n encryptedPayload: string,\n ): Promise<{ userId: string; lastSeen: string; payload: P } | null> {\n const key = await this.getPresenceKey()\n\n if (!this.encrypted || !key) {\n return JSON.parse(encryptedPayload) as { userId: string; lastSeen: string; payload: P }\n }\n\n const { iv: ivB64, data } = JSON.parse(encryptedPayload) as { iv: string; data: string }\n const plaintext = await decrypt(ivB64, data, key)\n return JSON.parse(plaintext) as { userId: string; lastSeen: string; payload: P }\n }\n\n private async writeStorageRecord(payload: P, now: string): Promise<void> {\n const key = await this.getPresenceKey()\n const plaintext = JSON.stringify(payload)\n let iv = ''\n let data: string\n\n if (this.encrypted && key) {\n const ivBytes = generateIV()\n iv = bufferToBase64(ivBytes)\n const result = await encrypt(plaintext, key)\n data = result.data\n } else {\n data = plaintext\n }\n\n const record: StoragePresenceRecord = { userId: this.userId, lastSeen: now, iv, data }\n const json = JSON.stringify(record)\n\n // Use the sync adapter if available (so other devices can read it);\n // fall back to local adapter.\n const storeAdapter = this.syncAdapter ?? this.adapter\n const envelope = {\n _noydb: 1 as const,\n _v: 1,\n _ts: now,\n _iv: '',\n _data: json,\n }\n try {\n await storeAdapter.put(\n this.vault,\n this.storageCollection,\n this.userId,\n envelope,\n )\n } catch {\n // Presence write failure is non-fatal — the user is still present locally\n }\n }\n\n private async pollStoragePresence(): Promise<void> {\n if (this.stopped || this.subscribers.length === 0) return\n\n try {\n const storeAdapter = this.syncAdapter ?? this.adapter\n const ids = await storeAdapter.list(this.vault, this.storageCollection)\n const cutoff = new Date(Date.now() - this.staleMs).toISOString()\n const peers: PresencePeer<P>[] = []\n\n for (const id of ids) {\n if (id === this.userId) continue // skip ourselves\n const envelope = await storeAdapter.get(this.vault, this.storageCollection, id)\n if (!envelope) continue\n\n const record = JSON.parse(envelope._data) as StoragePresenceRecord\n if (record.lastSeen < cutoff) continue\n\n let peerPayload: P\n if (this.encrypted && this.presenceKey && record.iv) {\n const plaintext = await decrypt(record.iv, record.data, this.presenceKey)\n peerPayload = JSON.parse(plaintext) as P\n } else {\n peerPayload = JSON.parse(record.data) as P\n }\n\n peers.push({ userId: record.userId, payload: peerPayload, lastSeen: record.lastSeen })\n }\n\n for (const cb of this.subscribers) {\n cb(peers)\n }\n } catch {\n // Poll failure is non-fatal\n }\n }\n}\n","import type {\n NoydbStore,\n DirtyEntry,\n Conflict,\n ConflictStrategy,\n CollectionConflictResolver,\n PushOptions,\n PullOptions,\n PushResult,\n PullResult,\n SyncStatus,\n EncryptedEnvelope,\n SyncMetadata,\n SyncTargetRole,\n ErasureEnforcement,\n} from '../../kernel/types.js'\nimport { NOYDB_SYNC_VERSION } from '../../kernel/types.js'\nimport { ConflictError } from '../../kernel/errors.js'\nimport type { NoydbEventEmitter } from '../../kernel/events.js'\nimport type { SyncPolicy } from '../../kernel/sync-policy.js'\nimport { SyncScheduler } from '../../kernel/sync-policy.js'\nimport { isTombstoneShape, isDeleteMarker } from '../../kernel/enclave/index.js'\n\n/** Sync engine: dirty tracking, push, pull, conflict resolution, scheduling. */\nexport class SyncEngine {\n private readonly local: NoydbStore\n private readonly remote: NoydbStore\n private readonly strategy: ConflictStrategy\n private readonly emitter: NoydbEventEmitter\n private readonly vault: string\n readonly role: SyncTargetRole\n readonly label: string | undefined\n\n private dirty: DirtyEntry[] = []\n private lastPush: string | null = null\n private lastPull: string | null = null\n private loaded = false\n private autoSyncInterval: ReturnType<typeof setInterval> | null = null\n private isOnline = true\n\n /** Sync scheduler. Manages push/pull timing. */\n readonly scheduler: SyncScheduler | null\n\n /** Per-collection conflict resolvers registered by Collection instances. */\n private readonly conflictResolvers = new Map<string, CollectionConflictResolver>()\n\n /**\n * Expands a collection-name filter to include its satellite pair partner(s)\n * (spec #591 convergence rule 5b) — wired from vault satellite declaration\n * via `setPairExpander`. `undefined` when no satellite has ever been\n * declared for this vault: identity behavior, zero change for non-satellite\n * users.\n */\n private pairExpander?: (names: readonly string[]) => readonly string[]\n\n /** #598: refreshes Collection in-memory views after a sync-applied local write. Wired by the vault at open. */\n private cacheInvalidator?: (collection: string, id: string) => Promise<void>\n\n /** Wire the Collection-cache invalidation hook (#598). Same injection pattern as `setPairExpander`. */\n setCacheInvalidator(fn: (collection: string, id: string) => Promise<void>): void {\n this.cacheInvalidator = fn\n }\n\n constructor(opts: {\n local: NoydbStore\n remote: NoydbStore\n vault: string\n strategy: ConflictStrategy\n emitter: NoydbEventEmitter\n syncPolicy?: SyncPolicy\n role?: SyncTargetRole\n label?: string\n }) {\n this.local = opts.local\n this.remote = opts.remote\n this.vault = opts.vault\n this.strategy = opts.strategy\n this.emitter = opts.emitter\n this.role = opts.role ?? 'sync-peer'\n this.label = opts.label\n\n // Create scheduler if a policy is provided\n const policy = opts.syncPolicy\n if (policy && policy.push.mode !== 'manual') {\n this.scheduler = new SyncScheduler(policy, {\n push: () => this.push().then(() => {}),\n pull: () => this.pull().then(() => {}),\n getDirtyCount: () => this.dirty.length,\n })\n } else {\n this.scheduler = null\n }\n }\n\n /** Start the sync scheduler. Called after vault is fully opened. */\n startScheduler(): void {\n this.scheduler?.start()\n }\n\n /** Stop the sync scheduler. Called on close. */\n stopScheduler(): void {\n this.scheduler?.stop()\n }\n\n /**\n * Register a per-collection conflict resolver.\n * Called by Collection when `conflictPolicy` is set.\n *\n * Pair-coupled (#591 rule 5b): registering for one satellite-pair member\n * registers the same resolver for both — the pair converges under one\n * resolution policy. Expands with whatever the pair-expander knows *at this\n * call*; resolvers registered before their pair existed are covered by\n * `remirrorPairResolvers` at pair-registration time.\n */\n registerConflictResolver(collection: string, resolver: CollectionConflictResolver): void {\n for (const n of this.pairExpander?.([collection]) ?? [collection]) this.conflictResolvers.set(n, resolver)\n }\n\n /**\n * Wire a satellite pair-expansion function (vault registration, #591 Task 11).\n * The closure re-reads the live `SatelliteRegistry` on every call, so pairs\n * declared after this is set are still picked up by `push`/`pull` filters.\n */\n setPairExpander(expander: (names: readonly string[]) => readonly string[]): void {\n this.pairExpander = expander\n }\n\n /**\n * Retroactively mirror per-collection conflict resolvers across a newly\n * registered satellite pair (#591 rule 5b) — covers the NORMAL declaration\n * order, where the base's `conflictPolicy` resolver was registered before\n * the satellite was declared (so the call-time expansion in\n * `registerConflictResolver` saw no pair yet). Tie-break: when BOTH members\n * already carry (different) resolvers, the FIRST name in `names` wins —\n * callers pass `[base, satellite]`, so the base's resolver is canonical.\n * Idempotent; a later explicit registration on either member still\n * overwrites both (last-wins, via the call-time expansion above).\n */\n remirrorPairResolvers(names: readonly string[]): void {\n const canonical = names.map(n => this.conflictResolvers.get(n)).find(r => r !== undefined)\n if (!canonical) return\n for (const n of names) this.conflictResolvers.set(n, canonical)\n }\n\n /** Record a local change for later push. */\n async trackChange(collection: string, id: string, action: 'put' | 'delete', version: number): Promise<void> {\n await this.ensureLoaded()\n\n // Deduplicate: if same collection+id already in dirty, update it\n const idx = this.dirty.findIndex(d => d.collection === collection && d.id === id)\n const entry: DirtyEntry = {\n vault: this.vault,\n collection,\n id,\n action,\n version,\n timestamp: new Date().toISOString(),\n }\n\n if (idx >= 0) {\n this.dirty[idx] = entry\n } else {\n this.dirty.push(entry)\n }\n\n await this.persistMeta()\n\n // Notify scheduler of the write (triggers on-change or debounce)\n this.scheduler?.notifyChange()\n }\n\n /** Remove a dirty entry (satellite fan-out revert cleanup — spec #591). */\n async removeDirty(collection: string, id: string): Promise<void> {\n const before = this.dirty.length\n this.dirty = this.dirty.filter(d => !(d.collection === collection && d.id === id))\n if (this.dirty.length !== before) await this.persistMeta()\n }\n\n /** Push dirty records to remote adapter. Accepts optional `PushOptions` for partial sync. */\n async push(options?: PushOptions): Promise<PushResult> {\n await this.ensureLoaded()\n\n let pushed = 0\n const conflicts: Conflict[] = []\n const erasures: ErasureEnforcement[] = []\n const errors: Error[] = []\n const completed: number[] = []\n\n // Partial sync: expand the filter to cover satellite pair partners (#591 rule 5b)\n const filter = options?.collections ? new Set(this.pairExpander?.(options.collections) ?? options.collections) : null\n\n for (let i = 0; i < this.dirty.length; i++) {\n const entry = this.dirty[i]!\n\n // Partial sync: skip collections not in the filter\n if (filter && !filter.has(entry.collection)) {\n continue\n }\n\n try {\n if (entry.action === 'delete') {\n await this.remote.delete(this.vault, entry.collection, entry.id)\n completed.push(i)\n pushed++\n } else {\n const envelope = await this.local.get(this.vault, entry.collection, entry.id)\n if (!envelope) {\n // Record was deleted locally after being marked dirty\n completed.push(i)\n continue\n }\n\n if (isTombstoneShape(envelope)) {\n // #590: a tombstone push is an erasure assertion — unconditional,\n // no CAS, no conflict resolution. Erasure always wins.\n await this.remote.put(this.vault, entry.collection, entry.id, envelope)\n completed.push(i)\n pushed++\n continue\n }\n\n try {\n await this.remote.put(\n this.vault,\n entry.collection,\n entry.id,\n envelope,\n entry.version - 1,\n )\n completed.push(i)\n pushed++\n } catch (err) {\n if (err instanceof ConflictError) {\n const remoteEnvelope = await this.remote.get(this.vault, entry.collection, entry.id)\n if (remoteEnvelope) {\n if (isTombstoneShape(remoteEnvelope)) {\n // #590: remote already shredded this record — enforce locally,\n // never resolve. Resolvers must not overrule an erasure.\n await this.applyRemote(entry.collection, entry.id, remoteEnvelope)\n erasures.push(this.reportErasure(entry.collection, entry.id, remoteEnvelope, envelope, 'push'))\n completed.push(i)\n } else if (\n remoteEnvelope._v === envelope._v &&\n isDeleteMarker(remoteEnvelope) !== isDeleteMarker(envelope) &&\n !this.conflictResolvers.get(entry.collection)\n ) {\n // #589: a same-_v delete-vs-edit tie on the push channel. handleConflict's db-level\n // 'version' default would resolve it to local-wins; the tie rule consults ONLY the\n // per-collection resolver, else delete-wins. (When a per-collection resolver IS set,\n // fall through to handleConflict, which already honors it — incl. the merged case.)\n if (isDeleteMarker(remoteEnvelope)) {\n // remote already deleted → converge locally, drop our (edit) push\n await this.applyRemote(entry.collection, entry.id, remoteEnvelope)\n completed.push(i)\n } else {\n // our local is the marker → force the delete onto the remote (unconditional put)\n await this.remote.put(this.vault, entry.collection, entry.id, envelope)\n completed.push(i)\n pushed++\n }\n } else {\n const { handled, conflict } = await this.handleConflict(\n entry.collection,\n entry.id,\n envelope,\n remoteEnvelope,\n 'push',\n )\n conflicts.push(conflict)\n if (handled === 'local') {\n await this.remote.put(this.vault, entry.collection, entry.id, conflict.local)\n completed.push(i)\n pushed++\n } else if (handled === 'remote') {\n await this.applyRemote(entry.collection, entry.id, conflict.remote)\n completed.push(i)\n } else if (handled === 'merged' && conflict.local !== envelope) {\n // Merged envelope is stored in conflict.local (the winner)\n const merged = conflict.local\n await this.remote.put(this.vault, entry.collection, entry.id, merged)\n await this.applyRemote(entry.collection, entry.id, merged)\n completed.push(i)\n pushed++\n }\n // handled === 'deferred': leave in dirty log\n }\n }\n } else {\n throw err\n }\n }\n }\n } catch (err) {\n errors.push(err instanceof Error ? err : new Error(String(err)))\n }\n }\n\n // Remove completed entries from dirty log (reverse order to preserve indices)\n for (const i of completed.sort((a, b) => b - a)) {\n this.dirty.splice(i, 1)\n }\n\n this.lastPush = new Date().toISOString()\n await this.persistMeta()\n\n const result: PushResult = { pushed, conflicts, errors, erasures }\n this.emitter.emit('sync:push', result)\n return result\n }\n\n /** Pull remote records to local adapter. Accepts optional `PullOptions` for partial sync. */\n async pull(options?: PullOptions): Promise<PullResult> {\n await this.ensureLoaded()\n\n let pulled = 0\n const conflicts: Conflict[] = []\n const erasures: ErasureEnforcement[] = []\n const errors: Error[] = []\n\n // Partial sync: expand the filter to cover satellite pair partners (#591 rule 5b)\n const filter = options?.collections ? new Set(this.pairExpander?.(options.collections) ?? options.collections) : null\n\n try {\n const remoteSnapshot = await this.remote.loadAll(this.vault)\n\n for (const [collName, records] of Object.entries(remoteSnapshot)) {\n // Partial sync: skip collections not in the filter\n if (filter && !filter.has(collName)) {\n continue\n }\n\n for (const [id, remoteEnvelope] of Object.entries(records)) {\n // Partial sync: modifiedSince filter — arriving tombstones are exempt (#590):\n // an erasure must never be skipped by partial sync.\n if (\n options?.modifiedSince &&\n remoteEnvelope._ts <= options.modifiedSince &&\n !isTombstoneShape(remoteEnvelope) &&\n !isDeleteMarker(remoteEnvelope)\n ) {\n continue\n }\n\n try {\n const localEnvelope = await this.local.get(this.vault, collName, id)\n const remoteIsTombstone = isTombstoneShape(remoteEnvelope)\n\n if (!localEnvelope) {\n // New record from remote (tombstones included — durable erasure evidence)\n await this.applyRemote(collName, id, remoteEnvelope)\n pulled++\n } else if (isTombstoneShape(localEnvelope)) {\n if (remoteIsTombstone) {\n // Both shredded — keep the higher version counter\n if (remoteEnvelope._v > localEnvelope._v) await this.applyRemote(collName, id, remoteEnvelope)\n } else {\n // Terminal rule (#590): a tombstone is never overwritten by a live\n // envelope, regardless of _v. Re-assert the shred outward instead.\n erasures.push(await this.reassertTombstone(collName, id, localEnvelope, remoteEnvelope))\n }\n } else if (remoteIsTombstone) {\n // Terminal rule (#590): an arriving tombstone wins over any local live\n // envelope — even a newer, dirty one. Resolvers are never consulted.\n const wasDirty = this.dirty.some(d => d.collection === collName && d.id === id)\n await this.applyRemote(collName, id, remoteEnvelope)\n this.dirty = this.dirty.filter(d => !(d.collection === collName && d.id === id))\n pulled++\n if (wasDirty) erasures.push(this.reportErasure(collName, id, remoteEnvelope, localEnvelope, 'pull'))\n } else if (\n remoteEnvelope._v === localEnvelope._v &&\n isDeleteMarker(remoteEnvelope) !== isDeleteMarker(localEnvelope)\n ) {\n // #589: true concurrent delete-vs-edit at the SAME version. Version order\n // can't break the tie. A per-collection resolver decides if one is set;\n // otherwise DELETE wins (the db-level 'version' default is deliberately NOT\n // consulted — it would resolve a tie to local-wins).\n const resolver = this.conflictResolvers.get(collName)\n if (resolver) {\n const winner = await resolver(id, localEnvelope, remoteEnvelope)\n if (winner !== localEnvelope && winner !== null) {\n // #589 (review): a novel/merged winner (neither side verbatim) must also be\n // pushed to remote — mirrors handleConflict's 'merged' handling — so both\n // sides converge instead of local applying it while remote keeps its old copy.\n if (winner !== remoteEnvelope) {\n await this.remote.put(this.vault, collName, id, winner)\n }\n await this.applyRemote(collName, id, winner)\n this.dirty = this.dirty.filter(d => !(d.collection === collName && d.id === id))\n pulled++\n }\n // winner === localEnvelope or null → keep local (its dirty entry, if any, pushes out)\n } else if (isDeleteMarker(remoteEnvelope)) {\n // no resolver → delete wins; the incoming marker is the delete\n await this.applyRemote(collName, id, remoteEnvelope)\n this.dirty = this.dirty.filter(d => !(d.collection === collName && d.id === id))\n pulled++\n }\n // no resolver and LOCAL is the marker → keep local marker; its dirty 'put' pushes it outward\n } else if (remoteEnvelope._v > localEnvelope._v) {\n // Remote is newer — check if we have a dirty entry for this\n const isDirty = this.dirty.some(d => d.collection === collName && d.id === id)\n if (isDirty) {\n // Both changed — conflict\n const { handled, conflict } = await this.handleConflict(\n collName,\n id,\n localEnvelope,\n remoteEnvelope,\n 'pull',\n )\n conflicts.push(conflict)\n if (handled === 'remote') {\n await this.applyRemote(collName, id, conflict.remote)\n this.dirty = this.dirty.filter(d => !(d.collection === collName && d.id === id))\n pulled++\n } else if (handled === 'merged' && conflict.local !== localEnvelope) {\n const merged = conflict.local\n await this.applyRemote(collName, id, merged)\n this.dirty = this.dirty.filter(d => !(d.collection === collName && d.id === id))\n pulled++\n }\n // 'local' or 'deferred': push handles it\n } else {\n // Remote is newer, no local changes — update\n await this.applyRemote(collName, id, remoteEnvelope)\n pulled++\n }\n }\n // Same version or local is newer — skip (push will handle)\n } catch (err) {\n errors.push(err instanceof Error ? err : new Error(String(err)))\n }\n }\n }\n } catch (err) {\n errors.push(err instanceof Error ? err : new Error(String(err)))\n }\n\n this.lastPull = new Date().toISOString()\n await this.persistMeta()\n\n const result: PullResult = { pulled, conflicts, errors, erasures }\n this.emitter.emit('sync:pull', result)\n return result\n }\n\n /** Bidirectional sync: pull then push. */\n async sync(options?: { push?: PushOptions; pull?: PullOptions }): Promise<{ pull: PullResult; push: PushResult }> {\n const pullResult = await this.pull(options?.pull)\n const pushResult = await this.push(options?.push)\n return { pull: pullResult, push: pushResult }\n }\n\n /**\n * Push a specific subset of dirty entries (for sync transactions, ).\n * Entries are matched by collection+id from the dirty log; matched entries\n * are removed from the dirty log on success.\n */\n async pushFiltered(predicate: (entry: DirtyEntry) => boolean): Promise<PushResult> {\n await this.ensureLoaded()\n\n let pushed = 0\n const conflicts: Conflict[] = []\n const erasures: ErasureEnforcement[] = []\n const errors: Error[] = []\n const completed: number[] = []\n\n for (let i = 0; i < this.dirty.length; i++) {\n const entry = this.dirty[i]!\n if (!predicate(entry)) continue\n\n try {\n if (entry.action === 'delete') {\n await this.remote.delete(this.vault, entry.collection, entry.id)\n completed.push(i)\n pushed++\n } else {\n const envelope = await this.local.get(this.vault, entry.collection, entry.id)\n if (!envelope) {\n completed.push(i)\n continue\n }\n\n if (isTombstoneShape(envelope)) {\n // #590: a tombstone push is an erasure assertion — unconditional,\n // no CAS, no conflict resolution. Erasure always wins.\n await this.remote.put(this.vault, entry.collection, entry.id, envelope)\n completed.push(i)\n pushed++\n continue\n }\n\n try {\n await this.remote.put(\n this.vault,\n entry.collection,\n entry.id,\n envelope,\n entry.version - 1,\n )\n completed.push(i)\n pushed++\n } catch (err) {\n if (err instanceof ConflictError) {\n const remoteEnvelope = await this.remote.get(this.vault, entry.collection, entry.id)\n if (remoteEnvelope) {\n if (isTombstoneShape(remoteEnvelope)) {\n // #590: remote already shredded this record — enforce locally,\n // never resolve. Resolvers must not overrule an erasure.\n await this.applyRemote(entry.collection, entry.id, remoteEnvelope)\n erasures.push(this.reportErasure(entry.collection, entry.id, remoteEnvelope, envelope, 'push'))\n completed.push(i)\n } else if (\n remoteEnvelope._v === envelope._v &&\n isDeleteMarker(remoteEnvelope) !== isDeleteMarker(envelope) &&\n !this.conflictResolvers.get(entry.collection)\n ) {\n // #589: a same-_v delete-vs-edit tie on the push channel. handleConflict's db-level\n // 'version' default would resolve it to local-wins; the tie rule consults ONLY the\n // per-collection resolver, else delete-wins. (When a per-collection resolver IS set,\n // fall through to handleConflict, which already honors it — incl. the merged case.)\n if (isDeleteMarker(remoteEnvelope)) {\n // remote already deleted → converge locally, drop our (edit) push\n await this.applyRemote(entry.collection, entry.id, remoteEnvelope)\n completed.push(i)\n } else {\n // our local is the marker → force the delete onto the remote (unconditional put)\n await this.remote.put(this.vault, entry.collection, entry.id, envelope)\n completed.push(i)\n pushed++\n }\n } else {\n const { handled, conflict } = await this.handleConflict(\n entry.collection,\n entry.id,\n envelope,\n remoteEnvelope,\n 'push',\n )\n conflicts.push(conflict)\n if (handled === 'local') {\n await this.remote.put(this.vault, entry.collection, entry.id, conflict.local)\n completed.push(i)\n pushed++\n } else if (handled === 'remote') {\n await this.applyRemote(entry.collection, entry.id, conflict.remote)\n completed.push(i)\n } else if (handled === 'merged' && conflict.local !== envelope) {\n const merged = conflict.local\n await this.remote.put(this.vault, entry.collection, entry.id, merged)\n await this.applyRemote(entry.collection, entry.id, merged)\n completed.push(i)\n pushed++\n }\n }\n }\n } else {\n throw err\n }\n }\n }\n } catch (err) {\n errors.push(err instanceof Error ? err : new Error(String(err)))\n }\n }\n\n for (const i of completed.sort((a, b) => b - a)) {\n this.dirty.splice(i, 1)\n }\n\n this.lastPush = new Date().toISOString()\n await this.persistMeta()\n\n const result: PushResult = { pushed, conflicts, errors, erasures }\n this.emitter.emit('sync:push', result)\n return result\n }\n\n /** Get current sync status. */\n status(): SyncStatus {\n return {\n dirty: this.dirty.length,\n lastPush: this.lastPush,\n lastPull: this.lastPull,\n online: this.isOnline,\n }\n }\n\n // ─── Auto-Sync ───────────────────────────────────────────────────\n\n /** Start auto-sync: listen for online/offline events, optional periodic sync. */\n startAutoSync(intervalMs?: number): void {\n // Online/offline detection\n if (typeof globalThis.addEventListener === 'function') {\n globalThis.addEventListener('online', this.handleOnline)\n globalThis.addEventListener('offline', this.handleOffline)\n }\n\n // Periodic sync\n if (intervalMs && intervalMs > 0) {\n this.autoSyncInterval = setInterval(() => {\n if (this.isOnline) {\n void this.sync()\n }\n }, intervalMs)\n }\n }\n\n /** Stop auto-sync and scheduler. */\n stopAutoSync(): void {\n this.stopScheduler()\n if (typeof globalThis.removeEventListener === 'function') {\n globalThis.removeEventListener('online', this.handleOnline)\n globalThis.removeEventListener('offline', this.handleOffline)\n }\n if (this.autoSyncInterval) {\n clearInterval(this.autoSyncInterval)\n this.autoSyncInterval = null\n }\n }\n\n private handleOnline = (): void => {\n this.isOnline = true\n this.emitter.emit('sync:online', undefined as never)\n void this.sync()\n }\n\n private handleOffline = (): void => {\n this.isOnline = false\n this.emitter.emit('sync:offline', undefined as never)\n }\n\n /** Apply an envelope to the local store and refresh in-memory views (#598). */\n private async applyRemote(collection: string, id: string, envelope: EncryptedEnvelope): Promise<void> {\n await this.local.put(this.vault, collection, id, envelope)\n await this.cacheInvalidator?.(collection, id)\n }\n\n /** Record + emit a tombstone enforcement (#590). */\n private reportErasure(\n collection: string, id: string,\n tombstone: EncryptedEnvelope, suppressed: EncryptedEnvelope,\n direction: 'pull' | 'push',\n ): ErasureEnforcement {\n const enforcement: ErasureEnforcement = { vault: this.vault, collection, id, tombstone, suppressed, direction }\n this.emitter.emit('sync:erasure', enforcement)\n return enforcement\n }\n\n /**\n * Re-assert a local tombstone over a live remote envelope (#590): the shred\n * wins in both directions, regardless of `_v`. Bumps the tombstone to the\n * suppressed envelope's `_v` when higher so per-key version counters stay\n * monotonic on every store; `_by` (the shredding actor) is preserved.\n */\n private async reassertTombstone(\n collection: string, id: string,\n tombstone: EncryptedEnvelope, suppressedRemote: EncryptedEnvelope,\n ): Promise<ErasureEnforcement> {\n let winner = tombstone\n if (suppressedRemote._v > tombstone._v) {\n winner = { ...tombstone, _v: suppressedRemote._v, _ts: new Date().toISOString() }\n await this.applyRemote(collection, id, winner)\n }\n await this.remote.put(this.vault, collection, id, winner)\n // The re-assert already delivered the tombstone remotely — drop any dirty\n // entry so the following push doesn't redundantly re-put it.\n this.dirty = this.dirty.filter(d => !(d.collection === collection && d.id === id))\n return this.reportErasure(collection, id, winner, suppressedRemote, 'pull')\n }\n\n /**\n * Resolve a conflict, checking per-collection resolvers first,\n * then falling back to the db-level `ConflictStrategy`.\n *\n * Returns the resolved `Conflict` object (possibly with `resolve` set for\n * manual mode) and a `handled` discriminant:\n * - `'local'` — keep the local envelope; push it to remote.\n * - `'remote'` — keep the remote envelope; update local.\n * - `'merged'` — a custom merge fn produced a new envelope stored as `conflict.local`.\n * - `'deferred'` — manual mode, resolve was not called synchronously.\n */\n private async handleConflict(\n collection: string,\n id: string,\n local: EncryptedEnvelope,\n remote: EncryptedEnvelope,\n _phase: 'push' | 'pull',\n ): Promise<{ handled: 'local' | 'remote' | 'merged' | 'deferred'; conflict: Conflict }> {\n const resolver = this.conflictResolvers.get(collection)\n\n if (resolver) {\n // Per-collection resolver is responsible for emitting sync:conflict\n // (manual policy emits with a resolve callback; LWW/FWW/custom are silent).\n const winner = await resolver(id, local, remote)\n const base: Conflict = {\n vault: this.vault,\n collection,\n id,\n local,\n remote,\n localVersion: local._v,\n remoteVersion: remote._v,\n }\n if (winner === null) return { handled: 'deferred', conflict: base }\n if (winner === local) return { handled: 'local', conflict: base }\n if (winner === remote) return { handled: 'remote', conflict: base }\n // Custom merge fn produced a new envelope — store as conflict.local for the caller\n return {\n handled: 'merged',\n conflict: { ...base, local: winner, localVersion: winner._v },\n }\n }\n\n // Fall back to db-level strategy — emit once\n const baseConflict: Conflict = {\n vault: this.vault,\n collection,\n id,\n local,\n remote,\n localVersion: local._v,\n remoteVersion: remote._v,\n }\n this.emitter.emit('sync:conflict', baseConflict)\n const side = this.legacyResolve(baseConflict)\n return { handled: side, conflict: baseConflict }\n }\n\n /** DB-level ConflictStrategy resolution (legacy, kept for backward compat). */\n private legacyResolve(conflict: Conflict): 'local' | 'remote' {\n if (typeof this.strategy === 'function') {\n return this.strategy(conflict)\n }\n switch (this.strategy) {\n case 'local-wins': return 'local'\n case 'remote-wins': return 'remote'\n case 'version':\n default:\n return conflict.localVersion >= conflict.remoteVersion ? 'local' : 'remote'\n }\n }\n\n // ─── Persistence ─────────────────────────────────────────────────\n\n private async ensureLoaded(): Promise<void> {\n if (this.loaded) return\n\n const envelope = await this.local.get(this.vault, '_sync', 'meta')\n if (envelope) {\n const meta = JSON.parse(envelope._data) as SyncMetadata\n this.dirty = [...meta.dirty]\n this.lastPush = meta.last_push\n this.lastPull = meta.last_pull\n }\n\n this.loaded = true\n }\n\n private async persistMeta(): Promise<void> {\n const meta: SyncMetadata = {\n _noydb_sync: NOYDB_SYNC_VERSION,\n last_push: this.lastPush,\n last_pull: this.lastPull,\n dirty: this.dirty,\n }\n\n const envelope: EncryptedEnvelope = {\n _noydb: 1,\n _v: 1,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify(meta),\n }\n\n await this.local.put(this.vault, '_sync', 'meta', envelope)\n }\n}\n","import type { SyncTransactionResult } from '../../kernel/types.js'\nimport type { SyncEngine } from './sync.js'\nimport type { Vault } from '../../kernel/vault.js'\n\ninterface TxOp {\n readonly type: 'put' | 'delete'\n readonly collection: string\n readonly id: string\n readonly record?: unknown\n}\n\n/**\n * Sync transaction.\n *\n * Stages local writes and then pushes only those records to remote in a\n * single batch. If any record conflicts during the push, the result\n * carries `status: 'conflict'` — no automatic rollback is performed;\n * the caller handles conflict resolution.\n *\n * Obtain via `db.transaction(compartmentName)`.\n */\nexport class SyncTransaction {\n private readonly comp: Vault\n private readonly engine: SyncEngine\n private readonly ops: TxOp[] = []\n\n /** @internal — constructed by `Noydb.transaction()` */\n constructor(comp: Vault, engine: SyncEngine) {\n this.comp = comp\n this.engine = engine\n }\n\n /** Stage a record write. Does not write to any adapter until `commit()`. */\n put(collection: string, id: string, record: unknown): this {\n this.ops.push({ type: 'put', collection, id, record })\n return this\n }\n\n /** Stage a record delete. Does not write to any adapter until `commit()`. */\n delete(collection: string, id: string): this {\n this.ops.push({ type: 'delete', collection, id })\n return this\n }\n\n /**\n * Commit the transaction.\n *\n * Phase 1 — writes all staged operations to the local adapter via the\n * collection layer (encryption + dirty-log tracking).\n *\n * Phase 2 — pushes only the records that were written in this\n * transaction to the remote adapter. Existing dirty entries from\n * outside this transaction are not affected.\n *\n * If any record conflicts during the push, `status` is `'conflict'`\n * and `conflicts` lists the affected records. No automatic rollback is\n * performed.\n */\n async commit(): Promise<SyncTransactionResult> {\n // Phase 1: write all staged ops to local via collection layer\n for (const op of this.ops) {\n if (op.type === 'put') {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n await (this.comp.collection<any>(op.collection)).put(op.id, op.record as any)\n } else {\n await this.comp.collection(op.collection).delete(op.id)\n }\n }\n\n // Phase 2: push only the records from this transaction\n const opSet = new Set<string>()\n for (const op of this.ops) {\n opSet.add(`${op.collection}::${op.id}`)\n }\n\n const pushResult = await this.engine.pushFiltered(\n (entry) => opSet.has(`${entry.collection}::${entry.id}`),\n )\n\n return {\n status: pushResult.conflicts.length > 0 ? 'conflict' : 'committed',\n pushed: pushResult.pushed,\n conflicts: pushResult.conflicts,\n ...(pushResult.erasures?.length ? { erasures: pushResult.erasures } : {}),\n }\n }\n}\n"],"mappings":"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","names":[]}
|