@noy-db/hub 0.3.0-pre.2 → 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 -6
- package/dist/at/index.d.ts +2 -2
- package/dist/attestation/index.d.ts +3 -3
- package/dist/attestation/index.js +19 -13
- package/dist/attestation/index.js.map +1 -1
- package/dist/{backup-KMJQ66MF.js → backup-MOB27UUN.js} +12 -6
- package/dist/{backup-KMJQ66MF.js.map → backup-MOB27UUN.js.map} +1 -1
- package/dist/blobs/index.d.ts +4 -4
- package/dist/blobs/index.js +11 -5
- package/dist/blobs/index.js.map +1 -1
- package/dist/bundle/index.d.ts +5 -5
- package/dist/bundle/index.js +21 -15
- package/dist/bundle/index.js.map +1 -1
- package/dist/{bundle-PwBGkhpe.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 -17
- package/dist/{chunk-SMXWYP24.js → chunk-3QRQOBS7.js} +3 -3
- package/dist/{chunk-QHJW5EXU.js → chunk-3Y4QLJ6K.js} +2 -2
- package/dist/{chunk-NF5JWERG.js → chunk-43ISXURO.js} +2 -2
- package/dist/{chunk-5LUY7UTV.js → chunk-4K3MQ5S3.js} +2 -2
- package/dist/{chunk-QZISFCVG.js → chunk-5CY35H55.js} +11 -9
- package/dist/{chunk-QZISFCVG.js.map → chunk-5CY35H55.js.map} +1 -1
- package/dist/chunk-5EWYUR5P.js +37 -0
- package/dist/chunk-5EWYUR5P.js.map +1 -0
- package/dist/{chunk-Y22T3KQE.js → chunk-5MRE3C4Q.js} +5 -5
- package/dist/{chunk-M6OST55S.js → chunk-5W7AOFWA.js} +2 -2
- package/dist/{chunk-AXRXJTE2.js → chunk-6HNKCKFZ.js} +7 -7
- package/dist/{chunk-UAG5KWVA.js → chunk-6RXPSMKR.js} +3 -3
- package/dist/{chunk-TJYQIGAP.js → chunk-7J7JRYXW.js} +9 -5
- package/dist/chunk-7J7JRYXW.js.map +1 -0
- package/dist/{chunk-UDRIWL7A.js → chunk-A5DYVMDR.js} +3 -3
- package/dist/chunk-AF5ZRTZ4.js +108 -0
- package/dist/chunk-AF5ZRTZ4.js.map +1 -0
- package/dist/{chunk-I3TIKTJO.js → chunk-BKGIWGCX.js} +2 -2
- package/dist/{chunk-IO6GRPT6.js → chunk-BMQOH7MM.js} +2 -2
- package/dist/{chunk-26LGBE4T.js → chunk-CPXPHQGX.js} +6 -4
- package/dist/{chunk-26LGBE4T.js.map → chunk-CPXPHQGX.js.map} +1 -1
- package/dist/{chunk-FISN7WE4.js → chunk-EWR7HL3K.js} +4 -4
- package/dist/{chunk-AQTBSL7R.js → chunk-FH52CDEW.js} +5 -5
- package/dist/chunk-G4MGYGSS.js +130 -0
- package/dist/chunk-G4MGYGSS.js.map +1 -0
- package/dist/{chunk-UV5MFVO3.js → chunk-G7RDYYTE.js} +2 -2
- package/dist/{chunk-UIU2THRG.js → chunk-GBTUANXF.js} +16 -297
- package/dist/chunk-GBTUANXF.js.map +1 -0
- package/dist/{chunk-TEILUWOI.js → chunk-GOUNIHFA.js} +10 -10
- package/dist/{chunk-LXQT4WMP.js → chunk-GQOT6QJR.js} +8 -6
- package/dist/{chunk-LXQT4WMP.js.map → chunk-GQOT6QJR.js.map} +1 -1
- package/dist/{chunk-RUEKROOS.js → chunk-GXGK22QU.js} +2 -2
- package/dist/{chunk-IELYAOGG.js → chunk-GZZL5R73.js} +4 -4
- package/dist/{chunk-JNUWZ6D3.js → chunk-H5XRIJX3.js} +7 -5
- package/dist/{chunk-JNUWZ6D3.js.map → chunk-H5XRIJX3.js.map} +1 -1
- package/dist/{chunk-CWPPNPOH.js → chunk-H7RYPVMJ.js} +2 -2
- package/dist/{chunk-5N6CZTCY.js → chunk-HBKDR7R3.js} +3 -3
- package/dist/{chunk-KXGNJJXB.js → chunk-HLUKZ36Q.js} +9 -9
- package/dist/{chunk-LMTH2RM6.js → chunk-HMXLN43G.js} +2 -2
- package/dist/{chunk-BG3SPSR4.js → chunk-HY6ZGGEC.js} +2 -2
- package/dist/{chunk-UPJNJGGA.js → chunk-K5P46KB3.js} +27 -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-62QYRORB.js → chunk-LIP6JWYK.js} +3 -3
- package/dist/{chunk-5TDJ56JE.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-IGUYVGGI.js → chunk-LPRPVCNY.js} +5 -5
- package/dist/{chunk-AIWULCUO.js → chunk-M66NAZA4.js} +5 -3
- package/dist/{chunk-AIWULCUO.js.map → chunk-M66NAZA4.js.map} +1 -1
- package/dist/{chunk-KVOXU4T5.js → chunk-N5YVZHCB.js} +2 -2
- package/dist/{chunk-76722EBH.js → chunk-NB47TXGP.js} +14 -12
- package/dist/{chunk-76722EBH.js.map → chunk-NB47TXGP.js.map} +1 -1
- package/dist/chunk-NO272T7Q.js +194 -0
- package/dist/chunk-NO272T7Q.js.map +1 -0
- package/dist/{chunk-SRB2XEWB.js → chunk-O2DB4C3M.js} +8 -6
- package/dist/{chunk-SRB2XEWB.js.map → chunk-O2DB4C3M.js.map} +1 -1
- package/dist/{chunk-IUEN57TV.js → chunk-OFBFAVLI.js} +6 -6
- package/dist/{chunk-MTMJVJ5W.js → chunk-OPTFANOX.js} +5 -3
- package/dist/chunk-OPTFANOX.js.map +1 -0
- package/dist/{chunk-5O3AOPDT.js → chunk-OVO2RZAE.js} +212 -439
- package/dist/chunk-OVO2RZAE.js.map +1 -0
- package/dist/{chunk-GYGWYIOZ.js → chunk-P27Z66EB.js} +7 -5
- package/dist/{chunk-GYGWYIOZ.js.map → chunk-P27Z66EB.js.map} +1 -1
- package/dist/{chunk-HCIPRAGS.js → chunk-P2LDXRGP.js} +2 -2
- package/dist/chunk-P3NGV5RV.js +41 -0
- package/dist/chunk-P3NGV5RV.js.map +1 -0
- package/dist/{chunk-5R3ERCDH.js → chunk-P3V7JTB6.js} +25 -9
- package/dist/{chunk-5R3ERCDH.js.map → chunk-P3V7JTB6.js.map} +1 -1
- package/dist/{chunk-LV7M27WM.js → chunk-P5WXDYMD.js} +8 -197
- package/dist/chunk-P5WXDYMD.js.map +1 -0
- package/dist/chunk-PGFY7IRW.js +95 -0
- package/dist/chunk-PGFY7IRW.js.map +1 -0
- package/dist/chunk-QKVILR7N.js +25 -0
- package/dist/chunk-QKVILR7N.js.map +1 -0
- package/dist/{chunk-V7RWQRG7.js → chunk-QNTEDPWP.js} +3 -3
- package/dist/{chunk-Q7SS3IME.js → chunk-RDHAGBEB.js} +3 -3
- package/dist/{chunk-EIZUR2XW.js → chunk-RTS23VIJ.js} +2 -2
- package/dist/{chunk-LFLXAY2W.js → chunk-SEQ2JI3Y.js} +9 -7
- package/dist/{chunk-LFLXAY2W.js.map → chunk-SEQ2JI3Y.js.map} +1 -1
- package/dist/{chunk-AZAOEIKW.js → chunk-SW56GSYC.js} +2 -2
- package/dist/{chunk-TA66UMI4.js → chunk-T2EGAXPM.js} +2 -2
- package/dist/{chunk-SM3AVUHC.js → chunk-T45LAT2Y.js} +2 -2
- package/dist/{chunk-SKF73JOP.js → chunk-T65UZXR6.js} +3 -3
- package/dist/{chunk-ITNUCOXM.js → chunk-TB5RNNJ4.js} +7 -5
- package/dist/{chunk-ITNUCOXM.js.map → chunk-TB5RNNJ4.js.map} +1 -1
- 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-TLJOJBZD.js +404 -0
- 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-DGDI2D4M.js → chunk-TVRQ3RYH.js} +28 -7
- package/dist/chunk-TVRQ3RYH.js.map +1 -0
- package/dist/{chunk-PSMV73A5.js → chunk-TYGW5TOR.js} +7 -7
- package/dist/{chunk-ZYUC53LT.js → chunk-U23JUUPX.js} +58 -2
- package/dist/{chunk-ZYUC53LT.js.map → chunk-U23JUUPX.js.map} +1 -1
- package/dist/{chunk-3YFXJ3ZP.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-WWGT2MDV.js → chunk-VAWY44JW.js} +8 -8
- package/dist/{chunk-WWGT2MDV.js.map → chunk-VAWY44JW.js.map} +1 -1
- package/dist/chunk-VFRNWE5P.js +239 -0
- package/dist/chunk-VFRNWE5P.js.map +1 -0
- package/dist/{chunk-IPB7FTQX.js → chunk-VMJ5URU7.js} +10 -8
- package/dist/chunk-VMJ5URU7.js.map +1 -0
- package/dist/{chunk-VFQGRQIH.js → chunk-VPCMJ5Z4.js} +7 -5
- package/dist/{chunk-VFQGRQIH.js.map → chunk-VPCMJ5Z4.js.map} +1 -1
- package/dist/{chunk-VCTFO5CO.js → chunk-VRPBEOWU.js} +2 -2
- package/dist/{chunk-ZCGAHGUS.js → chunk-VWGCJUTV.js} +2 -2
- package/dist/{chunk-XXYIOFUB.js → chunk-XJR3COJO.js} +5 -5
- package/dist/{chunk-PKHL44ER.js → chunk-XYYW64LB.js} +2 -2
- package/dist/{chunk-IAGBDSCS.js → chunk-YFF2RP3X.js} +2 -2
- package/dist/{chunk-MD3Y6J3L.js → chunk-Z3FZC5GV.js} +7 -5
- package/dist/{chunk-MD3Y6J3L.js.map → chunk-Z3FZC5GV.js.map} +1 -1
- package/dist/{chunk-DFEMTNPJ.js → chunk-Z5PIUYNB.js} +10 -8
- package/dist/{chunk-DFEMTNPJ.js.map → chunk-Z5PIUYNB.js.map} +1 -1
- package/dist/{chunk-WYSO2NIH.js → chunk-Z7KI4WHR.js} +2 -2
- package/dist/chunk-ZKF6HH7V.js +120 -0
- package/dist/chunk-ZKF6HH7V.js.map +1 -0
- package/dist/{chunk-I2NOIW44.js → chunk-ZKYMKF2S.js} +8 -6
- package/dist/{chunk-I2NOIW44.js.map → chunk-ZKYMKF2S.js.map} +1 -1
- package/dist/{chunk-PSHOXR6C.js → chunk-ZPHDGUZZ.js} +737 -1082
- package/dist/chunk-ZPHDGUZZ.js.map +1 -0
- package/dist/{chunk-CMGR4ZEW.js → chunk-ZROMNP7Q.js} +2 -2
- package/dist/classified/index.d.ts +17 -0
- package/dist/classified/index.js +38 -0
- 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 -4
- 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 -6
- package/dist/describe/index.d.ts +1 -1
- package/dist/{describe-Ccd1hS7a.d.ts → describe-C6iHNlqJ.d.ts} +19 -0
- package/dist/{dev-unlock-h0K-UZTk.d.ts → dev-unlock-Cqd5Xv5J.d.ts} +1 -1
- package/dist/{enclave-UL5LW66V.js → enclave-YN6223OG.js} +47 -18
- 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-GF3DJ6KR.js → fanout-sidecar-WS22Q5WH.js} +17 -14
- package/dist/fanout-sidecar-WS22Q5WH.js.map +1 -0
- 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 -4
- 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-CdSr06sm.d.ts → hash-D8Q5MJLA.d.ts} +7 -2
- package/dist/history/index.d.ts +4 -4
- package/dist/history/index.js +12 -6
- package/dist/history/index.js.map +1 -1
- package/dist/i18n/index.d.ts +3 -3
- package/dist/i18n/index.js +14 -8
- package/dist/i18n/index.js.map +1 -1
- package/dist/in/index.d.ts +2 -2
- package/dist/{index-_6At2_9_.d.ts → index-D4GQe1Rx.d.ts} +1058 -49
- package/dist/{index-CGLLRtFc.d.ts → index-DRxk8q_7.d.ts} +3 -3
- package/dist/index.d.ts +60 -15
- package/dist/index.js +1081 -121
- 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 -7
- package/dist/lazy/index.d.ts +21 -0
- package/dist/lazy/index.js +12 -0
- package/dist/{ledger-RYI35CDS.js → ledger-KZWBKAG5.js} +12 -6
- 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 -10
- package/dist/{mime-magic-B4RoFj3B.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 -4
- 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 -7
- package/dist/pod/index.d.ts +3 -3
- package/dist/pod/index.js +12 -6
- package/dist/{policy-IXK4FVXP.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-JHDQCPSX.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/registry-ZVO3T4M5.js.map +1 -0
- package/dist/request-withdrawal-EHALV66Y.js +31 -0
- package/dist/request-withdrawal-EHALV66Y.js.map +1 -0
- package/dist/reveal-TBLTFWQ2.js +38 -0
- package/dist/reveal-TBLTFWQ2.js.map +1 -0
- 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 -7
- package/dist/sealed-record/index.js.map +1 -1
- package/dist/session/index.d.ts +4 -4
- package/dist/session/index.js +10 -4
- 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 -5
- package/dist/snapshots/index.js.map +1 -1
- package/dist/{stale-3JWHNDIY.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-3I7XWZZK.js → store-coordination-provider-JJCEMTAE.js} +3 -3
- package/dist/sync/index.d.ts +2 -2
- package/dist/sync/index.js +10 -4
- package/dist/sync/index.js.map +1 -1
- package/dist/team/index.d.ts +18 -4
- package/dist/team/index.js +24 -11
- package/dist/tiers/index.d.ts +2 -2
- package/dist/tiers/index.js +11 -5
- package/dist/to/index.d.ts +2 -2
- package/dist/to/index.js +1 -1
- package/dist/{transition-guard-DqodPNKw.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-BtpiBvic.d.ts → vault-diff-B1Ir6EGs.d.ts} +1 -1
- package/dist/verify-YB5LQHNA.js +139 -0
- package/dist/verify-YB5LQHNA.js.map +1 -0
- 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 -6
- package/dist/{with-materialized-view-DMmWtYfJ.d.ts → with-materialized-view-Bp65kKdQ.d.ts} +1 -1
- package/dist/{with-overlayed-view-D_IB2nkM.d.ts → with-overlayed-view-BRhdQNrK.d.ts} +1 -1
- package/dist/{with-rollup-em2wxoHP.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 +11 -3
- package/dist/api-RW3KP7WU.js +0 -14
- package/dist/chunk-5O3AOPDT.js.map +0 -1
- package/dist/chunk-5TDJ56JE.js.map +0 -1
- package/dist/chunk-DGDI2D4M.js.map +0 -1
- package/dist/chunk-IPB7FTQX.js.map +0 -1
- package/dist/chunk-LV7M27WM.js.map +0 -1
- package/dist/chunk-M2YKN37S.js +0 -524
- package/dist/chunk-M2YKN37S.js.map +0 -1
- package/dist/chunk-MTMJVJ5W.js.map +0 -1
- package/dist/chunk-PSHOXR6C.js.map +0 -1
- package/dist/chunk-TJYQIGAP.js.map +0 -1
- package/dist/chunk-UIU2THRG.js.map +0 -1
- package/dist/chunk-UPJNJGGA.js.map +0 -1
- package/dist/collection-facade-GQAOGJP2.js +0 -33
- package/dist/delegation-UL5HKLER.js +0 -19
- package/dist/executor-2FWQRLMG.js +0 -15
- package/dist/executor-QZ7BXICW.js +0 -9
- package/dist/executor-Z5ZLJVTV.js +0 -9
- package/dist/export-accessible-KRV5W4GH.js +0 -17
- package/dist/extract-partition-GLKBOXFQ.js +0 -30
- package/dist/fanout-sidecar-GF3DJ6KR.js.map +0 -1
- package/dist/issue-Y6UVIR43.js +0 -13
- package/dist/liberate-CRPZEV7O.js +0 -18
- package/dist/noydb-LDEBLNHY.js +0 -50
- package/dist/registry-45RJNWGU.js +0 -9
- package/dist/registry-5GRV5VXI.js +0 -13
- package/dist/registry-WEUCHBO4.js +0 -11
- package/dist/request-withdrawal-GBPH2FDY.js +0 -25
- package/dist/revoke-TUFRGI6S.js +0 -18
- package/dist/signer-PNKTBVF7.js +0 -19
- package/dist/withdraw-accessible-FFS46EOD.js +0 -22
- /package/dist/{api-RW3KP7WU.js.map → api-JOXUNSKP.js.map} +0 -0
- /package/dist/{chunk-SMXWYP24.js.map → chunk-3QRQOBS7.js.map} +0 -0
- /package/dist/{chunk-QHJW5EXU.js.map → chunk-3Y4QLJ6K.js.map} +0 -0
- /package/dist/{chunk-NF5JWERG.js.map → chunk-43ISXURO.js.map} +0 -0
- /package/dist/{chunk-5LUY7UTV.js.map → chunk-4K3MQ5S3.js.map} +0 -0
- /package/dist/{chunk-Y22T3KQE.js.map → chunk-5MRE3C4Q.js.map} +0 -0
- /package/dist/{chunk-M6OST55S.js.map → chunk-5W7AOFWA.js.map} +0 -0
- /package/dist/{chunk-AXRXJTE2.js.map → chunk-6HNKCKFZ.js.map} +0 -0
- /package/dist/{chunk-UAG5KWVA.js.map → chunk-6RXPSMKR.js.map} +0 -0
- /package/dist/{chunk-UDRIWL7A.js.map → chunk-A5DYVMDR.js.map} +0 -0
- /package/dist/{chunk-I3TIKTJO.js.map → chunk-BKGIWGCX.js.map} +0 -0
- /package/dist/{chunk-IO6GRPT6.js.map → chunk-BMQOH7MM.js.map} +0 -0
- /package/dist/{chunk-FISN7WE4.js.map → chunk-EWR7HL3K.js.map} +0 -0
- /package/dist/{chunk-AQTBSL7R.js.map → chunk-FH52CDEW.js.map} +0 -0
- /package/dist/{chunk-UV5MFVO3.js.map → chunk-G7RDYYTE.js.map} +0 -0
- /package/dist/{chunk-TEILUWOI.js.map → chunk-GOUNIHFA.js.map} +0 -0
- /package/dist/{chunk-RUEKROOS.js.map → chunk-GXGK22QU.js.map} +0 -0
- /package/dist/{chunk-IELYAOGG.js.map → chunk-GZZL5R73.js.map} +0 -0
- /package/dist/{chunk-CWPPNPOH.js.map → chunk-H7RYPVMJ.js.map} +0 -0
- /package/dist/{chunk-5N6CZTCY.js.map → chunk-HBKDR7R3.js.map} +0 -0
- /package/dist/{chunk-KXGNJJXB.js.map → chunk-HLUKZ36Q.js.map} +0 -0
- /package/dist/{chunk-LMTH2RM6.js.map → chunk-HMXLN43G.js.map} +0 -0
- /package/dist/{chunk-BG3SPSR4.js.map → chunk-HY6ZGGEC.js.map} +0 -0
- /package/dist/{chunk-62QYRORB.js.map → chunk-LIP6JWYK.js.map} +0 -0
- /package/dist/{chunk-IGUYVGGI.js.map → chunk-LPRPVCNY.js.map} +0 -0
- /package/dist/{chunk-KVOXU4T5.js.map → chunk-N5YVZHCB.js.map} +0 -0
- /package/dist/{chunk-IUEN57TV.js.map → chunk-OFBFAVLI.js.map} +0 -0
- /package/dist/{chunk-HCIPRAGS.js.map → chunk-P2LDXRGP.js.map} +0 -0
- /package/dist/{chunk-V7RWQRG7.js.map → chunk-QNTEDPWP.js.map} +0 -0
- /package/dist/{chunk-Q7SS3IME.js.map → chunk-RDHAGBEB.js.map} +0 -0
- /package/dist/{chunk-EIZUR2XW.js.map → chunk-RTS23VIJ.js.map} +0 -0
- /package/dist/{chunk-AZAOEIKW.js.map → chunk-SW56GSYC.js.map} +0 -0
- /package/dist/{chunk-TA66UMI4.js.map → chunk-T2EGAXPM.js.map} +0 -0
- /package/dist/{chunk-SM3AVUHC.js.map → chunk-T45LAT2Y.js.map} +0 -0
- /package/dist/{chunk-SKF73JOP.js.map → chunk-T65UZXR6.js.map} +0 -0
- /package/dist/{chunk-PSMV73A5.js.map → chunk-TYGW5TOR.js.map} +0 -0
- /package/dist/{chunk-3YFXJ3ZP.js.map → chunk-U24XHXNK.js.map} +0 -0
- /package/dist/{chunk-VCTFO5CO.js.map → chunk-VRPBEOWU.js.map} +0 -0
- /package/dist/{chunk-ZCGAHGUS.js.map → chunk-VWGCJUTV.js.map} +0 -0
- /package/dist/{chunk-XXYIOFUB.js.map → chunk-XJR3COJO.js.map} +0 -0
- /package/dist/{chunk-PKHL44ER.js.map → chunk-XYYW64LB.js.map} +0 -0
- /package/dist/{chunk-IAGBDSCS.js.map → chunk-YFF2RP3X.js.map} +0 -0
- /package/dist/{chunk-WYSO2NIH.js.map → chunk-Z7KI4WHR.js.map} +0 -0
- /package/dist/{chunk-CMGR4ZEW.js.map → chunk-ZROMNP7Q.js.map} +0 -0
- /package/dist/{collection-facade-GQAOGJP2.js.map → classified/index.js.map} +0 -0
- /package/dist/{delegation-UL5HKLER.js.map → collection-facade-MKEBZDWW.js.map} +0 -0
- /package/dist/{enclave-UL5LW66V.js.map → computed-BMMEFRFJ.js.map} +0 -0
- /package/dist/{executor-2FWQRLMG.js.map → delegation-BQNIEHZE.js.map} +0 -0
- /package/dist/{executor-QZ7BXICW.js.map → enclave-YN6223OG.js.map} +0 -0
- /package/dist/{executor-Z5ZLJVTV.js.map → executor-CBTNU5VZ.js.map} +0 -0
- /package/dist/{export-accessible-KRV5W4GH.js.map → executor-DJHNSTIR.js.map} +0 -0
- /package/dist/{extract-partition-GLKBOXFQ.js.map → executor-FGISLQIN.js.map} +0 -0
- /package/dist/{issue-Y6UVIR43.js.map → export-accessible-P7SLRLK3.js.map} +0 -0
- /package/dist/{ledger-RYI35CDS.js.map → extract-partition-RNVSFHAB.js.map} +0 -0
- /package/dist/{liberate-CRPZEV7O.js.map → find-RNBG7LBY.js.map} +0 -0
- /package/dist/{noydb-LDEBLNHY.js.map → issue-W5QG53B5.js.map} +0 -0
- /package/dist/{policy-IXK4FVXP.js.map → lazy/index.js.map} +0 -0
- /package/dist/{public-envelope-JHDQCPSX.js.map → ledger-KZWBKAG5.js.map} +0 -0
- /package/dist/{registry-45RJNWGU.js.map → liberate-GMGCHIND.js.map} +0 -0
- /package/dist/{registry-5GRV5VXI.js.map → link-set-UQEIYQAM.js.map} +0 -0
- /package/dist/{registry-WEUCHBO4.js.map → noydb-KS2O2MRI.js.map} +0 -0
- /package/dist/{request-withdrawal-GBPH2FDY.js.map → policy-YIKUH4AD.js.map} +0 -0
- /package/dist/{revoke-TUFRGI6S.js.map → public-envelope-MRN5YYZZ.js.map} +0 -0
- /package/dist/{signer-PNKTBVF7.js.map → register-K6DDSIXN.js.map} +0 -0
- /package/dist/{stale-3JWHNDIY.js.map → registry-IMNMHWTM.js.map} +0 -0
- /package/dist/{withdraw-accessible-FFS46EOD.js.map → registry-K6VUQXKV.js.map} +0 -0
- /package/dist/{store-coordination-provider-3I7XWZZK.js.map → store-coordination-provider-JJCEMTAE.js.map} +0 -0
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
readPath,
|
|
6
|
-
scaleForCurrency
|
|
7
|
-
} from "./chunk-M2YKN37S.js";
|
|
2
|
+
moneyRuntime,
|
|
3
|
+
readPath
|
|
4
|
+
} from "./chunk-ZKF6HH7V.js";
|
|
8
5
|
import {
|
|
9
6
|
applyI18nLocale
|
|
10
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-4K3MQ5S3.js";
|
|
11
8
|
import {
|
|
12
9
|
GroupCardinalityError
|
|
13
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-U23JUUPX.js";
|
|
14
11
|
|
|
15
12
|
// src/with-lookup/aggregate/reducers.ts
|
|
16
13
|
function count(opts) {
|
|
@@ -282,191 +279,6 @@ function canonicalGroupKey(fields, row) {
|
|
|
282
279
|
return parts.join("|");
|
|
283
280
|
}
|
|
284
281
|
|
|
285
|
-
// src/with-shape/money/money-reducer.ts
|
|
286
|
-
function toScaledIntFromAny(v, scale) {
|
|
287
|
-
if (typeof v === "bigint") return v;
|
|
288
|
-
if (typeof v === "number") {
|
|
289
|
-
const r = parseToScaledInt(v, scale);
|
|
290
|
-
return r.ok ? r.value : null;
|
|
291
|
-
}
|
|
292
|
-
if (typeof v === "string") {
|
|
293
|
-
if (!v.includes(".")) {
|
|
294
|
-
try {
|
|
295
|
-
return BigInt(v);
|
|
296
|
-
} catch {
|
|
297
|
-
return null;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
const r = parseToScaledInt(v, scale);
|
|
301
|
-
return r.ok ? r.value : null;
|
|
302
|
-
}
|
|
303
|
-
return null;
|
|
304
|
-
}
|
|
305
|
-
function readMoney(record, field, desc) {
|
|
306
|
-
const raw = readPath(record, field);
|
|
307
|
-
if (raw === null || raw === void 0) return null;
|
|
308
|
-
if (desc.mode === "fixed") {
|
|
309
|
-
const cur = desc.fixedCurrency;
|
|
310
|
-
const value2 = toScaledIntFromAny(raw, desc.scaleFor(cur));
|
|
311
|
-
return value2 === null ? null : { currency: cur, value: value2 };
|
|
312
|
-
}
|
|
313
|
-
if (typeof raw !== "object") return null;
|
|
314
|
-
const o = raw;
|
|
315
|
-
if (typeof o.currency !== "string") return null;
|
|
316
|
-
const scale = desc.allows(o.currency) ? desc.scaleFor(o.currency) : 0;
|
|
317
|
-
const value = toScaledIntFromAny(o.amount, scale);
|
|
318
|
-
return value === null ? null : { currency: o.currency, value };
|
|
319
|
-
}
|
|
320
|
-
function targetScaleFor(desc, currency) {
|
|
321
|
-
if (desc.allows(currency)) return desc.scaleFor(currency);
|
|
322
|
-
const s = scaleForCurrency(currency);
|
|
323
|
-
if (s === null) {
|
|
324
|
-
throw new Error(`money: cannot determine scale for conversion target "${currency}"`);
|
|
325
|
-
}
|
|
326
|
-
return s;
|
|
327
|
-
}
|
|
328
|
-
function parseRate(rate) {
|
|
329
|
-
const s = String(rate).trim();
|
|
330
|
-
const neg = s.startsWith("-");
|
|
331
|
-
const body = neg ? s.slice(1) : s;
|
|
332
|
-
const dot = body.indexOf(".");
|
|
333
|
-
const intPart = dot === -1 ? body : body.slice(0, dot);
|
|
334
|
-
const fracPart = dot === -1 ? "" : body.slice(dot + 1);
|
|
335
|
-
const int = BigInt((intPart === "" ? "0" : intPart) + fracPart);
|
|
336
|
-
return { int: neg ? -int : int, scale: fracPart.length };
|
|
337
|
-
}
|
|
338
|
-
function divRoundHalfEven(n, d) {
|
|
339
|
-
const q = n / d;
|
|
340
|
-
const r = n % d;
|
|
341
|
-
const twiceR = (r < 0n ? -r : r) * 2n;
|
|
342
|
-
if (twiceR < d) return q;
|
|
343
|
-
if (twiceR > d) return q + (n < 0n ? -1n : 1n);
|
|
344
|
-
return q % 2n === 0n ? q : q + (n < 0n ? -1n : 1n);
|
|
345
|
-
}
|
|
346
|
-
function convertScaled(value, srcScale, rate, targetScale) {
|
|
347
|
-
const { int: rateInt, scale: rateScale } = parseRate(rate);
|
|
348
|
-
const product = value * rateInt;
|
|
349
|
-
const curScale = srcScale + rateScale;
|
|
350
|
-
if (curScale === targetScale) return product;
|
|
351
|
-
if (curScale < targetScale) return product * 10n ** BigInt(targetScale - curScale);
|
|
352
|
-
return divRoundHalfEven(product, 10n ** BigInt(curScale - targetScale));
|
|
353
|
-
}
|
|
354
|
-
function finalizeSum(state, desc, convertTo, fx) {
|
|
355
|
-
if (convertTo !== void 0) {
|
|
356
|
-
if (fx === void 0) {
|
|
357
|
-
throw new Error(`money: sum convertTo "${convertTo}" requires an fx rate map`);
|
|
358
|
-
}
|
|
359
|
-
const targetScale = targetScaleFor(desc, convertTo);
|
|
360
|
-
let total = 0n;
|
|
361
|
-
for (const [cur, v] of state) {
|
|
362
|
-
if (cur === convertTo) {
|
|
363
|
-
total += convertScaled(v, desc.scaleFor(cur), 1, targetScale);
|
|
364
|
-
continue;
|
|
365
|
-
}
|
|
366
|
-
const rate = fx[`${cur}->${convertTo}`];
|
|
367
|
-
if (rate === void 0) {
|
|
368
|
-
throw new Error(`money: no fx rate for "${cur}->${convertTo}"`);
|
|
369
|
-
}
|
|
370
|
-
total += convertScaled(v, desc.scaleFor(cur), rate, targetScale);
|
|
371
|
-
}
|
|
372
|
-
return formatScaledInt(total, targetScale);
|
|
373
|
-
}
|
|
374
|
-
if (desc.mode === "fixed") {
|
|
375
|
-
const cur = desc.fixedCurrency;
|
|
376
|
-
return formatScaledInt(state.get(cur) ?? 0n, desc.scaleFor(cur));
|
|
377
|
-
}
|
|
378
|
-
const out = {};
|
|
379
|
-
for (const [cur, v] of state) out[cur] = formatScaledInt(v, desc.scaleFor(cur));
|
|
380
|
-
return out;
|
|
381
|
-
}
|
|
382
|
-
function moneySumReducer(field, desc, convertTo, fx) {
|
|
383
|
-
return {
|
|
384
|
-
op: "sum",
|
|
385
|
-
field,
|
|
386
|
-
init: () => /* @__PURE__ */ new Map(),
|
|
387
|
-
step: (state, record) => {
|
|
388
|
-
const m = readMoney(record, field, desc);
|
|
389
|
-
if (m) state.set(m.currency, (state.get(m.currency) ?? 0n) + m.value);
|
|
390
|
-
return state;
|
|
391
|
-
},
|
|
392
|
-
remove: (state, record) => {
|
|
393
|
-
const m = readMoney(record, field, desc);
|
|
394
|
-
if (m) state.set(m.currency, (state.get(m.currency) ?? 0n) - m.value);
|
|
395
|
-
return state;
|
|
396
|
-
},
|
|
397
|
-
finalize: (state) => finalizeSum(state, desc, convertTo, fx)
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
function extremum(values, op) {
|
|
401
|
-
let out = values[0];
|
|
402
|
-
for (let i = 1; i < values.length; i++) {
|
|
403
|
-
const v = values[i];
|
|
404
|
-
if (op === "min" ? v < out : v > out) out = v;
|
|
405
|
-
}
|
|
406
|
-
return out;
|
|
407
|
-
}
|
|
408
|
-
function moneyMinMaxReducer(op, field, desc) {
|
|
409
|
-
return {
|
|
410
|
-
op,
|
|
411
|
-
field,
|
|
412
|
-
init: () => /* @__PURE__ */ new Map(),
|
|
413
|
-
step: (state, record) => {
|
|
414
|
-
const m = readMoney(record, field, desc);
|
|
415
|
-
if (m) {
|
|
416
|
-
const arr = state.get(m.currency);
|
|
417
|
-
if (arr) arr.push(m.value);
|
|
418
|
-
else state.set(m.currency, [m.value]);
|
|
419
|
-
}
|
|
420
|
-
return state;
|
|
421
|
-
},
|
|
422
|
-
remove: (state, record) => {
|
|
423
|
-
const m = readMoney(record, field, desc);
|
|
424
|
-
if (m) {
|
|
425
|
-
const arr = state.get(m.currency);
|
|
426
|
-
if (arr) {
|
|
427
|
-
const idx = arr.indexOf(m.value);
|
|
428
|
-
if (idx >= 0) arr.splice(idx, 1);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
return state;
|
|
432
|
-
},
|
|
433
|
-
finalize: (state) => {
|
|
434
|
-
if (desc.mode === "fixed") {
|
|
435
|
-
const cur = desc.fixedCurrency;
|
|
436
|
-
const arr = state.get(cur);
|
|
437
|
-
if (!arr || arr.length === 0) return null;
|
|
438
|
-
return formatScaledInt(extremum(arr, op), desc.scaleFor(cur));
|
|
439
|
-
}
|
|
440
|
-
const out = {};
|
|
441
|
-
for (const [cur, arr] of state) {
|
|
442
|
-
if (arr.length > 0) out[cur] = formatScaledInt(extremum(arr, op), desc.scaleFor(cur));
|
|
443
|
-
}
|
|
444
|
-
return out;
|
|
445
|
-
}
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
function wrapMoneyReducers(spec, moneyFields) {
|
|
449
|
-
let changed = false;
|
|
450
|
-
const out = {};
|
|
451
|
-
for (const [key, reducer] of Object.entries(spec)) {
|
|
452
|
-
const field = reducer.field;
|
|
453
|
-
const desc = field ? moneyFields[field] : void 0;
|
|
454
|
-
if (desc && reducer.op === "avg") {
|
|
455
|
-
throw new MoneyUnsupportedError(
|
|
456
|
-
field,
|
|
457
|
-
`avg() is not supported on money field "${field}" in v1 \u2014 use sum() and count() and divide at the boundary.`
|
|
458
|
-
);
|
|
459
|
-
}
|
|
460
|
-
if (desc && (reducer.op === "sum" || reducer.op === "min" || reducer.op === "max")) {
|
|
461
|
-
changed = true;
|
|
462
|
-
out[key] = reducer.op === "sum" ? moneySumReducer(field, desc, reducer.convertTo, reducer.fx) : moneyMinMaxReducer(reducer.op, field, desc);
|
|
463
|
-
} else {
|
|
464
|
-
out[key] = reducer;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
return changed ? out : spec;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
282
|
// src/with-lookup/aggregate/groupby.ts
|
|
471
283
|
var GROUPBY_WARN_CARDINALITY = 1e4;
|
|
472
284
|
var GROUPBY_MAX_CARDINALITY = 1e5;
|
|
@@ -504,7 +316,7 @@ var GroupedQueryBase = class {
|
|
|
504
316
|
fields;
|
|
505
317
|
/** Apply money-aware reducer rewriting when money fields are declared. */
|
|
506
318
|
wrapSpec(spec) {
|
|
507
|
-
return this.moneyFields ? wrapMoneyReducers(spec, this.moneyFields) : spec;
|
|
319
|
+
return this.moneyFields ? moneyRuntime().wrapMoneyReducers(spec, this.moneyFields) : spec;
|
|
508
320
|
}
|
|
509
321
|
};
|
|
510
322
|
var GroupedQuery = class extends GroupedQueryBase {
|
|
@@ -537,7 +349,7 @@ function groupAndReduce(records, fieldOrFields, spec, moneyFields) {
|
|
|
537
349
|
throw new Error(".groupBy() requires at least one field");
|
|
538
350
|
}
|
|
539
351
|
if (moneyFields) {
|
|
540
|
-
spec = wrapMoneyReducers(spec, moneyFields);
|
|
352
|
+
spec = moneyRuntime().wrapMoneyReducers(spec, moneyFields);
|
|
541
353
|
}
|
|
542
354
|
const buckets = /* @__PURE__ */ new Map();
|
|
543
355
|
const fieldLabel = fields.length === 1 ? fields[0] : `[${fields.join(", ")}]`;
|
|
@@ -686,7 +498,6 @@ export {
|
|
|
686
498
|
moneyMin,
|
|
687
499
|
moneyMax,
|
|
688
500
|
reducerBuilder,
|
|
689
|
-
wrapMoneyReducers,
|
|
690
501
|
reduceRecords,
|
|
691
502
|
Aggregation,
|
|
692
503
|
buildLiveAggregation,
|
|
@@ -699,4 +510,4 @@ export {
|
|
|
699
510
|
groupAndReduce,
|
|
700
511
|
GroupedAggregation
|
|
701
512
|
};
|
|
702
|
-
//# sourceMappingURL=chunk-
|
|
513
|
+
//# sourceMappingURL=chunk-P5WXDYMD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/with-lookup/aggregate/reducers.ts","../src/with-lookup/aggregate/aggregation.ts","../src/with-lookup/aggregate/canonical-key.ts","../src/with-lookup/aggregate/groupby.ts"],"sourcesContent":["/**\n * Aggregation reducers for the query DSL.\n *\n * the reducer protocol plus five built-in factories\n * (`count`, `sum`, `avg`, `min`, `max`) consumed by `Query.aggregate()`\n * and, in the future, `Scan.aggregate()`. Every factory accepts\n * an optional `{ seed }` parameter that is plumbed through the\n * protocol but unused by the executor — that's the load-bearing\n * half of constraint #2. When partition-aware aggregation\n * lands, the seed carries the previous partition's running total into\n * the next partition without requiring a protocol change.\n *\n * Reducers are intentionally generic over their internal state type\n * `S` so compound reducers (avg keeps `{sum, count}`, min/max keep a\n * value bag) can model internal bookkeeping without leaking the\n * implementation through the accumulator's public shape. `finalize`\n * collapses `S` back into the user-visible `R`.\n *\n * Reducers are pure data — `init` / `step` / `finalize` / optional\n * `remove` are stateless functions that receive and return `S`. This\n * is the shape that admits O(1) incremental maintenance in a future\n * optimization (delta-aware `LiveAggregation` applies `step` or\n * `remove` per delta), without blocking the simpler \"full re-run on\n * source change\" that ships.\n */\n\nimport { readPath } from '../../kernel/query/predicate.js'\nimport type { MoneyString } from '../../with-shape/money/branded.js'\nimport type { QueryField } from '../../kernel/types.js'\n\n/**\n * A single reducer: factory-produced, ready to plug into an\n * `.aggregate()` spec.\n *\n * Type parameters:\n * - `R` — user-visible result type (what the aggregation returns\n * for this slot, e.g. `number` for `sum()`)\n * - `S` — internal state type, defaults to `R` for simple reducers\n * that don't need compound bookkeeping\n *\n * A reducer is stateless: every method is pure over `S`. `init()` is\n * called once per aggregation run to build the initial state; `step()`\n * folds a record into the state; `remove()` (optional) un-folds a\n * record, enabling incremental live maintenance; `finalize()` reads\n * the final answer out of the state at the end of the run.\n */\nexport interface Reducer<R, S = R> {\n /** Build the initial state for a fresh aggregation run. */\n init(): S\n /** Fold a record into the state. Returns the new state. */\n step(state: S, record: unknown): S\n /**\n * Un-fold a record from the state. Returns the new state.\n *\n * Optional — reducers without `remove` cannot be maintained\n * incrementally and must be re-run from scratch when the underlying\n * record set changes. `sum`, `count`, `avg` implement `remove` in\n * O(1); `min` and `max` implement it in O(N) worst case (when the\n * extremum itself is removed and the next extremum must be\n * recomputed from the remaining contributing values).\n */\n remove?(state: S, record: unknown): S\n /** Collapse the internal state into the user-visible result. */\n finalize(state: S): R\n /**\n * Combine two independent partial states into one (then `finalize` once).\n * Optional. MUST be associative + commutative with `init()` as identity.\n * Never merge finalized results — only states. Enables parallel /\n * hierarchical aggregation (e.g. cross-shard or advisor→firm rollup).\n */\n merge?(a: S, b: S): S\n /**\n * Identifying operation tag stamped by each built-in factory.\n * Used by `summariseAggregateOp` in the introspection walker to\n * render human-readable aggregate descriptors in `dumpSchema()`.\n * Optional so third-party custom reducers are unaffected.\n */\n readonly op?: 'count' | 'sum' | 'avg' | 'min' | 'max'\n /**\n * Field name for field-based reducers (`sum`, `avg`, `min`, `max`).\n * Absent on `count` which aggregates over record count, not a field.\n */\n readonly field?: string\n /**\n * Money-only: target currency for `sum` over a multi-currency money\n * field. Consumed by `wrapMoneyReducers` to convert per-currency\n * subtotals to one figure. Ignored for non-money fields.\n */\n readonly convertTo?: string\n /**\n * Money-only: FX rate map (`'USD->EUR' → rate`) used with `convertTo`.\n */\n readonly fx?: Record<string, number | string>\n}\n\n/**\n * Common options accepted by every reducer factory.\n *\n * `seed` — optional initial value for the internal state. **Unused by\n * the executor**, plumbed through the protocol for constraint\n * #2 (partition-aware aggregation seam). In, partitioned\n * aggregations will pass the previous partition's carry as `seed` so\n * a long time series can be rolled forward one partition at a time\n * without re-aggregating closed partitions.\n *\n * always uses `init()` with the factory's zero value, regardless\n * of whether `seed` was passed. Do not remove the parameter — that's\n * the whole point of having it exist now.\n */\nexport interface ReducerOptions<TSeed = unknown> {\n /** constraint #2 — seed is plumbed through but unused in. */\n readonly seed?: TSeed\n /**\n * Money-only (honored by `sum` over a multi-currency money field):\n * convert per-currency subtotals to this currency for a single figure.\n */\n readonly convertTo?: string\n /** Money-only: FX rate map (`'USD->EUR' → rate`) used with `convertTo`. */\n readonly fx?: Record<string, number | string>\n}\n\n// ---------------------------------------------------------------------------\n// Factories\n// ---------------------------------------------------------------------------\n\n/**\n * Count the number of records that match the query. Ignores field\n * values entirely — the count is over the number of records, not over\n * the number of non-null field values in any column.\n */\nexport function count(opts?: ReducerOptions<number>): Reducer<number> {\n // Seed captured on the closure but unused at execution time in\n //. The reference in _seed keeps lint happy.\n const _seed = opts?.seed\n void _seed\n return {\n op: 'count',\n init: () => 0,\n step: (state) => state + 1,\n remove: (state) => state - 1,\n finalize: (state) => state,\n merge: (a, b) => a + b,\n }\n}\n\n/**\n * Sum a numeric field across all matching records. Non-number values\n * at the field path are coerced to 0 — consumers who want a different\n * behavior (throw, skip, treat as NaN) should filter upstream via\n * `.where()` or write a custom reducer.\n *\n * KNOWN LIMITATION (type imprecision): the declared result type is `number`,\n * but when `field` is a **money** field the runtime returns a decimal *string*\n * (e.g. `'0.30'`, or `{ EUR: '0.30' }` in multi-currency mode) — money sums are\n * BigInt-exact in scaled space and never collapse to a float. The `number` type\n * is therefore a lie for money fields; narrow the result yourself at the call\n * site. A precise fix requires threading money-field declarations into the type\n * system (tracked separately).\n */\nexport function sum(\n field: string,\n opts?: ReducerOptions<number>,\n): Reducer<number> {\n const _seed = opts?.seed\n void _seed\n return {\n op: 'sum',\n field,\n // Money-only metadata, read by `wrapMoneyReducers`. No effect on a\n // generic numeric sum.\n ...(opts?.convertTo !== undefined ? { convertTo: opts.convertTo } : {}),\n ...(opts?.fx !== undefined ? { fx: opts.fx } : {}),\n init: () => 0,\n step: (state, record) => state + readNumber(record, field),\n remove: (state, record) => state - readNumber(record, field),\n finalize: (state) => state,\n merge: (a, b) => a + b,\n }\n}\n\n/**\n * Arithmetic mean of a numeric field across all matching records.\n *\n * Returns `null` for an empty result set (zero records is not a\n * well-defined denominator — returning NaN would poison downstream\n * arithmetic, and throwing would force every consumer to wrap in\n * try/catch just to handle \"no matches\"). Consumers who want an\n * explicit zero should coalesce with `?? 0`.\n *\n * Internal state is `{sum, count}` so the running average can be\n * maintained incrementally — on each delta, both fields update in\n * O(1) and `finalize` divides. Directly storing `avg` as state would\n * not admit incremental removal without also tracking count.\n */\nexport function avg(\n field: string,\n opts?: ReducerOptions<{ sum: number; count: number }>,\n): Reducer<number | null, { sum: number; count: number }> {\n const _seed = opts?.seed\n void _seed\n return {\n op: 'avg',\n field,\n init: () => ({ sum: 0, count: 0 }),\n step: (state, record) => ({\n sum: state.sum + readNumber(record, field),\n count: state.count + 1,\n }),\n remove: (state, record) => ({\n sum: state.sum - readNumber(record, field),\n count: state.count - 1,\n }),\n finalize: (state) => (state.count === 0 ? null : state.sum / state.count),\n merge: (a, b) => ({ sum: a.sum + b.sum, count: a.count + b.count }),\n }\n}\n\ninterface MinMaxState {\n /**\n * Multiset of contributing field values. Stored as a plain array\n * because we need to support `remove` and a plain array gives us\n * O(1) push + O(N) worst-case removal — which matches the\n * documented min/max removal complexity. A sorted structure would\n * let us drop the O(N) rescan but adds complexity that doesn't\n * need; consumers hitting the O(N) ceiling should file an issue.\n */\n readonly values: number[]\n}\n\nfunction pushValue(state: MinMaxState, value: number): MinMaxState {\n return { values: [...state.values, value] }\n}\n\nfunction removeValue(state: MinMaxState, value: number): MinMaxState {\n // Remove the first matching value — duplicates are fine, we only\n // need to drop one instance per `remove()` call so the multiset\n // count stays consistent with the record count.\n const idx = state.values.indexOf(value)\n if (idx < 0) return state\n const next = state.values.slice()\n next.splice(idx, 1)\n return { values: next }\n}\n\n/**\n * Smallest numeric value of a field across all matching records.\n * Returns `null` for an empty result set. See `avg()` for the\n * reasoning on `null` vs NaN vs throwing.\n *\n * Incremental complexity: O(1) for `step`, O(N) worst case for\n * `remove` when the current minimum is removed (the state holds the\n * full multiset of contributing values and `finalize` scans for the\n * new minimum). Consumers with very large result sets and frequent\n * removals of the current extremum should either accept the cost or\n * wait for a future optimization.\n */\nexport function min(\n field: string,\n opts?: ReducerOptions<number>,\n): Reducer<number | null, MinMaxState> {\n const _seed = opts?.seed\n void _seed\n return {\n op: 'min',\n field,\n init: () => ({ values: [] }),\n step: (state, record) => pushValue(state, readNumber(record, field)),\n remove: (state, record) => removeValue(state, readNumber(record, field)),\n finalize: (state) => {\n if (state.values.length === 0) return null\n let out = state.values[0]!\n for (let i = 1; i < state.values.length; i++) {\n const v = state.values[i]!\n if (v < out) out = v\n }\n return out\n },\n merge: (a, b) => ({ values: [...a.values, ...b.values] }),\n }\n}\n\n/**\n * Largest numeric value of a field across all matching records.\n * Mirror of `min()` — see that doc for semantics, null-on-empty\n * behavior, and the O(N) removal caveat.\n */\nexport function max(\n field: string,\n opts?: ReducerOptions<number>,\n): Reducer<number | null, MinMaxState> {\n const _seed = opts?.seed\n void _seed\n return {\n op: 'max',\n field,\n init: () => ({ values: [] }),\n step: (state, record) => pushValue(state, readNumber(record, field)),\n remove: (state, record) => removeValue(state, readNumber(record, field)),\n finalize: (state) => {\n if (state.values.length === 0) return null\n let out = state.values[0]!\n for (let i = 1; i < state.values.length; i++) {\n const v = state.values[i]!\n if (v > out) out = v\n }\n return out\n },\n merge: (a, b) => ({ values: [...a.values, ...b.values] }),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Money-typed reducer constructors\n// ---------------------------------------------------------------------------\n\n/**\n * `sum()` for a **declared money field**, typed to match the runtime.\n *\n * `sum()` returns `Reducer<number>`, but `wrapMoneyReducers` (applied at\n * `query.aggregate()` time, once `moneyFields` is known) rewrites any\n * `sum`/`min`/`max` over a money field to a money reducer that finalizes to a\n * `MoneyString` decimal — so the `number` type is a lie for money fields and\n * consumers need a cast at every read site. `moneySum` is the same reducer with\n * the correct `Reducer<MoneyString>` return type, so no read-site cast is\n * needed. It is the caller's assertion that `field` is a money field; use plain\n * `sum()` for non-money fields. (For a multi-currency money field WITHOUT\n * `convertTo`, the runtime returns a per-currency `Record<string, MoneyString>`\n * rather than a single `MoneyString` — pass `convertTo` to collapse to one\n * currency, or read the map at the boundary.)\n */\nexport function moneySum(field: string, opts?: ReducerOptions<number>): Reducer<MoneyString> {\n // The constructed reducer is the plain numeric `sum`; `wrapMoneyReducers`\n // swaps in the MoneyString-producing money reducer at aggregate() time.\n return sum(field, opts) as unknown as Reducer<MoneyString>\n}\n\n/**\n * `min()` for a declared money field, typed `Reducer<MoneyString | null>`\n * (null on an empty result set in fixed-currency mode, mirroring `min()`). See\n * {@link moneySum} for the late-binding rewrite. Note: `convertTo`/`fx` on\n * `opts` have no effect on min/max (cross-currency min/max is unsupported — use\n * {@link moneySum} for currency conversion). In multi-currency mode the runtime\n * returns a per-currency map and an empty result is `{}` rather than `null`.\n */\nexport function moneyMin(field: string, opts?: ReducerOptions<number>): Reducer<MoneyString | null> {\n return min(field, opts) as unknown as Reducer<MoneyString | null>\n}\n\n/**\n * `max()` for a declared money field, typed `Reducer<MoneyString | null>`\n * (null on an empty result set in fixed-currency mode, mirroring `max()`). See\n * {@link moneyMin} for the `convertTo`/`fx` and multi-currency caveats.\n */\nexport function moneyMax(field: string, opts?: ReducerOptions<number>): Reducer<MoneyString | null> {\n return max(field, opts) as unknown as Reducer<MoneyString | null>\n}\n\n// ---------------------------------------------------------------------------\n// Builder (typed spec-builder for aggregate())\n// ---------------------------------------------------------------------------\n\n/**\n * Typed builder passed to the `aggregate(b => spec)` overload.\n *\n * Each field-taking method narrows `field` to `QueryField<T, S>`, which\n * excludes any field listed in the collection's `sensitive` option at\n * compile time. `count()` carries no field argument and is always allowed.\n *\n * The type parameters match the `Query<T, S>` they come from:\n * - `T` — the record type of the collection\n * - `S` — the union of sensitive field keys (defaults to `never`)\n *\n * ONE shared runtime instance (`reducerBuilder`) serves all `T`/`S`\n * combinations — the field narrowing is type-only; the methods delegate\n * directly to the standalone factories.\n */\nexport interface ReducerBuilder<T, S extends keyof T = never, M extends keyof T & string = never> {\n count(opts?: ReducerOptions<number>): Reducer<number>\n sum<F extends QueryField<T, S>>(field: F, opts?: ReducerOptions<number>): [F] extends [M] ? Reducer<MoneyString> : Reducer<number>\n avg(field: QueryField<T, S>, opts?: ReducerOptions<{ sum: number; count: number }>): ReturnType<typeof avg>\n min<F extends QueryField<T, S>>(field: F, opts?: ReducerOptions<number>): [F] extends [M] ? Reducer<MoneyString | null> : ReturnType<typeof min>\n max<F extends QueryField<T, S>>(field: F, opts?: ReducerOptions<number>): [F] extends [M] ? Reducer<MoneyString | null> : ReturnType<typeof max>\n moneySum(field: QueryField<T, S>, opts?: ReducerOptions<number>): Reducer<MoneyString>\n moneyMin(field: QueryField<T, S>, opts?: ReducerOptions<number>): Reducer<MoneyString | null>\n moneyMax(field: QueryField<T, S>, opts?: ReducerOptions<number>): Reducer<MoneyString | null>\n}\n\n/**\n * Shared runtime instance for the `aggregate(b => spec)` builder form.\n *\n * The field-narrowing to `QueryField<T, S>` is type-only — each method\n * delegates directly to its standalone factory. A `(field: string) => R`\n * factory is assignable to a `(field: QueryField<T,S>) => R` method by\n * parameter-contravariance, so this single instance works for all `T`/`S`.\n */\nexport const reducerBuilder: ReducerBuilder<Record<string, unknown>> = {\n count,\n // The generic F parameter on sum/min/max in the interface is type-only; the\n // standalone factories are plain `(field: string)` functions. With M=never the\n // conditional return collapses to the numeric branch, matching the factories'\n // return type, but TypeScript cannot verify that collapse for a generic method\n // signature — so each factory is cast directly to the method's type.\n sum: sum as ReducerBuilder<Record<string, unknown>>['sum'],\n avg,\n min: min as ReducerBuilder<Record<string, unknown>>['min'],\n max: max as ReducerBuilder<Record<string, unknown>>['max'],\n moneySum, moneyMin, moneyMax,\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Read a numeric field from a record. Non-number values (null,\n * undefined, strings, objects) coerce to 0 so sum/avg/min/max don't\n * produce NaN on one bad row. Consumers who want strict typing should\n * validate upstream with Standard Schema, which NOYDB already runs on\n * every `put()`.\n */\nfunction readNumber(record: unknown, field: string): number {\n const value = readPath(record, field)\n // Defensive: a `{ amount, currency }` value means a multi-currency\n // money field reached a generic numeric reducer instead of being\n // rewritten by `wrapMoneyReducers` — that would silently produce a\n // wrong total. Surface it loudly rather than coercing to 0.\n if (\n typeof value === 'object' &&\n value !== null &&\n 'amount' in value &&\n 'currency' in value\n ) {\n throw new Error(\n `aggregate: field \"${field}\" holds a money value but was not money-aware — ` +\n `declare it in the collection's moneyFields so sum/min/max stay exact`,\n )\n }\n return typeof value === 'number' && Number.isFinite(value) ? value : 0\n}\n","/**\n * Aggregate execution — the runtime behind `Query.aggregate()`.\n *\n * takes an `AggregateSpec` (a record of named reducers\n * built from `reducers.ts`) and runs every reducer over the records\n * produced by the underlying query. Two terminal surfaces:\n *\n * - `.run(): R` — synchronous one-shot reduction. Matches the\n * existing `Query.toArray()` / `.first()` / `.count()` style.\n * - `.live(): LiveAggregation<R>` — reactive primitive that\n * re-runs the reduction whenever the query's source notifies of\n * a change. uses naive full re-run; incremental delta\n * maintenance is admitted by the reducer protocol (`remove()`)\n * but not wired to the executor yet — a follow-up optimization\n * can switch from full re-run to delta-based without breaking\n * the public API. Consumers get correct, reactive values today.\n *\n * The `Aggregation<R>` wrapper is deliberately tiny — it exists so\n * `.aggregate(spec)` can be chained with either `.run()` or `.live()`\n * without the builder needing two separate terminal methods. It\n * holds the closure over the query execution (produces the current\n * matching record set) and the spec, and stitches them together in\n * either mode.\n *\n * This file depends ONLY on `reducers.ts` — it has no knowledge of\n * the `Query` class. Tests can therefore exercise the reduction\n * surface with plain record arrays, without spinning up a Collection.\n */\n\nimport type { Reducer } from './reducers.js'\n\n/**\n * A named set of reducers, keyed by output field name. Each key\n * becomes a field on the aggregated result.\n *\n * ```ts\n * const spec = {\n * total: sum('amount'),\n * n: count(),\n * avgAmount: avg('amount'),\n * }\n * ```\n */\nexport type AggregateSpec = Readonly<Record<string, Reducer<unknown, unknown>>>\n\n/**\n * Map an `AggregateSpec` to its reduced result shape — each key\n * carries the finalized result type from its reducer. A spec built\n * from `{ total: sum('amount'), n: count() }` yields a result of\n * `{ total: number, n: number }`.\n *\n * This uses a mapped type with a conditional to extract `R` from\n * each `Reducer<R, _>`. The `infer` captures the user-visible result\n * type, discarding the internal state type `S`.\n */\nexport type AggregateResult<Spec extends AggregateSpec> = {\n [K in keyof Spec]: Spec[K] extends Reducer<infer R, unknown> ? R : never\n}\n\n/**\n * Pure reduction over a record array. Runs every reducer's\n * `init → step* → finalize` pipeline exactly once over the records.\n *\n * Called by `Aggregation.run()` and by the live-mode refresh path.\n * Exported for tests and for future `scan().aggregate()` reuse\n * — the streaming path will call the same reducer protocol with a\n * per-page loop instead of a single array.\n */\nexport function reduceRecords<Spec extends AggregateSpec>(\n records: readonly unknown[],\n spec: Spec,\n): AggregateResult<Spec> {\n // Per-slot state, keyed by the spec's output field name.\n const state: Record<string, unknown> = {}\n for (const key of Object.keys(spec)) {\n state[key] = spec[key]!.init()\n }\n for (const record of records) {\n for (const key of Object.keys(spec)) {\n state[key] = spec[key]!.step(state[key], record)\n }\n }\n const result: Record<string, unknown> = {}\n for (const key of Object.keys(spec)) {\n result[key] = spec[key]!.finalize(state[key])\n }\n return result as AggregateResult<Spec>\n}\n\n/**\n * A minimal reactive primitive for aggregation results.\n *\n * Same spirit as the `LiveQuery` in : frame-agnostic, a plain\n * object with `value` / `error` fields and a `subscribe(cb)`\n * notification channel that Vue / React / Solid adapters wrap in\n * their own primitive. Intentionally NOT a Promise — aggregations\n * have a well-defined \"current value\" at every instant, and the\n * reactive consumer wants to read that value synchronously.\n *\n * Error semantics mirror `LiveQuery`: if a re-run throws, the\n * previous successful `value` is preserved and the error is stored\n * in `error` so consumers can render an error state without losing\n * the last-known-good result. The throw does NOT propagate out of\n * the source's change handler (which would tear down the upstream\n * emitter).\n *\n * `stop()` tears down the upstream subscription. It is idempotent —\n * calling it multiple times is safe — and subscribe calls after\n * stop are no-ops (they immediately return a no-op unsubscribe).\n * Always call `stop()` when done; Vue's `onUnmounted` is the\n * canonical place. Raw consumers must do it themselves.\n */\nexport interface LiveAggregation<R> {\n /** Current reduced value. Undefined only if the first compute threw. */\n readonly value: R | undefined\n /** Last execution error, if any. Cleared on the next successful run. */\n readonly error: unknown\n /** Notify on every recomputation (success or error). Returns unsubscribe. */\n subscribe(cb: () => void): () => void\n /** Tear down the upstream subscription. Idempotent. */\n stop(): void\n}\n\n/**\n * Upstream change-notification hook for live aggregation.\n *\n * Matches the shape that `QuerySource.subscribe` already uses — a\n * single method that accepts a callback and returns an unsubscribe\n * function. The `Aggregation` wrapper collects upstreams from the\n * query's source and wires them into a single re-run trigger.\n */\nexport interface AggregationUpstream {\n subscribe(cb: () => void): () => void\n}\n\n/**\n * Internal implementation of `LiveAggregation`. Not exported —\n * consumers get the interface only. The class wraps a `recompute`\n * closure (which runs the full reduction and returns the new value)\n * and a list of upstreams (sources whose changes should trigger a\n * re-run).\n *\n * Error isolation: if an individual listener callback throws, the\n * other listeners still fire and the error is logged to the warn\n * channel. This matches `LiveQuery` from and keeps one misbehaving\n * consumer from tearing down the whole live aggregation.\n */\nclass LiveAggregationImpl<R> implements LiveAggregation<R> {\n public value: R | undefined\n public error: unknown\n private readonly listeners = new Set<() => void>()\n private readonly unsubscribes: Array<() => void> = []\n private stopped = false\n\n constructor(\n private readonly recompute: () => R,\n upstreams: readonly AggregationUpstream[],\n ) {\n // Initial computation — surface any error through the `error`\n // field rather than letting the constructor throw, so consumers\n // can always construct a LiveAggregation and check its state\n // afterwards. Throwing from a constructor would force every\n // caller to wrap in try/catch, which is the opposite of the\n // \"reactive value with error state\" ergonomics we want.\n try {\n this.value = recompute()\n this.error = undefined\n } catch (err) {\n this.value = undefined\n this.error = err\n }\n\n // Wire up upstream subscriptions. Each one triggers a full\n // recomputation; we don't attempt incremental updates in.\n for (const upstream of upstreams) {\n const unsub = upstream.subscribe(() => this.refresh())\n this.unsubscribes.push(unsub)\n }\n }\n\n private refresh(): void {\n if (this.stopped) return\n try {\n this.value = this.recompute()\n this.error = undefined\n } catch (err) {\n // Preserve the previous successful value — consumers render an\n // error state using `error` without losing the last-known-good\n // number. This matches LiveQuery's error-preservation contract.\n this.error = err\n }\n for (const listener of this.listeners) {\n try {\n listener()\n } catch (err) {\n // Isolate listener errors so one bad consumer can't tear\n // down every other subscriber on the same aggregation.\n console.warn('[noy-db] LiveAggregation listener threw:', err)\n }\n }\n }\n\n subscribe(cb: () => void): () => void {\n if (this.stopped) {\n // No-op after stop. Returning a harmless unsubscribe lets\n // consumers use the same teardown pattern unconditionally.\n return () => {}\n }\n this.listeners.add(cb)\n return () => {\n this.listeners.delete(cb)\n }\n }\n\n stop(): void {\n if (this.stopped) return\n this.stopped = true\n for (const unsub of this.unsubscribes) {\n try {\n unsub()\n } catch (err) {\n console.warn('[noy-db] LiveAggregation upstream unsubscribe threw:', err)\n }\n }\n this.unsubscribes.length = 0\n this.listeners.clear()\n }\n}\n\n/**\n * Chainable wrapper returned by `Query.aggregate(spec)`. Holds the\n * execute-records closure and the spec; terminal methods (`run`,\n * `live`) stitch them together in either mode.\n *\n * Why a wrapper instead of two terminal methods on `Query` directly?\n *\n * The `.aggregate(spec)` call is where the spec is bound — both\n * `.run()` and `.live()` need the same spec, and the consumer's\n * fluent style is `query.where(...).aggregate(spec).run()` or\n * `.aggregate(spec).live()`. Wrapping lets the spec be named once\n * and reused for either terminal, and keeps the `Query` class\n * from growing a pair of near-duplicate method overloads\n * (`aggregateRun` / `aggregateLive`) that would be harder to\n * discover.\n */\nexport class Aggregation<R> {\n constructor(\n private readonly executeRecords: () => readonly unknown[],\n private readonly spec: AggregateSpec,\n private readonly upstreams: readonly AggregationUpstream[],\n ) {}\n\n /**\n * Execute the query and reduce the results synchronously.\n * Returns the reduced shape matching the spec — e.g. a spec of\n * `{ total: sum('amount'), n: count() }` returns\n * `{ total: number, n: number }`.\n */\n run(): R {\n return reduceRecords(this.executeRecords(), this.spec) as unknown as R\n }\n\n /**\n * Build a reactive `LiveAggregation<R>` that re-runs the reduction\n * whenever any upstream source notifies of a change. The initial\n * value is computed eagerly in the constructor, so consumers can\n * read `live.value` immediately after calling `.live()`.\n *\n * Always call `live.stop()` when finished — it tears down the\n * upstream subscriptions. Vue's `onUnmounted` is the canonical\n * place.\n *\n * **Implementation note:** every upstream change triggers a full\n * re-reduction. Incremental maintenance (O(1) per delta for\n * sum/count/avg via the reducer protocol's `remove()` method) is a\n * planned follow-up optimization — the protocol already supports\n * it, but the executor doesn't drive it yet. Consumers get\n * correct, reactive values today; future PRs can switch to\n * delta-based maintenance without changing this API.\n */\n live(): LiveAggregation<R> {\n const recompute = (): R =>\n reduceRecords(this.executeRecords(), this.spec) as unknown as R\n return new LiveAggregationImpl<R>(recompute, this.upstreams)\n }\n}\n\n/**\n * Build a `LiveAggregation<V>` from a recompute closure and a list\n * of upstreams. Exposed so sibling files in the query DSL\n * (currently `groupby.ts`) can reuse the reactive primitive\n * without reaching into `LiveAggregationImpl` directly. This keeps\n * the implementation class private while still allowing planned\n * composition with `.groupBy().aggregate().live()`.\n */\nexport function buildLiveAggregation<V>(\n recompute: () => V,\n upstreams: readonly AggregationUpstream[],\n): LiveAggregation<V> {\n return new LiveAggregationImpl<V>(recompute, upstreams)\n}\n","/**\n * Canonicalise a group-key tuple to a stable string for dedup hashing.\n *\n * Sorts field names lexicographically before serialising, so that\n * `.groupBy('a', 'b')` and `.groupBy('b', 'a')` produce identical\n * keys for the same logical group. Values are JSON-stringified;\n * `undefined` and `null` are distinguished (matching the Map-key\n * semantics in `groupAndReduce`).\n *\n * NOT part of the public API. Used by:\n * - `groupAndReduce` for the dedup Map's key\n * - `materialized-views/query-hash` for UNION MV cross-arm row-key dedup (PR 2)\n *\n * Pure: same input → same output, no side effects.\n */\nexport function canonicalGroupKey(\n fields: readonly string[],\n row: Record<string, unknown>,\n): string {\n const sorted = [...fields].sort()\n const parts: string[] = []\n for (const name of sorted) {\n const v = row[name]\n const serialised =\n v === undefined ? 'undefined' : JSON.stringify(v)\n parts.push(`${name}=${serialised}`)\n }\n return parts.join('|')\n}\n","/**\n * Query DSL `.groupBy()` —.\n *\n * Chains after `.where()` / `.filter()` / `.or()` / `.and()` on a\n * Query and before a reducer spec, so consumers can compute\n * per-bucket aggregates without folding in userland:\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 * Execution pipeline:\n *\n * 1. Run the query's where/filter clauses (same candidate /\n * filter pipeline as `.aggregate()` directly on Query).\n * 2. Partition the matching records into buckets keyed by\n * `readPath(record, field)`. JS `Map` preserves insertion\n * order, so the first-seen key for a bucket determines its\n * position in the result array — consumers who want a\n * specific ordering should `.sort()` downstream.\n * 3. Enforce cardinality: warn once per field at 10% of the cap\n * (10_000 buckets), throw `GroupCardinalityError` at 100% of\n * the cap (100_000 buckets).\n * 4. For each bucket, build a per-group reducer state and\n * step every record in the bucket through it.\n * 5. Emit one result row per bucket, shaped as\n * `{ [field]: key, ...reduced }`.\n *\n * **Null / undefined keys:** `Map` distinguishes `null` from\n * `undefined`, so records with a missing group field get their own\n * bucket, and records with an explicit `null` value get a separate\n * bucket from that. Consumers who want them merged can coalesce\n * upstream with `.filter()`.\n *\n * **Live mode:** `.groupBy().aggregate().live()` re-runs the full\n * grouping pipeline on every source change. Per-bucket incremental\n * delta maintenance is a future optimization — the reducer\n * protocol's `remove()` hook admits it, but ships naive\n * re-grouping for simplicity.\n *\n * **Type-level stable-key narrowing:** when\n * `dictKey` lands, `groupBy<DictField>()` will narrow the group key\n * type to the stable dictionary key rather than the resolved locale\n * label. That prevents grouping by the locale-resolved label,\n * which would produce different buckets per reader. types the\n * key as `unknown` at the result shape; the dictKey narrowing\n * layers on top without an API break.\n *\n * Partition-awareness seam: when partitioned collections land,\n * per-partition grouping will need to merge sub-results across\n * partitions. The reducer protocol's `{ seed }` parameter\n * (already plumbed through in `reducers.ts`) is the mechanism —\n * groupBy doesn't need its own seam for the moment, because it\n * delegates to the reducer protocol for all per-bucket state.\n */\n\nimport { readPath } from '../../kernel/query/predicate.js'\nimport type {\n AggregateSpec,\n AggregateResult,\n AggregationUpstream,\n LiveAggregation,\n} from './aggregation.js'\nimport { buildLiveAggregation } from './aggregation.js'\nimport type { ReducerBuilder } from './reducers.js'\nimport { reducerBuilder } from './reducers.js'\nimport { canonicalGroupKey } from './canonical-key.js'\nimport { GroupCardinalityError } from '../../kernel/errors.js'\nimport type { MoneyDescriptor } from '../../with-shape/money/descriptor.js'\nimport { moneyRuntime } from '../../kernel/money-runtime.js'\nimport { applyI18nLocale, type I18nTextDescriptor } from '../../with-shape/i18n/core.js'\n\n/**\n * Cardinality thresholds for `.groupBy()`. The warn threshold gives\n * consumers a heads-up before the hard error; the cap is a fixed\n * constant in (not overridable). A `{ maxGroups }` override\n * can be added later without a break if a real consumer asks.\n */\nexport const GROUPBY_WARN_CARDINALITY = 10_000\nexport const GROUPBY_MAX_CARDINALITY = 100_000\n\n/**\n * One-shot warning dedup per-field-set — reactive dashboards\n * re-executing the same grouped query should produce the warning\n * once, not once per re-fire. Keyed on the sorted JSON of grouping\n * field names so `.groupBy('a', 'b')` and `.groupBy('b', 'a')`\n * share the same dedup slot (their result tuples are isomorphic).\n */\nconst warnedCardinalityFields = new Set<string>()\nfunction warnCardinalityApproaching(\n fields: readonly string[],\n observed: number,\n): void {\n const key = JSON.stringify([...fields].sort())\n if (warnedCardinalityFields.has(key)) return\n warnedCardinalityFields.add(key)\n const label = `[${fields.join(', ')}]`\n console.warn(\n `[noy-db] .groupBy(${label}) produced ${observed} distinct groups, ` +\n `${Math.round((observed / GROUPBY_MAX_CARDINALITY) * 100)}% of the ` +\n `${GROUPBY_MAX_CARDINALITY}-group ceiling. Narrow the query with ` +\n `.where() before grouping, or switch to a lower-cardinality field.`,\n )\n}\n\n/**\n * Test-only: clear the per-field cardinality warning dedup between\n * tests. Production code never calls this — matching the\n * `resetJoinWarnings` pattern in `join.ts`.\n */\nexport function resetGroupByWarnings(): void {\n warnedCardinalityFields.clear()\n}\n\n/**\n * Result row shape for a grouped aggregation. Each row carries the\n * group key value under the grouping field name plus every reducer\n * output from the spec.\n *\n * types the group key as `unknown` at the result shape — the\n * runtime read via `readPath` can return any value, and narrowing\n * to a specific type would require the caller to assert at the\n * call site. `dictKey` narrowing layers on top of this by\n * adding an overload that constrains `F` when the grouping field\n * is a `dictKey`.\n */\nexport type GroupedRow<F extends string, R> = { [K in F]: unknown } & R\n\n/**\n * Multi-key variant — result-row shape for variadic\n * `.groupBy(...fields)`. Every grouped field name appears on the row\n * (typed as `unknown` for the same reason as `GroupedRow`), plus the\n * reducer outputs from the spec.\n */\nexport type GroupedRowN<F extends readonly string[], R> =\n { [K in F[number]]: unknown } & R\n\n/**\n * Shared base class for the chainable grouped-query wrappers. Holds\n * the constructor + protected fields that both single-key\n * `GroupedQuery<T, F>` and variadic `GroupedQueryN<T, F>` need; each\n * subclass only overrides `aggregate()` with its own result-row\n * generic.\n *\n * Not exported — implementation detail. Adding `.having()` /\n * `.live()` / `.orderByGroup()` etc. in the future lands here once\n * and both subclasses pick it up automatically.\n *\n * @internal\n */\nabstract class GroupedQueryBase {\n /**\n * Field set this grouped query buckets on. Stored in declaration\n * order — the same order is preserved on every result row by\n * `groupAndReduce`. For the single-field constructor, this is\n * `[field]`.\n */\n protected readonly fields: readonly string[]\n\n constructor(\n protected readonly executeRecords: () => readonly unknown[],\n fieldOrFields: string | readonly string[],\n protected readonly upstreams: readonly AggregationUpstream[],\n /**\n * Optional dict label resolver attached by the query builder when\n * the grouping field is a dictKey. Variadic groupings always pass\n * `undefined` — `<field>Label` projection has no meaningful shape\n * for composite keys.\n */\n protected readonly dictLabelResolver?: (\n key: string,\n locale: string,\n fallback?: string | readonly string[],\n ) => Promise<string | undefined>,\n /**\n * Money field descriptors for the backing collection — used to\n * rewrite `sum`/`min`/`max` over money fields into exact BigInt\n * reducers when `.aggregate(spec)` is terminated.\n */\n protected readonly moneyFields?: Record<string, MoneyDescriptor>,\n ) {\n this.fields =\n typeof fieldOrFields === 'string' ? [fieldOrFields] : [...fieldOrFields]\n }\n\n /** Apply money-aware reducer rewriting when money fields are declared. */\n protected wrapSpec<Spec extends AggregateSpec>(spec: Spec): Spec {\n return this.moneyFields ? (moneyRuntime().wrapMoneyReducers(spec, this.moneyFields) as Spec) : spec\n }\n}\n\n/**\n * Chainable wrapper returned by `Query.groupBy(field)`. Terminates\n * with `.aggregate(spec)` which returns a `GroupedAggregation`.\n *\n * Kept minimal — the only operation on a grouped query is\n * aggregation. Ordering, limiting, and further filtering belong on\n * the underlying `Query` before `.groupBy()` is called; applying\n * them post-group would be a different operation (`having` /\n * `groupOrderBy`), out of scope for.\n */\nexport class GroupedQuery<T, F extends string, S extends keyof T = never, M extends keyof T & string = never> extends GroupedQueryBase {\n /**\n * Build a grouped aggregation. Returns a `GroupedAggregation`\n * with `.run()`, `.runAsync()`, and `.live()` terminals — same shape\n * as the non-grouped `.aggregate()` wrapper, just with an array\n * result (one row per bucket) instead of a single reduced object.\n *\n * The builder overload `aggregate(b => spec)` types `b` as\n * `ReducerBuilder<T, S, M>`, so field-taking reducers (`sum`, `avg`,\n * `min`, `max`) refuse any field listed in the collection's\n * `sensitive` option at compile time, and `sum`/`min`/`max` over a\n * declared `moneyFields` (`M`) member return a `MoneyString`. The\n * bare-spec overload is preserved for backward compatibility.\n */\n aggregate<Spec extends AggregateSpec>(spec: Spec): GroupedAggregation<GroupedRow<F, AggregateResult<Spec>>>\n aggregate<Spec extends AggregateSpec>(build: (b: ReducerBuilder<T, S, M>) => Spec): GroupedAggregation<GroupedRow<F, AggregateResult<Spec>>>\n aggregate<Spec extends AggregateSpec>(\n specOrBuild: Spec | ((b: ReducerBuilder<T, S, M>) => Spec),\n ): GroupedAggregation<GroupedRow<F, AggregateResult<Spec>>> {\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 // T is phantom on the wrapper so consumers can still see the\n // source row type on hover. Reference it to keep lint quiet.\n void undefined as T | undefined\n return new GroupedAggregation<GroupedRow<F, AggregateResult<Spec>>>(\n this.executeRecords,\n this.fields,\n this.wrapSpec(spec),\n this.upstreams,\n this.dictLabelResolver,\n )\n }\n}\n\n/**\n * Variadic-keyed sibling of `GroupedQuery<T, F>`. Constructed by the\n * multi-arg `Query.groupBy(...fields)` overload. The runtime shape is\n * identical — only the type-level result-row narrowing differs.\n */\nexport class GroupedQueryN<T, F extends readonly string[], S extends keyof T = never, M extends keyof T & string = never> extends GroupedQueryBase {\n aggregate<Spec extends AggregateSpec>(spec: Spec): GroupedAggregation<GroupedRowN<F, AggregateResult<Spec>>>\n aggregate<Spec extends AggregateSpec>(build: (b: ReducerBuilder<T, S, M>) => Spec): GroupedAggregation<GroupedRowN<F, AggregateResult<Spec>>>\n aggregate<Spec extends AggregateSpec>(\n specOrBuild: Spec | ((b: ReducerBuilder<T, S, M>) => Spec),\n ): GroupedAggregation<GroupedRowN<F, AggregateResult<Spec>>> {\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 void undefined as T | undefined\n return new GroupedAggregation<GroupedRowN<F, AggregateResult<Spec>>>(\n this.executeRecords,\n this.fields,\n this.wrapSpec(spec),\n this.upstreams,\n this.dictLabelResolver,\n )\n }\n}\n\n/**\n * Execute the group-and-reduce pipeline. Pure function over a\n * record array and a spec — shared by `GroupedAggregation.run()`\n * and the live-mode refresh path. Exported for tests and for any\n * future `scan().groupBy().aggregate()` reuse.\n *\n * Enforces the cardinality cap incrementally during the partition\n * loop, so a runaway grouping throws at the moment the 100_001st\n * bucket would be created — the consumer doesn't have to wait for\n * the full partition to materialize before the error fires.\n */\nexport function groupAndReduce<R>(\n records: readonly unknown[],\n fieldOrFields: string | readonly string[],\n spec: AggregateSpec,\n moneyFields?: Record<string, MoneyDescriptor>,\n): R[] {\n const fields: readonly string[] =\n typeof fieldOrFields === 'string' ? [fieldOrFields] : fieldOrFields\n if (fields.length === 0) {\n throw new Error('.groupBy() requires at least one field')\n }\n\n // Money-aware aggregation: when the caller declares money descriptors\n // for output/intermediate fields, rewrite any `sum`/`min`/`max` over\n // them into exact BigInt reducers before bucketing. Omitted → spec\n // passes through unchanged (backward compatible). The chainable\n // `GroupedQuery` path already wraps upstream via `wrapSpec`; this\n // covers direct `groupAndReduce` callers (UNION-form MVs) that have\n // no Query wrapper to do it.\n if (moneyFields) {\n spec = moneyRuntime().wrapMoneyReducers(spec, moneyFields)\n }\n\n // Bucket value is { keyValues, records } so the output row can stamp\n // every grouped field in DECLARATION ORDER. Map preserves insertion\n // order natively (ES2015), so first-seen keys determine ordering.\n interface Bucket {\n keyValues: Record<string, unknown>\n records: unknown[]\n }\n const buckets = new Map<string, Bucket>()\n // Field-label string for error messages — matches the variadic\n // surface (`[a, b]` for multi-key, `\"k\"` for single-key back-compat).\n const fieldLabel = fields.length === 1 ? fields[0]! : `[${fields.join(', ')}]`\n\n for (const record of records) {\n // Read each field's value into a row object, then canonicalise.\n const keyValues: Record<string, unknown> = {}\n for (const f of fields) {\n keyValues[f] = readPath(record, f)\n }\n const dedupKey = canonicalGroupKey(fields, keyValues)\n let bucket = buckets.get(dedupKey)\n if (bucket === undefined) {\n if (buckets.size >= GROUPBY_MAX_CARDINALITY) {\n throw new GroupCardinalityError(\n fieldLabel,\n buckets.size + 1,\n GROUPBY_MAX_CARDINALITY,\n )\n }\n bucket = { keyValues, records: [] }\n buckets.set(dedupKey, bucket)\n }\n bucket.records.push(record)\n }\n\n if (buckets.size >= GROUPBY_WARN_CARDINALITY) {\n warnCardinalityApproaching(fields, buckets.size)\n }\n\n // Reduce each bucket through the spec. Same init/step/finalize\n // pipeline as `reduceRecords` in aggregate.ts, but one state per\n // bucket. Inlining the loop here keeps the per-bucket path tight\n // — calling `reduceRecords` per bucket would recompute\n // `Object.keys(spec)` once per bucket unnecessarily.\n const reducerKeys = Object.keys(spec)\n const out: R[] = []\n for (const bucket of buckets.values()) {\n const state: Record<string, unknown> = {}\n for (const rk of reducerKeys) {\n state[rk] = spec[rk]!.init()\n }\n for (const record of bucket.records) {\n for (const rk of reducerKeys) {\n state[rk] = spec[rk]!.step(state[rk], record)\n }\n }\n // Stamp grouped fields FIRST, in declaration order — this is\n // tested via `Object.keys(row).slice(0, fields.length)`.\n const row: Record<string, unknown> = {}\n for (const f of fields) {\n row[f] = bucket.keyValues[f]\n }\n for (const rk of reducerKeys) {\n row[rk] = spec[rk]!.finalize(state[rk])\n }\n out.push(row as unknown as R)\n }\n return out\n}\n\n/**\n * Grouped aggregation wrapper — the `.groupBy(field).aggregate(spec)`\n * terminal. Shape mirrors `Aggregation<R>` from aggregate.ts: two\n * terminals (`.run()` and `.live()`), spec bound at construction\n * time, upstreams collected for live mode.\n *\n * The generic `R` is the per-row result shape (i.e. a single\n * grouped row), and the terminals return `R[]` — one row per\n * bucket.\n */\nexport class GroupedAggregation<R> {\n private readonly fields: readonly string[]\n\n constructor(\n private readonly executeRecords: () => readonly unknown[],\n fields: string | readonly string[],\n private readonly spec: AggregateSpec,\n private readonly upstreams: readonly AggregationUpstream[],\n /**\n * Optional dict label resolver for `<field>Label` projection\n *. Present when the grouping field is a dictKey.\n */\n private readonly dictLabelResolver?: (\n key: string,\n locale: string,\n fallback?: string | readonly string[],\n ) => Promise<string | undefined>,\n ) {\n this.fields = typeof fields === 'string' ? [fields] : [...fields]\n }\n\n /**\n * Execute the query, group, reduce, and return an array of rows.\n *\n * `opts` (query-form MV grouping): when a `locale` + `i18nFields` are\n * given, the declared group-key `i18nText` fields are resolved to that locale\n * at the `mv` layer BEFORE bucketing — so an i18n group key is a stable string\n * instead of a raw `{locale}` map. The MV executor passes the MV's\n * `i18nLocale`/`i18nFields`; ordinary `.run()` callers pass nothing and are\n * unaffected.\n */\n run(opts?: { locale?: string; i18nFields?: Record<string, I18nTextDescriptor> }): R[] {\n let records = this.executeRecords()\n if (opts?.locale !== undefined && opts.i18nFields !== undefined) {\n const groupI18n: Record<string, I18nTextDescriptor> = {}\n for (const f of this.fields) {\n const d = opts.i18nFields[f]\n if (d !== undefined) groupI18n[f] = d\n }\n if (Object.keys(groupI18n).length > 0) {\n records = records.map((r) => applyI18nLocale(r as Record<string, unknown>, groupI18n, opts.locale!, undefined, 'mv'))\n }\n }\n return groupAndReduce<R>(records, this.fields, this.spec)\n }\n\n /**\n * Execute the query, group, reduce, and resolve `<field>Label` for\n * each result row when the grouping field is a `dictKey` and a\n * `locale` is provided. Returns `R[]` synchronously when\n * no locale is specified (identical to `.run()`).\n *\n * The `<field>Label` field is appended to each row. Rows whose group\n * key has no dictionary entry get `<field>Label: undefined`.\n *\n * Dict-label resolution is single-field only — multi-key groupings\n * do not produce a `<field>Label`. The resolver is only attached\n * by the builder when `fields.length === 1`.\n */\n async runAsync(opts?: {\n locale?: string\n fallback?: string | readonly string[]\n }): Promise<R[]> {\n const rows = groupAndReduce<R>(this.executeRecords(), this.fields, this.spec)\n if (!opts?.locale || !this.dictLabelResolver || this.fields.length !== 1) return rows\n\n const resolve = this.dictLabelResolver\n const locale = opts.locale\n const fallback = opts.fallback\n const field = this.fields[0]!\n const labelKey = `${field}Label`\n\n return Promise.all(\n rows.map(async (row) => {\n const key = (row as Record<string, unknown>)[field]\n if (typeof key !== 'string') return row\n const label = await resolve(key, locale, fallback)\n return { ...(row as Record<string, unknown>), [labelKey]: label } as unknown as R\n }),\n )\n }\n\n /**\n * Build a reactive `LiveAggregation<R[]>` that re-runs the full\n * group-and-reduce pipeline whenever any upstream source notifies\n * of a change. Same error-isolation and idempotent-stop contract\n * as `Aggregation.live()` — the implementation delegates to the\n * same `LiveAggregationImpl` class by threading a fresh\n * recompute closure through the existing constructor.\n *\n * uses naive full re-run on every change. Incremental\n * per-bucket maintenance (apply `step` on inserted records,\n * `remove` on deleted records, route by bucket key) is a future\n * optimization — the reducer protocol admits it, but wiring\n * delta-aware source subscriptions is a separate PR.\n *\n * Always call `live.stop()` when finished.\n */\n live(): LiveAggregation<R[]> {\n const recompute = (): R[] =>\n groupAndReduce<R>(this.executeRecords(), this.fields, this.spec)\n return buildLiveAggregation<R[]>(recompute, this.upstreams)\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {
|
|
2
|
+
base64ToBuffer,
|
|
3
|
+
decryptBytesWithAAD,
|
|
4
|
+
encryptBytesWithAAD
|
|
5
|
+
} from "./chunk-TLJOJBZD.js";
|
|
6
|
+
import {
|
|
7
|
+
TamperedError
|
|
8
|
+
} from "./chunk-U23JUUPX.js";
|
|
9
|
+
|
|
10
|
+
// src/kernel/enclave/classify/compare.ts
|
|
11
|
+
var subtle = globalThis.crypto.subtle;
|
|
12
|
+
function ctEqualTags(a, b) {
|
|
13
|
+
if (a.length !== 32 || b.length !== 32) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
`ctEqualTags: tags must be exactly 32 bytes (got ${a.length}/${b.length}) \u2014 caller bug; reduce comparands with blindedEqual first`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
let diff = 0;
|
|
19
|
+
for (let i = 0; i < 32; i++) diff |= a[i] ^ b[i];
|
|
20
|
+
return diff === 0;
|
|
21
|
+
}
|
|
22
|
+
async function blindedEqual(a, b) {
|
|
23
|
+
const ke = await subtle.generateKey({ name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
24
|
+
const tagA = new Uint8Array(await subtle.sign("HMAC", ke, a));
|
|
25
|
+
const tagB = new Uint8Array(await subtle.sign("HMAC", ke, b));
|
|
26
|
+
return ctEqualTags(tagA, tagB);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/kernel/enclave/classify/vdig.ts
|
|
30
|
+
var subtle2 = globalThis.crypto.subtle;
|
|
31
|
+
var VDIG_SALT_DOMAIN = "noydb-classify-vdig";
|
|
32
|
+
function buildVdigAad(collection, recordId, field) {
|
|
33
|
+
return new TextEncoder().encode(JSON.stringify([VDIG_SALT_DOMAIN, collection, recordId, field]));
|
|
34
|
+
}
|
|
35
|
+
async function deriveVdigSlotKey(cek, collection, field) {
|
|
36
|
+
const raw = await subtle2.exportKey("raw", cek);
|
|
37
|
+
const hkdf = await subtle2.importKey("raw", raw, "HKDF", false, ["deriveBits"]);
|
|
38
|
+
const salt = new TextEncoder().encode(VDIG_SALT_DOMAIN);
|
|
39
|
+
const info = new TextEncoder().encode(JSON.stringify([VDIG_SALT_DOMAIN, collection, field]));
|
|
40
|
+
const bits = await subtle2.deriveBits({ name: "HKDF", hash: "SHA-256", salt, info }, hkdf, 256);
|
|
41
|
+
return subtle2.importKey("raw", bits, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]);
|
|
42
|
+
}
|
|
43
|
+
async function sealVdigPayload(payload, cek, collection, recordId, field) {
|
|
44
|
+
const key = await deriveVdigSlotKey(cek, collection, field);
|
|
45
|
+
const { iv, data } = await encryptBytesWithAAD(
|
|
46
|
+
new TextEncoder().encode(JSON.stringify(payload)),
|
|
47
|
+
key,
|
|
48
|
+
buildVdigAad(collection, recordId, field)
|
|
49
|
+
);
|
|
50
|
+
return `${iv}:${data}`;
|
|
51
|
+
}
|
|
52
|
+
async function openVdigPayload(blob, cek, collection, recordId, field) {
|
|
53
|
+
const sep = blob.indexOf(":");
|
|
54
|
+
if (sep === -1) throw new TamperedError();
|
|
55
|
+
const ivPart = blob.slice(0, sep);
|
|
56
|
+
const dataPart = blob.slice(sep + 1);
|
|
57
|
+
try {
|
|
58
|
+
base64ToBuffer(ivPart);
|
|
59
|
+
base64ToBuffer(dataPart);
|
|
60
|
+
} catch {
|
|
61
|
+
throw new TamperedError();
|
|
62
|
+
}
|
|
63
|
+
const key = await deriveVdigSlotKey(cek, collection, field);
|
|
64
|
+
const bytes = await decryptBytesWithAAD(
|
|
65
|
+
ivPart,
|
|
66
|
+
dataPart,
|
|
67
|
+
key,
|
|
68
|
+
buildVdigAad(collection, recordId, field)
|
|
69
|
+
);
|
|
70
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/kernel/enclave/classify/kofn.ts
|
|
74
|
+
function evaluateKofN(results, min) {
|
|
75
|
+
if (!Number.isInteger(min) || min < 1 || min > results.length) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`evaluateKofN: min ${min} out of range 1..${results.length} \u2014 caller bug (matchGroup validates bounds up front, before any PBKDF2)`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
let count = 0;
|
|
81
|
+
for (const r of results) {
|
|
82
|
+
if (r) count += 1;
|
|
83
|
+
}
|
|
84
|
+
return count >= min;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export {
|
|
88
|
+
ctEqualTags,
|
|
89
|
+
blindedEqual,
|
|
90
|
+
deriveVdigSlotKey,
|
|
91
|
+
sealVdigPayload,
|
|
92
|
+
openVdigPayload,
|
|
93
|
+
evaluateKofN
|
|
94
|
+
};
|
|
95
|
+
//# sourceMappingURL=chunk-PGFY7IRW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/kernel/enclave/classify/compare.ts","../src/kernel/enclave/classify/vdig.ts","../src/kernel/enclave/classify/kofn.ts"],"sourcesContent":["/**\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":";;;;;;;;;;AAYA,IAAM,SAAS,WAAW,OAAO;AAG1B,SAAS,YAAY,GAAe,GAAwB;AACjE,MAAI,EAAE,WAAW,MAAM,EAAE,WAAW,IAAI;AACtC,UAAM,IAAI;AAAA,MACR,mDAAmD,EAAE,MAAM,IAAI,EAAE,MAAM;AAAA,IAEzE;AAAA,EACF;AACA,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,IAAI,IAAK,SAAS,EAAE,CAAC,IAAK,EAAE,CAAC;AACjD,SAAO,SAAS;AAClB;AASA,eAAsB,aAAa,GAAe,GAAiC;AACjF,QAAM,KAAK,MAAM,OAAO,YAAY,EAAE,MAAM,QAAQ,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;AACtF,QAAM,OAAO,IAAI,WAAW,MAAM,OAAO,KAAK,QAAQ,IAAI,CAAiB,CAAC;AAC5E,QAAM,OAAO,IAAI,WAAW,MAAM,OAAO,KAAK,QAAQ,IAAI,CAAiB,CAAC;AAC5E,SAAO,YAAY,MAAM,IAAI;AAC/B;;;ACpBA,IAAMA,UAAS,WAAW,OAAO;AAE1B,IAAM,mBAAmB;AAiBzB,SAAS,aAAa,YAAoB,UAAkB,OAA2B;AAC5F,SAAO,IAAI,YAAY,EAAE,OAAO,KAAK,UAAU,CAAC,kBAAkB,YAAY,UAAU,KAAK,CAAC,CAAC;AACjG;AAGA,eAAsB,kBACpB,KACA,YACA,OACqB;AACrB,QAAM,MAAM,MAAMA,QAAO,UAAU,OAAO,GAAG;AAC7C,QAAM,OAAO,MAAMA,QAAO,UAAU,OAAO,KAAK,QAAQ,OAAO,CAAC,YAAY,CAAC;AAC7E,QAAM,OAAO,IAAI,YAAY,EAAE,OAAO,gBAAgB;AACtD,QAAM,OAAO,IAAI,YAAY,EAAE,OAAO,KAAK,UAAU,CAAC,kBAAkB,YAAY,KAAK,CAAC,CAAC;AAC3F,QAAM,OAAO,MAAMA,QAAO,WAAW,EAAE,MAAM,QAAQ,MAAM,WAAW,MAAM,KAAK,GAAG,MAAM,GAAG;AAC7F,SAAOA,QAAO,UAAU,OAAO,MAAM,EAAE,MAAM,WAAW,QAAQ,IAAI,GAAG,OAAO,CAAC,WAAW,SAAS,CAAC;AACtG;AAEA,eAAsB,gBACpB,SACA,KACA,YACA,UACA,OACiB;AACjB,QAAM,MAAM,MAAM,kBAAkB,KAAK,YAAY,KAAK;AAC1D,QAAM,EAAE,IAAI,KAAK,IAAI,MAAM;AAAA,IACzB,IAAI,YAAY,EAAE,OAAO,KAAK,UAAU,OAAO,CAAC;AAAA,IAChD;AAAA,IACA,aAAa,YAAY,UAAU,KAAK;AAAA,EAC1C;AACA,SAAO,GAAG,EAAE,IAAI,IAAI;AACtB;AAGA,eAAsB,gBACpB,MACA,KACA,YACA,UACA,OACsB;AACtB,QAAM,MAAM,KAAK,QAAQ,GAAG;AAC5B,MAAI,QAAQ,GAAI,OAAM,IAAI,cAAc;AACxC,QAAM,SAAS,KAAK,MAAM,GAAG,GAAG;AAChC,QAAM,WAAW,KAAK,MAAM,MAAM,CAAC;AACnC,MAAI;AACF,mBAAe,MAAM;AACrB,mBAAe,QAAQ;AAAA,EACzB,QAAQ;AACN,UAAM,IAAI,cAAc;AAAA,EAC1B;AACA,QAAM,MAAM,MAAM,kBAAkB,KAAK,YAAY,KAAK;AAC1D,QAAM,QAAQ,MAAM;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa,YAAY,UAAU,KAAK;AAAA,EAC1C;AACA,SAAO,KAAK,MAAM,IAAI,YAAY,EAAE,OAAO,KAAK,CAAC;AACnD;;;AC5FO,SAAS,aAAa,SAA6B,KAAsB;AAC9E,MAAI,CAAC,OAAO,UAAU,GAAG,KAAK,MAAM,KAAK,MAAM,QAAQ,QAAQ;AAC7D,UAAM,IAAI;AAAA,MACR,qBAAqB,GAAG,oBAAoB,QAAQ,MAAM;AAAA,IAE5D;AAAA,EACF;AACA,MAAI,QAAQ;AACZ,aAAW,KAAK,SAAS;AACvB,QAAI,EAAG,UAAS;AAAA,EAClB;AACA,SAAO,SAAS;AAClB;","names":["subtle"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NOYDB_FORMAT_VERSION
|
|
3
|
+
} from "./chunk-LN22XH4B.js";
|
|
4
|
+
|
|
5
|
+
// src/kernel/enclave/record-keys/tombstone.ts
|
|
6
|
+
function isTombstone(envelope, encrypted) {
|
|
7
|
+
if (!encrypted) return false;
|
|
8
|
+
return !envelope._data && envelope._cek === void 0;
|
|
9
|
+
}
|
|
10
|
+
function buildTombstone(version, actor) {
|
|
11
|
+
return {
|
|
12
|
+
_noydb: NOYDB_FORMAT_VERSION,
|
|
13
|
+
_v: version,
|
|
14
|
+
_ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15
|
+
_iv: "",
|
|
16
|
+
_data: "",
|
|
17
|
+
...actor ? { _by: actor } : {}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
isTombstone,
|
|
23
|
+
buildTombstone
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=chunk-QKVILR7N.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/kernel/enclave/record-keys/tombstone.ts"],"sourcesContent":["/**\n * Tombstone shape + predicate for the per-record-key (CEK) layer.\n *\n * A *tombstone* is the residue a GDPR crypto-shred (`vault.forget()`)\n * leaves on disk: the live envelope rewritten to `{ _noydb, _v, _ts, _by,\n * _iv:'', _data:'' }`, dropping `_iv`/`_data`/`_cek`/`_det`. With the wrapped\n * per-record CEK gone, the body — and every history version under the same\n * CEK — is permanently undecryptable, while the record KEY survives so the\n * version counter and \"record existed and was erased\" persist for the audit\n * ledger.\n *\n * These two helpers are the pure, collection-independent core of that\n * contract: {@link buildTombstone} mints the shape, {@link isTombstone}\n * recognises it on the read path. The orchestration that *writes* a tombstone\n * (`_writeTombstone`) and drives `vault.forget()` lives with the collection /\n * vault; it calls into these.\n */\nimport { NOYDB_FORMAT_VERSION, type EncryptedEnvelope } from '../../types.js'\n\n/**\n * Is this envelope a crypto-shred tombstone?\n *\n * A tombstone carries no body (`_data` empty) and no wrapped CEK (`_cek`\n * absent) — decrypting it would call `decrypt('', '', dek)` → an AES-GCM\n * throw, so every read choke point must short-circuit to `null` instead.\n *\n * `encrypted` is the collection's encryption flag: on an unencrypted\n * collection there are no envelopes to shred, so nothing is ever a tombstone.\n * (Legacy migration envelopes carry non-empty `_iv`/`_data`, so they are not\n * tombstones and stay readable.)\n */\nexport function isTombstone(envelope: EncryptedEnvelope, encrypted: boolean): boolean {\n if (!encrypted) return false\n return !envelope._data && envelope._cek === undefined\n}\n\n/**\n * Mint a tombstone envelope from a record's live version + actor.\n *\n * Keeps `_v` (the displaced version) so the counter is monotonic across the\n * shred, stamps a fresh `_ts`, and records `_by` when an actor is supplied.\n * Deliberately omits `_iv`/`_data`/`_cek`/`_det` — that omission *is* the\n * erasure.\n */\nexport function buildTombstone(version: number, actor: string): EncryptedEnvelope {\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: version,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: '',\n ...(actor ? { _by: actor } : {}),\n }\n}\n"],"mappings":";;;;;AA+BO,SAAS,YAAY,UAA6B,WAA6B;AACpF,MAAI,CAAC,UAAW,QAAO;AACvB,SAAO,CAAC,SAAS,SAAS,SAAS,SAAS;AAC9C;AAUO,SAAS,eAAe,SAAiB,OAAkC;AAChF,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,IAAI;AAAA,IACJ,MAAK,oBAAI,KAAK,GAAE,YAAY;AAAA,IAC5B,KAAK;AAAA,IACL,OAAO;AAAA,IACP,GAAI,QAAQ,EAAE,KAAK,MAAM,IAAI,CAAC;AAAA,EAChC;AACF;","names":[]}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
evaluateClause,
|
|
3
3
|
readPath
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZKF6HH7V.js";
|
|
5
5
|
import {
|
|
6
6
|
IndexRequiredError
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-U23JUUPX.js";
|
|
8
8
|
|
|
9
9
|
// src/with-lookup/indexing/persisted-indexes.ts
|
|
10
10
|
var IDX_PREFIX = "_idx/";
|
|
@@ -427,4 +427,4 @@ export {
|
|
|
427
427
|
PersistedCollectionIndex,
|
|
428
428
|
LazyQuery
|
|
429
429
|
};
|
|
430
|
-
//# sourceMappingURL=chunk-
|
|
430
|
+
//# sourceMappingURL=chunk-QNTEDPWP.js.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
sha256Hex
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-TLJOJBZD.js";
|
|
4
4
|
import {
|
|
5
5
|
DerivationCycleError
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-U23JUUPX.js";
|
|
7
7
|
|
|
8
8
|
// src/with-formula/derivations/strategy-hash.ts
|
|
9
9
|
async function computeStrategyHash(source, outputKeys, derive, sources) {
|
|
@@ -117,4 +117,4 @@ var DerivationRegistry = class {
|
|
|
117
117
|
export {
|
|
118
118
|
DerivationRegistry
|
|
119
119
|
};
|
|
120
|
-
//# sourceMappingURL=chunk-
|
|
120
|
+
//# sourceMappingURL=chunk-RDHAGBEB.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ValidationError
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-U23JUUPX.js";
|
|
4
4
|
|
|
5
5
|
// src/with-formula/overlay-views/with-overlayed-view.ts
|
|
6
6
|
function withOverlayedView(spec) {
|
|
@@ -33,4 +33,4 @@ function withOverlayedView(spec) {
|
|
|
33
33
|
export {
|
|
34
34
|
withOverlayedView
|
|
35
35
|
};
|
|
36
|
-
//# sourceMappingURL=chunk-
|
|
36
|
+
//# sourceMappingURL=chunk-RTS23VIJ.js.map
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ensureCollectionDEK
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-VAWY44JW.js";
|
|
4
4
|
import {
|
|
5
5
|
envelopePayloadHash
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-M66NAZA4.js";
|
|
7
7
|
import {
|
|
8
|
-
encrypt,
|
|
9
8
|
openEnvelopeJson
|
|
10
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-OVO2RZAE.js";
|
|
11
10
|
import {
|
|
12
11
|
NOYDB_FORMAT_VERSION
|
|
13
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-LN22XH4B.js";
|
|
13
|
+
import {
|
|
14
|
+
encrypt
|
|
15
|
+
} from "./chunk-TLJOJBZD.js";
|
|
14
16
|
import {
|
|
15
17
|
DictKeyMissingError,
|
|
16
18
|
PermissionDeniedError
|
|
17
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-U23JUUPX.js";
|
|
18
20
|
|
|
19
21
|
// src/with-shape/i18n/dictionary.ts
|
|
20
22
|
var DICT_COLLECTION_PREFIX = "_dict_";
|
|
@@ -411,4 +413,4 @@ export {
|
|
|
411
413
|
isStaticDictDescriptor,
|
|
412
414
|
DictionaryHandle
|
|
413
415
|
};
|
|
414
|
-
//# sourceMappingURL=chunk-
|
|
416
|
+
//# sourceMappingURL=chunk-SEQ2JI3Y.js.map
|