@noy-db/hub 0.7.0 → 0.8.0-pre.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +105 -14
- package/LICENSE +202 -21
- package/NOTICE +1 -0
- package/README.md +27 -10
- package/codemods/0.8.0-pre.json +61 -0
- package/dist/api-GVYEKSJE.js +19 -0
- package/dist/as/index.js +11 -11
- package/dist/at/index.js +9 -9
- package/dist/attestation/index.js +20 -20
- package/dist/{backup-IJTB4S4M.js → backup-7TFRVFLH.js} +21 -15
- package/dist/backup-7TFRVFLH.js.map +1 -0
- package/dist/blobs/index.js +13 -13
- package/dist/broker/index.js +12 -12
- package/dist/by/index.js +2 -2
- package/dist/cargo/index.js +39 -36
- package/dist/{chunk-7O3Y4PPD.js → chunk-244VXYPG.js} +140 -16
- package/dist/chunk-244VXYPG.js.map +1 -0
- package/dist/{chunk-BUE2D7PX.js → chunk-32YV3TJT.js} +2 -2
- package/dist/{chunk-3RCJJBNR.js → chunk-3ELRMVJK.js} +12 -12
- package/dist/{chunk-6GMZ7HEE.js → chunk-3F7GLNAE.js} +6 -6
- package/dist/{chunk-2Y4IMKA6.js → chunk-3H5KLDZN.js} +4 -4
- package/dist/{chunk-VTFVLCUH.js → chunk-3JWHAR6E.js} +2 -2
- package/dist/{chunk-YMUFFJCO.js → chunk-3TDUWVWB.js} +5 -2
- package/dist/chunk-3TDUWVWB.js.map +1 -0
- package/dist/{chunk-JBDFMYKQ.js → chunk-4AF3TUMP.js} +2 -2
- package/dist/{chunk-ZORTV6NH.js → chunk-4I522ERM.js} +88 -4
- package/dist/chunk-4I522ERM.js.map +1 -0
- package/dist/{chunk-MUM7IANK.js → chunk-4MXI3ODD.js} +4 -4
- package/dist/chunk-4MXI3ODD.js.map +1 -0
- package/dist/{chunk-BLN54W3L.js → chunk-5FDLC6G7.js} +56 -3
- package/dist/chunk-5FDLC6G7.js.map +1 -0
- package/dist/chunk-5O7D6ZTW.js +53 -0
- package/dist/chunk-5O7D6ZTW.js.map +1 -0
- package/dist/{chunk-J6FXAEUG.js → chunk-6B3VNR4P.js} +12 -12
- package/dist/{chunk-TUFM4UZP.js → chunk-6K7DPM7D.js} +2 -2
- package/dist/{chunk-K6LHRFBL.js → chunk-6LMQ5R26.js} +7 -7
- package/dist/{chunk-SPXXX5E4.js → chunk-6LNL42F5.js} +94 -32
- package/dist/chunk-6LNL42F5.js.map +1 -0
- package/dist/{chunk-XLMEZIAJ.js → chunk-6OWXYZK2.js} +2 -2
- package/dist/{chunk-BOWNCWZT.js → chunk-6QSQQLSE.js} +3 -3
- package/dist/chunk-7JE7BDYJ.js +1558 -0
- package/dist/chunk-7JE7BDYJ.js.map +1 -0
- package/dist/{chunk-KG7TZKZG.js → chunk-7VCXZDA4.js} +5 -5
- package/dist/chunk-7VCXZDA4.js.map +1 -0
- package/dist/{chunk-NQKIUNW5.js → chunk-A6IJU4TK.js} +2 -2
- package/dist/{chunk-BM5TWITZ.js → chunk-ANWPSTZU.js} +38 -9
- package/dist/chunk-ANWPSTZU.js.map +1 -0
- package/dist/{chunk-QIZPZMUI.js → chunk-AOWGXU6D.js} +2 -2
- package/dist/{chunk-CUZLNGD5.js → chunk-B2YY6WDD.js} +1 -1
- package/dist/chunk-B2YY6WDD.js.map +1 -0
- package/dist/{chunk-SFAWCTEM.js → chunk-B57AFBY2.js} +2 -2
- package/dist/{chunk-GPEQUXHL.js → chunk-B6SJIBQK.js} +4 -4
- package/dist/chunk-BJ2IXQ5Q.js +100 -0
- package/dist/chunk-BJ2IXQ5Q.js.map +1 -0
- package/dist/{chunk-Q2PU57HC.js → chunk-BK2HHQGN.js} +2 -2
- package/dist/chunk-BKT776R5.js +1 -0
- package/dist/{chunk-KUX3OB6D.js → chunk-BP6DARYA.js} +33 -5
- package/dist/chunk-BP6DARYA.js.map +1 -0
- package/dist/{chunk-FXBIP2WD.js → chunk-BUDAGWL5.js} +6 -6
- package/dist/chunk-CF5UU4S7.js +757 -0
- package/dist/chunk-CF5UU4S7.js.map +1 -0
- package/dist/{chunk-BOET4NA4.js → chunk-CKNGS5MH.js} +3 -3
- package/dist/chunk-CY2CYVWZ.js +41 -0
- package/dist/chunk-CY2CYVWZ.js.map +1 -0
- package/dist/{chunk-2577DKXB.js → chunk-DC5QVPV7.js} +2 -2
- package/dist/chunk-DC5QVPV7.js.map +1 -0
- package/dist/{chunk-YSRUYAVU.js → chunk-DE4DH2RZ.js} +2 -2
- package/dist/{chunk-6I2RQCET.js → chunk-DF73H4UN.js} +4 -4
- package/dist/{chunk-4GVM6QZA.js → chunk-ENAG3IY7.js} +2 -2
- package/dist/chunk-ENAG3IY7.js.map +1 -0
- package/dist/{chunk-K7NTTA6P.js → chunk-EOVMT6TB.js} +2 -2
- package/dist/chunk-F24ZZTHQ.js +193 -0
- package/dist/chunk-F24ZZTHQ.js.map +1 -0
- package/dist/chunk-F2TDU7MQ.js +456 -0
- package/dist/chunk-F2TDU7MQ.js.map +1 -0
- package/dist/chunk-F6NCB7KT.js +13 -0
- package/dist/chunk-F6NCB7KT.js.map +1 -0
- package/dist/{chunk-SPYWXKJN.js → chunk-F7XGHL5O.js} +3 -3
- package/dist/chunk-F7XGHL5O.js.map +1 -0
- package/dist/{chunk-XTXOCYOJ.js → chunk-FCKWQE2H.js} +5 -5
- package/dist/{chunk-U4FOQDY4.js → chunk-FH2L2E34.js} +4 -4
- package/dist/chunk-FH2L2E34.js.map +1 -0
- package/dist/{chunk-4OZNXTO4.js → chunk-FN5EBVTQ.js} +2 -2
- package/dist/chunk-FQ54DWUQ.js +270 -0
- package/dist/chunk-FQ54DWUQ.js.map +1 -0
- package/dist/{chunk-NKHRDYOR.js → chunk-FQ5VRHIB.js} +4 -4
- package/dist/{chunk-FAHMLERK.js → chunk-FW22ZCXT.js} +2 -2
- package/dist/chunk-FXQBGFOX.js +524 -0
- package/dist/chunk-FXQBGFOX.js.map +1 -0
- package/dist/{chunk-AO42KJO6.js → chunk-GF5BZ7D4.js} +45 -9
- package/dist/chunk-GF5BZ7D4.js.map +1 -0
- package/dist/{chunk-TRGTKX5E.js → chunk-GHLTDZRS.js} +5 -3
- package/dist/{chunk-TRGTKX5E.js.map → chunk-GHLTDZRS.js.map} +1 -1
- package/dist/{chunk-5WDXHNSW.js → chunk-HBPPGWUM.js} +5 -5
- package/dist/chunk-HBPPGWUM.js.map +1 -0
- package/dist/{chunk-KTFRCC5C.js → chunk-HBRLNOHL.js} +82 -8
- package/dist/chunk-HBRLNOHL.js.map +1 -0
- package/dist/chunk-HLEK5VAN.js +275 -0
- package/dist/chunk-HLEK5VAN.js.map +1 -0
- package/dist/{chunk-RMS3NEA3.js → chunk-HT2AHGQI.js} +2 -2
- package/dist/{chunk-CHJBLCOG.js → chunk-HVSCEIYL.js} +7 -7
- package/dist/{chunk-UXWB3FPF.js → chunk-I3JDRCMW.js} +150 -22
- package/dist/chunk-I3JDRCMW.js.map +1 -0
- package/dist/{chunk-GY4WYNNG.js → chunk-ID6Z6E66.js} +2 -2
- package/dist/{chunk-EYBQEIUY.js → chunk-IEX7NK2K.js} +3 -3
- package/dist/{chunk-KUJDV5QH.js → chunk-ISZQJBML.js} +5 -5
- package/dist/{chunk-LWBCUYZW.js → chunk-IT22PWKV.js} +2 -2
- package/dist/{chunk-EQEYOJBM.js → chunk-IUWCVHQ7.js} +3 -3
- package/dist/chunk-IUWCVHQ7.js.map +1 -0
- package/dist/{chunk-7V4TP3AF.js → chunk-J6UOHJY5.js} +4 -4
- package/dist/{chunk-JMGDHKQF.js → chunk-JCN44NCY.js} +2 -2
- package/dist/{chunk-CARABLSQ.js → chunk-K6W7BVZ6.js} +3 -3
- package/dist/{chunk-77K4PG6P.js → chunk-KJFW62P4.js} +29 -11
- package/dist/chunk-KJFW62P4.js.map +1 -0
- package/dist/{chunk-453F654R.js → chunk-L7DOS3TE.js} +2 -2
- package/dist/chunk-LGNDFRYC.js +23 -0
- package/dist/chunk-LGNDFRYC.js.map +1 -0
- package/dist/{chunk-WEQEYLCS.js → chunk-LGRLME4M.js} +7 -7
- package/dist/{chunk-RMF67KCG.js → chunk-LQ4ZHOKP.js} +2 -2
- package/dist/{chunk-ZVO4QVPD.js → chunk-M522UYZ2.js} +5 -5
- package/dist/{chunk-G56KBWFY.js → chunk-M7NI3IWE.js} +45 -12
- package/dist/chunk-M7NI3IWE.js.map +1 -0
- package/dist/chunk-MDWKM2XC.js +293 -0
- package/dist/chunk-MDWKM2XC.js.map +1 -0
- package/dist/chunk-MKXXMNJH.js +204 -0
- package/dist/chunk-MKXXMNJH.js.map +1 -0
- package/dist/{chunk-SKGMN35F.js → chunk-MLPHLJIQ.js} +3 -3
- package/dist/chunk-MLPHLJIQ.js.map +1 -0
- package/dist/{chunk-2N4GY74U.js → chunk-MTL7MXGR.js} +6 -6
- package/dist/{chunk-TFR7DSOV.js → chunk-MVZ2TPVP.js} +2 -2
- package/dist/{chunk-CPWQB3N5.js → chunk-N5DNADLQ.js} +7 -7
- package/dist/{chunk-ELNXJMGB.js → chunk-NNARWO72.js} +6 -6
- package/dist/{chunk-PT43Y56J.js → chunk-NRT6QZQB.js} +102 -16
- package/dist/chunk-NRT6QZQB.js.map +1 -0
- package/dist/{chunk-QIBVIOXO.js → chunk-NWHBJQAL.js} +99 -8
- package/dist/chunk-NWHBJQAL.js.map +1 -0
- package/dist/chunk-NYMLINRP.js +28 -0
- package/dist/chunk-NYMLINRP.js.map +1 -0
- package/dist/{chunk-7MG7SJT7.js → chunk-ODVPTDPK.js} +1 -1
- package/dist/chunk-ODVPTDPK.js.map +1 -0
- package/dist/chunk-ORDURZBU.js +461 -0
- package/dist/chunk-ORDURZBU.js.map +1 -0
- package/dist/{chunk-6YXCPL4T.js → chunk-PDCG6PR5.js} +2 -2
- package/dist/{chunk-REDOOY2Q.js → chunk-PSIV7CJV.js} +2 -2
- package/dist/{chunk-NA2NIJZI.js → chunk-QBBUZ3I3.js} +126 -19
- package/dist/chunk-QBBUZ3I3.js.map +1 -0
- package/dist/{chunk-5VCRVKOZ.js → chunk-QOOUUR4A.js} +6 -4
- package/dist/{chunk-5VCRVKOZ.js.map → chunk-QOOUUR4A.js.map} +1 -1
- package/dist/{chunk-ANATMFIO.js → chunk-QWFHIOD2.js} +5 -5
- package/dist/{chunk-X2DLENDQ.js → chunk-QWGU66MP.js} +3 -3
- package/dist/chunk-RHVMZSUU.js +7 -0
- package/dist/chunk-RHVMZSUU.js.map +1 -0
- package/dist/{chunk-BSFNRLK2.js → chunk-S57HTZ6X.js} +5 -5
- package/dist/{chunk-SZJ3DLZA.js → chunk-SEE332EV.js} +2 -2
- package/dist/{chunk-YYP7WSEB.js → chunk-SI3V7D4G.js} +2 -2
- package/dist/{chunk-UGTBXF5U.js → chunk-SOIQ4MM4.js} +3 -3
- package/dist/chunk-SUZKW6UR.js +25 -0
- package/dist/chunk-SUZKW6UR.js.map +1 -0
- package/dist/{chunk-VXGSWL6L.js → chunk-SYVZW77S.js} +6 -6
- package/dist/{chunk-6GOE5PGN.js → chunk-T2LBQ5DV.js} +883 -227
- package/dist/chunk-T2LBQ5DV.js.map +1 -0
- package/dist/chunk-T52GX4SA.js +215 -0
- package/dist/chunk-T52GX4SA.js.map +1 -0
- package/dist/{chunk-MVMXZYXT.js → chunk-TKUOEU2F.js} +2 -2
- package/dist/chunk-TOPYQ6GA.js +31 -0
- package/dist/chunk-TOPYQ6GA.js.map +1 -0
- package/dist/{chunk-SMH6SRX7.js → chunk-TROR3ALR.js} +2 -2
- package/dist/{chunk-2C3AQB46.js → chunk-UD5EVGZ7.js} +42 -389
- package/dist/chunk-UD5EVGZ7.js.map +1 -0
- package/dist/{chunk-KJIMGIWO.js → chunk-UPD5NWI5.js} +65 -17
- package/dist/chunk-UPD5NWI5.js.map +1 -0
- package/dist/{chunk-SL3JAAKN.js → chunk-UYB234ZR.js} +30 -2
- package/dist/chunk-UYB234ZR.js.map +1 -0
- package/dist/chunk-VHM7HCKB.js +220 -0
- package/dist/chunk-VHM7HCKB.js.map +1 -0
- package/dist/{chunk-DGOZ4UZD.js → chunk-VNNRUGMM.js} +2 -2
- package/dist/{chunk-HDOU6I2Y.js → chunk-VNPEWFPD.js} +4 -4
- package/dist/{chunk-L7CIOOBW.js → chunk-WNJ4AVGE.js} +2 -2
- package/dist/{chunk-FMZY5Q6L.js → chunk-WX5ADGTZ.js} +3 -3
- package/dist/{chunk-VXANTWBJ.js → chunk-WY2XXTET.js} +92 -6
- package/dist/{chunk-VXANTWBJ.js.map → chunk-WY2XXTET.js.map} +1 -1
- package/dist/{chunk-6X6SEMEE.js → chunk-X25NWUZB.js} +33 -8
- package/dist/chunk-X25NWUZB.js.map +1 -0
- package/dist/{chunk-Q7HZX3IF.js → chunk-X4773RBR.js} +2 -2
- package/dist/chunk-XBKH4FOC.js +195 -0
- package/dist/chunk-XBKH4FOC.js.map +1 -0
- package/dist/{chunk-ANSIZYEG.js → chunk-YNIUIERI.js} +3 -3
- package/dist/chunk-YSK2CU2Q.js +936 -0
- package/dist/chunk-YSK2CU2Q.js.map +1 -0
- package/dist/{chunk-UXVWEX5L.js → chunk-Z5HTYMMQ.js} +6 -2
- package/dist/chunk-Z5HTYMMQ.js.map +1 -0
- package/dist/{chunk-RRNRNANG.js → chunk-ZE4CHIED.js} +66 -75
- package/dist/chunk-ZE4CHIED.js.map +1 -0
- package/dist/chunk-ZSX4S5XB.js +1289 -0
- package/dist/chunk-ZSX4S5XB.js.map +1 -0
- package/dist/{chunk-BZ3534YM.js → chunk-ZUVBC32X.js} +2 -2
- package/dist/{chunk-L742JPKZ.js → chunk-ZXABPVM6.js} +6 -6
- package/dist/classified/index.js +5 -5
- package/dist/{classified-marker-LI2SF2E4.js → classified-marker-W3USLL5M.js} +3 -3
- package/dist/collection-facade-QU6IJBN3.js +48 -0
- package/dist/{computed-VPOKQK4N.js → computed-TPFBX7U4.js} +3 -3
- package/dist/consent/index.js +9 -9
- package/dist/cover/index.js +10 -10
- package/dist/coverage/index.js +306 -0
- package/dist/coverage/index.js.map +1 -0
- package/dist/custody/index.js +12 -12
- package/dist/{dead-filter-TSWZQRR5.js → dead-filter-U2UYKSZL.js} +2 -2
- package/dist/debug/index.js +1 -1
- package/dist/{delegation-OVIFFPG4.js → delegation-S3AEPW2T.js} +11 -11
- package/dist/derivations/index.js +25 -22
- package/dist/derive-OX7F6TGG.js +21 -0
- package/dist/directory/index.js +10 -10
- package/dist/{dispatch-WC6X2M6C.js → dispatch-3CQDJV5Q.js} +5 -5
- package/dist/{dispatch-S73TK3IY.js → dispatch-RH4LHKBH.js} +21 -21
- package/dist/{enclave-KJOTOH7X.js → enclave-27SMF6GF.js} +15 -9
- package/dist/executor-B6SRO2QS.js +36 -0
- package/dist/executor-BI5CHWRY.js +8 -0
- package/dist/executor-RBKM5W5K.js +8 -0
- package/dist/export-accessible-VMA3YM5V.js +22 -0
- package/dist/extract-partition-NF7PXGPP.js +37 -0
- package/dist/{fanout-sidecar-WUFXNYP4.js → fanout-sidecar-OT6RYLOL.js} +9 -9
- package/dist/find-LZMP26KF.js +10 -0
- package/dist/forget/index.js +9 -9
- package/dist/guards/index.js +4 -4
- package/dist/history/index.js +13 -13
- package/dist/i18n/index.js +14 -14
- package/dist/index.d.ts +29 -6
- package/dist/index.js +999 -191
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.js +13 -5
- package/dist/indexing/index.js.map +1 -1
- package/dist/introspection/index.js +11 -11
- package/dist/issue-IHARXWW5.js +18 -0
- package/dist/ivf-flat-UZ6DQDLS.js +314 -0
- package/dist/ivf-flat-UZ6DQDLS.js.map +1 -0
- package/dist/{json-schema-V3M5MUCM.js → json-schema-3SHX72CC.js} +2 -1
- package/dist/json-schema-3SHX72CC.js.map +1 -0
- package/dist/kernel/collection-config.d.ts +4 -20
- package/dist/kernel/collection.d.ts +29 -8
- package/dist/kernel/enclave/crypto.d.ts +16 -0
- package/dist/kernel/enclave/index.d.ts +1 -1
- package/dist/kernel/enclave/record-keys/record-codec.d.ts +25 -0
- package/dist/kernel/errors.d.ts +227 -36
- package/dist/kernel/generation-map.d.ts +48 -0
- package/dist/kernel/memory-store.d.ts +35 -7
- package/dist/kernel/noydb.d.ts +6 -1
- package/dist/kernel/present-cache.d.ts +58 -0
- package/dist/kernel/query/all.d.ts +7 -0
- package/dist/kernel/query/builder.d.ts +601 -270
- package/dist/kernel/query/civil-date.d.ts +64 -0
- package/dist/kernel/query/cursor.d.ts +40 -0
- package/dist/kernel/query/distinct-key.d.ts +98 -0
- package/dist/kernel/query/hydration.d.ts +76 -0
- package/dist/kernel/query/index.d.ts +31 -15
- package/dist/kernel/query/internal/core.d.ts +30 -0
- package/dist/kernel/query/internal/hooks.d.ts +22 -0
- package/dist/kernel/query/internal/maintenance.d.ts +43 -0
- package/dist/kernel/query/live/incremental.d.ts +314 -0
- package/dist/kernel/query/live/index.d.ts +22 -0
- package/dist/kernel/query/live/install.d.ts +2 -0
- package/dist/kernel/query/{live.d.ts → live/live.d.ts} +39 -9
- package/dist/kernel/query/live/methods.d.ts +107 -0
- package/dist/kernel/query/predicate.d.ts +119 -1
- package/dist/kernel/query/reduce/date-trunc.d.ts +75 -0
- package/dist/kernel/query/reduce/index.d.ts +36 -0
- package/dist/kernel/query/reduce/install.d.ts +2 -0
- package/dist/kernel/query/reduce/methods.d.ts +259 -0
- package/dist/kernel/query/reduce/scan-methods.d.ts +121 -0
- package/dist/kernel/query/relate/explain.d.ts +199 -0
- package/dist/kernel/query/relate/index.d.ts +48 -0
- package/dist/kernel/query/relate/install.d.ts +2 -0
- package/dist/kernel/query/relate/join-on.d.ts +126 -0
- package/dist/kernel/query/relate/join-reduce.d.ts +16 -0
- package/dist/kernel/query/relate/join.d.ts +452 -0
- package/dist/kernel/query/relate/methods.d.ts +426 -0
- package/dist/kernel/query/relate/partition.d.ts +87 -0
- package/dist/kernel/query/relate/scan-methods.d.ts +163 -0
- package/dist/kernel/query/relate/traverse.d.ts +109 -0
- package/dist/kernel/query/scan-builder.d.ts +79 -176
- package/dist/kernel/query/sort-key.d.ts +52 -0
- package/dist/kernel/tx-write-gate.d.ts +77 -0
- package/dist/kernel/types.d.ts +71 -3
- package/dist/kernel/validation.d.ts +27 -10
- package/dist/kernel/vault.d.ts +9 -4
- package/dist/kernel/via/compose.d.ts +3 -0
- package/dist/kernel/via/index.d.ts +56 -0
- package/dist/kernel/via/pipeline.d.ts +48 -0
- package/dist/{ledger-F33ZVMTK.js → ledger-KZPCMVZY.js} +11 -11
- package/dist/liberate-GRLCZMW4.js +24 -0
- package/dist/link-set-JCMJ7ULP.js +30 -0
- package/dist/{managed-secret-PQYKSR6T.js → managed-secret-S3MQ6SH6.js} +10 -10
- package/dist/materialized-views/index.js +30 -23
- package/dist/{migrate-cek-5ENH64AA.js → migrate-cek-V7GKJZ7O.js} +2 -2
- package/dist/money/index.js +5 -5
- package/dist/noydb-SWQJ3MZS.js +97 -0
- package/dist/overlay-views/index.js +4 -4
- package/dist/periods/index.js +12 -12
- package/dist/pod/index.js +75 -68
- package/dist/{pod-handle-Q736KYON.js → pod-handle-WGDFB4C2.js} +9 -9
- package/dist/policy/index.js +10 -10
- package/dist/port/with/blob-strategy.d.ts +9 -0
- package/dist/port/with/collection-options.d.ts +5 -0
- package/dist/port/with/coverage-strategy.d.ts +83 -0
- package/dist/port/with/service-bus.d.ts +46 -0
- package/dist/port/with/strategies.d.ts +2 -0
- package/dist/portability/index.js +8 -8
- package/dist/{post-register-LNE2FE2A.js → post-register-SLSXIPBV.js} +6 -6
- package/dist/query/all/index.js +31 -0
- package/dist/query/index.js +20 -32
- package/dist/query/live/index.js +21 -0
- package/dist/query/live/index.js.map +1 -0
- package/dist/query/reduce/index.js +33 -0
- package/dist/query/reduce/index.js.map +1 -0
- package/dist/query/relate/index.js +56 -0
- package/dist/query/relate/index.js.map +1 -0
- package/dist/reduce/index.js +72 -18
- package/dist/reduce/index.js.map +1 -1
- package/dist/register-F3OYE7MJ.js +31 -0
- package/dist/registry-CILQFXFK.js +8 -0
- package/dist/registry-XNNYQVKV.js +8 -0
- package/dist/registry-YCDZV3VW.js +35 -0
- package/dist/registry-ZGYS33TE.js +24 -0
- package/dist/request-withdrawal-Z5NC6TXT.js +30 -0
- package/dist/{reveal-67RDQRIN.js → reveal-4BCKPEC2.js} +6 -6
- package/dist/revoke-ZL62WC2D.js +23 -0
- package/dist/satellites/index.js +1 -1
- package/dist/schema-update/index.js +2 -2
- package/dist/sealed-record/index.js +12 -12
- package/dist/search/index.js +11 -5
- package/dist/{seed-G42JYN56.js → seed-4JS2RJ4A.js} +12 -12
- package/dist/sequence/index.js +9 -9
- package/dist/session/index.js +9 -9
- package/dist/shadow/index.js +2 -2
- package/dist/signer-MBZZMXTC.js +24 -0
- package/dist/snapshots/index.js +10 -10
- package/dist/{stale-DDL7WQEZ.js → stale-TJ3DYZMQ.js} +10 -10
- package/dist/storage-SVMKRGCW.js +22 -0
- package/dist/{store-coordination-provider-MEQ2MJQG.js → store-coordination-provider-2O2VUHKW.js} +10 -10
- package/dist/sync/index.js +10 -10
- package/dist/team/index.js +17 -15
- package/dist/tiers/index.js +13 -11
- package/dist/to/index.js +2 -2
- package/dist/transactions/index.js +3 -3
- package/dist/util/index.js +1 -1
- package/dist/vault-head/index.js +8 -8
- package/dist/{verify-KOV2G5D6.js → verify-RVT67IMH.js} +5 -5
- package/dist/via/blob/index.d.ts +1 -0
- package/dist/via/geo/binding.d.ts +31 -0
- package/dist/via/geo/descriptor.d.ts +54 -0
- package/dist/via/geo/geohash.d.ts +95 -0
- package/dist/via/geo/index.d.ts +13 -0
- package/dist/via/geo/where.d.ts +50 -0
- package/dist/via/i18n/core.d.ts +1 -1
- package/dist/via/lookup/registry.d.ts +1 -1
- package/dist/via/lookup/snapshot.d.ts +3 -3
- package/dist/walk-HJ42EISE.js +21 -0
- package/dist/walk-HJ42EISE.js.map +1 -0
- package/dist/with-audit/coverage/accounting.d.ts +112 -0
- package/dist/with-audit/coverage/active.d.ts +29 -0
- package/dist/with-audit/coverage/index.d.ts +61 -0
- package/dist/with-audit/coverage/sketch.d.ts +75 -0
- package/dist/with-audit/periods/periods.d.ts +55 -7
- package/dist/with-audit/periods/reserved-collections.d.ts +3 -0
- package/dist/with-audit/periods/vault-facade.d.ts +17 -0
- package/dist/with-audit/tiers/index.d.ts +51 -0
- package/dist/with-audit/tiers/strategy.d.ts +8 -0
- package/dist/with-cargo/floor.d.ts +2 -2
- package/dist/with-commit/tx/invariants.d.ts +1 -1
- package/dist/with-commit/tx/transaction.d.ts +13 -3
- package/dist/with-formula/materialized-views/dependency-analyzer.d.ts +4 -3
- package/dist/with-formula/materialized-views/emit-memo.d.ts +40 -0
- package/dist/with-formula/materialized-views/executor.d.ts +8 -0
- package/dist/with-formula/materialized-views/index.d.ts +1 -1
- package/dist/with-formula/materialized-views/registry.d.ts +15 -0
- package/dist/with-formula/materialized-views/types.d.ts +106 -14
- package/dist/with-formula/materialized-views/window-stage.d.ts +11 -0
- package/dist/with-formula/overlay-views/types.d.ts +1 -1
- package/dist/with-formula/overlay-views/virtual-collection.d.ts +13 -4
- package/dist/with-lookup/embeddings/chunks.d.ts +79 -0
- package/dist/with-lookup/embeddings/descriptor.d.ts +21 -0
- package/dist/with-lookup/embeddings/index.d.ts +3 -0
- package/dist/with-lookup/embeddings/ivf-flat.d.ts +93 -0
- package/dist/with-lookup/embeddings/vector-index.d.ts +73 -0
- package/dist/with-lookup/embeddings/vector-set.d.ts +94 -1
- package/dist/with-lookup/embeddings/with-vector-index.d.ts +67 -0
- package/dist/with-lookup/indexing/collection-facade.d.ts +85 -2
- package/dist/with-lookup/indexing/compound-indexes.d.ts +129 -0
- package/dist/with-lookup/indexing/eager-indexes.d.ts +170 -1
- package/dist/with-lookup/indexing/index-snapshot.d.ts +62 -0
- package/dist/with-lookup/indexing/lazy-builder.d.ts +33 -0
- package/dist/with-lookup/indexing/persisted-field-indexes.d.ts +104 -0
- package/dist/with-lookup/indexing/persisted-indexes.d.ts +12 -0
- package/dist/with-lookup/indexing/sorted-indexes.d.ts +117 -0
- package/dist/with-lookup/indexing/unique-constraints.d.ts +106 -23
- package/dist/with-lookup/reduce/active.d.ts +2 -24
- package/dist/with-lookup/reduce/group-core.d.ts +43 -0
- package/dist/with-lookup/reduce/groupby.d.ts +195 -36
- package/dist/with-lookup/reduce/incremental-group.d.ts +153 -0
- package/dist/with-lookup/reduce/index.d.ts +26 -5
- package/dist/with-lookup/reduce/reducers.d.ts +255 -1
- package/dist/with-lookup/reduce/reduction.d.ts +44 -13
- package/dist/with-lookup/reduce/strategy.d.ts +43 -3
- package/dist/with-lookup/reduce/window.d.ts +262 -0
- package/dist/with-lookup/search/collection-facade.d.ts +44 -8
- package/dist/with-lookup/search/index-store.d.ts +5 -3
- package/dist/with-lookup/search/index.d.ts +4 -2
- package/dist/with-lookup/search/inverted-index.d.ts +38 -2
- package/dist/with-lookup/search/persisted-index-store.d.ts +5 -2
- package/dist/with-lookup/search/phrase.d.ts +28 -0
- package/dist/with-lookup/search/retrieve-types.d.ts +57 -1
- package/dist/with-lookup/search/serialize.d.ts +29 -3
- package/dist/with-lookup/search/strategy.d.ts +2 -6
- package/dist/with-party/directory/cover/index.d.ts +1 -1
- package/dist/with-party/directory/cover/storage.d.ts +1 -1
- package/dist/with-party/directory/cover/types.d.ts +1 -1
- package/dist/with-party/directory/index.d.ts +1 -1
- package/dist/with-party/directory/types.d.ts +1 -1
- package/dist/with-party/directory/user-envelope/api.d.ts +1 -1
- package/dist/with-party/policy/engine.d.ts +1 -1
- package/dist/with-party/policy/index.d.ts +1 -1
- package/dist/with-party/policy/presets.d.ts +1 -1
- package/dist/with-party/team/authenticators.d.ts +91 -1
- package/dist/with-party/team/index.d.ts +3 -2
- package/dist/with-party/team/keyring.d.ts +13 -6
- package/dist/with-party/team/managed-secret.d.ts +1 -1
- package/dist/with-party/team/noydb-facade.d.ts +12 -3
- package/dist/with-party/team/wrapped-deks.d.ts +4 -4
- package/dist/with-pod/format.d.ts +1 -1
- package/dist/with-pod/pod.d.ts +5 -5
- package/dist/with-shape/blobs/blob-compaction.d.ts +16 -0
- package/dist/with-shape/blobs/blob-set.d.ts +21 -2
- package/dist/with-shape/blobs/legacy-sweep.d.ts +35 -0
- package/dist/with-shape/introspection/describe.d.ts +6 -0
- package/dist/with-shape/introspection/field-meta.d.ts +18 -0
- package/dist/with-shape/introspection/types.d.ts +57 -1
- package/dist/with-shape/links/vault-facade.d.ts +1 -1
- package/dist/with-store/partitioned/index.d.ts +337 -0
- package/dist/with-sync/change-broadcast.d.ts +153 -0
- package/dist/withdraw-accessible-5NZUPAVA.js +27 -0
- package/dist/withdraw-accessible-5NZUPAVA.js.map +1 -0
- package/package.json +37 -10
- package/dist/api-TXFOVOXX.js +0 -19
- package/dist/backup-IJTB4S4M.js.map +0 -1
- package/dist/chunk-2577DKXB.js.map +0 -1
- package/dist/chunk-2C3AQB46.js.map +0 -1
- package/dist/chunk-4GVM6QZA.js.map +0 -1
- package/dist/chunk-5WDXHNSW.js.map +0 -1
- package/dist/chunk-6GOE5PGN.js.map +0 -1
- package/dist/chunk-6X6SEMEE.js.map +0 -1
- package/dist/chunk-77K4PG6P.js.map +0 -1
- package/dist/chunk-7MG2LHO5.js +0 -1616
- package/dist/chunk-7MG2LHO5.js.map +0 -1
- package/dist/chunk-7MG7SJT7.js.map +0 -1
- package/dist/chunk-7O3Y4PPD.js.map +0 -1
- package/dist/chunk-AO42KJO6.js.map +0 -1
- package/dist/chunk-ATL3ZVXT.js +0 -151
- package/dist/chunk-ATL3ZVXT.js.map +0 -1
- package/dist/chunk-BLN54W3L.js.map +0 -1
- package/dist/chunk-BM5TWITZ.js.map +0 -1
- package/dist/chunk-CUZLNGD5.js.map +0 -1
- package/dist/chunk-EQEYOJBM.js.map +0 -1
- package/dist/chunk-G56KBWFY.js.map +0 -1
- package/dist/chunk-I3ISCYT7.js +0 -159
- package/dist/chunk-I3ISCYT7.js.map +0 -1
- package/dist/chunk-KG7TZKZG.js.map +0 -1
- package/dist/chunk-KJIMGIWO.js.map +0 -1
- package/dist/chunk-KTFRCC5C.js.map +0 -1
- package/dist/chunk-KUX3OB6D.js.map +0 -1
- package/dist/chunk-MUM7IANK.js.map +0 -1
- package/dist/chunk-NA2NIJZI.js.map +0 -1
- package/dist/chunk-NSITFOCJ.js +0 -99
- package/dist/chunk-NSITFOCJ.js.map +0 -1
- package/dist/chunk-PT43Y56J.js.map +0 -1
- package/dist/chunk-QIBVIOXO.js.map +0 -1
- package/dist/chunk-RRNRNANG.js.map +0 -1
- package/dist/chunk-RTBJ5IOO.js +0 -87
- package/dist/chunk-RTBJ5IOO.js.map +0 -1
- package/dist/chunk-SKGMN35F.js.map +0 -1
- package/dist/chunk-SL3JAAKN.js.map +0 -1
- package/dist/chunk-SPXXX5E4.js.map +0 -1
- package/dist/chunk-SPYWXKJN.js.map +0 -1
- package/dist/chunk-U4FOQDY4.js.map +0 -1
- package/dist/chunk-UXVWEX5L.js.map +0 -1
- package/dist/chunk-UXWB3FPF.js.map +0 -1
- package/dist/chunk-YMUFFJCO.js.map +0 -1
- package/dist/chunk-ZN4FD4PE.js +0 -364
- package/dist/chunk-ZN4FD4PE.js.map +0 -1
- package/dist/chunk-ZORTV6NH.js.map +0 -1
- package/dist/collection-facade-4GRREZ6C.js +0 -45
- package/dist/derive-6O5SWQKN.js +0 -21
- package/dist/executor-5NIMLSKZ.js +0 -8
- package/dist/executor-AQVXV5GZ.js +0 -8
- package/dist/executor-PUAO6AKJ.js +0 -29
- package/dist/export-accessible-72EQNBMR.js +0 -22
- package/dist/extract-partition-PUYNU3UF.js +0 -37
- package/dist/find-2ABFCZX4.js +0 -10
- package/dist/issue-SIDORTSZ.js +0 -18
- package/dist/json-schema-V3M5MUCM.js.map +0 -1
- package/dist/kernel/query/join.d.ts +0 -232
- package/dist/liberate-LCDPNAG2.js +0 -24
- package/dist/link-set-FKNWAYKS.js +0 -30
- package/dist/noydb-DUXZPKSN.js +0 -90
- package/dist/register-I2DZXGAR.js +0 -31
- package/dist/registry-23WF3TGH.js +0 -8
- package/dist/registry-4AYVUNXK.js +0 -8
- package/dist/registry-VSSQPIGF.js +0 -18
- package/dist/registry-YLEWMGAC.js +0 -32
- package/dist/request-withdrawal-KFQHEZ25.js +0 -30
- package/dist/revoke-DQDZAZZM.js +0 -23
- package/dist/signer-WAMDZHEN.js +0 -24
- package/dist/storage-FLR73HHF.js +0 -22
- package/dist/walk-3UQBRMDV.js +0 -21
- package/dist/withdraw-accessible-EL5GXZBS.js +0 -27
- /package/dist/{api-TXFOVOXX.js.map → api-GVYEKSJE.js.map} +0 -0
- /package/dist/{chunk-BUE2D7PX.js.map → chunk-32YV3TJT.js.map} +0 -0
- /package/dist/{chunk-3RCJJBNR.js.map → chunk-3ELRMVJK.js.map} +0 -0
- /package/dist/{chunk-6GMZ7HEE.js.map → chunk-3F7GLNAE.js.map} +0 -0
- /package/dist/{chunk-2Y4IMKA6.js.map → chunk-3H5KLDZN.js.map} +0 -0
- /package/dist/{chunk-VTFVLCUH.js.map → chunk-3JWHAR6E.js.map} +0 -0
- /package/dist/{chunk-JBDFMYKQ.js.map → chunk-4AF3TUMP.js.map} +0 -0
- /package/dist/{chunk-J6FXAEUG.js.map → chunk-6B3VNR4P.js.map} +0 -0
- /package/dist/{chunk-TUFM4UZP.js.map → chunk-6K7DPM7D.js.map} +0 -0
- /package/dist/{chunk-K6LHRFBL.js.map → chunk-6LMQ5R26.js.map} +0 -0
- /package/dist/{chunk-XLMEZIAJ.js.map → chunk-6OWXYZK2.js.map} +0 -0
- /package/dist/{chunk-BOWNCWZT.js.map → chunk-6QSQQLSE.js.map} +0 -0
- /package/dist/{chunk-NQKIUNW5.js.map → chunk-A6IJU4TK.js.map} +0 -0
- /package/dist/{chunk-QIZPZMUI.js.map → chunk-AOWGXU6D.js.map} +0 -0
- /package/dist/{chunk-SFAWCTEM.js.map → chunk-B57AFBY2.js.map} +0 -0
- /package/dist/{chunk-GPEQUXHL.js.map → chunk-B6SJIBQK.js.map} +0 -0
- /package/dist/{chunk-Q2PU57HC.js.map → chunk-BK2HHQGN.js.map} +0 -0
- /package/dist/{collection-facade-4GRREZ6C.js.map → chunk-BKT776R5.js.map} +0 -0
- /package/dist/{chunk-FXBIP2WD.js.map → chunk-BUDAGWL5.js.map} +0 -0
- /package/dist/{chunk-BOET4NA4.js.map → chunk-CKNGS5MH.js.map} +0 -0
- /package/dist/{chunk-YSRUYAVU.js.map → chunk-DE4DH2RZ.js.map} +0 -0
- /package/dist/{chunk-6I2RQCET.js.map → chunk-DF73H4UN.js.map} +0 -0
- /package/dist/{chunk-K7NTTA6P.js.map → chunk-EOVMT6TB.js.map} +0 -0
- /package/dist/{chunk-XTXOCYOJ.js.map → chunk-FCKWQE2H.js.map} +0 -0
- /package/dist/{chunk-4OZNXTO4.js.map → chunk-FN5EBVTQ.js.map} +0 -0
- /package/dist/{chunk-NKHRDYOR.js.map → chunk-FQ5VRHIB.js.map} +0 -0
- /package/dist/{chunk-FAHMLERK.js.map → chunk-FW22ZCXT.js.map} +0 -0
- /package/dist/{chunk-RMS3NEA3.js.map → chunk-HT2AHGQI.js.map} +0 -0
- /package/dist/{chunk-CHJBLCOG.js.map → chunk-HVSCEIYL.js.map} +0 -0
- /package/dist/{chunk-GY4WYNNG.js.map → chunk-ID6Z6E66.js.map} +0 -0
- /package/dist/{chunk-EYBQEIUY.js.map → chunk-IEX7NK2K.js.map} +0 -0
- /package/dist/{chunk-KUJDV5QH.js.map → chunk-ISZQJBML.js.map} +0 -0
- /package/dist/{chunk-LWBCUYZW.js.map → chunk-IT22PWKV.js.map} +0 -0
- /package/dist/{chunk-7V4TP3AF.js.map → chunk-J6UOHJY5.js.map} +0 -0
- /package/dist/{chunk-JMGDHKQF.js.map → chunk-JCN44NCY.js.map} +0 -0
- /package/dist/{chunk-CARABLSQ.js.map → chunk-K6W7BVZ6.js.map} +0 -0
- /package/dist/{chunk-453F654R.js.map → chunk-L7DOS3TE.js.map} +0 -0
- /package/dist/{chunk-WEQEYLCS.js.map → chunk-LGRLME4M.js.map} +0 -0
- /package/dist/{chunk-RMF67KCG.js.map → chunk-LQ4ZHOKP.js.map} +0 -0
- /package/dist/{chunk-ZVO4QVPD.js.map → chunk-M522UYZ2.js.map} +0 -0
- /package/dist/{chunk-2N4GY74U.js.map → chunk-MTL7MXGR.js.map} +0 -0
- /package/dist/{chunk-TFR7DSOV.js.map → chunk-MVZ2TPVP.js.map} +0 -0
- /package/dist/{chunk-CPWQB3N5.js.map → chunk-N5DNADLQ.js.map} +0 -0
- /package/dist/{chunk-ELNXJMGB.js.map → chunk-NNARWO72.js.map} +0 -0
- /package/dist/{chunk-6YXCPL4T.js.map → chunk-PDCG6PR5.js.map} +0 -0
- /package/dist/{chunk-REDOOY2Q.js.map → chunk-PSIV7CJV.js.map} +0 -0
- /package/dist/{chunk-ANATMFIO.js.map → chunk-QWFHIOD2.js.map} +0 -0
- /package/dist/{chunk-X2DLENDQ.js.map → chunk-QWGU66MP.js.map} +0 -0
- /package/dist/{chunk-BSFNRLK2.js.map → chunk-S57HTZ6X.js.map} +0 -0
- /package/dist/{chunk-SZJ3DLZA.js.map → chunk-SEE332EV.js.map} +0 -0
- /package/dist/{chunk-YYP7WSEB.js.map → chunk-SI3V7D4G.js.map} +0 -0
- /package/dist/{chunk-UGTBXF5U.js.map → chunk-SOIQ4MM4.js.map} +0 -0
- /package/dist/{chunk-VXGSWL6L.js.map → chunk-SYVZW77S.js.map} +0 -0
- /package/dist/{chunk-MVMXZYXT.js.map → chunk-TKUOEU2F.js.map} +0 -0
- /package/dist/{chunk-SMH6SRX7.js.map → chunk-TROR3ALR.js.map} +0 -0
- /package/dist/{chunk-DGOZ4UZD.js.map → chunk-VNNRUGMM.js.map} +0 -0
- /package/dist/{chunk-HDOU6I2Y.js.map → chunk-VNPEWFPD.js.map} +0 -0
- /package/dist/{chunk-L7CIOOBW.js.map → chunk-WNJ4AVGE.js.map} +0 -0
- /package/dist/{chunk-FMZY5Q6L.js.map → chunk-WX5ADGTZ.js.map} +0 -0
- /package/dist/{chunk-Q7HZX3IF.js.map → chunk-X4773RBR.js.map} +0 -0
- /package/dist/{chunk-ANSIZYEG.js.map → chunk-YNIUIERI.js.map} +0 -0
- /package/dist/{chunk-BZ3534YM.js.map → chunk-ZUVBC32X.js.map} +0 -0
- /package/dist/{chunk-L742JPKZ.js.map → chunk-ZXABPVM6.js.map} +0 -0
- /package/dist/{classified-marker-LI2SF2E4.js.map → classified-marker-W3USLL5M.js.map} +0 -0
- /package/dist/{computed-VPOKQK4N.js.map → collection-facade-QU6IJBN3.js.map} +0 -0
- /package/dist/{derive-6O5SWQKN.js.map → computed-TPFBX7U4.js.map} +0 -0
- /package/dist/{dead-filter-TSWZQRR5.js.map → dead-filter-U2UYKSZL.js.map} +0 -0
- /package/dist/{delegation-OVIFFPG4.js.map → delegation-S3AEPW2T.js.map} +0 -0
- /package/dist/{enclave-KJOTOH7X.js.map → derive-OX7F6TGG.js.map} +0 -0
- /package/dist/{dispatch-WC6X2M6C.js.map → dispatch-3CQDJV5Q.js.map} +0 -0
- /package/dist/{dispatch-S73TK3IY.js.map → dispatch-RH4LHKBH.js.map} +0 -0
- /package/dist/{executor-5NIMLSKZ.js.map → enclave-27SMF6GF.js.map} +0 -0
- /package/dist/{executor-AQVXV5GZ.js.map → executor-B6SRO2QS.js.map} +0 -0
- /package/dist/{executor-PUAO6AKJ.js.map → executor-BI5CHWRY.js.map} +0 -0
- /package/dist/{export-accessible-72EQNBMR.js.map → executor-RBKM5W5K.js.map} +0 -0
- /package/dist/{extract-partition-PUYNU3UF.js.map → export-accessible-VMA3YM5V.js.map} +0 -0
- /package/dist/{find-2ABFCZX4.js.map → extract-partition-NF7PXGPP.js.map} +0 -0
- /package/dist/{fanout-sidecar-WUFXNYP4.js.map → fanout-sidecar-OT6RYLOL.js.map} +0 -0
- /package/dist/{issue-SIDORTSZ.js.map → find-LZMP26KF.js.map} +0 -0
- /package/dist/{ledger-F33ZVMTK.js.map → issue-IHARXWW5.js.map} +0 -0
- /package/dist/{liberate-LCDPNAG2.js.map → ledger-KZPCMVZY.js.map} +0 -0
- /package/dist/{link-set-FKNWAYKS.js.map → liberate-GRLCZMW4.js.map} +0 -0
- /package/dist/{managed-secret-PQYKSR6T.js.map → link-set-JCMJ7ULP.js.map} +0 -0
- /package/dist/{noydb-DUXZPKSN.js.map → managed-secret-S3MQ6SH6.js.map} +0 -0
- /package/dist/{migrate-cek-5ENH64AA.js.map → migrate-cek-V7GKJZ7O.js.map} +0 -0
- /package/dist/{register-I2DZXGAR.js.map → noydb-SWQJ3MZS.js.map} +0 -0
- /package/dist/{pod-handle-Q736KYON.js.map → pod-handle-WGDFB4C2.js.map} +0 -0
- /package/dist/{post-register-LNE2FE2A.js.map → post-register-SLSXIPBV.js.map} +0 -0
- /package/dist/{registry-23WF3TGH.js.map → query/all/index.js.map} +0 -0
- /package/dist/{registry-4AYVUNXK.js.map → register-F3OYE7MJ.js.map} +0 -0
- /package/dist/{registry-VSSQPIGF.js.map → registry-CILQFXFK.js.map} +0 -0
- /package/dist/{registry-YLEWMGAC.js.map → registry-XNNYQVKV.js.map} +0 -0
- /package/dist/{request-withdrawal-KFQHEZ25.js.map → registry-YCDZV3VW.js.map} +0 -0
- /package/dist/{revoke-DQDZAZZM.js.map → registry-ZGYS33TE.js.map} +0 -0
- /package/dist/{signer-WAMDZHEN.js.map → request-withdrawal-Z5NC6TXT.js.map} +0 -0
- /package/dist/{reveal-67RDQRIN.js.map → reveal-4BCKPEC2.js.map} +0 -0
- /package/dist/{stale-DDL7WQEZ.js.map → revoke-ZL62WC2D.js.map} +0 -0
- /package/dist/{seed-G42JYN56.js.map → seed-4JS2RJ4A.js.map} +0 -0
- /package/dist/{storage-FLR73HHF.js.map → signer-MBZZMXTC.js.map} +0 -0
- /package/dist/{walk-3UQBRMDV.js.map → stale-TJ3DYZMQ.js.map} +0 -0
- /package/dist/{withdraw-accessible-EL5GXZBS.js.map → storage-SVMKRGCW.js.map} +0 -0
- /package/dist/{store-coordination-provider-MEQ2MJQG.js.map → store-coordination-provider-2O2VUHKW.js.map} +0 -0
- /package/dist/{verify-KOV2G5D6.js.map → verify-RVT67IMH.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/kernel/query/relate/join-reduce.ts","../src/kernel/query/reduce/methods.ts","../src/kernel/query/reduce/scan-methods.ts","../src/kernel/query/reduce/install.ts"],"sourcesContent":["/**\n * Reducing over a JOIN ALIAS (#1338).\n *\n * `aggregate()` and `groupBy()` used to refuse any field rooted at a join\n * alias. The refusal's own doc named the real blocker, and it was never \"the\n * legs do not run\" — running them is the easy half. It was that the Via\n * pipeline is LEFT-SCOPED: `Query.aggregate()` calls\n * `this.source.via.wrapReducers(spec)`, which resolves every reducer's field\n * against the LEFT collection's map. For `sum('client.balance')` that map has\n * no entry, so two things silently did not happen — money's exact-BigInt\n * rewrite was skipped (a generic sum over stored scaled-integer strings, which\n * `readNumber` coerces to 0), and `refuseUnqueryableReducers` never fired, so\n * the `queryable: 'none'` gate that would refuse a blob field never applied.\n *\n * This module supplies the missing half as a Via BINDING rather than as a\n * special case threaded through the reduce strategy. A binding composes: the\n * pipeline handed to `GroupedQuery`/`aggregate()` is the left one plus this,\n * so every existing call site keeps its exact behaviour and the alias case\n * arrives through the same `wrapReducers` door as everything else.\n *\n * Two properties make that safe:\n *\n * - **It delegates, it does not reimplement.** An aliased reducer is handed\n * to the RIGHT collection's OWN pipeline under its BARE field name — which\n * is how a pipeline is keyed (that is the whole shape of #1335) — so the\n * right side's rewrite AND its posture gate apply together, from one call.\n * - **`covers()` returns false**, so the binding is inert for `postureFor`,\n * `present`, `decodeResults` and every other phase. It exists for exactly\n * one hook.\n */\nimport type { NoydbVia } from '../../via/index.js'\nimport { ViaPipeline } from '../../via/pipeline.js'\n\n/**\n * The reducer shape this file needs, declared STRUCTURALLY rather than\n * imported from `with-lookup/reduce/`.\n *\n * Not a style choice: `check-architecture.mjs`'s port-layering guard refuses a\n * NEW static kernel→`with-*` import (its allowlist is a frozen per-file, per-\n * specifier baseline, and adding a row would be the thing the freeze exists to\n * stop). `NoydbVia.wrapReducers` is declared `(spec: unknown) => unknown`\n * precisely so a binding never has to name the service's types, and these\n * three members are all this file touches — every reducer that reaches it was\n * built by the reduce service and keeps every other member intact through the\n * spread below.\n */\ninterface AliasReducer {\n readonly field?: string\n init(): unknown\n step(state: unknown, record: unknown): unknown\n remove?(state: unknown, record: unknown): unknown\n finalize(state: unknown): unknown\n}\ntype AliasSpec = Readonly<Record<string, AliasReducer>>\n\n/**\n * Re-aim a reducer at the sub-record under `alias`.\n *\n * The right side's pipeline rewrote the reducer for the BARE field name, so\n * its `step`/`remove` read `credit` off whatever record they are handed. On a\n * joined row that value lives at `row.client.credit`, so the row is unwrapped\n * one level on the way in rather than the field path being rewritten — the\n * rewritten reducer closes over its field name internally, so re-pointing the\n * `.field` property afterwards would change the label and not the read.\n *\n * An unmatched left row carries `null` under the alias; every reducer's read\n * already treats a nullish record as \"no value here\", so it contributes\n * nothing — the same thing a missing left-side field does.\n */\nfunction liftToAlias(alias: string, path: string, inner: AliasReducer): AliasReducer {\n const under = (record: unknown): unknown =>\n record === null || typeof record !== 'object' ? undefined : (record as Record<string, unknown>)[alias]\n return {\n ...inner,\n // The dotted path, so anything reading `.field` for a label or a\n // dependency still sees the field the CALLER named.\n field: path,\n init: () => inner.init(),\n step: (state, record) => inner.step(state, under(record)),\n ...(inner.remove ? { remove: (state: unknown, record: unknown) => inner.remove!(state, under(record)) } : {}),\n finalize: (state) => inner.finalize(state),\n }\n}\n\n/**\n * The binding: rewrite every reducer whose field is rooted at a join alias\n * through that alias's own collection pipeline, and leave everything else\n * exactly as it arrived.\n *\n * A reducer the right side does NOT rewrite (a plain `avg('client.score')`)\n * is returned untouched and keeps reading the dotted path off the joined row,\n * which is already correct — only a REWRITTEN reducer needs lifting, because\n * only a rewritten one has swapped to the bare name.\n */\nfunction joinAliasBinding(aliasVia: ReadonlyMap<string, ViaPipeline>): NoydbVia {\n return {\n brand: 'join-alias',\n posture: { encryptedAtRest: 'envelope', queryable: 'full', exportable: true, forgettable: true },\n // Claims no field: this binding must never shadow a left-side posture.\n covers: () => false,\n wrapReducers: (spec) => {\n let changed = false\n const out: Record<string, AliasReducer> = {}\n for (const [key, reducer] of Object.entries(spec as AliasSpec)) {\n const field = reducer.field\n const dot = field === undefined ? -1 : field.indexOf('.')\n const via = dot > 0 ? aliasVia.get(field!.slice(0, dot)) : undefined\n if (!via) { out[key] = reducer; continue }\n const bare = field!.slice(dot + 1)\n // One call: the right side's rewrite and its `queryable: 'none'`\n // refusal, both keyed by the bare name it actually declares.\n const wrapped = via.wrapReducers({ [key]: { ...reducer, field: bare } } as AliasSpec)[key]!\n if (wrapped === reducer || wrapped.field !== bare) { out[key] = reducer; continue }\n changed = true\n out[key] = liftToAlias(field!.slice(0, dot), field!, wrapped)\n }\n return changed ? out : spec\n },\n }\n}\n\n/**\n * The pipeline `aggregate()` / `groupBy()` should reduce THIS plan with: the\n * source's own, plus the alias binding when the plan can produce an alias.\n *\n * Returns the left pipeline unchanged when there is no alias to serve, so a\n * query with no joins — every query written before #1338 — is handed exactly\n * the object it was handed before.\n */\nexport function reduceViaFor(\n leftVia: ViaPipeline | undefined,\n aliasVia: ReadonlyMap<string, ViaPipeline> | undefined,\n): ViaPipeline | undefined {\n if (aliasVia === undefined || aliasVia.size === 0) return leftVia\n return ViaPipeline.build([...(leftVia?.bindings ?? []), joinAliasBinding(aliasVia)], leftVia?.taint)\n}\n\n/**\n * The other half, and the reason this one is not merely a missing feature:\n * a binding that REFUSES an aliased reducer instead of rewriting it.\n *\n * It is installed on the path where the legs do NOT run —\n * `groupBy('status').aggregate({ x: sum('client.credit') })`, a group key over\n * a left field with a reducer over an alias. `Query.aggregate()`'s guard never\n * saw that spec (the grouped terminal is `GroupedQuery.aggregate`, one object\n * along the chain), so it reduced `undefined` for every row and returned a\n * confident `0`. Group by the alias — then the legs run and the reducer is\n * rewritten by the branch above — or drop the grouping.\n */\nfunction refuseAliasBinding(aliases: ReadonlySet<string>): NoydbVia {\n return {\n brand: 'join-alias-refuse',\n posture: { encryptedAtRest: 'envelope', queryable: 'full', exportable: true, forgettable: true },\n covers: () => false,\n wrapReducers: (spec) => {\n for (const reducer of Object.values(spec as AliasSpec)) {\n const field = reducer.field\n if (field === undefined) continue\n const head = field.split('.')[0]!\n if (!aliases.has(head)) continue\n throw new Error(\n `Query.groupBy().aggregate(): reducer field \"${field}\" addresses the join alias ` +\n `\"${head}\", but the GROUP KEY does not — so the join legs are not applied for ` +\n `this grouping and the reducer would fold undefined into every bucket. ` +\n `Group by a field on \"${head}\" (then the legs run), or aggregate without ` +\n `grouping, where an aliased reducer is applied directly.`,\n )\n }\n return spec\n },\n }\n}\n\n/**\n * The pipeline for a grouped plan whose GROUP KEY stays on the left: the\n * source's own, plus the refusal above so an aliased reducer is a loud error\n * rather than a confident zero. No aliases in the plan → unchanged.\n */\nexport function refuseAliasReduceVia(\n leftVia: ViaPipeline | undefined,\n aliases: ReadonlySet<string>,\n): ViaPipeline | undefined {\n if (aliases.size === 0) return leftVia\n return ViaPipeline.build([...(leftVia?.bindings ?? []), refuseAliasBinding(aliases)], leftVia?.taint)\n}\n","/**\n * #1458 — **Reduce**: a fold over a result set.\n *\n * `aggregate` · `groupBy` · `window` · `distinct`\n *\n * Bodies moved out of `kernel/query/builder.ts` unchanged; see\n * `../relate/methods.ts` for why `this.plan` still resolves.\n *\n * ⚠️ This group depends on TWO others, and both edges are real rather than\n * tidyable:\n * - **Live**, for `incrementalMaintainer` — a live reduction maintains its\n * state through Live's maintainer (see `../internal/maintenance.ts`);\n * - **Relate**, for `reduceViaFor` / `refuseAliasReduceVia` — a reducer over\n * a JOIN ALIAS has to resolve the right side's Via pipeline, and refusing\n * it correctly is #1338's whole subject.\n * Neither is a layering accident: both are what \"reduce over a joined, live\n * result\" means.\n */\nimport type { DeclaredPredicate, InternalSource, QueryPlan } from '../builder.js'\n// @internal Find helpers. A reduce terminal narrows its record set through the\n// SAME executor the projection terminals use — sharing the functions is what\n// keeps \"which records does this terminal see\" one definition (#1347).\nimport { buildDictLabelResolver, candidateRecords, executeClausePipeline, filterRecords, fnViewDecoder } from '../builder.js'\nimport { readPath } from '../predicate.js'\nimport type { SourceChange } from '../live/incremental.js'\nimport type { QueryField } from '../../types.js'\nimport type { ViaPipeline } from '../../via/pipeline.js'\nimport type { JoinContext, JoinLeg } from '../relate/join.js'\nimport { reduceViaFor, refuseAliasReduceVia } from '../relate/join-reduce.js'\nimport type { DateTruncKey, GroupKey } from './date-trunc.js'\nimport { groupKeyName, isDateTruncKey, projectDateTruncKeys } from './date-trunc.js'\nimport { distinctKeyOf } from '../distinct-key.js'\nimport type { LiveMaintainer, GroupMaintenanceSource } from '../live/incremental.js'\nimport type { ReduceSpec, ReduceResult, ReductionUpstream, Reduction } from '../../../with-lookup/reduce/reduction.js'\nimport type { ReducerBuilder } from '../../../with-lookup/reduce/reducers.js'\nimport { bindDistinctReducers, reducerBuilder } from '../../../with-lookup/reduce/reducers.js'\nimport type { GroupedQuery, GroupedQueryN } from '../../../with-lookup/reduce/groupby.js'\nimport type { ReduceStrategy, WindowSpec, WindowedQuery } from '../../../with-lookup/reduce/strategy.js'\nimport { FieldNotQueryableError } from '../../errors.js'\n\nfunction assertNoJoinAliasField(\n fields: readonly string[],\n joins: readonly JoinLeg[],\n terminal: string,\n): void {\n if (joins.length === 0) return\n const aliases = new Set(joins.map(leg => leg.as))\n for (const field of fields) {\n const head = field.split('.')[0]!\n if (!aliases.has(head)) continue\n throw new Error(\n `Query.${terminal}(): field \"${field}\" addresses the join alias \"${head}\", but ` +\n `${terminal}() does not apply join legs — it would silently group every row ` +\n `under undefined. Joined aggregation is not supported. Either aggregate over ` +\n `the left collection's own fields, or use .crossJoin(\"${head}\", { as: … }), ` +\n `whose expansion IS visible to ${terminal}().`,\n )\n }\n}\n\n/** @internal — the mixin whose prototype `./index.ts` copies onto `Query`. */\n /* eslint-disable @typescript-eslint/no-unused-vars -- #1458: the parameter\n list must match `Query`'s exactly (T, S, Q, M) because `declare module`\n merges this mixin's `Pick` into it, and TypeScript requires every\n declaration of an interface to carry identical type parameters. A group\n whose methods happen to use only `T` still declares all four. */\nexport class ReduceMethods<\n T,\n S extends keyof T = never,\n Q extends keyof T & string = never,\n M extends keyof T & string = never,\n /* eslint-enable @typescript-eslint/no-unused-vars */\n> {\n declare protected readonly source: InternalSource\n declare protected readonly plan: QueryPlan\n declare protected readonly joinContext: JoinContext | undefined\n declare protected readonly reduceStrategy: ReduceStrategy\n declare protected readonly predicates: ReadonlyMap<string, DeclaredPredicate> | undefined\n declare protected matchedRecords: (terminal: string, forceJoins?: boolean) => readonly unknown[]\n declare protected decodeVia: (records: readonly unknown[], locale?: string) => unknown[]\n declare protected aliasVia: () => ReadonlyMap<string, ViaPipeline> | undefined\n declare protected incrementalMaintainer: (mode: 'rows' | 'records') => LiveMaintainer | undefined\n declare protected groupMaintenance: (derived: readonly DateTruncKey[]) => GroupMaintenanceSource | undefined\n declare toArray: (opts?: { locale?: string }) => T[]\n\n /**\n * The right-side change streams a joined query has to merge (#1338), deduped\n * by target the way `live()` does — a chain joining the same collection\n * twice must not double-fire. Extracted so a joined REDUCTION and a joined\n * live query cannot disagree on what \"the sources of this result\" means.\n */\n private rightSideUpstreams(): ReductionUpstream[] {\n const ctx = this.joinContext\n if (!ctx) return []\n const out: ReductionUpstream[] = []\n const seen = new Set<string>()\n for (const leg of this.plan.joins) {\n if (seen.has(leg.target)) continue\n seen.add(leg.target)\n const right = ctx.resolveSource(leg.target)\n if (!right?.subscribe) continue\n const subscribe = right.subscribe.bind(right)\n out.push({ subscribe: (cb: () => void) => subscribe(cb) })\n }\n return out\n }\n\n /**\n * Every alias this plan can produce, pipeline or not (#1338).\n *\n * `aliasVia()` above holds only the aliases whose collection compiles a Via\n * pipeline; refusal has to know about ALL of them, or an aliased reducer\n * over a plain right-side collection stays silent.\n */\n private aliasNames(): ReadonlySet<string> {\n const out = new Set<string>()\n for (const clause of this.plan.clauses) {\n if (clause.type !== 'crossJoin') continue\n out.add(clause.as)\n if (clause.leftAs !== undefined) out.add(clause.leftAs)\n }\n for (const leg of this.plan.joins) out.add(leg.as)\n return out\n }\n\n /** Is `field` rooted at an alias this plan produces? */\n\n private addressesAlias(field: string, aliases: ReadonlySet<string>): boolean {\n const dot = field.indexOf('.')\n return dot > 0 && aliases.has(field.slice(0, dot))\n }\n\n /**\n * The distinct values of `field` across the match set (#1347) — the\n * declarative form of `new Set(q.toArray().map(r => r[field]))`, which is\n * what consumers write today and which is wrong in two ways this is not.\n *\n * **Distinctness is decided on the canonical index key, not on the value\n * you get back.** For a Via-covered field that is the difference between a\n * right answer and a plausible one: money stores a scaled integer, and a\n * row written before the field was declared can spell it non-canonically\n * (`'0100'` for `'100'`). Deduping the stored strings reports two values\n * where there is one; deduping the FORMATTED strings makes the answer\n * depend on a locale this layer does not have (`'10.00'` vs `'10,00'`).\n * See `distinct-key.ts`. The values RETURNED are the decoded ones, so they\n * match what `toArray()` hands back for the same field.\n *\n * **Nullish values are excluded** — `null`/`undefined`/missing is the\n * absence of a value, not a distinct one. That is also the only choice\n * under which the index-backed and scanned paths can agree, since the hash\n * index does not hold nullish keys; see `distinctKeyOf`.\n *\n * Same match set as `count()`: where/filter apply, orderBy/limit/offset do\n * not. Values come back in first-encountered order.\n *\n * ⭐ INDEX-BACKED when the field carries a hash index AND the plan narrows\n * nothing — the buckets ARE the distinct key set, so the answer costs\n * O(distinct values) instead of O(records) and never decrypts a snapshot.\n * The guard is deliberately all-or-nothing: with a where clause the whole\n * collection's buckets are not the answer, and intersecting a candidate id\n * set with every bucket would cost more than the scan it replaced.\n *\n * ⛔ THE RETURN TYPE IS `(T & Record<F, unknown>)[F][]`, NOT\n * `F extends keyof T ? T[F][] : unknown[]` — and it must stay that way. The\n * conditional form reads better and type-checks at every call site, but a\n * DEFERRED conditional over `T` makes TypeScript give up relating\n * `Query<T>` to `Query<unknown>`, which makes `Collection<T>` invariant in\n * `T`, which breaks `vault.ts`'s `Map<string, Collection<unknown>>` cache\n * with four errors that name neither this method nor this file. The\n * intersection form resolves to the same thing (`T[F]` for a literal key,\n * `unknown` for a `string`-typed one) and stays covariant.\n */\n distinct<F extends QueryField<T, S>>(field: F): (T & Record<F, unknown>)[F][] {\n const name = field as string\n assertNoJoinAliasField([name], this.plan.joins, 'distinct')\n const via = this.source.via\n // Same posture gate `.where()` / `.orderBy()` apply: a `queryable: 'none'`\n // field (a blob handle) must not have its value set enumerated either.\n if (via?.postureFor(name)?.queryable === 'none') throw new FieldNotQueryableError(name)\n const decode = via?.hasResultDecode ? (r: unknown) => via.decodeResults(r) : (r: unknown) => r\n\n const indexes =\n this.plan.clauses.length === 0 && this.plan.joins.length === 0\n ? this.source.getIndexes?.()\n : undefined\n if (indexes && this.source.lookupById) {\n const lookupById = (id: string): unknown => this.source.lookupById?.(id)\n const reps = indexes.bucketRepresentatives(name)\n if (reps) {\n const fromIndex: unknown[] = []\n for (const id of reps) {\n const record = lookupById(id)\n if (record === undefined) continue\n fromIndex.push(readPath(decode(record), name))\n }\n return fromIndex as (T & Record<F, unknown>)[F][]\n }\n }\n\n const seen = new Set<string>()\n const out: unknown[] = []\n for (const record of this.matchedRecords('distinct')) {\n const key = distinctKeyOf(name, readPath(record, name), via)\n if (key === undefined || seen.has(key)) continue\n seen.add(key)\n out.push(readPath(decode(record), name))\n }\n return out as (T & Record<F, unknown>)[F][]\n }\n\n /**\n * Reduce the matching records through a named set of reducers.\n * the aggregation terminal.\n *\n * ```ts\n * const { total, n, avgAmount } = invoices.query()\n * .where('status', '==', 'open')\n * .aggregate({\n * total: sum('amount'),\n * n: count(),\n * avgAmount: avg('amount'),\n * })\n * .run()\n * ```\n *\n * Returns an `Reduction<R>` wrapper with two terminals:\n * - `.run(): R` — synchronous one-shot reduction\n * - `.live(): LiveReduction<R>` — reactive primitive that\n * re-runs the reduction whenever the source notifies of a\n * change. Always call `live.stop()` when finished.\n *\n * The reducer spec is bound here once and reused by both\n * terminals — this is why `.aggregate()` returns a wrapper instead\n * of being a direct terminal. Consumers who only need the static\n * value read `.run()`; consumers wiring a reactive UI read\n * `.live()`.\n *\n * Joins are NOT applied by default — the same logic as `.count()`. A leg is\n * projection-only, so running one just to throw the alias away would be\n * wasteful. ⭐ **The exception is a reducer that ADDRESSES an alias**\n * (#1338): then the joined relation IS what is being reduced, so the legs\n * run and the reducer is rewritten by the right collection's own Via\n * pipeline — money stays exact, and the right side's `queryable: 'none'`\n * posture refuses a field it would refuse locally. A live joined\n * aggregation also merges every right-side change stream, so it does not\n * quietly stop updating when the right side moves.\n *\n * Every reducer factory accepts an optional `{ seed }` parameter\n * that is plumbed through the protocol but unused by the\n * executor — that's constraint #2. When partition-aware\n * aggregation lands, the seed will carry running state across\n * partition boundaries without an API break.\n *\n * KNOWN GAP (sealed fields, bare-spec form): `where`/`orderBy`/`groupBy`\n * refuse a `sensitive` field at compile time, but a reducer over a sensitive\n * field in the BARE-SPEC form (e.g. `aggregate({ x: sum('ssn') })`) is NOT\n * refused — the reducer factories (`sum`/`min`/`max`/…) are standalone\n * `(field: string)` functions with no collection-type context. This form is\n * preserved as-is for backward compatibility.\n *\n * The BUILDER form closes this gap: `aggregate(b => ({ x: b.sum('field') }))`\n * types the builder's field parameter as `QueryField<T, S>`, refusing any\n * `sensitive` field at compile time. Use the builder form for new code that\n * aggregates over a collection with sensitive fields.\n *\n */\n aggregate<Spec extends ReduceSpec>(spec: Spec): Reduction<ReduceResult<Spec>>\n aggregate<Spec extends ReduceSpec>(build: (b: ReducerBuilder<T, S, M>) => Spec): Reduction<ReduceResult<Spec>>\n aggregate<Spec extends ReduceSpec>(\n specOrBuild: Spec | ((b: ReducerBuilder<T, S, M>) => Spec),\n ): Reduction<ReduceResult<Spec>> {\n let spec = typeof specOrBuild === 'function'\n ? (specOrBuild as (b: ReducerBuilder<T, S, M>) => Spec)((reducerBuilder as unknown) as ReducerBuilder<T, S, M>)\n : specOrBuild\n // #1338 — a reducer over a joined alias used to be refused here. It now\n // routes: the legs run (so the reducer sees the joined relation) and the\n // spec is wrapped by a pipeline that delegates each aliased field to the\n // RIGHT collection's own — which carries money's exact-BigInt rewrite and\n // the `queryable: 'none'` posture gate together. Reducers without a field\n // (e.g. `count()`) address nothing and are unaffected.\n const aliases = this.aliasNames()\n const aliasedReducer =\n this.plan.joins.length > 0 &&\n Object.values(spec).some(r => {\n const f = (r as { field?: unknown }).field\n return typeof f === 'string' && this.addressesAlias(f, aliases)\n })\n // Rewrite sum/min/max over Via-covered fields (e.g. money) into exact\n // BigInt reducers before the strategy runs (covers static run() and\n // live/MV paths).\n spec = (aliasedReducer ? reduceViaFor(this.source.via, this.aliasVia()) : this.source.via)?.wrapReducers(spec) ?? spec\n // #1347 — `countDistinct` needs the SAME canonicalizer the hash index\n // buckets by, and only the source knows it. Bound here, beside the\n // money rewrite, so both spec-wrapping seams stay in one place.\n spec = bindDistinctReducers(spec, this.source.via)\n // Closure over the current query. Produces the record set that\n // the aggregation reduces — same pipeline as `count()`, skipping\n // limit/offset because aggregation is over the full match set,\n // not a paginated slice. (A paginated aggregation would be a\n // different operation; see docs for rationale.)\n const source = this.source\n const clauses = this.plan.clauses\n const joinCtx = this.joinContext\n const hasCrossJoins = clauses.some(c => c.type === 'crossJoin')\n const fullScan = aliasedReducer\n // The reduced relation IS the joined one — same pipeline `count()` uses,\n // with the legs forced on (#1338).\n ? (): readonly unknown[] => this.matchedRecords('aggregate', true)\n : (): readonly unknown[] => {\n if (hasCrossJoins) {\n if (!joinCtx) throw new Error('Query.aggregate(): crossJoin requires a join context')\n return executeClausePipeline(source, clauses, joinCtx)\n }\n const { candidates, remainingClauses } = candidateRecords(source, clauses)\n return remainingClauses.length === 0\n ? candidates\n : filterRecords(candidates, remainingClauses, fnViewDecoder(source))\n }\n\n // #1341 — when the plan admits it, the match set is MAINTAINED across\n // change events instead of re-scanned, so a live reduction folds over an\n // incrementally-patched array. The reducers themselves still run a full\n // fold: the array they see is byte-for-byte the one a re-scan would\n // produce (same records, same candidate order), so an incremental result\n // is not merely close to a re-run's — it is the identical computation.\n const maintainer = this.incrementalMaintainer('records')\n const executeRecords = maintainer ? (): readonly unknown[] => maintainer.rows() : fullScan\n\n // Upstream for live mode. The left source always subscribes; a joined\n // aggregation ALSO merges every right-side change stream (#1338) — the\n // refusal's own doc named a live joined aggregate that silently stops\n // updating as half the work, and it is this half.\n const upstreams: ReductionUpstream[] = []\n if (aliasedReducer) upstreams.push(...this.rightSideUpstreams())\n if (source.subscribe) {\n const subscribe = source.subscribe.bind(source)\n upstreams.push(\n maintainer\n ? {\n // The maintainer folds the delta in BEFORE the reduction reads,\n // which is why it is wrapped here rather than subscribed\n // separately — emitter callback order would otherwise decide\n // whether the reduction saw the new record.\n subscribe: (cb: () => void) => {\n maintainer.attach()\n const unsubscribe = subscribe(change => {\n // A predicate that throws must not escape into the emitter —\n // the maintainer drops its state and the reduction's own\n // re-run raises the same error where it can be caught.\n try {\n maintainer.apply(change)\n } catch {\n maintainer.invalidate()\n }\n cb()\n })\n // Detach on teardown: the Reduction survives its LiveReduction\n // (`.run()` reuses the same closure), and a maintainer with no\n // subscription feeding it would go quietly out of date.\n return () => {\n maintainer.detach()\n unsubscribe()\n }\n },\n }\n : { subscribe: (cb: () => void) => subscribe(cb) },\n )\n }\n\n // #1414 — the gate travels INTO the reduction rather than wrapping it.\n // `Reduction.run()` is the synchronous terminal, and `await …aggregate(\n // spec).run()` is what an async guard's Σ-over-siblings invariant writes;\n // gating the builder call instead would break that awaited form.\n return this.reduceStrategy.aggregate<Spec>(executeRecords, spec, upstreams, this.source.hydration)\n }\n\n /**\n * Partition matching records into buckets keyed by a field, then\n * terminate with `.aggregate(spec)` to compute per-bucket\n * reducers..\n *\n * ```ts\n * const byClient = invoices.query()\n * .where('status', '==', 'open')\n * .groupBy('clientId')\n * .aggregate({ total: sum('amount'), n: count() })\n * .run()\n * // → [ { clientId: 'c1', total: 5250, n: 3 }, … ]\n * ```\n *\n * Result rows carry the group key value under the grouping field\n * name plus every reducer output from the spec. Buckets are\n * emitted in first-seen order — consumers who want a specific\n * ordering should `.sort()` downstream.\n *\n * **Cardinality caps:** a one-shot warning fires at 10_000\n * distinct groups; `GroupCardinalityError` throws at 100_000.\n * Grouping on a high-uniqueness field like `id` or `createdAt` is\n * almost always a query mistake — the error message names the\n * field and observed cardinality and suggests narrowing with\n * `.where()` first.\n *\n * **Null / undefined keys:** records with a missing or explicitly\n * `null` group field get their own buckets. `Map`-based\n * partitioning distinguishes `undefined` from `null`, so the two\n * cases do NOT merge. Consumers who want them merged should\n * coalesce upstream with `.filter()`.\n *\n * **Joins are not applied unless the GROUP KEY addresses an alias** (#1338).\n * For a left-side key the old rationale still holds — a leg is\n * projection-only, so running one inside a grouping pipeline would be\n * wasteful and could trigger `DanglingReferenceError` in strict mode for a\n * call whose intent is purely to bucket-and-reduce. For an aliased key\n * (`groupBy('client.region')`, the accounting-report shape) the legs run and\n * the buckets are the joined relation's: the key is stamped under its DOTTED\n * path (`row['client.region']`), and a left row whose right side is absent\n * reads `undefined` and lands in the undefined bucket — the same bucket a\n * missing left-side key gets, and still distinct from an explicit `null`.\n *\n * ⚠️ The decision is taken from the KEY, because the reducer spec arrives a\n * call later. A reducer over an alias under a LEFT key is therefore REFUSED\n * rather than folded as `undefined` — group by the alias, or aggregate\n * without grouping.\n *\n * **Filter clauses (`.filter(fn)`):** grouped queries still\n * support filter clauses in the underlying plan — they run in\n * the same candidate/filter pipeline that `.aggregate()` uses.\n * The performance caveat is the same: filter clauses cost O(N)\n * per record and can't be index-accelerated.\n */\n // The `field` param is `QueryField<T, S>` so a sealed (`sensitive`) field is\n // refused — grouping BY a sensitive field leaks its value distribution as\n // group-key labels. With `S = never` this is exactly `string` (zero churn).\n\n groupBy<F extends QueryField<T, S>>(field: F): GroupedQuery<T, F, S, M>\n groupBy<F extends readonly [QueryField<T, S>, QueryField<T, S>, ...QueryField<T, S>[]]>(\n ...fields: F\n ): GroupedQueryN<T, F, S, M>\n // Derived calendar keys (#1350). Listed last so the two field-name overloads\n // above still win for plain string arguments — a `DateTruncKey` is an object\n // and matches neither, so nothing existing re-resolves onto these.\n groupBy(key: DateTruncKey): GroupedQuery<T, string, S, M>\n // The string members stay `QueryField<T, S>`, so the sealed-field refusal\n // still applies to every plain key in a mixed grouping.\n groupBy(\n ...keys: readonly [\n QueryField<T, S> | DateTruncKey,\n QueryField<T, S> | DateTruncKey,\n ...(QueryField<T, S> | DateTruncKey)[],\n ]\n ): GroupedQueryN<T, readonly string[], S, M>\n groupBy(...keys: readonly GroupKey[]): GroupedQuery<T, string, S, M> | GroupedQueryN<T, readonly string[], S, M> {\n if (keys.length === 0) {\n throw new Error('.groupBy() requires at least one field')\n }\n // A derived key is bucketed into an ordinary row field before the grouping\n // pipeline runs, so everything downstream — cardinality caps, null/undefined\n // bucket semantics, live re-grouping — keeps working unchanged.\n const derived = keys.filter(isDateTruncKey)\n const fields: readonly string[] = keys.map(groupKeyName)\n // #1338 — a group key addressing an alias used to be refused. It now\n // decides the shape of the whole grouped pipeline: the legs run, and the\n // reducers are wrapped through the right side's pipeline.\n //\n // ⚠️ The decision is made HERE, from the group KEY alone, because the\n // reducer spec does not exist yet — it arrives one call later, at\n // `.aggregate()`. That is exactly why the left-key case installs a\n // REFUSING binding rather than nothing: a reducer over an alias under a\n // left-side key would otherwise fold undefined into every bucket, and the\n // grouped terminal is not the one `Query.aggregate()`'s guard sees.\n const aliases = this.aliasNames()\n const keyIsAliased = this.plan.joins.length > 0 && fields.some(f => this.addressesAlias(f, aliases))\n // Same record-producing closure as .aggregate() — grouped and\n // non-grouped aggregations execute over the same candidate set.\n // We inline the closure here instead of sharing a helper so the\n // builder stays allocation-friendly for the hot path.\n const source = this.source\n const clauses = this.plan.clauses\n const joinCtx = this.joinContext\n const hasCrossJoins = clauses.some(c => c.type === 'crossJoin')\n const executeRecords = keyIsAliased\n ? (): readonly unknown[] => this.matchedRecords('groupBy', true)\n : (): readonly unknown[] => {\n if (hasCrossJoins) {\n if (!joinCtx) throw new Error('Query.groupBy(): crossJoin requires a join context')\n return executeClausePipeline(source, clauses, joinCtx)\n }\n const { candidates, remainingClauses } = candidateRecords(source, clauses)\n return remainingClauses.length === 0\n ? candidates\n : filterRecords(candidates, remainingClauses, fnViewDecoder(source))\n }\n const executeGroupRecords = derived.length === 0\n ? executeRecords\n : (): readonly unknown[] => projectDateTruncKeys(executeRecords(), derived)\n\n // #1341 (grouped half) — the upstream passes the DELTA through, not just a\n // \"something changed\" ping, so `GroupedReduction.live()` can patch the one\n // or two buckets a change touches instead of re-grouping everything. A cb\n // that ignores the argument (every pre-#1341 caller) is unaffected.\n const upstreams: ReductionUpstream[] = []\n if (source.subscribe) {\n const subscribe = source.subscribe.bind(source)\n upstreams.push({ subscribe: (cb: (change?: SourceChange) => void) => subscribe(cb) })\n }\n if (keyIsAliased) upstreams.push(...this.rightSideUpstreams())\n // Withheld for a plan the whitelist refuses, and for an aliased key (which\n // implies joins, which the whitelist refuses anyway) — `.live()` then\n // re-runs the whole grouping, exactly as it did before.\n const maintenance = keyIsAliased ? undefined : this.groupMaintenance(derived)\n\n // The pipeline the reducers are wrapped with: the right side's rewrite\n // when the legs run, the refusal when they do not (#1338).\n const reduceVia = keyIsAliased\n ? reduceViaFor(this.source.via, this.aliasVia())\n : refuseAliasReduceVia(this.source.via, aliases)\n\n // Dict-label resolution is single-field only — the <field>Label\n // projection has no meaningful shape for composite keys. A derived\n // calendar key is never a dictKey, so it skips the lookup entirely.\n if (fields.length === 1) {\n const field = fields[0]!\n const dictLabelResolver = derived.length > 0\n ? undefined\n : buildDictLabelResolver(this.joinContext, field)\n return this.reduceStrategy.groupBy<T, string, S, M>(\n executeGroupRecords,\n field,\n upstreams,\n dictLabelResolver,\n reduceVia,\n maintenance,\n )\n }\n return this.reduceStrategy.groupByN<T, readonly string[], S, M>(\n executeGroupRecords,\n fields,\n upstreams,\n reduceVia,\n maintenance,\n )\n }\n\n /**\n * SQL window functions over the query's result rows (#1349) — terminate with\n * `.select(spec)`.\n *\n * ```ts\n * invoices.query()\n * .where('status', '==', 'open')\n * .window({ partitionBy: 'clientId', orderBy: 'date' })\n * .select({ balance: runningSum('amount'), prev: lag('amount', 1), n: rowNumber() })\n * .run()\n * ```\n *\n * ⭐ Distinguished from `.groupBy()` by ARITY, not by capability: grouping\n * emits one row per bucket and drops the source rows; a window keeps EVERY\n * row and attaches per-row values computed over its partition.\n *\n * **The record set is `toArray()`'s** — the full pipeline, including joins,\n * `orderBy`, `offset`/`limit` and the Via decode. That differs deliberately\n * from `.groupBy()`, which reads the candidate/filter pipeline only: a\n * window returns ROWS, so the query's own ordering and paging are exactly\n * what decides which rows there are and how they are presented. `orderBy`\n * inside the window decides only how a partition is WALKED.\n *\n * **Frame:** `rows unbounded preceding → current row`, and only that in v1.\n *\n * `partitionBy` / `orderBy` accept a `dateTrunc()` key (#1350) alongside\n * plain field names; the derived bucket is used for partitioning and is not\n * stamped on the output row. A sealed (`sensitive`) field is refused at\n * compile time, same as `.groupBy()`.\n *\n * ⛔ **Not an `explain()` node.** `explain()` is a terminal on `Query`;\n * `.window()` leaves `Query` for `WindowedQuery`, so there is nothing past\n * here to add a node to — the same reason #1336's post-group ops are absent.\n */\n window(spec: WindowSpec<QueryField<T, S>>): WindowedQuery<T> {\n const upstreams: ReductionUpstream[] = []\n if (this.source.subscribe) {\n const subscribe = this.source.subscribe.bind(this.source)\n upstreams.push({ subscribe: (cb: () => void) => subscribe(cb) })\n }\n return this.reduceStrategy.window<T>(\n () => this.toArray() as readonly unknown[],\n spec as WindowSpec,\n upstreams,\n this.source.via,\n )\n }\n}\n\n/** The public Reduce surface — merged into `Query` by `./index.ts`. */\nexport type ReduceSurface<\n T,\n S extends keyof T = never,\n Q extends keyof T & string = never,\n M extends keyof T & string = never,\n> = Pick<ReduceMethods<T, S, Q, M>, 'aggregate' | 'groupBy' | 'window' | 'distinct'>\n\n","/**\n * #1458 — the **Reduce** half of `ScanBuilder`.\n *\n * `aggregate` · `groupBy` · the grouped-scan handle they return.\n *\n * See `../relate/scan-methods.ts` for why `ScanBuilder` is split at all.\n *\n * ⭐ **The streaming memory property travels with the code.**\n * `scan().aggregate()` is O(reducers), not O(records) — it steps every reducer\n * per record and never collects the stream — and `groupBy().aggregate()` is\n * O(groups). That is a code-level invariant visible in the bodies below, so\n * moving them wholesale is what preserves it; re-expressing them over the\n * eager reducers would have quietly turned an unbounded scan into an array.\n */\nimport type { MultiReduceResult, ScanPageProvider } from '../scan-builder.js'\nimport type { QueryField } from '../../types.js'\nimport type { Clause } from '../predicate.js'\nimport { readPath } from '../predicate.js'\nimport type { ViaPipeline } from '../../via/pipeline.js'\nimport type { JoinContext, JoinLeg } from '../relate/join.js'\nimport type { ReducerBuilder } from '../../../with-lookup/reduce/reducers.js'\nimport { bindDistinctReducers, reducerBuilder } from '../../../with-lookup/reduce/reducers.js'\nimport type { ReduceSpec, ReduceResult } from '../../../with-lookup/reduce/reduction.js'\nimport type { DateTruncKey } from './date-trunc.js'\nimport { groupKeyName, isDateTruncKey, projectDateTruncKeys } from './date-trunc.js'\nimport { GroupCardinalityError } from '../../errors.js'\n\n/** @internal — the mixin whose prototype `./index.ts` copies onto `ScanBuilder`. */\nexport class ScanReduceMethods<T, S extends keyof T = never, M extends keyof T & string = never> {\n declare protected readonly pageProvider: ScanPageProvider<T>\n declare protected readonly pageSize: number\n declare protected readonly clauses: readonly Clause[]\n declare protected readonly joins: readonly JoinLeg[]\n declare protected readonly joinContext: JoinContext | undefined\n declare protected readonly via: ViaPipeline | undefined\n declare protected decodeVia: (record: T) => T\n declare protected recordMatches: (record: T, clauses?: readonly Clause[]) => boolean\n declare [Symbol.asyncIterator]: () => AsyncIterator<T>\n\n async aggregate<const Specs extends readonly ReduceSpec[]>(specs: Specs): Promise<MultiReduceResult<Specs>>\n async aggregate<Spec extends ReduceSpec>(spec: Spec): Promise<ReduceResult<Spec>>\n async aggregate<Spec extends ReduceSpec>(build: (b: ReducerBuilder<T, S, M>) => Spec): Promise<ReduceResult<Spec>>\n async aggregate<Spec extends ReduceSpec>(\n specOrBuild: Spec | readonly ReduceSpec[] | ((b: ReducerBuilder<T, S, M>) => Spec),\n ): Promise<ReduceResult<Spec> | unknown[]> {\n // #1340 — the multi-spec form. N independent specs, ONE pass: the states\n // for every spec are stepped from the same yielded record, so the page\n // provider is walked exactly once no matter how many specs are passed.\n // (Calling `.aggregate(specA)` then `.aggregate(specB)` costs two full\n // scans — the immutable-builder docs above say so, and this is the fix.)\n if (Array.isArray(specOrBuild)) return this.aggregateMany(specOrBuild as readonly ReduceSpec[])\n // Opt-in builder form `aggregate(b => spec)`: `b`'s field args are\n // `QueryField<T, S>`, refusing sensitive fields (the standalone-spec form\n // stays unrefused for back-compat), and `sum`/`min`/`max` over a declared\n // `moneyFields` (`M`) member return a `MoneyString`. Mirrors `Query.aggregate`.\n const spec: Spec = typeof specOrBuild === 'function'\n ? (specOrBuild as (b: ReducerBuilder<T, S, M>) => Spec)(reducerBuilder as unknown as ReducerBuilder<T, S, M>)\n // `Array.isArray` above does not narrow a READONLY array out of the\n // union, so the array arm is already returned and this cast is the\n // remainder — a plain spec.\n : (specOrBuild as Spec)\n this.via?.refuseUnqueryableReducers(spec)\n const keys = Object.keys(spec)\n // Per-reducer state. Exactly |keys| entries, never grows with\n // the record count — that's the O(reducers) memory guarantee.\n const state: Record<string, unknown> = {}\n for (const key of keys) {\n state[key] = spec[key]!.init()\n }\n\n // Record-by-record streaming step. `for await (… of this)`\n // invokes the Symbol.asyncIterator above, which honors the\n // clause list, so filtered-out records never reach the step\n // loop — they're dropped at the iterator boundary.\n for await (const record of this) {\n for (const key of keys) {\n state[key] = spec[key]!.step(state[key], record)\n }\n }\n\n const result: Record<string, unknown> = {}\n for (const key of keys) {\n result[key] = spec[key]!.finalize(state[key])\n }\n return result as ReduceResult<Spec>\n }\n\n /**\n * The multi-spec `.aggregate([specA, specB, …])` executor (#1340).\n *\n * One iteration of the scan, `Σ|spec|` reducer states — memory stays\n * O(reducers) exactly as the single-spec terminal's does, with the sum taken\n * across specs instead of within one. Every spec's state is stepped from the\n * SAME yielded record, so the page provider is read once per page.\n *\n * Semantics per spec are identical to the single-spec form, deliberately:\n * the posture gate is `refuseUnqueryableReducers` (metadata only), NOT the\n * full `wrapReducers` — so `aggregate([a, b])` returns exactly what\n * `aggregate(a)` and `aggregate(b)` return, and the array form is a pure\n * cost optimisation with no behavioural delta to reason about. (The grouped\n * scan path DOES wrap — it has to agree with the eager grouped path, which\n * wraps. See `scan-groupby.ts`.)\n */\n private async aggregateMany(specs: readonly ReduceSpec[]): Promise<unknown[]> {\n for (const spec of specs) this.via?.refuseUnqueryableReducers(spec)\n const keysPerSpec = specs.map((spec) => Object.keys(spec))\n const states: Record<string, unknown>[] = specs.map((spec, i) => {\n const state: Record<string, unknown> = {}\n for (const key of keysPerSpec[i]!) state[key] = spec[key]!.init()\n return state\n })\n\n for await (const record of this) {\n for (let i = 0; i < specs.length; i++) {\n const spec = specs[i]!\n const state = states[i]!\n for (const key of keysPerSpec[i]!) state[key] = spec[key]!.step(state[key], record)\n }\n }\n\n return specs.map((spec, i) => {\n const result: Record<string, unknown> = {}\n for (const key of keysPerSpec[i]!) result[key] = spec[key]!.finalize(states[i]![key])\n return result\n })\n }\n\n /**\n * Group the scan stream and reduce each group — `#1340`.\n *\n * ```ts\n * const byClient = await invoices.scan()\n * .where('status', '==', 'open')\n * .groupBy('clientId', { maxGroups: 5_000 })\n * .aggregate({ total: sum('amount'), n: count() })\n * ```\n *\n * ⚠️ **This is the one scan terminal whose memory is NOT O(pageSize).** It\n * holds one reducer state per group, which is why the budget is explicit:\n * `maxGroups` defaults to the eager path's 100_000-group ceiling and is\n * REFUSED — never truncated — the moment a scan would exceed it. Price the\n * budget before raising it: an exact `median`/`percentile`, a `mode` or a\n * `countDistinct` holds O(values) per group, so an unbounded scan wants\n * `{ approx: true }` on the quantiles. Full rationale in `scan-groupby.ts`.\n *\n * The key may be a field name or a `dateTrunc()` derived calendar key (the\n * monthly-rollup shape); grouping BY a `sensitive` field is refused at\n * compile time, same as `Query.groupBy()`.\n */\n groupBy<F extends QueryField<T, S>>(field: F, opts?: { maxGroups?: number }): ScanGroupedScan<T, F, S, M>\n groupBy(key: DateTruncKey, opts?: { maxGroups?: number }): ScanGroupedScan<T, string, S, M>\n groupBy(key: QueryField<T, S> | DateTruncKey, opts?: { maxGroups?: number }): ScanGroupedScan<T, string, S, M> {\n return new ScanGroupedScan<T, string, S, M>(\n this,\n key as string | DateTruncKey,\n opts?.maxGroups ?? SCAN_GROUPBY_DEFAULT_MAX_GROUPS,\n this.via,\n )\n }\n}\n\n/** The public Reduce surface of `ScanBuilder` — merged by `./index.ts`. */\nexport type ScanReduceSurface<\n T,\n S extends keyof T = never,\n M extends keyof T & string = never,\n> = Pick<ScanReduceMethods<T, S, M>, 'aggregate' | 'groupBy'>\n\n/**\n * Default group ceiling — the same constant the eager `.groupBy()` enforces.\n * Duplicated as a literal rather than imported from\n * `with-lookup/reduce/groupby.ts` on purpose: that module carries the eager\n * `groupAndReduce` + `GroupedReduction` classes, and the always-on kernel must\n * not pull them in to read one number. The pairing is asserted by a test.\n */\nexport const SCAN_GROUPBY_DEFAULT_MAX_GROUPS = 100_000\n\n/**\n * Single-field spelling of `canonicalGroupKey` (`with-lookup/reduce/canonical-key.ts`).\n *\n * ⛔ Inlined, not imported, and not by preference: `check-architecture`'s\n * `port-layering` rule grandfathers this file for exactly two\n * `with-lookup/reduce/*` specifiers (`reducers.js`, `reduction.js`), and\n * `canonical-key.js` is not one of them — a new one may not be added\n * silently. The eager helper sorts its field list and serialises\n * each value — with exactly one field, that reduces to this line, including\n * the part that matters: `undefined` gets a sentinel so a MISSING key and an\n * explicit `null` land in different buckets, as they do under\n * `Query.groupBy()`. The agreement is held by test, not by comment\n * (`query-scan-groupby.test.ts` — null/undefined bucketing, and full equality\n * with the eager path).\n */\nfunction scanGroupKey(field: string, value: unknown): string {\n return `${field}=${value === undefined ? 'undefined' : JSON.stringify(value)}`\n}\n\n/** Group-key result-row shape: the key under its own name, plus the reducers. */\n\nexport type ScanGroupedRow<F extends string, R> = { [K in F]: unknown } & R\n\n/**\n * Chainable wrapper returned by `ScanBuilder.groupBy()`. The only operation on\n * it is `.aggregate()` — same minimal shape as `GroupedQuery`.\n */\nexport class ScanGroupedScan<\n T,\n F extends string,\n S extends keyof T = never,\n M extends keyof T & string = never,\n> {\n constructor(\n private readonly stream: AsyncIterable<T>,\n private readonly key: string | DateTruncKey,\n private readonly maxGroups: number,\n private readonly via: ViaPipeline | undefined,\n ) {\n if (!Number.isInteger(maxGroups) || maxGroups < 1) {\n throw new Error(\n `scan().groupBy(): { maxGroups: ${String(maxGroups)} } must be a positive integer — ` +\n `it is the declared ceiling on how many reducer states the grouped scan may hold.`,\n )\n }\n }\n\n /**\n * Fold the scan into one row per group. Resolves to `R[]`, ordered by each\n * group's FIRST-SEEN position in the stream (`Map` insertion order), which\n * is the same ordering rule the eager path documents.\n *\n * ```ts\n * const byMonth = await invoices.scan()\n * .where('status', '==', 'paid')\n * .groupBy(dateTrunc('closedAt', 'month', { as: 'month', timeZone: 'UTC' }), { maxGroups: 240 })\n * .aggregate({ total: sum('amount'), n: count() })\n * ```\n */\n async aggregate<Spec extends ReduceSpec>(spec: Spec): Promise<ScanGroupedRow<F, ReduceResult<Spec>>[]>\n async aggregate<Spec extends ReduceSpec>(\n build: (b: ReducerBuilder<T, S, M>) => Spec,\n ): Promise<ScanGroupedRow<F, ReduceResult<Spec>>[]>\n async aggregate<Spec extends ReduceSpec>(\n specOrBuild: Spec | ((b: ReducerBuilder<T, S, M>) => Spec),\n ): Promise<ScanGroupedRow<F, ReduceResult<Spec>>[]> {\n const raw: Spec =\n typeof specOrBuild === 'function'\n ? (specOrBuild as (b: ReducerBuilder<T, S, M>) => Spec)(reducerBuilder as unknown as ReducerBuilder<T, S, M>)\n : specOrBuild\n // Same reducer rewriting as the eager grouped path, and for the same\n // reason: a money `sum` must accumulate per-currency BigInt totals, and a\n // `countDistinct` must dedup on the canonical index key — otherwise a\n // grouped scan and a grouped query disagree on identical data.\n // `wrapReducers` runs the `queryable: 'none'` posture refusal itself.\n const spec: ReduceSpec = bindDistinctReducers(this.via ? this.via.wrapReducers(raw) : raw, this.via)\n const keys = Object.keys(spec)\n\n const field = groupKeyName(this.key)\n const derived: readonly DateTruncKey[] = isDateTruncKey(this.key) ? [this.key] : []\n const groups = new Map<string, { keyValue: unknown; state: Record<string, unknown> }>()\n\n for await (const record of this.stream) {\n // A derived calendar key is stamped onto a shallow copy before bucketing\n // — the reducers then see an ordinary row carrying an ordinary field,\n // exactly as they do under `Query.groupBy(dateTrunc(...))`.\n const row = derived.length === 0 ? record : projectDateTruncKeys([record], derived)[0]!\n const keyValue = readPath(row, field)\n const dedupKey = scanGroupKey(field, keyValue)\n let group = groups.get(dedupKey)\n if (group === undefined) {\n if (groups.size >= this.maxGroups) {\n // Loud and early: the state for this group is never allocated, so\n // the refusal fires at the budget, not after blowing through it.\n throw new GroupCardinalityError(field, groups.size + 1, this.maxGroups, 'scan')\n }\n const state: Record<string, unknown> = {}\n for (const k of keys) state[k] = spec[k]!.init()\n group = { keyValue, state }\n groups.set(dedupKey, group)\n }\n for (const k of keys) group.state[k] = spec[k]!.step(group.state[k], row)\n }\n\n const out: ScanGroupedRow<F, ReduceResult<Spec>>[] = []\n for (const group of groups.values()) {\n // Group key first, then the reducer outputs — same row shape as the\n // eager path, which tests assert by key order.\n const outRow: Record<string, unknown> = { [field]: group.keyValue }\n for (const k of keys) outRow[k] = spec[k]!.finalize(group.state[k])\n out.push(outRow as ScanGroupedRow<F, ReduceResult<Spec>>)\n }\n return out\n }\n}\n","/**\n * #1458 — the Reduce install. See `../relate/install.ts` for why this is a\n * function called from the entry rather than a module-level side effect.\n */\nimport { Query } from '../builder.js'\nimport { ScanBuilder } from '../scan-builder.js'\nimport { installMethods } from '../internal/core.js'\nimport { MaintenanceMethods } from '../internal/maintenance.js'\nimport { ReduceMethods } from './methods.js'\nimport { ScanReduceMethods } from './scan-methods.js'\n\nlet done = false\n\n/** Idempotent: the root barrel and the subpath entry both call it. */\nexport function installReduce(): void {\n if (done) return\n done = true\n installMethods(Query.prototype, MaintenanceMethods)\n installMethods(Query.prototype, ReduceMethods)\n installMethods(ScanBuilder.prototype, ScanReduceMethods)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqEA,SAAS,YAAY,OAAe,MAAc,OAAmC;AACnF,QAAM,QAAQ,CAAC,WACb,WAAW,QAAQ,OAAO,WAAW,WAAW,SAAa,OAAmC,KAAK;AACvG,SAAO;AAAA,IACL,GAAG;AAAA;AAAA;AAAA,IAGH,OAAO;AAAA,IACP,MAAM,MAAM,MAAM,KAAK;AAAA,IACvB,MAAM,CAAC,OAAO,WAAW,MAAM,KAAK,OAAO,MAAM,MAAM,CAAC;AAAA,IACxD,GAAI,MAAM,SAAS,EAAE,QAAQ,CAAC,OAAgB,WAAoB,MAAM,OAAQ,OAAO,MAAM,MAAM,CAAC,EAAE,IAAI,CAAC;AAAA,IAC3G,UAAU,CAAC,UAAU,MAAM,SAAS,KAAK;AAAA,EAC3C;AACF;AAYA,SAAS,iBAAiB,UAAsD;AAC9E,SAAO;AAAA,IACL,OAAO;AAAA,IACP,SAAS,EAAE,iBAAiB,YAAY,WAAW,QAAQ,YAAY,MAAM,aAAa,KAAK;AAAA;AAAA,IAE/F,QAAQ,MAAM;AAAA,IACd,cAAc,CAAC,SAAS;AACtB,UAAI,UAAU;AACd,YAAM,MAAoC,CAAC;AAC3C,iBAAW,CAAC,KAAK,OAAO,KAAK,OAAO,QAAQ,IAAiB,GAAG;AAC9D,cAAM,QAAQ,QAAQ;AACtB,cAAM,MAAM,UAAU,SAAY,KAAK,MAAM,QAAQ,GAAG;AACxD,cAAM,MAAM,MAAM,IAAI,SAAS,IAAI,MAAO,MAAM,GAAG,GAAG,CAAC,IAAI;AAC3D,YAAI,CAAC,KAAK;AAAE,cAAI,GAAG,IAAI;AAAS;AAAA,QAAS;AACzC,cAAM,OAAO,MAAO,MAAM,MAAM,CAAC;AAGjC,cAAM,UAAU,IAAI,aAAa,EAAE,CAAC,GAAG,GAAG,EAAE,GAAG,SAAS,OAAO,KAAK,EAAE,CAAc,EAAE,GAAG;AACzF,YAAI,YAAY,WAAW,QAAQ,UAAU,MAAM;AAAE,cAAI,GAAG,IAAI;AAAS;AAAA,QAAS;AAClF,kBAAU;AACV,YAAI,GAAG,IAAI,YAAY,MAAO,MAAM,GAAG,GAAG,GAAG,OAAQ,OAAO;AAAA,MAC9D;AACA,aAAO,UAAU,MAAM;AAAA,IACzB;AAAA,EACF;AACF;AAUO,SAAS,aACd,SACA,UACyB;AACzB,MAAI,aAAa,UAAa,SAAS,SAAS,EAAG,QAAO;AAC1D,SAAO,YAAY,MAAM,CAAC,GAAI,SAAS,YAAY,CAAC,GAAI,iBAAiB,QAAQ,CAAC,GAAG,SAAS,KAAK;AACrG;AAcA,SAAS,mBAAmB,SAAwC;AAClE,SAAO;AAAA,IACL,OAAO;AAAA,IACP,SAAS,EAAE,iBAAiB,YAAY,WAAW,QAAQ,YAAY,MAAM,aAAa,KAAK;AAAA,IAC/F,QAAQ,MAAM;AAAA,IACd,cAAc,CAAC,SAAS;AACtB,iBAAW,WAAW,OAAO,OAAO,IAAiB,GAAG;AACtD,cAAM,QAAQ,QAAQ;AACtB,YAAI,UAAU,OAAW;AACzB,cAAM,OAAO,MAAM,MAAM,GAAG,EAAE,CAAC;AAC/B,YAAI,CAAC,QAAQ,IAAI,IAAI,EAAG;AACxB,cAAM,IAAI;AAAA,UACR,+CAA+C,KAAK,+BAC9C,IAAI,wKAEgB,IAAI;AAAA,QAEhC;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAOO,SAAS,qBACd,SACA,SACyB;AACzB,MAAI,QAAQ,SAAS,EAAG,QAAO;AAC/B,SAAO,YAAY,MAAM,CAAC,GAAI,SAAS,YAAY,CAAC,GAAI,mBAAmB,OAAO,CAAC,GAAG,SAAS,KAAK;AACtG;;;AChJA,SAAS,uBACP,QACA,OACA,UACM;AACN,MAAI,MAAM,WAAW,EAAG;AACxB,QAAM,UAAU,IAAI,IAAI,MAAM,IAAI,SAAO,IAAI,EAAE,CAAC;AAChD,aAAW,SAAS,QAAQ;AAC1B,UAAM,OAAO,MAAM,MAAM,GAAG,EAAE,CAAC;AAC/B,QAAI,CAAC,QAAQ,IAAI,IAAI,EAAG;AACxB,UAAM,IAAI;AAAA,MACR,SAAS,QAAQ,cAAc,KAAK,+BAA+B,IAAI,UAClE,QAAQ,yMAE6C,IAAI,qDAC3B,QAAQ;AAAA,IAC7C;AAAA,EACF;AACF;AAQO,IAAM,gBAAN,MAML;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBQ,qBAA0C;AAChD,UAAM,MAAM,KAAK;AACjB,QAAI,CAAC,IAAK,QAAO,CAAC;AAClB,UAAM,MAA2B,CAAC;AAClC,UAAM,OAAO,oBAAI,IAAY;AAC7B,eAAW,OAAO,KAAK,KAAK,OAAO;AACjC,UAAI,KAAK,IAAI,IAAI,MAAM,EAAG;AAC1B,WAAK,IAAI,IAAI,MAAM;AACnB,YAAM,QAAQ,IAAI,cAAc,IAAI,MAAM;AAC1C,UAAI,CAAC,OAAO,UAAW;AACvB,YAAM,YAAY,MAAM,UAAU,KAAK,KAAK;AAC5C,UAAI,KAAK,EAAE,WAAW,CAAC,OAAmB,UAAU,EAAE,EAAE,CAAC;AAAA,IAC3D;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,aAAkC;AACxC,UAAM,MAAM,oBAAI,IAAY;AAC5B,eAAW,UAAU,KAAK,KAAK,SAAS;AACtC,UAAI,OAAO,SAAS,YAAa;AACjC,UAAI,IAAI,OAAO,EAAE;AACjB,UAAI,OAAO,WAAW,OAAW,KAAI,IAAI,OAAO,MAAM;AAAA,IACxD;AACA,eAAW,OAAO,KAAK,KAAK,MAAO,KAAI,IAAI,IAAI,EAAE;AACjD,WAAO;AAAA,EACT;AAAA;AAAA,EAIQ,eAAe,OAAe,SAAuC;AAC3E,UAAM,MAAM,MAAM,QAAQ,GAAG;AAC7B,WAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,MAAM,GAAG,GAAG,CAAC;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA0CA,SAAqC,OAAyC;AAC5E,UAAM,OAAO;AACb,2BAAuB,CAAC,IAAI,GAAG,KAAK,KAAK,OAAO,UAAU;AAC1D,UAAM,MAAM,KAAK,OAAO;AAGxB,QAAI,KAAK,WAAW,IAAI,GAAG,cAAc,OAAQ,OAAM,IAAI,uBAAuB,IAAI;AACtF,UAAM,SAAS,KAAK,kBAAkB,CAAC,MAAe,IAAI,cAAc,CAAC,IAAI,CAAC,MAAe;AAE7F,UAAM,UACJ,KAAK,KAAK,QAAQ,WAAW,KAAK,KAAK,KAAK,MAAM,WAAW,IACzD,KAAK,OAAO,aAAa,IACzB;AACN,QAAI,WAAW,KAAK,OAAO,YAAY;AACrC,YAAM,aAAa,CAAC,OAAwB,KAAK,OAAO,aAAa,EAAE;AACvE,YAAM,OAAO,QAAQ,sBAAsB,IAAI;AAC/C,UAAI,MAAM;AACR,cAAM,YAAuB,CAAC;AAC9B,mBAAW,MAAM,MAAM;AACrB,gBAAM,SAAS,WAAW,EAAE;AAC5B,cAAI,WAAW,OAAW;AAC1B,oBAAU,KAAK,SAAS,OAAO,MAAM,GAAG,IAAI,CAAC;AAAA,QAC/C;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,OAAO,oBAAI,IAAY;AAC7B,UAAM,MAAiB,CAAC;AACxB,eAAW,UAAU,KAAK,eAAe,UAAU,GAAG;AACpD,YAAM,MAAM,cAAc,MAAM,SAAS,QAAQ,IAAI,GAAG,GAAG;AAC3D,UAAI,QAAQ,UAAa,KAAK,IAAI,GAAG,EAAG;AACxC,WAAK,IAAI,GAAG;AACZ,UAAI,KAAK,SAAS,OAAO,MAAM,GAAG,IAAI,CAAC;AAAA,IACzC;AACA,WAAO;AAAA,EACT;AAAA,EA4DA,UACE,aAC+B;AAC/B,QAAI,OAAO,OAAO,gBAAgB,aAC7B,YAAsD,cAAqD,IAC5G;AAOJ,UAAM,UAAU,KAAK,WAAW;AAChC,UAAM,iBACJ,KAAK,KAAK,MAAM,SAAS,KACzB,OAAO,OAAO,IAAI,EAAE,KAAK,OAAK;AAC5B,YAAM,IAAK,EAA0B;AACrC,aAAO,OAAO,MAAM,YAAY,KAAK,eAAe,GAAG,OAAO;AAAA,IAChE,CAAC;AAIH,YAAQ,iBAAiB,aAAa,KAAK,OAAO,KAAK,KAAK,SAAS,CAAC,IAAI,KAAK,OAAO,MAAM,aAAa,IAAI,KAAK;AAIlH,WAAO,qBAAqB,MAAM,KAAK,OAAO,GAAG;AAMjD,UAAM,SAAS,KAAK;AACpB,UAAM,UAAU,KAAK,KAAK;AAC1B,UAAM,UAAU,KAAK;AACrB,UAAM,gBAAgB,QAAQ,KAAK,OAAK,EAAE,SAAS,WAAW;AAC9D,UAAM,WAAW,iBAGb,MAA0B,KAAK,eAAe,aAAa,IAAI,IAC/D,MAA0B;AAC1B,UAAI,eAAe;AACjB,YAAI,CAAC,QAAS,OAAM,IAAI,MAAM,sDAAsD;AACpF,eAAO,sBAAsB,QAAQ,SAAS,OAAO;AAAA,MACvD;AACA,YAAM,EAAE,YAAY,iBAAiB,IAAI,iBAAiB,QAAQ,OAAO;AACzE,aAAO,iBAAiB,WAAW,IAC/B,aACA,cAAc,YAAY,kBAAkB,cAAc,MAAM,CAAC;AAAA,IACvE;AAQF,UAAM,aAAa,KAAK,sBAAsB,SAAS;AACvD,UAAM,iBAAiB,aAAa,MAA0B,WAAW,KAAK,IAAI;AAMlF,UAAM,YAAiC,CAAC;AACxC,QAAI,eAAgB,WAAU,KAAK,GAAG,KAAK,mBAAmB,CAAC;AAC/D,QAAI,OAAO,WAAW;AACpB,YAAM,YAAY,OAAO,UAAU,KAAK,MAAM;AAC9C,gBAAU;AAAA,QACR,aACI;AAAA;AAAA;AAAA;AAAA;AAAA,UAKE,WAAW,CAAC,OAAmB;AAC7B,uBAAW,OAAO;AAClB,kBAAM,cAAc,UAAU,YAAU;AAItC,kBAAI;AACF,2BAAW,MAAM,MAAM;AAAA,cACzB,QAAQ;AACN,2BAAW,WAAW;AAAA,cACxB;AACA,iBAAG;AAAA,YACL,CAAC;AAID,mBAAO,MAAM;AACX,yBAAW,OAAO;AAClB,0BAAY;AAAA,YACd;AAAA,UACF;AAAA,QACF,IACA,EAAE,WAAW,CAAC,OAAmB,UAAU,EAAE,EAAE;AAAA,MACrD;AAAA,IACF;AAMA,WAAO,KAAK,eAAe,UAAgB,gBAAgB,MAAM,WAAW,KAAK,OAAO,SAAS;AAAA,EACnG;AAAA,EA6EA,WAAW,MAAsG;AAC/G,QAAI,KAAK,WAAW,GAAG;AACrB,YAAM,IAAI,MAAM,wCAAwC;AAAA,IAC1D;AAIA,UAAM,UAAU,KAAK,OAAO,cAAc;AAC1C,UAAM,SAA4B,KAAK,IAAI,YAAY;AAWvD,UAAM,UAAU,KAAK,WAAW;AAChC,UAAM,eAAe,KAAK,KAAK,MAAM,SAAS,KAAK,OAAO,KAAK,OAAK,KAAK,eAAe,GAAG,OAAO,CAAC;AAKnG,UAAM,SAAS,KAAK;AACpB,UAAM,UAAU,KAAK,KAAK;AAC1B,UAAM,UAAU,KAAK;AACrB,UAAM,gBAAgB,QAAQ,KAAK,OAAK,EAAE,SAAS,WAAW;AAC9D,UAAM,iBAAiB,eACnB,MAA0B,KAAK,eAAe,WAAW,IAAI,IAC7D,MAA0B;AAC1B,UAAI,eAAe;AACjB,YAAI,CAAC,QAAS,OAAM,IAAI,MAAM,oDAAoD;AAClF,eAAO,sBAAsB,QAAQ,SAAS,OAAO;AAAA,MACvD;AACA,YAAM,EAAE,YAAY,iBAAiB,IAAI,iBAAiB,QAAQ,OAAO;AACzE,aAAO,iBAAiB,WAAW,IAC/B,aACA,cAAc,YAAY,kBAAkB,cAAc,MAAM,CAAC;AAAA,IACvE;AACF,UAAM,sBAAsB,QAAQ,WAAW,IAC3C,iBACA,MAA0B,qBAAqB,eAAe,GAAG,OAAO;AAM5E,UAAM,YAAiC,CAAC;AACxC,QAAI,OAAO,WAAW;AACpB,YAAM,YAAY,OAAO,UAAU,KAAK,MAAM;AAC9C,gBAAU,KAAK,EAAE,WAAW,CAAC,OAAwC,UAAU,EAAE,EAAE,CAAC;AAAA,IACtF;AACA,QAAI,aAAc,WAAU,KAAK,GAAG,KAAK,mBAAmB,CAAC;AAI7D,UAAM,cAAc,eAAe,SAAY,KAAK,iBAAiB,OAAO;AAI5E,UAAM,YAAY,eACd,aAAa,KAAK,OAAO,KAAK,KAAK,SAAS,CAAC,IAC7C,qBAAqB,KAAK,OAAO,KAAK,OAAO;AAKjD,QAAI,OAAO,WAAW,GAAG;AACvB,YAAM,QAAQ,OAAO,CAAC;AACtB,YAAM,oBAAoB,QAAQ,SAAS,IACvC,SACA,uBAAuB,KAAK,aAAa,KAAK;AAClD,aAAO,KAAK,eAAe;AAAA,QACzB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,WAAO,KAAK,eAAe;AAAA,MACzB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoCA,OAAO,MAAsD;AAC3D,UAAM,YAAiC,CAAC;AACxC,QAAI,KAAK,OAAO,WAAW;AACzB,YAAM,YAAY,KAAK,OAAO,UAAU,KAAK,KAAK,MAAM;AACxD,gBAAU,KAAK,EAAE,WAAW,CAAC,OAAmB,UAAU,EAAE,EAAE,CAAC;AAAA,IACjE;AACA,WAAO,KAAK,eAAe;AAAA,MACzB,MAAM,KAAK,QAAQ;AAAA,MACnB;AAAA,MACA;AAAA,MACA,KAAK,OAAO;AAAA,IACd;AAAA,EACF;AACF;;;ACljBO,IAAM,oBAAN,MAA0F;AAAA,EAc/F,MAAM,UACJ,aACyC;AAMzC,QAAI,MAAM,QAAQ,WAAW,EAAG,QAAO,KAAK,cAAc,WAAoC;AAK9F,UAAM,OAAa,OAAO,gBAAgB,aACrC,YAAqD,cAAoD,IAIzG;AACL,SAAK,KAAK,0BAA0B,IAAI;AACxC,UAAM,OAAO,OAAO,KAAK,IAAI;AAG7B,UAAM,QAAiC,CAAC;AACxC,eAAW,OAAO,MAAM;AACtB,YAAM,GAAG,IAAI,KAAK,GAAG,EAAG,KAAK;AAAA,IAC/B;AAMA,qBAAiB,UAAU,MAAM;AAC/B,iBAAW,OAAO,MAAM;AACtB,cAAM,GAAG,IAAI,KAAK,GAAG,EAAG,KAAK,MAAM,GAAG,GAAG,MAAM;AAAA,MACjD;AAAA,IACF;AAEA,UAAM,SAAkC,CAAC;AACzC,eAAW,OAAO,MAAM;AACtB,aAAO,GAAG,IAAI,KAAK,GAAG,EAAG,SAAS,MAAM,GAAG,CAAC;AAAA,IAC9C;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,MAAc,cAAc,OAAkD;AAC5E,eAAW,QAAQ,MAAO,MAAK,KAAK,0BAA0B,IAAI;AAClE,UAAM,cAAc,MAAM,IAAI,CAAC,SAAS,OAAO,KAAK,IAAI,CAAC;AACzD,UAAM,SAAoC,MAAM,IAAI,CAAC,MAAM,MAAM;AAC/D,YAAM,QAAiC,CAAC;AACxC,iBAAW,OAAO,YAAY,CAAC,EAAI,OAAM,GAAG,IAAI,KAAK,GAAG,EAAG,KAAK;AAChE,aAAO;AAAA,IACT,CAAC;AAED,qBAAiB,UAAU,MAAM;AAC/B,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,cAAM,OAAO,MAAM,CAAC;AACpB,cAAM,QAAQ,OAAO,CAAC;AACtB,mBAAW,OAAO,YAAY,CAAC,EAAI,OAAM,GAAG,IAAI,KAAK,GAAG,EAAG,KAAK,MAAM,GAAG,GAAG,MAAM;AAAA,MACpF;AAAA,IACF;AAEA,WAAO,MAAM,IAAI,CAAC,MAAM,MAAM;AAC5B,YAAM,SAAkC,CAAC;AACzC,iBAAW,OAAO,YAAY,CAAC,EAAI,QAAO,GAAG,IAAI,KAAK,GAAG,EAAG,SAAS,OAAO,CAAC,EAAG,GAAG,CAAC;AACpF,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EA0BA,QAAQ,KAAsC,MAAiE;AAC7G,WAAO,IAAI;AAAA,MACT;AAAA,MACA;AAAA,MACA,MAAM,aAAa;AAAA,MACnB,KAAK;AAAA,IACP;AAAA,EACF;AACF;AAgBO,IAAM,kCAAkC;AAiB/C,SAAS,aAAa,OAAe,OAAwB;AAC3D,SAAO,GAAG,KAAK,IAAI,UAAU,SAAY,cAAc,KAAK,UAAU,KAAK,CAAC;AAC9E;AAUO,IAAM,kBAAN,MAKL;AAAA,EACA,YACmB,QACA,KACA,WACA,KACjB;AAJiB;AACA;AACA;AACA;AAEjB,QAAI,CAAC,OAAO,UAAU,SAAS,KAAK,YAAY,GAAG;AACjD,YAAM,IAAI;AAAA,QACR,kCAAkC,OAAO,SAAS,CAAC;AAAA,MAErD;AAAA,IACF;AAAA,EACF;AAAA,EAXmB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EA0BnB,MAAM,UACJ,aACkD;AAClD,UAAM,MACJ,OAAO,gBAAgB,aAClB,YAAqD,cAAoD,IAC1G;AAMN,UAAM,OAAmB,qBAAqB,KAAK,MAAM,KAAK,IAAI,aAAa,GAAG,IAAI,KAAK,KAAK,GAAG;AACnG,UAAM,OAAO,OAAO,KAAK,IAAI;AAE7B,UAAM,QAAQ,aAAa,KAAK,GAAG;AACnC,UAAM,UAAmC,eAAe,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;AAClF,UAAM,SAAS,oBAAI,IAAmE;AAEtF,qBAAiB,UAAU,KAAK,QAAQ;AAItC,YAAM,MAAM,QAAQ,WAAW,IAAI,SAAS,qBAAqB,CAAC,MAAM,GAAG,OAAO,EAAE,CAAC;AACrF,YAAM,WAAW,SAAS,KAAK,KAAK;AACpC,YAAM,WAAW,aAAa,OAAO,QAAQ;AAC7C,UAAI,QAAQ,OAAO,IAAI,QAAQ;AAC/B,UAAI,UAAU,QAAW;AACvB,YAAI,OAAO,QAAQ,KAAK,WAAW;AAGjC,gBAAM,IAAI,sBAAsB,OAAO,OAAO,OAAO,GAAG,KAAK,WAAW,MAAM;AAAA,QAChF;AACA,cAAM,QAAiC,CAAC;AACxC,mBAAW,KAAK,KAAM,OAAM,CAAC,IAAI,KAAK,CAAC,EAAG,KAAK;AAC/C,gBAAQ,EAAE,UAAU,MAAM;AAC1B,eAAO,IAAI,UAAU,KAAK;AAAA,MAC5B;AACA,iBAAW,KAAK,KAAM,OAAM,MAAM,CAAC,IAAI,KAAK,CAAC,EAAG,KAAK,MAAM,MAAM,CAAC,GAAG,GAAG;AAAA,IAC1E;AAEA,UAAM,MAA+C,CAAC;AACtD,eAAW,SAAS,OAAO,OAAO,GAAG;AAGnC,YAAM,SAAkC,EAAE,CAAC,KAAK,GAAG,MAAM,SAAS;AAClE,iBAAW,KAAK,KAAM,QAAO,CAAC,IAAI,KAAK,CAAC,EAAG,SAAS,MAAM,MAAM,CAAC,CAAC;AAClE,UAAI,KAAK,MAA+C;AAAA,IAC1D;AACA,WAAO;AAAA,EACT;AACF;;;ACxRA,IAAI,OAAO;AAGJ,SAAS,gBAAsB;AACpC,MAAI,KAAM;AACV,SAAO;AACP,iBAAe,MAAM,WAAW,kBAAkB;AAClD,iBAAe,MAAM,WAAW,aAAa;AAC7C,iBAAe,YAAY,WAAW,iBAAiB;AACzD;","names":[]}
|
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BLOB_CHUNKS_COLLECTION,
|
|
3
|
+
BLOB_COLLECTION,
|
|
3
4
|
BLOB_INDEX_COLLECTION
|
|
4
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-ANWPSTZU.js";
|
|
5
6
|
import {
|
|
6
7
|
installViaBinder
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-Z5HTYMMQ.js";
|
|
8
9
|
import {
|
|
9
|
-
buildRecordAad
|
|
10
|
-
|
|
10
|
+
buildRecordAad,
|
|
11
|
+
openEnvelopeJson
|
|
12
|
+
} from "./chunk-KJFW62P4.js";
|
|
11
13
|
import {
|
|
12
14
|
buildRecordEnvelope
|
|
13
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-SEE332EV.js";
|
|
14
16
|
import {
|
|
15
17
|
encrypt
|
|
16
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-UYB234ZR.js";
|
|
17
19
|
import {
|
|
20
|
+
TamperedError,
|
|
18
21
|
ValidationError
|
|
19
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-244VXYPG.js";
|
|
20
23
|
|
|
21
24
|
// src/via/blob/binding.ts
|
|
22
25
|
async function eraseBlobs(ctx, cfg) {
|
|
@@ -48,6 +51,8 @@ function blobVia(cfg) {
|
|
|
48
51
|
// vault.forget().
|
|
49
52
|
posture: { encryptedAtRest: "envelope", queryable: "none", exportable: true, forgettable: true },
|
|
50
53
|
covers: (field) => field in cfg.fields,
|
|
54
|
+
coveredFields: Object.keys(cfg.fields),
|
|
55
|
+
// #1447
|
|
51
56
|
erase: (ctx) => eraseBlobs(ctx, cfg),
|
|
52
57
|
describeFragment: () => buildBlobDescribeFragment(cfg.fields)
|
|
53
58
|
};
|
|
@@ -211,6 +216,35 @@ async function reportOrphanBlobChunks(adapter, vault) {
|
|
|
211
216
|
};
|
|
212
217
|
}
|
|
213
218
|
|
|
219
|
+
// src/with-shape/blobs/legacy-sweep.ts
|
|
220
|
+
async function sweepUnreferencedLegacyBlobs(ctx, reclaim) {
|
|
221
|
+
let blobs = 0, chunks = 0, reclaimed = 0;
|
|
222
|
+
const eTags = await ctx.adapter.list(ctx.vault, BLOB_INDEX_COLLECTION);
|
|
223
|
+
const dek = ctx.encrypted ? await ctx.getDEK(BLOB_COLLECTION) : null;
|
|
224
|
+
for (const eTag of eTags) {
|
|
225
|
+
const env = await ctx.adapter.get(ctx.vault, BLOB_INDEX_COLLECTION, eTag);
|
|
226
|
+
if (!env) continue;
|
|
227
|
+
let blob;
|
|
228
|
+
try {
|
|
229
|
+
const json = dek ? await openEnvelopeJson({ collection: BLOB_INDEX_COLLECTION, id: eTag }, env, dek) : env["_data"];
|
|
230
|
+
blob = JSON.parse(json);
|
|
231
|
+
} catch (err) {
|
|
232
|
+
if (err instanceof TamperedError) continue;
|
|
233
|
+
throw err;
|
|
234
|
+
}
|
|
235
|
+
if (Object.hasOwn(blob, "_cek") || blob.refCount > 0) continue;
|
|
236
|
+
blobs += 1;
|
|
237
|
+
chunks += blob.chunkCount;
|
|
238
|
+
if (!reclaim) continue;
|
|
239
|
+
for (let i = 0; i < blob.chunkCount; i++) {
|
|
240
|
+
await ctx.adapter.delete(ctx.vault, BLOB_CHUNKS_COLLECTION, `${eTag}_${i}`);
|
|
241
|
+
}
|
|
242
|
+
await ctx.adapter.delete(ctx.vault, BLOB_INDEX_COLLECTION, eTag);
|
|
243
|
+
reclaimed += 1;
|
|
244
|
+
}
|
|
245
|
+
return { blobs, chunks, reclaimed };
|
|
246
|
+
}
|
|
247
|
+
|
|
214
248
|
// src/with-shape/blobs/blob-compaction.ts
|
|
215
249
|
var BLOB_EVICTION_AUDIT_COLLECTION = "_blob_eviction_audit";
|
|
216
250
|
async function runCompaction(ctx, options = {}) {
|
|
@@ -218,6 +252,7 @@ async function runCompaction(ctx, options = {}) {
|
|
|
218
252
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
219
253
|
const maxEvictions = options.maxEvictions ?? Infinity;
|
|
220
254
|
const dryRun = options.dryRun === true;
|
|
255
|
+
const unreferencedLegacyBlobs = await sweepUnreferencedLegacyBlobs(ctx, options.reclaimLegacyBlobs === true && !dryRun);
|
|
221
256
|
if (options.cacheBudget !== void 0 && (!Number.isFinite(options.cacheBudget.maxBytes) || options.cacheBudget.maxBytes < 0)) {
|
|
222
257
|
throw new ValidationError("compact(): cacheBudget.maxBytes must be a non-negative finite number (#808)");
|
|
223
258
|
}
|
|
@@ -300,7 +335,8 @@ async function runCompaction(ctx, options = {}) {
|
|
|
300
335
|
budgetEvicted,
|
|
301
336
|
budgetBytesFreed,
|
|
302
337
|
byCollection,
|
|
303
|
-
orphanBlobChunks
|
|
338
|
+
orphanBlobChunks,
|
|
339
|
+
unreferencedLegacyBlobs
|
|
304
340
|
};
|
|
305
341
|
}
|
|
306
342
|
async function runBudgetPass(ctx, maxBytes, opts) {
|
|
@@ -438,4 +474,4 @@ export {
|
|
|
438
474
|
BLOB_EVICTION_AUDIT_COLLECTION,
|
|
439
475
|
runCompaction
|
|
440
476
|
};
|
|
441
|
-
//# sourceMappingURL=chunk-
|
|
477
|
+
//# sourceMappingURL=chunk-GF5BZ7D4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/via/blob/binding.ts","../src/port/with/blob-strategy.ts","../src/with-shape/blobs/export-blobs.ts","../src/with-shape/blobs/orphan-report.ts","../src/with-shape/blobs/legacy-sweep.ts","../src/with-shape/blobs/blob-compaction.ts"],"sourcesContent":["/**\n * The blob `NoydbVia` (#629 Task 7) — wires the `blobFields` declaration\n * into the kernel's generic Via port as a deliberately THIN binding:\n * declaration + posture + `describeFragment` + `erase` glue, and nothing\n * else. Blob content is fully out-of-band (`collection.blob(id)` →\n * `BlobSet` writing `_blob_*` side-collections; it never touches\n * `_putInternal` or the record codec), so this binding declares **no\n * write/read pipeline hooks** — in particular no `encodeAtRest`/\n * `decodeAtRest`: a blobFields collection must not flip the pipeline's\n * `hasAtRestHooks`, or the codec would abandon its inline seal path for a\n * feature that never seals record fields. The routing/compaction/TTL/\n * legal-hold/exportBlobs machinery stays service-side in\n * `with-shape/blobs/` (the i18n-dictionary precedent — that machinery\n * does real chunk AEAD + key-lifecycle work `via-enclave-isolation`\n * forbids under `via/*`).\n *\n * Like classified (see `port/with/classified-strategy.ts`), the link is\n * EAGER — `port/with/blob-strategy.ts` calls {@link linkBlobVia} at module\n * load. `blobFields` policies are plain object literals (there is no\n * `blob.*()` declaration factory to hang a lazy link on), so the binder\n * must be installed before `compileVias` ever sees one.\n *\n * There is no `declare`-time validation: `blobFields` has never had a\n * construction-time refusal matrix (policies are consulted lazily by\n * `vault.compact()` and `BlobSet.put`), and inventing one here would\n * break the behavior lock.\n *\n * `covers` (#629 Task 8) is a passive coverage predicate, not a pipeline\n * hook — it lets `ViaPipeline.postureFor` recognize a `blobFields` slot so\n * `.where()`/`.orderBy()`/`.aggregate()` can refuse it (posture\n * `queryable: 'none'`); it does not participate in clause building or\n * evaluation.\n */\nimport type { NoydbVia, ViaEraseCtx, ViaEraseReport } from '../../kernel/via/index.js'\nimport { installViaBinder } from '../../kernel/via/index.js'\nimport type { BlobFieldsConfig } from '../../with-shape/blobs/blob-compaction.js'\n\n/**\n * Config a blob collection's `blobFields` declaration resolves to — the\n * binding's construction input. `purgeBlobsForRecord` is an OPTIONAL\n * vault/service-provided closure for the `erase` hook, mapping\n * `collection.blob(id).shredAllForRecord()`'s `{shredded, retainedShared,\n * residue}: string[]` accounting onto `ViaEraseReport` — `shredded`/\n * `retainedShared` become counts, each residue eTag becomes a tagged\n * `{kind: 'blob-residue', eTag}` entry.\n *\n * DELIBERATELY left unwired by `compileVias` (#629 Task 10, resolved\n * within parity-first — see task-10-report.md \"blob purge\" section):\n * `per-blob-cek.test.ts`/`forget.test.ts` prove blob-shred-on-forget is\n * gated SOLELY on the vault's `blobsStrategy` being configured, never on a\n * given collection's OWN `blobFields` declaration (`.blob(id)` is callable,\n * and IS exercised by those tests, on collections with no `blobFields` at\n * all). Routing it exclusively through this binding would silently stop\n * blob crypto-shredding for every such collection; `vault.forget()` keeps\n * calling `collection.blob(id).shredAllForRecord()` directly for that\n * reason. The hook stays real/tested (see `blob-binding.test.ts`) for a\n * future caller that wants it.\n */\nexport interface BlobViaConfig {\n readonly fields: BlobFieldsConfig\n readonly collectionName: string\n /** Purge one record's blob slots (crypto-shred participation). Returns the erase report for `forget()`'s summary. */\n readonly purgeBlobsForRecord?: (id: string) => Promise<ViaEraseReport>\n}\n\n/**\n * `forget()`'s per-ref blob-purge participation: delegate to the wired\n * closure, or report a clean zero-shredded, zero-residue no-op — the\n * default in production too (#629 Task 10 deliberately leaves this unwired;\n * see `BlobViaConfig`'s doc comment).\n */\nasync function eraseBlobs(ctx: ViaEraseCtx, cfg: BlobViaConfig): Promise<ViaEraseReport> {\n if (cfg.purgeBlobsForRecord) return cfg.purgeBlobsForRecord(ctx.id)\n return { shredded: 0, residue: [] }\n}\n\n/**\n * One field's blob knobs as they appear on `describeFragment()`'s payload\n * (#657 — the second real `NoydbVia.describeFragment` consumer after\n * `lookup`'s; see `with-shape/introspection/describe.ts`'s `buildDescription`).\n * Declarative scalars (`retainDays`/`external`/`public`/`backlink`) verbatim;\n * predicate knobs (`evictWhen`/`legalHold`/`retainUntil` — functions over the\n * decrypted record) as presence flags, since a predicate has no serializable\n * form — mirrors `buildBlobDescribeFragment`'s per-field shape below.\n */\nexport interface BlobDescribeFragmentEntry {\n readonly retainDays?: number\n readonly evictWhen?: true\n readonly legalHold?: true\n readonly retainUntil?: true\n readonly external?: true\n readonly public?: true\n readonly backlink?: string\n}\n\n/** The `'blob'` binding's `describeFragment()` payload shape. */\nexport interface BlobDescribeFragment {\n readonly blobFields: Record<string, BlobDescribeFragmentEntry>\n}\n\n/**\n * `{ blobFields: { <field>: <knobs> } }` — declarative scalars\n * (`retainDays`/`external`/`public`/`backlink`) verbatim; predicate knobs\n * (`evictWhen`/`legalHold`/`retainUntil` — functions over the decrypted\n * record) as presence flags, since a predicate has no serializable form.\n */\nfunction buildBlobDescribeFragment(fields: BlobFieldsConfig): Record<string, unknown> {\n return {\n blobFields: Object.fromEntries(\n Object.entries(fields).map(([field, p]) => [field, {\n ...(p.retainDays !== undefined ? { retainDays: p.retainDays } : {}),\n ...(p.evictWhen !== undefined ? { evictWhen: true } : {}),\n ...(p.legalHold !== undefined ? { legalHold: true } : {}),\n ...(p.retainUntil !== undefined ? { retainUntil: true } : {}),\n ...(p.external === true ? { external: true } : {}),\n ...(p.public === true ? { public: true } : {}),\n ...(p.backlink !== undefined ? { backlink: p.backlink } : {}),\n }]),\n ),\n }\n}\n\nexport function blobVia(cfg: BlobViaConfig): NoydbVia {\n return {\n brand: 'blob',\n // encryptedAtRest: 'envelope' — blob chunks are AEAD-encrypted envelopes\n // in `_blob_chunks` (service-side), not sealed record fields; queryable:\n // 'none' — nothing indexes blob content; exportable — vault.exportBlobs()\n // is a first-class door; forgettable — shredAllForRecord participates in\n // vault.forget().\n posture: { encryptedAtRest: 'envelope', queryable: 'none', exportable: true, forgettable: true },\n covers: (field) => field in cfg.fields,\n coveredFields: Object.keys(cfg.fields), // #1447\n erase: (ctx) => eraseBlobs(ctx, cfg),\n describeFragment: () => buildBlobDescribeFragment(cfg.fields),\n }\n}\n\nexport function linkBlobVia(): void {\n installViaBinder('blob', (c) => blobVia(c as BlobViaConfig))\n}\n","/**\n * Strategy seam between core Collection and the optional blob service\n * (#629 Task 7 — moved out of `with-shape/blobs/strategy.ts`, precedent:\n * `port/with/classified-strategy.ts`/`i18n-strategy.ts`). Lives on the\n * `/with` port (the one seam the kernel spine may import statically) so\n * `Collection`/`Vault` can hold the `NO_BLOBS` default without a\n * spine→service static import.\n *\n * Core imports `BlobsStrategy` as a TYPE-ONLY symbol and `NO_BLOBS` as a\n * minimal runtime stub. Neither pulls in the heavy `BlobSet` / chunk /\n * MIME machinery — those only arrive when the consumer explicitly\n * imports `@noy-db/hub/blobs` (see `via/blob/index.ts` →\n * `withBlobs()` factory).\n *\n * This file is intentionally tiny so the bundler keeps it in the graph\n * without dragging everything else in.\n *\n * @internal\n */\n\nimport type { BlobSet } from '../../with-shape/blobs/blob-set.js'\nimport type { NoydbStore } from '../../kernel/types.js'\nimport type { ObjectProjection } from '../../with-shape/blobs/object-projection.js'\nimport type { BlobFieldsConfig } from '../../with-shape/blobs/blob-compaction.js'\nimport type { EnclaveKey } from '../../kernel/enclave/index.js'\nimport type { BlobCacheStats } from '../../with-shape/blobs/blob-pinning.js'\nimport type { UnlockedKeyring } from '../../with-party/team/keyring.js'\nimport { linkBlobVia } from '../../via/blob/binding.js'\n\n// Install the blob Via binder EAGERLY (#629 Task 7) — `blobFields` policies\n// are plain object literals (there is no `blob.*()` declaration factory to\n// hang a lazy money/i18n-style link on), so the binder must be installed\n// before `compileVias` ever compiles one. This port module is\n// already unconditionally imported by the kernel spine (for the `NO_BLOBS`\n// strategy default below), so linking here guarantees it — same rationale\n// as `port/with/classified-strategy.ts`'s eager `linkClassifiedVia()`. The\n// binding module is hook-free glue (declare/describe/erase) and imports no\n// machinery, so the eager cost is a few hundred bytes, not `BlobSet`.\nlinkBlobVia()\n\n/**\n * Args forwarded by `Collection.blob(id)` to the active strategy's\n * `openSlot`. The strategy is responsible for returning a live\n * `BlobSet` bound to the given record.\n *\n * @internal\n */\nexport interface BlobsStrategyOpenArgs {\n readonly store: NoydbStore\n readonly vault: string\n readonly collection: string\n readonly recordId: string\n readonly getDEK: (collectionName: string) => Promise<EnclaveKey>\n readonly encrypted: boolean\n readonly userId: string\n /**\n * Collection opts into per-record keys (`perRecordKeys`), so its blobs are\n * erasable: new uploads mint a per-blob content CEK (crypto-shreddable at\n * `refCount → 0`). Off → legacy shared-`_blob`-DEK chunks. See the per-blob\n * CEK design spec.\n */\n readonly erasableBlobs?: boolean\n /**\n * #1452 — refuse a content-changing blob verb (`put`/`delete`/`publish`/\n * `deleteVersion`/`adoptExternal`) when the OWNING RECORD is not writable:\n * the collection runs its `beforePut` gate bus (closed periods, record\n * guards) with `incoming = existing`, so an attachment change is gated\n * exactly as an update of the record would be. Absent → ungated (direct\n * `BlobSet` construction in tests).\n */\n readonly assertOwnerWritable?: () => Promise<void>\n /**\n * Collection has `tiers` active (#724 I1 completion). Combined with\n * `erasableBlobs` false, a content write is refused —\n * `UnsupportedTierCompositionError` — since a legacy blob has no\n * per-blob `_cek` and can never be tier-isolated on elevate/demote.\n */\n readonly tiersActive?: boolean\n /**\n * Vault is in debug-plaintext mode (`encrypt: false` + `debugPlaintext: true`).\n * Blobs are then written as a single un-gzipped object (one base64 chunk) so\n * the stored object is directly recoverable with native tools (`base64 -d`),\n * matching the directly-inspectable record layout. Dev-only — see the\n * plaintext/debug-store-mode design.\n */\n readonly debugPlaintext?: boolean\n /**\n * Object projection for `external` blob fields (`createNoydb({ objectStore })`).\n * When present, fields declared `external` in `blobFields` route their raw\n * bytes here instead of the encrypted-chunk path.\n */\n readonly objectStore?: ObjectProjection\n /** Per-collection blob field policies — used to resolve `external` / `public`. */\n readonly blobFields?: BlobFieldsConfig\n /**\n * The caller's unlocked keyring (#749). Threaded through so `BlobSet.atTier()`\n * can run `assertTierAccess` — the SAME clearance gate `putAtTier`/`elevate`/\n * `demote` run before any tier `getDEK` call — rather than relying on a bare\n * `getDEK`, which auto-mints a DEK for ANY role and never throws (verified: it\n * is not itself a gate). Omitted only by a construction path that never calls\n * `atTier()`.\n */\n readonly keyring?: UnlockedKeyring\n}\n\n/**\n * The seam interface. `@internal` — do not build public APIs on this\n * shape; it can evolve freely until blobs are extracted into their\n * own package, at which point it will be promoted to public.\n *\n * @internal\n */\nexport interface BlobsStrategy {\n openSlot(args: BlobsStrategyOpenArgs): BlobSet\n /**\n * Snapshot of this device's blob-cache KPI counters: local-read hits,\n * network misses, and total bytes downloaded from the object store (#808).\n *\n * Lives on the seam rather than on the `withBlobs()` return type so the\n * service has exactly one name (#844b) — `NO_BLOBS` answers with zeros,\n * which is the truthful reading of \"blobs were never enabled\".\n */\n cacheStats(): BlobCacheStats\n}\n\n/**\n * Default strategy for collections that did not opt into blob storage.\n * Every operation surfaces an actionable error that points the caller\n * at the opt-in path.\n *\n * @internal\n */\nexport const NO_BLOBS: BlobsStrategy = {\n openSlot() {\n throw new Error(\n 'Blob storage is not enabled on this Noydb instance. ' +\n 'Import `{ withBlobs }` from \"@noy-db/hub/blobs\" and pass `withBlobs()` to `createNoydb({ blobsStrategy: withBlobs() })`.',\n )\n },\n cacheStats() {\n return { hits: 0, misses: 0, bytesDownloaded: 0 }\n },\n}\n","/**\n * `vault.exportBlobs()` — bulk blob extraction primitive.\n *\n * Async-iterable handle over every blob attached to records in a\n * vault, optionally filtered by collection allowlist and per-record\n * predicate. Emits tuples of `{ blobId, recordRef, bytes, meta }` so\n * the consumer can pipe into any sink (zip stream, S3 multipart, USB\n * copy, cold-storage tape) without pulling the whole export into\n * memory.\n *\n * ## Auth + audit\n *\n * - Capability check runs **once** at handle creation via\n * `Vault.assertCanExport('plaintext', 'blob')`. An operator whose\n * keyring lacks that bit fails before a single byte of ciphertext\n * is decrypted.\n * - Audit entry lands in `_export_audit` at handle creation: the\n * actor, start timestamp, target collections, predicate presence,\n * and batch mechanism. **No content hashes** — per the spec\n * non-correlation invariant.\n *\n * ## Abort + resume\n *\n * - `handle.abort()` flips the internal signal; the next iteration\n * boundary throws `AbortError`. Consumers already in `for await`\n * can catch and exit cleanly.\n * - Restart after a partial failure with `{ afterBlobId }` — the\n * iterator skips tuples up to (and including) that blob id before\n * yielding again. Combined with a blob-count ceiling it supports\n * idempotent batch re-runs.\n *\n * @module\n */\n\nimport type { Collection } from '../../kernel/collection.js'\nimport type { SlotInfo } from '../../kernel/types.js'\n\n// ─── Types ──────────────────────────────────────────────────────────────\n\nexport interface ExportBlobsOptions {\n /**\n * Collection allowlist. Omit to export blobs from every collection\n * the caller has read access to.\n */\n readonly collections?: readonly string[]\n /**\n * Per-record predicate. Called on the decrypted record BEFORE any\n * blob bytes are read for that record — returning false skips the\n * record and all its slots without touching their chunks.\n */\n readonly where?: (record: unknown, context: { collection: string; id: string }) => boolean\n /**\n * Resume after a specific blob id. The iterator skips tuples up to\n * and including this id, then yields. Format of the id is the same\n * as `ExportedBlob.blobId` (the HMAC-keyed eTag).\n */\n readonly afterBlobId?: string\n /**\n * External abort signal. When fired, the next iterator tick throws\n * `ExportBlobsAbortedError`. Honored alongside `handle.abort()`.\n */\n readonly signal?: AbortSignal\n}\n\nexport interface ExportedBlob {\n /** Opaque blob identifier — HMAC-keyed eTag, stable across vaults. */\n readonly blobId: string\n /** Where this blob came from in the vault. */\n readonly recordRef: {\n readonly collection: string\n readonly id: string\n readonly slot: string\n }\n /** Decrypted plaintext bytes. */\n readonly bytes: Uint8Array\n /** Best-effort metadata (from the blob slot record). */\n readonly meta: {\n readonly size: number\n /**\n * User-visible filename stored on the slot. Often equal to the\n * slot name; differs when the caller supplied an explicit\n * `filename` to `BlobSet.put()`.\n */\n readonly filename: string\n readonly mimeType?: string\n readonly createdAt?: string\n }\n}\n\nexport interface ExportBlobsHandle extends AsyncIterable<ExportedBlob> {\n /** Abort the export. Safe to call multiple times. */\n abort(): void\n /** True once `abort()` has fired or the external signal aborted. */\n readonly aborted: boolean\n}\n\nexport class ExportBlobsAbortedError extends Error {\n constructor(reason: string) {\n super(`exportBlobs aborted: ${reason}`)\n this.name = 'ExportBlobsAbortedError'\n }\n}\n\n// ─── Audit ──────────────────────────────────────────────────────────────\n\nexport const EXPORT_AUDIT_COLLECTION = '_export_audit'\n\nexport interface ExportBlobsAuditEntry {\n readonly id: string\n readonly mechanism: 'exportBlobs'\n readonly actor: string\n readonly startedAt: string\n readonly collections: readonly string[] | null\n readonly predicate: boolean\n readonly afterBlobId: string | null\n}\n\n// ─── Implementation ─────────────────────────────────────────────────────\n\n/**\n * Build the handle. Factored out of `Vault.exportBlobs` so the\n * implementation can be unit-tested without going through the\n * compartment lifecycle.\n */\nexport function createExportBlobsHandle(\n actor: string,\n listAccessibleCollections: () => Promise<string[]>,\n getCollection: <T>(name: string) => Collection<T>,\n writeAudit: (entry: ExportBlobsAuditEntry) => Promise<void>,\n options: ExportBlobsOptions,\n): ExportBlobsHandle {\n let aborted = false\n\n const abort = (): void => {\n aborted = true\n }\n\n if (options.signal) {\n if (options.signal.aborted) aborted = true\n options.signal.addEventListener('abort', () => { aborted = true })\n }\n\n function assertLive(): void {\n if (aborted) throw new ExportBlobsAbortedError('aborted by caller')\n }\n\n const allowlist = options.collections ? new Set(options.collections) : null\n\n // Write the audit entry BEFORE the first yield so a blocked\n // iteration still leaves an audit trail that the export started.\n let auditPromise: Promise<void> | null = null\n function writeAuditOnce(): Promise<void> {\n if (!auditPromise) {\n auditPromise = writeAudit({\n id: generateBatchId(),\n mechanism: 'exportBlobs',\n actor,\n startedAt: new Date().toISOString(),\n collections: options.collections ?? null,\n predicate: Boolean(options.where),\n afterBlobId: options.afterBlobId ?? null,\n })\n }\n return auditPromise\n }\n\n async function* generate(): AsyncGenerator<ExportedBlob> {\n await writeAuditOnce()\n assertLive()\n\n // Resolve target collections lazily — also keeps the call async.\n const allCollections = await listAccessibleCollections()\n const targets = allCollections.filter(name => {\n if (name.startsWith('_')) return false\n if (allowlist && !allowlist.has(name)) return false\n return true\n })\n\n let resumeCursorHit = options.afterBlobId === undefined\n\n for (const collectionName of targets) {\n if (aborted) return\n\n const coll = getCollection<Record<string, unknown>>(collectionName)\n const records = await coll.list().catch(() => [])\n for (const record of records) {\n if (aborted) return\n assertLive()\n\n const idField = (record as { id?: unknown }).id\n if (typeof idField !== 'string') continue\n\n if (options.where && !options.where(record, { collection: collectionName, id: idField })) continue\n\n const blobSet = coll.blob(idField)\n const slots = await blobSet.list().catch(() => [] as SlotInfo[])\n for (const slot of slots) {\n if (aborted) return\n\n if (!resumeCursorHit) {\n if (slot.eTag === options.afterBlobId) {\n resumeCursorHit = true\n }\n continue\n }\n\n const bytes = await blobSet.get(slot.name)\n if (!bytes) continue\n\n const item: ExportedBlob = {\n blobId: slot.eTag,\n recordRef: { collection: collectionName, id: idField, slot: slot.name },\n bytes,\n meta: {\n size: slot.size,\n filename: slot.filename,\n ...(slot.mimeType !== undefined && { mimeType: slot.mimeType }),\n ...(slot.uploadedAt !== undefined && { createdAt: slot.uploadedAt }),\n },\n }\n yield item\n }\n }\n }\n }\n\n const handle: ExportBlobsHandle = {\n abort,\n get aborted() { return aborted },\n [Symbol.asyncIterator]: () => generate(),\n }\n return handle\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────\n\nfunction generateBatchId(): string {\n // 16 bytes of crypto randomness, URL-safe base64, no padding.\n const raw = globalThis.crypto.getRandomValues(new Uint8Array(16))\n let s = ''\n for (const b of raw) s += b.toString(16).padStart(2, '0')\n return `batch-${Date.now().toString(36)}-${s.slice(0, 12)}`\n}\n","/**\n * Orphan blob-chunk REPORT (#1133) — the read-only half of the residue #1127\n * stopped producing.\n *\n * Before #1127, `releaseRef` deleted a blob's index row BEFORE its chunks, so a\n * crash in between stranded chunk bodies nothing can ever reach: `loadBlobObject`\n * returns null so no reader addresses them, and `rekeyBlobSet` derives its chunk\n * ids from each index entry's `chunkCount` so a rotation never visits them. For a\n * LEGACY blob (bytes under the shared `_blob` DEK rather than a per-blob content\n * CEK) those bodies then stay openable under the retired DEK indefinitely.\n *\n * #1127 reversed the order, which removes the class at the source. It does\n * nothing for orphans created before the fix, and this module does not delete\n * them either. It counts them.\n *\n * ## Why this REPORTS and does not RECLAIM\n *\n * Deciding \"orphaned\" requires `store.list(_blob_index)`, and the store is\n * untrusted — that is the premise of the product, not a caveat on it. A store\n * that withholds a single index row makes a LIVE blob's chunks look orphaned. A\n * reclaim pass acting on that would convert **withholding**, which is reversible\n * the moment the store stops lying, into **permanent destruction** — handing an\n * untrusted store a data-loss primitive it does not currently have, in order to\n * tidy up residue.\n *\n * Reporting inverts the risk to something harmless: a lying store can only\n * inflate this count. Nothing is deleted on its word, so the worst outcome is an\n * operator investigating residue that turns out not to exist.\n *\n * Re-keying is not an alternative either: the chunk AAD is\n * `{eTag}:{chunkIndex}:{chunkCount}`, and `chunkCount` lives only in the index\n * row that is already gone — an orphan's AAD is unreconstructable, so it cannot\n * be decrypted-and-re-encrypted at all.\n */\n\nimport type { NoydbStore } from '../../kernel/types.js'\nimport { BLOB_INDEX_COLLECTION, BLOB_CHUNKS_COLLECTION } from './blob-set.js'\n\n/** How many distinct orphaned eTags to name in the report. */\nconst SAMPLE_LIMIT = 10\n\nexport interface OrphanChunkReport {\n /** Chunk rows whose eTag has no `_blob_index` entry. */\n readonly chunks: number\n /** Distinct eTags those chunks belong to. */\n readonly eTags: number\n /**\n * Up to {@link SAMPLE_LIMIT} of those eTags, so an operator can inspect a real\n * one rather than only a count. Deterministic (store list order), not random.\n */\n readonly sampleETags: readonly string[]\n /**\n * Chunk ids that do not match the `{eTag}_{index}` grammar at all. Counted\n * rather than skipped: an id this module cannot parse is a thing it cannot\n * classify either way, and reporting zero orphans while silently dropping\n * such ids would be the reassuring-but-wrong answer.\n */\n readonly unparseable: number\n}\n\n/**\n * Split a chunk id into `{eTag, index}`, or `null` if it does not fit the\n * grammar `BlobSet` writes (`${eTag}_${i}`).\n *\n * Splits on the LAST underscore and requires a digits-only suffix. eTags are\n * HMAC-SHA-256 hex today and contain no underscore, so a simpler split would\n * work — but it would break silently if that ever changed, and this rule holds\n * either way.\n */\nfunction parseChunkId(id: string): { eTag: string; index: number } | null {\n const cut = id.lastIndexOf('_')\n if (cut <= 0 || cut === id.length - 1) return null\n const suffix = id.slice(cut + 1)\n if (!/^\\d+$/.test(suffix)) return null\n return { eTag: id.slice(0, cut), index: Number(suffix) }\n}\n\n/**\n * Count chunk rows with no surviving index row. Read-only: two `list` calls and\n * no `get`, no `delete`, and no decryption — an orphan cannot be opened anyway.\n *\n * A store that cannot list either collection yields an empty report rather than\n * throwing: this is a diagnostic riding along with a maintenance pass, and it\n * must not be able to fail one.\n */\nexport async function reportOrphanBlobChunks(\n adapter: NoydbStore,\n vault: string,\n): Promise<OrphanChunkReport> {\n const empty: OrphanChunkReport = { chunks: 0, eTags: 0, sampleETags: [], unparseable: 0 }\n let liveETags: Set<string>\n let chunkIds: string[]\n try {\n liveETags = new Set(await adapter.list(vault, BLOB_INDEX_COLLECTION))\n chunkIds = await adapter.list(vault, BLOB_CHUNKS_COLLECTION)\n } catch {\n return empty\n }\n\n let chunks = 0\n let unparseable = 0\n const orphanETags = new Set<string>()\n for (const id of chunkIds) {\n const parsed = parseChunkId(id)\n if (parsed === null) { unparseable += 1; continue }\n if (liveETags.has(parsed.eTag)) continue\n chunks += 1\n orphanETags.add(parsed.eTag)\n }\n\n return {\n chunks,\n eTags: orphanETags.size,\n sampleETags: [...orphanETags].slice(0, SAMPLE_LIMIT),\n unparseable,\n }\n}\n","/**\n * #1453 — count, and optionally reclaim, legacy blobs nothing references.\n *\n * A legacy blob (no per-blob `_cek`) is left at refCount 0 \"for deferred GC\"\n * by `releaseRef`; before this the deferred GC had no verb outside\n * `forget()` (which needs `withForget`), and `orphanBlobChunks` could not see\n * these rows because they keep their index entry — so `vault.compact()`\n * reported clean over a vault whose blob storage grew on every overwrite.\n *\n * Reclaiming here is SAFE where reclaiming orphans is not (see\n * `orphan-report.ts`): the decision reads `refCount` off an AAD-bound index\n * row the hub decrypted itself, never the store's `list()`. A withholding\n * store cannot make a live blob look unreferenced; it can only hide a row from\n * this sweep, which then does nothing to it.\n *\n * Rows sealed at an elevated tier do not open under the flat `_blob` DEK and\n * are skipped, not counted: this is a tier-0 pass.\n */\nimport type { NoydbStore, BlobObject } from '../../kernel/types.js'\nimport { openEnvelopeJson, type EnclaveKey } from '../../kernel/enclave/index.js'\nimport { TamperedError } from '../../kernel/errors.js'\nimport { BLOB_COLLECTION, BLOB_INDEX_COLLECTION, BLOB_CHUNKS_COLLECTION } from './blob-set.js'\n\n/** What `vault.compact()` reports (and, on request, reclaims) about legacy blobs. */\nexport interface UnreferencedLegacyBlobReport {\n /** `_blob_index` rows with no `_cek` (legacy) and `refCount <= 0`. */\n readonly blobs: number\n /** Chunk rows those blobs still hold. */\n readonly chunks: number\n /** How many of those blobs this run deleted (chunks first, then the index row). */\n readonly reclaimed: number\n}\n\nexport async function sweepUnreferencedLegacyBlobs(\n ctx: { adapter: NoydbStore; vault: string; getDEK: (name: string) => Promise<EnclaveKey>; encrypted: boolean },\n reclaim: boolean,\n): Promise<UnreferencedLegacyBlobReport> {\n let blobs = 0, chunks = 0, reclaimed = 0\n const eTags = await ctx.adapter.list(ctx.vault, BLOB_INDEX_COLLECTION)\n const dek = ctx.encrypted ? await ctx.getDEK(BLOB_COLLECTION) : null\n for (const eTag of eTags) {\n const env = await ctx.adapter.get(ctx.vault, BLOB_INDEX_COLLECTION, eTag)\n if (!env) continue\n let blob: BlobObject\n try {\n // `encrypt: false` stores the body as plaintext JSON; read it as the\n // opaque string it is (no enclave field is interpreted here).\n const json = dek ? await openEnvelopeJson({ collection: BLOB_INDEX_COLLECTION, id: eTag }, env, dek) : env['_data']\n blob = JSON.parse(json) as BlobObject\n } catch (err) {\n if (err instanceof TamperedError) continue // elevated-tier row: not this pass's\n throw err\n }\n // A BlobObject with a wrapped content key is erasable and shreds eagerly at\n // refCount 0; only the key-less legacy shape ever lingers.\n if (Object.hasOwn(blob, '_cek') || blob.refCount > 0) continue\n blobs += 1\n chunks += blob.chunkCount\n if (!reclaim) continue\n // CHUNKS BEFORE INDEX — same ordering rule as `deleteChunksThenIndex` (#1127).\n for (let i = 0; i < blob.chunkCount; i++) {\n await ctx.adapter.delete(ctx.vault, BLOB_CHUNKS_COLLECTION, `${eTag}_${i}`)\n }\n await ctx.adapter.delete(ctx.vault, BLOB_INDEX_COLLECTION, eTag)\n reclaimed += 1\n }\n return { blobs, chunks, reclaimed }\n}\n","/**\n * Blob retention + compaction.\n *\n * Declarative per-collection / per-slot eviction policy. Two\n * triggers:\n *\n * - **`retainDays`** — age-based TTL. A slot uploaded more than N\n * days ago is evicted.\n * - **`evictWhen(record)`** — predicate over the **decrypted**\n * record. Lets consumers express \"the image is safe to drop once\n * the structured invoice has been reviewed and confirmed.\"\n *\n * Either trigger (or both) causes the slot to evict. Eviction removes\n * the slot entry from `_blob_slots_{collection}`, decrements the\n * blob's refCount (so unreferenced chunks can be GC'd by the next\n * sweep), and writes one entry to the `_blob_eviction_audit`\n * collection for tamper-evident record-keeping.\n *\n * The audit entry carries the eTag of the evicted blob (opaque HMAC\n * of plaintext under the vault's `_blob` DEK) — no plaintext leakage,\n * per the SPEC non-correlation invariant. Consumers reconstructing\n * \"what used to be attached\" can look up the audit entry by record\n * id.\n *\n * Compaction is **consumer-scheduled** — noy-db never runs a\n * background daemon. Call `vault.compact()` whenever your workflow\n * allows (cron, manual \"tidy\" button, cold-storage export prep, …).\n *\n * @module\n */\n\nimport type { NoydbStore, EncryptedEnvelope, SlotInfo } from '../../kernel/types.js'\nimport { ValidationError } from '../../kernel/errors.js'\nimport { buildRecordAad, buildRecordEnvelope, encrypt, type EnclaveKey } from '../../kernel/enclave/index.js'\nimport { reportOrphanBlobChunks, type OrphanChunkReport } from './orphan-report.js'\nimport { sweepUnreferencedLegacyBlobs, type UnreferencedLegacyBlobReport } from './legacy-sweep.js'\n\n// ─── Config types ───────────────────────────────────────────────────────\n\nexport interface BlobFieldPolicy<T = unknown> {\n /**\n * Via port brand marker — lets a `BlobFieldPolicy` satisfy the kernel's\n * opaque `ViaDescriptor` (#629 Task 7). Optional (the\n * `ClassifiedFieldSpec._viaBrand` precedent): `blobFields` policies are\n * plain object literals with no declaration factory to stamp it, so a\n * mandatory field would break every existing declaration.\n */\n readonly _viaBrand?: 'blob'\n /**\n * Age-based TTL in days. A slot whose `uploadedAt` is older than\n * `now - retainDays × 86400s` evicts on the next `vault.compact()`.\n * Omit to disable age-based eviction.\n */\n readonly retainDays?: number\n /**\n * Predicate evaluated against the decrypted record. When it returns\n * `true`, every matching slot on that record evicts. Omit to\n * disable predicate-based eviction.\n */\n readonly evictWhen?: (record: T) => boolean\n /**\n * **Legal hold.** When this predicate returns `true`, the slot is\n * never evicted — `retainDays`/`evictWhen` are overridden. Use for a\n * litigation / audit hold on a fiscal document: the blob stays until\n * the predicate returns `false` (the hold is released). Fail-closed:\n * if the predicate throws, the slot is treated as held.\n */\n readonly legalHold?: (record: T) => boolean\n /**\n * **Period-bound retention.** Returns the date (Date / ISO string /\n * epoch ms) until which the slot must be retained — typically derived\n * from the record's fiscal period (e.g. period end + 10 years). While\n * `now < retainUntil`, the slot is never evicted, regardless of\n * `retainDays`. Return `null`/`undefined` to impose no floor.\n * Fail-closed: a throwing function holds the slot.\n */\n readonly retainUntil?: (record: T) => Date | string | number | null | undefined\n /**\n * **External projection.** When `true`, this field's bytes are stored in the\n * vault's `ObjectProjection` (`createNoydb({ objectStore })`) as a single raw,\n * **unencrypted** object — servable directly from S3/CDN and processable by\n * native tooling — instead of the encrypted-chunk path. The encrypted slot\n * record remains the catalog (anchoring invariant). Requires an `objectStore`;\n * **outside the zero-knowledge guarantee** — use only for assets meant to\n * leave the vault. See the as-aws-s3 design spec.\n */\n readonly external?: boolean\n /**\n * For an `external` field: make the object world-readable (CDN origin) rather\n * than presigned-only. Default `false` (presigned). Ignored unless `external`.\n */\n readonly public?: boolean\n /**\n * For an `external` field: how to stamp a **backlink** (this record's\n * vault/collection/id/field) onto the object's metadata — the self-describing\n * \"secondary store\" that powers reconcile / DR / import re-pairing.\n * - `'opaque-token'` (default): a random id; preserves the opaque-bucket\n * property (no names leak); the token is also recorded on the slot.\n * - `'encrypted'`: the reference encrypted under the blob DEK (ZK-preserving;\n * falls back to `'opaque-token'` on a plaintext vault).\n * - `'plain'`: the reference in cleartext metadata — **leaks structure** to\n * bucket readers; only for non-sensitive deployments.\n * - `'none'`: no backlink.\n */\n readonly backlink?: 'opaque-token' | 'encrypted' | 'plain' | 'none'\n}\n\nexport type BlobFieldsConfig<T = unknown> = Record<string, BlobFieldPolicy<T>>\n\n// ─── Audit collection ──────────────────────────────────────────────────\n\nexport const BLOB_EVICTION_AUDIT_COLLECTION = '_blob_eviction_audit'\n\nexport interface BlobEvictionEntry {\n readonly id: string\n readonly collection: string\n readonly recordId: string\n readonly slotName: string\n readonly blobHash: string\n /** `'budget'` — evicted by the #808 cache-budget LRU pass, not a policy. */\n readonly reason: 'ttl' | 'predicate' | 'both' | 'budget'\n readonly evictedAt: string\n readonly actor: string\n}\n\n// ─── Compaction result ──────────────────────────────────────────────────\n\nexport interface CompactionResult {\n /** Number of blob slots evicted by the policy pass across all collections. */\n readonly evicted: number\n /** Number of records touched (iterated + policy checked). */\n readonly records: number\n /** Number of collections with `blobFields` configured. */\n readonly collections: number\n /**\n * Number of audit entries written — one per policy eviction plus one per\n * budget SLOT eviction (dropping a device-local cache copy writes none).\n */\n readonly auditEntries: number\n /**\n * Number of slots that would have evicted (TTL/predicate triggered)\n * but were retained by a `legalHold` or `retainUntil` floor.\n */\n readonly held: number\n /**\n * #808: number of slots whose due policy eviction was skipped because they\n * are pinned for offline on THIS device (pin state is device-local).\n */\n readonly pinned: number\n /**\n * #808: number of evictions performed by the `cacheBudget` LRU pass —\n * internal slots evicted through the standard eviction writer plus\n * device-local external cache copies dropped. 0 when no budget was given.\n */\n readonly budgetEvicted: number\n /** #808: plaintext bytes freed by the `cacheBudget` LRU pass. */\n readonly budgetBytesFreed: number\n /** Per-collection breakdown for diagnostics. */\n readonly byCollection: Record<string, { records: number; evicted: number }>\n /**\n * #1133 — chunk rows in `_blob_chunks` with no surviving `_blob_index` entry.\n * Pre-#1127 residue: unreachable by every reader AND by rotation, so for a\n * legacy blob the bytes stay openable under a retired `_blob` DEK.\n *\n * REPORTED, never reclaimed — deciding \"orphaned\" means trusting the store's\n * `list`, and a store that withholds one index row would have us destroy a\n * LIVE blob's chunks. See `orphan-report.ts` for the full argument. A vault\n * that has run `migrate()` has no legacy chunks to strand, and `chunks: 0`\n * is the expected reading everywhere else.\n */\n readonly orphanBlobChunks: OrphanChunkReport\n /**\n * #1453 — legacy (no `_cek`) blobs at `refCount <= 0`: released by every\n * reference-drop path but never reclaimed, because a legacy chunk has no\n * eager shred. Counted on every run; deleted only with\n * `reclaimLegacyBlobs: true`. Not an orphan in the `orphanBlobChunks`\n * sense — these rows keep their index entry, which is exactly why that\n * report could not see them.\n */\n readonly unreferencedLegacyBlobs: UnreferencedLegacyBlobReport\n}\n\n// ─── Core ──────────────────────────────────────────────────────────────\n\nexport interface CompactRunOptions {\n /** Override \"now\" for deterministic testing. */\n readonly now?: Date\n /**\n * Stop after this many evictions. Useful for capped batches / cron\n * jobs that need to fit in a time window. `undefined` = unbounded.\n */\n readonly maxEvictions?: number\n /**\n * Dry-run — evaluate policies and return the counts, but do NOT\n * delete slots or write audit entries. Lets a consumer preview\n * what would happen.\n */\n readonly dryRun?: boolean\n /**\n * #808 mobile cache budget: cap the locally-cached UNPINNED blob bytes.\n * After the policy pass, a dedicated LRU pass walks every collection's\n * slots (pinned slots and `legalHold`/`retainUntil`-held slots are exempt\n * and uncounted) and evicts oldest-access-first — internal slots through\n * the standard eviction writer (`deleteSlot` + a `'budget'` audit entry),\n * external slots by dropping their device-local encrypted cache copy only\n * (the object-store copy is authoritative and untouched) — until the\n * remaining unpinned bytes fit `maxBytes`. Internal slots count their\n * uncompressed `size`; external slots count `cachedBytes` (0 = nothing\n * local, nothing to evict). LRU order comes from the device-local\n * `lastAccessAt` stamp, falling back to `uploadedAt`.\n */\n readonly cacheBudget?: { readonly maxBytes: number }\n /**\n * #1453 — reclaim unreferenced legacy blobs (chunks first, then the index\n * row). Off by default: reclaiming is irreversible and the default posture\n * for a maintenance pass is to report. Ignored under `dryRun`.\n */\n readonly reclaimLegacyBlobs?: boolean\n}\n\nexport interface CompactionContext {\n readonly adapter: NoydbStore\n readonly vault: string\n readonly actor: string\n readonly encrypted: boolean\n readonly getDEK: (collection: string) => Promise<EnclaveKey>\n /**\n * Resolve a collection's declared `blobFields` config. Returns an\n * empty map for collections without the config — the walk skips\n * those.\n */\n readonly getBlobFields: <T>(collection: string) => BlobFieldsConfig<T> | null\n /** List collection names in the vault. */\n readonly listCollections: () => Promise<string[]>\n /** List record ids in a collection. */\n readonly listRecords: (collection: string) => Promise<string[]>\n /** Decrypt and return the record. Null when absent. */\n readonly getRecord: <T>(collection: string, id: string) => Promise<T | null>\n /** Return the BlobSet-like handle for a record's slots. */\n readonly listSlots: (collection: string, id: string) => Promise<SlotInfo[]>\n /** Delete a slot and decrement its blob's refCount. */\n readonly deleteSlot: (collection: string, id: string, slotName: string) => Promise<void>\n /**\n * #808: drop a slot's DEVICE-LOCAL cached copy (the encrypted external\n * side-cache) without touching the slot or the object store. Returns the\n * plaintext bytes freed. Optional — absent (or a 0 return) means external\n * cache entries simply cannot be budget-evicted through this context.\n */\n readonly dropLocalCache?: (collection: string, id: string, slotName: string) => Promise<number>\n}\n\nexport async function runCompaction(\n ctx: CompactionContext,\n options: CompactRunOptions = {},\n): Promise<CompactionResult> {\n // #1133 — two `list` calls, no decryption, nothing written. Always on rather\n // than opt-in: residue an operator has to know to ask about is residue nobody\n // finds. The cost is negligible beside this pass's own per-record `getRecord`.\n const orphanBlobChunks = await reportOrphanBlobChunks(ctx.adapter, ctx.vault)\n const now = options.now ?? new Date()\n const maxEvictions = options.maxEvictions ?? Infinity\n const dryRun = options.dryRun === true\n // #1453 — always counted, reclaimed only on request and never under dryRun.\n const unreferencedLegacyBlobs = await sweepUnreferencedLegacyBlobs(ctx, options.reclaimLegacyBlobs === true && !dryRun)\n if (options.cacheBudget !== undefined\n && (!Number.isFinite(options.cacheBudget.maxBytes) || options.cacheBudget.maxBytes < 0)) {\n throw new ValidationError('compact(): cacheBudget.maxBytes must be a non-negative finite number (#808)')\n }\n\n const allCollections = await ctx.listCollections()\n const byCollection: Record<string, { records: number; evicted: number }> = {}\n let evicted = 0\n let records = 0\n let auditEntries = 0\n let held = 0\n let pinned = 0\n let collectionsWithPolicy = 0\n\n outer: for (const collectionName of allCollections) {\n if (collectionName.startsWith('_')) continue\n const config = ctx.getBlobFields(collectionName)\n if (!config) continue\n const configuredSlots = Object.keys(config)\n if (configuredSlots.length === 0) continue\n collectionsWithPolicy += 1\n byCollection[collectionName] = { records: 0, evicted: 0 }\n\n const ids = await ctx.listRecords(collectionName)\n for (const recordId of ids) {\n if (evicted >= maxEvictions) break outer\n\n const record = await ctx.getRecord(collectionName, recordId).catch(() => null)\n if (record === null) continue\n records += 1\n byCollection[collectionName].records += 1\n\n const slots = await ctx.listSlots(collectionName, recordId).catch(() => [])\n for (const slot of slots) {\n if (evicted >= maxEvictions) break outer\n const policy = config[slot.name]\n if (!policy) continue\n\n const reason = evaluatePolicy(policy, record, slot, now)\n if (!reason) continue\n\n // #808: device-local offline pin — an exemption inside this existing\n // pass, exactly like a hold: counted, never evicted. `slot.pinned` is\n // the device-local annotation `BlobSet.list()` stamps from the\n // withBlobs() pin registry (never synced state).\n if (slot.pinned === true) {\n pinned += 1\n continue\n }\n\n // Retention floor: a legal hold or period-bound retainUntil\n // blocks an otherwise-due eviction. Counted, never evicted.\n if (isHeld(policy, record, now)) {\n held += 1\n continue\n }\n\n if (!dryRun) {\n await ctx.deleteSlot(collectionName, recordId, slot.name)\n await writeAuditEntry(ctx, {\n id: generateEvictionId(collectionName, recordId, slot.name),\n collection: collectionName,\n recordId,\n slotName: slot.name,\n blobHash: slot.eTag,\n reason,\n evictedAt: now.toISOString(),\n actor: ctx.actor,\n })\n auditEntries += 1\n }\n evicted += 1\n byCollection[collectionName].evicted += 1\n }\n }\n }\n\n // #808: cache-budget LRU pass — runs AFTER the policy pass so a slot that\n // just evicted no longer counts toward the budget. Reuses this module's own\n // eviction writer (deleteSlot + audit), never a parallel one.\n let budgetEvicted = 0\n let budgetBytesFreed = 0\n if (options.cacheBudget !== undefined) {\n const pass = await runBudgetPass(ctx, options.cacheBudget.maxBytes, {\n now, dryRun, remainingEvictions: maxEvictions - evicted,\n })\n budgetEvicted = pass.evicted\n budgetBytesFreed = pass.bytesFreed\n auditEntries += pass.auditEntries\n }\n\n return {\n evicted,\n records,\n collections: collectionsWithPolicy,\n auditEntries,\n held,\n pinned,\n budgetEvicted,\n budgetBytesFreed,\n byCollection,\n orphanBlobChunks,\n unreferencedLegacyBlobs,\n }\n}\n\n// ─── #808: cache-budget LRU pass ───────────────────────────────────────\n\ninterface BudgetCandidate {\n readonly collection: string\n readonly recordId: string\n readonly slotName: string\n readonly eTag: string\n readonly bytes: number\n /** `'slot'` — internal blob, evicted via `deleteSlot`; `'cache'` — external device-local copy, dropped via `dropLocalCache`. */\n readonly kind: 'slot' | 'cache'\n readonly lastAccessMs: number\n}\n\n/**\n * Enforce `cacheBudget.maxBytes` over the locally-cached UNPINNED blob bytes\n * (see {@link CompactRunOptions.cacheBudget} for the full contract). Walks\n * EVERY collection — unlike the policy pass, the budget is not gated on a\n * declared `blobFields` config — but still honors a declared policy's\n * `legalHold`/`retainUntil` floor where one exists.\n */\nasync function runBudgetPass(\n ctx: CompactionContext,\n maxBytes: number,\n opts: { now: Date; dryRun: boolean; remainingEvictions: number },\n): Promise<{ evicted: number; bytesFreed: number; auditEntries: number }> {\n const candidates: BudgetCandidate[] = []\n let total = 0\n\n for (const collectionName of await ctx.listCollections()) {\n if (collectionName.startsWith('_')) continue\n const config = ctx.getBlobFields(collectionName)\n for (const recordId of await ctx.listRecords(collectionName)) {\n const slots = await ctx.listSlots(collectionName, recordId).catch(() => [])\n if (slots.length === 0) continue\n // The record is only needed to evaluate a declared policy's retention\n // floor — skip the decrypt entirely for policy-less collections.\n const record = config\n ? await ctx.getRecord<unknown>(collectionName, recordId).catch(() => null)\n : null\n for (const slot of slots) {\n if (slot.pinned === true) continue // pinned: exempt AND uncounted (#808)\n const policy = config?.[slot.name]\n if (policy && record !== null && isHeld(policy, record, opts.now)) continue // floor blocks budget too\n const lastAccessMs = Date.parse(slot.lastAccessAt ?? slot.uploadedAt) || 0\n const isExternal = slot.external !== undefined\n const bytes = isExternal ? slot.cachedBytes ?? 0 : slot.size\n if (bytes <= 0) continue // external with no local copy: nothing cached to evict\n total += bytes\n candidates.push({\n collection: collectionName, recordId, slotName: slot.name, eTag: slot.eTag,\n bytes, kind: isExternal ? 'cache' : 'slot', lastAccessMs,\n })\n }\n }\n }\n\n // Oldest access first; deterministic tiebreak so equal timestamps (common\n // within one ms) never make the eviction order flap between runs.\n candidates.sort((a, b) =>\n a.lastAccessMs - b.lastAccessMs\n || a.collection.localeCompare(b.collection)\n || a.recordId.localeCompare(b.recordId)\n || a.slotName.localeCompare(b.slotName))\n\n let evicted = 0\n let bytesFreed = 0\n let auditEntries = 0\n for (const c of candidates) {\n if (total <= maxBytes || evicted >= opts.remainingEvictions) break\n if (!opts.dryRun) {\n if (c.kind === 'slot') {\n await ctx.deleteSlot(c.collection, c.recordId, c.slotName)\n await writeAuditEntry(ctx, {\n id: generateEvictionId(c.collection, c.recordId, c.slotName),\n collection: c.collection,\n recordId: c.recordId,\n slotName: c.slotName,\n blobHash: c.eTag,\n reason: 'budget',\n evictedAt: opts.now.toISOString(),\n actor: ctx.actor,\n })\n auditEntries += 1\n } else {\n // Device-local cache drop only — the slot (catalog) and the\n // object-store copy are untouched, so no eviction audit entry.\n await ctx.dropLocalCache?.(c.collection, c.recordId, c.slotName)\n }\n }\n evicted += 1\n bytesFreed += c.bytes\n total -= c.bytes\n }\n\n return { evicted, bytesFreed, auditEntries }\n}\n\n/**\n * Whether a retention floor (legal hold or period-bound `retainUntil`)\n * currently blocks eviction of this record's slots. Fail-closed: a\n * throwing predicate holds the slot.\n */\nfunction isHeld<T>(policy: BlobFieldPolicy<T>, record: T, now: Date): boolean {\n if (policy.legalHold) {\n try {\n if (policy.legalHold(record)) return true\n } catch {\n return true\n }\n }\n if (policy.retainUntil) {\n try {\n const until = policy.retainUntil(record)\n if (until !== null && until !== undefined) {\n const t = until instanceof Date ? until.getTime() : typeof until === 'number' ? until : Date.parse(String(until))\n if (!Number.isFinite(t)) return true // fail-closed: unparseable retainUntil holds the slot\n if (t > now.getTime()) return true\n }\n } catch {\n return true\n }\n }\n return false\n}\n\nfunction evaluatePolicy<T>(\n policy: BlobFieldPolicy<T>,\n record: T,\n slot: SlotInfo,\n now: Date,\n): 'ttl' | 'predicate' | 'both' | null {\n let ttlTriggered = false\n let predicateTriggered = false\n\n if (policy.retainDays !== undefined && policy.retainDays > 0) {\n const uploadedAt = Date.parse(slot.uploadedAt)\n if (Number.isFinite(uploadedAt)) {\n const ageMs = now.getTime() - uploadedAt\n const limitMs = policy.retainDays * 86_400_000\n if (ageMs > limitMs) ttlTriggered = true\n }\n }\n\n if (policy.evictWhen) {\n try {\n if (policy.evictWhen(record)) predicateTriggered = true\n } catch {\n // Predicate error → do NOT evict. Fail closed.\n }\n }\n\n if (ttlTriggered && predicateTriggered) return 'both'\n if (ttlTriggered) return 'ttl'\n if (predicateTriggered) return 'predicate'\n return null\n}\n\nfunction generateEvictionId(collection: string, recordId: string, slotName: string): string {\n const rand = globalThis.crypto.getRandomValues(new Uint8Array(8))\n let suffix = ''\n for (const b of rand) suffix += b.toString(16).padStart(2, '0')\n return `${collection}__${recordId}__${slotName}__${suffix}`\n}\n\nasync function writeAuditEntry(ctx: CompactionContext, entry: BlobEvictionEntry): Promise<void> {\n const json = JSON.stringify(entry)\n const identity = { collection: BLOB_EVICTION_AUDIT_COLLECTION, id: entry.id, by: entry.actor, version: 1 }\n const body = { ts: entry.evictedAt }\n let envelope: EncryptedEnvelope\n if (ctx.encrypted) {\n const dek = await ctx.getDEK(BLOB_EVICTION_AUDIT_COLLECTION)\n const { iv, data } = await encrypt(json, dek, buildRecordAad(identity))\n envelope = buildRecordEnvelope(identity, { ...body, iv, data })\n } else {\n envelope = buildRecordEnvelope(identity, { ...body, iv: '', data: json })\n }\n await ctx.adapter.put(ctx.vault, BLOB_EVICTION_AUDIT_COLLECTION, entry.id, envelope)\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
installViaBinder
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-Z5HTYMMQ.js";
|
|
4
4
|
import {
|
|
5
5
|
LocaleNotSpecifiedError,
|
|
6
6
|
MissingTranslationError,
|
|
7
7
|
ScriptViolationError,
|
|
8
8
|
TranslatorNotConfiguredError
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-244VXYPG.js";
|
|
10
10
|
|
|
11
11
|
// src/port/with/i18n-strategy.ts
|
|
12
12
|
function notEnabled(op) {
|
|
@@ -403,6 +403,8 @@ function i18nVia(cfg) {
|
|
|
403
403
|
// name under this prefix.
|
|
404
404
|
reservedPrefixes: ["_dict_"],
|
|
405
405
|
covers: (field) => cfg.i18nFields !== void 0 && field in cfg.i18nFields || cfg.dictKeyFields !== void 0 && field in cfg.dictKeyFields,
|
|
406
|
+
// #1447 — both config maps, since either makes the field vault-resolved.
|
|
407
|
+
coveredFields: [...Object.keys(cfg.i18nFields ?? {}), ...Object.keys(cfg.dictKeyFields ?? {})],
|
|
406
408
|
encodeWrite: async (record, ctx) => runI18nWriteStages(record, ctx, cfg),
|
|
407
409
|
present: async (record, ctx) => runI18nPresent(record, ctx, cfg),
|
|
408
410
|
describeFragment: () => buildI18nDescribeFragment(cfg)
|
|
@@ -618,4 +620,4 @@ export {
|
|
|
618
620
|
presentI18nForJoin,
|
|
619
621
|
stripI18nFilled
|
|
620
622
|
};
|
|
621
|
-
//# sourceMappingURL=chunk-
|
|
623
|
+
//# sourceMappingURL=chunk-GHLTDZRS.js.map
|