@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,1289 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_CROSS_JOIN_MAX_ROWS,
|
|
3
|
+
DEFAULT_JOIN_MAX_ROWS,
|
|
4
|
+
applyJoins,
|
|
5
|
+
bucketByRefKey,
|
|
6
|
+
coerceRefKey,
|
|
7
|
+
joinsDropLeftRows,
|
|
8
|
+
orderReferencesJoinAlias,
|
|
9
|
+
splitAroundJoins
|
|
10
|
+
} from "./chunk-4I522ERM.js";
|
|
11
|
+
import {
|
|
12
|
+
describeJoinOn,
|
|
13
|
+
joinOnDispatch,
|
|
14
|
+
normalizeJoinOn
|
|
15
|
+
} from "./chunk-MKXXMNJH.js";
|
|
16
|
+
import {
|
|
17
|
+
ScanBuilder
|
|
18
|
+
} from "./chunk-F24ZZTHQ.js";
|
|
19
|
+
import {
|
|
20
|
+
installMethods
|
|
21
|
+
} from "./chunk-F6NCB7KT.js";
|
|
22
|
+
import {
|
|
23
|
+
Query,
|
|
24
|
+
indexableClauses,
|
|
25
|
+
installRelateHooks,
|
|
26
|
+
isRangeOperator,
|
|
27
|
+
pickCompound,
|
|
28
|
+
pickCompoundOrder,
|
|
29
|
+
viaOrdersField
|
|
30
|
+
} from "./chunk-7JE7BDYJ.js";
|
|
31
|
+
import {
|
|
32
|
+
gateTerminal
|
|
33
|
+
} from "./chunk-5O7D6ZTW.js";
|
|
34
|
+
import {
|
|
35
|
+
readPath
|
|
36
|
+
} from "./chunk-HLEK5VAN.js";
|
|
37
|
+
import {
|
|
38
|
+
isViaPrefixProbe
|
|
39
|
+
} from "./chunk-Z5HTYMMQ.js";
|
|
40
|
+
import {
|
|
41
|
+
CrossJoinTooLargeError,
|
|
42
|
+
DanglingReferenceError,
|
|
43
|
+
RefNotDeclaredError,
|
|
44
|
+
TraversalCycleError
|
|
45
|
+
} from "./chunk-244VXYPG.js";
|
|
46
|
+
|
|
47
|
+
// src/kernel/query/relate/explain.ts
|
|
48
|
+
var WARN_FRACTION = 0.8;
|
|
49
|
+
function tupleSourceOf(indexes) {
|
|
50
|
+
return { compoundTuples: () => indexes.compoundTuples?.() ?? [] };
|
|
51
|
+
}
|
|
52
|
+
function indexDispatchFor(source, clauses) {
|
|
53
|
+
const indexes = source.getIndexes?.();
|
|
54
|
+
if (!indexes || !source.lookupById || clauses.length === 0) return null;
|
|
55
|
+
const compound = compoundDispatchFor(source, indexes, clauses);
|
|
56
|
+
if (compound) return compound;
|
|
57
|
+
const prefixed = prefixDispatchFor(indexes, clauses);
|
|
58
|
+
if (prefixed) return prefixed;
|
|
59
|
+
for (let i = 0; i < clauses.length; i++) {
|
|
60
|
+
const clause = clauses[i];
|
|
61
|
+
if (clause.type !== "field") continue;
|
|
62
|
+
if (!indexes.has(clause.field) && indexes.hasSorted?.(clause.field) !== true) continue;
|
|
63
|
+
if (clause.via && clause.via.indexValue === void 0) continue;
|
|
64
|
+
const probeValue = clause.via ? clause.via.indexValue : clause.value;
|
|
65
|
+
let ids = null;
|
|
66
|
+
let dispatch = "index:hash";
|
|
67
|
+
if (clause.op === "==") {
|
|
68
|
+
ids = indexes.lookupEqual(clause.field, probeValue);
|
|
69
|
+
} else if (clause.op === "in" && Array.isArray(probeValue)) {
|
|
70
|
+
ids = indexes.lookupIn(clause.field, probeValue);
|
|
71
|
+
} else if (clause.via === void 0 && isRangeOperator(clause.op)) {
|
|
72
|
+
ids = indexes.lookupRange?.(clause.field, clause.op, clause.value) ?? null;
|
|
73
|
+
dispatch = "index:range";
|
|
74
|
+
}
|
|
75
|
+
if (ids !== null) return { consumed: /* @__PURE__ */ new Map([[i, dispatch]]), rows: ids.size };
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
function prefixDispatchFor(indexes, clauses) {
|
|
80
|
+
for (let i = 0; i < clauses.length; i++) {
|
|
81
|
+
const clause = clauses[i];
|
|
82
|
+
if (clause.type !== "field" || clause.via === void 0) continue;
|
|
83
|
+
const probe = clause.via.indexValue;
|
|
84
|
+
if (!isViaPrefixProbe(probe)) continue;
|
|
85
|
+
if (indexes.hasSorted?.(clause.field) !== true) continue;
|
|
86
|
+
const ids = /* @__PURE__ */ new Set();
|
|
87
|
+
for (const prefix of probe.prefixes) {
|
|
88
|
+
const hit = indexes.lookupRange?.(clause.field, "startsWith", prefix) ?? null;
|
|
89
|
+
if (hit === null) return null;
|
|
90
|
+
for (const id of hit) ids.add(id);
|
|
91
|
+
}
|
|
92
|
+
return { consumed: /* @__PURE__ */ new Map([[i, "index:prefix"]]), rows: ids.size };
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
function compoundDispatchFor(source, indexes, clauses) {
|
|
97
|
+
const pick = pickCompound(tupleSourceOf(indexes), clauses);
|
|
98
|
+
if (!pick) return null;
|
|
99
|
+
if (indexes.compoundSize?.(pick.fields) !== source.snapshot().length) return null;
|
|
100
|
+
const ids = indexes.lookupCompound?.(pick.fields, pick.values, pick.range) ?? null;
|
|
101
|
+
if (!ids) return null;
|
|
102
|
+
const consumed = /* @__PURE__ */ new Map();
|
|
103
|
+
for (const at of pick.consumed) consumed.set(at, "index:compound");
|
|
104
|
+
return { consumed, rows: ids.size };
|
|
105
|
+
}
|
|
106
|
+
function orderedIndexPick(source, plan) {
|
|
107
|
+
const limit = plan.limit;
|
|
108
|
+
if (limit === void 0 || plan.orderBy.length !== 1) return null;
|
|
109
|
+
const [order] = plan.orderBy;
|
|
110
|
+
if (!order || order.by === "label") return null;
|
|
111
|
+
const indexes = source.getIndexes?.();
|
|
112
|
+
if (!indexes || !source.lookupById) return null;
|
|
113
|
+
if (viaOrdersField(source.via, order.field)) return null;
|
|
114
|
+
const snapshotRows = source.snapshot().length;
|
|
115
|
+
if (plan.clauses.length === 0) {
|
|
116
|
+
if (indexes.hasSorted?.(order.field) !== true) return null;
|
|
117
|
+
if (indexes.sortedSize?.(order.field) !== snapshotRows) return null;
|
|
118
|
+
const ids2 = indexes.orderedIds?.(order.field, order.direction) ?? null;
|
|
119
|
+
if (!ids2) return null;
|
|
120
|
+
return { rows: pageOf(ids2.length, plan.offset, limit), clauseDispatch: null, detail: `sorted index on "${order.field}"` };
|
|
121
|
+
}
|
|
122
|
+
const { eq } = indexableClauses(plan.clauses);
|
|
123
|
+
if (eq.size !== plan.clauses.length) return null;
|
|
124
|
+
const match = pickCompoundOrder(tupleSourceOf(indexes), eq, order.field);
|
|
125
|
+
if (!match) return null;
|
|
126
|
+
if (indexes.compoundSize?.(match.fields) !== snapshotRows) return null;
|
|
127
|
+
const ids = indexes.compoundOrderedIds?.(match.fields, match.values, order.direction) ?? null;
|
|
128
|
+
if (!ids) return null;
|
|
129
|
+
return {
|
|
130
|
+
rows: pageOf(ids.length, plan.offset, limit),
|
|
131
|
+
clauseDispatch: "index:compound",
|
|
132
|
+
detail: `compound index on (${match.fields.join(", ")})`
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function pageOf(total, offset, limit) {
|
|
136
|
+
return Math.min(Math.max(0, total - offset), limit);
|
|
137
|
+
}
|
|
138
|
+
function isSupersetDispatch(dispatch, clause) {
|
|
139
|
+
if (dispatch === "index:prefix" || dispatch === "index:compound") return true;
|
|
140
|
+
if (dispatch !== "index:hash") return false;
|
|
141
|
+
return clause.type === "field" && (clause.op === "==" || clause.op === "in");
|
|
142
|
+
}
|
|
143
|
+
function describeClause(clause) {
|
|
144
|
+
switch (clause.type) {
|
|
145
|
+
case "field": {
|
|
146
|
+
const detail = clause.subquery !== void 0 ? `${clause.field} ${clause.op} subquery(${clause.subquery.from}) \u2192 ${clause.subquery.ids} ids` : `${clause.field} ${clause.op} ${formatValue(clause.value)}`;
|
|
147
|
+
return { op: "where", detail };
|
|
148
|
+
}
|
|
149
|
+
case "group":
|
|
150
|
+
return { op: "group", detail: `${clause.op} (${clause.clauses.length} clause${clause.clauses.length === 1 ? "" : "s"})` };
|
|
151
|
+
case "filter":
|
|
152
|
+
return { op: "filter", detail: "fn" };
|
|
153
|
+
case "wherePredicate":
|
|
154
|
+
return { op: "wherePredicate", detail: clause.name };
|
|
155
|
+
case "crossJoin":
|
|
156
|
+
return {
|
|
157
|
+
op: clause.leftAs === void 0 ? "crossJoin" : "crossJoinWith",
|
|
158
|
+
detail: (clause.leftAs === void 0 ? `${clause.as} x ${clause.target}` : `${clause.leftAs} x ${clause.as} (self ${clause.target})`) + `${clause.on ? " on fn" : ""}${clause.outer === true ? " outer" : ""}`
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function formatValue(value) {
|
|
163
|
+
if (typeof value === "bigint") return `${value.toString()}n`;
|
|
164
|
+
try {
|
|
165
|
+
const json = JSON.stringify(value);
|
|
166
|
+
return json === void 0 ? String(value) : json;
|
|
167
|
+
} catch {
|
|
168
|
+
return "[unserializable]";
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function referencedFields(plan) {
|
|
172
|
+
const out = [];
|
|
173
|
+
const walk = (clauses) => {
|
|
174
|
+
for (const c of clauses) {
|
|
175
|
+
if (c.type === "field") out.push(c.field);
|
|
176
|
+
else if (c.type === "group") walk(c.clauses);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
walk(plan.clauses);
|
|
180
|
+
for (const o of plan.orderBy) out.push(o.field);
|
|
181
|
+
return [...new Set(out)];
|
|
182
|
+
}
|
|
183
|
+
function capStatus(observed, limit) {
|
|
184
|
+
if (observed > limit) return "exceeded";
|
|
185
|
+
if (observed > limit * WARN_FRACTION) return "warn";
|
|
186
|
+
return "ok";
|
|
187
|
+
}
|
|
188
|
+
function explainPlan(source, plan, joinContext) {
|
|
189
|
+
const nodes = [];
|
|
190
|
+
const caps = [];
|
|
191
|
+
const indexes = source.getIndexes?.() ?? null;
|
|
192
|
+
const indexedFields = indexes ? indexes.fields() : [];
|
|
193
|
+
const sourceRows = source.snapshot().length;
|
|
194
|
+
let rows = sourceRows;
|
|
195
|
+
const reducerRewrite = collectReducerRewrites(source.via, referencedFields(plan));
|
|
196
|
+
const sourceNotes = [];
|
|
197
|
+
const notHydrated = source.hydration?.isHydrated() === false;
|
|
198
|
+
if (notHydrated) {
|
|
199
|
+
sourceNotes.push(
|
|
200
|
+
"collection not hydrated \u2014 this plan's row estimates are not authoritative; call `await collection.list()` / `await collection.get(id)` first"
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
for (const r of reducerRewrite) {
|
|
204
|
+
sourceNotes.push(`reducer rewrite: ${r.brand}("${r.field}")`);
|
|
205
|
+
}
|
|
206
|
+
nodes.push({
|
|
207
|
+
op: "source",
|
|
208
|
+
dispatch: "source",
|
|
209
|
+
detail: notHydrated ? `snapshot (NOT HYDRATED \u2014 nothing has been read yet${indexedFields.length > 0 ? `; indexes: ${indexedFields.join(", ")}` : ""})` : indexedFields.length > 0 ? `snapshot (indexes: ${indexedFields.join(", ")})` : "snapshot (no indexes)",
|
|
210
|
+
estimatedRows: sourceRows,
|
|
211
|
+
notes: sourceNotes,
|
|
212
|
+
children: []
|
|
213
|
+
});
|
|
214
|
+
const hasCrossJoin = plan.clauses.some((c) => c.type === "crossJoin");
|
|
215
|
+
const { preJoin, postJoin } = splitAroundJoins(plan.clauses, plan.joins);
|
|
216
|
+
const orderPostJoin = orderReferencesJoinAlias(plan.orderBy, plan.joins);
|
|
217
|
+
const runsPostJoin = postJoin.length > 0 || orderPostJoin;
|
|
218
|
+
const innerSplit = !runsPostJoin && plan.joins.length > 0 && joinsDropLeftRows(plan.joins);
|
|
219
|
+
let ordered = null;
|
|
220
|
+
if (hasCrossJoin) {
|
|
221
|
+
for (const clause of plan.clauses) {
|
|
222
|
+
if (clause.type === "crossJoin") {
|
|
223
|
+
const rightRows = joinContext?.resolveSource(clause.target)?.snapshot().length;
|
|
224
|
+
const limit = clause.maxRows ?? DEFAULT_CROSS_JOIN_MAX_ROWS;
|
|
225
|
+
const product = rows !== void 0 && rightRows !== void 0 ? rows * rightRows : void 0;
|
|
226
|
+
const { op, detail } = describeClause(clause);
|
|
227
|
+
const notes = [
|
|
228
|
+
rightRows === void 0 ? "right side unresolved" : `right side ${rightRows} rows`,
|
|
229
|
+
`cap ${limit}`
|
|
230
|
+
];
|
|
231
|
+
nodes.push({ op, dispatch: "crossJoin", detail, estimatedRows: product, notes, children: [] });
|
|
232
|
+
if (product !== void 0) {
|
|
233
|
+
caps.push({ name: `crossJoin:${clause.as}`, limit, observed: product, status: capStatus(product, limit) });
|
|
234
|
+
}
|
|
235
|
+
rows = product;
|
|
236
|
+
} else {
|
|
237
|
+
nodes.push(scanNode(clause, rows, ["index fast path unavailable: the plan carries a crossJoin"]));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
ordered = runsPostJoin || innerSplit ? null : orderedIndexPick(source, plan);
|
|
242
|
+
const pick = ordered ? null : indexDispatchFor(source, preJoin);
|
|
243
|
+
if (ordered) rows = ordered.rows;
|
|
244
|
+
preJoin.forEach((clause, i) => {
|
|
245
|
+
const dispatch = ordered ? ordered.clauseDispatch : pick?.consumed.get(i) ?? null;
|
|
246
|
+
if (dispatch !== null) {
|
|
247
|
+
const { op, detail } = describeClause(clause);
|
|
248
|
+
if (pick) rows = pick.rows;
|
|
249
|
+
const notes = isSupersetDispatch(dispatch, clause) ? [`${dispatch === "index:prefix" ? "prefix cover" : "index match"} is a superset: the clause still filters the candidates exactly`] : [];
|
|
250
|
+
nodes.push({ op, dispatch, detail, estimatedRows: rows, notes, children: [] });
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
nodes.push(scanNode(clause, rows, scanReason(clause, indexes, pick !== null)));
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
const emitJoins = () => {
|
|
257
|
+
for (const leg of plan.joins) {
|
|
258
|
+
nodes.push(joinNode(leg, joinContext, rows, caps));
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
if (runsPostJoin) {
|
|
262
|
+
emitJoins();
|
|
263
|
+
for (const clause of postJoin) {
|
|
264
|
+
nodes.push(scanNode(clause, rows, ["post-join: addresses a join alias, so no index can serve it"]));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const placement = plan.joins.length === 0 ? void 0 : runsPostJoin ? "post-join" : "pre-join";
|
|
268
|
+
if (plan.orderBy.length > 0) {
|
|
269
|
+
const orderNotes = placement ? [placement] : [];
|
|
270
|
+
if (ordered) orderNotes.push(`ordered index page off the ${ordered.detail}: no sort runs`);
|
|
271
|
+
nodes.push({
|
|
272
|
+
op: "orderBy",
|
|
273
|
+
dispatch: ordered ? "index:ordered" : "sort",
|
|
274
|
+
detail: plan.orderBy.map((o) => `${o.field} ${o.direction}${o.by === "label" ? " by label" : ""}`).join(", "),
|
|
275
|
+
estimatedRows: rows,
|
|
276
|
+
notes: orderNotes,
|
|
277
|
+
children: []
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
if (innerSplit) emitJoins();
|
|
281
|
+
if (plan.offset > 0 || plan.limit !== void 0) {
|
|
282
|
+
const afterOffset = rows === void 0 ? void 0 : Math.max(0, rows - plan.offset);
|
|
283
|
+
const paged = afterOffset === void 0 ? void 0 : plan.limit === void 0 ? afterOffset : Math.min(afterOffset, plan.limit);
|
|
284
|
+
nodes.push({
|
|
285
|
+
op: "page",
|
|
286
|
+
dispatch: "page",
|
|
287
|
+
detail: `offset=${plan.offset} limit=${plan.limit ?? "none"}`,
|
|
288
|
+
estimatedRows: paged,
|
|
289
|
+
notes: [
|
|
290
|
+
...innerSplit ? ["post-join"] : placement ? [placement] : [],
|
|
291
|
+
...ordered ? ["consumed by the ordered index walk; no rows are materialized past the page"] : []
|
|
292
|
+
],
|
|
293
|
+
children: []
|
|
294
|
+
});
|
|
295
|
+
rows = paged;
|
|
296
|
+
}
|
|
297
|
+
if (!runsPostJoin && !innerSplit) emitJoins();
|
|
298
|
+
return { nodes, caps, reducerRewrite, text: renderExplainText(nodes) };
|
|
299
|
+
}
|
|
300
|
+
function scanReason(clause, indexes, indexAlreadyUsed) {
|
|
301
|
+
if (indexAlreadyUsed) {
|
|
302
|
+
const residual = "residual: an earlier clause is already index-served";
|
|
303
|
+
return clause.type === "field" && indexes !== null && !indexes.has(clause.field) ? [residual, `no index on "${clause.field}"`] : [residual];
|
|
304
|
+
}
|
|
305
|
+
if (clause.type !== "field") return [`${clause.type} clauses are never index-served`];
|
|
306
|
+
if (!indexes) return ["linear scan: no index store on this source"];
|
|
307
|
+
const sorted = indexes.hasSorted?.(clause.field) === true;
|
|
308
|
+
if (!indexes.has(clause.field) && !sorted) return [`linear scan: no index on "${clause.field}"`];
|
|
309
|
+
if (clause.op !== "==" && clause.op !== "in") {
|
|
310
|
+
if (!isRangeOperator(clause.op)) {
|
|
311
|
+
return [`linear scan: no index serves ${clause.op} on "${clause.field}"`];
|
|
312
|
+
}
|
|
313
|
+
if (!sorted) {
|
|
314
|
+
return [
|
|
315
|
+
`linear scan: the hash index on "${clause.field}" serves == and in only \u2014 declare a sorted index on it to serve ${clause.op}`
|
|
316
|
+
];
|
|
317
|
+
}
|
|
318
|
+
if (clause.via !== void 0) {
|
|
319
|
+
return [`linear scan: "${clause.field}" is Via-covered, and a Via range operand is evaluated per record`];
|
|
320
|
+
}
|
|
321
|
+
return [`linear scan: the sorted index on "${clause.field}" could not order this operand`];
|
|
322
|
+
}
|
|
323
|
+
return [`linear scan: the index on "${clause.field}" could not serve this operand`];
|
|
324
|
+
}
|
|
325
|
+
function scanNode(clause, rows, notes) {
|
|
326
|
+
const { op, detail } = describeClause(clause);
|
|
327
|
+
return {
|
|
328
|
+
op,
|
|
329
|
+
dispatch: "scan",
|
|
330
|
+
detail,
|
|
331
|
+
estimatedRows: rows,
|
|
332
|
+
notes: [...notes, ...rows === void 0 ? [] : ["estimate is an upper bound"]],
|
|
333
|
+
children: clause.type === "group" ? clause.clauses.map((c) => scanNode(c, rows, ["inside a group: evaluated per record"])) : []
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
function joinNode(leg, joinContext, leftRows, caps) {
|
|
337
|
+
const right = leg.isDictJoin === true ? joinContext?.resolveDictSource?.(leg.field) : joinContext?.resolveSource(leg.target);
|
|
338
|
+
const rightRows = right?.snapshot().length;
|
|
339
|
+
const strategy = leg.strategy ?? (right?.lookupById ? "nested" : "hash");
|
|
340
|
+
const limit = leg.maxRows ?? DEFAULT_JOIN_MAX_ROWS;
|
|
341
|
+
const direction = leg.direction ?? "left";
|
|
342
|
+
const notes = [
|
|
343
|
+
rightRows === void 0 ? "right side unresolved" : `right side ${rightRows} rows`,
|
|
344
|
+
`cap ${limit}`,
|
|
345
|
+
`ref mode ${leg.mode}`
|
|
346
|
+
];
|
|
347
|
+
if (direction !== "left") {
|
|
348
|
+
notes.push("reverse index over the left rows; right snapshot drives");
|
|
349
|
+
}
|
|
350
|
+
if (leg.inner === true) {
|
|
351
|
+
notes.push("inner: unmatched left rows are dropped, so the estimate is an upper bound");
|
|
352
|
+
}
|
|
353
|
+
if (leg.strategy !== void 0) notes.push("strategy overridden by the caller");
|
|
354
|
+
if (leftRows !== void 0) {
|
|
355
|
+
caps.push({ name: `join:${leg.as}:left`, limit, observed: leftRows, status: capStatus(leftRows, limit) });
|
|
356
|
+
}
|
|
357
|
+
if (rightRows !== void 0) {
|
|
358
|
+
caps.push({ name: `join:${leg.as}:right`, limit, observed: rightRows, status: capStatus(rightRows, limit) });
|
|
359
|
+
}
|
|
360
|
+
if (leg.on !== void 0) {
|
|
361
|
+
notes.splice(notes.findIndex((n) => n.startsWith("ref mode")), 1);
|
|
362
|
+
notes.push(
|
|
363
|
+
leg.on.kind === "composite" ? "declared composite `on`: one hash pass over the right snapshot, keyed on the field tuple" : "declared range `on`: the right snapshot is sorted once, then binary-searched per left row"
|
|
364
|
+
);
|
|
365
|
+
notes.push("many-to-many: one row per match, so the estimate is a lower bound and the output ceiling applies");
|
|
366
|
+
return {
|
|
367
|
+
op: "join",
|
|
368
|
+
dispatch: joinOnDispatch(leg.on),
|
|
369
|
+
detail: `${leg.as} <- ${describeJoinOn(leg.on)} (${leg.target})`,
|
|
370
|
+
estimatedRows: leftRows,
|
|
371
|
+
notes,
|
|
372
|
+
children: []
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
const label = direction === "left" ? "" : direction === "right" ? " right outer" : " full outer";
|
|
376
|
+
const estimatedRows = direction === "left" ? leftRows : leftRows === void 0 || rightRows === void 0 ? void 0 : leftRows + rightRows;
|
|
377
|
+
return {
|
|
378
|
+
op: "join",
|
|
379
|
+
dispatch: direction === "left" ? `join:${strategy}` : "join:reverse-index",
|
|
380
|
+
detail: `${leg.as} <- ${leg.field} (${leg.target})${label}`,
|
|
381
|
+
estimatedRows,
|
|
382
|
+
notes,
|
|
383
|
+
children: []
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
function collectReducerRewrites(via, fields) {
|
|
387
|
+
if (!via) return [];
|
|
388
|
+
const out = [];
|
|
389
|
+
for (const field of fields) {
|
|
390
|
+
for (const binding of via.bindings) {
|
|
391
|
+
if (binding.wrapReducers && binding.covers?.(field) === true) out.push({ brand: binding.brand, field });
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return out;
|
|
395
|
+
}
|
|
396
|
+
function renderExplainText(nodes) {
|
|
397
|
+
const lines = [];
|
|
398
|
+
const walk = (ns, depth) => {
|
|
399
|
+
for (const n of ns) {
|
|
400
|
+
const rows = n.estimatedRows === void 0 ? "" : ` rows=${n.estimatedRows}`;
|
|
401
|
+
const notes = n.notes.length > 0 ? ` -- ${n.notes.join("; ")}` : "";
|
|
402
|
+
lines.push(`${" ".repeat(depth)}${n.op} ${n.detail} [${n.dispatch}]${rows}${notes}`);
|
|
403
|
+
walk(n.children, depth + 1);
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
walk(nodes, 0);
|
|
407
|
+
return lines.join("\n");
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// src/kernel/query/relate/traverse.ts
|
|
411
|
+
function entriesOf(source) {
|
|
412
|
+
const paired = source.snapshotEntries?.();
|
|
413
|
+
if (paired !== void 0) return paired;
|
|
414
|
+
const out = [];
|
|
415
|
+
for (const record of source.snapshot()) {
|
|
416
|
+
const id = coerceRefKey(readPath(record, "id"));
|
|
417
|
+
if (id !== null) out.push({ id, record });
|
|
418
|
+
}
|
|
419
|
+
return out;
|
|
420
|
+
}
|
|
421
|
+
function runTraversal(source, field, seeds, opts) {
|
|
422
|
+
const { direction, maxDepth } = opts;
|
|
423
|
+
if (!Number.isInteger(maxDepth) || maxDepth < 0) {
|
|
424
|
+
throw new Error(
|
|
425
|
+
`.traverse("${field}"): maxDepth must be a non-negative integer, got ${String(maxDepth)}. It is required and has no default \u2014 an unbounded walk over a large collection is a denial of service against your own UI.`
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
const onCycle = opts.onCycle ?? "stop";
|
|
429
|
+
const entries = entriesOf(source);
|
|
430
|
+
const lookup = source.lookupById !== void 0 ? (id) => source.lookupById?.(id) : (() => {
|
|
431
|
+
const byId = new Map(entries.map((e) => [e.id, e.record]));
|
|
432
|
+
return (id) => byId.get(id);
|
|
433
|
+
})();
|
|
434
|
+
const children = direction === "down" ? bucketByRefKey(entries, (e) => readPath(e.record, field)) : void 0;
|
|
435
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
436
|
+
const out = [];
|
|
437
|
+
let frontier = [];
|
|
438
|
+
for (const seedId of seeds) {
|
|
439
|
+
if (emitted.has(seedId)) continue;
|
|
440
|
+
const record = lookup(seedId);
|
|
441
|
+
if (record === void 0) continue;
|
|
442
|
+
emitted.add(seedId);
|
|
443
|
+
const node = { id: seedId, record, depth: 0, path: [seedId] };
|
|
444
|
+
out.push(node);
|
|
445
|
+
frontier.push(node);
|
|
446
|
+
}
|
|
447
|
+
for (let depth = 1; depth <= maxDepth && frontier.length > 0; depth++) {
|
|
448
|
+
const next = [];
|
|
449
|
+
for (const current of frontier) {
|
|
450
|
+
for (const { id, record } of neighboursOf(current, field, direction, lookup, children)) {
|
|
451
|
+
if (current.path.includes(id)) {
|
|
452
|
+
if (onCycle === "throw") {
|
|
453
|
+
throw new TraversalCycleError({ field, cycle: [...current.path, id] });
|
|
454
|
+
}
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
if (emitted.has(id)) continue;
|
|
458
|
+
emitted.add(id);
|
|
459
|
+
const node = { id, record, depth, path: [...current.path, id] };
|
|
460
|
+
out.push(node);
|
|
461
|
+
next.push(node);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
frontier = next;
|
|
465
|
+
}
|
|
466
|
+
return out;
|
|
467
|
+
}
|
|
468
|
+
function neighboursOf(current, field, direction, lookup, children) {
|
|
469
|
+
if (direction === "down") return children?.get(current.id) ?? [];
|
|
470
|
+
const parentId = coerceRefKey(readPath(current.record, field));
|
|
471
|
+
if (parentId === null) return [];
|
|
472
|
+
const parent = lookup(parentId);
|
|
473
|
+
if (parent === void 0) return [];
|
|
474
|
+
return [{ id: parentId, record: parent }];
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// src/kernel/query/relate/methods.ts
|
|
478
|
+
var RelateMethods = class {
|
|
479
|
+
// The implementation signature spans both overloads: the alias is
|
|
480
|
+
// non-nullable only under `mode: 'inner'`, and TS needs a return type both
|
|
481
|
+
// published shapes are assignable to.
|
|
482
|
+
join(field, opts) {
|
|
483
|
+
return this.withJoinLeg(field, opts, "left");
|
|
484
|
+
}
|
|
485
|
+
joinOn(target, opts) {
|
|
486
|
+
if (!this.joinContext) {
|
|
487
|
+
throw new Error(
|
|
488
|
+
`Query.joinOn() requires a join context. Use collection.query() to construct a join-capable Query instead of the Query constructor directly (the direct constructor is only used for tests with plain-object sources).`
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
const on = normalizeJoinOn(opts.on, target);
|
|
492
|
+
const leg = {
|
|
493
|
+
// The driving left field. Not a ref — it exists so error messages and
|
|
494
|
+
// `explain()` have something to name, and it is derivable from `on`.
|
|
495
|
+
field: on.kind === "composite" ? on.pairs[0][0] : on.left,
|
|
496
|
+
as: opts.as,
|
|
497
|
+
target,
|
|
498
|
+
// There is no ref(), so there is no dangling-ref policy to apply.
|
|
499
|
+
// 'cascade' is the mode that attaches null silently, which is exactly
|
|
500
|
+
// the left-outer behaviour a declared join wants.
|
|
501
|
+
mode: "cascade",
|
|
502
|
+
strategy: void 0,
|
|
503
|
+
maxRows: opts.maxRows,
|
|
504
|
+
...opts.mode === "inner" ? { inner: true } : {},
|
|
505
|
+
on,
|
|
506
|
+
partitionScope: "all"
|
|
507
|
+
};
|
|
508
|
+
return new Query(
|
|
509
|
+
this.source,
|
|
510
|
+
{ ...this.plan, joins: [...this.plan.joins, leg] },
|
|
511
|
+
this.joinContext,
|
|
512
|
+
this.reduceStrategy,
|
|
513
|
+
this.predicates
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* RIGHT outer join (#1289): every record of the TARGET collection appears,
|
|
518
|
+
* including one no left row points at. The mirror of `.join()`, which is
|
|
519
|
+
* and always was the left outer join.
|
|
520
|
+
*
|
|
521
|
+
* ```ts
|
|
522
|
+
* invoices.query().rightJoin<'client', Client>('clientId', { as: 'client' })
|
|
523
|
+
* // → one row per invoice/client match, PLUS { client } for every client
|
|
524
|
+
* // no invoice references. An invoice whose clientId matches nothing is
|
|
525
|
+
* // dropped — that is what makes it a right join.
|
|
526
|
+
* ```
|
|
527
|
+
*
|
|
528
|
+
* **The row shape is not `T`.** A right-only row carries the alias and
|
|
529
|
+
* nothing else, so the left fields are typed `Partial<T>` — SQL's "the left
|
|
530
|
+
* columns are NULL" in an object language. Read them defensively; a row
|
|
531
|
+
* where `amount` is `undefined` is a real, correct result, not a bug.
|
|
532
|
+
*
|
|
533
|
+
* **Cost.** A forward leg follows the left row's FK and is O(1) per row. A
|
|
534
|
+
* right leg cannot: the rows it must produce are exactly the ones no FK
|
|
535
|
+
* names. It builds a reverse index — the left rows bucketed by FK value —
|
|
536
|
+
* and walks the right snapshot against it. That is one extra pass over the
|
|
537
|
+
* left set and one `Map` the size of the distinct FK values; both sides are
|
|
538
|
+
* fully materialized either way, and both row ceilings still apply.
|
|
539
|
+
*
|
|
540
|
+
* **Ordering.** Like `.join()`, legs run AFTER `orderBy`/`limit`/`offset`
|
|
541
|
+
* unless the ordering addresses the alias (#1337),
|
|
542
|
+
* so those narrow the LEFT side. Rows are emitted in right-snapshot order.
|
|
543
|
+
*
|
|
544
|
+
* A left row whose non-null FK resolves to nothing is dropped, but the
|
|
545
|
+
* ref-mode check still runs on it: `strict` still throws
|
|
546
|
+
* `DanglingReferenceError`. Corruption should not become invisible because
|
|
547
|
+
* the caller changed join direction.
|
|
548
|
+
*/
|
|
549
|
+
rightJoin(field, opts) {
|
|
550
|
+
return this.withJoinLeg(field, opts, "right");
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* FULL outer join (#1289): the union of `.join()` and `.rightJoin()` —
|
|
554
|
+
* every left row, every right record, matched where they meet.
|
|
555
|
+
*
|
|
556
|
+
* ```ts
|
|
557
|
+
* invoices.query().fullOuterJoin<'client', Client>('clientId', { as: 'client' })
|
|
558
|
+
* // → matched rows, plus { client: null, ...invoice } for an unreferenced
|
|
559
|
+
* // invoice, plus { client } for a client no invoice points at.
|
|
560
|
+
* ```
|
|
561
|
+
*
|
|
562
|
+
* The alias is `R | null` and the left fields are `Partial<T>`, because a
|
|
563
|
+
* row can be missing either side — never both. Same reverse index, same
|
|
564
|
+
* ordering and same ref-mode semantics as {@link rightJoin}; the only
|
|
565
|
+
* difference is that the unmatched LEFT rows are emitted (after the
|
|
566
|
+
* right-driven ones) instead of dropped.
|
|
567
|
+
*/
|
|
568
|
+
fullOuterJoin(field, opts) {
|
|
569
|
+
return this.withJoinLeg(field, opts, "full");
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* The one place a `JoinLeg` is built. `.join()`, `.rightJoin()` and
|
|
573
|
+
* `.fullOuterJoin()` differ ONLY in `direction` and in the type they
|
|
574
|
+
* publish — keeping the plan-time validation (ref declared? dict join?
|
|
575
|
+
* join context?) in a single body is what stops the three from drifting on
|
|
576
|
+
* which errors they raise.
|
|
577
|
+
*/
|
|
578
|
+
withJoinLeg(field, opts, direction) {
|
|
579
|
+
if (!this.joinContext) {
|
|
580
|
+
throw new Error(
|
|
581
|
+
`Query.join() requires a join context (same for .rightJoin() and .fullOuterJoin()). Use collection.query() to construct a join-capable Query instead of the Query constructor directly (the direct constructor is only used for tests with plain-object sources).`
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
const descriptor = this.joinContext.resolveRef(field);
|
|
585
|
+
const isDictJoinField = !descriptor && this.joinContext.resolveDictSource?.(field) != null;
|
|
586
|
+
if (!descriptor && !isDictJoinField) {
|
|
587
|
+
throw new RefNotDeclaredError({
|
|
588
|
+
collection: this.joinContext.leftCollection,
|
|
589
|
+
field,
|
|
590
|
+
message: `Query.join(): no ref() declared for field "${field}" on collection "${this.joinContext.leftCollection}". Add refs: { ${field}: ref('<target-collection>') } to the collection options, then retry. See the ref() docs for the full list of modes.`
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
const directionField = direction === "left" ? {} : { direction };
|
|
594
|
+
const innerField = opts.mode === "inner" ? { inner: true } : {};
|
|
595
|
+
const leg = descriptor ? {
|
|
596
|
+
field,
|
|
597
|
+
as: opts.as,
|
|
598
|
+
target: descriptor.target,
|
|
599
|
+
mode: descriptor.mode,
|
|
600
|
+
strategy: opts.strategy,
|
|
601
|
+
maxRows: opts.maxRows,
|
|
602
|
+
...directionField,
|
|
603
|
+
...innerField,
|
|
604
|
+
// The partition seam — always 'all'. Do not remove, and do not
|
|
605
|
+
// populate without reading JoinLeg.partitionScope's two
|
|
606
|
+
// constraints (#1342): this value is inside every stored MV
|
|
607
|
+
// queryHash. Pinned by __tests__/query-partition-scope.test.ts.
|
|
608
|
+
partitionScope: "all"
|
|
609
|
+
} : {
|
|
610
|
+
// Dict join leg
|
|
611
|
+
field,
|
|
612
|
+
as: opts.as,
|
|
613
|
+
target: field,
|
|
614
|
+
// dict name = field name for dictKey
|
|
615
|
+
mode: "strict",
|
|
616
|
+
strategy: opts.strategy,
|
|
617
|
+
maxRows: opts.maxRows,
|
|
618
|
+
partitionScope: "all",
|
|
619
|
+
...directionField,
|
|
620
|
+
...innerField,
|
|
621
|
+
isDictJoin: true
|
|
622
|
+
};
|
|
623
|
+
return new Query(
|
|
624
|
+
this.source,
|
|
625
|
+
{ ...this.plan, joins: [...this.plan.joins, leg] },
|
|
626
|
+
this.joinContext,
|
|
627
|
+
this.reduceStrategy,
|
|
628
|
+
this.predicates
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Cartesian-product cross-join against `target` collection. Each result row
|
|
633
|
+
* carries the original `T` fields plus `result[as]` populated from every
|
|
634
|
+
* right-side row (or the filtered subset when `on:` is supplied).
|
|
635
|
+
*
|
|
636
|
+
* **⚠️ INNER-join semantics — an empty right subset DROPS the left row.**
|
|
637
|
+
* Each left row is emitted once per matching right row, so when `on:`
|
|
638
|
+
* yields nothing the row vanishes with no error, no warning and no count
|
|
639
|
+
* mismatch. This bites hardest on a reverse FK, where `.join()` (which is
|
|
640
|
+
* forward-only, and already a genuine LEFT outer join) does not apply and
|
|
641
|
+
* `.crossJoin()` is the only tool. To keep the row, return a one-element
|
|
642
|
+
* array holding `null`:
|
|
643
|
+
*
|
|
644
|
+
* ```ts
|
|
645
|
+
* .crossJoin('clients', { as: 'client', on: (b) => byEntity.get(b.entityId) ?? [null] })
|
|
646
|
+
* // ^^^^^^^^
|
|
647
|
+
* // the ONLY thing preserving the row
|
|
648
|
+
* ```
|
|
649
|
+
*
|
|
650
|
+
* **Prefer `outer: true`**, which does exactly this and types the alias as
|
|
651
|
+
* `TTarget | null`:
|
|
652
|
+
*
|
|
653
|
+
* ```ts
|
|
654
|
+
* .crossJoin('clients', { as: 'client', outer: true, on: (b) => byEntity.get(b.entityId) ?? [] })
|
|
655
|
+
* ```
|
|
656
|
+
*
|
|
657
|
+
* The `?? [null]` form remains valid and is what `outer` does internally, but
|
|
658
|
+
* it is invisible intent: it types the alias as non-null while the row can
|
|
659
|
+
* hold null, and a later "simplification" that drops it silently
|
|
660
|
+
* reintroduces the row loss.
|
|
661
|
+
*
|
|
662
|
+
* **Order matters:** `.where().crossJoin()` filters BEFORE expanding (cheaper);
|
|
663
|
+
* `.crossJoin().where('alias.field', ...)` filters AFTER (required when the
|
|
664
|
+
* where clause references the aliased fields).
|
|
665
|
+
*
|
|
666
|
+
* **Cost ceiling:** `CrossJoinTooLargeError` fires before allocation when
|
|
667
|
+
* `leftRows × rightRows` (or the cumulative lateral count) exceeds the limit.
|
|
668
|
+
* Default: 50,000 rows. Override per-clause with `{ maxRows: N }`.
|
|
669
|
+
*
|
|
670
|
+
* **`on:` shapes:**
|
|
671
|
+
* - `on: (left) => TTarget[]` — subset form (most efficient)
|
|
672
|
+
* - `on: (left) => (right) => boolean` — predicate form
|
|
673
|
+
* - `on: { predicate: 'name' }` — MV-safe, hash-tracked form
|
|
674
|
+
* (requires the Query to have been augmented via `_withPredicates`)
|
|
675
|
+
*
|
|
676
|
+
* Requires a JoinContext (constructed via `collection.query()`).
|
|
677
|
+
*/
|
|
678
|
+
crossJoin(target, opts) {
|
|
679
|
+
if (!this.joinContext) {
|
|
680
|
+
throw new Error(
|
|
681
|
+
`Query.crossJoin("${target}"): requires a join context. Use collection.query() to construct a cross-join-capable Query instead of the Query constructor directly.`
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
let onFn;
|
|
685
|
+
let onPredicateName;
|
|
686
|
+
if (opts.on !== void 0) {
|
|
687
|
+
if (typeof opts.on === "function") {
|
|
688
|
+
onFn = opts.on;
|
|
689
|
+
if (this.predicates) {
|
|
690
|
+
console.warn(
|
|
691
|
+
`Query.crossJoin("${target}", { on: callback }): inline on: callback inside a withMaterializedView query() disables queryHash drift detection for this cross-join. Use on: { predicate: '<name>' } to enable it.`
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
} else {
|
|
695
|
+
const predName = opts.on.predicate;
|
|
696
|
+
if (!this.predicates) {
|
|
697
|
+
throw new Error(
|
|
698
|
+
`Query.crossJoin("${target}", { on: { predicate: "${predName}" } }): the { predicate } form requires a predicates map. Use this form inside a withMaterializedView query() callback that declares predicates: { ${predName}: { hash, fn } }.`
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
const decl = this.predicates.get(predName);
|
|
702
|
+
if (!decl) {
|
|
703
|
+
throw new Error(
|
|
704
|
+
`Query.crossJoin("${target}"): predicate "${predName}" not registered. Available: ${[...this.predicates.keys()].join(", ") || "(none)"}.`
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
const as = opts.as;
|
|
708
|
+
const predicateFn = decl.fn;
|
|
709
|
+
onFn = (_left) => (right) => predicateFn({ ..._left, [as]: right });
|
|
710
|
+
onPredicateName = predName;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
const clause = {
|
|
714
|
+
type: "crossJoin",
|
|
715
|
+
target,
|
|
716
|
+
as: opts.as,
|
|
717
|
+
...onFn !== void 0 && { on: onFn },
|
|
718
|
+
...onPredicateName !== void 0 && { onPredicateName },
|
|
719
|
+
...opts.maxRows !== void 0 && { maxRows: opts.maxRows },
|
|
720
|
+
...opts.outer === true && { outer: true }
|
|
721
|
+
};
|
|
722
|
+
return new Query(
|
|
723
|
+
this.source,
|
|
724
|
+
{ ...this.plan, clauses: [...this.plan.clauses, clause] },
|
|
725
|
+
this.joinContext,
|
|
726
|
+
this.reduceStrategy,
|
|
727
|
+
this.predicates
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Self cross-join with BOTH sides aliased (#1289).
|
|
732
|
+
*
|
|
733
|
+
* ```ts
|
|
734
|
+
* const pairs = trades.query()
|
|
735
|
+
* .crossJoinWith({ leftAs: 'a', rightAs: 'b', on: (t) => laterThan(t) })
|
|
736
|
+
* .toArray()
|
|
737
|
+
* // → [{ a: Trade, b: Trade }, ...] — no field at the top level
|
|
738
|
+
* ```
|
|
739
|
+
*
|
|
740
|
+
* `.crossJoin('trades', { as: 'other' })` can already pair a collection
|
|
741
|
+
* with itself, but only ASYMMETRICALLY: the left row's fields stay at the
|
|
742
|
+
* top level and only the right side gets a name. Every comparison then
|
|
743
|
+
* reads `r.amount` against `r.other.amount`, which is exactly the shape
|
|
744
|
+
* that makes an accidentally-transposed pair invisible.
|
|
745
|
+
*
|
|
746
|
+
* ⚠️ **The cost is not the join, it is the Via dressing.** Aliasing the
|
|
747
|
+
* left row moves every field off the top level, and the money / i18n /
|
|
748
|
+
* lookup pipeline keys by BARE FIELD NAME — so the plain top-level decode
|
|
749
|
+
* `toArray()` applies cannot see either side. Both aliases are therefore
|
|
750
|
+
* dressed explicitly, through the source's own Via result decode, before
|
|
751
|
+
* the rows are returned. Silently serving raw money under an alias is the
|
|
752
|
+
* failure this method exists to not have; `dressAliases` is where it is
|
|
753
|
+
* prevented and `__tests__/query-outer-join.test.ts` is what proves it.
|
|
754
|
+
*
|
|
755
|
+
* `on:` takes the same subset / predicate shapes as `.crossJoin()`, and the
|
|
756
|
+
* same `maxRows` ceiling applies to the product. The target is always this
|
|
757
|
+
* query's own collection — a cross-join against a DIFFERENT collection is
|
|
758
|
+
* `.crossJoin()`, which needs no left alias to stay unambiguous.
|
|
759
|
+
*
|
|
760
|
+
* The returned `Query` drops this query's schema / queryable / money field
|
|
761
|
+
* parameters. That is not laziness: those parameters name TOP-LEVEL fields,
|
|
762
|
+
* and after `crossJoinWith` there are no top-level fields — only the two
|
|
763
|
+
* aliases. Carrying them would let `where('amount', ...)` type-check on a
|
|
764
|
+
* row where `amount` does not exist.
|
|
765
|
+
*/
|
|
766
|
+
crossJoinWith(opts) {
|
|
767
|
+
if (!this.joinContext) {
|
|
768
|
+
throw new Error(
|
|
769
|
+
`Query.crossJoinWith(): requires a join context. Use collection.query() to construct a cross-join-capable Query instead of the Query constructor directly.`
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
if (opts.leftAs === opts.rightAs) {
|
|
773
|
+
throw new Error(
|
|
774
|
+
`Query.crossJoinWith({ leftAs: "${opts.leftAs}", rightAs: "${opts.rightAs}" }): the two aliases must differ \u2014 a self cross-join whose sides share a name would emit the right row under both and silently lose the left one.`
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
const built = this.crossJoin(this.joinContext.leftCollection, {
|
|
778
|
+
as: opts.rightAs,
|
|
779
|
+
...opts.on !== void 0 && { on: opts.on },
|
|
780
|
+
...opts.maxRows !== void 0 && { maxRows: opts.maxRows }
|
|
781
|
+
});
|
|
782
|
+
const plan = built._plan();
|
|
783
|
+
const last = plan.clauses[plan.clauses.length - 1];
|
|
784
|
+
const clauses = [...plan.clauses.slice(0, -1), { ...last, leftAs: opts.leftAs }];
|
|
785
|
+
return new Query(
|
|
786
|
+
this.source,
|
|
787
|
+
{ ...plan, clauses },
|
|
788
|
+
this.joinContext,
|
|
789
|
+
this.reduceStrategy,
|
|
790
|
+
this.predicates
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Describe how this plan WOULD run (#1348): the dispatch per clause
|
|
795
|
+
* (`index:hash` vs `scan`, and why), row estimates taken from real index
|
|
796
|
+
* cardinalities, join strategy and side sizes against the row ceiling,
|
|
797
|
+
* whether ordering/pagination lands pre- or post-join, and which
|
|
798
|
+
* cardinality caps are close to tripping. `.text` renders one line per
|
|
799
|
+
* node.
|
|
800
|
+
*
|
|
801
|
+
* ⛔ Purely observational — it probes index buckets and reads snapshot
|
|
802
|
+
* sizes, never executes the plan. A terminal returns the same thing
|
|
803
|
+
* whether or not `explain()` was called. See `query/explain.ts`.
|
|
804
|
+
*/
|
|
805
|
+
explain() {
|
|
806
|
+
return explainPlan(this.source, this.plan, this.joinContext);
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Recursive traversal over ONE declared, self-referencing `ref()` field
|
|
810
|
+
* (#1352) — the org-chart / bill-of-materials / parent-client shape that
|
|
811
|
+
* otherwise forces the consumer to hand-roll recursion.
|
|
812
|
+
*
|
|
813
|
+
* ```ts
|
|
814
|
+
* people.query().where('name', '==', 'Dana')
|
|
815
|
+
* .traverse('parentId', { direction: 'up', maxDepth: 10 })
|
|
816
|
+
* // → [{ id: 'dana', record, depth: 0, path: ['dana'] },
|
|
817
|
+
* // { id: 'bea', record, depth: 1, path: ['dana', 'bea'] }, …]
|
|
818
|
+
* ```
|
|
819
|
+
*
|
|
820
|
+
* **A TERMINAL.** It returns rows, not a `Query`, so it composes with
|
|
821
|
+
* `where()` / `orderBy()` / `limit()` on the way IN, not on the way out.
|
|
822
|
+
*
|
|
823
|
+
* **Seeds are this query's matched records** — the clauses choose the roots
|
|
824
|
+
* of the walk, not which nodes the walk may pass through. A traversal from
|
|
825
|
+
* one seed is `ancestorsOf` / `descendantsOf` below.
|
|
826
|
+
*
|
|
827
|
+
* **`maxDepth` is required**, with no default: an unbounded walk over a
|
|
828
|
+
* large collection is a denial of service against your own UI, and a
|
|
829
|
+
* silent default would truncate a result without saying so.
|
|
830
|
+
*
|
|
831
|
+
* **Cycles terminate.** A parent chain can be circular through user error,
|
|
832
|
+
* and an unguarded BFS hangs the tab. The default `{ onCycle: 'stop' }`
|
|
833
|
+
* prunes a node the walk is already standing on; `'throw'` raises
|
|
834
|
+
* `TraversalCycleError` for hierarchies that are supposed to be acyclic.
|
|
835
|
+
*
|
|
836
|
+
* **A node is emitted once**, at its shallowest depth — so a diamond (two
|
|
837
|
+
* seeds converging on one ancestor) yields ONE row for that ancestor,
|
|
838
|
+
* carrying the path of whichever branch reached it first.
|
|
839
|
+
*
|
|
840
|
+
* **Declared refs only.** This is not a general graph engine: the field
|
|
841
|
+
* must carry a `ref()` whose target is this same collection.
|
|
842
|
+
*
|
|
843
|
+
* **Cost.** Every hop reads the vault's already-decrypted in-memory cache
|
|
844
|
+
* (`query()` is eager-mode only), so a depth-5 traversal costs
|
|
845
|
+
* `depth × frontier` Map lookups and ZERO additional decrypts — the
|
|
846
|
+
* AES-GCM work was paid once at `openVault()`. `direction: 'down'` adds one
|
|
847
|
+
* O(n) pass to build the reverse-FK index; `'up'` adds nothing.
|
|
848
|
+
*
|
|
849
|
+
* ⛔ **Not an `explain()` node.** `explain()` describes the plan, and a
|
|
850
|
+
* traversal is a terminal over the plan's result rather than a clause in
|
|
851
|
+
* it — the same reason `.window()` and #1336's post-group ops are absent.
|
|
852
|
+
* `explain()` on the seed query still describes how the SEEDS are found.
|
|
853
|
+
*/
|
|
854
|
+
traverse(field, opts) {
|
|
855
|
+
this.assertSelfRef(field, "traverse");
|
|
856
|
+
const _h = this.source.hydration;
|
|
857
|
+
if (_h !== void 0 && !_h.isHydrated()) return gateTerminal(_h, "traverse", () => this.traverse(field, opts));
|
|
858
|
+
return this.runTraverse(field, this.ids(), opts);
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* `.traverse()` sugar: every ancestor of one record, that record included
|
|
862
|
+
* at `depth: 0`. See {@link traverse} for the full contract.
|
|
863
|
+
*
|
|
864
|
+
* Refused on a query that already carries clauses — the seed is named
|
|
865
|
+
* explicitly here, so a `where()` would silently do nothing. Use
|
|
866
|
+
* `.traverse()` when the seeds come from a query.
|
|
867
|
+
*/
|
|
868
|
+
ancestorsOf(id, field, opts) {
|
|
869
|
+
return this.traverseFromId(id, field, "up", opts);
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* `.traverse()` sugar: every descendant of one record, that record included
|
|
873
|
+
* at `depth: 0`. See {@link traverse} for the full contract.
|
|
874
|
+
*/
|
|
875
|
+
descendantsOf(id, field, opts) {
|
|
876
|
+
return this.traverseFromId(id, field, "down", opts);
|
|
877
|
+
}
|
|
878
|
+
traverseFromId(id, field, direction, opts) {
|
|
879
|
+
const _h = this.source.hydration;
|
|
880
|
+
if (_h !== void 0 && !_h.isHydrated()) return gateTerminal(_h, direction === "up" ? "ancestorsOf" : "descendantsOf", () => this.traverseFromId(id, field, direction, opts));
|
|
881
|
+
const sugar = direction === "up" ? "ancestorsOf" : "descendantsOf";
|
|
882
|
+
const plan = this.plan;
|
|
883
|
+
if (plan.clauses.length > 0 || plan.orderBy.length > 0 || plan.joins.length > 0 || plan.limit !== void 0 || plan.offset !== 0) {
|
|
884
|
+
throw new Error(
|
|
885
|
+
`Query.${sugar}("${id}"): this query already carries clauses, but ${sugar}() seeds from the id you passed \u2014 the clauses would silently do nothing. Use .traverse("${field}", { direction: '${direction}', \u2026 }), whose seeds ARE the query's matched records, or call ${sugar}() on a bare collection.query().`
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
this.assertSelfRef(field, sugar);
|
|
889
|
+
return this.runTraverse(field, [id], { ...opts, direction });
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* The one place a traversal runs. Both entry points share it so they cannot
|
|
893
|
+
* drift on Via decoding or on which source view the walk reads.
|
|
894
|
+
*/
|
|
895
|
+
runTraverse(field, seeds, opts) {
|
|
896
|
+
const rows = runTraversal(this.source, field, seeds, opts);
|
|
897
|
+
return rows.map((row) => ({ ...row, record: this.decodeVia([row.record])[0] }));
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Plan-time validation shared by `.traverse()` and its sugar: the field must
|
|
901
|
+
* carry a `ref()`, and that ref must point back at this same collection.
|
|
902
|
+
*
|
|
903
|
+
* The second half is what keeps this from becoming a general graph engine.
|
|
904
|
+
* A ref to ANOTHER collection has no second hop — the target's records do
|
|
905
|
+
* not carry this field — so a two-collection "traversal" is a `.join()`
|
|
906
|
+
* wearing the wrong name, and would walk exactly one level before stopping
|
|
907
|
+
* for reasons the caller could not see.
|
|
908
|
+
*/
|
|
909
|
+
assertSelfRef(field, caller) {
|
|
910
|
+
if (!this.joinContext) {
|
|
911
|
+
throw new Error(
|
|
912
|
+
`Query.${caller}(): requires a collection-backed query. Use collection.query() instead of the Query constructor directly (the direct constructor is only used for tests with plain-object sources).`
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
const descriptor = this.joinContext.resolveRef(field);
|
|
916
|
+
if (!descriptor) {
|
|
917
|
+
throw new RefNotDeclaredError({
|
|
918
|
+
collection: this.joinContext.leftCollection,
|
|
919
|
+
field,
|
|
920
|
+
message: `Query.${caller}(): no ref() declared for field "${field}" on collection "${this.joinContext.leftCollection}". Traversal walks DECLARED refs only \u2014 add refs: { ${field}: ref('${this.joinContext.leftCollection}') } to the collection options, then retry.`
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
if (descriptor.target !== this.joinContext.leftCollection) {
|
|
924
|
+
throw new Error(
|
|
925
|
+
`Query.${caller}(): field "${field}" refs "${descriptor.target}", but traversal requires a self-referencing ref \u2014 one whose target is the collection being queried ("${this.joinContext.leftCollection}"). A ref across collections has no second hop; use .join("${field}", { as: \u2026 }) for that.`
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
function applyCrossJoin(leftRel, clause, rightSource) {
|
|
931
|
+
const rightRows = rightSource.snapshot();
|
|
932
|
+
const maxRows = clause.maxRows ?? DEFAULT_CROSS_JOIN_MAX_ROWS;
|
|
933
|
+
const { as } = clause;
|
|
934
|
+
const outer = clause.outer === true;
|
|
935
|
+
const { leftAs } = clause;
|
|
936
|
+
const emit = leftAs === void 0 ? (left, right) => ({ ...left, [as]: right }) : (left, right) => ({ [leftAs]: left, [as]: right });
|
|
937
|
+
if (!clause.on) {
|
|
938
|
+
const rightSide = outer && rightRows.length === 0 ? [null] : rightRows;
|
|
939
|
+
const product = leftRel.length * rightSide.length;
|
|
940
|
+
if (product > maxRows) {
|
|
941
|
+
throw new CrossJoinTooLargeError({ target: clause.target, expected: product, limit: maxRows });
|
|
942
|
+
}
|
|
943
|
+
const expanded2 = [];
|
|
944
|
+
for (const left of leftRel) {
|
|
945
|
+
const leftObj = left;
|
|
946
|
+
for (const right of rightSide) {
|
|
947
|
+
expanded2.push(emit(leftObj, right));
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return expanded2;
|
|
951
|
+
}
|
|
952
|
+
const expanded = [];
|
|
953
|
+
let cumulative = 0;
|
|
954
|
+
for (const left of leftRel) {
|
|
955
|
+
const callbackResult = clause.on(left);
|
|
956
|
+
let filteredRight;
|
|
957
|
+
if (Array.isArray(callbackResult)) {
|
|
958
|
+
filteredRight = callbackResult;
|
|
959
|
+
} else {
|
|
960
|
+
filteredRight = rightRows.filter(
|
|
961
|
+
callbackResult
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
if (outer && filteredRight.length === 0) filteredRight = [null];
|
|
965
|
+
cumulative += filteredRight.length;
|
|
966
|
+
if (cumulative > maxRows) {
|
|
967
|
+
throw new CrossJoinTooLargeError({
|
|
968
|
+
target: clause.target,
|
|
969
|
+
expected: cumulative,
|
|
970
|
+
limit: maxRows
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
const leftObj = left;
|
|
974
|
+
for (const right of filteredRight) {
|
|
975
|
+
expanded.push(emit(leftObj, right));
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
return expanded;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// src/kernel/query/relate/scan-methods.ts
|
|
982
|
+
var ScanRelateMethods = class {
|
|
983
|
+
/**
|
|
984
|
+
* Resolve a `ref()`-declared foreign key per record as the scan
|
|
985
|
+
* stream flows, attaching the right-side record (or null) under
|
|
986
|
+
* `opts.as`. — streaming joins over `scan()`.
|
|
987
|
+
*
|
|
988
|
+
* ```ts
|
|
989
|
+
* for await (const inv of invoices.scan().join('clientId', { as: 'client' })) {
|
|
990
|
+
* await processInvoice(inv) // inv.client is attached
|
|
991
|
+
* }
|
|
992
|
+
*
|
|
993
|
+
* // Or terminate with .aggregate() for streaming joined aggregation
|
|
994
|
+
* const { total } = await invoices.scan()
|
|
995
|
+
* .where('status', '==', 'open')
|
|
996
|
+
* .join('clientId', { as: 'client' })
|
|
997
|
+
* .aggregate({ total: sum('amount') })
|
|
998
|
+
* ```
|
|
999
|
+
*
|
|
1000
|
+
* **The key difference from eager `.join()`:** the LEFT
|
|
1001
|
+
* side streams page-by-page from the adapter and is never
|
|
1002
|
+
* materialized. Memory ceiling on the left is O(pageSize), not
|
|
1003
|
+
* O(rowCount). This is what makes streaming joins suitable for
|
|
1004
|
+
* collections that exceed the eager join's 50_000-row ceiling.
|
|
1005
|
+
*
|
|
1006
|
+
* **Right-side strategy** is auto-selected per leg:
|
|
1007
|
+
* - **Indexed** — right source exposes `lookupById`, so each
|
|
1008
|
+
* left row costs O(1). This is the common path for
|
|
1009
|
+
* Collection right sides, which back `lookupById` with a Map
|
|
1010
|
+
* lookup over the in-memory cache. The right collection must
|
|
1011
|
+
* be in eager mode (the same constraint as eager join's
|
|
1012
|
+
* `querySourceForJoin` from ).
|
|
1013
|
+
* - **Hash** — right source has only `snapshot()`. Build a
|
|
1014
|
+
* `Map<id, record>` once at iteration start, probe per left
|
|
1015
|
+
* row. Same correctness, same per-row cost as the indexed
|
|
1016
|
+
* path; the difference is the upfront cost of materializing
|
|
1017
|
+
* the right side once.
|
|
1018
|
+
*
|
|
1019
|
+
* Both strategies hold the right side in memory for the duration
|
|
1020
|
+
* of the iteration. The "streaming" property applies to the LEFT
|
|
1021
|
+
* side only — true left-and-right streaming joins (where neither
|
|
1022
|
+
* side fits in memory) require a sort-merge join planner that's
|
|
1023
|
+
* out of scope for.
|
|
1024
|
+
*
|
|
1025
|
+
* **Ref-mode semantics** match eager `.join()` exactly:
|
|
1026
|
+
* - `strict` → throws `DanglingReferenceError` mid-stream
|
|
1027
|
+
* when a left record points at a non-existent right id.
|
|
1028
|
+
* The throw aborts the async iterator — consumers should
|
|
1029
|
+
* wrap the `for await` in try/catch if they want to recover.
|
|
1030
|
+
* - `warn` → attaches `null` and emits a one-shot warning
|
|
1031
|
+
* per unique dangling pair (deduped via the same warn
|
|
1032
|
+
* channel as eager join).
|
|
1033
|
+
* - `cascade` → attaches `null` silently. A delete-time mode;
|
|
1034
|
+
* dangling refs at read time are mid-flight or pre-existing
|
|
1035
|
+
* orphans, not a DSL error.
|
|
1036
|
+
*
|
|
1037
|
+
* Left records with null/undefined FK values attach `null`
|
|
1038
|
+
* regardless of mode — same "no reference at all" policy as
|
|
1039
|
+
* eager join and write-time `enforceRefsOnPut`.
|
|
1040
|
+
*
|
|
1041
|
+
* **Multi-FK chaining** is supported via repeated `.join()`
|
|
1042
|
+
* calls: each leg resolves an independent ref. Each leg
|
|
1043
|
+
* independently picks its right-side strategy and applies its
|
|
1044
|
+
* own ref mode.
|
|
1045
|
+
*
|
|
1046
|
+
* **Joins are NOT applied** to a `.aggregate()` terminal that
|
|
1047
|
+
* doesn't reference joined fields — wait, that's not quite
|
|
1048
|
+
* right. The streaming path actually DOES apply joins before
|
|
1049
|
+
* `.aggregate()` because the join attaches a field that the
|
|
1050
|
+
* spec might reference. Unlike `Query.aggregate()` (which skips
|
|
1051
|
+
* joins entirely as a projection-only short-circuit), the
|
|
1052
|
+
* streaming aggregation can't know whether the spec touches a
|
|
1053
|
+
* joined field, so it always applies joins. Consumers who want
|
|
1054
|
+
* unjoined streaming aggregation should leave `.join()` off the
|
|
1055
|
+
* chain — the chain is composable for a reason.
|
|
1056
|
+
*
|
|
1057
|
+
* Every JoinLeg carries `partitionScope: 'all'`, plumbed through but never
|
|
1058
|
+
* read. Same seam as the eager join — see {@link JoinLeg.partitionScope}.
|
|
1059
|
+
*/
|
|
1060
|
+
join(field, opts) {
|
|
1061
|
+
if (!this.joinContext) {
|
|
1062
|
+
throw new Error(
|
|
1063
|
+
`ScanBuilder.join() requires a join context. Use collection.scan() to construct a join-capable scan instead of the ScanBuilder constructor directly (the direct constructor is only used for tests with synthetic page providers).`
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
const descriptor = this.joinContext.resolveRef(field);
|
|
1067
|
+
if (!descriptor) {
|
|
1068
|
+
throw new RefNotDeclaredError({
|
|
1069
|
+
collection: this.joinContext.leftCollection,
|
|
1070
|
+
field,
|
|
1071
|
+
message: `ScanBuilder.join(): no ref() declared for field "${field}" on collection "${this.joinContext.leftCollection}". Add refs: { ${field}: ref('<target-collection>') } to the collection options, then retry.`
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1074
|
+
const leg = {
|
|
1075
|
+
field,
|
|
1076
|
+
as: opts.as,
|
|
1077
|
+
target: descriptor.target,
|
|
1078
|
+
mode: descriptor.mode,
|
|
1079
|
+
strategy: void 0,
|
|
1080
|
+
maxRows: void 0,
|
|
1081
|
+
// Always 'all', never read by the streaming executor. This is the
|
|
1082
|
+
// path where partition pruning would actually pay (every listPage()
|
|
1083
|
+
// page is decrypted) — and the path blocked on a store-contract
|
|
1084
|
+
// change. See JoinLeg.partitionScope constraint 2 (#1342).
|
|
1085
|
+
partitionScope: "all"
|
|
1086
|
+
};
|
|
1087
|
+
return new ScanBuilder(
|
|
1088
|
+
this.pageProvider,
|
|
1089
|
+
this.pageSize,
|
|
1090
|
+
this.clauses,
|
|
1091
|
+
[...this.joins, leg],
|
|
1092
|
+
this.joinContext,
|
|
1093
|
+
this.via
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Iterate the scan as an async iterable. Walks the page
|
|
1098
|
+
* provider's cursors forward until exhaustion, applying every
|
|
1099
|
+
* clause per record — only matching records are yielded.
|
|
1100
|
+
*
|
|
1101
|
+
* Backward-compatible with the previous async-generator `scan()`
|
|
1102
|
+
* return type for `for await … of` consumers.
|
|
1103
|
+
*/
|
|
1104
|
+
/**
|
|
1105
|
+
* Per-leg right-side resolution state. Built once at iteration
|
|
1106
|
+
* start and reused for every left record. Two strategies:
|
|
1107
|
+
*
|
|
1108
|
+
* - `lookupById`: present when the right source exposes the
|
|
1109
|
+
* hook directly (typical Collection right side). Per-row
|
|
1110
|
+
* cost is O(1).
|
|
1111
|
+
* - `hashByPrimaryKey`: built from `snapshot()` when no
|
|
1112
|
+
* lookupById. Per-row cost is O(1) after the upfront O(N)
|
|
1113
|
+
* materialization. Same as eager join's hash strategy.
|
|
1114
|
+
*
|
|
1115
|
+
* `warnedKeys` is the per-leg dedup set for ref-mode 'warn'. We
|
|
1116
|
+
* key on `field→target:refId` so the same dangling pair only
|
|
1117
|
+
* warns once per iteration. The dedup is per-iteration, not
|
|
1118
|
+
* per-process — a long-running scan that re-iterates would warn
|
|
1119
|
+
* again, which is the desired behavior (the data may have
|
|
1120
|
+
* changed between iterations).
|
|
1121
|
+
*/
|
|
1122
|
+
buildJoinResolvers() {
|
|
1123
|
+
if (!this.joinContext) {
|
|
1124
|
+
throw new Error(
|
|
1125
|
+
`ScanBuilder iterator: ${this.joins.length} join leg(s) present but no JoinContext attached. Use collection.scan() to construct a join-capable scan.`
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
const resolvers = [];
|
|
1129
|
+
for (const leg of this.joins) {
|
|
1130
|
+
if (leg.on !== void 0) {
|
|
1131
|
+
throw new Error(
|
|
1132
|
+
`ScanBuilder: the declared \`on\` join leg "${leg.as}" cannot stream \u2014 it emits one row per match, while scan() yields one row per stored record. Use collection.query().joinOn().`
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1135
|
+
const source = this.joinContext.resolveSource(leg.target);
|
|
1136
|
+
if (!source) {
|
|
1137
|
+
throw new Error(
|
|
1138
|
+
`ScanBuilder.join() cannot resolve target collection "${leg.target}" (referenced from field "${leg.field}" on "${this.joinContext.leftCollection}"). Make sure the target collection has been opened via vault.collection() at least once before iterating the scan.`
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
let lookupById = null;
|
|
1142
|
+
let hashByPrimaryKey = null;
|
|
1143
|
+
if (source.lookupById) {
|
|
1144
|
+
const fn = source.lookupById.bind(source);
|
|
1145
|
+
lookupById = (id) => fn(id);
|
|
1146
|
+
} else {
|
|
1147
|
+
const map = /* @__PURE__ */ new Map();
|
|
1148
|
+
for (const record of source.snapshot()) {
|
|
1149
|
+
const rawId = readPath(record, "id");
|
|
1150
|
+
const key = coerceRefKey(rawId);
|
|
1151
|
+
if (key !== null) map.set(key, record);
|
|
1152
|
+
}
|
|
1153
|
+
hashByPrimaryKey = map;
|
|
1154
|
+
}
|
|
1155
|
+
resolvers.push({
|
|
1156
|
+
leg,
|
|
1157
|
+
source,
|
|
1158
|
+
lookupById,
|
|
1159
|
+
hashByPrimaryKey,
|
|
1160
|
+
warnedKeys: /* @__PURE__ */ new Set()
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
return resolvers;
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Resolve a single join leg for one left record and return the
|
|
1167
|
+
* left record with the joined field attached under
|
|
1168
|
+
* `leg.as`. Pure function over `(left, resolver)`; never
|
|
1169
|
+
* mutates the input.
|
|
1170
|
+
*
|
|
1171
|
+
* Ref-mode dispatch matches eager `applyJoins` from :
|
|
1172
|
+
* - null/undefined FK → attach null silently (always allowed)
|
|
1173
|
+
* - dangling FK + strict → throw `DanglingReferenceError`
|
|
1174
|
+
* - dangling FK + warn → attach null, warn-once per pair
|
|
1175
|
+
* - dangling FK + cascade → attach null silently
|
|
1176
|
+
*/
|
|
1177
|
+
applyOneJoinStreaming(left, resolver) {
|
|
1178
|
+
if (left === null || typeof left !== "object") {
|
|
1179
|
+
return left;
|
|
1180
|
+
}
|
|
1181
|
+
const { leg } = resolver;
|
|
1182
|
+
const rawId = readPath(left, leg.field);
|
|
1183
|
+
const refKey = coerceRefKey(rawId);
|
|
1184
|
+
let right = void 0;
|
|
1185
|
+
if (refKey !== null) {
|
|
1186
|
+
if (resolver.lookupById !== null) {
|
|
1187
|
+
right = resolver.lookupById(refKey);
|
|
1188
|
+
} else if (resolver.hashByPrimaryKey !== null) {
|
|
1189
|
+
right = resolver.hashByPrimaryKey.get(refKey);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
const merged = {
|
|
1193
|
+
...left
|
|
1194
|
+
};
|
|
1195
|
+
if (right === void 0) {
|
|
1196
|
+
if (refKey !== null && leg.mode === "strict") {
|
|
1197
|
+
throw new DanglingReferenceError({
|
|
1198
|
+
field: leg.field,
|
|
1199
|
+
target: leg.target,
|
|
1200
|
+
refId: refKey,
|
|
1201
|
+
message: `ScanBuilder.join() strict dangling: record references "${leg.target}:${refKey}" via field "${leg.field}", but no such record exists. Use ref() mode 'warn' or 'cascade' if dangling refs are acceptable, or run vault.checkIntegrity() to find and fix the orphans.`
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
if (refKey !== null && leg.mode === "warn") {
|
|
1205
|
+
const dedupKey = `${leg.field}\u2192${leg.target}:${refKey}`;
|
|
1206
|
+
if (!resolver.warnedKeys.has(dedupKey)) {
|
|
1207
|
+
resolver.warnedKeys.add(dedupKey);
|
|
1208
|
+
console.warn(
|
|
1209
|
+
`[noy-db] ScanBuilder.join() encountered dangling ref in 'warn' mode: field "${leg.field}" \u2192 "${leg.target}:${refKey}" not found. Attaching null.`
|
|
1210
|
+
);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
merged[leg.as] = null;
|
|
1214
|
+
} else {
|
|
1215
|
+
merged[leg.as] = right;
|
|
1216
|
+
}
|
|
1217
|
+
return merged;
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Reduce the scan stream through a named set of reducers and
|
|
1221
|
+
* return the final aggregated shape.
|
|
1222
|
+
*
|
|
1223
|
+
* Memory is O(reducers): one mutable state slot per spec key.
|
|
1224
|
+
* Records flow through the pipeline one at a time via
|
|
1225
|
+
* `for await` and are discarded after their `step()` is applied
|
|
1226
|
+
* — never collected into an array. This is the distinguishing
|
|
1227
|
+
* property from `Query.aggregate()`, which materializes the full
|
|
1228
|
+
* match set first.
|
|
1229
|
+
*
|
|
1230
|
+
* Reuses the same reducer protocol as `Query.aggregate()`,
|
|
1231
|
+
* so `count()`, `sum(field)`, `avg(field)`, `min(field)`,
|
|
1232
|
+
* `max(field)` all work unchanged. The `{ seed }` parameter
|
|
1233
|
+
* plumbing from constraint #2 is honored transparently — the
|
|
1234
|
+
* factories ignore it in and the scan executor never
|
|
1235
|
+
* touches the per-reducer state construction.
|
|
1236
|
+
*
|
|
1237
|
+
* **Returns a Promise**, unlike `Query.aggregate().run()` which
|
|
1238
|
+
* is synchronous. The scan is inherently async because it walks
|
|
1239
|
+
* adapter pages, so the terminal has to be too. Consumers
|
|
1240
|
+
* destructure with await:
|
|
1241
|
+
*
|
|
1242
|
+
* ```ts
|
|
1243
|
+
* const { total, n } = await invoices.scan()
|
|
1244
|
+
* .where('year', '==', 2025)
|
|
1245
|
+
* .aggregate({ total: sum('amount'), n: count() })
|
|
1246
|
+
* ```
|
|
1247
|
+
*
|
|
1248
|
+
* **No `.live()` in.** `scan().aggregate().live()` would
|
|
1249
|
+
* require reconciling an unbounded streaming iteration with a
|
|
1250
|
+
* change-stream subscription — a design problem, not just a code
|
|
1251
|
+
* one. Consumers with huge collections and live needs should
|
|
1252
|
+
* narrow with `.where()` enough to fit in the 50k `query()`
|
|
1253
|
+
* limit and use `query().aggregate().live()` instead.
|
|
1254
|
+
*
|
|
1255
|
+
* Consults the Via pipeline's posture before reducing (#629 Task 8 review
|
|
1256
|
+
* fix wave 1): a reducer over a field whose posture is `queryable: 'none'`
|
|
1257
|
+
* throws `FieldNotQueryableError` here, metadata-only — via
|
|
1258
|
+
* `ViaPipeline.refuseUnqueryableReducers`, NOT the full `wrapReducers`
|
|
1259
|
+
* (which would also activate money's exact-reducer rewrite, a path this
|
|
1260
|
+
* method has never run and must not start running as a side effect of
|
|
1261
|
+
* this gate).
|
|
1262
|
+
*/
|
|
1263
|
+
// `const` so an inline `[specA, specB]` infers as a TUPLE, not a `Spec[]` —
|
|
1264
|
+
// that is what makes `const [a, b] = await …` land on the right shapes.
|
|
1265
|
+
};
|
|
1266
|
+
|
|
1267
|
+
// src/kernel/query/relate/install.ts
|
|
1268
|
+
var done = false;
|
|
1269
|
+
function installRelate() {
|
|
1270
|
+
if (done) return;
|
|
1271
|
+
done = true;
|
|
1272
|
+
installMethods(Query.prototype, RelateMethods);
|
|
1273
|
+
installMethods(ScanBuilder.prototype, ScanRelateMethods);
|
|
1274
|
+
installRelateHooks({
|
|
1275
|
+
applyJoins,
|
|
1276
|
+
splitAroundJoins,
|
|
1277
|
+
orderReferencesJoinAlias,
|
|
1278
|
+
joinsDropLeftRows,
|
|
1279
|
+
applyCrossJoin
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
export {
|
|
1284
|
+
explainPlan,
|
|
1285
|
+
renderExplainText,
|
|
1286
|
+
runTraversal,
|
|
1287
|
+
installRelate
|
|
1288
|
+
};
|
|
1289
|
+
//# sourceMappingURL=chunk-ZSX4S5XB.js.map
|