@noy-db/hub 0.3.0-pre.3 → 0.3.0-pre.4
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 -2
- package/dist/adapter/index.js +1 -1
- package/dist/aggregate/index.d.ts +3 -3
- package/dist/aggregate/index.js +4 -4
- package/dist/api-JOXUNSKP.js +20 -0
- package/dist/as/index.d.ts +4 -4
- package/dist/as/index.js +12 -9
- package/dist/at/index.d.ts +2 -2
- package/dist/attestation/index.d.ts +3 -3
- package/dist/attestation/index.js +21 -18
- package/dist/attestation/index.js.map +1 -1
- package/dist/{backup-NGRJ46SH.js → backup-MOB27UUN.js} +12 -9
- package/dist/{backup-NGRJ46SH.js.map → backup-MOB27UUN.js.map} +1 -1
- package/dist/blobs/index.d.ts +4 -4
- package/dist/blobs/index.js +11 -8
- package/dist/blobs/index.js.map +1 -1
- package/dist/bundle/index.d.ts +5 -5
- package/dist/bundle/index.js +21 -18
- package/dist/bundle/index.js.map +1 -1
- package/dist/{bundle-B-P3_Jvb.d.ts → bundle-Bs13EZtX.d.ts} +1 -1
- package/dist/by/index.js +2 -2
- package/dist/cargo/index.d.ts +4 -4
- package/dist/cargo/index.js +23 -20
- package/dist/{chunk-VBYVKOMJ.js → chunk-3QRQOBS7.js} +3 -3
- package/dist/{chunk-JFYIHLU3.js → chunk-3Y4QLJ6K.js} +2 -2
- package/dist/{chunk-BZIWKN74.js → chunk-43ISXURO.js} +2 -2
- package/dist/{chunk-7IP75FP2.js → chunk-4K3MQ5S3.js} +2 -2
- package/dist/{chunk-6DP5HBQD.js → chunk-5CY35H55.js} +9 -9
- package/dist/chunk-5EWYUR5P.js +37 -0
- package/dist/chunk-5EWYUR5P.js.map +1 -0
- package/dist/{chunk-2KSPDSWI.js → chunk-5MRE3C4Q.js} +5 -5
- package/dist/{chunk-5ZPWVNL3.js → chunk-5W7AOFWA.js} +2 -2
- package/dist/{chunk-PUNJAEY6.js → chunk-6HNKCKFZ.js} +7 -7
- package/dist/{chunk-PJDK2PPQ.js → chunk-6RXPSMKR.js} +3 -3
- package/dist/{chunk-Z44OY24N.js → chunk-7J7JRYXW.js} +9 -5
- package/dist/chunk-7J7JRYXW.js.map +1 -0
- package/dist/{chunk-MCJAUQGE.js → chunk-A5DYVMDR.js} +3 -3
- package/dist/{chunk-MIOLJG2R.js → chunk-AF5ZRTZ4.js} +5 -2
- package/dist/chunk-AF5ZRTZ4.js.map +1 -0
- package/dist/{chunk-TICO2I2O.js → chunk-BKGIWGCX.js} +2 -2
- package/dist/{chunk-O5DNEXQC.js → chunk-BMQOH7MM.js} +2 -2
- package/dist/{chunk-U6OM4E67.js → chunk-CPXPHQGX.js} +6 -6
- package/dist/{chunk-3UDPDRUC.js → chunk-EWR7HL3K.js} +4 -4
- package/dist/{chunk-CVXLJIAV.js → chunk-FH52CDEW.js} +5 -5
- package/dist/chunk-G4MGYGSS.js +130 -0
- package/dist/chunk-G4MGYGSS.js.map +1 -0
- package/dist/{chunk-FELHYKIO.js → chunk-G7RDYYTE.js} +2 -2
- package/dist/{chunk-N2FP26NH.js → chunk-GBTUANXF.js} +16 -297
- package/dist/chunk-GBTUANXF.js.map +1 -0
- package/dist/{chunk-UP6EMMDI.js → chunk-GOUNIHFA.js} +10 -10
- package/dist/{chunk-DHIN36UC.js → chunk-GQOT6QJR.js} +8 -8
- package/dist/{chunk-DGUR3CC4.js → chunk-GXGK22QU.js} +2 -2
- package/dist/{chunk-YTIAXSUE.js → chunk-GZZL5R73.js} +4 -4
- package/dist/{chunk-E4YJUNPU.js → chunk-H5XRIJX3.js} +7 -7
- package/dist/{chunk-DA34OEZU.js → chunk-H7RYPVMJ.js} +2 -2
- package/dist/{chunk-2ZORB5RW.js → chunk-HBKDR7R3.js} +3 -3
- package/dist/{chunk-BV7KNFJY.js → chunk-HLUKZ36Q.js} +9 -9
- package/dist/{chunk-ZNI3RTFV.js → chunk-HMXLN43G.js} +2 -2
- package/dist/{chunk-BYW7EU5L.js → chunk-HY6ZGGEC.js} +2 -2
- package/dist/{chunk-OVIVYAQL.js → chunk-K5P46KB3.js} +25 -10
- package/dist/chunk-K5P46KB3.js.map +1 -0
- package/dist/chunk-KYY7L72M.js +106 -0
- package/dist/chunk-KYY7L72M.js.map +1 -0
- package/dist/{chunk-5Z3RNFJC.js → chunk-LIP6JWYK.js} +3 -3
- package/dist/{chunk-OCDUQUAP.js → chunk-LN22XH4B.js} +1 -1
- package/dist/chunk-LN22XH4B.js.map +1 -0
- package/dist/chunk-LP7BEXCT.js +32 -0
- package/dist/chunk-LP7BEXCT.js.map +1 -0
- package/dist/{chunk-IJW6K6UX.js → chunk-LPRPVCNY.js} +5 -5
- package/dist/{chunk-6EVZXETK.js → chunk-M66NAZA4.js} +3 -3
- package/dist/{chunk-HGQG3VIP.js → chunk-N5YVZHCB.js} +2 -2
- package/dist/{chunk-IWVWB7BZ.js → chunk-NB47TXGP.js} +12 -12
- package/dist/chunk-NO272T7Q.js +194 -0
- package/dist/chunk-NO272T7Q.js.map +1 -0
- package/dist/{chunk-6OQ7NKMZ.js → chunk-O2DB4C3M.js} +6 -6
- package/dist/{chunk-LHYQE3BP.js → chunk-OFBFAVLI.js} +6 -6
- package/dist/{chunk-ZFME46HJ.js → chunk-OPTFANOX.js} +3 -3
- package/dist/chunk-OPTFANOX.js.map +1 -0
- package/dist/{chunk-5EZAJEES.js → chunk-OVO2RZAE.js} +81 -27
- package/dist/chunk-OVO2RZAE.js.map +1 -0
- package/dist/{chunk-6I2YPLAG.js → chunk-P27Z66EB.js} +7 -7
- package/dist/{chunk-7MHVHGQZ.js → chunk-P2LDXRGP.js} +2 -2
- package/dist/chunk-P3NGV5RV.js +41 -0
- package/dist/chunk-P3NGV5RV.js.map +1 -0
- package/dist/{chunk-YN66UOXT.js → chunk-P3V7JTB6.js} +25 -9
- package/dist/{chunk-YN66UOXT.js.map → chunk-P3V7JTB6.js.map} +1 -1
- package/dist/{chunk-NPVICKZE.js → chunk-P5WXDYMD.js} +8 -197
- package/dist/chunk-P5WXDYMD.js.map +1 -0
- package/dist/{chunk-CPAROOKP.js → chunk-PGFY7IRW.js} +12 -41
- package/dist/chunk-PGFY7IRW.js.map +1 -0
- package/dist/{chunk-6RASM2J4.js → chunk-QKVILR7N.js} +2 -2
- package/dist/{chunk-LKBLAUOB.js → chunk-QNTEDPWP.js} +3 -3
- package/dist/{chunk-P6UXDALK.js → chunk-RDHAGBEB.js} +3 -3
- package/dist/{chunk-XRE4JOEO.js → chunk-RTS23VIJ.js} +2 -2
- package/dist/{chunk-PYWW4KLO.js → chunk-SEQ2JI3Y.js} +9 -9
- package/dist/{chunk-VHEEU4ZO.js → chunk-SW56GSYC.js} +2 -2
- package/dist/{chunk-46YGCU6Z.js → chunk-T2EGAXPM.js} +2 -2
- package/dist/{chunk-LES2Z7B4.js → chunk-T45LAT2Y.js} +2 -2
- package/dist/{chunk-4NMFWOS2.js → chunk-T65UZXR6.js} +3 -3
- package/dist/{chunk-2N4MUSF3.js → chunk-TB5RNNJ4.js} +7 -7
- package/dist/chunk-TCIUO62Z.js +29 -0
- package/dist/chunk-TCIUO62Z.js.map +1 -0
- package/dist/chunk-THNJ3IKU.js +17 -0
- package/dist/chunk-THNJ3IKU.js.map +1 -0
- package/dist/{chunk-L4IRFTIF.js → chunk-TLJOJBZD.js} +3 -23
- package/dist/chunk-TLJOJBZD.js.map +1 -0
- package/dist/chunk-TQ3REHVF.js +95 -0
- package/dist/chunk-TQ3REHVF.js.map +1 -0
- package/dist/{chunk-NIA5VQ7G.js → chunk-TVRQ3RYH.js} +28 -9
- package/dist/chunk-TVRQ3RYH.js.map +1 -0
- package/dist/{chunk-6CNLU4TO.js → chunk-TYGW5TOR.js} +7 -7
- package/dist/{chunk-BLZRNHMG.js → chunk-U23JUUPX.js} +8 -1
- package/dist/{chunk-BLZRNHMG.js.map → chunk-U23JUUPX.js.map} +1 -1
- package/dist/{chunk-U5DWHU3S.js → chunk-U24XHXNK.js} +2 -2
- package/dist/chunk-ULIHDPKL.js +94 -0
- package/dist/chunk-ULIHDPKL.js.map +1 -0
- package/dist/chunk-UOEWDCUX.js +69 -0
- package/dist/chunk-UOEWDCUX.js.map +1 -0
- package/dist/{chunk-5A6TC3RQ.js → chunk-VAWY44JW.js} +8 -8
- package/dist/{chunk-5A6TC3RQ.js.map → chunk-VAWY44JW.js.map} +1 -1
- package/dist/{chunk-NTHKOFVL.js → chunk-VFRNWE5P.js} +52 -8
- package/dist/chunk-VFRNWE5P.js.map +1 -0
- package/dist/{chunk-4DZGZ7II.js → chunk-VMJ5URU7.js} +8 -8
- package/dist/chunk-VMJ5URU7.js.map +1 -0
- package/dist/{chunk-YDP2SA5K.js → chunk-VPCMJ5Z4.js} +5 -5
- package/dist/{chunk-ZAWD6O46.js → chunk-VRPBEOWU.js} +2 -2
- package/dist/{chunk-AY4P6PHN.js → chunk-VWGCJUTV.js} +2 -2
- package/dist/{chunk-IIK7W3AN.js → chunk-XJR3COJO.js} +5 -5
- package/dist/{chunk-ZD4D7UM6.js → chunk-XYYW64LB.js} +2 -2
- package/dist/{chunk-4NZCZUGL.js → chunk-YFF2RP3X.js} +2 -2
- package/dist/{chunk-WVIIJPTJ.js → chunk-Z3FZC5GV.js} +7 -7
- package/dist/{chunk-55HDKLI3.js → chunk-Z5PIUYNB.js} +8 -8
- package/dist/{chunk-ZRDYQZYH.js → chunk-Z7KI4WHR.js} +2 -2
- package/dist/chunk-ZKF6HH7V.js +120 -0
- package/dist/chunk-ZKF6HH7V.js.map +1 -0
- package/dist/{chunk-P6DN5QU7.js → chunk-ZKYMKF2S.js} +8 -8
- package/dist/{chunk-ADBMJDBW.js → chunk-ZPHDGUZZ.js} +445 -1077
- package/dist/chunk-ZPHDGUZZ.js.map +1 -0
- package/dist/{chunk-NMRKAGHE.js → chunk-ZROMNP7Q.js} +2 -2
- package/dist/classified/index.d.ts +4 -4
- package/dist/classified/index.js +3 -3
- package/dist/collection-facade-MKEBZDWW.js +39 -0
- package/dist/computed-BMMEFRFJ.js +11 -0
- package/dist/config-drift-KGM7ZRW4.js +24 -0
- package/dist/config-drift-KGM7ZRW4.js.map +1 -0
- package/dist/consent/index.d.ts +3 -3
- package/dist/consent/index.js +10 -7
- package/dist/consent/index.js.map +1 -1
- package/dist/crdt/index.d.ts +3 -3
- package/dist/delegation-BQNIEHZE.js +25 -0
- package/dist/derivations/index.d.ts +4 -4
- package/dist/derivations/index.js +12 -9
- package/dist/describe/index.d.ts +1 -1
- package/dist/{describe-0tiXAjoO.d.ts → describe-C6iHNlqJ.d.ts} +9 -0
- package/dist/{dev-unlock-B_s9DUWa.d.ts → dev-unlock-Cqd5Xv5J.d.ts} +1 -1
- package/dist/{enclave-IS7HZSQ7.js → enclave-YN6223OG.js} +21 -8
- package/dist/executor-CBTNU5VZ.js +9 -0
- package/dist/executor-DJHNSTIR.js +9 -0
- package/dist/executor-FGISLQIN.js +21 -0
- package/dist/export-accessible-P7SLRLK3.js +23 -0
- package/dist/extract-partition-RNVSFHAB.js +36 -0
- package/dist/{fanout-sidecar-DDKRG2MX.js → fanout-sidecar-WS22Q5WH.js} +12 -9
- package/dist/{fanout-sidecar-DDKRG2MX.js.map → fanout-sidecar-WS22Q5WH.js.map} +1 -1
- package/dist/fence-watcher-DHQ775JB.js +69 -0
- package/dist/fence-watcher-DHQ775JB.js.map +1 -0
- package/dist/find-RNBG7LBY.js +11 -0
- package/dist/forget/index.js +10 -7
- package/dist/forget/index.js.map +1 -1
- package/dist/guards/index.d.ts +4 -4
- package/dist/guards/index.js +3 -3
- package/dist/{hash-CWxn7sf6.d.ts → hash-D8Q5MJLA.d.ts} +1 -1
- package/dist/history/index.d.ts +4 -4
- package/dist/history/index.js +12 -9
- package/dist/history/index.js.map +1 -1
- package/dist/i18n/index.d.ts +3 -3
- package/dist/i18n/index.js +14 -11
- package/dist/i18n/index.js.map +1 -1
- package/dist/in/index.d.ts +2 -2
- package/dist/{index-D05bV0_g.d.ts → index-D4GQe1Rx.d.ts} +818 -49
- package/dist/{index-DTMUUwwW.d.ts → index-DRxk8q_7.d.ts} +3 -3
- package/dist/index.d.ts +46 -17
- package/dist/index.js +1021 -122
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.d.ts +3 -3
- package/dist/indexing/index.js +4 -4
- package/dist/issue-W5QG53B5.js +19 -0
- package/dist/json-schema-I22JCYCG.js +44 -0
- package/dist/json-schema-I22JCYCG.js.map +1 -0
- package/dist/kernel/index.d.ts +3 -3
- package/dist/kernel/index.js +13 -10
- package/dist/lazy/index.d.ts +21 -0
- package/dist/lazy/index.js +12 -0
- package/dist/{ledger-YUAJUNFP.js → ledger-KZWBKAG5.js} +12 -9
- package/dist/liberate-GMGCHIND.js +24 -0
- package/dist/link-set-UQEIYQAM.js +31 -0
- package/dist/materialized-views/index.d.ts +4 -4
- package/dist/materialized-views/index.js +16 -13
- package/dist/{mime-magic-Qr_4HjP6.d.ts → mime-magic-DmwT7OzZ.d.ts} +1 -1
- package/dist/noydb-KS2O2MRI.js +60 -0
- package/dist/on/index.d.ts +2 -2
- package/dist/on/index.js +10 -7
- package/dist/overlay-views/index.d.ts +4 -4
- package/dist/overlay-views/index.js +4 -4
- package/dist/periods/index.d.ts +3 -3
- package/dist/periods/index.js +13 -10
- package/dist/pod/index.d.ts +3 -3
- package/dist/pod/index.js +12 -9
- package/dist/{policy-LRE6HTO5.js → policy-YIKUH4AD.js} +5 -5
- package/dist/portability/index.d.ts +3 -3
- package/dist/portability/index.js +8 -8
- package/dist/{public-envelope-WVRRUVAJ.js → public-envelope-MRN5YYZZ.js} +4 -4
- package/dist/query/index.d.ts +2 -2
- package/dist/query/index.js +6 -6
- package/dist/register-K6DDSIXN.js +21 -0
- package/dist/registry-IMNMHWTM.js +17 -0
- package/dist/registry-K6VUQXKV.js +19 -0
- package/dist/registry-ZVO3T4M5.js +9 -0
- package/dist/request-withdrawal-EHALV66Y.js +31 -0
- package/dist/request-withdrawal-EHALV66Y.js.map +1 -0
- package/dist/{reveal-WSUHXCSS.js → reveal-TBLTFWQ2.js} +6 -5
- package/dist/{reveal-WSUHXCSS.js.map → reveal-TBLTFWQ2.js.map} +1 -1
- package/dist/revoke-MHAUAESM.js +24 -0
- package/dist/revoke-MHAUAESM.js.map +1 -0
- package/dist/sealed-record/index.d.ts +3 -3
- package/dist/sealed-record/index.js +13 -10
- package/dist/sealed-record/index.js.map +1 -1
- package/dist/session/index.d.ts +4 -4
- package/dist/session/index.js +10 -7
- package/dist/session/index.js.map +1 -1
- package/dist/shadow/index.d.ts +3 -3
- package/dist/shadow/index.js +2 -2
- package/dist/signer-PQ74YXRY.js +25 -0
- package/dist/signer-PQ74YXRY.js.map +1 -0
- package/dist/snapshots/index.d.ts +3 -3
- package/dist/snapshots/index.js +11 -8
- package/dist/snapshots/index.js.map +1 -1
- package/dist/{stale-LX4BR43V.js → stale-7Q62KVI3.js} +2 -2
- package/dist/stale-7Q62KVI3.js.map +1 -0
- package/dist/storage-5E6YB2JY.js +21 -0
- package/dist/storage-5E6YB2JY.js.map +1 -0
- package/dist/{store-coordination-provider-KTRIC6PR.js → store-coordination-provider-JJCEMTAE.js} +3 -3
- package/dist/sync/index.d.ts +2 -2
- package/dist/sync/index.js +10 -7
- package/dist/sync/index.js.map +1 -1
- package/dist/team/index.d.ts +18 -4
- package/dist/team/index.js +24 -14
- package/dist/tiers/index.d.ts +2 -2
- package/dist/tiers/index.js +11 -8
- package/dist/to/index.d.ts +2 -2
- package/dist/to/index.js +1 -1
- package/dist/{transition-guard-C1Pyz8nH.d.ts → transition-guard-C4hvR-Ac.d.ts} +1 -1
- package/dist/tx/index.d.ts +3 -3
- package/dist/tx/index.js +3 -3
- package/dist/ui/index.d.ts +1 -1
- package/dist/util/index.js +1 -1
- package/dist/validators-Ctgkj5qd.d.ts +101 -0
- package/dist/{vault-diff-Fn0WeY2w.d.ts → vault-diff-B1Ir6EGs.d.ts} +1 -1
- package/dist/{verify-SW6J63Q2.js → verify-YB5LQHNA.js} +11 -7
- package/dist/{verify-SW6J63Q2.js.map → verify-YB5LQHNA.js.map} +1 -1
- package/dist/walk-5C3GPKT2.js +241 -0
- package/dist/walk-5C3GPKT2.js.map +1 -0
- package/dist/with/index.d.ts +3 -3
- package/dist/with/index.js +12 -9
- package/dist/{with-materialized-view-NKxs6iK5.d.ts → with-materialized-view-Bp65kKdQ.d.ts} +1 -1
- package/dist/{with-overlayed-view-B4fPYHwV.d.ts → with-overlayed-view-BRhdQNrK.d.ts} +1 -1
- package/dist/{with-rollup-Bl0sRFEt.d.ts → with-rollup-bQRPc3y1.d.ts} +1 -1
- package/dist/withdraw-accessible-JMX2TCPX.js +28 -0
- package/dist/withdraw-accessible-JMX2TCPX.js.map +1 -0
- package/package.json +7 -3
- package/dist/api-LORZ2EZU.js +0 -17
- package/dist/chunk-4DZGZ7II.js.map +0 -1
- package/dist/chunk-5EZAJEES.js.map +0 -1
- package/dist/chunk-ADBMJDBW.js.map +0 -1
- package/dist/chunk-CPAROOKP.js.map +0 -1
- package/dist/chunk-L4IRFTIF.js.map +0 -1
- package/dist/chunk-MIOLJG2R.js.map +0 -1
- package/dist/chunk-N2FP26NH.js.map +0 -1
- package/dist/chunk-NIA5VQ7G.js.map +0 -1
- package/dist/chunk-NPVICKZE.js.map +0 -1
- package/dist/chunk-NTHKOFVL.js.map +0 -1
- package/dist/chunk-OCDUQUAP.js.map +0 -1
- package/dist/chunk-OVIVYAQL.js.map +0 -1
- package/dist/chunk-QLQS5A7X.js +0 -524
- package/dist/chunk-QLQS5A7X.js.map +0 -1
- package/dist/chunk-Z44OY24N.js.map +0 -1
- package/dist/chunk-ZFME46HJ.js.map +0 -1
- package/dist/collection-facade-XPVRHBGU.js +0 -36
- package/dist/delegation-4HUHUE6T.js +0 -22
- package/dist/executor-ANFBCOYA.js +0 -9
- package/dist/executor-IKT47SH2.js +0 -9
- package/dist/executor-OIECZXTV.js +0 -18
- package/dist/export-accessible-NZWCFZHL.js +0 -20
- package/dist/extract-partition-52LX6RQH.js +0 -33
- package/dist/issue-F4SYPJGJ.js +0 -16
- package/dist/liberate-6LDETRIW.js +0 -21
- package/dist/noydb-WQNBVJIG.js +0 -54
- package/dist/registry-6HZ2JSQ7.js +0 -14
- package/dist/registry-GPXZQ7DT.js +0 -9
- package/dist/registry-USYD2ECC.js +0 -16
- package/dist/request-withdrawal-54V4L6CR.js +0 -28
- package/dist/revoke-JV26NTQD.js +0 -21
- package/dist/signer-AE56T5HE.js +0 -22
- package/dist/validators-Dzn7T-3x.d.ts +0 -62
- package/dist/withdraw-accessible-DGA4V2TP.js +0 -25
- /package/dist/{api-LORZ2EZU.js.map → api-JOXUNSKP.js.map} +0 -0
- /package/dist/{chunk-VBYVKOMJ.js.map → chunk-3QRQOBS7.js.map} +0 -0
- /package/dist/{chunk-JFYIHLU3.js.map → chunk-3Y4QLJ6K.js.map} +0 -0
- /package/dist/{chunk-BZIWKN74.js.map → chunk-43ISXURO.js.map} +0 -0
- /package/dist/{chunk-7IP75FP2.js.map → chunk-4K3MQ5S3.js.map} +0 -0
- /package/dist/{chunk-6DP5HBQD.js.map → chunk-5CY35H55.js.map} +0 -0
- /package/dist/{chunk-2KSPDSWI.js.map → chunk-5MRE3C4Q.js.map} +0 -0
- /package/dist/{chunk-5ZPWVNL3.js.map → chunk-5W7AOFWA.js.map} +0 -0
- /package/dist/{chunk-PUNJAEY6.js.map → chunk-6HNKCKFZ.js.map} +0 -0
- /package/dist/{chunk-PJDK2PPQ.js.map → chunk-6RXPSMKR.js.map} +0 -0
- /package/dist/{chunk-MCJAUQGE.js.map → chunk-A5DYVMDR.js.map} +0 -0
- /package/dist/{chunk-TICO2I2O.js.map → chunk-BKGIWGCX.js.map} +0 -0
- /package/dist/{chunk-O5DNEXQC.js.map → chunk-BMQOH7MM.js.map} +0 -0
- /package/dist/{chunk-U6OM4E67.js.map → chunk-CPXPHQGX.js.map} +0 -0
- /package/dist/{chunk-3UDPDRUC.js.map → chunk-EWR7HL3K.js.map} +0 -0
- /package/dist/{chunk-CVXLJIAV.js.map → chunk-FH52CDEW.js.map} +0 -0
- /package/dist/{chunk-FELHYKIO.js.map → chunk-G7RDYYTE.js.map} +0 -0
- /package/dist/{chunk-UP6EMMDI.js.map → chunk-GOUNIHFA.js.map} +0 -0
- /package/dist/{chunk-DHIN36UC.js.map → chunk-GQOT6QJR.js.map} +0 -0
- /package/dist/{chunk-DGUR3CC4.js.map → chunk-GXGK22QU.js.map} +0 -0
- /package/dist/{chunk-YTIAXSUE.js.map → chunk-GZZL5R73.js.map} +0 -0
- /package/dist/{chunk-E4YJUNPU.js.map → chunk-H5XRIJX3.js.map} +0 -0
- /package/dist/{chunk-DA34OEZU.js.map → chunk-H7RYPVMJ.js.map} +0 -0
- /package/dist/{chunk-2ZORB5RW.js.map → chunk-HBKDR7R3.js.map} +0 -0
- /package/dist/{chunk-BV7KNFJY.js.map → chunk-HLUKZ36Q.js.map} +0 -0
- /package/dist/{chunk-ZNI3RTFV.js.map → chunk-HMXLN43G.js.map} +0 -0
- /package/dist/{chunk-BYW7EU5L.js.map → chunk-HY6ZGGEC.js.map} +0 -0
- /package/dist/{chunk-5Z3RNFJC.js.map → chunk-LIP6JWYK.js.map} +0 -0
- /package/dist/{chunk-IJW6K6UX.js.map → chunk-LPRPVCNY.js.map} +0 -0
- /package/dist/{chunk-6EVZXETK.js.map → chunk-M66NAZA4.js.map} +0 -0
- /package/dist/{chunk-HGQG3VIP.js.map → chunk-N5YVZHCB.js.map} +0 -0
- /package/dist/{chunk-IWVWB7BZ.js.map → chunk-NB47TXGP.js.map} +0 -0
- /package/dist/{chunk-6OQ7NKMZ.js.map → chunk-O2DB4C3M.js.map} +0 -0
- /package/dist/{chunk-LHYQE3BP.js.map → chunk-OFBFAVLI.js.map} +0 -0
- /package/dist/{chunk-6I2YPLAG.js.map → chunk-P27Z66EB.js.map} +0 -0
- /package/dist/{chunk-7MHVHGQZ.js.map → chunk-P2LDXRGP.js.map} +0 -0
- /package/dist/{chunk-6RASM2J4.js.map → chunk-QKVILR7N.js.map} +0 -0
- /package/dist/{chunk-LKBLAUOB.js.map → chunk-QNTEDPWP.js.map} +0 -0
- /package/dist/{chunk-P6UXDALK.js.map → chunk-RDHAGBEB.js.map} +0 -0
- /package/dist/{chunk-XRE4JOEO.js.map → chunk-RTS23VIJ.js.map} +0 -0
- /package/dist/{chunk-PYWW4KLO.js.map → chunk-SEQ2JI3Y.js.map} +0 -0
- /package/dist/{chunk-VHEEU4ZO.js.map → chunk-SW56GSYC.js.map} +0 -0
- /package/dist/{chunk-46YGCU6Z.js.map → chunk-T2EGAXPM.js.map} +0 -0
- /package/dist/{chunk-LES2Z7B4.js.map → chunk-T45LAT2Y.js.map} +0 -0
- /package/dist/{chunk-4NMFWOS2.js.map → chunk-T65UZXR6.js.map} +0 -0
- /package/dist/{chunk-2N4MUSF3.js.map → chunk-TB5RNNJ4.js.map} +0 -0
- /package/dist/{chunk-6CNLU4TO.js.map → chunk-TYGW5TOR.js.map} +0 -0
- /package/dist/{chunk-U5DWHU3S.js.map → chunk-U24XHXNK.js.map} +0 -0
- /package/dist/{chunk-YDP2SA5K.js.map → chunk-VPCMJ5Z4.js.map} +0 -0
- /package/dist/{chunk-ZAWD6O46.js.map → chunk-VRPBEOWU.js.map} +0 -0
- /package/dist/{chunk-AY4P6PHN.js.map → chunk-VWGCJUTV.js.map} +0 -0
- /package/dist/{chunk-IIK7W3AN.js.map → chunk-XJR3COJO.js.map} +0 -0
- /package/dist/{chunk-ZD4D7UM6.js.map → chunk-XYYW64LB.js.map} +0 -0
- /package/dist/{chunk-4NZCZUGL.js.map → chunk-YFF2RP3X.js.map} +0 -0
- /package/dist/{chunk-WVIIJPTJ.js.map → chunk-Z3FZC5GV.js.map} +0 -0
- /package/dist/{chunk-55HDKLI3.js.map → chunk-Z5PIUYNB.js.map} +0 -0
- /package/dist/{chunk-ZRDYQZYH.js.map → chunk-Z7KI4WHR.js.map} +0 -0
- /package/dist/{chunk-P6DN5QU7.js.map → chunk-ZKYMKF2S.js.map} +0 -0
- /package/dist/{chunk-NMRKAGHE.js.map → chunk-ZROMNP7Q.js.map} +0 -0
- /package/dist/{collection-facade-XPVRHBGU.js.map → collection-facade-MKEBZDWW.js.map} +0 -0
- /package/dist/{delegation-4HUHUE6T.js.map → computed-BMMEFRFJ.js.map} +0 -0
- /package/dist/{enclave-IS7HZSQ7.js.map → delegation-BQNIEHZE.js.map} +0 -0
- /package/dist/{executor-ANFBCOYA.js.map → enclave-YN6223OG.js.map} +0 -0
- /package/dist/{executor-IKT47SH2.js.map → executor-CBTNU5VZ.js.map} +0 -0
- /package/dist/{executor-OIECZXTV.js.map → executor-DJHNSTIR.js.map} +0 -0
- /package/dist/{export-accessible-NZWCFZHL.js.map → executor-FGISLQIN.js.map} +0 -0
- /package/dist/{extract-partition-52LX6RQH.js.map → export-accessible-P7SLRLK3.js.map} +0 -0
- /package/dist/{issue-F4SYPJGJ.js.map → extract-partition-RNVSFHAB.js.map} +0 -0
- /package/dist/{ledger-YUAJUNFP.js.map → find-RNBG7LBY.js.map} +0 -0
- /package/dist/{liberate-6LDETRIW.js.map → issue-W5QG53B5.js.map} +0 -0
- /package/dist/{noydb-WQNBVJIG.js.map → lazy/index.js.map} +0 -0
- /package/dist/{policy-LRE6HTO5.js.map → ledger-KZWBKAG5.js.map} +0 -0
- /package/dist/{public-envelope-WVRRUVAJ.js.map → liberate-GMGCHIND.js.map} +0 -0
- /package/dist/{registry-6HZ2JSQ7.js.map → link-set-UQEIYQAM.js.map} +0 -0
- /package/dist/{registry-GPXZQ7DT.js.map → noydb-KS2O2MRI.js.map} +0 -0
- /package/dist/{registry-USYD2ECC.js.map → policy-YIKUH4AD.js.map} +0 -0
- /package/dist/{request-withdrawal-54V4L6CR.js.map → public-envelope-MRN5YYZZ.js.map} +0 -0
- /package/dist/{revoke-JV26NTQD.js.map → register-K6DDSIXN.js.map} +0 -0
- /package/dist/{signer-AE56T5HE.js.map → registry-IMNMHWTM.js.map} +0 -0
- /package/dist/{stale-LX4BR43V.js.map → registry-K6VUQXKV.js.map} +0 -0
- /package/dist/{withdraw-accessible-DGA4V2TP.js.map → registry-ZVO3T4M5.js.map} +0 -0
- /package/dist/{store-coordination-provider-KTRIC6PR.js.map → store-coordination-provider-JJCEMTAE.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/kernel/enclave/classify/digest.ts","../src/kernel/enclave/classify/normalize.ts","../src/kernel/enclave/classify/compare.ts","../src/kernel/enclave/classify/vdig.ts","../src/kernel/enclave/classify/kofn.ts"],"sourcesContent":["/**\n * Verify-digest primitive for classified `digest-only` fields (stage 2).\n * PBKDF2-SHA256 → fixed 32-byte digest. The digest NEVER leaves the enclave\n * in recoverable form — it is sealed into the `_vdig` envelope slot (vdig.ts)\n * or compared in-enclave (verify.ts). @module\n */\nconst subtle = globalThis.crypto.subtle\n\n/** Family KDF constant — matches crypto.ts PBKDF2_ITERATIONS (600K). */\nexport const VDIG_ITERATIONS = 600_000\n\n/**\n * Digest a (pre-normalized) candidate/value to exactly 32 bytes.\n * PBKDF2-SHA256 with a per-record per-write random salt (§2): verify\n * digests are non-equatable by construction.\n */\nexport async function pbkdf2VerifyDigest(\n value: string,\n salt: Uint8Array,\n iterations: number,\n): Promise<Uint8Array> {\n const keyMaterial = await subtle.importKey(\n 'raw',\n new TextEncoder().encode(value),\n 'PBKDF2',\n false,\n ['deriveBits'],\n )\n const bits = await subtle.deriveBits(\n { name: 'PBKDF2', salt: salt as BufferSource, iterations, hash: 'SHA-256' },\n keyMaterial,\n 256,\n )\n return new Uint8Array(bits)\n}\n","/**\n * Preset normalization for verify candidates AND stored comparands (§4).\n * Both sides of every comparison route through the SAME function so\n * write-time digests and verify-time candidates agree. @module\n */\nexport type VerifyNormalizeMode = 'password' | 'secret-answer'\n\nexport function normalizeForVerify(mode: VerifyNormalizeMode, value: string): string {\n const nfc = value.normalize('NFC')\n if (mode === 'password') return nfc\n // secret-answer: NFC -> casefold -> re-NFC -> trim -> collapse internal\n // whitespace runs. `.toLowerCase()` is the chosen casefold approximation\n // (JS has no full Unicode caseless-match in the stdlib, e.g. ß stays ß);\n // the re-NFC after lowercasing keeps the result deterministic.\n return nfc.toLowerCase().normalize('NFC').trim().replace(/\\s+/g, ' ')\n}\n","/**\n * C2 — fixed-length-tag constant-time comparison (hub-portable; no\n * Node timingSafeEqual). Two mandatory rules (spec §3):\n * 1. Only fixed 32-byte tags are ever compared — ctEqualTags throws on\n * anything else (tag length is structural, never secret-dependent).\n * 2. Every comparand is reduced to a 32-byte tag under a FRESH ephemeral\n * HMAC-SHA256 key before comparison — keyed blinding makes compare\n * timing uncorrelated with underlying values; rule 1 (not blinding)\n * is what removes input-length timing. Length inequality folds into\n * unequal tags — never an early return.\n * @module\n */\nconst subtle = globalThis.crypto.subtle\n\n/** Compare exactly-32-byte tags. XOR-accumulate over all 32 bytes, no early exit. */\nexport function ctEqualTags(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== 32 || b.length !== 32) {\n throw new Error(\n `ctEqualTags: tags must be exactly 32 bytes (got ${a.length}/${b.length}) — caller bug; ` +\n `reduce comparands with blindedEqual first`,\n )\n }\n let diff = 0\n for (let i = 0; i < 32; i++) diff |= (a[i]! ^ b[i]!)\n return diff === 0\n}\n\n/**\n * Blinded equality of arbitrary-length byte strings: fresh K_e per\n * comparison (never stored, never reused), HMAC both sides to 32-byte\n * tags, then ctEqualTags. On the digest path the inputs are already\n * 32-byte PBKDF2 outputs but still route through this reduction so\n * there is exactly ONE comparison construction (spec §3 rule 2).\n */\nexport async function blindedEqual(a: Uint8Array, b: Uint8Array): Promise<boolean> {\n const ke = await subtle.generateKey({ name: 'HMAC', hash: 'SHA-256' }, false, ['sign'])\n const tagA = new Uint8Array(await subtle.sign('HMAC', ke, a as BufferSource))\n const tagB = new Uint8Array(await subtle.sign('HMAC', ke, b as BufferSource))\n return ctEqualTags(tagA, tagB)\n}\n","/**\n * The `_vdig` slot payload + crypto (stage 2, spec §2).\n *\n * A `_vdig[field]` blob is AES-256-GCM `\"iv:data\"` sealed under an\n * HKDF(CEK) slot key with AAD = ['noydb-classify-vdig', collection,\n * recordId, field] (C1 rollback-splice hardening). The AAD is NOT stored —\n * readers reconstruct it. It is deliberately `_v`-independent: C6's\n * carry-forward copies blob bytes verbatim across version bumps; the\n * same-record same-field TEMPORAL rollback residual is detected by the\n * ledger's conditional `_vdig` binding (envelope-body.ts).\n *\n * CEK-ONLY (I3): there is no DEK derivation and no\n * 'noydb-classify-vdig-dek' salt domain — every vdig slot dies with the\n * record's `_cek` (forget() shreds it totally; no vdig-dekResidue class).\n * @module\n */\nimport { encryptBytesWithAAD, decryptBytesWithAAD, base64ToBuffer, type EnclaveKey } from '../crypto.js'\nimport { TamperedError } from '../../errors.js'\n\nconst subtle = globalThis.crypto.subtle\n\nexport const VDIG_SALT_DOMAIN = 'noydb-classify-vdig'\n\nexport interface VdigDigestEntry {\n readonly salt: string // base64 32-byte per-write random salt\n readonly hash: string // base64 32-byte pbkdf2VerifyDigest output\n}\n\nexport interface VdigPayload {\n readonly v: 1\n readonly alg: 'PBKDF2-SHA256'\n readonly iter: number\n readonly cur: VdigDigestEntry & { readonly at: string } // ISO write-time\n /** Previous digests, oldest-first, length ≤ notLastN (cap 8). */\n readonly ring?: readonly VdigDigestEntry[]\n}\n\n/** Injective JSON-array AAD — same collision argument as deriveSealedFieldKey. */\nexport function buildVdigAad(collection: string, recordId: string, field: string): Uint8Array {\n return new TextEncoder().encode(JSON.stringify([VDIG_SALT_DOMAIN, collection, recordId, field]))\n}\n\n/** HKDF(CEK) → non-extractable AES-256-GCM vdig slot key. CEK-only (I3). */\nexport async function deriveVdigSlotKey(\n cek: EnclaveKey,\n collection: string,\n field: string,\n): Promise<EnclaveKey> {\n const raw = await subtle.exportKey('raw', cek)\n const hkdf = await subtle.importKey('raw', raw, 'HKDF', false, ['deriveBits'])\n const salt = new TextEncoder().encode(VDIG_SALT_DOMAIN)\n const info = new TextEncoder().encode(JSON.stringify([VDIG_SALT_DOMAIN, collection, field]))\n const bits = await subtle.deriveBits({ name: 'HKDF', hash: 'SHA-256', salt, info }, hkdf, 256)\n return subtle.importKey('raw', bits, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt'])\n}\n\nexport async function sealVdigPayload(\n payload: VdigPayload,\n cek: EnclaveKey,\n collection: string,\n recordId: string,\n field: string,\n): Promise<string> {\n const key = await deriveVdigSlotKey(cek, collection, field)\n const { iv, data } = await encryptBytesWithAAD(\n new TextEncoder().encode(JSON.stringify(payload)),\n key,\n buildVdigAad(collection, recordId, field),\n )\n return `${iv}:${data}`\n}\n\n/** Throws TamperedError on any AAD / auth-tag mismatch (C1). */\nexport async function openVdigPayload(\n blob: string,\n cek: EnclaveKey,\n collection: string,\n recordId: string,\n field: string,\n): Promise<VdigPayload> {\n const sep = blob.indexOf(':')\n if (sep === -1) throw new TamperedError()\n const ivPart = blob.slice(0, sep)\n const dataPart = blob.slice(sep + 1)\n try {\n base64ToBuffer(ivPart)\n base64ToBuffer(dataPart)\n } catch {\n throw new TamperedError()\n }\n const key = await deriveVdigSlotKey(cek, collection, field)\n const bytes = await decryptBytesWithAAD(\n ivPart,\n dataPart,\n key,\n buildVdigAad(collection, recordId, field),\n )\n return JSON.parse(new TextDecoder().decode(bytes)) as VdigPayload\n}\n","/**\n * k-of-n aggregate for matchGroup (§3). Deliberately a full scan that\n * never breaks: per-member results must never influence control flow in\n * a way an observer could time or decompose. Returns ONLY the aggregate.\n * @module\n */\nexport function evaluateKofN(results: readonly boolean[], min: number): boolean {\n if (!Number.isInteger(min) || min < 1 || min > results.length) {\n throw new Error(\n `evaluateKofN: min ${min} out of range 1..${results.length} — caller bug ` +\n `(matchGroup validates bounds up front, before any PBKDF2)`,\n )\n }\n let count = 0\n for (const r of results) {\n if (r) count += 1 // no short-circuit — collect, never break\n }\n return count >= min\n}\n"],"mappings":"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","names":["subtle","subtle"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/kernel/enclave/crypto.ts","../src/kernel/enclave/record-keys/sealed-slot.ts"],"sourcesContent":["/**\n * Cryptographic primitives — thin wrappers around the Web Crypto API.\n *\n * ## Design principle\n *\n * **Zero npm crypto dependencies.** Every operation uses `globalThis.crypto.subtle`,\n * which is available natively in Node.js ≥ 18, all modern browsers, and\n * Deno/Bun. This avoids supply-chain risk from third-party crypto packages and\n * ensures the library stays auditable.\n *\n * ## Algorithms\n *\n * | Use case | Algorithm | Parameters |\n * |----------|-----------|------------|\n * | Key derivation | PBKDF2-SHA256 | 600,000 iterations, 32-byte salt |\n * | Record encryption | AES-256-GCM | 12-byte random IV per operation |\n * | DEK wrapping | AES-KW (RFC 3394) | 256-bit KEK |\n * | Binary encrypt | AES-256-GCM | same as record encryption |\n * | Integrity | HMAC-SHA256 | for presence channels |\n * | Content hash | SHA-256 | for ledger and bundle integrity |\n *\n * ## Key lifecycle\n *\n * ```\n * passphrase + salt\n * └─► deriveKey() → KEK (CryptoKey, extractable: false)\n * └─► wrapKey() → wrapped DEK bytes [stored in keyring]\n * └─► unwrapKey() → DEK (CryptoKey) [memory only during session]\n * └─► encrypt() / decrypt() → ciphertext / plaintext\n * ```\n *\n * IVs are generated fresh by {@link generateIV} on every encrypt call.\n * Reusing an IV with the same key would break GCM's authentication guarantee —\n * this function should be the only place IVs are produced.\n *\n * @module\n */\n\nimport { DecryptionError, InvalidKeyError, TamperedError } from '../errors.js'\n\n/**\n * **EnclaveKey** — the opaque key type at the enclave seam.\n *\n * In noy-db's reference enclave this is `CryptoKey` (the Web Crypto API's\n * key handle). A fork's enclave redefines this alias to its own key\n * representation (e.g. `type EnclaveKey = null` for a keyless HSM-backed\n * fork) — outside `kernel/enclave/**`, every consumer must treat it as\n * opaque: never construct, inspect, or serialize it directly, only pass it\n * between barrel functions (`encrypt`/`decrypt`/`wrapKey`/`unwrapKey`/…).\n */\nexport type EnclaveKey = CryptoKey\n\nconst PBKDF2_ITERATIONS = 600_000\nconst SALT_BYTES = 32\nconst IV_BYTES = 12\nconst KEY_BITS = 256\n\nconst subtle = globalThis.crypto.subtle\n\n// ─── Key Derivation ────────────────────────────────────────────────────\n\n/** Derive a KEK from a passphrase and salt using PBKDF2-SHA256. */\nexport async function deriveKey(\n passphrase: string,\n salt: Uint8Array,\n): Promise<CryptoKey> {\n const keyMaterial = await subtle.importKey(\n 'raw',\n new TextEncoder().encode(passphrase),\n 'PBKDF2',\n false,\n ['deriveKey'],\n )\n\n return subtle.deriveKey(\n {\n name: 'PBKDF2',\n salt: salt as BufferSource,\n iterations: PBKDF2_ITERATIONS,\n hash: 'SHA-256',\n },\n keyMaterial,\n { name: 'AES-KW', length: KEY_BITS },\n false,\n ['wrapKey', 'unwrapKey'],\n )\n}\n\n/**\n * Which AES algorithm/usage a {@link derivePassphraseKey} result is for:\n * `'aes-kw'` mints a key-wrapping key (`wrapKey`/`unwrapKey`, e.g. for\n * KEK derivation); `'aes-gcm'` mints a direct encrypt/decrypt key (e.g.\n * for the wrap-DEKs primitive in `with-party/team/wrapped-deks.ts`).\n */\nexport type PassphraseKeyUsage = 'aes-kw' | 'aes-gcm'\n\n/**\n * Derive a non-extractable AES key from a passphrase/credential and salt\n * via PBKDF2-SHA256, generalizing {@link deriveKey}'s AES-KW derivation to\n * also cover AES-GCM-usage keys. Callers pin their own `iterations` and\n * `keyUsage` so an existing call site's exact parameters (and thus its\n * derived-key bytes) are preserved verbatim when migrated onto this\n * shared primitive — this is call-site consolidation, not a KDF change.\n */\nexport async function derivePassphraseKey(\n passphrase: string,\n salt: Uint8Array,\n params: { iterations: number; keyUsage: PassphraseKeyUsage },\n): Promise<CryptoKey> {\n const keyMaterial = await subtle.importKey(\n 'raw',\n new TextEncoder().encode(passphrase),\n 'PBKDF2',\n false,\n ['deriveKey'],\n )\n\n return params.keyUsage === 'aes-gcm'\n ? subtle.deriveKey(\n {\n name: 'PBKDF2',\n salt: salt as BufferSource,\n iterations: params.iterations,\n hash: 'SHA-256',\n },\n keyMaterial,\n { name: 'AES-GCM', length: KEY_BITS },\n false,\n ['encrypt', 'decrypt'],\n )\n : subtle.deriveKey(\n {\n name: 'PBKDF2',\n salt: salt as BufferSource,\n iterations: params.iterations,\n hash: 'SHA-256',\n },\n keyMaterial,\n { name: 'AES-KW', length: KEY_BITS },\n false,\n ['wrapKey', 'unwrapKey'],\n )\n}\n\n// ─── DEK Generation ────────────────────────────────────────────────────\n\n/** Generate a random AES-256-GCM data encryption key. */\nexport async function generateDEK(): Promise<CryptoKey> {\n return subtle.generateKey(\n { name: 'AES-GCM', length: KEY_BITS },\n true, // extractable — needed for AES-KW wrapping\n ['encrypt', 'decrypt'],\n )\n}\n\n// ─── Key Wrapping ──────────────────────────────────────────────────────\n\n/** Wrap (encrypt) a DEK with a KEK using AES-KW. Returns base64 string. */\nexport async function wrapKey(dek: CryptoKey, kek: CryptoKey): Promise<string> {\n const wrapped = await subtle.wrapKey('raw', dek, kek, 'AES-KW')\n return bufferToBase64(wrapped)\n}\n\n/** Unwrap (decrypt) a DEK from base64 string using a KEK. */\nexport async function unwrapKey(\n wrappedBase64: string,\n kek: CryptoKey,\n): Promise<CryptoKey> {\n try {\n return await subtle.unwrapKey(\n 'raw',\n base64ToBuffer(wrappedBase64) as BufferSource,\n kek,\n 'AES-KW',\n { name: 'AES-GCM', length: KEY_BITS },\n true,\n ['encrypt', 'decrypt'],\n )\n } catch {\n throw new InvalidKeyError()\n }\n}\n\n// ─── Per-record CEK wrapping ───────────────────────────────────────────\n\n/**\n * Derive a **dedicated** AES-KW wrapping key from a collection/tier DEK via\n * HKDF-SHA256, used to wrap/unwrap a per-record CEK.\n *\n * The collection/tier DEKs are AES-256-**GCM** keys (usages\n * `encrypt`/`decrypt`) and cannot themselves act as an AES-KW KEK. We do NOT\n * re-import the raw DEK bytes directly as an AES-KW key — that would reuse one\n * key across two primitives (AES-GCM for `_det`/presence, AES-KW for CEK\n * wrapping), violating key separation. Instead we HKDF-derive a domain-\n * separated KW key (salt `noydb-cek-wrap`), exactly as `derivePresenceKey`\n * derives a separate presence key. The derivation is deterministic, so\n * wrapping the same CEK under the same DEK always yields identical ciphertext\n * — which is what lets an update reuse a record's stable CEK and produce a\n * byte-identical `_cek`. The KW key is independent of the DEK's GCM key.\n *\n * The DEK must be extractable (it is — `generateDEK`/`unwrapKey` mint\n * extractable keys).\n */\nasync function asKwKey(dek: CryptoKey): Promise<CryptoKey> {\n const rawDek = await subtle.exportKey('raw', dek)\n const hkdfKey = await subtle.importKey('raw', rawDek, 'HKDF', false, ['deriveBits'])\n const salt = new TextEncoder().encode('noydb-cek-wrap')\n const info = new TextEncoder().encode('v1')\n const bits = await subtle.deriveBits(\n { name: 'HKDF', hash: 'SHA-256', salt, info },\n hkdfKey,\n KEY_BITS,\n )\n return subtle.importKey('raw', bits, 'AES-KW', false, ['wrapKey', 'unwrapKey'])\n}\n\n/**\n * AES-KW-wrap a per-record CEK under a collection/tier DEK. Returns base64.\n * Deterministic over `(cek, dek)`.\n */\nexport async function wrapCek(cek: CryptoKey, dek: CryptoKey): Promise<string> {\n const kw = await asKwKey(dek)\n const wrapped = await subtle.wrapKey('raw', cek, kw, 'AES-KW')\n return bufferToBase64(wrapped)\n}\n\n/**\n * Unwrap a per-record CEK from base64 under a collection/tier DEK. Throws\n * `InvalidKeyError` if the wrapped bytes do not authenticate under the DEK.\n */\nexport async function unwrapCek(wrappedBase64: string, dek: CryptoKey): Promise<CryptoKey> {\n const kw = await asKwKey(dek)\n try {\n return await subtle.unwrapKey(\n 'raw',\n base64ToBuffer(wrappedBase64) as BufferSource,\n kw,\n 'AES-KW',\n { name: 'AES-GCM', length: KEY_BITS },\n true,\n ['encrypt', 'decrypt'],\n )\n } catch {\n throw new InvalidKeyError()\n }\n}\n\n/**\n * Import a raw 32-byte AES-256-GCM record CEK (e.g. from a sealed-CEK\n * delivery binding). Non-extractable, decrypt-only — the imported key is\n * used solely to open the record's `_iv`/`_data` body under `decrypt()`.\n */\nexport async function importCek(rawKey: Uint8Array): Promise<CryptoKey> {\n return subtle.importKey('raw', rawKey as BufferSource, { name: 'AES-GCM', length: KEY_BITS }, false, ['decrypt'])\n}\n\n// ─── Encrypt / Decrypt ─────────────────────────────────────────────────\n\nexport interface EncryptResult {\n iv: string // base64\n data: string // base64\n}\n\n/** Encrypt plaintext JSON string with AES-256-GCM. Fresh IV per call. */\nexport async function encrypt(\n plaintext: string,\n dek: CryptoKey,\n): Promise<EncryptResult> {\n const iv = generateIV()\n const encoded = new TextEncoder().encode(plaintext)\n\n const ciphertext = await subtle.encrypt(\n { name: 'AES-GCM', iv: iv as BufferSource },\n dek,\n encoded,\n )\n\n return {\n iv: bufferToBase64(iv),\n data: bufferToBase64(ciphertext),\n }\n}\n\n/** Decrypt AES-256-GCM ciphertext. Throws on wrong key or tampered data. */\nexport async function decrypt(\n ivBase64: string,\n dataBase64: string,\n dek: CryptoKey,\n): Promise<string> {\n const iv = base64ToBuffer(ivBase64)\n const ciphertext = base64ToBuffer(dataBase64)\n\n try {\n const plaintext = await subtle.decrypt(\n { name: 'AES-GCM', iv: iv as BufferSource },\n dek,\n ciphertext as BufferSource,\n )\n return new TextDecoder().decode(plaintext)\n } catch (err) {\n if (err instanceof Error && err.name === 'OperationError') {\n throw new TamperedError()\n }\n throw new DecryptionError(\n err instanceof Error ? err.message : 'Decryption failed',\n )\n }\n}\n\n// ─── Binary Encrypt / Decrypt ────────\n\n/**\n * Encrypt raw bytes with AES-256-GCM using a fresh random IV.\n * Used by the attachment store so binary blobs avoid double base64 encoding\n * (the existing `encrypt()` function calls `TextEncoder` on a string — here\n * we pass the `Uint8Array` directly to `subtle.encrypt`).\n */\nexport async function encryptBytes(\n data: Uint8Array,\n dek: CryptoKey,\n): Promise<EncryptResult> {\n const iv = generateIV()\n const ciphertext = await subtle.encrypt(\n { name: 'AES-GCM', iv: iv as BufferSource },\n dek,\n data as unknown as BufferSource,\n )\n return {\n iv: bufferToBase64(iv),\n data: bufferToBase64(ciphertext),\n }\n}\n\n/**\n * Decrypt AES-256-GCM ciphertext back to raw bytes.\n * Counterpart to `encryptBytes`. Throws `TamperedError` on auth-tag failure.\n */\nexport async function decryptBytes(\n ivBase64: string,\n dataBase64: string,\n dek: CryptoKey,\n): Promise<Uint8Array> {\n const iv = base64ToBuffer(ivBase64)\n const ciphertext = base64ToBuffer(dataBase64)\n try {\n const plaintext = await subtle.decrypt(\n { name: 'AES-GCM', iv: iv as BufferSource },\n dek,\n ciphertext as BufferSource,\n )\n return new Uint8Array(plaintext)\n } catch (err) {\n if (err instanceof Error && err.name === 'OperationError') {\n throw new TamperedError()\n }\n throw new DecryptionError(\n err instanceof Error ? err.message : 'Decryption failed',\n )\n }\n}\n\n/**\n * SHA-256 hex digest of raw bytes. Used to derive content-addressed\n * eTags for blob deduplication. Computed on plaintext bytes\n * before compression and encryption so the eTag identifies content, not\n * ciphertext, and survives re-encryption (key rotation, re-upload).\n */\nexport async function sha256Hex(data: Uint8Array): Promise<string> {\n const hash = await subtle.digest('SHA-256', data as unknown as BufferSource)\n return Array.from(new Uint8Array(hash))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\n// ─── HMAC-SHA-256 ─────────────────────────────\n\n/**\n * Compute HMAC-SHA-256(key, data) and return hex string.\n *\n * Used to derive content-addressed eTags that are opaque to the store:\n * ```\n * eTag = hmacSha256Hex(blobDEK, plaintext)\n * ```\n *\n * Unlike a plain SHA-256, the HMAC is keyed by the vault-shared `_blob` DEK,\n * so an attacker with store access cannot pre-compute eTags for known files.\n * Deduplication still works within a vault (same key + same content = same eTag).\n */\nexport async function hmacSha256Hex(key: CryptoKey, data: Uint8Array): Promise<string> {\n // Export AES-GCM DEK raw bytes → import as HMAC key\n const rawKey = await subtle.exportKey('raw', key)\n const hmacKey = await subtle.importKey(\n 'raw',\n rawKey,\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n )\n const sig = await subtle.sign('HMAC', hmacKey, data as unknown as BufferSource)\n return Array.from(new Uint8Array(sig))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\n// ─── AAD-aware Binary Encrypt / Decrypt ──\n\n/**\n * Encrypt raw bytes with AES-256-GCM using Additional Authenticated Data.\n *\n * The AAD binds each chunk to its parent blob and position, preventing\n * chunk reorder, substitution, and truncation attacks:\n * ```\n * AAD = UTF-8(\"{eTag}:{chunkIndex}:{chunkCount}\")\n * ```\n *\n * The AAD is NOT stored — the reader reconstructs it from `BlobObject`\n * metadata and passes it to `decryptBytesWithAAD`.\n */\nexport async function encryptBytesWithAAD(\n data: Uint8Array,\n dek: CryptoKey,\n aad: Uint8Array,\n): Promise<EncryptResult> {\n const iv = generateIV()\n const ciphertext = await subtle.encrypt(\n {\n name: 'AES-GCM',\n iv: iv as BufferSource,\n additionalData: aad as BufferSource,\n },\n dek,\n data as unknown as BufferSource,\n )\n return {\n iv: bufferToBase64(iv),\n data: bufferToBase64(ciphertext),\n }\n}\n\n/**\n * Decrypt AES-256-GCM ciphertext with AAD verification.\n *\n * If the AAD does not match the one used at encryption time (e.g. because\n * a chunk was reordered or substituted from another blob), the GCM auth\n * tag fails and this throws `TamperedError`.\n */\nexport async function decryptBytesWithAAD(\n ivBase64: string,\n dataBase64: string,\n dek: CryptoKey,\n aad: Uint8Array,\n): Promise<Uint8Array> {\n const iv = base64ToBuffer(ivBase64)\n const ciphertext = base64ToBuffer(dataBase64)\n try {\n const plaintext = await subtle.decrypt(\n {\n name: 'AES-GCM',\n iv: iv as BufferSource,\n additionalData: aad as BufferSource,\n },\n dek,\n ciphertext as BufferSource,\n )\n return new Uint8Array(plaintext)\n } catch (err) {\n if (err instanceof Error && err.name === 'OperationError') {\n throw new TamperedError()\n }\n throw new DecryptionError(\n err instanceof Error ? err.message : 'Decryption failed',\n )\n }\n}\n\n// ─── Presence Key Derivation ──────────────────────────────\n\n/**\n * Derive an AES-256-GCM presence key from a collection DEK using HKDF-SHA256.\n *\n * The presence key is domain-separated from the data DEK by the fixed salt\n * `'noydb-presence'` and the `info` = collection name. This means:\n * - The adapter never sees the presence key.\n * - Presence payloads rotate automatically when the collection DEK is rotated.\n * - Revoked users cannot derive the new presence key after a DEK rotation.\n *\n * @param dek The collection's AES-256-GCM DEK (extractable).\n * @param collectionName Used as the HKDF `info` parameter for domain separation.\n * @returns A non-extractable AES-256-GCM key suitable for presence payload encryption.\n */\nexport async function derivePresenceKey(dek: CryptoKey, collectionName: string): Promise<CryptoKey> {\n // Step 1: export DEK raw bytes\n const rawDek = await subtle.exportKey('raw', dek)\n\n // Step 2: import as HKDF key material\n const hkdfKey = await subtle.importKey(\n 'raw',\n rawDek,\n 'HKDF',\n false,\n ['deriveBits'],\n )\n\n // Step 3: derive 256 bits with salt='noydb-presence' and info=collectionName\n const salt = new TextEncoder().encode('noydb-presence')\n const info = new TextEncoder().encode(collectionName)\n const bits = await subtle.deriveBits(\n { name: 'HKDF', hash: 'SHA-256', salt, info },\n hkdfKey,\n KEY_BITS,\n )\n\n // Step 4: import derived bits as AES-GCM key\n return subtle.importKey(\n 'raw',\n bits,\n { name: 'AES-GCM', length: KEY_BITS },\n false,\n ['encrypt', 'decrypt'],\n )\n}\n\n// ─── Sealed Field Key Derivation ──────────────────────────\n\n/**\n * Derive a **per-field** AES-256-GCM key from a collection DEK using\n * HKDF-SHA256, used to encrypt a single sensitive field into its own\n * `_sealed[field]` envelope slot (structural group-encryption).\n *\n * Domain-separated from the data DEK (and from the presence/deterministic\n * channels) by the fixed salt `'noydb-sealed'` and an `info` of\n * `JSON.stringify(['noydb-sealed', collectionName, field])`. The JSON-array\n * encoding is injective — each element is separately quoted/escaped — so\n * collection `a/sealed/b` + field `c` cannot collide with collection `a` +\n * field `b/sealed/c`. Each sensitive field gets a **distinct** key, so a\n * `_sealed[a]` ciphertext does not authenticate under field `b`'s key —\n * sealed fields are cryptographically isolated from one another. The\n * collection name keeps the same field name in two collections from sharing a\n * key, mirroring how `derivePresenceKey` / `encryptDeterministic` domain-separate.\n *\n * @param dek The collection's AES-256-GCM DEK (extractable).\n * @param collectionName Part of the HKDF `info` for domain separation.\n * @param field Sensitive field name — the rest of the `info`.\n * @returns A non-extractable AES-256-GCM key for that field's sealed slot.\n */\nexport async function deriveSealedFieldKey(\n dek: CryptoKey,\n collectionName: string,\n field: string,\n): Promise<CryptoKey> {\n const rawDek = await subtle.exportKey('raw', dek)\n const hkdfKey = await subtle.importKey('raw', rawDek, 'HKDF', false, ['deriveBits'])\n const salt = new TextEncoder().encode('noydb-sealed')\n // JSON-array encoding is injective: each element is separately quoted/\n // escaped, so collection `a/sealed/b` + field `c` cannot collide with\n // collection `a` + field `b/sealed/c`.\n const info = new TextEncoder().encode(JSON.stringify(['noydb-sealed', collectionName, field]))\n const bits = await subtle.deriveBits(\n { name: 'HKDF', hash: 'SHA-256', salt, info },\n hkdfKey,\n KEY_BITS,\n )\n return subtle.importKey(\n 'raw',\n bits,\n { name: 'AES-GCM', length: KEY_BITS },\n false,\n ['encrypt', 'decrypt'],\n )\n}\n\n/**\n * Derive a per-record sealed-field key from the record's **per-record CEK**\n * rather than the collection DEK. Same HKDF construction as\n * {@link deriveSealedFieldKey} but with salt `'noydb-sealed-cek'` (distinct\n * from `'noydb-sealed'`), so a CEK-derived key can never collide with a\n * DEK-derived one for the same `{collection, field}`.\n *\n * The point: the sealed ciphertext's only key now flows from the record's CEK.\n * Dropping the record's wrapped `_cek` (crypto-shred / `forget`) makes the key\n * — and thus `_sealed[field]` — unrecoverable, giving sealed fields the same\n * erasure guarantee `_data` already has. The CEK must be extractable (it is —\n * `unwrapCek`/`generateDEK` mint extractable keys).\n *\n * @param cek The record's AES-256-GCM CEK (extractable).\n * @param collectionName Part of the HKDF `info` for domain separation.\n * @param field Sensitive field name — the rest of the `info`.\n * @returns A non-extractable AES-256-GCM key for that field's sealed slot.\n */\nexport async function deriveSealedFieldKeyFromCek(\n cek: CryptoKey,\n collectionName: string,\n field: string,\n): Promise<CryptoKey> {\n const rawCek = await subtle.exportKey('raw', cek)\n const hkdfKey = await subtle.importKey('raw', rawCek, 'HKDF', false, ['deriveBits'])\n const salt = new TextEncoder().encode('noydb-sealed-cek')\n const info = new TextEncoder().encode(JSON.stringify(['noydb-sealed-cek', collectionName, field]))\n const bits = await subtle.deriveBits(\n { name: 'HKDF', hash: 'SHA-256', salt, info },\n hkdfKey,\n KEY_BITS,\n )\n return subtle.importKey(\n 'raw',\n bits,\n { name: 'AES-GCM', length: KEY_BITS },\n false,\n ['encrypt', 'decrypt'],\n )\n}\n\n// ─── Deterministic Encryption ────────────────────────────\n\n/**\n * Derive the collection's **dedicated deterministic-index key** from its DEK\n * via HKDF-SHA256 (L-1, #554).\n *\n * `_det` slots previously encrypted under the raw collection DEK — the same\n * key `_data` uses with a *randomized*-IV regime while `_det` uses a\n * *deterministic*-IV regime. Two IV regimes on one key is avoidable hygiene\n * debt (~2^-96 theoretical collision), so the deterministic index now gets its\n * own HKDF-separated key: salt `'noydb-det'`, info\n * `JSON.stringify(['noydb-det'])` (the injective JSON-array domain tag, same\n * convention as {@link deriveSealedFieldKey}). Per-field separation still\n * comes from the `'<collection>/<field>'` context inside\n * {@link encryptDeterministic}, exactly as before.\n *\n * The derived key stays **DEK-derived (collection-level)** on purpose: `_det`\n * is carried verbatim through per-record CEK rotation (see\n * `record-keys/sealing.ts`), so its key must not depend on the CEK. It rotates\n * with the collection DEK, like the presence key.\n *\n * The key is minted **extractable** — {@link encryptDeterministic} derives its\n * per-value IV via HKDF over the raw key bytes (an `exportKey` under the\n * hood), which a non-extractable key would forbid. Same in-memory posture as\n * the DEK itself (`generateDEK` mints extractable keys).\n *\n * @param dek The collection's AES-256-GCM DEK (extractable).\n * @returns A dedicated AES-256-GCM key for the `_det` deterministic index.\n */\nexport async function deriveDeterministicKey(dek: CryptoKey): Promise<CryptoKey> {\n const rawDek = await subtle.exportKey('raw', dek)\n const hkdfKey = await subtle.importKey('raw', rawDek, 'HKDF', false, ['deriveBits'])\n const salt = new TextEncoder().encode('noydb-det')\n const info = new TextEncoder().encode(JSON.stringify(['noydb-det']))\n const bits = await subtle.deriveBits(\n { name: 'HKDF', hash: 'SHA-256', salt, info },\n hkdfKey,\n KEY_BITS,\n )\n return subtle.importKey(\n 'raw',\n bits,\n { name: 'AES-GCM', length: KEY_BITS },\n true,\n ['encrypt', 'decrypt'],\n )\n}\n\n/**\n * Derive a deterministic 12-byte IV from `{ DEK, context, plaintext }`\n * via HKDF-SHA256. Given the same three inputs, the IV is identical, so\n * `encryptDeterministic` produces the same ciphertext on every call —\n * which is precisely what enables blind equality search on encrypted\n * fields.\n *\n * **The side channel this opens.** Two records whose field value is the\n * same produce the same ciphertext. An observer with store access can\n * therefore tell which records share a value — not *what* the value is,\n * but the equivalence class. This is the well-known trade-off of\n * deterministic encryption and is why the feature is strictly opt-in\n * per field, guarded by `acknowledgeDeterministicRisk: true` at\n * collection creation.\n *\n * The context string MUST include the collection name and field name,\n * so:\n * - The same plaintext in two different fields encrypts differently\n * (no cross-field equality leak).\n * - The same plaintext in two different collections (different DEKs)\n * encrypts differently by virtue of the key, even before HKDF\n * domain separation kicks in.\n */\nasync function deriveDeterministicIV(\n dek: CryptoKey,\n context: string,\n plaintext: string,\n): Promise<Uint8Array> {\n const rawDek = await subtle.exportKey('raw', dek)\n const hkdfKey = await subtle.importKey('raw', rawDek, 'HKDF', false, ['deriveBits'])\n const salt = new TextEncoder().encode('noydb-deterministic-v1')\n const info = new TextEncoder().encode(`${context}\\x00${plaintext}`)\n const bits = await subtle.deriveBits(\n { name: 'HKDF', hash: 'SHA-256', salt, info },\n hkdfKey,\n IV_BYTES * 8,\n )\n return new Uint8Array(bits)\n}\n\n/**\n * Encrypt a plaintext string with AES-256-GCM and a deterministic,\n * HKDF-derived IV.\n *\n * The same `{ dek, context, plaintext }` triple always produces the\n * same `{ iv, data }` — call this twice and you can string-compare the\n * ciphertexts to check equality of the inputs without decrypting them.\n *\n * @param context Domain-separation string — by convention\n * `'<collection>/<field>'`. Different contexts encrypt\n * the same plaintext to different ciphertexts, so\n * `email` in collection `users` does not collide with\n * `email` in collection `customers`.\n */\nexport async function encryptDeterministic(\n plaintext: string,\n dek: CryptoKey,\n context: string,\n): Promise<EncryptResult> {\n const iv = await deriveDeterministicIV(dek, context, plaintext)\n const encoded = new TextEncoder().encode(plaintext)\n const ciphertext = await subtle.encrypt(\n { name: 'AES-GCM', iv: iv as BufferSource },\n dek,\n encoded,\n )\n return {\n iv: bufferToBase64(iv),\n data: bufferToBase64(ciphertext),\n }\n}\n\n/**\n * Counterpart to {@link encryptDeterministic}. The IV is stored\n * alongside the ciphertext (exactly like the randomized path), so\n * decrypt uses the stored IV and verifies the GCM auth tag — a tampered\n * ciphertext throws `TamperedError` just like randomized AES-GCM.\n */\nexport async function decryptDeterministic(\n ivBase64: string,\n dataBase64: string,\n dek: CryptoKey,\n): Promise<string> {\n return decrypt(ivBase64, dataBase64, dek)\n}\n\n// ─── Random Generation ─────────────────────────────────────────────────\n\n/** Generate a random 12-byte IV for AES-GCM. */\nexport function generateIV(): Uint8Array {\n return globalThis.crypto.getRandomValues(new Uint8Array(IV_BYTES))\n}\n\n/** Generate a random 32-byte salt for PBKDF2. */\nexport function generateSalt(): Uint8Array {\n return globalThis.crypto.getRandomValues(new Uint8Array(SALT_BYTES))\n}\n\n// ─── Base64 Helpers ────────────────────────────────────────────────────\n\nexport function bufferToBase64(buffer: ArrayBuffer | Uint8Array): string {\n const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer)\n let binary = ''\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]!)\n }\n return btoa(binary)\n}\n\nexport function base64ToBuffer(base64: string): Uint8Array<ArrayBuffer> {\n const binary = atob(base64)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i)\n }\n return bytes\n}\n","/**\n * Sealed-slot (`iv:data`) parsing + the CEK/DEK dual-read, factored out so the\n * three callers that decrypt a `_sealed[field]` slot share ONE implementation\n * with no coupling between them:\n *\n * - {@link RecordCodec.unsealField} / `classifySealedShred` (read + classify),\n * - `record-keys/sealing.ts:rotateRecordCek` (re-seal under a new CEK).\n *\n * These are pure helpers over an explicit `(cek, dek)` pair — NOT a context\n * object — so `sealing.ts` (which holds only the DEK) can import them without\n * pulling in the whole codec, avoiding a codec↔sealing dependency cycle.\n */\nimport { decrypt, deriveSealedFieldKey, deriveSealedFieldKeyFromCek } from '../crypto.js'\n\n/** Parse an `iv:data` sealed slot (split on the FIRST `:`). */\nexport function parseSealedSlot(blob: string): { iv: string; data: string } {\n const sep = blob.indexOf(':')\n return { iv: blob.slice(0, sep), data: blob.slice(sep + 1) }\n}\n\n/**\n * Dual-read: decrypt a sealed slot trying the CEK-derived field key first,\n * falling back to the collection-DEK field key. Legacy records\n * (even ones whose body is CEK-encrypted) are sealed under the collection-DEK\n * key; current writes seal under the per-record CEK. Try the CEK key first; on\n * its AES-GCM auth failure, fall back to the DEK key. Throws only if BOTH fail.\n * Returns the plaintext string (callers `JSON.parse` as needed).\n */\nexport async function dualReadSealedSlot(\n blob: string,\n field: string,\n collection: string,\n cek: CryptoKey | undefined,\n dek: CryptoKey,\n): Promise<string> {\n const { iv, data } = parseSealedSlot(blob)\n if (cek !== undefined) {\n try {\n const fieldKey = await deriveSealedFieldKeyFromCek(cek, collection, field)\n return await decrypt(iv, data, fieldKey)\n } catch {\n // fall through to the legacy collection-DEK derivation\n }\n }\n const fieldKey = await deriveSealedFieldKey(dek, collection, field)\n return decrypt(iv, data, fieldKey)\n}\n"],"mappings":"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","names":["fieldKey"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/with-shape/classified/descriptor.ts","../src/with-shape/classified/errors.ts","../src/with-shape/classified/resolve.ts","../src/with-shape/classified/write.ts","../src/with-shape/classified/strategy.ts"],"sourcesContent":["/**\n * Classified fields — behavioral sensitive-field descriptors (stage 1).\n * Design: docs/superpowers/specs/2026-07-04-classified-fields-design.md\n * Law: open on write, declarative on read — read-side behavior is this\n * closed vocabulary; there are no read-side callbacks.\n * @module\n */\n\nexport type ClassifiedStorage = 'recoverable' | 'never' | 'digest-only'\n\nexport type ClassifiedList =\n | { readonly kind: 'omit' }\n | { readonly kind: 'mask'; readonly pattern: string }\n | { readonly kind: 'rider'; readonly rider: string }\n\nexport type ClassifiedRider = (value: unknown) => unknown\n\nexport interface ClassifiedFieldSpec {\n readonly _noydbClassified: true\n readonly preset: string\n readonly storage: ClassifiedStorage\n readonly list: ClassifiedList\n readonly sensitivity: 'pii' | 'secret'\n /** Write-time safe projections; companion field name is `<field>_<rider>`. */\n readonly riders?: Record<string, ClassifiedRider>\n /** Write-time validator: error message, or null when valid. */\n readonly validate?: (value: unknown) => string | null\n /** Digest-only verify policy (stage 2). Mode both sides normalize under. */\n readonly verifyNormalize?: 'password' | 'secret-answer'\n /** Decorate ok:true verdicts with mustRotate after this many days (I1). */\n readonly rotateDays?: number\n /** Refuse reuse of the last N values on rotate (cap 8, spec Q4). */\n readonly notLastN?: number\n /** Member of the collection's matchGroup (secretAnswer preset). */\n readonly verifyGroupMember?: true\n}\n\nexport interface ClassifiedGroup {\n readonly _noydbClassifiedGroup: true\n readonly preset: string\n /** member record-field name -> spec (differential per-member policy). */\n readonly members: Record<string, ClassifiedFieldSpec>\n}\n\nexport type ClassifiedEntry = ClassifiedFieldSpec | ClassifiedGroup\n\nexport function isClassifiedFieldSpec(x: unknown): x is ClassifiedFieldSpec {\n return typeof x === 'object' && x !== null\n && (x as { _noydbClassified?: unknown })._noydbClassified === true\n}\n\nexport function isClassifiedGroup(x: unknown): x is ClassifiedGroup {\n return typeof x === 'object' && x !== null\n && (x as { _noydbClassifiedGroup?: unknown })._noydbClassifiedGroup === true\n}\n","/**\n * Configuration/write errors for classified fields. @module\n *\n * `ClassifiedConfigError` / `ClassifiedRevealError` moved to `kernel/errors.ts`\n * (stage 2) so `kernel/enclave/classify/*` can throw them without importing\n * with-*. Re-exported here under the same names so existing import paths\n * keep working.\n */\nexport { ClassifiedConfigError, ClassifiedRevealError, ClassifiedVerifyError, ClassifiedRotationError } from '../../kernel/errors.js'\n\nexport class ClassifiedNeverStoredError extends Error {\n constructor(public readonly collection: string, public readonly field: string) {\n super(`Field \"${field}\" in collection \"${collection}\" is classified storage:'never' `\n + `(e.g. a CVC) and must not be persisted. Validate it at capture and drop it before put().`)\n this.name = 'ClassifiedNeverStoredError'\n }\n}\n\nexport class ClassifiedValidationError extends Error {\n constructor(public readonly collection: string, public readonly field: string, detail: string) {\n super(`Classified field \"${field}\" in collection \"${collection}\" failed validation: ${detail}`)\n this.name = 'ClassifiedValidationError'\n }\n}\n","/** Flatten a classifiedFields config into a per-field map + rider computed entries. @module */\n\nimport { isClassifiedGroup, type ClassifiedEntry, type ClassifiedFieldSpec } from './descriptor.js'\nimport { ClassifiedConfigError } from './errors.js'\n\nexport type { ClassifiedEntry, ClassifiedFieldSpec, ClassifiedGroup, ClassifiedList } from './descriptor.js'\nexport { ClassifiedConfigError } from './errors.js'\n\nexport interface ResolvedClassified {\n readonly byField: Record<string, ClassifiedFieldSpec>\n readonly riderComputed: Record<string, (record: Record<string, unknown>) => unknown>\n}\n\nexport function resolveClassifiedFields(\n collection: string,\n config: Record<string, ClassifiedEntry>,\n): ResolvedClassified {\n const byField: Record<string, ClassifiedFieldSpec> = {}\n const claim = (field: string, spec: ClassifiedFieldSpec): void => {\n if (byField[field] !== undefined) {\n throw new ClassifiedConfigError(collection,\n `field \"${field}\" is claimed twice — storage forms are mutually exclusive per field (R5): ` +\n `a field is digest-only OR recoverable OR never, exactly one`)\n }\n byField[field] = spec\n }\n for (const [key, entry] of Object.entries(config)) {\n if (isClassifiedGroup(entry)) {\n for (const [field, spec] of Object.entries(entry.members)) claim(field, spec)\n } else {\n claim(key, entry)\n }\n }\n const riderComputed: Record<string, (record: Record<string, unknown>) => unknown> = {}\n for (const [field, spec] of Object.entries(byField)) {\n for (const [name, rider] of Object.entries(spec.riders ?? {})) {\n const companion = `${field}_${name}`\n if (byField[companion] !== undefined || riderComputed[companion] !== undefined) {\n throw new ClassifiedConfigError(collection, `rider companion \"${companion}\" collides with a declared field`)\n }\n riderComputed[companion] = (record) =>\n record[field] === undefined ? undefined : rider(record[field])\n }\n }\n return { byField, riderComputed }\n}\n","/** Write-path enforcement: storage:'never' rejection + preset validators.\n * Runs BEFORE riders/computed and BEFORE schema validation. Pure. @module */\n\nimport type { ClassifiedFieldSpec } from './descriptor.js'\nimport { ClassifiedNeverStoredError, ClassifiedValidationError } from './errors.js'\n\nexport function enforceClassifiedWrite(\n record: Record<string, unknown>,\n byField: Record<string, ClassifiedFieldSpec>,\n collection: string,\n): void {\n for (const [field, spec] of Object.entries(byField)) {\n const value = record[field]\n if (value === undefined) continue\n if (value === null && spec.storage === 'digest-only') continue // explicit clear (C6 branch 3)\n if (spec.storage === 'never') throw new ClassifiedNeverStoredError(collection, field)\n const problem = spec.validate?.(value) ?? null\n if (problem !== null) throw new ClassifiedValidationError(collection, field, problem)\n }\n}\n","/** The ② capability seam for classified read-egress ops (stage 1: reveal; stage 2: verify). @module */\nimport type { ClassifiedFieldSpec } from './descriptor.js'\nimport { ClassifiedNotEnabledError } from '../../kernel/errors.js'\nimport type { EncryptedEnvelope } from '../../kernel/types.js'\nimport type { EnclaveKey } from '../../kernel/enclave/index.js'\n\nexport type { ClassifiedVerdict } from '../../kernel/types.js'\nimport type { ClassifiedVerdict } from '../../kernel/types.js'\n\nexport interface ClassifiedRevealCtx {\n readonly collection: string\n readonly spec: ClassifiedFieldSpec\n /** True on an encrypted collection — false selects the plaintext-body read. */\n readonly encrypted: boolean\n readonly getEnvelope: (id: string) => Promise<EncryptedEnvelope | null>\n readonly resolveCek: (env: EncryptedEnvelope) => Promise<EnclaveKey | undefined>\n readonly getDEK: () => Promise<EnclaveKey>\n readonly onAccess?: ((op: 'reveal', id: string) => Promise<void>) | undefined\n}\n\nexport interface ClassifiedVerifyCtx {\n readonly collection: string\n readonly spec: ClassifiedFieldSpec\n readonly getEnvelope: (id: string) => Promise<EncryptedEnvelope | null> // raw envelope, NOT a decrypted view\n readonly resolveCek: (env: EncryptedEnvelope) => Promise<EnclaveKey | undefined>\n readonly getDEK: () => Promise<EnclaveKey>\n readonly now: () => number // injected (Q7)\n /** Group members resolved by the collection (matchGroup only). */\n readonly groupMembers?: ReadonlyArray<{ readonly field: string; readonly spec: ClassifiedFieldSpec }>\n readonly onAccess?: ((op: 'verify', id: string) => Promise<void>) | undefined\n}\n\nexport interface ClassifiedStrategy {\n reveal(ctx: ClassifiedRevealCtx, id: string, field: string): Promise<unknown>\n verify(ctx: ClassifiedVerifyCtx, id: string, field: string, candidate: string): Promise<ClassifiedVerdict>\n verifyText(ctx: ClassifiedVerifyCtx, id: string, field: string, candidate: string): Promise<ClassifiedVerdict>\n matchGroup(ctx: ClassifiedVerifyCtx, id: string, answers: Record<string, string>,\n opts: { readonly min: number }): Promise<{ readonly passed: boolean }>\n}\n\nexport const NO_CLASSIFIED: ClassifiedStrategy = {\n async reveal() { throw new ClassifiedNotEnabledError() },\n async verify() { throw new ClassifiedNotEnabledError() },\n async verifyText() { throw new ClassifiedNotEnabledError() },\n async matchGroup() { throw new ClassifiedNotEnabledError() },\n}\n"],"mappings":";;;;;;AA8CO,SAAS,sBAAsB,GAAsC;AAC1E,SAAO,OAAO,MAAM,YAAY,MAAM,QAChC,EAAqC,qBAAqB;AAClE;AAEO,SAAS,kBAAkB,GAAkC;AAClE,SAAO,OAAO,MAAM,YAAY,MAAM,QAChC,EAA0C,0BAA0B;AAC5E;;;AC5CO,IAAM,6BAAN,cAAyC,MAAM;AAAA,EACpD,YAA4B,YAAoC,OAAe;AAC7E,UAAM,UAAU,KAAK,oBAAoB,UAAU,0HAC2C;AAFpE;AAAoC;AAG9D,SAAK,OAAO;AAAA,EACd;AAAA,EAJ4B;AAAA,EAAoC;AAKlE;AAEO,IAAM,4BAAN,cAAwC,MAAM;AAAA,EACnD,YAA4B,YAAoC,OAAe,QAAgB;AAC7F,UAAM,qBAAqB,KAAK,oBAAoB,UAAU,wBAAwB,MAAM,EAAE;AADpE;AAAoC;AAE9D,SAAK,OAAO;AAAA,EACd;AAAA,EAH4B;AAAA,EAAoC;AAIlE;;;ACVO,SAAS,wBACd,YACA,QACoB;AACpB,QAAM,UAA+C,CAAC;AACtD,QAAM,QAAQ,CAAC,OAAe,SAAoC;AAChE,QAAI,QAAQ,KAAK,MAAM,QAAW;AAChC,YAAM,IAAI;AAAA,QAAsB;AAAA,QAC9B,UAAU,KAAK;AAAA,MAC8C;AAAA,IACjE;AACA,YAAQ,KAAK,IAAI;AAAA,EACnB;AACA,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,QAAI,kBAAkB,KAAK,GAAG;AAC5B,iBAAW,CAAC,OAAO,IAAI,KAAK,OAAO,QAAQ,MAAM,OAAO,EAAG,OAAM,OAAO,IAAI;AAAA,IAC9E,OAAO;AACL,YAAM,KAAK,KAAK;AAAA,IAClB;AAAA,EACF;AACA,QAAM,gBAA8E,CAAC;AACrF,aAAW,CAAC,OAAO,IAAI,KAAK,OAAO,QAAQ,OAAO,GAAG;AACnD,eAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,KAAK,UAAU,CAAC,CAAC,GAAG;AAC7D,YAAM,YAAY,GAAG,KAAK,IAAI,IAAI;AAClC,UAAI,QAAQ,SAAS,MAAM,UAAa,cAAc,SAAS,MAAM,QAAW;AAC9E,cAAM,IAAI,sBAAsB,YAAY,oBAAoB,SAAS,kCAAkC;AAAA,MAC7G;AACA,oBAAc,SAAS,IAAI,CAAC,WAC1B,OAAO,KAAK,MAAM,SAAY,SAAY,MAAM,OAAO,KAAK,CAAC;AAAA,IACjE;AAAA,EACF;AACA,SAAO,EAAE,SAAS,cAAc;AAClC;;;ACvCO,SAAS,uBACd,QACA,SACA,YACM;AACN,aAAW,CAAC,OAAO,IAAI,KAAK,OAAO,QAAQ,OAAO,GAAG;AACnD,UAAM,QAAQ,OAAO,KAAK;AAC1B,QAAI,UAAU,OAAW;AACzB,QAAI,UAAU,QAAQ,KAAK,YAAY,cAAe;AACtD,QAAI,KAAK,YAAY,QAAS,OAAM,IAAI,2BAA2B,YAAY,KAAK;AACpF,UAAM,UAAU,KAAK,WAAW,KAAK,KAAK;AAC1C,QAAI,YAAY,KAAM,OAAM,IAAI,0BAA0B,YAAY,OAAO,OAAO;AAAA,EACtF;AACF;;;ACqBO,IAAM,gBAAoC;AAAA,EAC/C,MAAM,SAAS;AAAE,UAAM,IAAI,0BAA0B;AAAA,EAAE;AAAA,EACvD,MAAM,SAAS;AAAE,UAAM,IAAI,0BAA0B;AAAA,EAAE;AAAA,EACvD,MAAM,aAAa;AAAE,UAAM,IAAI,0BAA0B;AAAA,EAAE;AAAA,EAC3D,MAAM,aAAa;AAAE,UAAM,IAAI,0BAA0B;AAAA,EAAE;AAC7D;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/kernel/query/join.ts","../src/kernel/query/live.ts","../src/with-lookup/aggregate/strategy.ts","../src/with-shape/money/paths.ts","../src/with-shape/money/normalize.ts","../src/kernel/query/builder.ts","../src/kernel/query/scan-builder.ts"],"sourcesContent":["/**\n * Query DSL `.join()` — eager, single-FK, intra-vault joins.\n *\n * resolves a ref()-declared foreign key into an attached\n * right-side record under an alias, using one of two planner paths\n * selected automatically:\n *\n * - **nested-loop** — right-side source exposes `lookupById`, so\n * each left row costs O(1). This is the common path for joins\n * against a Collection, which backs `lookupById` with a Map\n * lookup.\n * - **hash** — right-side has only `snapshot()`. Build a\n * `Map<id, record>` once, probe per left row. Same asymptotic\n * cost for our collections, but the path exists as a fallback\n * for custom QuerySource implementations and as an explicit\n * test-only override via `{ strategy: 'hash' }`.\n *\n * Scope:\n *\n * - Equi-joins on declared `ref()` fields only. Joins on\n * undeclared fields throw at plan time with an actionable error\n * naming the field and collection.\n * - Same-vault only. Cross-vault correlation goes\n * through `queryAcross`; this is an architectural\n * invariant, not a limitation we plan to lift.\n * - Hard row ceiling via `JoinTooLargeError` — default 50k per\n * side, override via `{ maxRows }`. Warns at 80% of the ceiling\n * on the existing warn channel.\n * - Three ref-mode behaviors on dangling refs:\n * strict → `DanglingReferenceError`,\n * warn → attach `null` with a one-shot warning,\n * cascade → attach `null` silently (cascade is a delete-time\n * mode; any dangling refs still present at read time are\n * mid-flight cascades or orphans from earlier, not a DSL error).\n *\n * Partition-awareness seam:\n *\n * Every `JoinLeg` carries a `partitionScope` field that is always\n * `'all'` in. The executor never reads this field.\n * partition-aware joins will start populating it from `where()`\n * predicates on the partition key without changing the planner's\n * external shape — this is the whole reason it exists now.\n *\n * Joins stay OUT of the ledger: reads don't touch `_ledger/`,\n * including joined reads.\n */\n\nimport type { RefDescriptor, RefMode } from '../refs.js'\nimport { readPath } from './predicate.js'\nimport { JoinTooLargeError, DanglingReferenceError } from '../errors.js'\nimport { applyI18nLocale, type I18nTextDescriptor } from '../../with-shape/i18n/core.js'\n\n/** Planner strategy for a single join leg. Auto-selected unless overridden. */\nexport type JoinStrategy = 'hash' | 'nested'\n\n/** Default per-side row ceiling before `.join()` throws `JoinTooLargeError`. */\nexport const DEFAULT_JOIN_MAX_ROWS = 50_000\n\n/**\n * Fraction of the row ceiling at which a one-shot warning is emitted.\n * At 80% we warn; at 100% we throw. The warn gives consumers a\n * heads-up before the hard error so they can raise the ceiling or\n * filter further without first hitting a broken query.\n */\nconst JOIN_WARN_FRACTION = 0.8\n\n/**\n * Internal representation of a single join leg in the query plan.\n *\n * This is the primary place where constraint #1 is honored:\n * every leg carries a `partitionScope` field that is always `'all'`\n * in and is never read by the executor. partition-aware\n * joins will start populating it from `where()` predicates on the\n * partition key without changing the planner's external shape.\n */\nexport interface JoinLeg {\n /** Field on the left-side record holding the foreign key value. */\n readonly field: string\n /** Alias key under which the joined right-side record attaches. */\n readonly as: string\n /** Target collection name, resolved from the `ref()` declaration. */\n readonly target: string\n /** Ref mode controlling behavior on dangling refs at read time. */\n readonly mode: RefMode\n /** Manual planner strategy override. `undefined` → auto-select. */\n readonly strategy: JoinStrategy | undefined\n /** Per-side row ceiling override. `undefined` → DEFAULT_JOIN_MAX_ROWS. */\n readonly maxRows: number | undefined\n /**\n * Partition scope for future partition-aware joins. Always `'all'`\n * today — the executor never reads this field. Future versions will\n * populate it from `where()` predicates without breaking the\n * planner's external shape. Do not remove even though it looks\n * unused today — that's the whole point of having it.\n */\n readonly partitionScope: 'all' | readonly string[]\n /**\n * When `true`, this is a dictionary join. The executor\n * resolves the left-field value against the dict snapshot and\n * attaches `{ ...labels, key }` rather than a right-side record.\n * `target` holds the dictionary name (not a collection name).\n */\n readonly isDictJoin?: true\n}\n\n/**\n * Minimal shape of a joinable right-side record source.\n *\n * Collections implement this structurally via their `QuerySource`;\n * sources without `lookupById` force the hash-join fallback. Kept as\n * a thin interface so tests can wire up plain-object sources without\n * pulling in the full Collection class.\n *\n * The optional `subscribe` is used by `Query.live()` to merge\n * right-side change streams into the live re-run trigger. Sources\n * that omit `subscribe` still work for live joins — they just\n * don't drive re-fires when their right side mutates. Collection\n * implements `subscribe` by hooking into the existing per-\n * vault event emitter.\n */\nexport interface JoinableSource {\n snapshot(): readonly unknown[]\n lookupById?(id: string): unknown\n /**\n * Default locale a label-resolving query falls back to when the query\n * itself is locale-less. Set by a `staticDict()`-backed source from its\n * `displayLocale` so `{ by: 'label' }` resolves under a locale-less read.\n * Plain `_dict_*`-backed sources omit it.\n */\n readonly displayLocale?: string\n /**\n * i18nText descriptors of the right-side collection (`join`\n * layer). When present and the query carries a locale, each joined\n * right-side record's i18n fields resolve to that locale at the `join`\n * layer (`resolvePolicy(onMissing, 'join')`) BEFORE it is attached under\n * the leg's alias — so a joined `i18nText` field is a resolved string, not\n * a raw `{ locale }` map. Locale-less queries leave joined fields raw\n * (consistent with a locale-less read).\n */\n readonly i18nFields?: Record<string, I18nTextDescriptor>\n /**\n * Subscribe to mutations on this source. The callback fires\n * AFTER the underlying record set has been updated. Returns an\n * unsubscribe function. Optional — sources without this method\n * cannot trigger live-join re-fires from their side.\n */\n subscribe?(cb: () => void): () => void\n}\n\n/**\n * Join resolution context attached to a `Query` when it's constructed\n * from a `Collection`. Holds everything the `.join()` method needs to\n * translate a field name into a target collection + ref mode, and\n * everything the executor needs to read the right side.\n *\n * Kept as a structural interface so `Vault` can implement it\n * without `Query` needing to import `Vault` (circular-import\n * avoid). The Collection wires this up in its `query()` method using\n * the `joinResolver` back-reference the Vault passes in.\n */\nexport interface JoinContext {\n /** Name of the left-side (owning) collection. */\n readonly leftCollection: string\n /**\n * The owning collection's default locale. Used to resolve joined\n * i18n fields at the `join` layer when a terminal call doesn't pass an\n * explicit locale — so `openVault({ locale })` flows to joins like it does\n * to `get`/`list`. A per-call `toArray({ locale })` overrides it.\n */\n readonly defaultLocale?: string\n /** Look up a `RefDescriptor` by field name on the left collection. */\n resolveRef(field: string): RefDescriptor | null\n /** Resolve a right-side source by target collection name. */\n resolveSource(collectionName: string): JoinableSource | null\n /**\n * Resolve a dictKey join source. Returns a `JoinableSource`\n * whose snapshot exposes `{ key, ...labels }` records, keyed by the\n * stable dictionary key. `null` when the field is not a dictKey.\n *\n * The source is built from the compartment's in-memory dictionary\n * snapshot — same data as `DictionaryHandle.list()`, O(1) per lookup.\n */\n resolveDictSource?(field: string): JoinableSource | null\n}\n\n/**\n * Coerce an unknown FK value into a lookup key string.\n *\n * Legitimate ref values are strings or numbers — the same narrowing\n * the write-time `enforceRefsOnPut` path applies. Anything else\n * (objects, arrays, booleans, null, undefined) is treated as \"no\n * ref\" and returns `null`, so the join attaches `null` instead of\n * running `String({})` and producing `'[object Object]'` as a\n * bucket key. This matches the lint rule guidance and keeps\n * bizarre FK values from producing silently-wrong lookups.\n */\nfunction coerceRefKey(value: unknown): string | null {\n if (value === null || value === undefined) return null\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'bigint') return String(value)\n return null\n}\n\n/**\n * Warn-channel deduplication for dangling-ref `'warn'` mode. Keyed\n * by `field → target:refId` so the same dangling ref only produces\n * one warning even across many rows or repeated queries.\n */\nconst warnedDanglingKeys = new Set<string>()\nfunction warnOnceDangling(field: string, target: string, refId: string): void {\n const key = `${field}→${target}:${refId}`\n if (warnedDanglingKeys.has(key)) return\n warnedDanglingKeys.add(key)\n console.warn(\n `[noy-db] .join() encountered dangling ref in 'warn' mode: ` +\n `field \"${field}\" → \"${target}:${refId}\" not found. Attaching null.`,\n )\n}\n\n/**\n * Track row-ceiling warnings to fire only once per (target, side).\n * Prevents per-query spam when a consumer is running the same query\n * repeatedly (e.g. in a reactive loop).\n */\nconst warnedCeilingKeys = new Set<string>()\nfunction warnCeilingApproaching(\n target: string,\n side: 'left' | 'right',\n rows: number,\n maxRows: number,\n): void {\n const key = `${target}:${side}`\n if (warnedCeilingKeys.has(key)) return\n warnedCeilingKeys.add(key)\n const pct = Math.round((rows / maxRows) * 100)\n console.warn(\n `[noy-db] .join() ${side} side is at ${pct}% of the ${maxRows}-row ` +\n `ceiling for target \"${target}\" (${rows} rows). Streaming joins over ` +\n `scan() are not yet supported for collections that need to exceed this.`,\n )\n}\n\n/**\n * Apply every join leg in the plan against a base set of left-side\n * rows. Called by the query executor after `where` / `orderBy` /\n * `offset` / `limit` have narrowed the left set.\n *\n * Each leg attaches a `leg.as` field to every row. Returns a new\n * array of plain objects — the original left rows are not mutated\n * (structural sharing is fine for the inner fields, but the\n * top-level object is a fresh clone so consumers can further mutate\n * safely).\n *\n * **Ordering:** joins run AFTER orderBy / limit / offset in v1.\n * This keeps the planner simple and means queries like \"top 10\n * invoices with client\" sort and paginate the left side first, then\n * join. Sorting *by* a joined field is out of scope for — users\n * can post-sort the result array in userland or wait for \n * (multi-FK chaining) which can be layered on top.\n *\n * **Multi-FK chaining:** each leg's `maxRows` is enforced\n * against the current left-row count independently. Because\n * joins are equi-joins on the target's primary key (one-to-one or\n * one-to-null), the left row count is constant across legs — no\n * cartesian blowup. The per-leg left-side check is still necessary\n * so that a later leg with a tighter ceiling correctly fires on a\n * query like `.join('a', { maxRows: 100_000 }).join('b', { maxRows: 50 })`,\n * which should throw on the second leg if the left set exceeds 50.\n */\nexport function applyJoins(\n rows: readonly unknown[],\n joins: readonly JoinLeg[],\n context: JoinContext,\n locale?: string,\n): unknown[] {\n if (joins.length === 0) return [...rows]\n\n let result: unknown[] = [...rows]\n for (const leg of joins) {\n result = applyOneJoin(result, leg, context, locale)\n }\n return result\n}\n\nfunction applyOneJoin(\n leftRows: readonly unknown[],\n leg: JoinLeg,\n context: JoinContext,\n locale?: string,\n): unknown[] {\n // Dict join path — resolve left-field value against the\n // dictionary snapshot and attach { key, ...labels } under leg.as.\n if (leg.isDictJoin) {\n const dictSource = context.resolveDictSource?.(leg.field)\n if (!dictSource) {\n throw new Error(\n `.join() field \"${leg.field}\" on \"${context.leftCollection}\" is declared as a ` +\n `dictKey join but the dict source could not be resolved. ` +\n `Ensure the dictionary has at least one entry.`,\n )\n }\n const out: unknown[] = []\n const snapshot = dictSource.snapshot()\n const dictMap = new Map<string, unknown>()\n for (const entry of snapshot) {\n const k = readPath(entry, 'key')\n if (typeof k === 'string') dictMap.set(k, entry)\n }\n for (const left of leftRows) {\n const rawId = readPath(left, leg.field)\n const key = coerceRefKey(rawId)\n const dictEntry = key === null ? undefined : dictMap.get(key)\n out.push({ ...(left as Record<string, unknown>), [leg.as]: dictEntry ?? null })\n }\n return out\n }\n\n const source = context.resolveSource(leg.target)\n if (!source) {\n throw new Error(\n `.join() cannot resolve target collection \"${leg.target}\" ` +\n `(referenced from field \"${leg.field}\" on \"${context.leftCollection}\"). ` +\n `Make sure the target collection has been opened via vault.collection() ` +\n `at least once before running the query.`,\n )\n }\n\n const maxRows = leg.maxRows ?? DEFAULT_JOIN_MAX_ROWS\n\n // Per-leg left-side ceiling check. In a\n // multi-FK chain, each leg's `maxRows` is enforced independently\n // against the current left-row count, so\n // `.join('a', { maxRows: 100_000 }).join('b', { maxRows: 50 })`\n // correctly throws on the second leg if the left set exceeds 50.\n if (leftRows.length > maxRows) {\n throw new JoinTooLargeError({\n leftRows: leftRows.length,\n rightRows: -1,\n maxRows,\n side: 'left',\n message:\n `.join() left side has ${leftRows.length} rows, exceeding the ${maxRows}-row ` +\n `ceiling for target \"${leg.target}\". Filter the left side further with ` +\n `where()/limit() before joining, or raise the ceiling via { maxRows }. ` +\n `Streaming joins over scan() are not yet supported.`,\n })\n }\n if (leftRows.length > maxRows * JOIN_WARN_FRACTION) {\n warnCeilingApproaching(leg.target, 'left', leftRows.length, maxRows)\n }\n\n const rightSnapshot = source.snapshot()\n if (rightSnapshot.length > maxRows) {\n throw new JoinTooLargeError({\n leftRows: leftRows.length,\n rightRows: rightSnapshot.length,\n maxRows,\n side: 'right',\n message:\n `.join() right side \"${leg.target}\" has ${rightSnapshot.length} rows, ` +\n `exceeding the ${maxRows}-row ceiling. Raise the ceiling via { maxRows } ` +\n `if the data genuinely fits in memory, or track for streaming joins.`,\n })\n }\n if (rightSnapshot.length > maxRows * JOIN_WARN_FRACTION) {\n warnCeilingApproaching(leg.target, 'right', rightSnapshot.length, maxRows)\n }\n\n // `join`-layer i18n. When the query carries a locale (per-call\n // or the owning collection's default) and the right side declares i18n\n // fields, resolve each matched right record's i18n fields to that locale\n // at the `join` layer before it's attached. Locale-less → leave raw.\n const effLocale = locale ?? context.defaultLocale\n const i18nResolve: ((right: unknown) => unknown) | undefined =\n effLocale !== undefined && source.i18nFields !== undefined\n ? (right) =>\n right !== null && typeof right === 'object'\n ? applyI18nLocale(right as Record<string, unknown>, source.i18nFields!, effLocale, undefined, 'join')\n : right\n : undefined\n\n // Strategy selection: explicit override wins; otherwise prefer\n // nested-loop when the source exposes lookupById (O(1) per row),\n // falling back to hash join when it doesn't.\n const strategy: JoinStrategy =\n leg.strategy ?? (source.lookupById ? 'nested' : 'hash')\n\n if (strategy === 'nested' && source.lookupById) {\n // Bind through an arrow so the `this` context of lookupById\n // doesn't drift — same pattern as the existing candidateRecords\n // helper in builder.ts.\n const lookup = (id: string): unknown => source.lookupById?.(id)\n return nestedLoopJoin(leftRows, leg, lookup, i18nResolve)\n }\n return hashJoin(leftRows, leg, rightSnapshot, i18nResolve)\n}\n\nfunction nestedLoopJoin(\n leftRows: readonly unknown[],\n leg: JoinLeg,\n lookupById: (id: string) => unknown,\n i18nResolve?: (right: unknown) => unknown,\n): unknown[] {\n const out: unknown[] = []\n for (const left of leftRows) {\n const rawId = readPath(left, leg.field)\n const key = coerceRefKey(rawId)\n let right = key === null ? undefined : lookupById(key)\n if (i18nResolve && right !== undefined) right = i18nResolve(right)\n out.push(attachJoin(left, leg, right, rawId))\n }\n return out\n}\n\nfunction hashJoin(\n leftRows: readonly unknown[],\n leg: JoinLeg,\n rightSnapshot: readonly unknown[],\n i18nResolve?: (right: unknown) => unknown,\n): unknown[] {\n // Build the right-side hash once per query execution. We key on\n // the `id` field because ref() always points to a target's primary\n // key — non-equi and non-id joins are out of scope for.\n const rightMap = new Map<string, unknown>()\n for (const record of rightSnapshot) {\n const rawId = readPath(record, 'id')\n const key = coerceRefKey(rawId)\n if (key !== null) {\n rightMap.set(key, record)\n }\n }\n const out: unknown[] = []\n for (const left of leftRows) {\n const rawId = readPath(left, leg.field)\n const key = coerceRefKey(rawId)\n let right = key === null ? undefined : rightMap.get(key)\n if (i18nResolve && right !== undefined) right = i18nResolve(right)\n out.push(attachJoin(left, leg, right, rawId))\n }\n return out\n}\n\n/**\n * Attach the resolved right-side record (or null) to the left row\n * under the alias, applying ref-mode semantics for the dangling\n * case.\n *\n * A left-side record whose FK field is null/undefined is NOT a\n * dangling ref — it's \"no reference at all\", which is always\n * allowed regardless of mode. This matches the write-time\n * `enforceRefsOnPut` behavior: \"Nullish ref values are allowed —\n * treat them as 'no reference'.\"\n *\n * Only non-null FKs pointing at non-existent targets trigger the\n * mode behavior.\n */\nfunction attachJoin(\n left: unknown,\n leg: JoinLeg,\n right: unknown,\n rawId: unknown,\n): unknown {\n if (left === null || typeof left !== 'object') {\n // Pathological input — return as-is. Shouldn't happen in\n // practice because QuerySource yields objects, but defensive\n // because plan execution is untyped at this layer.\n return left\n }\n const merged: Record<string, unknown> = { ...(left as Record<string, unknown>) }\n\n // \"No ref at all\" — null/undefined FK value, or a non-string/non-\n // number FK that coerceRefKey treated as no-ref. Never throws\n // regardless of mode; matches the write-time policy that nullish\n // refs are allowed.\n const refKey = coerceRefKey(rawId)\n if (right === undefined) {\n if (refKey !== null && leg.mode === 'strict') {\n throw new DanglingReferenceError({\n field: leg.field,\n target: leg.target,\n refId: refKey,\n message:\n `.join() strict dangling: record references \"${leg.target}:${refKey}\" ` +\n `via field \"${leg.field}\", but no such record exists. Use ref() mode 'warn' ` +\n `or 'cascade' if dangling refs are acceptable, or run ` +\n `vault.checkIntegrity() to find and fix the orphans.`,\n })\n }\n if (refKey !== null && leg.mode === 'warn') {\n warnOnceDangling(leg.field, leg.target, refKey)\n }\n // For 'cascade' and null refs we attach null silently. Cascade\n // is a delete-time mode; any dangling refs visible at read time\n // are either mid-flight or pre-existing orphans, not a DSL error.\n merged[leg.as] = null\n } else {\n merged[leg.as] = right\n }\n return merged\n}\n\n/**\n * Test-only: reset the join warning deduplication state between\n * tests. Production code never calls this — the dedup state is\n * intentionally process-scoped so a noisy query doesn't spam the\n * console once per component render.\n */\nexport function resetJoinWarnings(): void {\n warnedDanglingKeys.clear()\n warnedCeilingKeys.clear()\n}\n","/**\n * Reactive query primitive — `query.live()`.\n *\n * produces a `LiveQuery<T>` that re-runs the query and\n * updates its `value` whenever any source feeding it (the left\n * collection AND every right-side collection a join leg points at)\n * mutates.\n *\n * Framework-agnostic by design. The Vue layer wraps a `LiveQuery`\n * in a Vue `Ref<T[]>` by subscribing once and copying `value` into\n * the ref on every notification. React/Solid/Svelte adapters do the\n * same with their own primitives. Core never depends on a UI\n * framework.\n *\n * **Error semantics.** A `.live()` query may throw at re-run time —\n * a strict-mode `DanglingReferenceError` is the most common case\n * (a right-side record was deleted out-of-band, leaving a left\n * row's FK pointing at nothing). When the re-run throws, the\n * `LiveQuery` catches the error and stores it in the `error`\n * field; it does NOT propagate the throw out of the source's\n * change handler, because doing so would tear down whatever\n * upstream emitter is dispatching. Listeners check `error` after\n * each notification and render an error state in the UI.\n *\n * **Dedup of right-side subscriptions.** A multi-FK chain that\n * joins the same target twice (e.g.\n * `.join('billingClientId').join('shippingClientId')`, both\n * pointing at `clients`) only subscribes to that target once. We\n * dedup by target collection name, on the assumption that\n * `resolveSource(name)` returns a single subscribable source per\n * vault + name. Vault's `resolveSource` reads from\n * `collectionCache` so this assumption holds.\n *\n * **What .live() does NOT do in v1:**\n * - No granular delta updates — the whole query re-runs on every\n * change. Granular delta tracking is a v2 optimization once\n * the API is stable.\n * - No batching of bursty changes — one event in, one re-run\n * out. Batching with microtask coalescing is a v2 enhancement.\n * - No async notifications — every notification is synchronous\n * within the source's change handler.\n * - No re-planning under live mutations — the planner picks once\n * at subscription time and reuses the same plan for every\n * re-run.\n */\n\n/**\n * The reactive primitive returned by `Query.live()`.\n *\n * Listeners can read the current `value` snapshot at any time and\n * subscribe to changes via `.subscribe(cb)`. The `error` field\n * carries the most recent re-run error, if any — read it after\n * each notification to render error state.\n *\n * Always call `stop()` when the live query is no longer needed.\n * Without it, the upstream change-stream subscriptions stay live\n * forever and the query keeps re-running on every mutation.\n */\nexport interface LiveQuery<T> {\n /**\n * Current snapshot of the query result. Updated in place on\n * every upstream change. The reference returned is the same\n * `readonly T[]` array — consumers that want change detection by\n * reference should copy: `const arr = [...live.value]`.\n */\n readonly value: readonly T[]\n /**\n * Most recent re-run error, or `null` on success. Set when the\n * executor throws (e.g. `DanglingReferenceError` in strict mode\n * after a right-side delete). Cleared on the next successful\n * re-run.\n */\n readonly error: Error | null\n /**\n * Register a notification callback. Fires AFTER `value` and\n * `error` have been updated for a given upstream change.\n * Returns an unsubscribe function.\n *\n * The first call to `subscribe` does NOT fire the callback\n * immediately — call sites that want the initial value should\n * read `live.value` directly before subscribing.\n */\n subscribe(cb: () => void): () => void\n /**\n * Tear down every upstream subscription and clear the listener\n * set. Idempotent — calling twice is safe. After `stop()`, the\n * query no longer re-runs and `subscribe()` becomes a no-op\n * (the returned unsubscribe is still callable and is also a\n * no-op).\n */\n stop(): void\n}\n\n/**\n * Internal subscription handle for an upstream source — left or\n * right side. The contract is just `subscribe(cb): unsubscribe`,\n * matching the existing `QuerySource.subscribe` and the new\n * `JoinableSource.subscribe` (added in ).\n */\nexport interface LiveUpstream {\n subscribe(cb: () => void): () => void\n}\n\n/**\n * Build a LiveQuery from a `recompute` callback (typically the\n * Query's bound `toArray`) and a list of upstream sources to\n * subscribe to.\n *\n * The recompute fires once synchronously to populate the initial\n * value, then re-fires every time any upstream notifies. Errors\n * thrown by recompute are caught and stored in `error` instead of\n * propagating — see the file docstring for the rationale.\n */\nexport function buildLiveQuery<T>(\n recompute: () => T[],\n upstreams: readonly LiveUpstream[],\n): LiveQuery<T> {\n return new LiveQueryImpl<T>(recompute, upstreams)\n}\n\nclass LiveQueryImpl<T> implements LiveQuery<T> {\n private _value: readonly T[] = []\n private _error: Error | null = null\n private readonly listeners = new Set<() => void>()\n private readonly unsubs: Array<() => void> = []\n private stopped = false\n\n constructor(\n private readonly recompute: () => T[],\n upstreams: readonly LiveUpstream[],\n ) {\n // Initial compute. If this throws, the constructor still\n // succeeds — we want consumers to be able to render an error\n // state from `live.error` rather than wrapping every\n // `query.live()` call in a try/catch.\n this.refresh()\n for (const upstream of upstreams) {\n try {\n this.unsubs.push(upstream.subscribe(this.onUpstreamChange))\n } catch (err) {\n // Upstream subscription failed — record it as the live\n // error and continue with the upstreams that did work.\n // The LiveQuery is now degraded (won't re-fire on this\n // upstream's changes) but isn't broken; consumers can\n // detect this via `live.error`.\n this._error = err instanceof Error ? err : new Error(String(err))\n }\n }\n }\n\n get value(): readonly T[] {\n return this._value\n }\n\n get error(): Error | null {\n return this._error\n }\n\n /**\n * Bound change handler — used as the callback passed to every\n * upstream's subscribe. Bound via class field so the `this`\n * context survives the indirect call from arbitrary upstreams.\n */\n private readonly onUpstreamChange = (): void => {\n this.refresh()\n for (const cb of this.listeners) {\n try {\n cb()\n } catch {\n // Listener errors are isolated — one buggy consumer\n // doesn't break the others or tear down the live query.\n }\n }\n }\n\n private refresh(): void {\n if (this.stopped) return\n try {\n this._value = this.recompute()\n this._error = null\n } catch (err) {\n this._error = err instanceof Error ? err : new Error(String(err))\n // Don't clobber the previous value on error — consumers\n // typically want to keep showing the last known good state\n // alongside the error message rather than flashing to an\n // empty list.\n }\n }\n\n subscribe(cb: () => void): () => void {\n if (this.stopped) return () => {}\n this.listeners.add(cb)\n return () => this.listeners.delete(cb)\n }\n\n stop(): void {\n if (this.stopped) return\n this.stopped = true\n for (const unsub of this.unsubs) {\n try {\n unsub()\n } catch {\n // Unsub errors are swallowed — at this point we're tearing\n // down anyway and the failure is noise.\n }\n }\n this.unsubs.length = 0\n this.listeners.clear()\n }\n}\n","/**\n * Strategy seam between the core Query / ScanBuilder chain and the\n * optional aggregate / groupBy service. Core imports\n * `AggregateStrategy` as a TYPE-ONLY symbol and `NO_AGGREGATE` as a\n * tiny runtime stub.\n *\n * The heavy machinery — `Aggregation`, `GroupedQuery`, the\n * reducer-step logic — is only reachable from `withAggregate()` in\n * `./active.ts`, which is only exported through the\n * `@noy-db/hub/aggregate` subpath. Consumers that don't import the\n * subpath ship none of the ~886 LOC.\n *\n * @internal\n */\n\nimport type {\n Aggregation,\n AggregateSpec,\n AggregateResult,\n AggregationUpstream,\n} from './aggregation.js'\nimport type { GroupedQuery, GroupedQueryN } from './groupby.js'\nimport type { MoneyDescriptor } from '../../with-shape/money/descriptor.js'\n\n/**\n * Seam interface. `@internal` — will promote to public only when the\n * aggregate service is extracted into its own package.\n *\n * @internal\n */\nexport interface AggregateStrategy {\n /**\n * Build an `Aggregation<R>` for `Query.aggregate(spec)`. `executeRecords`\n * is a closure that produces the matching record set when the\n * aggregation runs. NO_AGGREGATE throws; the active strategy\n * constructs a real `Aggregation`.\n */\n aggregate<Spec extends AggregateSpec>(\n executeRecords: () => readonly unknown[],\n spec: Spec,\n upstreams: readonly AggregationUpstream[],\n ): Aggregation<AggregateResult<Spec>>\n\n /**\n * Build a `GroupedQuery<T, F, S>` for `Query.groupBy(field)`. Same\n * closure / upstream inputs as `aggregate` plus the group key field.\n */\n groupBy<T, F extends string, S extends keyof T = never, M extends keyof T & string = never>(\n executeRecords: () => readonly unknown[],\n field: F,\n upstreams: readonly AggregationUpstream[],\n dictLabelResolver?: (\n key: string,\n locale: string,\n fallback?: string | readonly string[],\n ) => Promise<string | undefined>,\n moneyFields?: Record<string, MoneyDescriptor>,\n ): GroupedQuery<T, F, S, M>\n\n /**\n * Variadic-keyed sibling — builds a `GroupedQueryN<T, F, S>` for\n * `Query.groupBy(...fields)`. No dictLabelResolver — `<field>Label`\n * projection only applies to single-field groupings, which dispatch\n * through `groupBy` above.\n */\n groupByN<T, F extends readonly string[], S extends keyof T = never, M extends keyof T & string = never>(\n executeRecords: () => readonly unknown[],\n fields: F,\n upstreams: readonly AggregationUpstream[],\n moneyFields?: Record<string, MoneyDescriptor>,\n ): GroupedQueryN<T, F, S, M>\n\n /**\n * Terminal streaming aggregator for `ScanBuilder.aggregate(spec)`.\n * Takes an async iterable of decrypted records + the spec and\n * returns the reduced result.\n */\n scanAggregate<Spec extends AggregateSpec>(\n iter: AsyncIterable<unknown>,\n spec: Spec,\n ): Promise<AggregateResult<Spec>>\n}\n\nconst NOT_ENABLED = new Error(\n 'Aggregate / groupBy is not enabled on this Noydb instance. ' +\n 'Import `{ withAggregate }` from \"@noy-db/hub/aggregate\" and pass it to ' +\n '`createNoydb({ aggregateStrategy: withAggregate() })`.',\n)\n\n/**\n * No-aggregate stub. Every `.aggregate()` / `.groupBy()` / streaming\n * `scan().aggregate()` call throws with a pointer at the subpath. The\n * real `Aggregation` / `GroupedQuery` classes are never referenced at\n * runtime, so the bundler drops the ~886 LOC.\n *\n * @internal\n */\nexport const NO_AGGREGATE: AggregateStrategy = {\n aggregate() { throw NOT_ENABLED },\n groupBy() { throw NOT_ENABLED },\n groupByN() { throw NOT_ENABLED },\n scanAggregate() { throw NOT_ENABLED },\n}\n","/**\n * Nested-path support for `moneyFields` declarations.\n *\n * A descriptor map key is a PATH, not just a top-level field name:\n *\n * - `'total'` — top-level scalar (the original form)\n * - `'billing.monthlyServiceFee'` — nested object member\n * - `'lineItems[].amount'` — member of every element of an array\n * - `'summary.*'` — every value of a record/map object\n * - `'income[].taxWithheld'`, `'byMonth.*.amount'` — segments compose\n *\n * Paths are parsed ONCE, at collection registration, and a syntactically\n * invalid declaration throws there — loudly, at setup time. (The\n * historical failure mode this kills: a declared-but-unreachable path\n * that was silently ignored, leaving the field un-quantized — a latent\n * 100× bug.) A path that doesn't match a given record at write time is\n * NOT an error — optional fields and empty arrays are legitimate — but\n * a path segment that hits a value of the WRONG SHAPE (`[]` on a\n * non-array, `.*` on a non-object) throws, because that means the\n * declaration and the data model disagree.\n */\n\nimport { ValidationError } from '../../kernel/errors.js'\n\nexport type MoneyPathSegment =\n | { readonly kind: 'key'; readonly key: string; readonly array: boolean }\n | { readonly kind: 'wildcard'; readonly array: boolean }\n\nconst SEGMENT_RE = /^(\\*|[^.[\\]*]+)(\\[\\])?$/\n\nconst parseCache = new Map<string, readonly MoneyPathSegment[]>()\n\n/**\n * Parse a moneyFields path into segments. Throws `ValidationError` on\n * bad syntax. Results are memoized — the same declared paths are walked\n * on every write/read.\n */\nexport function parseMoneyPath(path: string): readonly MoneyPathSegment[] {\n const cached = parseCache.get(path)\n if (cached) return cached\n\n if (typeof path !== 'string' || path.length === 0) {\n throw new ValidationError('moneyFields: path must be a non-empty string')\n }\n const segments: MoneyPathSegment[] = []\n for (const part of path.split('.')) {\n const m = SEGMENT_RE.exec(part)\n if (!m) {\n throw new ValidationError(\n `moneyFields: invalid path \"${path}\" — segment \"${part}\" must be a key, \"key[]\", \"*\", or \"*[]\"`,\n )\n }\n const array = m[2] === '[]'\n segments.push(\n m[1] === '*' ? { kind: 'wildcard', array } : { kind: 'key', key: m[1]!, array },\n )\n }\n parseCache.set(path, segments)\n return segments\n}\n\n/** True when the path is a plain top-level field name (the fast path). */\nexport function isSimpleMoneyPath(path: string): boolean {\n return !path.includes('.') && !path.includes('[') && !path.includes('*')\n}\n\n/**\n * Validate every declared path's syntax. Call at collection\n * registration so typos throw at setup, not silently no-op per write.\n */\nexport function validateMoneyFieldPaths(moneyFields: Record<string, unknown>): void {\n for (const path of Object.keys(moneyFields)) parseMoneyPath(path)\n}\n\n/**\n * The leaf visitor: receives the (already-cloned) container holding the\n * money value and the key/index of that value. Mutating the container\n * is safe — every container on a matched path is a fresh clone.\n */\nexport type MoneyLeafVisitor = (\n container: Record<string, unknown> | unknown[],\n key: string | number,\n) => void\n\n/**\n * Walk `node` along `segments`, copy-on-write-cloning every container on\n * a matched path, and call `visit` at each leaf position. Returns the\n * (possibly new) node; unmatched paths return the input untouched.\n *\n * Shape mismatches (`[]` on a non-array, `*` on a non-object) THROW on\n * the write path — the declaration and the data model disagree, and\n * writing through would store an un-quantized amount. On the READ path\n * pass `lenient: true` instead: stored data predating a declaration\n * change must stay readable, so a mismatched node is returned untouched\n * (mirrors the defensive `continue` the flat decoder always had).\n */\nexport function transformAtMoneyPath(\n node: unknown,\n path: string,\n segments: readonly MoneyPathSegment[],\n index: number,\n visit: MoneyLeafVisitor,\n lenient: boolean,\n): unknown {\n if (node === null || node === undefined) return node\n const seg = segments[index]!\n const last = index === segments.length - 1\n\n if (seg.kind === 'key') {\n if (typeof node !== 'object' || Array.isArray(node)) {\n if (lenient) return node\n throw new ValidationError(\n `moneyFields: path \"${path}\" expected an object at segment \"${seg.key}\", got ${Array.isArray(node) ? 'an array' : typeof node}`,\n )\n }\n const obj = node as Record<string, unknown>\n if (!(seg.key in obj) || obj[seg.key] === null || obj[seg.key] === undefined) return node\n\n if (seg.array) {\n const arr = obj[seg.key]\n if (!Array.isArray(arr)) {\n if (lenient) return node\n throw new ValidationError(\n `moneyFields: path \"${path}\" declares \"${seg.key}[]\" but the value is not an array`,\n )\n }\n const cloned = [...arr]\n if (last) {\n for (let i = 0; i < cloned.length; i++) visit(cloned, i)\n } else {\n for (let i = 0; i < cloned.length; i++) {\n cloned[i] = transformAtMoneyPath(cloned[i], path, segments, index + 1, visit, lenient)\n }\n }\n return { ...obj, [seg.key]: cloned }\n }\n\n const clone = { ...obj }\n if (last) {\n visit(clone, seg.key)\n } else {\n clone[seg.key] = transformAtMoneyPath(clone[seg.key], path, segments, index + 1, visit, lenient)\n }\n return clone\n }\n\n // wildcard\n if (seg.array) {\n if (!Array.isArray(node)) {\n if (lenient) return node\n throw new ValidationError(`moneyFields: path \"${path}\" declares \"*[]\" but the value is not an array`)\n }\n const cloned = [...node]\n if (last) {\n for (let i = 0; i < cloned.length; i++) visit(cloned, i)\n } else {\n for (let i = 0; i < cloned.length; i++) {\n cloned[i] = transformAtMoneyPath(cloned[i], path, segments, index + 1, visit, lenient)\n }\n }\n return cloned\n }\n if (typeof node !== 'object' || Array.isArray(node)) {\n if (lenient) return node\n throw new ValidationError(\n `moneyFields: path \"${path}\" applies \"*\" to a non-object (${Array.isArray(node) ? 'array — use \"*[]\"' : typeof node})`,\n )\n }\n const obj = node as Record<string, unknown>\n const clone: Record<string, unknown> = { ...obj }\n for (const key of Object.keys(obj)) {\n const v = clone[key]\n if (v === null || v === undefined) continue\n if (last) visit(clone, key)\n else clone[key] = transformAtMoneyPath(v, path, segments, index + 1, visit, lenient)\n }\n return clone\n}\n","/**\n * Write-side and read-side normalization for money fields.\n *\n * - **Write** ({@link quantizeMoneyFields}): user input (`number | string`,\n * or `{ amount, currency }` in multi mode) → the canonical *stored*\n * form — a scaled-integer **digit string** (`'12345'`), or\n * `{ amount: '12345', currency: 'EUR' }` in multi mode. A JSON number\n * would truncate past 2^53, so the integer is always stored as a string.\n * - **Read** ({@link decodeMoneyFields}): stored form → an exact decimal\n * string (`'123.45'`) plus, when formatting is requested, the virtual\n * `<field>Formatted` (locale currency string) and `<field>Number`\n * (convenience JS number, explicitly lossy past 2^53).\n *\n * Both return a shallow clone; neither mutates the input record.\n */\n\nimport { parseToScaledInt, formatScaledInt } from './fixed-point.js'\nimport { MoneyPrecisionError, type MoneyDescriptor } from './descriptor.js'\nimport { isSimpleMoneyPath, parseMoneyPath, transformAtMoneyPath } from './paths.js'\n\ninterface MoneyValueObject {\n amount: unknown\n currency: unknown\n}\n\nfunction isMoneyValueObject(v: unknown): v is MoneyValueObject {\n return typeof v === 'object' && v !== null && 'currency' in v\n}\n\n/** Parse one decimal input to a stored digit string at `scale`. */\nfunction quantizeAmount(\n field: string,\n input: number | string,\n scale: number,\n rounding: MoneyDescriptor['rounding'],\n): string {\n const r = parseToScaledInt(input, scale, rounding)\n if (!r.ok) {\n if (r.reason === 'precision') throw new MoneyPrecisionError(field, input, scale)\n throw new TypeError(`money: field \"${field}\" value ${JSON.stringify(input)} is not a finite decimal`)\n }\n return r.value.toString()\n}\n\n/**\n * Canonicalize a STORED-form record's money fields for an internal\n * callback boundary. Gate handlers (guard `check` /\n * `frozenFields` / `onDelete`, period guard, amendment invariants) and\n * derivation `derive(source, ctx)` callbacks are user-facing: they must\n * see the same decoded canonical decimal that `get()` returns — the\n * scaled-int storage form never escapes. Decoded with `'raw'` (no\n * `<field>Formatted`/`<field>Number` virtuals): these boundaries carry\n * no locale, and fabricating one would re-create a two-read-paths\n * skew inside comparisons.\n */\nexport function canonicalizeStoredMoney(\n record: unknown,\n moneyFields: Record<string, MoneyDescriptor> | undefined,\n): unknown {\n if (record === null || record === undefined) return record\n if (!moneyFields || Object.keys(moneyFields).length === 0) return record\n return decodeMoneyFields(record as Record<string, unknown>, moneyFields, 'raw')\n}\n\n/**\n * Canonicalize an INCOMING record's money fields at the top of the\n * write pipeline. Raw user input (pre-quantize) may hold a\n * number (`10000`), a major-unit string (`'10000.00'`), or a spread of\n * an already-decoded read. Quantize→decode folds all three to the\n * canonical decimal string, so gate handlers, computed-field\n * callbacks, and schema validation all see the `get()` shape — and\n * freeze-style guards comparing `incoming[f]` vs `existing[f]` see\n * equal values for an unchanged field. Best-effort: input that fails\n * to quantize passes through unchanged — the write path quantizes\n * again after validation and surfaces the real\n * `MoneyPrecisionError`/`TypeError`.\n */\nexport function canonicalizeIncomingMoney(\n record: unknown,\n moneyFields: Record<string, MoneyDescriptor> | undefined,\n): unknown {\n if (!moneyFields || Object.keys(moneyFields).length === 0) return record\n try {\n return decodeMoneyFields(\n quantizeMoneyFields(record as Record<string, unknown>, moneyFields),\n moneyFields,\n 'raw',\n )\n } catch {\n return record\n }\n}\n\n/** Quantize ONE field value (any nesting level) to its stored form. */\nfunction quantizeValue(field: string, raw: unknown, desc: MoneyDescriptor): unknown {\n if (desc.mode === 'fixed') {\n const currency = desc.fixedCurrency!\n return quantizeAmount(field, raw as number | string, desc.scaleFor(currency), desc.rounding)\n }\n // multi mode\n let amount: number | string\n let currency: string\n if (isMoneyValueObject(raw)) {\n currency = String(raw.currency)\n amount = raw.amount as number | string\n } else {\n const sole = desc.soleCurrency()\n if (sole === undefined) {\n throw new TypeError(\n `money: field \"${field}\" is multi-currency — write { amount, currency }, not a bare amount`,\n )\n }\n currency = sole\n amount = raw as number | string\n }\n const scale = desc.scaleFor(currency) // throws MoneyCurrencyError if disallowed\n return { amount: quantizeAmount(field, amount, scale, desc.rounding), currency }\n}\n\n/**\n * Convert money fields in `record` from user input to their canonical\n * stored form. Returns a shallow clone (deep along declared nested\n * paths). A nested path whose declared shape disagrees with the\n * data throws: writing through would store an un-quantized amount.\n */\nexport function quantizeMoneyFields<T extends Record<string, unknown>>(\n record: T,\n moneyFields: Record<string, MoneyDescriptor>,\n): T {\n let out: Record<string, unknown> = { ...record }\n for (const [path, desc] of Object.entries(moneyFields)) {\n if (isSimpleMoneyPath(path)) {\n const raw = out[path]\n if (raw === null || raw === undefined) continue\n out[path] = quantizeValue(path, raw, desc)\n continue\n }\n out = transformAtMoneyPath(out, path, parseMoneyPath(path), 0, (container, key) => {\n const raw = (container as Record<string | number, unknown>)[key]\n if (raw === null || raw === undefined) return\n ;(container as Record<string | number, unknown>)[key] = quantizeValue(path, raw, desc)\n }, /* lenient */ false) as Record<string, unknown>\n }\n return out as T\n}\n\nfunction formatCurrency(decimal: string, currency: string, scale: number, locale: string): string {\n const fmt = new Intl.NumberFormat(locale, {\n style: 'currency',\n currency,\n minimumFractionDigits: scale,\n maximumFractionDigits: scale,\n })\n // V8's Intl.format accepts a decimal STRING and formats it at full\n // precision (exact past 2^53). The TS lib types only declare\n // number|bigint, so cast — a number arg here would re-introduce the\n // float drift this whole feature exists to eliminate.\n return (fmt.format as unknown as (value: string) => string)(decimal)\n}\n\n/**\n * The stored scaled-integer value of a money field as a `BigInt`, for\n * exact numeric comparison (e.g. `orderBy` / sorting), or `null` when the\n * stored value is missing/malformed. Mirrors the stored form `decodeValue`\n * reads: a bare scaled-int (fixed mode) or `{ amount, currency }`\n * (multi-currency). Comparison is in scaled space and is exact within a\n * single currency/scale (the `where` / `sum` BigInt model); across\n * currencies of different scales the raw scaled comparison is best-effort.\n */\nexport function moneyScaledValue(stored: unknown, desc: MoneyDescriptor): bigint | null {\n let raw: unknown\n if (desc.mode === 'fixed') {\n raw = stored\n } else {\n if (!isMoneyValueObject(stored)) return null\n raw = stored.amount\n }\n if (typeof raw !== 'string' && typeof raw !== 'number') return null\n try {\n return BigInt(String(raw))\n } catch {\n return null\n }\n}\n\n/**\n * Decode ONE stored field value to its read shape, or `null` when the\n * stored value is malformed (defensive — never brick a read).\n */\nfunction decodeValue(\n stored: unknown,\n desc: MoneyDescriptor,\n): { decoded: unknown; decimal: string; currency: string; scale: number } | null {\n let currency: string\n let scaledIntString: string\n if (desc.mode === 'fixed') {\n if (typeof stored !== 'string' && typeof stored !== 'number') return null\n currency = desc.fixedCurrency!\n scaledIntString = String(stored)\n } else {\n if (!isMoneyValueObject(stored)) return null // defensive: malformed stored value\n const amount = stored.amount\n if (typeof stored.currency !== 'string' || (typeof amount !== 'string' && typeof amount !== 'number')) return null\n currency = stored.currency\n scaledIntString = String(amount)\n }\n const scale = desc.scaleFor(currency)\n let decimal: string\n try {\n decimal = formatScaledInt(BigInt(scaledIntString), scale)\n } catch {\n return null // defensive: non-integer stored value\n }\n return {\n decoded: desc.mode === 'fixed' ? decimal : { amount: decimal, currency },\n decimal,\n currency,\n scale,\n }\n}\n\n/**\n * Convert money fields in `record` from stored form to the read shape:\n * an exact decimal string, plus `<field>Formatted` / `<field>Number`\n * virtuals when `locale !== 'raw'`. Returns a shallow clone (deep along\n * declared nested paths; virtuals land as siblings inside the\n * nested container, e.g. each `lineItems[]` element gains\n * `amountFormatted`, except for values held directly in arrays where a\n * scalar has no sibling slot). The decode walk is LENIENT: stored data\n * predating a declaration change must stay readable.\n */\nexport function decodeMoneyFields<T extends Record<string, unknown>>(\n record: T,\n moneyFields: Record<string, MoneyDescriptor>,\n locale: string | undefined,\n): T {\n let out: Record<string, unknown> = { ...record }\n const format = locale !== 'raw'\n const fmtLocale = typeof locale === 'string' && locale !== 'raw' ? locale : 'en-US'\n\n for (const [path, desc] of Object.entries(moneyFields)) {\n if (isSimpleMoneyPath(path)) {\n const stored = out[path]\n if (stored === null || stored === undefined) continue\n const r = decodeValue(stored, desc)\n if (r === null) continue\n out[path] = r.decoded\n if (format) {\n out[`${path}Formatted`] = formatCurrency(r.decimal, r.currency, r.scale, fmtLocale)\n out[`${path}Number`] = Number(r.decimal)\n }\n continue\n }\n out = transformAtMoneyPath(out, path, parseMoneyPath(path), 0, (container, key) => {\n const stored = (container as Record<string | number, unknown>)[key]\n if (stored === null || stored === undefined) return\n const r = decodeValue(stored, desc)\n if (r === null) return\n ;(container as Record<string | number, unknown>)[key] = r.decoded\n if (format && typeof key === 'string' && !Array.isArray(container)) {\n container[`${key}Formatted`] = formatCurrency(r.decimal, r.currency, r.scale, fmtLocale)\n container[`${key}Number`] = Number(r.decimal)\n }\n }, /* lenient */ true) as Record<string, unknown>\n }\n return out as T\n}\n","/**\n * Chainable, immutable query builder.\n *\n * Each builder operation returns a NEW Query — the underlying plan is never\n * mutated. This makes plans safe to share, cache, and serialize.\n */\n\nimport type { QueryField } from '../types.js'\nimport type { Clause, CrossJoinClause, FieldClause, FilterClause, GroupClause, Operator, WherePredicateClause } from './predicate.js'\nimport { evaluateClause, hasFnClause } from './predicate.js'\nimport type { CollectionIndexes } from '../../with-lookup/indexing/eager-indexes.js'\nimport type { JoinableSource, JoinContext, JoinLeg, JoinStrategy } from './join.js'\nimport { applyJoins } from './join.js'\nimport { CrossJoinTooLargeError, CrossJoinSourceUnknownError } from '../errors.js'\nimport type { LiveQuery, LiveUpstream } from './live.js'\nimport { buildLiveQuery } from './live.js'\nimport type { AggregateSpec, AggregateResult, AggregationUpstream, Aggregation } from '../../with-lookup/aggregate/aggregation.js'\nimport type { ReducerBuilder } from '../../with-lookup/aggregate/reducers.js'\nimport { reducerBuilder } from '../../with-lookup/aggregate/reducers.js'\nimport type { GroupedQuery, GroupedQueryN } from '../../with-lookup/aggregate/groupby.js'\nimport { NO_AGGREGATE, type AggregateStrategy } from '../../with-lookup/aggregate/strategy.js'\nimport type { MoneyDescriptor } from '../../with-shape/money/descriptor.js'\nimport { wrapMoneyReducers } from '../../with-shape/money/money-reducer.js'\nimport { decodeMoneyFields, moneyScaledValue } from '../../with-shape/money/normalize.js'\nimport { moneyFieldClause } from '../../with-shape/money/where.js'\n\nexport interface OrderBy {\n readonly field: string\n readonly direction: 'asc' | 'desc'\n /**\n * Sort key for a `dictKey`/`staticDict` field: `'value'` (default)\n * sorts by the stored code; `'label'` sorts by the code's resolved label at\n * the query locale (`toArray({ locale })`, or a `staticDict` `displayLocale`).\n * Falls back to the code when no label resolves.\n */\n readonly by?: 'value' | 'label'\n}\n\n/**\n * A complete query plan: zero-or-more clauses, optional ordering, pagination,\n * and optional joins.\n *\n * Plans are JSON-serializable as long as no FilterClause is present and no\n * join leg carries a manual `strategy` override (JoinLeg itself is plain\n * data, so it serializes cleanly).\n *\n * Plans are intentionally NOT parametric on T — see `predicate.ts` FilterClause\n * for the variance reasoning. The public `Query<T>` API attaches the type tag.\n */\nexport interface QueryPlan {\n readonly clauses: readonly Clause[]\n readonly orderBy: readonly OrderBy[]\n readonly limit: number | undefined\n readonly offset: number\n /**\n * Zero-or-more join legs to apply after where/orderBy/limit/offset.\n * Each leg attaches a resolved right-side record (or null) under its\n * alias. See `query/join.ts` for the full semantics.\n */\n readonly joins: readonly JoinLeg[]\n}\n\nconst EMPTY_PLAN: QueryPlan = {\n clauses: [],\n orderBy: [],\n limit: undefined,\n offset: 0,\n joins: [],\n}\n\n/** Default row ceiling for cross-join expansion. Matches JoinTooLargeError's ceiling. */\nexport const DEFAULT_CROSS_JOIN_MAX_ROWS = 50_000\n\n/**\n * Source of records that a query executes against.\n *\n * The interface is non-parametric to keep variance friendly: callers cast\n * their typed source (e.g. `QuerySource<Invoice>`) into this opaque shape.\n *\n * `getIndexes` and `lookupById` are optional fast-path hooks. When both are\n * present and a where clause matches an indexed field, the executor uses\n * the index to skip a linear scan. Sources without these methods (or with\n * `getIndexes` returning `null`) always fall back to a linear scan.\n */\nexport interface QuerySource<T> {\n /** Snapshot of all current records. The query never mutates this array. */\n snapshot(): readonly T[]\n /** Subscribe to mutations; returns an unsubscribe function. */\n subscribe?(cb: () => void): () => void\n /** Index store for the indexed-fast-path. Optional. */\n getIndexes?(): CollectionIndexes | null\n /** O(1) record lookup by id, used to materialize index hits. */\n lookupById?(id: string): T | undefined\n /**\n * Money field descriptors for the backing collection, used to rewrite\n * `sum`/`min`/`max` over money fields into exact BigInt reducers.\n */\n moneyFields?: Record<string, MoneyDescriptor>\n /**\n * Id-paired snapshot for `Query._idArray()` (the `retrieve({within})`\n * id projection). Optional: only collection-backed queries supply it.\n */\n snapshotEntries?(): readonly { id: string; record: T }[]\n}\n\ninterface InternalSource {\n snapshot(): readonly unknown[]\n subscribe?(cb: () => void): () => void\n getIndexes?(): CollectionIndexes | null\n lookupById?(id: string): unknown\n moneyFields?: Record<string, MoneyDescriptor>\n snapshotEntries?(): readonly { id: string; record: unknown }[]\n}\n\n/**\n * The chainable builder. All methods return a new Query — the original\n * remains unchanged. Terminal methods (`toArray`, `first`, `count`,\n * `subscribe`) execute the plan against the source.\n *\n * Type parameter T flows through the public API for ergonomics, but the\n * internal storage uses `unknown` so Collection<T> stays covariant.\n *\n * The optional `joinContext` is attached when the Query is constructed\n * via `Collection.query()` (Collection passes in a context built from\n * the Vault's join resolver). A Query constructed via `new Query`\n * directly — e.g. from tests with a plain-object source — has no\n * joinContext, and calling `.join()` on it throws with an actionable\n * error. See `query/join.ts` for the full design.\n */\n/**\n * Declared deterministic predicate. Carries the consumer's\n * stable `hash` (for function-body identity), the function itself,\n * and is keyed by name when registered on a `Query<T>` via\n * `_withPredicates()`.\n */\nexport interface DeclaredPredicate {\n hash: string\n fn: (record: unknown, ctx?: unknown) => boolean\n}\n\nexport class Query<T, S extends keyof T = never, Q extends keyof T & string = never, M extends keyof T & string = never> {\n private readonly source: InternalSource\n private readonly plan: QueryPlan\n private readonly joinContext: JoinContext | undefined\n private readonly aggregateStrategy: AggregateStrategy\n private readonly predicates: ReadonlyMap<string, DeclaredPredicate> | undefined\n\n constructor(\n source: QuerySource<T>,\n plan: QueryPlan = EMPTY_PLAN,\n joinContext?: JoinContext,\n aggregateStrategy: AggregateStrategy = NO_AGGREGATE,\n predicates?: ReadonlyMap<string, DeclaredPredicate>,\n ) {\n this.source = source as InternalSource\n this.plan = plan\n this.joinContext = joinContext\n this.aggregateStrategy = aggregateStrategy\n this.predicates = predicates\n }\n\n /**\n * @internal — accessor for the materialized-view dependency\n * analyzer. Not part of the public API; consumers should use the\n * builder methods, not inspect the plan directly.\n */\n _plan(): QueryPlan {\n return this.plan\n }\n\n /**\n * @internal — accessor for the materialized-view dependency\n * analyzer. Returns the join resolution context (or `undefined` for\n * queries constructed without a Collection backing).\n */\n _joinContext(): JoinContext | undefined {\n return this.joinContext\n }\n\n /**\n * @internal — clone this Query with a declared-predicate map\n * attached. Used by the materialized-view registry to enable\n * `.wherePredicate(name, ctx?)` for the MV's query callback.\n * Consumers don't call this directly.\n */\n _withPredicates(predicates: ReadonlyMap<string, DeclaredPredicate>): Query<T, S, Q, M> {\n return new Query<T, S, Q, M>(\n this.source as QuerySource<T>,\n this.plan,\n this.joinContext,\n this.aggregateStrategy,\n predicates,\n )\n }\n\n /**\n * @internal — the ids of records matching this query's plan,\n * recovered by reference identity: `executePlanWithSource` returns the\n * ORIGINAL snapshot record references (money-decode and joins are applied\n * later, in `toArray`), so each matched record is found in the id-paired\n * `snapshotEntries()` map. Used by `collection.retrieve({ within })`.\n * Throws if the source is not collection-backed (no `snapshotEntries`).\n */\n _idArray(): string[] {\n const entries = this.source.snapshotEntries?.()\n if (entries === undefined) {\n throw new Error(\n 'Query._idArray(): the query source has no snapshotEntries(); ' +\n 'retrieve({ within }) requires a collection-backed query (collection.query()).',\n )\n }\n if (this.plan.clauses.some(c => c.type === 'crossJoin')) {\n throw new Error(\n 'Query._idArray(): retrieve({ within }) does not support crossJoin queries ' +\n '(cross-join produces new row objects, breaking id recovery). ' +\n 'Use where/filter/and/or, or a projection .join().',\n )\n }\n const refToId = new Map<unknown, string>()\n for (const { id, record } of entries) refToId.set(record, id)\n const matched = executePlanWithSource(this.source, this.plan, this.joinContext)\n const ids: string[] = []\n for (const r of matched) {\n const id = refToId.get(r)\n if (id !== undefined) ids.push(id)\n }\n return ids\n }\n\n /**\n * Filter by a registered deterministic predicate. Requires\n * the Query to have been augmented with a predicates map (typically\n * via the materialized-view registry — bare Queries constructed\n * outside an MV throw on `.wherePredicate()`).\n *\n * `ctx` is an optional opaque value passed verbatim to the predicate\n * function. Both `predicateHash` (from the registration) and a\n * canonical-JSON hash of `ctx` fold into the MV's `queryHash`, so\n * either changing forces refresh on next visit.\n */\n wherePredicate(name: string, ctx?: unknown): Query<T, S, Q, M> {\n if (!this.predicates) {\n throw new Error(\n `.wherePredicate(\"${name}\"): no predicates registered on this Query. ` +\n `Function-based predicates require the Query to be obtained from ` +\n `inside a materialized-view query() callback whose strategy declares ` +\n `\\`predicates: { ${name}: { hash, fn } }\\`.`,\n )\n }\n const decl = this.predicates.get(name)\n if (!decl) {\n throw new Error(\n `.wherePredicate(\"${name}\"): predicate not registered. ` +\n `Available: ${[...this.predicates.keys()].join(', ') || '(none)'}.`,\n )\n }\n const clause: WherePredicateClause = {\n type: 'wherePredicate',\n name,\n ctx,\n predicateHash: decl.hash,\n ctxHash: canonicalCtxHash(ctx),\n fn: decl.fn,\n }\n return new Query<T, S, Q, M>(\n this.source as QuerySource<T>,\n { ...this.plan, clauses: [...this.plan.clauses, clause] },\n this.joinContext,\n this.aggregateStrategy,\n this.predicates,\n )\n }\n\n /**\n * Add a field comparison. Multiple where() calls are AND-combined.\n *\n * A declared money field compares in MAJOR units: the operand\n * (`10000`, `'10000.00'`, or `{ amount, currency }` in multi mode) is\n * quantized into stored scaled-int space at build time and evaluated\n * BigInt-exact per record. A malformed operand or a string operator\n * (`contains`/`startsWith`) throws here, at the call site.\n */\n where(field: QueryField<T, S, Q>, op: Operator, value: unknown): Query<T, S, Q, M> {\n const desc = this.source.moneyFields?.[field]\n const clause: FieldClause = desc\n ? moneyFieldClause(field, op, value, desc)\n : { type: 'field', field, op, value }\n return new Query<T, S, Q, M>(\n this.source as QuerySource<T>,\n { ...this.plan, clauses: [...this.plan.clauses, clause] },\n this.joinContext,\n this.aggregateStrategy,\n this.predicates,\n )\n }\n\n /**\n * Logical OR group. Pass a callback that builds a sub-query.\n * Each clause inside the callback is OR-combined; the group itself\n * joins the parent plan with AND.\n */\n or(builder: (q: Query<T, S, Q, M>) => Query<T, S, Q, M>): Query<T, S, Q, M> {\n const sub = builder(\n new Query<T, S, Q, M>(this.source as QuerySource<T>, EMPTY_PLAN, this.joinContext, this.aggregateStrategy, this.predicates),\n )\n const group: GroupClause = {\n type: 'group',\n op: 'or',\n clauses: sub.plan.clauses,\n }\n return new Query<T, S, Q, M>(\n this.source as QuerySource<T>,\n { ...this.plan, clauses: [...this.plan.clauses, group] },\n this.joinContext,\n this.aggregateStrategy,\n this.predicates,\n )\n }\n\n /**\n * Logical AND group. Same shape as `or()` but every clause inside the group\n * must match. Useful for explicit grouping inside a larger OR.\n */\n and(builder: (q: Query<T, S, Q, M>) => Query<T, S, Q, M>): Query<T, S, Q, M> {\n const sub = builder(\n new Query<T, S, Q, M>(this.source as QuerySource<T>, EMPTY_PLAN, this.joinContext, this.aggregateStrategy, this.predicates),\n )\n const group: GroupClause = {\n type: 'group',\n op: 'and',\n clauses: sub.plan.clauses,\n }\n return new Query<T, S, Q, M>(\n this.source as QuerySource<T>,\n { ...this.plan, clauses: [...this.plan.clauses, group] },\n this.joinContext,\n this.aggregateStrategy,\n this.predicates,\n )\n }\n\n /** Escape hatch: add an arbitrary predicate function. Not serializable. */\n filter(fn: (record: T) => boolean): Query<T, S, Q, M> {\n const clause: FilterClause = {\n type: 'filter',\n fn: fn as (record: unknown) => boolean,\n }\n return new Query<T, S, Q, M>(\n this.source as QuerySource<T>,\n { ...this.plan, clauses: [...this.plan.clauses, clause] },\n this.joinContext,\n this.aggregateStrategy,\n this.predicates,\n )\n }\n\n /**\n * Sort by a field. Subsequent calls are tie-breakers. Pass\n * `{ by: 'label' }` to sort a `dictKey`/`staticDict` field by its resolved\n * label at the query locale instead of the stored code.\n */\n orderBy(field: QueryField<T, S>, direction: 'asc' | 'desc' = 'asc', opts?: { by?: 'value' | 'label' }): Query<T, S, Q, M> {\n const entry: OrderBy = opts?.by === 'label' ? { field, direction, by: 'label' } : { field, direction }\n return new Query<T, S, Q, M>(\n this.source as QuerySource<T>,\n { ...this.plan, orderBy: [...this.plan.orderBy, entry] },\n this.joinContext,\n this.aggregateStrategy,\n this.predicates,\n )\n }\n\n /** Cap the result size. */\n limit(n: number): Query<T, S, Q, M> {\n return new Query<T, S, Q, M>(\n this.source as QuerySource<T>,\n { ...this.plan, limit: n },\n this.joinContext,\n this.aggregateStrategy,\n this.predicates,\n )\n }\n\n /** Skip the first N matching records (after ordering). */\n offset(n: number): Query<T, S, Q, M> {\n return new Query<T, S, Q, M>(\n this.source as QuerySource<T>,\n { ...this.plan, offset: n },\n this.joinContext,\n this.aggregateStrategy,\n this.predicates,\n )\n }\n\n /**\n * Resolve a `ref()`-declared foreign key and attach the right-side\n * record under `opts.as`. — eager, single-FK, intra-\n * vault joins.\n *\n * ```ts\n * const rows = invoices.query()\n * .where('status', '==', 'open')\n * .join('clientId', { as: 'client' })\n * .toArray()\n * // → [{ id, amount, client: { id, name, ... } }, ...]\n * ```\n *\n * Preconditions:\n * - The Query must have a `joinContext` (constructed via\n * `Collection.query()`, not `new Query`).\n * - `field` must have a matching `refs: { [field]: ref('<target>') }`\n * declaration on the left collection.\n * - The target collection must be reachable via the vault\n * (either currently open or openable on demand).\n *\n * Strategy:\n * - Nested-loop against `lookupById` when the target source\n * provides it (the common path for Collection targets).\n * - Hash join otherwise, or when `{ strategy: 'hash' }` is\n * explicitly passed for test purposes.\n *\n * Ref-mode semantics on dangling refs (left record has a non-null\n * FK value pointing at a right-side id that doesn't exist):\n * - `strict` → throws `DanglingReferenceError` with the full\n * field / target / refId context.\n * - `warn` → attaches `null` and emits a one-shot warning per\n * unique dangling pair.\n * - `cascade` → attaches `null` silently. Cascade is a\n * delete-time mode; dangling refs visible at read time are\n * either mid-flight cascades or pre-existing orphans, not a\n * DSL-level error.\n *\n * A left-side record whose FK field is `null` / `undefined` is NOT\n * a dangling ref — it's \"no reference at all\", always allowed\n * regardless of mode.\n *\n * The return type widens `T` with `Record<As, R | null>`. The `R`\n * parameter is optional — supply it explicitly for type-checked\n * access to the joined fields:\n *\n * ```ts\n * invoices.query().join<'client', Client>('clientId', { as: 'client' })\n * // ^^^^^^^^^^^^^^^^^^^ alias literal + right-side type\n * ```\n *\n * Without the generic, the joined field is typed as `unknown`, which\n * still works but requires a cast to access its properties.\n *\n * Joins stay intra-vault by construction — cross-vault\n * correlation goes through `Noydb.queryAcross`, not\n * `.join()`.\n */\n join<As extends string, R = unknown>(\n field: QueryField<T, S>,\n opts: { as: As; strategy?: JoinStrategy; maxRows?: number },\n ): Query<T & Record<As, R | null>, S, Q, M> {\n if (!this.joinContext) {\n throw new Error(\n `Query.join() requires a join context. Use collection.query() ` +\n `to construct a join-capable Query instead of the Query constructor ` +\n `directly (the direct constructor is only used for tests with ` +\n `plain-object sources).`,\n )\n }\n const descriptor = this.joinContext.resolveRef(field)\n // Check for dictKey join when no ref() is declared\n const isDictJoinField = !descriptor && this.joinContext.resolveDictSource?.(field) != null\n if (!descriptor && !isDictJoinField) {\n throw new Error(\n `Query.join(): no ref() declared for field \"${field}\" on collection ` +\n `\"${this.joinContext.leftCollection}\". Add ` +\n `refs: { ${field}: ref('<target-collection>') } to the collection ` +\n `options, then retry. See the ref() docs for the full list of modes.`,\n )\n }\n const leg: JoinLeg = descriptor\n ? {\n field,\n as: opts.as,\n target: descriptor.target,\n mode: descriptor.mode,\n strategy: opts.strategy,\n maxRows: opts.maxRows,\n // constraint #1 — always 'all' in. Do not remove.\n partitionScope: 'all',\n }\n : {\n // Dict join leg\n field,\n as: opts.as,\n target: field, // dict name = field name for dictKey\n mode: 'strict',\n strategy: opts.strategy,\n maxRows: opts.maxRows,\n partitionScope: 'all',\n isDictJoin: true,\n }\n return new Query<T & Record<As, R | null>, S, Q, M>(\n this.source as unknown as QuerySource<T & Record<As, R | null>>,\n { ...this.plan, joins: [...this.plan.joins, leg] },\n this.joinContext,\n this.aggregateStrategy,\n this.predicates,\n )\n }\n\n /**\n * Cartesian-product cross-join against `target` collection. Each result row\n * carries the original `T` fields plus `result[as]` populated from every\n * right-side row (or the filtered subset when `on:` is supplied).\n *\n * **Order matters:** `.where().crossJoin()` filters BEFORE expanding (cheaper);\n * `.crossJoin().where('alias.field', ...)` filters AFTER (required when the\n * where clause references the aliased fields).\n *\n * **Cost ceiling:** `CrossJoinTooLargeError` fires before allocation when\n * `leftRows × rightRows` (or the cumulative lateral count) exceeds the limit.\n * Default: 50,000 rows. Override per-clause with `{ maxRows: N }`.\n *\n * **`on:` shapes:**\n * - `on: (left) => TTarget[]` — subset form (most efficient)\n * - `on: (left) => (right) => boolean` — predicate form\n * - `on: { predicate: 'name' }` — MV-safe, hash-tracked form\n * (requires the Query to have been augmented via `_withPredicates`)\n *\n * Requires a JoinContext (constructed via `collection.query()`).\n */\n crossJoin<TTarget = unknown, As extends string = string>(\n target: string,\n opts: {\n as: As\n on?:\n | ((left: T) => unknown[] | ((right: TTarget) => boolean))\n | { readonly predicate: string }\n maxRows?: number\n },\n ): Query<T & { [K in As]: TTarget }, S, Q, M> {\n if (!this.joinContext) {\n throw new Error(\n `Query.crossJoin(\"${target}\"): requires a join context. ` +\n `Use collection.query() to construct a cross-join-capable Query instead of ` +\n `the Query constructor directly.`,\n )\n }\n\n let onFn: CrossJoinClause['on']\n let onPredicateName: string | undefined\n\n if (opts.on !== undefined) {\n if (typeof opts.on === 'function') {\n onFn = opts.on as CrossJoinClause['on']\n if (this.predicates) {\n console.warn(\n `Query.crossJoin(\"${target}\", { on: callback }): inline on: callback inside a ` +\n `withMaterializedView query() disables queryHash drift detection for this cross-join. ` +\n `Use on: { predicate: '<name>' } to enable it.`,\n )\n }\n } else {\n const predName = (opts.on as { predicate: string }).predicate\n if (!this.predicates) {\n throw new Error(\n `Query.crossJoin(\"${target}\", { on: { predicate: \"${predName}\" } }): ` +\n `the { predicate } form requires a predicates map. ` +\n `Use this form inside a withMaterializedView query() callback that declares ` +\n `predicates: { ${predName}: { hash, fn } }.`,\n )\n }\n const decl = this.predicates.get(predName)\n if (!decl) {\n throw new Error(\n `Query.crossJoin(\"${target}\"): predicate \"${predName}\" not registered. ` +\n `Available: ${[...this.predicates.keys()].join(', ') || '(none)'}.`,\n )\n }\n const as = opts.as\n const predicateFn = decl.fn\n onFn = (_left: unknown): ((right: unknown) => boolean) =>\n (right: unknown) =>\n predicateFn({ ...(_left as Record<string, unknown>), [as]: right })\n onPredicateName = predName\n }\n }\n\n const clause: CrossJoinClause = {\n type: 'crossJoin',\n target,\n as: opts.as,\n ...(onFn !== undefined && { on: onFn }),\n ...(onPredicateName !== undefined && { onPredicateName }),\n ...(opts.maxRows !== undefined && { maxRows: opts.maxRows }),\n }\n\n return new Query<T & { [K in As]: TTarget }, S, Q, M>(\n this.source as unknown as QuerySource<T & { [K in As]: TTarget }>,\n { ...this.plan, clauses: [...this.plan.clauses, clause] },\n this.joinContext,\n this.aggregateStrategy,\n this.predicates,\n )\n }\n\n /**\n * Execute the plan and return the matching records. When the plan\n * carries any join legs, they are applied after `where` / `orderBy`\n * / `limit` / `offset` narrow the left set. See the `.join()` doc\n * for the ordering rationale.\n *\n * `opts.locale` resolves JOINED right-side i18n fields at the\n * `join` layer to that locale; without it, the owning collection's default\n * locale applies, and a locale-less query leaves joined i18n fields raw.\n * (Left/base i18n fields are resolved by `get`/`list`, not here.)\n */\n toArray(opts?: { locale?: string }): T[] {\n // Decode money fields (stored scaled-int → canonical decimal) so\n // query().toArray() matches get()/sum(), which already return decimal.\n // Decode the left/base records before joins (right-side aliased fields\n // belong to other collections and are out of this source's money scope).\n const base = this.decodeMoney(executePlanWithSource(this.source, this.plan, this.joinContext, opts?.locale))\n if (this.plan.joins.length === 0) return base as T[]\n if (!this.joinContext) {\n // Unreachable in practice — .join() throws if joinContext is\n // missing — but belt-and-braces for direct plan construction.\n throw new Error(\n `Query.toArray(): plan carries ${this.plan.joins.length} join leg(s) ` +\n `but no JoinContext is attached. This usually means the Query was ` +\n `constructed via the raw Query constructor with a plan that had joins ` +\n `pre-populated. Use collection.query().join(...) instead.`,\n )\n }\n return applyJoins(base, this.plan.joins, this.joinContext, opts?.locale) as T[]\n }\n\n /**\n * Decode this source's money fields on read (stored scaled-int → canonical\n * decimal), so `query().toArray()` agrees with `get()`/`sum()` on the value.\n * No-op when the source declares no money fields.\n *\n * The query layer carries no locale context, so we decode with `'raw'` —\n * canonical decimal, WITHOUT fabricating locale-formatted `<field>Formatted`\n * / `<field>Number` virtuals. Producing a guessed-locale string here would\n * reintroduce a \"two read paths disagree\" failure on the virtual\n * field (e.g. it-IT via `get()` vs en-US here). Consumers who need formatted\n * money read through `get()`/`list()` with a locale.\n */\n private decodeMoney(records: readonly unknown[]): unknown[] {\n const moneyFields = this.source.moneyFields\n if (!moneyFields || Object.keys(moneyFields).length === 0) return records as unknown[]\n return records.map(r => decodeMoneyFields(r as Record<string, unknown>, moneyFields, 'raw'))\n }\n\n /** Return the first matching record, or null. Joins are applied. `opts.locale` resolves joined i18n fields. */\n first(opts?: { locale?: string }): T | null {\n const arr = this.limit(1).toArray(opts)\n return arr[0] ?? null\n }\n\n /**\n * Return the number of matching records (after where/filter,\n * before limit). **Joins are NOT applied** — count() reports the\n * left-side cardinality, because joins in are projection-only\n * (they attach an aliased field; they never filter). Running joins\n * here just to discard the aliases would be wasteful, and in strict\n * mode it could throw `DanglingReferenceError` for a call whose\n * intent is purely to count.\n */\n count(): number {\n if (this.plan.clauses.some(c => c.type === 'crossJoin')) {\n if (!this.joinContext) {\n throw new Error(\n `Query.count(): plan contains crossJoin clauses but no JoinContext is attached.`,\n )\n }\n return executeClausePipeline(this.source, this.plan.clauses, this.joinContext).length\n }\n // Use the same index-aware candidate machinery as toArray(); skip the\n // index-driving clause from re-evaluation. The length BEFORE limit/offset\n // is what `count()` documents.\n const { candidates, remainingClauses } = candidateRecords(this.source, this.plan.clauses)\n if (remainingClauses.length === 0) return candidates.length\n return filterRecords(candidates, remainingClauses, fnViewDecoder(this.source)).length\n }\n\n /**\n * Reduce the matching records through a named set of reducers.\n * the aggregation terminal.\n *\n * ```ts\n * const { total, n, avgAmount } = invoices.query()\n * .where('status', '==', 'open')\n * .aggregate({\n * total: sum('amount'),\n * n: count(),\n * avgAmount: avg('amount'),\n * })\n * .run()\n * ```\n *\n * Returns an `Aggregation<R>` wrapper with two terminals:\n * - `.run(): R` — synchronous one-shot reduction\n * - `.live(): LiveAggregation<R>` — reactive primitive that\n * re-runs the reduction whenever the source notifies of a\n * change. Always call `live.stop()` when finished.\n *\n * The reducer spec is bound here once and reused by both\n * terminals — this is why `.aggregate()` returns a wrapper instead\n * of being a direct terminal. Consumers who only need the static\n * value read `.run()`; consumers wiring a reactive UI read\n * `.live()`.\n *\n * Joins are intentionally NOT applied to aggregations in —\n * the same logic as `.count()`. Joins in are projection-only\n * (they attach an aliased field and never filter), so running\n * them just to throw the aliases away would be wasteful. If you\n * need a reducer that reads a joined field, open an issue —\n * aggregations-across-joins is explicitly out of scope for v1.\n *\n * Every reducer factory accepts an optional `{ seed }` parameter\n * that is plumbed through the protocol but unused by the\n * executor — that's constraint #2. When partition-aware\n * aggregation lands, the seed will carry running state across\n * partition boundaries without an API break.\n *\n * KNOWN GAP (sealed fields, bare-spec form): `where`/`orderBy`/`groupBy`\n * refuse a `sensitive` field at compile time, but a reducer over a sensitive\n * field in the BARE-SPEC form (e.g. `aggregate({ x: sum('ssn') })`) is NOT\n * refused — the reducer factories (`sum`/`min`/`max`/…) are standalone\n * `(field: string)` functions with no collection-type context. This form is\n * preserved as-is for backward compatibility.\n *\n * The BUILDER form closes this gap: `aggregate(b => ({ x: b.sum('field') }))`\n * types the builder's field parameter as `QueryField<T, S>`, refusing any\n * `sensitive` field at compile time. Use the builder form for new code that\n * aggregates over a collection with sensitive fields.\n *\n */\n aggregate<Spec extends AggregateSpec>(spec: Spec): Aggregation<AggregateResult<Spec>>\n aggregate<Spec extends AggregateSpec>(build: (b: ReducerBuilder<T, S, M>) => Spec): Aggregation<AggregateResult<Spec>>\n aggregate<Spec extends AggregateSpec>(\n specOrBuild: Spec | ((b: ReducerBuilder<T, S, M>) => Spec),\n ): Aggregation<AggregateResult<Spec>> {\n let spec = typeof specOrBuild === 'function'\n ? (specOrBuild as (b: ReducerBuilder<T, S, M>) => Spec)((reducerBuilder as unknown) as ReducerBuilder<T, S, M>)\n : specOrBuild\n // Rewrite sum/min/max over money fields into exact BigInt reducers\n // before the strategy runs (covers static run() and live/MV paths).\n const moneyFields = this.source.moneyFields\n if (moneyFields) {\n spec = wrapMoneyReducers(spec, moneyFields) as Spec\n }\n // Closure over the current query. Produces the record set that\n // the aggregation reduces — same pipeline as `count()`, skipping\n // limit/offset because aggregation is over the full match set,\n // not a paginated slice. (A paginated aggregation would be a\n // different operation; see docs for rationale.)\n const source = this.source\n const clauses = this.plan.clauses\n const joinCtx = this.joinContext\n const hasCrossJoins = clauses.some(c => c.type === 'crossJoin')\n const executeRecords = (): readonly unknown[] => {\n if (hasCrossJoins) {\n if (!joinCtx) throw new Error('Query.aggregate(): crossJoin requires a join context')\n return executeClausePipeline(source, clauses, joinCtx)\n }\n const { candidates, remainingClauses } = candidateRecords(source, clauses)\n return remainingClauses.length === 0\n ? candidates\n : filterRecords(candidates, remainingClauses, fnViewDecoder(source))\n }\n\n // Upstream for live mode — only the left source subscribes.\n // Joined aggregations are out of scope for (see above), so\n // there are no right-side change streams to merge in.\n const upstreams: AggregationUpstream[] = []\n if (source.subscribe) {\n const subscribe = source.subscribe.bind(source)\n upstreams.push({ subscribe: (cb: () => void) => subscribe(cb) })\n }\n\n return this.aggregateStrategy.aggregate<Spec>(executeRecords, spec, upstreams)\n }\n\n /**\n * Partition matching records into buckets keyed by a field, then\n * terminate with `.aggregate(spec)` to compute per-bucket\n * reducers..\n *\n * ```ts\n * const byClient = invoices.query()\n * .where('status', '==', 'open')\n * .groupBy('clientId')\n * .aggregate({ total: sum('amount'), n: count() })\n * .run()\n * // → [ { clientId: 'c1', total: 5250, n: 3 }, … ]\n * ```\n *\n * Result rows carry the group key value under the grouping field\n * name plus every reducer output from the spec. Buckets are\n * emitted in first-seen order — consumers who want a specific\n * ordering should `.sort()` downstream.\n *\n * **Cardinality caps:** a one-shot warning fires at 10_000\n * distinct groups; `GroupCardinalityError` throws at 100_000.\n * Grouping on a high-uniqueness field like `id` or `createdAt` is\n * almost always a query mistake — the error message names the\n * field and observed cardinality and suggests narrowing with\n * `.where()` first.\n *\n * **Null / undefined keys:** records with a missing or explicitly\n * `null` group field get their own buckets. `Map`-based\n * partitioning distinguishes `undefined` from `null`, so the two\n * cases do NOT merge. Consumers who want them merged should\n * coalesce upstream with `.filter()`.\n *\n * **Joins are not applied** — same rationale as `.count()` and\n * `.aggregate()`. Joined fields in are projection-only, so\n * running a join inside a grouping pipeline would be wasteful and\n * could trigger `DanglingReferenceError` in strict mode for a\n * call whose intent is purely to bucket-and-reduce. Grouping by\n * a joined field is explicitly out of scope for — file an\n * issue if a real consumer needs it.\n *\n * **Filter clauses (`.filter(fn)`):** grouped queries still\n * support filter clauses in the underlying plan — they run in\n * the same candidate/filter pipeline that `.aggregate()` uses.\n * The performance caveat is the same: filter clauses cost O(N)\n * per record and can't be index-accelerated.\n */\n // The `field` param is `QueryField<T, S>` so a sealed (`sensitive`) field is\n // refused — grouping BY a sensitive field leaks its value distribution as\n // group-key labels. With `S = never` this is exactly `string` (zero churn).\n groupBy<F extends QueryField<T, S>>(field: F): GroupedQuery<T, F, S, M>\n groupBy<F extends readonly [QueryField<T, S>, QueryField<T, S>, ...QueryField<T, S>[]]>(\n ...fields: F\n ): GroupedQueryN<T, F, S, M>\n groupBy(...fields: readonly string[]): GroupedQuery<T, string, S, M> | GroupedQueryN<T, readonly string[], S, M> {\n if (fields.length === 0) {\n throw new Error('.groupBy() requires at least one field')\n }\n // Same record-producing closure as .aggregate() — grouped and\n // non-grouped aggregations execute over the same candidate set.\n // We inline the closure here instead of sharing a helper so the\n // builder stays allocation-friendly for the hot path.\n const source = this.source\n const clauses = this.plan.clauses\n const joinCtx = this.joinContext\n const hasCrossJoins = clauses.some(c => c.type === 'crossJoin')\n const executeRecords = (): readonly unknown[] => {\n if (hasCrossJoins) {\n if (!joinCtx) throw new Error('Query.groupBy(): crossJoin requires a join context')\n return executeClausePipeline(source, clauses, joinCtx)\n }\n const { candidates, remainingClauses } = candidateRecords(source, clauses)\n return remainingClauses.length === 0\n ? candidates\n : filterRecords(candidates, remainingClauses, fnViewDecoder(source))\n }\n\n const upstreams: AggregationUpstream[] = []\n if (source.subscribe) {\n const subscribe = source.subscribe.bind(source)\n upstreams.push({ subscribe: (cb: () => void) => subscribe(cb) })\n }\n\n // Dict-label resolution is single-field only — the <field>Label\n // projection has no meaningful shape for composite keys.\n if (fields.length === 1) {\n const field = fields[0]!\n const dictLabelResolver = buildDictLabelResolver(this.joinContext, field)\n return this.aggregateStrategy.groupBy<T, string, S, M>(\n executeRecords,\n field,\n upstreams,\n dictLabelResolver,\n this.source.moneyFields,\n )\n }\n return this.aggregateStrategy.groupByN<T, readonly string[], S, M>(\n executeRecords,\n fields,\n upstreams,\n this.source.moneyFields,\n )\n }\n\n /**\n * Re-run the query whenever the source notifies of changes.\n * Returns an unsubscribe function. The callback receives the latest result.\n * Throws if the source does not support subscriptions.\n *\n * **For joined queries, prefer `.live()`** — `subscribe()`\n * only re-fires on LEFT-side changes, so joined data can be\n * stale if the right side mutates between emissions. `.live()`\n * merges change streams from every join target.\n */\n subscribe(cb: (result: T[]) => void): () => void {\n if (!this.source.subscribe) {\n throw new Error('Query source does not support subscriptions. Pass a source with a subscribe() method.')\n }\n cb(this.toArray())\n return this.source.subscribe(() => cb(this.toArray()))\n }\n\n /**\n * Reactive terminal — returns a `LiveQuery<T>` that re-runs the\n * query and updates its `value` whenever any source feeding it\n * mutates..\n *\n * For non-joined queries, `.live()` is a convenience over the\n * existing `.subscribe()` callback shape: a hand-rolled reactive\n * primitive with `value` / `error` fields and a `subscribe(cb)`\n * notification channel. Frame-agnostic — Vue / React / Solid\n * adapters wrap it in their own primitive.\n *\n * For joined queries, `.live()` additionally subscribes to every\n * join target's change stream. Mutations on a right-side\n * collection (insert / update / delete of a client referenced by\n * an invoice) re-fire the live query and re-evaluate every\n * dependent left row. Right-side targets are deduped by\n * collection name, so a chain that joins the same target twice\n * (e.g. billing client + shipping client → both 'clients') only\n * subscribes once.\n *\n * **Ref-mode behavior on right-side disappearance** — matches the\n * eager `.toArray()` contract from :\n * - `strict` → re-run throws `DanglingReferenceError`. The\n * LiveQuery catches the throw, stores it in `live.error`, and\n * notifies listeners (the throw does NOT propagate out of\n * the source's change handler — that would tear down the\n * emitter). Consumers check `live.error` after each\n * notification and render an error state in the UI.\n * - `warn` → joined value flips to `null`; the existing\n * warn-channel deduplication keeps repeated re-runs from\n * spamming the console.\n * - `cascade` → no special handling needed; the cascade-\n * delete mechanism propagates the right-side delete into the\n * left collection on the next tick, and the live query\n * naturally re-fires with the orphaned left rows gone.\n *\n * Always call `live.stop()` when finished — it tears down every\n * upstream subscription. The Vue layer's `onUnmounted` hook\n * should call `stop()` automatically; raw consumers must do it\n * themselves.\n *\n * **Limitations:**\n * - No granular delta updates — the whole query re-runs on\n * every change.\n * - No microtask batching — bursty changes produce one re-run\n * per change.\n * - No re-planning under live mutations — the planner picks\n * once at subscription time and reuses the same plan.\n * - Streaming live joins are deferred.\n */\n live(): LiveQuery<T> {\n const upstreams: LiveUpstream[] = []\n\n // Left-side change stream — every live query subscribes to\n // its source if the source supports subscriptions.\n if (this.source.subscribe) {\n const leftSubscribe = this.source.subscribe.bind(this.source)\n upstreams.push({\n subscribe: (cb: () => void) => leftSubscribe(cb),\n })\n }\n\n // Right-side change streams — only for joined queries. Dedup\n // by target name so a chain joining the same target twice\n // doesn't double-subscribe and double-fire on every right-side\n // mutation.\n if (this.plan.joins.length > 0 && this.joinContext) {\n const subscribed = new Set<string>()\n for (const leg of this.plan.joins) {\n if (subscribed.has(leg.target)) continue\n subscribed.add(leg.target)\n const rightSource = this.joinContext.resolveSource(leg.target)\n if (rightSource?.subscribe) {\n const rightSubscribe = rightSource.subscribe.bind(rightSource)\n upstreams.push({\n subscribe: (cb: () => void) => rightSubscribe(cb),\n })\n }\n }\n }\n\n // Cross-join right-side change streams — symmetric with FK joins above.\n if (this.joinContext) {\n const subscribedCross = new Set<string>()\n for (const clause of this.plan.clauses) {\n if (clause.type !== 'crossJoin') continue\n if (subscribedCross.has(clause.target)) continue\n subscribedCross.add(clause.target)\n const rightSource = this.joinContext.resolveSource(clause.target)\n if (rightSource?.subscribe) {\n const rightSubscribe = rightSource.subscribe.bind(rightSource)\n upstreams.push({\n subscribe: (cb: () => void) => rightSubscribe(cb),\n })\n }\n }\n }\n\n // The recompute is just toArray bound to this query — same\n // pipeline as eager execution, including join application.\n return buildLiveQuery<T>(() => this.toArray(), upstreams)\n }\n\n /**\n * Return the plan as a JSON-friendly object. FilterClause entries are\n * stripped (their `fn` cannot be serialized) and replaced with\n * { type: 'filter', fn: '[function]' } so devtools can still see them.\n */\n toPlan(): unknown {\n return serializePlan(this.plan)\n }\n}\n\n/**\n * Index-aware execution: try the indexed fast path first, fall back to a\n * full scan otherwise. Mirrors `executePlan` for the public surface but\n * takes a `QuerySource` so it can consult `getIndexes()` and `lookupById()`.\n */\nfunction executePlanWithSource(\n source: InternalSource,\n plan: QueryPlan,\n joinContext?: JoinContext,\n locale?: string,\n): unknown[] {\n const hasCrossJoins = plan.clauses.some(c => c.type === 'crossJoin')\n\n let result: unknown[]\n if (hasCrossJoins) {\n if (!joinContext) {\n throw new Error(\n `Query.toArray(): plan contains crossJoin clauses but no JoinContext is attached. ` +\n `Use collection.query() instead of new Query() for cross-join support.`,\n )\n }\n result = executeClausePipeline(source, plan.clauses, joinContext)\n } else {\n // Index-aware fast path: only the clauses NOT consumed by the index need\n // re-evaluation. For a single-clause query against an indexed field,\n // `remainingClauses` is empty and we skip per-record predicate evaluation.\n const { candidates, remainingClauses } = candidateRecords(source, plan.clauses)\n result =\n remainingClauses.length === 0\n ? [...candidates]\n : filterRecords(candidates, remainingClauses, fnViewDecoder(source))\n }\n\n if (plan.orderBy.length > 0) {\n // dictKey label-sort: for any `orderBy(..., { by: 'label' })`, build a\n // sync code→label map at the query locale so the sort compares labels.\n const labelMaps = buildOrderLabelMaps(plan.orderBy, joinContext, locale)\n result = sortRecords(result, plan.orderBy, source.moneyFields, labelMaps)\n }\n if (plan.offset > 0) {\n result = result.slice(plan.offset)\n }\n if (plan.limit !== undefined) {\n result = result.slice(0, plan.limit)\n }\n return result\n}\n\ninterface CandidateResult {\n /** The reduced candidate set, materialized to record objects. */\n readonly candidates: readonly unknown[]\n /** The clauses that the index could not satisfy and must still be evaluated. */\n readonly remainingClauses: readonly Clause[]\n}\n\n/**\n * Pick a candidate record set using the index store when possible.\n *\n * Strategy: scan the top-level clauses for the FIRST `==` or `in` clause\n * against an indexed field. If found, use the index to materialize a\n * candidate set and return the OTHER clauses as `remainingClauses`. The\n * caller skips re-evaluating the index-driving clause because the index\n * is authoritative for that field.\n *\n * This is a deliberately simple planner. A future optimizer could pick\n * the most selective index, intersect multiple indexes, or push composite\n * keys through. For the single-index fast path is good enough.\n */\nfunction candidateRecords(source: InternalSource, clauses: readonly Clause[]): CandidateResult {\n const indexes = source.getIndexes?.()\n if (!indexes || !source.lookupById || clauses.length === 0) {\n return { candidates: source.snapshot(), remainingClauses: clauses }\n }\n // Bind the lookup method through an arrow so it doesn't drift from\n // its `this` context — keeps the unbound-method lint rule happy.\n const lookupById = (id: string): unknown => source.lookupById?.(id)\n\n for (let i = 0; i < clauses.length; i++) {\n const clause = clauses[i]!\n if (clause.type !== 'field') continue\n if (!indexes.has(clause.field)) continue\n // Multi-currency money operands are { amount, currency } objects —\n // the index stringifies object keys to a no-match sentinel, so a\n // lookup would return an authoritative-empty set and silently drop\n // every record. Fixed-mode money is fine: `value` was rewritten to\n // the stored digit string at build time.\n if (clause.money?.mode === 'multi') continue\n\n let ids: ReadonlySet<string> | null = null\n if (clause.op === '==') {\n ids = indexes.lookupEqual(clause.field, clause.value)\n } else if (clause.op === 'in' && Array.isArray(clause.value)) {\n ids = indexes.lookupIn(clause.field, clause.value)\n }\n\n if (ids !== null) {\n // Found an index-eligible clause: materialize the candidate set and\n // remove this clause from the remaining list.\n const remaining: Clause[] = []\n for (let j = 0; j < clauses.length; j++) {\n if (j !== i) remaining.push(clauses[j]!)\n }\n return {\n candidates: materializeIds(ids, lookupById),\n remainingClauses: remaining,\n }\n }\n // Not index-eligible — keep scanning in case a later clause is a\n // better candidate.\n }\n\n // No clause was index-eligible — fall back to a full scan.\n return { candidates: source.snapshot(), remainingClauses: clauses }\n}\n\nfunction materializeIds(\n ids: ReadonlySet<string>,\n lookupById: (id: string) => unknown,\n): unknown[] {\n const out: unknown[] = []\n for (const id of ids) {\n const record = lookupById(id)\n if (record !== undefined) out.push(record)\n }\n return out\n}\n\n/**\n * Execute a plan against a snapshot of records.\n * Pure function — same input, same output, no side effects.\n *\n * Records are typed as `unknown` because plans are non-parametric; callers\n * cast the return type at the API surface (see `Query.toArray()`).\n */\nexport function executePlan(records: readonly unknown[], plan: QueryPlan): unknown[] {\n if (plan.clauses.some(c => c.type === 'crossJoin')) {\n throw new Error(\n `executePlan(): does not support crossJoin clauses. ` +\n `executePlan is a stateless pure function — it cannot resolve cross-join right-side ` +\n `collections. Use Query.toArray() (via collection.query()) instead.`,\n )\n }\n let result = filterRecords(records, plan.clauses)\n if (plan.orderBy.length > 0) {\n result = sortRecords(result, plan.orderBy)\n }\n if (plan.offset > 0) {\n result = result.slice(plan.offset)\n }\n if (plan.limit !== undefined) {\n result = result.slice(0, plan.limit)\n }\n return result\n}\n\n/**\n * Build the per-record DECODED view factory for user-callback clauses\n * (`filter` / `wherePredicate`) — those callbacks must see the canonical\n * money shape, never the stored scaled-int. Field clauses are NOT\n * affected: their operands were quantized into raw stored space at build\n * time. Returns undefined when the source declares no money.\n */\nfunction fnViewDecoder(source: InternalSource): ((r: unknown) => unknown) | undefined {\n const mf = source.moneyFields\n if (!mf || Object.keys(mf).length === 0) return undefined\n return r => decodeMoneyFields(r as Record<string, unknown>, mf, 'raw')\n}\n\nfunction filterRecords(\n records: readonly unknown[],\n clauses: readonly Clause[],\n decodeForFns?: (r: unknown) => unknown,\n): unknown[] {\n if (clauses.length === 0) return [...records]\n // Decode once per record, and only when a callback clause will consume it.\n const needsFnView = decodeForFns !== undefined && hasFnClause(clauses)\n const out: unknown[] = []\n for (const r of records) {\n const fnView = needsFnView ? decodeForFns(r) : undefined\n let matches = true\n for (const clause of clauses) {\n if (!evaluateClause(r, clause, fnView)) {\n matches = false\n break\n }\n }\n if (matches) out.push(r)\n }\n return out\n}\n\n/**\n * Walk the clause list in declaration order, batching filter clauses and\n * expanding on crossJoin clauses. Falls back to `candidateRecords + filterRecords`\n * (index fast-path) when no crossJoin clauses are present.\n *\n * Precondition: `joinContext` must be non-null when any `CrossJoinClause` is in `clauses`.\n */\nfunction executeClausePipeline(\n source: InternalSource,\n clauses: readonly Clause[],\n joinContext: JoinContext,\n): unknown[] {\n let rel: unknown[] = [...source.snapshot()]\n let filterBatch: Clause[] = []\n const decodeForFns = fnViewDecoder(source)\n\n for (const clause of clauses) {\n if (clause.type === 'crossJoin') {\n if (filterBatch.length > 0) {\n rel = filterRecords(rel, filterBatch, decodeForFns)\n filterBatch = []\n }\n const rightSource = joinContext.resolveSource(clause.target)\n if (!rightSource) {\n throw new CrossJoinSourceUnknownError(clause.target, joinContext.leftCollection)\n }\n rel = applyCrossJoin(rel, clause, rightSource)\n } else {\n filterBatch.push(clause)\n }\n }\n\n if (filterBatch.length > 0) {\n rel = filterRecords(rel, filterBatch, decodeForFns)\n }\n\n return rel\n}\n\n/**\n * Expand `leftRel` by cross-joining with `rightSource`. Enforces the cost ceiling\n * BEFORE allocating the expanded relation (full cartesian) or cumulatively\n * (lateral form). Throws `CrossJoinTooLargeError` on breach.\n */\nfunction applyCrossJoin(\n leftRel: unknown[],\n clause: CrossJoinClause,\n rightSource: JoinableSource,\n): unknown[] {\n const rightRows = rightSource.snapshot()\n const maxRows = clause.maxRows ?? DEFAULT_CROSS_JOIN_MAX_ROWS\n const { as } = clause\n\n if (!clause.on) {\n const product = leftRel.length * rightRows.length\n if (product > maxRows) {\n throw new CrossJoinTooLargeError({ target: clause.target, expected: product, limit: maxRows })\n }\n const expanded: unknown[] = []\n for (const left of leftRel) {\n const leftObj = left as Record<string, unknown>\n for (const right of rightRows) {\n expanded.push({ ...leftObj, [as]: right })\n }\n }\n return expanded\n }\n\n // Lateral — ceiling is cumulative (post-filter count)\n const expanded: unknown[] = []\n let cumulative = 0\n for (const left of leftRel) {\n const callbackResult = clause.on(left)\n let filteredRight: readonly unknown[]\n if (Array.isArray(callbackResult)) {\n filteredRight = callbackResult\n } else {\n filteredRight = (rightRows as unknown[]).filter(\n callbackResult as (r: unknown) => boolean,\n )\n }\n cumulative += filteredRight.length\n if (cumulative > maxRows) {\n throw new CrossJoinTooLargeError({\n target: clause.target,\n expected: cumulative,\n limit: maxRows,\n })\n }\n const leftObj = left as Record<string, unknown>\n for (const right of filteredRight) {\n expanded.push({ ...leftObj, [as]: right })\n }\n }\n return expanded\n}\n\nfunction sortRecords(\n records: unknown[],\n orderBy: readonly OrderBy[],\n moneyFields?: Record<string, MoneyDescriptor>,\n labelMaps?: Map<string, Map<string, string>>,\n): unknown[] {\n // Stable sort: Array.prototype.sort is required to be stable since ES2019.\n return [...records].sort((a, b) => {\n for (const { field, direction, by } of orderBy) {\n let av = readField(a, field)\n let bv = readField(b, field)\n // dictKey label-sort: compare resolved labels (fallback to the code\n // when unresolved), so e.g. honorific codes sort by their locale label.\n const labelMap = by === 'label' ? labelMaps?.get(field) : undefined\n if (labelMap) {\n av = (typeof av === 'string' ? labelMap.get(av) : undefined) ?? av\n bv = (typeof bv === 'string' ? labelMap.get(bv) : undefined) ?? bv\n const cmp = compareValues(av, bv)\n if (cmp !== 0) return direction === 'asc' ? cmp : -cmp\n continue\n }\n // Money fields are stored as scaled-integer strings; the generic\n // string comparator would sort them lexically ('9882' > '10004'). Compare\n // declared money fields by their BigInt scaled value instead — exact, and\n // consistent with `where` and `sum`.\n const desc = moneyFields?.[field]\n const cmp = desc ? compareMoney(av, bv, desc) : compareValues(av, bv)\n if (cmp !== 0) return direction === 'asc' ? cmp : -cmp\n }\n return 0\n })\n}\n\n/**\n * dictKey label-sort: for each `orderBy(..., { by: 'label' })` field, build\n * a sync `code → label` map at the query `locale` (falling back to a\n * `staticDict` `displayLocale`) from the join context's dict source. Fields\n * with no resolvable dict source are skipped — the sort then falls back to the\n * stored code for them.\n */\nfunction buildOrderLabelMaps(\n orderBy: readonly OrderBy[],\n joinContext: JoinContext | undefined,\n locale: string | undefined,\n): Map<string, Map<string, string>> | undefined {\n if (!joinContext?.resolveDictSource) return undefined\n const resolveDict = joinContext.resolveDictSource.bind(joinContext)\n let maps: Map<string, Map<string, string>> | undefined\n for (const { field, by } of orderBy) {\n if (by !== 'label') continue\n const dictSource = resolveDict(field)\n if (!dictSource) continue\n const loc = locale ?? dictSource.displayLocale\n if (loc === undefined) continue\n const codeToLabel = new Map<string, string>()\n for (const entry of dictSource.snapshot()) {\n const k = (entry as Record<string, unknown>)['key']\n const labels = (entry as Record<string, unknown>)['labels'] as Record<string, string> | undefined\n const label = labels?.[loc]\n if (typeof k === 'string' && typeof label === 'string') codeToLabel.set(k, label)\n }\n ;(maps ??= new Map()).set(field, codeToLabel)\n }\n return maps\n}\n\n/** Compare two stored money values by BigInt scaled-int; nullish/malformed last (asc). */\nfunction compareMoney(a: unknown, b: unknown, desc: MoneyDescriptor): number {\n const av = moneyScaledValue(a, desc)\n const bv = moneyScaledValue(b, desc)\n if (av === null) return bv === null ? 0 : 1\n if (bv === null) return -1\n return av < bv ? -1 : av > bv ? 1 : 0\n}\n\nfunction readField(record: unknown, field: string): unknown {\n if (record === null || record === undefined) return undefined\n if (!field.includes('.')) {\n return (record as Record<string, unknown>)[field]\n }\n const segments = field.split('.')\n let cursor: unknown = record\n for (const segment of segments) {\n if (cursor === null || cursor === undefined) return undefined\n cursor = (cursor as Record<string, unknown>)[segment]\n }\n return cursor\n}\n\nfunction compareValues(a: unknown, b: unknown): number {\n // Nullish goes last in asc order.\n if (a === undefined || a === null) return b === undefined || b === null ? 0 : 1\n if (b === undefined || b === null) return -1\n if (typeof a === 'number' && typeof b === 'number') return a - b\n if (typeof a === 'string' && typeof b === 'string') return a < b ? -1 : a > b ? 1 : 0\n if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime()\n // Mixed/unsupported types: treat as equal so the sort stays stable.\n // (Deliberate choice — we don't try to coerce arbitrary objects to strings.)\n return 0\n}\n\nfunction serializePlan(plan: QueryPlan): unknown {\n return {\n clauses: plan.clauses.map(serializeClause),\n orderBy: plan.orderBy,\n limit: plan.limit,\n offset: plan.offset,\n joins: plan.joins,\n }\n}\n\nfunction serializeClause(clause: Clause): unknown {\n if (clause.type === 'filter') {\n return { type: 'filter', fn: '[function]' }\n }\n if (clause.type === 'wherePredicate') {\n // Strip the live `fn` reference (non-serializable) but keep the\n // identity-carrying fields so distinct predicates still serialize\n // distinctly. `predicateHash` + `ctxHash` are the hash identity;\n // `name` is the named predicate reference. This matters because\n // A previous fall-through (return clause) exposed the live fn and produced\n // identical serializations for distinct predicates with different ctx values.\n return {\n type: 'wherePredicate',\n name: clause.name,\n ctx: clause.ctx,\n predicateHash: clause.predicateHash,\n ctxHash: clause.ctxHash,\n fn: '[function]',\n }\n }\n if (clause.type === 'group') {\n return {\n type: 'group',\n op: clause.op,\n clauses: clause.clauses.map(serializeClause),\n }\n }\n if (clause.type === 'crossJoin') {\n return {\n type: 'crossJoin',\n target: clause.target,\n as: clause.as,\n on: clause.on ? '[function]' : undefined,\n onPredicateName: clause.onPredicateName,\n maxRows: clause.maxRows,\n }\n }\n return clause\n}\n\n/**\n * Compute a stable hash of a `ctx` value supplied to\n * `.wherePredicate(name, ctx)`. Canonical-JSON: keys sorted at each\n * level so `{a, b}` and `{b, a}` hash to the same value. Undefined ctx\n * hashes to the empty string. The hash is sync because it just runs\n * a cheap djb2-style fold — used at builder time, not security-sensitive.\n *\n * @internal\n */\nfunction canonicalCtxHash(ctx: unknown): string {\n if (ctx === undefined) return \"\"\n const canonical = JSON.stringify(ctx, (_key, value) => {\n if (value && typeof value === \"object\" && !Array.isArray(value)) {\n const sorted: Record<string, unknown> = {}\n for (const k of Object.keys(value as Record<string, unknown>).sort()) {\n sorted[k] = (value as Record<string, unknown>)[k]\n }\n return sorted\n }\n return value\n })\n // djb2 fold over the canonical string; converted to hex.\n let h = 5381\n for (let i = 0; i < canonical.length; i++) {\n h = ((h << 5) + h) ^ canonical.charCodeAt(i)\n }\n return (h >>> 0).toString(16).padStart(8, \"0\")\n}\n\n/**\n * Build a dict-label resolver for `Query.groupBy(field)` when the\n * grouping field is a `dictKey`. Extracted from the inline closure\n * inside `groupBy` so the multi-key path (which has no meaningful\n * `<field>Label` shape) can skip it cleanly. Pure refactor — no\n * behaviour change for the single-field path.\n *\n * Returns `undefined` when:\n * - the join context lacks a `resolveDictSource` hook, or\n * - no dictionary source is registered for `field`.\n *\n * @internal\n */\nfunction buildDictLabelResolver(\n joinCtx: JoinContext | undefined,\n field: string,\n):\n | ((key: string, locale: string, fallback?: string | readonly string[]) => Promise<string | undefined>)\n | undefined {\n if (!joinCtx?.resolveDictSource) return undefined\n const dictSource = joinCtx.resolveDictSource(field)\n if (!dictSource) return undefined\n const snapshot = dictSource.snapshot()\n // A staticDict-backed source carries a `displayLocale`; use it as the\n // locale default when the query is locale-less, so `{ by: 'label' }` still\n // resolves under a locale-less read. Plain _dict_* sources omit it.\n const displayLocale = dictSource.displayLocale\n const dictMap = new Map<string, Record<string, string>>()\n for (const entry of snapshot) {\n const k = (entry as Record<string, unknown>)['key']\n const labels = (entry as Record<string, unknown>)['labels']\n if (typeof k === 'string' && labels && typeof labels === 'object') {\n dictMap.set(k, labels as Record<string, string>)\n }\n }\n return async (\n key: string,\n locale: string,\n fallback?: string | readonly string[],\n ): Promise<string | undefined> => {\n const effLocale = locale || displayLocale\n if (!effLocale) return undefined\n const labels = dictMap.get(key)\n if (!labels) return undefined\n if (labels[effLocale] !== undefined) return labels[effLocale]\n const chain = Array.isArray(fallback)\n ? (fallback as readonly string[])\n : fallback\n ? [fallback as string]\n : []\n for (const fb of chain) {\n if (fb === 'any') {\n const any = Object.values(labels)[0]\n if (any !== undefined) return any\n } else if (labels[fb] !== undefined) {\n return labels[fb]\n }\n }\n return undefined\n }\n}\n","/**\n * Streaming scan builder with filter + aggregate support.\n *\n * `Collection.scan()` now returns a `ScanBuilder<T>` that\n * implements `AsyncIterable<T>` (for existing `for await … of`\n * consumers) AND exposes chainable `.where()` / `.filter()` clauses\n * plus a `.aggregate(spec)` async terminal that reduces the scan\n * stream through the same reducer protocol as `Query.aggregate()`\n *.\n *\n * **Memory model:** O(reducers), not O(records). The aggregate\n * terminal initializes one state per reducer, iterates through the\n * scan one record at a time via `for await`, applies every reducer's\n * `step` per record, and never collects the stream into an array.\n * This is what makes `scan().aggregate()` suitable for collections\n * that don't fit in memory — the bound is a code-level invariant\n * visible in the function body, not a runtime assertion.\n *\n * **Paginated iteration:** the builder holds a `pageProvider`\n * closure that maps `(cursor, limit) → Promise<page>`, plumbed by\n * `Collection.scan()` to `collection.listPage(...)`. The page\n * iterator walks cursors forward until exhaustion, same as the\n * previous async-generator `scan()` did.\n *\n * **Backward compatibility:** existing `for await (const rec of\n * collection.scan()) { … }` code continues to work because\n * `ScanBuilder` implements `[Symbol.asyncIterator]`. The previous\n * signature returned an `AsyncIterableIterator<T>` (which has both\n * `[Symbol.asyncIterator]` and `.next()`). We verified at grep time\n * that no call sites use `.next()` on the scan result directly, so\n * the narrowed interface is safe.\n *\n * **Immutability:** each `.where()` / `.filter()` call returns a\n * fresh builder sharing the same page provider and page size. This\n * lets a base scan be reused for multiple parallel aggregations:\n *\n * ```ts\n * const scan = invoices.scan()\n * const [open, paid] = await Promise.all([\n * scan.where('status', '==', 'open').aggregate({ n: count() }),\n * scan.where('status', '==', 'paid').aggregate({ n: count() }),\n * ])\n * ```\n *\n * Note that each aggregation pays a full scan — there's no shared\n * iteration across the two. Multi-way aggregation in a single pass\n * is out of scope; consumers who need it should build a compound spec\n * and run a single `.aggregate({ openN, paidN })` at the DSL level.\n *\n * **Out of scope for (tracked separately):**\n * - `scan().aggregate().live()` — unbounded scan + change-stream\n * reconciliation is a design problem, not just a code one\n * - `scan().groupBy().aggregate()` — high-cardinality grouping on\n * huge collections would re-introduce the O(groups) memory\n * problem that aggregate fixes\n * - Parallel scan across pages — race-safe page cursor contracts\n * are not in the adapter API yet\n * - `scan().join(...)` — tracked under (streaming join)\n */\n\nimport type { QueryField } from '../types.js'\nimport type { ReducerBuilder } from '../../with-lookup/aggregate/reducers.js'\nimport { reducerBuilder } from '../../with-lookup/aggregate/reducers.js'\nimport type { Clause, FieldClause, Operator } from './predicate.js'\nimport { evaluateClause, hasFnClause, readPath } from './predicate.js'\nimport type {\n AggregateSpec,\n AggregateResult,\n} from '../../with-lookup/aggregate/aggregation.js'\nimport type { JoinContext, JoinLeg, JoinableSource } from './join.js'\nimport { DanglingReferenceError } from '../errors.js'\nimport type { MoneyDescriptor } from '../../with-shape/money/descriptor.js'\nimport { decodeMoneyFields } from '../../with-shape/money/normalize.js'\nimport { moneyFieldClause } from '../../with-shape/money/where.js'\n\n/**\n * Page provider — the Collection-shaped hook the builder calls to\n * walk cursors forward. Kept as a structural interface so tests can\n * wire up a synthetic provider without pulling in the full\n * Collection class. Collection's `listPage` matches this shape\n * exactly.\n */\nexport interface ScanPageProvider<T> {\n listPage(opts: {\n cursor?: string\n limit?: number\n }): Promise<{ items: T[]; nextCursor: string | null }>\n}\n\nconst DEFAULT_SCAN_PAGE_SIZE = 100\n\n/**\n * Chainable streaming scan. Implements `AsyncIterable<T>` for\n * drop-in use with `for await … of`; adds `.where()` / `.filter()`\n * chainable clauses and a `.aggregate(spec)` async terminal.\n *\n * The builder is immutable per operation — each chained call\n * returns a fresh `ScanBuilder` sharing the same page provider and\n * page size. The original builder is never mutated, so it's safe\n * to reuse across multiple parallel consumers.\n */\nexport class ScanBuilder<T, S extends keyof T = never, M extends keyof T & string = never> implements AsyncIterable<T> {\n private readonly pageProvider: ScanPageProvider<T>\n private readonly pageSize: number\n private readonly clauses: readonly Clause[]\n /**\n * Zero-or-more join legs to apply per record as the stream flows.\n * Each leg attaches the resolved right-side record (or null) under\n * its alias. — streaming joins.\n *\n * Joins are evaluated AFTER clauses, so a `where()` filtered-out\n * record never triggers a right-side lookup. This is the same\n * ordering as `Query.toArray()` (clauses first, joins after) and\n * keeps the streaming path from doing wasted work.\n */\n private readonly joins: readonly JoinLeg[]\n /**\n * Join resolution context. Required for `.join()` to translate a\n * field name into a target collection + ref mode and to resolve\n * the right-side `JoinableSource`. Optional because tests\n * construct ScanBuilder directly with synthetic page providers\n * that don't know about ref() — calling `.join()` without a\n * context throws with an actionable error.\n */\n private readonly joinContext: JoinContext | undefined\n /**\n * Money field descriptors for the backing collection. When present, yielded\n * records are decoded (stored scaled-int → canonical decimal) so `scan()`\n * agrees with `get()`/`list()`/`query().toArray()`. Decoded with\n * `'raw'` (canonical decimal, no locale-formatted virtuals) since the scan\n * stream carries no locale context, mirroring `Query.toArray()`.\n */\n private readonly moneyFields: Record<string, MoneyDescriptor> | undefined\n\n constructor(\n pageProvider: ScanPageProvider<T>,\n pageSize: number = DEFAULT_SCAN_PAGE_SIZE,\n clauses: readonly Clause[] = [],\n joins: readonly JoinLeg[] = [],\n joinContext?: JoinContext,\n moneyFields?: Record<string, MoneyDescriptor>,\n ) {\n this.pageProvider = pageProvider\n this.pageSize = pageSize\n this.clauses = clauses\n this.joins = joins\n this.joinContext = joinContext\n this.moneyFields = moneyFields\n }\n\n /**\n * Decode this scan's money fields on a record (stored scaled-int → canonical\n * decimal). No-op when no money fields are declared. See {@link moneyFields}.\n */\n private decodeMoney(record: T): T {\n if (!this.moneyFields || Object.keys(this.moneyFields).length === 0) return record\n return decodeMoneyFields(record as Record<string, unknown>, this.moneyFields, 'raw') as T\n }\n\n /**\n * Add a field comparison. Runs per record as the scan stream\n * flows through, so non-matching records are dropped before they\n * reach `.aggregate()` or the iteration consumer. Multiple\n * `.where()` calls are AND-combined — same semantics as\n * `Query.where()`.\n *\n * Clauses cannot use the secondary-index fast path here because\n * the scan sources records from the adapter's paginator, not from\n * the in-memory cache where indexes live. Index-accelerated scans\n * are a future optimization — the current implementation\n * evaluates clauses per record in O(1) per clause.\n */\n where(field: QueryField<T, S>, op: Operator, value: unknown): ScanBuilder<T, S, M> {\n // Money fields compare in major units, BigInt-exact in scaled space —\n // same build-time operand rewrite as Query.where().\n const desc = this.moneyFields?.[field as string]\n const clause: FieldClause = desc\n ? moneyFieldClause(field as string, op, value, desc)\n : { type: 'field', field: field as string, op, value }\n return new ScanBuilder<T, S, M>(\n this.pageProvider,\n this.pageSize,\n [...this.clauses, clause],\n this.joins,\n this.joinContext,\n this.moneyFields,\n )\n }\n\n /**\n * Escape hatch: add an arbitrary predicate function. Same\n * non-serializable caveat as `Query.filter()` — filter clauses\n * don't round-trip through `toPlan()`. Prefer `.where()` when\n * possible.\n */\n filter(fn: (record: T) => boolean): ScanBuilder<T, S, M> {\n const clause: Clause = {\n type: 'filter',\n fn: fn as (record: unknown) => boolean,\n }\n return new ScanBuilder<T, S, M>(\n this.pageProvider,\n this.pageSize,\n [...this.clauses, clause],\n this.joins,\n this.joinContext,\n this.moneyFields,\n )\n }\n\n /**\n * Resolve a `ref()`-declared foreign key per record as the scan\n * stream flows, attaching the right-side record (or null) under\n * `opts.as`. — streaming joins over `scan()`.\n *\n * ```ts\n * for await (const inv of invoices.scan().join('clientId', { as: 'client' })) {\n * await processInvoice(inv) // inv.client is attached\n * }\n *\n * // Or terminate with .aggregate() for streaming joined aggregation\n * const { total } = await invoices.scan()\n * .where('status', '==', 'open')\n * .join('clientId', { as: 'client' })\n * .aggregate({ total: sum('amount') })\n * ```\n *\n * **The key difference from eager `.join()`:** the LEFT\n * side streams page-by-page from the adapter and is never\n * materialized. Memory ceiling on the left is O(pageSize), not\n * O(rowCount). This is what makes streaming joins suitable for\n * collections that exceed the eager join's 50_000-row ceiling.\n *\n * **Right-side strategy** is auto-selected per leg:\n * - **Indexed** — right source exposes `lookupById`, so each\n * left row costs O(1). This is the common path for\n * Collection right sides, which back `lookupById` with a Map\n * lookup over the in-memory cache. The right collection must\n * be in eager mode (the same constraint as eager join's\n * `querySourceForJoin` from ).\n * - **Hash** — right source has only `snapshot()`. Build a\n * `Map<id, record>` once at iteration start, probe per left\n * row. Same correctness, same per-row cost as the indexed\n * path; the difference is the upfront cost of materializing\n * the right side once.\n *\n * Both strategies hold the right side in memory for the duration\n * of the iteration. The \"streaming\" property applies to the LEFT\n * side only — true left-and-right streaming joins (where neither\n * side fits in memory) require a sort-merge join planner that's\n * out of scope for.\n *\n * **Ref-mode semantics** match eager `.join()` exactly:\n * - `strict` → throws `DanglingReferenceError` mid-stream\n * when a left record points at a non-existent right id.\n * The throw aborts the async iterator — consumers should\n * wrap the `for await` in try/catch if they want to recover.\n * - `warn` → attaches `null` and emits a one-shot warning\n * per unique dangling pair (deduped via the same warn\n * channel as eager join).\n * - `cascade` → attaches `null` silently. A delete-time mode;\n * dangling refs at read time are mid-flight or pre-existing\n * orphans, not a DSL error.\n *\n * Left records with null/undefined FK values attach `null`\n * regardless of mode — same \"no reference at all\" policy as\n * eager join and write-time `enforceRefsOnPut`.\n *\n * **Multi-FK chaining** is supported via repeated `.join()`\n * calls: each leg resolves an independent ref. Each leg\n * independently picks its right-side strategy and applies its\n * own ref mode.\n *\n * **Joins are NOT applied** to a `.aggregate()` terminal that\n * doesn't reference joined fields — wait, that's not quite\n * right. The streaming path actually DOES apply joins before\n * `.aggregate()` because the join attaches a field that the\n * spec might reference. Unlike `Query.aggregate()` (which skips\n * joins entirely as a projection-only short-circuit), the\n * streaming aggregation can't know whether the spec touches a\n * joined field, so it always applies joins. Consumers who want\n * unjoined streaming aggregation should leave `.join()` off the\n * chain — the chain is composable for a reason.\n *\n * constraint #1 — every JoinLeg carries `partitionScope:\n * 'all'` plumbed through but never read by. Same seam as\n * eager join.\n */\n join<As extends string, R = unknown>(\n field: QueryField<T, S>,\n opts: { as: As },\n ): ScanBuilder<T & Record<As, R | null>, S, M> {\n if (!this.joinContext) {\n throw new Error(\n `ScanBuilder.join() requires a join context. Use ` +\n `collection.scan() to construct a join-capable scan instead ` +\n `of the ScanBuilder constructor directly (the direct ` +\n `constructor is only used for tests with synthetic page ` +\n `providers).`,\n )\n }\n const descriptor = this.joinContext.resolveRef(field)\n if (!descriptor) {\n throw new Error(\n `ScanBuilder.join(): no ref() declared for field \"${field}\" on ` +\n `collection \"${this.joinContext.leftCollection}\". Add ` +\n `refs: { ${field}: ref('<target-collection>') } to the ` +\n `collection options, then retry.`,\n )\n }\n const leg: JoinLeg = {\n field,\n as: opts.as,\n target: descriptor.target,\n mode: descriptor.mode,\n strategy: undefined,\n maxRows: undefined,\n // constraint #1 — always 'all' in, never read by\n // the streaming executor. partition-aware scan joins\n // will populate this from where() predicates without\n // changing the planner shape.\n partitionScope: 'all',\n }\n return new ScanBuilder<T & Record<As, R | null>, S, M>(\n this.pageProvider as unknown as ScanPageProvider<T & Record<As, R | null>>,\n this.pageSize,\n this.clauses,\n [...this.joins, leg],\n this.joinContext,\n this.moneyFields,\n )\n }\n\n /**\n * Iterate the scan as an async iterable. Walks the page\n * provider's cursors forward until exhaustion, applying every\n * clause per record — only matching records are yielded.\n *\n * Backward-compatible with the previous async-generator `scan()`\n * return type for `for await … of` consumers.\n */\n async *[Symbol.asyncIterator](): AsyncIterator<T> {\n // One-time setup: resolve every join leg's right-side source\n // and pick its strategy (lookupById per row vs hash from\n // snapshot once). Both are O(left) per record after setup; the\n // difference is the upfront cost of hashing the right side\n // when there's no lookupById.\n //\n // Hash maps live for the lifetime of the iteration, so memory\n // for the right side is O(rightRowCount) per leg. Memory for\n // the left side stays O(pageSize) regardless — that's the\n // streaming property we're after.\n const joinResolvers = this.joins.length === 0 ? null : this.buildJoinResolvers()\n\n let page = await this.pageProvider.listPage({ limit: this.pageSize })\n while (true) {\n for (const record of page.items) {\n // Filter on the raw stored record (same order as Query.toArray:\n // clauses first), then decode money to the canonical decimal before\n // yielding so scan() never leaks the internal scaled-int.\n if (!this.recordMatches(record)) continue\n const decoded = this.decodeMoney(record)\n if (joinResolvers === null) {\n yield decoded\n } else {\n // Apply every join leg in declaration order. Each\n // leg attaches a field — the result of one leg becomes\n // the input to the next. Multi-FK chaining is\n // supported by construction.\n let attached: unknown = decoded\n for (const resolver of joinResolvers) {\n attached = this.applyOneJoinStreaming(attached, resolver)\n }\n yield attached as T\n }\n }\n if (page.nextCursor === null) return\n page = await this.pageProvider.listPage({\n cursor: page.nextCursor,\n limit: this.pageSize,\n })\n }\n }\n\n /**\n * Per-leg right-side resolution state. Built once at iteration\n * start and reused for every left record. Two strategies:\n *\n * - `lookupById`: present when the right source exposes the\n * hook directly (typical Collection right side). Per-row\n * cost is O(1).\n * - `hashByPrimaryKey`: built from `snapshot()` when no\n * lookupById. Per-row cost is O(1) after the upfront O(N)\n * materialization. Same as eager join's hash strategy.\n *\n * `warnedKeys` is the per-leg dedup set for ref-mode 'warn'. We\n * key on `field→target:refId` so the same dangling pair only\n * warns once per iteration. The dedup is per-iteration, not\n * per-process — a long-running scan that re-iterates would warn\n * again, which is the desired behavior (the data may have\n * changed between iterations).\n */\n private buildJoinResolvers(): Array<{\n leg: JoinLeg\n source: JoinableSource\n lookupById: ((id: string) => unknown) | null\n hashByPrimaryKey: ReadonlyMap<string, unknown> | null\n warnedKeys: Set<string>\n }> {\n if (!this.joinContext) {\n // Unreachable — .join() throws if joinContext is missing.\n // Belt-and-braces because the iterator is invoked via\n // Symbol.asyncIterator on a builder that may have been\n // constructed via the direct constructor with pre-populated\n // joins.\n throw new Error(\n `ScanBuilder iterator: ${this.joins.length} join leg(s) ` +\n `present but no JoinContext attached. Use collection.scan() ` +\n `to construct a join-capable scan.`,\n )\n }\n const resolvers: Array<{\n leg: JoinLeg\n source: JoinableSource\n lookupById: ((id: string) => unknown) | null\n hashByPrimaryKey: ReadonlyMap<string, unknown> | null\n warnedKeys: Set<string>\n }> = []\n for (const leg of this.joins) {\n const source = this.joinContext.resolveSource(leg.target)\n if (!source) {\n throw new Error(\n `ScanBuilder.join() cannot resolve target collection ` +\n `\"${leg.target}\" (referenced from field \"${leg.field}\" on ` +\n `\"${this.joinContext.leftCollection}\"). Make sure the target ` +\n `collection has been opened via vault.collection() ` +\n `at least once before iterating the scan.`,\n )\n }\n // Strategy selection: prefer lookupById when available\n // (O(1) per row, no upfront cost), fall back to hashing\n // snapshot() once otherwise.\n let lookupById: ((id: string) => unknown) | null = null\n let hashByPrimaryKey: ReadonlyMap<string, unknown> | null = null\n if (source.lookupById) {\n // Bind through an arrow so the lookupById's `this`\n // doesn't drift — same pattern as the eager join's\n // strategy resolver.\n const fn = source.lookupById.bind(source)\n lookupById = (id: string): unknown => fn(id)\n } else {\n const map = new Map<string, unknown>()\n for (const record of source.snapshot()) {\n const rawId = readPath(record, 'id')\n const key = coerceRefKey(rawId)\n if (key !== null) map.set(key, record)\n }\n hashByPrimaryKey = map\n }\n resolvers.push({\n leg,\n source,\n lookupById,\n hashByPrimaryKey,\n warnedKeys: new Set<string>(),\n })\n }\n return resolvers\n }\n\n /**\n * Resolve a single join leg for one left record and return the\n * left record with the joined field attached under\n * `leg.as`. Pure function over `(left, resolver)`; never\n * mutates the input.\n *\n * Ref-mode dispatch matches eager `applyJoins` from :\n * - null/undefined FK → attach null silently (always allowed)\n * - dangling FK + strict → throw `DanglingReferenceError`\n * - dangling FK + warn → attach null, warn-once per pair\n * - dangling FK + cascade → attach null silently\n */\n private applyOneJoinStreaming(\n left: unknown,\n resolver: {\n leg: JoinLeg\n source: JoinableSource\n lookupById: ((id: string) => unknown) | null\n hashByPrimaryKey: ReadonlyMap<string, unknown> | null\n warnedKeys: Set<string>\n },\n ): unknown {\n if (left === null || typeof left !== 'object') {\n // Pathological input; matches eager join's defensive return.\n return left\n }\n const { leg } = resolver\n const rawId = readPath(left, leg.field)\n const refKey = coerceRefKey(rawId)\n let right: unknown = undefined\n if (refKey !== null) {\n if (resolver.lookupById !== null) {\n right = resolver.lookupById(refKey)\n } else if (resolver.hashByPrimaryKey !== null) {\n right = resolver.hashByPrimaryKey.get(refKey)\n }\n }\n\n const merged: Record<string, unknown> = {\n ...(left as Record<string, unknown>),\n }\n if (right === undefined) {\n // No matching record. Distinguish \"no ref at all\" (null FK)\n // from \"dangling ref\" (FK pointed at nothing).\n if (refKey !== null && leg.mode === 'strict') {\n throw new DanglingReferenceError({\n field: leg.field,\n target: leg.target,\n refId: refKey,\n message:\n `ScanBuilder.join() strict dangling: record references ` +\n `\"${leg.target}:${refKey}\" via field \"${leg.field}\", but no ` +\n `such record exists. Use ref() mode 'warn' or 'cascade' if ` +\n `dangling refs are acceptable, or run ` +\n `vault.checkIntegrity() to find and fix the orphans.`,\n })\n }\n if (refKey !== null && leg.mode === 'warn') {\n const dedupKey = `${leg.field}→${leg.target}:${refKey}`\n if (!resolver.warnedKeys.has(dedupKey)) {\n resolver.warnedKeys.add(dedupKey)\n console.warn(\n `[noy-db] ScanBuilder.join() encountered dangling ref in ` +\n `'warn' mode: field \"${leg.field}\" → \"${leg.target}:` +\n `${refKey}\" not found. Attaching null.`,\n )\n }\n }\n // strict already threw above; warn falls through here; cascade\n // hits this path silently.\n merged[leg.as] = null\n } else {\n merged[leg.as] = right\n }\n return merged\n }\n\n /**\n * Reduce the scan stream through a named set of reducers and\n * return the final aggregated shape.\n *\n * Memory is O(reducers): one mutable state slot per spec key.\n * Records flow through the pipeline one at a time via\n * `for await` and are discarded after their `step()` is applied\n * — never collected into an array. This is the distinguishing\n * property from `Query.aggregate()`, which materializes the full\n * match set first.\n *\n * Reuses the same reducer protocol as `Query.aggregate()`,\n * so `count()`, `sum(field)`, `avg(field)`, `min(field)`,\n * `max(field)` all work unchanged. The `{ seed }` parameter\n * plumbing from constraint #2 is honored transparently — the\n * factories ignore it in and the scan executor never\n * touches the per-reducer state construction.\n *\n * **Returns a Promise**, unlike `Query.aggregate().run()` which\n * is synchronous. The scan is inherently async because it walks\n * adapter pages, so the terminal has to be too. Consumers\n * destructure with await:\n *\n * ```ts\n * const { total, n } = await invoices.scan()\n * .where('year', '==', 2025)\n * .aggregate({ total: sum('amount'), n: count() })\n * ```\n *\n * **No `.live()` in.** `scan().aggregate().live()` would\n * require reconciling an unbounded streaming iteration with a\n * change-stream subscription — a design problem, not just a code\n * one. Consumers with huge collections and live needs should\n * narrow with `.where()` enough to fit in the 50k `query()`\n * limit and use `query().aggregate().live()` instead.\n */\n async aggregate<Spec extends AggregateSpec>(spec: Spec): Promise<AggregateResult<Spec>>\n async aggregate<Spec extends AggregateSpec>(build: (b: ReducerBuilder<T, S, M>) => Spec): Promise<AggregateResult<Spec>>\n async aggregate<Spec extends AggregateSpec>(\n specOrBuild: Spec | ((b: ReducerBuilder<T, S, M>) => Spec),\n ): Promise<AggregateResult<Spec>> {\n // Opt-in builder form `aggregate(b => spec)`: `b`'s field args are\n // `QueryField<T, S>`, refusing sensitive fields (the standalone-spec form\n // stays unrefused for back-compat), and `sum`/`min`/`max` over a declared\n // `moneyFields` (`M`) member return a `MoneyString`. Mirrors `Query.aggregate`.\n const spec: Spec = typeof specOrBuild === 'function'\n ? (specOrBuild as (b: ReducerBuilder<T, S, M>) => Spec)(reducerBuilder as unknown as ReducerBuilder<T, S, M>)\n : specOrBuild\n const keys = Object.keys(spec)\n // Per-reducer state. Exactly |keys| entries, never grows with\n // the record count — that's the O(reducers) memory guarantee.\n const state: Record<string, unknown> = {}\n for (const key of keys) {\n state[key] = spec[key]!.init()\n }\n\n // Record-by-record streaming step. `for await (… of this)`\n // invokes the Symbol.asyncIterator above, which honors the\n // clause list, so filtered-out records never reach the step\n // loop — they're dropped at the iterator boundary.\n for await (const record of this) {\n for (const key of keys) {\n state[key] = spec[key]!.step(state[key], record)\n }\n }\n\n const result: Record<string, unknown> = {}\n for (const key of keys) {\n result[key] = spec[key]!.finalize(state[key])\n }\n return result as AggregateResult<Spec>\n }\n\n /**\n * Evaluate the clause list against a single record. Linear in\n * the clause count; short-circuits on first false. Clauses on a\n * scan are always re-evaluated per record — no index-accelerated\n * path, because the stream sources records from the adapter\n * paginator, not from the in-memory cache where indexes live.\n */\n private recordMatches(record: T): boolean {\n if (this.clauses.length === 0) return true\n // User-callback clauses (filter) see the DECODED money view;\n // field clauses keep the raw record — their operands are pre-quantized\n // into stored space. Decoded at most once per record, only\n // when a callback clause exists.\n const fnView =\n this.moneyFields && Object.keys(this.moneyFields).length > 0 && hasFnClause(this.clauses)\n ? this.decodeMoney(record)\n : undefined\n for (const clause of this.clauses) {\n if (!evaluateClause(record, clause, fnView)) return false\n }\n return true\n }\n}\n\n/**\n * Coerce an unknown FK value into a lookup key string.\n *\n * Mirror of the same helper in `query/join.ts` — kept local to\n * `scan-builder.ts` to avoid pulling the eager join executor's\n * surface area into this file. Strings and numbers convert to\n * string keys; everything else (objects, arrays, booleans, null,\n * undefined) returns null and is treated as \"no ref at all\".\n *\n * Matches the write-time `enforceRefsOnPut` policy: nullish ref\n * values are never dangling, regardless of mode.\n */\nfunction coerceRefKey(value: unknown): string | null {\n if (value === null || value === undefined) return null\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'bigint') return String(value)\n return null\n}\n"],"mappings":"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","names":["obj","clone","currency","expanded","cmp","coerceRefKey"]}
|