@noy-db/hub 0.2.0-pre.2 → 0.2.0-pre.21
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/README.md +126 -0
- package/dist/aggregate/index.cjs +643 -37
- package/dist/aggregate/index.cjs.map +1 -1
- package/dist/aggregate/index.d.cts +3 -2
- package/dist/aggregate/index.d.ts +3 -2
- package/dist/aggregate/index.js +9 -8
- package/dist/aggregate/index.js.map +1 -1
- package/dist/attestation/index.cjs.map +1 -1
- package/dist/attestation/index.d.cts +7 -5
- package/dist/attestation/index.d.ts +7 -5
- package/dist/attestation/index.js +6 -6
- package/dist/blobs/index.cjs +509 -22
- package/dist/blobs/index.cjs.map +1 -1
- package/dist/blobs/index.d.cts +9 -7
- package/dist/blobs/index.d.ts +9 -7
- package/dist/blobs/index.js +11 -6
- package/dist/blobs/index.js.map +1 -1
- package/dist/bundle/index.cjs +7886 -841
- package/dist/bundle/index.cjs.map +1 -1
- package/dist/bundle/index.d.cts +20 -18
- package/dist/bundle/index.d.ts +20 -18
- package/dist/bundle/index.js +24 -13
- package/dist/bundle/index.js.map +1 -1
- package/dist/{chunk-PFSNOPBQ.js → chunk-2XA2ZML4.js} +31 -3
- package/dist/chunk-2XA2ZML4.js.map +1 -0
- package/dist/{chunk-2PAQNPE3.js → chunk-37VGJM3T.js} +37 -2
- package/dist/chunk-37VGJM3T.js.map +1 -0
- package/dist/{chunk-7BRE6EUA.js → chunk-3HNKR65T.js} +4 -4
- package/dist/chunk-3HNKR65T.js.map +1 -0
- package/dist/{chunk-Y2RKOPNC.js → chunk-5YTXYPES.js} +46 -10
- package/dist/chunk-5YTXYPES.js.map +1 -0
- package/dist/{chunk-OVZDFEOR.js → chunk-6QAZ5O6X.js} +2 -2
- package/dist/chunk-6QAZ5O6X.js.map +1 -0
- package/dist/{chunk-RTZVQAJ7.js → chunk-6QE4DUYC.js} +19 -4
- package/dist/chunk-6QE4DUYC.js.map +1 -0
- package/dist/{chunk-7Q5PLD5C.js → chunk-7MRT7EPB.js} +3 -3
- package/dist/{chunk-E535SAN4.js → chunk-7PH4OPBZ.js} +4258 -520
- package/dist/chunk-7PH4OPBZ.js.map +1 -0
- package/dist/{chunk-PEULZC6M.js → chunk-A3JMGXPG.js} +8 -1
- package/dist/chunk-A3JMGXPG.js.map +1 -0
- package/dist/{chunk-UMLVJTYV.js → chunk-ADB7GPM3.js} +7 -4
- package/dist/chunk-ADB7GPM3.js.map +1 -0
- package/dist/{chunk-G6FRSBKK.js → chunk-AI4USDRI.js} +4 -4
- package/dist/chunk-BZW5IL43.js +151 -0
- package/dist/chunk-BZW5IL43.js.map +1 -0
- package/dist/chunk-C2RJVZZL.js +123 -0
- package/dist/chunk-C2RJVZZL.js.map +1 -0
- package/dist/{chunk-UND4XIB6.js → chunk-C6W5KVDV.js} +52 -38
- package/dist/chunk-C6W5KVDV.js.map +1 -0
- package/dist/chunk-CQYEDODS.js +125 -0
- package/dist/chunk-CQYEDODS.js.map +1 -0
- package/dist/{chunk-NWZ3I6R6.js → chunk-EYK72OTL.js} +5 -5
- package/dist/{chunk-7BUTTVMR.js → chunk-F5GWNSE2.js} +2 -2
- package/dist/{chunk-AHPFONIL.js → chunk-F5ILTHMU.js} +5 -5
- package/dist/{chunk-Q6W2CMEJ.js → chunk-FRRJIUSI.js} +18 -5
- package/dist/chunk-FRRJIUSI.js.map +1 -0
- package/dist/{chunk-YMYK7US4.js → chunk-GJTKMME7.js} +2 -2
- package/dist/chunk-GJTKMME7.js.map +1 -0
- package/dist/{chunk-EUYOGYGV.js → chunk-HYJMAV53.js} +6 -6
- package/dist/chunk-HYJMAV53.js.map +1 -0
- package/dist/{chunk-QPEXPHJR.js → chunk-I3IYTUUI.js} +4 -4
- package/dist/{chunk-3QAKZ37R.js → chunk-IVZWHIEK.js} +5 -5
- package/dist/{chunk-PLI5TV7N.js → chunk-IW4L4X65.js} +2 -2
- package/dist/chunk-IW4L4X65.js.map +1 -0
- package/dist/{chunk-3Z2TPHC4.js → chunk-IY24WS2P.js} +69 -5
- package/dist/chunk-IY24WS2P.js.map +1 -0
- package/dist/{chunk-HXJXPZRE.js → chunk-J6RGRZOY.js} +10 -3
- package/dist/chunk-J6RGRZOY.js.map +1 -0
- package/dist/{chunk-3S4BJX25.js → chunk-JBBWALNI.js} +2 -2
- package/dist/chunk-JBBWALNI.js.map +1 -0
- package/dist/{chunk-7Z23ZFLV.js → chunk-JDCPRJVS.js} +5 -5
- package/dist/chunk-JDCPRJVS.js.map +1 -0
- package/dist/{chunk-243PNUA6.js → chunk-JOK73NDT.js} +3 -3
- package/dist/chunk-JTI57WRT.js +164 -0
- package/dist/chunk-JTI57WRT.js.map +1 -0
- package/dist/{chunk-VRBCTEKQ.js → chunk-JYNH4FIM.js} +233 -11
- package/dist/chunk-JYNH4FIM.js.map +1 -0
- package/dist/{chunk-TBKOGSYR.js → chunk-KOAJ3TZM.js} +27 -5
- package/dist/chunk-KOAJ3TZM.js.map +1 -0
- package/dist/{chunk-YTXSFG3C.js → chunk-MBXKRHSS.js} +50 -20
- package/dist/chunk-MBXKRHSS.js.map +1 -0
- package/dist/{chunk-MUWOSVEP.js → chunk-NSXNXLYM.js} +10 -2
- package/dist/chunk-NSXNXLYM.js.map +1 -0
- package/dist/{chunk-J4KLMEUL.js → chunk-NV4IHBZS.js} +664 -51
- package/dist/chunk-NV4IHBZS.js.map +1 -0
- package/dist/{chunk-LRAZDV5X.js → chunk-O5XKZCUD.js} +31 -8
- package/dist/chunk-O5XKZCUD.js.map +1 -0
- package/dist/{chunk-W3XXT26A.js → chunk-OTWT6BAJ.js} +358 -3
- package/dist/chunk-OTWT6BAJ.js.map +1 -0
- package/dist/{chunk-XG3PTSCD.js → chunk-PDVP3C2I.js} +1 -1
- package/dist/chunk-PDVP3C2I.js.map +1 -0
- package/dist/{chunk-GIV6DWBG.js → chunk-S45MDEEF.js} +44 -5
- package/dist/chunk-S45MDEEF.js.map +1 -0
- package/dist/{chunk-VK5EER6C.js → chunk-SQKAECUL.js} +2 -2
- package/dist/{chunk-FAQVNJD4.js → chunk-SQOK5UM6.js} +12 -2
- package/dist/{chunk-FAQVNJD4.js.map → chunk-SQOK5UM6.js.map} +1 -1
- package/dist/chunk-STNPB3UM.js +9 -0
- package/dist/chunk-STNPB3UM.js.map +1 -0
- package/dist/{chunk-YS3POABP.js → chunk-TA6HPKWQ.js} +1 -1
- package/dist/chunk-TA6HPKWQ.js.map +1 -0
- package/dist/{chunk-4HIL6AHQ.js → chunk-TAMRU7A2.js} +4 -4
- package/dist/{chunk-QXQRKXCU.js → chunk-TGIJTNM3.js} +2 -2
- package/dist/chunk-TNH5SLCD.js +361 -0
- package/dist/chunk-TNH5SLCD.js.map +1 -0
- package/dist/{chunk-VPSUZLOJ.js → chunk-TYMDCIQM.js} +31 -5
- package/dist/chunk-TYMDCIQM.js.map +1 -0
- package/dist/chunk-U2XSUCDF.js +524 -0
- package/dist/chunk-U2XSUCDF.js.map +1 -0
- package/dist/{chunk-3Y53S2SA.js → chunk-UU6M64HI.js} +4 -4
- package/dist/{chunk-VCGTOS2A.js → chunk-WE2BUQD2.js} +3 -3
- package/dist/chunk-WE2BUQD2.js.map +1 -0
- package/dist/{chunk-JYQTXEIO.js → chunk-WWVJXBOT.js} +449 -29
- package/dist/chunk-WWVJXBOT.js.map +1 -0
- package/dist/chunk-YPIOFSN3.js +129 -0
- package/dist/chunk-YPIOFSN3.js.map +1 -0
- package/dist/chunk-ZC7J6ZYV.js +7 -0
- package/dist/chunk-ZC7J6ZYV.js.map +1 -0
- package/dist/{chunk-5ZGZ6HIZ.js → chunk-ZONKSLF2.js} +30 -7
- package/dist/chunk-ZONKSLF2.js.map +1 -0
- package/dist/consent/index.cjs.map +1 -1
- package/dist/consent/index.d.cts +8 -6
- package/dist/consent/index.d.ts +8 -6
- package/dist/consent/index.js +3 -3
- package/dist/{crypto-5ZDIY3NG.js → crypto-456N7UVX.js} +7 -3
- package/dist/{delegation-QYXZW25W.js → delegation-DP4COTXB.js} +5 -5
- package/dist/derivations/index.cjs +124 -6
- package/dist/derivations/index.cjs.map +1 -1
- package/dist/derivations/index.d.cts +11 -9
- package/dist/derivations/index.d.ts +11 -9
- package/dist/derivations/index.js +8 -6
- package/dist/{dev-unlock-DQCNDfFp.d.cts → dev-unlock-CY0HIZA0.d.cts} +1 -1
- package/dist/{dev-unlock-utkybTKb.d.ts → dev-unlock-CpKSkl2c.d.ts} +1 -1
- package/dist/discriminant-BN9REW3o.d.cts +60 -0
- package/dist/discriminant-BN9REW3o.d.ts +60 -0
- package/dist/errors-Dkc_fi-S.d.cts +1467 -0
- package/dist/errors-Dkc_fi-S.d.ts +1467 -0
- package/dist/executor-4IEW4KG5.js +8 -0
- package/dist/executor-KYJCJCIN.js +12 -0
- package/dist/executor-W7VIBOBZ.js +8 -0
- package/dist/{fanout-sidecar-VJ52RIEY.js → fanout-sidecar-YXNAEZ33.js} +2 -2
- package/dist/fanout-sidecar-YXNAEZ33.js.map +1 -0
- package/dist/forget/index.cjs +43 -0
- package/dist/forget/index.cjs.map +1 -0
- package/dist/forget/index.d.cts +1 -0
- package/dist/forget/index.d.ts +1 -0
- package/dist/forget/index.js +14 -0
- package/dist/guards/index.cjs +144 -4
- package/dist/guards/index.cjs.map +1 -1
- package/dist/guards/index.d.cts +16 -8
- package/dist/guards/index.d.ts +16 -8
- package/dist/guards/index.js +13 -7
- package/dist/{hash-jDowCrK2.d.cts → hash-BSd0-_L8.d.cts} +1 -1
- package/dist/{hash-DcoYWfJ_.d.ts → hash-BnBQx39y.d.ts} +1 -1
- package/dist/history/index.cjs +28 -5
- package/dist/history/index.cjs.map +1 -1
- package/dist/history/index.d.cts +9 -7
- package/dist/history/index.d.ts +9 -7
- package/dist/history/index.js +9 -7
- package/dist/history/index.js.map +1 -1
- package/dist/i18n/index.cjs +356 -26
- package/dist/i18n/index.cjs.map +1 -1
- package/dist/i18n/index.d.cts +8 -6
- package/dist/i18n/index.d.ts +8 -6
- package/dist/i18n/index.js +36 -15
- package/dist/i18n/index.js.map +1 -1
- package/dist/index-BMmajblo.d.cts +362 -0
- package/dist/index-BMmajblo.d.ts +362 -0
- package/dist/{index-BCKdioeh.d.ts → index-Bm9hIY7t.d.ts} +169 -1127
- package/dist/{index-BMjrzNZr.d.cts → index-tZqVB9g5.d.cts} +169 -1127
- package/dist/index.cjs +10286 -2168
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +258 -23
- package/dist/index.d.ts +258 -23
- package/dist/index.js +443 -110
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.cjs +97 -32
- package/dist/indexing/index.cjs.map +1 -1
- package/dist/indexing/index.d.cts +3 -3
- package/dist/indexing/index.d.ts +3 -3
- package/dist/indexing/index.js +4 -4
- package/dist/issue-JXC6T2QR.js +12 -0
- package/dist/{lazy-builder-Rpd-V3jP.d.ts → lazy-builder-ChSqcF5t.d.ts} +2 -2
- package/dist/{lazy-builder-C-rPfWG0.d.cts → lazy-builder-eYZzLEL1.d.cts} +2 -2
- package/dist/{ledger-3IU5GMXA.js → ledger-I7JUYP4L.js} +6 -6
- package/dist/materialized-views/index.cjs +687 -13
- package/dist/materialized-views/index.cjs.map +1 -1
- package/dist/materialized-views/index.d.cts +23 -20
- package/dist/materialized-views/index.d.ts +23 -20
- package/dist/materialized-views/index.js +8 -7
- package/dist/mime-magic-BnJCGJzB.d.cts +103 -0
- package/dist/mime-magic-CjSyakO4.d.ts +103 -0
- package/dist/noydb-ZZCRF6TE.js +38 -0
- package/dist/overlay-views/index.cjs +58 -18
- package/dist/overlay-views/index.cjs.map +1 -1
- package/dist/overlay-views/index.d.cts +32 -12
- package/dist/overlay-views/index.d.ts +32 -12
- package/dist/overlay-views/index.js +6 -6
- package/dist/periods/index.cjs.map +1 -1
- package/dist/periods/index.d.cts +8 -6
- package/dist/periods/index.d.ts +8 -6
- package/dist/periods/index.js +6 -6
- package/dist/{predicate-Dnu81tsS.d.cts → predicate-BmhBSPCH.d.cts} +87 -5
- package/dist/{predicate-Dnu81tsS.d.ts → predicate-BmhBSPCH.d.ts} +87 -5
- package/dist/{public-envelope-U3CMEOMV.js → public-envelope-5XRTUNKF.js} +4 -4
- package/dist/query/index.cjs +1438 -130
- package/dist/query/index.cjs.map +1 -1
- package/dist/query/index.d.cts +4 -3
- package/dist/query/index.d.ts +4 -3
- package/dist/query/index.js +13 -6
- package/dist/read-only-facade-EX6WZZBP.js +7 -0
- package/dist/registry-ATRHOG5B.js +8 -0
- package/dist/registry-DKEXOJVO.js +7 -0
- package/dist/registry-LEHB26TY.js +8 -0
- package/dist/{registry-3ALP62P6.js → registry-NWHOLD5M.js} +3 -3
- package/dist/{revoke-KY2GB4KP.js → revoke-5IEK22KT.js} +6 -6
- package/dist/sealed-record/index.cjs +139 -0
- package/dist/sealed-record/index.cjs.map +1 -0
- package/dist/sealed-record/index.d.cts +123 -0
- package/dist/sealed-record/index.d.ts +123 -0
- package/dist/sealed-record/index.js +42 -0
- package/dist/sealed-record/index.js.map +1 -0
- package/dist/session/index.cjs.map +1 -1
- package/dist/session/index.d.cts +9 -7
- package/dist/session/index.d.ts +9 -7
- package/dist/session/index.js +3 -3
- package/dist/shadow/index.cjs.map +1 -1
- package/dist/shadow/index.d.cts +8 -6
- package/dist/shadow/index.d.ts +8 -6
- package/dist/shadow/index.js +2 -2
- package/dist/{signer-GRI5TZKH.js → signer-I6YARZQA.js} +5 -5
- package/dist/snapshots/index.cjs +937 -0
- package/dist/snapshots/index.cjs.map +1 -0
- package/dist/snapshots/index.d.cts +30 -0
- package/dist/snapshots/index.d.ts +30 -0
- package/dist/snapshots/index.js +152 -0
- package/dist/snapshots/index.js.map +1 -0
- package/dist/{stale-OTOF3FH7.js → stale-CPESGAPL.js} +2 -2
- package/dist/stale-CPESGAPL.js.map +1 -0
- package/dist/state-vault-JR3CFGNP.js +14 -0
- package/dist/state-vault-JR3CFGNP.js.map +1 -0
- package/dist/store/index.cjs +8 -0
- package/dist/store/index.cjs.map +1 -1
- package/dist/store/index.d.cts +15 -6
- package/dist/store/index.d.ts +15 -6
- package/dist/store/index.js +2 -2
- package/dist/{strategy-DSTrsZ8t.d.ts → strategy-54eIwox5.d.ts} +456 -7
- package/dist/{strategy-DSTrsZ8t.d.cts → strategy-WtB-jXYv.d.cts} +456 -7
- package/dist/sync/index.cjs.map +1 -1
- package/dist/sync/index.d.cts +7 -5
- package/dist/sync/index.d.ts +7 -5
- package/dist/sync/index.js +4 -4
- package/dist/team/index.cjs +1 -1
- package/dist/team/index.cjs.map +1 -1
- package/dist/team/index.d.cts +8 -6
- package/dist/team/index.d.ts +8 -6
- package/dist/team/index.js +8 -8
- package/dist/transition-guard-D4bfIAiW.d.ts +165 -0
- package/dist/transition-guard-Dmpqzg-_.d.cts +165 -0
- package/dist/tx/index.cjs +155 -5
- package/dist/tx/index.cjs.map +1 -1
- package/dist/tx/index.d.cts +27 -9
- package/dist/tx/index.d.ts +27 -9
- package/dist/tx/index.js +61 -4
- package/dist/tx/index.js.map +1 -1
- package/dist/{types-BoFFiskX.d.ts → types-DLfWFr6U.d.ts} +3997 -1262
- package/dist/{types-DJG8HG6F.d.cts → types-DyOI6XZ_.d.cts} +3997 -1262
- package/dist/{ulid-BmBgooGm.d.ts → ulid-B2L_aqVA.d.ts} +19 -19
- package/dist/{ulid-C7ms9oli.d.cts → ulid-LaxfH2tK.d.cts} +19 -19
- package/dist/util/index.cjs +7 -0
- package/dist/util/index.cjs.map +1 -1
- package/dist/util/index.d.cts +2 -0
- package/dist/util/index.d.ts +2 -0
- package/dist/util/index.js +5 -1
- package/dist/util/index.js.map +1 -1
- package/dist/vault-group-BB246VIM.js +804 -0
- package/dist/vault-group-BB246VIM.js.map +1 -0
- package/dist/{with-materialized-view-CqnRwI2S.d.ts → with-materialized-view-CeZYGJVf.d.cts} +2 -2
- package/dist/{with-materialized-view-BbEPFIIJ.d.cts → with-materialized-view-DNULSxoP.d.ts} +2 -2
- package/dist/{with-overlayed-view-Ct1fSJt-.d.ts → with-overlayed-view-C9joG7UZ.d.ts} +2 -2
- package/dist/{with-overlayed-view-bwlmmFjx.d.cts → with-overlayed-view-kdcPGHih.d.cts} +2 -2
- package/dist/with-rollup-DJDbrxjf.d.ts +47 -0
- package/dist/with-rollup-s58XAeWO.d.cts +47 -0
- package/package.json +35 -4
- package/dist/chunk-2PAQNPE3.js.map +0 -1
- package/dist/chunk-3S4BJX25.js.map +0 -1
- package/dist/chunk-3XHOCQK4.js +0 -118
- package/dist/chunk-3XHOCQK4.js.map +0 -1
- package/dist/chunk-3Z2TPHC4.js.map +0 -1
- package/dist/chunk-5ZGZ6HIZ.js.map +0 -1
- package/dist/chunk-7BRE6EUA.js.map +0 -1
- package/dist/chunk-7Z23ZFLV.js.map +0 -1
- package/dist/chunk-CXSCDO5T.js +0 -51
- package/dist/chunk-CXSCDO5T.js.map +0 -1
- package/dist/chunk-E535SAN4.js.map +0 -1
- package/dist/chunk-EUYOGYGV.js.map +0 -1
- package/dist/chunk-GIV6DWBG.js.map +0 -1
- package/dist/chunk-HXJXPZRE.js.map +0 -1
- package/dist/chunk-J4KLMEUL.js.map +0 -1
- package/dist/chunk-JYQTXEIO.js.map +0 -1
- package/dist/chunk-LRAZDV5X.js.map +0 -1
- package/dist/chunk-MRIBLZL3.js +0 -86
- package/dist/chunk-MRIBLZL3.js.map +0 -1
- package/dist/chunk-MUWOSVEP.js.map +0 -1
- package/dist/chunk-OVZDFEOR.js.map +0 -1
- package/dist/chunk-PEULZC6M.js.map +0 -1
- package/dist/chunk-PFSNOPBQ.js.map +0 -1
- package/dist/chunk-PLI5TV7N.js.map +0 -1
- package/dist/chunk-Q6W2CMEJ.js.map +0 -1
- package/dist/chunk-RTZVQAJ7.js.map +0 -1
- package/dist/chunk-TBKOGSYR.js.map +0 -1
- package/dist/chunk-UMLVJTYV.js.map +0 -1
- package/dist/chunk-UND4XIB6.js.map +0 -1
- package/dist/chunk-VCGTOS2A.js.map +0 -1
- package/dist/chunk-VE6YVP32.js +0 -19
- package/dist/chunk-VE6YVP32.js.map +0 -1
- package/dist/chunk-VPSUZLOJ.js.map +0 -1
- package/dist/chunk-VRBCTEKQ.js.map +0 -1
- package/dist/chunk-W3XXT26A.js.map +0 -1
- package/dist/chunk-XG3PTSCD.js.map +0 -1
- package/dist/chunk-Y2RKOPNC.js.map +0 -1
- package/dist/chunk-YMYK7US4.js.map +0 -1
- package/dist/chunk-YS3POABP.js.map +0 -1
- package/dist/chunk-YTXSFG3C.js.map +0 -1
- package/dist/executor-AS2IDHKZ.js +0 -11
- package/dist/executor-HLXFXNFM.js +0 -8
- package/dist/executor-HN6YBHZ5.js +0 -8
- package/dist/fanout-sidecar-VJ52RIEY.js.map +0 -1
- package/dist/issue-ORP37MVW.js +0 -12
- package/dist/mime-magic-CBBSOkjm.d.cts +0 -50
- package/dist/mime-magic-CBBSOkjm.d.ts +0 -50
- package/dist/noydb-5H3C24GG.js +0 -34
- package/dist/read-only-facade-ITU6L7BL.js +0 -7
- package/dist/registry-7HE6VJGC.js +0 -8
- package/dist/registry-PSIPG2QR.js +0 -8
- package/dist/registry-RFGGMVNJ.js +0 -7
- package/dist/with-derivation-BKXXa8Vt.d.ts +0 -13
- package/dist/with-derivation-BjQ7q4NE.d.cts +0 -13
- package/dist/with-guard-C25yNjzd.d.ts +0 -18
- package/dist/with-guard-DQme5DKE.d.cts +0 -18
- /package/dist/{chunk-7Q5PLD5C.js.map → chunk-7MRT7EPB.js.map} +0 -0
- /package/dist/{chunk-G6FRSBKK.js.map → chunk-AI4USDRI.js.map} +0 -0
- /package/dist/{chunk-NWZ3I6R6.js.map → chunk-EYK72OTL.js.map} +0 -0
- /package/dist/{chunk-7BUTTVMR.js.map → chunk-F5GWNSE2.js.map} +0 -0
- /package/dist/{chunk-AHPFONIL.js.map → chunk-F5ILTHMU.js.map} +0 -0
- /package/dist/{chunk-QPEXPHJR.js.map → chunk-I3IYTUUI.js.map} +0 -0
- /package/dist/{chunk-3QAKZ37R.js.map → chunk-IVZWHIEK.js.map} +0 -0
- /package/dist/{chunk-243PNUA6.js.map → chunk-JOK73NDT.js.map} +0 -0
- /package/dist/{chunk-VK5EER6C.js.map → chunk-SQKAECUL.js.map} +0 -0
- /package/dist/{chunk-4HIL6AHQ.js.map → chunk-TAMRU7A2.js.map} +0 -0
- /package/dist/{chunk-QXQRKXCU.js.map → chunk-TGIJTNM3.js.map} +0 -0
- /package/dist/{chunk-3Y53S2SA.js.map → chunk-UU6M64HI.js.map} +0 -0
- /package/dist/{crypto-5ZDIY3NG.js.map → crypto-456N7UVX.js.map} +0 -0
- /package/dist/{delegation-QYXZW25W.js.map → delegation-DP4COTXB.js.map} +0 -0
- /package/dist/{executor-AS2IDHKZ.js.map → executor-4IEW4KG5.js.map} +0 -0
- /package/dist/{executor-HLXFXNFM.js.map → executor-KYJCJCIN.js.map} +0 -0
- /package/dist/{executor-HN6YBHZ5.js.map → executor-W7VIBOBZ.js.map} +0 -0
- /package/dist/{issue-ORP37MVW.js.map → forget/index.js.map} +0 -0
- /package/dist/{ledger-3IU5GMXA.js.map → issue-JXC6T2QR.js.map} +0 -0
- /package/dist/{noydb-5H3C24GG.js.map → ledger-I7JUYP4L.js.map} +0 -0
- /package/dist/{public-envelope-U3CMEOMV.js.map → noydb-ZZCRF6TE.js.map} +0 -0
- /package/dist/{read-only-facade-ITU6L7BL.js.map → public-envelope-5XRTUNKF.js.map} +0 -0
- /package/dist/{registry-3ALP62P6.js.map → read-only-facade-EX6WZZBP.js.map} +0 -0
- /package/dist/{registry-7HE6VJGC.js.map → registry-ATRHOG5B.js.map} +0 -0
- /package/dist/{registry-PSIPG2QR.js.map → registry-DKEXOJVO.js.map} +0 -0
- /package/dist/{registry-RFGGMVNJ.js.map → registry-LEHB26TY.js.map} +0 -0
- /package/dist/{revoke-KY2GB4KP.js.map → registry-NWHOLD5M.js.map} +0 -0
- /package/dist/{signer-GRI5TZKH.js.map → revoke-5IEK22KT.js.map} +0 -0
- /package/dist/{stale-OTOF3FH7.js.map → signer-I6YARZQA.js.map} +0 -0
package/dist/query/index.cjs
CHANGED
|
@@ -22,6 +22,9 @@ var query_exports = {};
|
|
|
22
22
|
__export(query_exports, {
|
|
23
23
|
Aggregation: () => Aggregation,
|
|
24
24
|
CollectionIndexes: () => CollectionIndexes,
|
|
25
|
+
CrossJoinSourceUnknownError: () => CrossJoinSourceUnknownError,
|
|
26
|
+
CrossJoinTooLargeError: () => CrossJoinTooLargeError,
|
|
27
|
+
DEFAULT_CROSS_JOIN_MAX_ROWS: () => DEFAULT_CROSS_JOIN_MAX_ROWS,
|
|
25
28
|
DEFAULT_JOIN_MAX_ROWS: () => DEFAULT_JOIN_MAX_ROWS,
|
|
26
29
|
DanglingReferenceError: () => DanglingReferenceError,
|
|
27
30
|
GROUPBY_MAX_CARDINALITY: () => GROUPBY_MAX_CARDINALITY,
|
|
@@ -54,84 +57,156 @@ __export(query_exports, {
|
|
|
54
57
|
});
|
|
55
58
|
module.exports = __toCommonJS(query_exports);
|
|
56
59
|
|
|
57
|
-
// src/
|
|
58
|
-
function
|
|
59
|
-
|
|
60
|
-
if (!
|
|
61
|
-
|
|
60
|
+
// src/money/fixed-point.ts
|
|
61
|
+
function expandExponent(s) {
|
|
62
|
+
const m = /^([+-]?)(\d+)(?:\.(\d+))?[eE]([+-]?\d+)$/.exec(s);
|
|
63
|
+
if (!m) return s;
|
|
64
|
+
const sign = m[1] === "-" ? "-" : "";
|
|
65
|
+
const intp = m[2];
|
|
66
|
+
const frac = m[3] ?? "";
|
|
67
|
+
const exp = Number(m[4]);
|
|
68
|
+
const digits = intp + frac;
|
|
69
|
+
const pointPos = intp.length + exp;
|
|
70
|
+
let body;
|
|
71
|
+
if (pointPos <= 0) {
|
|
72
|
+
body = "0." + "0".repeat(-pointPos) + digits;
|
|
73
|
+
} else if (pointPos >= digits.length) {
|
|
74
|
+
body = digits + "0".repeat(pointPos - digits.length);
|
|
75
|
+
} else {
|
|
76
|
+
body = digits.slice(0, pointPos) + "." + digits.slice(pointPos);
|
|
62
77
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
78
|
+
return sign + body;
|
|
79
|
+
}
|
|
80
|
+
function toCanonicalDecimalString(input) {
|
|
81
|
+
let s;
|
|
82
|
+
if (typeof input === "number") {
|
|
83
|
+
if (!Number.isFinite(input)) return null;
|
|
84
|
+
s = String(input);
|
|
85
|
+
} else {
|
|
86
|
+
s = input.trim();
|
|
68
87
|
}
|
|
69
|
-
|
|
88
|
+
s = expandExponent(s);
|
|
89
|
+
if (s.startsWith("+")) s = s.slice(1);
|
|
90
|
+
if (!/^-?(\d+(\.\d*)?|\.\d+)$/.test(s)) return null;
|
|
91
|
+
return s;
|
|
70
92
|
}
|
|
71
|
-
function
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
case "
|
|
76
|
-
return actual === value;
|
|
77
|
-
case "!=":
|
|
78
|
-
return actual !== value;
|
|
79
|
-
case "<":
|
|
80
|
-
return isComparable(actual, value) && actual < value;
|
|
81
|
-
case "<=":
|
|
82
|
-
return isComparable(actual, value) && actual <= value;
|
|
83
|
-
case ">":
|
|
84
|
-
return isComparable(actual, value) && actual > value;
|
|
85
|
-
case ">=":
|
|
86
|
-
return isComparable(actual, value) && actual >= value;
|
|
87
|
-
case "in":
|
|
88
|
-
return Array.isArray(value) && value.includes(actual);
|
|
89
|
-
case "contains":
|
|
90
|
-
if (typeof actual === "string") return typeof value === "string" && actual.includes(value);
|
|
91
|
-
if (Array.isArray(actual)) return actual.includes(value);
|
|
93
|
+
function shouldRoundUp(negative, lastKeptDigit, firstDiscarded, hasMoreNonZeroAfterFirst, mode) {
|
|
94
|
+
switch (mode) {
|
|
95
|
+
case "up":
|
|
96
|
+
return true;
|
|
97
|
+
case "down":
|
|
92
98
|
return false;
|
|
93
|
-
case "
|
|
94
|
-
return
|
|
95
|
-
case "
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return
|
|
105
|
-
}
|
|
99
|
+
case "ceil":
|
|
100
|
+
return !negative;
|
|
101
|
+
case "floor":
|
|
102
|
+
return negative;
|
|
103
|
+
case "half-up":
|
|
104
|
+
return firstDiscarded >= 5;
|
|
105
|
+
case "half-down":
|
|
106
|
+
return firstDiscarded > 5 || firstDiscarded === 5 && hasMoreNonZeroAfterFirst;
|
|
107
|
+
case "half-even":
|
|
108
|
+
if (firstDiscarded > 5) return true;
|
|
109
|
+
if (firstDiscarded < 5) return false;
|
|
110
|
+
return hasMoreNonZeroAfterFirst || lastKeptDigit % 2 === 1;
|
|
106
111
|
}
|
|
107
112
|
}
|
|
108
|
-
function
|
|
109
|
-
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
|
|
113
|
+
function parseToScaledInt(input, scale, rounding) {
|
|
114
|
+
const canonical = toCanonicalDecimalString(input);
|
|
115
|
+
if (canonical === null) return { ok: false, reason: "nonfinite" };
|
|
116
|
+
const negative = canonical.startsWith("-");
|
|
117
|
+
const unsigned = negative ? canonical.slice(1) : canonical;
|
|
118
|
+
const dot = unsigned.indexOf(".");
|
|
119
|
+
const intPart = dot === -1 ? unsigned : unsigned.slice(0, dot);
|
|
120
|
+
const fracPart = dot === -1 ? "" : unsigned.slice(dot + 1);
|
|
121
|
+
const intDigits = intPart === "" ? "0" : intPart;
|
|
122
|
+
if (fracPart.length <= scale) {
|
|
123
|
+
const keep2 = fracPart.padEnd(scale, "0");
|
|
124
|
+
const magnitude2 = BigInt(intDigits + keep2);
|
|
125
|
+
return { ok: true, value: negative && magnitude2 !== 0n ? -magnitude2 : magnitude2 };
|
|
126
|
+
}
|
|
127
|
+
const keep = fracPart.slice(0, scale);
|
|
128
|
+
const tail = fracPart.slice(scale);
|
|
129
|
+
const magnitudeDigits = intDigits + keep;
|
|
130
|
+
let magnitude = BigInt(magnitudeDigits);
|
|
131
|
+
if (/^0+$/.test(tail)) {
|
|
132
|
+
return { ok: true, value: negative && magnitude !== 0n ? -magnitude : magnitude };
|
|
133
|
+
}
|
|
134
|
+
if (rounding === void 0) return { ok: false, reason: "precision" };
|
|
135
|
+
const lastKeptDigit = Number(magnitudeDigits[magnitudeDigits.length - 1]);
|
|
136
|
+
const firstDiscarded = Number(tail[0]);
|
|
137
|
+
const hasMoreNonZeroAfterFirst = /[1-9]/.test(tail.slice(1));
|
|
138
|
+
if (shouldRoundUp(negative, lastKeptDigit, firstDiscarded, hasMoreNonZeroAfterFirst, rounding)) {
|
|
139
|
+
magnitude += 1n;
|
|
140
|
+
}
|
|
141
|
+
return { ok: true, value: negative && magnitude !== 0n ? -magnitude : magnitude };
|
|
113
142
|
}
|
|
114
|
-
function
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
143
|
+
function formatScaledInt(value, scale) {
|
|
144
|
+
const negative = value < 0n;
|
|
145
|
+
const abs = (negative ? -value : value).toString();
|
|
146
|
+
if (scale === 0) return (negative ? "-" : "") + abs;
|
|
147
|
+
const padded = abs.padStart(scale + 1, "0");
|
|
148
|
+
const cut = padded.length - scale;
|
|
149
|
+
const intPart = padded.slice(0, cut);
|
|
150
|
+
const fracPart = padded.slice(cut);
|
|
151
|
+
return (negative ? "-" : "") + intPart + "." + fracPart;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/money/iso4217.ts
|
|
155
|
+
var MINOR_UNITS = {
|
|
156
|
+
// 2-decimal majors
|
|
157
|
+
EUR: 2,
|
|
158
|
+
USD: 2,
|
|
159
|
+
GBP: 2,
|
|
160
|
+
CHF: 2,
|
|
161
|
+
CAD: 2,
|
|
162
|
+
AUD: 2,
|
|
163
|
+
NZD: 2,
|
|
164
|
+
SGD: 2,
|
|
165
|
+
HKD: 2,
|
|
166
|
+
CNY: 2,
|
|
167
|
+
INR: 2,
|
|
168
|
+
BRL: 2,
|
|
169
|
+
MXN: 2,
|
|
170
|
+
ZAR: 2,
|
|
171
|
+
RUB: 2,
|
|
172
|
+
TRY: 2,
|
|
173
|
+
PLN: 2,
|
|
174
|
+
SEK: 2,
|
|
175
|
+
NOK: 2,
|
|
176
|
+
DKK: 2,
|
|
177
|
+
CZK: 2,
|
|
178
|
+
HUF: 2,
|
|
179
|
+
RON: 2,
|
|
180
|
+
ILS: 2,
|
|
181
|
+
THB: 2,
|
|
182
|
+
PHP: 2,
|
|
183
|
+
MYR: 2,
|
|
184
|
+
IDR: 2,
|
|
185
|
+
AED: 2,
|
|
186
|
+
SAR: 2,
|
|
187
|
+
QAR: 2,
|
|
188
|
+
EGP: 2,
|
|
189
|
+
// 0-decimal
|
|
190
|
+
JPY: 0,
|
|
191
|
+
KRW: 0,
|
|
192
|
+
ISK: 0,
|
|
193
|
+
CLP: 0,
|
|
194
|
+
VND: 0,
|
|
195
|
+
XOF: 0,
|
|
196
|
+
XAF: 0,
|
|
197
|
+
PYG: 0,
|
|
198
|
+
// 3-decimal
|
|
199
|
+
BHD: 3,
|
|
200
|
+
KWD: 3,
|
|
201
|
+
OMR: 3,
|
|
202
|
+
TND: 3,
|
|
203
|
+
JOD: 3,
|
|
204
|
+
IQD: 3,
|
|
205
|
+
LYD: 3
|
|
206
|
+
};
|
|
207
|
+
function scaleForCurrency(code) {
|
|
208
|
+
const v = MINOR_UNITS[code];
|
|
209
|
+
return v === void 0 ? null : v;
|
|
135
210
|
}
|
|
136
211
|
|
|
137
212
|
// src/errors.ts
|
|
@@ -144,6 +219,12 @@ var NoydbError = class extends Error {
|
|
|
144
219
|
this.code = code;
|
|
145
220
|
}
|
|
146
221
|
};
|
|
222
|
+
var ValidationError = class extends NoydbError {
|
|
223
|
+
constructor(message = "Validation error") {
|
|
224
|
+
super("VALIDATION_ERROR", message);
|
|
225
|
+
this.name = "ValidationError";
|
|
226
|
+
}
|
|
227
|
+
};
|
|
147
228
|
var GroupCardinalityError = class extends NoydbError {
|
|
148
229
|
/** The field being grouped on. */
|
|
149
230
|
field;
|
|
@@ -194,6 +275,18 @@ var IndexWriteFailureError = class extends NoydbError {
|
|
|
194
275
|
this.cause = args.cause;
|
|
195
276
|
}
|
|
196
277
|
};
|
|
278
|
+
var LocaleNotSpecifiedError = class extends NoydbError {
|
|
279
|
+
/** The field name that required a locale. */
|
|
280
|
+
field;
|
|
281
|
+
constructor(field, message) {
|
|
282
|
+
super(
|
|
283
|
+
"LOCALE_NOT_SPECIFIED",
|
|
284
|
+
message ?? `Cannot read i18nText field "${field}" without a locale. Pass { locale } to get()/list()/query() or set a default via openVault(name, { locale }).`
|
|
285
|
+
);
|
|
286
|
+
this.name = "LocaleNotSpecifiedError";
|
|
287
|
+
this.field = field;
|
|
288
|
+
}
|
|
289
|
+
};
|
|
197
290
|
var JoinTooLargeError = class extends NoydbError {
|
|
198
291
|
leftRows;
|
|
199
292
|
rightRows;
|
|
@@ -208,6 +301,34 @@ var JoinTooLargeError = class extends NoydbError {
|
|
|
208
301
|
this.side = opts.side;
|
|
209
302
|
}
|
|
210
303
|
};
|
|
304
|
+
var CrossJoinTooLargeError = class extends NoydbError {
|
|
305
|
+
target;
|
|
306
|
+
expected;
|
|
307
|
+
limit;
|
|
308
|
+
constructor(opts) {
|
|
309
|
+
super(
|
|
310
|
+
"CROSS_JOIN_TOO_LARGE",
|
|
311
|
+
`crossJoin("${opts.target}"): would produce ${opts.expected} rows, exceeding the limit of ${opts.limit}. Narrow the left side with .where() first, or raise the ceiling with crossJoin("${opts.target}", { ..., maxRows: ${opts.expected} }).`
|
|
312
|
+
);
|
|
313
|
+
this.name = "CrossJoinTooLargeError";
|
|
314
|
+
this.target = opts.target;
|
|
315
|
+
this.expected = opts.expected;
|
|
316
|
+
this.limit = opts.limit;
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
var CrossJoinSourceUnknownError = class extends NoydbError {
|
|
320
|
+
target;
|
|
321
|
+
leftCollection;
|
|
322
|
+
constructor(target, leftCollection) {
|
|
323
|
+
super(
|
|
324
|
+
"CROSS_JOIN_SOURCE_UNKNOWN",
|
|
325
|
+
`crossJoin("${target}"): collection "${target}" is not known in the vault (cross-joining from "${leftCollection}"). Make sure "${target}" is open in the same vault before executing this query.`
|
|
326
|
+
);
|
|
327
|
+
this.name = "CrossJoinSourceUnknownError";
|
|
328
|
+
this.target = target;
|
|
329
|
+
this.leftCollection = leftCollection;
|
|
330
|
+
}
|
|
331
|
+
};
|
|
211
332
|
var DanglingReferenceError = class extends NoydbError {
|
|
212
333
|
field;
|
|
213
334
|
target;
|
|
@@ -221,6 +342,436 @@ var DanglingReferenceError = class extends NoydbError {
|
|
|
221
342
|
}
|
|
222
343
|
};
|
|
223
344
|
|
|
345
|
+
// src/money/descriptor.ts
|
|
346
|
+
var MoneyUnsupportedError = class extends NoydbError {
|
|
347
|
+
constructor(field, message) {
|
|
348
|
+
super(
|
|
349
|
+
"MONEY_UNSUPPORTED",
|
|
350
|
+
message ?? `money: operation is not supported on field "${field}" \u2014 use sum() and count() and divide at the boundary`
|
|
351
|
+
);
|
|
352
|
+
this.field = field;
|
|
353
|
+
this.name = "MoneyUnsupportedError";
|
|
354
|
+
}
|
|
355
|
+
field;
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
// src/money/where.ts
|
|
359
|
+
function isMoneyValueObject(v) {
|
|
360
|
+
return typeof v === "object" && v !== null && "currency" in v;
|
|
361
|
+
}
|
|
362
|
+
function parseOperand(field, raw, desc) {
|
|
363
|
+
let amount;
|
|
364
|
+
let currency;
|
|
365
|
+
if (desc.mode === "fixed") {
|
|
366
|
+
currency = desc.fixedCurrency;
|
|
367
|
+
amount = raw;
|
|
368
|
+
} else if (isMoneyValueObject(raw)) {
|
|
369
|
+
currency = String(raw.currency);
|
|
370
|
+
amount = raw.amount;
|
|
371
|
+
} else {
|
|
372
|
+
const sole = desc.soleCurrency();
|
|
373
|
+
if (sole === void 0) {
|
|
374
|
+
throw new MoneyUnsupportedError(
|
|
375
|
+
`where("${field}"): field is multi-currency \u2014 compare against { amount, currency }, not a bare amount`
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
currency = sole;
|
|
379
|
+
amount = raw;
|
|
380
|
+
}
|
|
381
|
+
if (typeof amount !== "number" && typeof amount !== "string") {
|
|
382
|
+
throw new MoneyUnsupportedError(
|
|
383
|
+
`where("${field}"): operand ${JSON.stringify(raw)} is not a money amount`
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
const r = parseToScaledInt(amount, desc.scaleFor(currency), desc.rounding);
|
|
387
|
+
if (!r.ok) {
|
|
388
|
+
throw new MoneyUnsupportedError(
|
|
389
|
+
`where("${field}"): operand ${JSON.stringify(amount)} is not a finite decimal`
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
return { scaled: r.value.toString(), currency };
|
|
393
|
+
}
|
|
394
|
+
function moneyFieldClause(field, op, value, desc) {
|
|
395
|
+
switch (op) {
|
|
396
|
+
case "==":
|
|
397
|
+
case "!=":
|
|
398
|
+
case "<":
|
|
399
|
+
case "<=":
|
|
400
|
+
case ">":
|
|
401
|
+
case ">=": {
|
|
402
|
+
const e = parseOperand(field, value, desc);
|
|
403
|
+
return withMoney(field, op, value, desc, [e]);
|
|
404
|
+
}
|
|
405
|
+
case "between": {
|
|
406
|
+
if (!Array.isArray(value) || value.length !== 2) {
|
|
407
|
+
throw new MoneyUnsupportedError(`where("${field}"): 'between' needs a [lo, hi] tuple`);
|
|
408
|
+
}
|
|
409
|
+
const lo = parseOperand(field, value[0], desc);
|
|
410
|
+
const hi = parseOperand(field, value[1], desc);
|
|
411
|
+
if (lo.currency !== hi.currency) {
|
|
412
|
+
throw new MoneyUnsupportedError(
|
|
413
|
+
`where("${field}"): 'between' bounds mix currencies (${lo.currency} vs ${hi.currency})`
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
return withMoney(field, op, value, desc, [lo, hi]);
|
|
417
|
+
}
|
|
418
|
+
case "in": {
|
|
419
|
+
if (!Array.isArray(value)) {
|
|
420
|
+
throw new MoneyUnsupportedError(`where("${field}"): 'in' needs an array of amounts`);
|
|
421
|
+
}
|
|
422
|
+
return withMoney(field, op, value, desc, value.map((v) => parseOperand(field, v, desc)));
|
|
423
|
+
}
|
|
424
|
+
default:
|
|
425
|
+
throw new MoneyUnsupportedError(
|
|
426
|
+
`where("${field}"): operator '${op}' is not supported on a money field`
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function withMoney(field, op, originalValue, desc, entries) {
|
|
431
|
+
const money = { mode: desc.mode, entries };
|
|
432
|
+
const value = desc.mode !== "fixed" ? originalValue : entries.length === 1 && op !== "in" && op !== "between" ? entries[0].scaled : entries.map((e) => e.scaled);
|
|
433
|
+
return { type: "field", field, op, value, money };
|
|
434
|
+
}
|
|
435
|
+
function readStored(actual, operand) {
|
|
436
|
+
let amount;
|
|
437
|
+
let currency;
|
|
438
|
+
if (operand.mode === "fixed") {
|
|
439
|
+
if (typeof actual !== "string" && typeof actual !== "number") return null;
|
|
440
|
+
amount = actual;
|
|
441
|
+
currency = operand.entries[0]?.currency ?? "";
|
|
442
|
+
} else {
|
|
443
|
+
if (!isMoneyValueObject(actual)) return null;
|
|
444
|
+
if (typeof actual.currency !== "string") return null;
|
|
445
|
+
amount = actual.amount;
|
|
446
|
+
currency = actual.currency;
|
|
447
|
+
}
|
|
448
|
+
if (typeof amount !== "string" && typeof amount !== "number") return null;
|
|
449
|
+
try {
|
|
450
|
+
return { scaled: BigInt(amount).toString(), currency };
|
|
451
|
+
} catch {
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
function evaluateMoneyClause(actual, op, operand) {
|
|
456
|
+
const stored = readStored(actual, operand);
|
|
457
|
+
if (stored === null) return op === "!=";
|
|
458
|
+
const a = BigInt(stored.scaled);
|
|
459
|
+
if (op === "in") {
|
|
460
|
+
return operand.entries.some(
|
|
461
|
+
(e2) => e2.currency === stored.currency && BigInt(e2.scaled) === a
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
if (op === "between") {
|
|
465
|
+
const [lo, hi] = operand.entries;
|
|
466
|
+
if (!lo || !hi || lo.currency !== stored.currency) return false;
|
|
467
|
+
return a >= BigInt(lo.scaled) && a <= BigInt(hi.scaled);
|
|
468
|
+
}
|
|
469
|
+
const e = operand.entries[0];
|
|
470
|
+
if (!e) return false;
|
|
471
|
+
if (e.currency !== stored.currency) return op === "!=";
|
|
472
|
+
const b = BigInt(e.scaled);
|
|
473
|
+
switch (op) {
|
|
474
|
+
case "==":
|
|
475
|
+
return a === b;
|
|
476
|
+
case "!=":
|
|
477
|
+
return a !== b;
|
|
478
|
+
case "<":
|
|
479
|
+
return a < b;
|
|
480
|
+
case "<=":
|
|
481
|
+
return a <= b;
|
|
482
|
+
case ">":
|
|
483
|
+
return a > b;
|
|
484
|
+
case ">=":
|
|
485
|
+
return a >= b;
|
|
486
|
+
default:
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// src/query/predicate.ts
|
|
492
|
+
function readPath(record, path) {
|
|
493
|
+
if (record === null || record === void 0) return void 0;
|
|
494
|
+
if (!path.includes(".")) {
|
|
495
|
+
return record[path];
|
|
496
|
+
}
|
|
497
|
+
const segments = path.split(".");
|
|
498
|
+
let cursor = record;
|
|
499
|
+
for (const segment of segments) {
|
|
500
|
+
if (cursor === null || cursor === void 0) return void 0;
|
|
501
|
+
cursor = cursor[segment];
|
|
502
|
+
}
|
|
503
|
+
return cursor;
|
|
504
|
+
}
|
|
505
|
+
function evaluateFieldClause(record, clause) {
|
|
506
|
+
const actual = readPath(record, clause.field);
|
|
507
|
+
const { op, value } = clause;
|
|
508
|
+
if (clause.money) return evaluateMoneyClause(actual, op, clause.money);
|
|
509
|
+
switch (op) {
|
|
510
|
+
case "==":
|
|
511
|
+
return actual === value;
|
|
512
|
+
case "!=":
|
|
513
|
+
return actual !== value;
|
|
514
|
+
case "<":
|
|
515
|
+
return isComparable(actual, value) && actual < value;
|
|
516
|
+
case "<=":
|
|
517
|
+
return isComparable(actual, value) && actual <= value;
|
|
518
|
+
case ">":
|
|
519
|
+
return isComparable(actual, value) && actual > value;
|
|
520
|
+
case ">=":
|
|
521
|
+
return isComparable(actual, value) && actual >= value;
|
|
522
|
+
case "in":
|
|
523
|
+
return Array.isArray(value) && value.includes(actual);
|
|
524
|
+
case "contains":
|
|
525
|
+
if (typeof actual === "string") return typeof value === "string" && actual.includes(value);
|
|
526
|
+
if (Array.isArray(actual)) return actual.includes(value);
|
|
527
|
+
return false;
|
|
528
|
+
case "startsWith":
|
|
529
|
+
return typeof actual === "string" && typeof value === "string" && actual.startsWith(value);
|
|
530
|
+
case "between": {
|
|
531
|
+
if (!Array.isArray(value) || value.length !== 2) return false;
|
|
532
|
+
const [lo, hi] = value;
|
|
533
|
+
if (!isComparable(actual, lo) || !isComparable(actual, hi)) return false;
|
|
534
|
+
return actual >= lo && actual <= hi;
|
|
535
|
+
}
|
|
536
|
+
default: {
|
|
537
|
+
const _exhaustive = op;
|
|
538
|
+
void _exhaustive;
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
function isComparable(a, b) {
|
|
544
|
+
if (typeof a === "number" && typeof b === "number") return true;
|
|
545
|
+
if (typeof a === "string" && typeof b === "string") return true;
|
|
546
|
+
if (a instanceof Date && b instanceof Date) return true;
|
|
547
|
+
return false;
|
|
548
|
+
}
|
|
549
|
+
function evaluateClause(record, clause, fnRecord) {
|
|
550
|
+
switch (clause.type) {
|
|
551
|
+
case "field":
|
|
552
|
+
return evaluateFieldClause(record, clause);
|
|
553
|
+
case "filter":
|
|
554
|
+
return clause.fn(fnRecord !== void 0 ? fnRecord : record);
|
|
555
|
+
case "wherePredicate":
|
|
556
|
+
return clause.fn(fnRecord !== void 0 ? fnRecord : record, clause.ctx);
|
|
557
|
+
case "crossJoin":
|
|
558
|
+
throw new Error(
|
|
559
|
+
`evaluateClause: 'crossJoin' clauses are expansion primitives and are not evaluated per-record. This is a query planner routing error \u2014 crossJoin clauses must be extracted from the clause list before calling evaluateClause or filterRecords.`
|
|
560
|
+
);
|
|
561
|
+
case "group":
|
|
562
|
+
if (clause.op === "and") {
|
|
563
|
+
for (const child of clause.clauses) {
|
|
564
|
+
if (!evaluateClause(record, child, fnRecord)) return false;
|
|
565
|
+
}
|
|
566
|
+
return true;
|
|
567
|
+
} else {
|
|
568
|
+
for (const child of clause.clauses) {
|
|
569
|
+
if (evaluateClause(record, child, fnRecord)) return true;
|
|
570
|
+
}
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
function hasFnClause(clauses) {
|
|
576
|
+
for (const c of clauses) {
|
|
577
|
+
if (c.type === "filter" || c.type === "wherePredicate") return true;
|
|
578
|
+
if (c.type === "group" && hasFnClause(c.clauses)) return true;
|
|
579
|
+
}
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// src/i18n/policy.ts
|
|
584
|
+
function resolvePolicy(onMissing, layer) {
|
|
585
|
+
const explicit = onMissing && typeof onMissing === "object" ? onMissing[layer] : void 0;
|
|
586
|
+
const scalar = typeof onMissing === "string" ? onMissing : void 0;
|
|
587
|
+
const layerDefault = layer === "guard" ? "substitute" : void 0;
|
|
588
|
+
return explicit ?? layerDefault ?? scalar ?? "throw";
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// src/i18n/script.ts
|
|
592
|
+
var LATIN_BASE = /* @__PURE__ */ new Set([
|
|
593
|
+
"en",
|
|
594
|
+
"fr",
|
|
595
|
+
"de",
|
|
596
|
+
"es",
|
|
597
|
+
"it",
|
|
598
|
+
"pt",
|
|
599
|
+
"nl",
|
|
600
|
+
"sv",
|
|
601
|
+
"no",
|
|
602
|
+
"da",
|
|
603
|
+
"fi",
|
|
604
|
+
"is",
|
|
605
|
+
"pl",
|
|
606
|
+
"cs",
|
|
607
|
+
"sk",
|
|
608
|
+
"hu",
|
|
609
|
+
"ro",
|
|
610
|
+
"hr",
|
|
611
|
+
"sl",
|
|
612
|
+
"et",
|
|
613
|
+
"lv",
|
|
614
|
+
"lt",
|
|
615
|
+
"tr",
|
|
616
|
+
"vi",
|
|
617
|
+
"id",
|
|
618
|
+
"ms",
|
|
619
|
+
"tl",
|
|
620
|
+
"sw",
|
|
621
|
+
"af",
|
|
622
|
+
"ca",
|
|
623
|
+
"gl",
|
|
624
|
+
"eu",
|
|
625
|
+
"cy",
|
|
626
|
+
"ga"
|
|
627
|
+
]);
|
|
628
|
+
var SCRIPT_TABLE = {
|
|
629
|
+
th: ["Thai"],
|
|
630
|
+
ko: ["Hangul", "Han"],
|
|
631
|
+
ja: ["Han", "Hiragana", "Katakana"],
|
|
632
|
+
zh: ["Han"],
|
|
633
|
+
ar: ["Arabic"],
|
|
634
|
+
fa: ["Arabic"],
|
|
635
|
+
ur: ["Arabic"],
|
|
636
|
+
ru: ["Cyrillic"],
|
|
637
|
+
uk: ["Cyrillic"],
|
|
638
|
+
bg: ["Cyrillic"],
|
|
639
|
+
sr: ["Cyrillic"],
|
|
640
|
+
he: ["Hebrew"],
|
|
641
|
+
el: ["Greek"],
|
|
642
|
+
hi: ["Devanagari"],
|
|
643
|
+
ta: ["Tamil"],
|
|
644
|
+
km: ["Khmer"],
|
|
645
|
+
lo: ["Lao"],
|
|
646
|
+
my: ["Myanmar"]
|
|
647
|
+
};
|
|
648
|
+
var SUBTAG_SCRIPTS = {
|
|
649
|
+
Latn: ["Latin"],
|
|
650
|
+
Cyrl: ["Cyrillic", "Latin"],
|
|
651
|
+
Hans: ["Han", "Latin"],
|
|
652
|
+
Hant: ["Han", "Latin"],
|
|
653
|
+
Thai: ["Thai", "Latin"],
|
|
654
|
+
Arab: ["Arabic", "Latin"]
|
|
655
|
+
};
|
|
656
|
+
function inferScripts(locale) {
|
|
657
|
+
const parts = locale.split("-");
|
|
658
|
+
const subtag = parts.find((t) => /^[A-Z][a-z]{3}$/.test(t));
|
|
659
|
+
if (subtag && SUBTAG_SCRIPTS[subtag]) return SUBTAG_SCRIPTS[subtag];
|
|
660
|
+
const base = (parts[0] ?? "").toLowerCase();
|
|
661
|
+
if (LATIN_BASE.has(base)) return ["Latin"];
|
|
662
|
+
const primary = SCRIPT_TABLE[base];
|
|
663
|
+
if (primary) return [...primary, "Latin"];
|
|
664
|
+
return ["Latin"];
|
|
665
|
+
}
|
|
666
|
+
var BASELINE = String.raw`\p{White_Space}\p{Script=Common}\p{Script=Inherited}\p{Mark}`;
|
|
667
|
+
|
|
668
|
+
// src/i18n/core.ts
|
|
669
|
+
function toChain(fallback) {
|
|
670
|
+
return Array.isArray(fallback) ? fallback : fallback ? [fallback] : [];
|
|
671
|
+
}
|
|
672
|
+
function pickFromChain(value, chain) {
|
|
673
|
+
for (const fb of chain) {
|
|
674
|
+
if (fb === "any") {
|
|
675
|
+
const any = Object.values(value).find((v) => v !== "");
|
|
676
|
+
if (any !== void 0) return any;
|
|
677
|
+
} else if (value[fb] !== void 0 && value[fb] !== "") {
|
|
678
|
+
return value[fb];
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return void 0;
|
|
682
|
+
}
|
|
683
|
+
function resolveI18nText(value, locale, fallback, field, opts) {
|
|
684
|
+
if (locale === "raw") {
|
|
685
|
+
return value;
|
|
686
|
+
}
|
|
687
|
+
if (!locale) {
|
|
688
|
+
throw new LocaleNotSpecifiedError(field ?? "<unknown>");
|
|
689
|
+
}
|
|
690
|
+
if (value[locale] !== void 0 && value[locale] !== "") {
|
|
691
|
+
return value[locale];
|
|
692
|
+
}
|
|
693
|
+
const policy = opts?.policy ?? "throw";
|
|
694
|
+
const callerChain = toChain(fallback);
|
|
695
|
+
const callerHit = pickFromChain(value, callerChain);
|
|
696
|
+
if (callerHit !== void 0) return callerHit;
|
|
697
|
+
if (policy === "substitute") {
|
|
698
|
+
const subHit = pickFromChain(value, toChain(opts?.substitute));
|
|
699
|
+
if (subHit !== void 0) return subHit;
|
|
700
|
+
if (opts?.smartSubstitute) {
|
|
701
|
+
const smartHit = pickNearestScript(value, locale);
|
|
702
|
+
if (smartHit !== void 0) return smartHit;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
if (policy === "throw") {
|
|
706
|
+
throw new LocaleNotSpecifiedError(
|
|
707
|
+
field ?? "<unknown>",
|
|
708
|
+
`No translation available for locale "${locale}"` + (callerChain.length > 0 ? ` or fallback chain [${callerChain.join(", ")}]` : "") + "."
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
function pickNearestScript(value, target) {
|
|
714
|
+
const targetScript = inferScripts(target)[0] ?? "Latin";
|
|
715
|
+
let best;
|
|
716
|
+
for (const [loc, v] of Object.entries(value)) {
|
|
717
|
+
if (typeof v !== "string" || v === "") continue;
|
|
718
|
+
const s = inferScripts(loc)[0] ?? "Latin";
|
|
719
|
+
const score = s === targetScript ? 0 : s === "Latin" ? 1 : 2;
|
|
720
|
+
if (best === void 0 || score < best.score) best = { score, v };
|
|
721
|
+
if (score === 0) break;
|
|
722
|
+
}
|
|
723
|
+
return best?.v;
|
|
724
|
+
}
|
|
725
|
+
function applyAtPath(obj, path, locale, fallback, opts) {
|
|
726
|
+
const arrayIdx = path.indexOf("[].");
|
|
727
|
+
if (arrayIdx !== -1) {
|
|
728
|
+
const arrayKey = path.slice(0, arrayIdx);
|
|
729
|
+
const restPath = path.slice(arrayIdx + 3);
|
|
730
|
+
const arr = obj[arrayKey];
|
|
731
|
+
if (!Array.isArray(arr)) return obj;
|
|
732
|
+
return {
|
|
733
|
+
...obj,
|
|
734
|
+
[arrayKey]: arr.map((item) => {
|
|
735
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return item;
|
|
736
|
+
return applyAtPath(item, restPath, locale, fallback, opts);
|
|
737
|
+
})
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
const dotIdx = path.indexOf(".");
|
|
741
|
+
if (dotIdx !== -1) {
|
|
742
|
+
const head = path.slice(0, dotIdx);
|
|
743
|
+
const rest = path.slice(dotIdx + 1);
|
|
744
|
+
const nested = obj[head];
|
|
745
|
+
if (!nested || typeof nested !== "object" || Array.isArray(nested)) return obj;
|
|
746
|
+
return {
|
|
747
|
+
...obj,
|
|
748
|
+
[head]: applyAtPath(nested, rest, locale, fallback, opts)
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
const raw = obj[path];
|
|
752
|
+
if (raw === void 0 || raw === null) return obj;
|
|
753
|
+
if (typeof raw !== "object" || Array.isArray(raw)) return obj;
|
|
754
|
+
return {
|
|
755
|
+
...obj,
|
|
756
|
+
[path]: resolveI18nText(raw, locale, fallback, path, opts)
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
function applyI18nLocale(record, i18nFields, locale, fallback, layer = "read") {
|
|
760
|
+
const fieldNames = Object.keys(i18nFields);
|
|
761
|
+
if (fieldNames.length === 0) return record;
|
|
762
|
+
let result = record;
|
|
763
|
+
for (const [field, descriptor] of Object.entries(i18nFields)) {
|
|
764
|
+
const { onMissing, substitute, smartSubstitute } = descriptor.options;
|
|
765
|
+
const opts = {
|
|
766
|
+
policy: resolvePolicy(onMissing, layer),
|
|
767
|
+
...substitute !== void 0 ? { substitute } : {},
|
|
768
|
+
...smartSubstitute ? { smartSubstitute } : {}
|
|
769
|
+
};
|
|
770
|
+
result = applyAtPath(result, field, locale, fallback, opts);
|
|
771
|
+
}
|
|
772
|
+
return result;
|
|
773
|
+
}
|
|
774
|
+
|
|
224
775
|
// src/query/join.ts
|
|
225
776
|
var DEFAULT_JOIN_MAX_ROWS = 5e4;
|
|
226
777
|
var JOIN_WARN_FRACTION = 0.8;
|
|
@@ -249,15 +800,15 @@ function warnCeilingApproaching(target, side, rows, maxRows) {
|
|
|
249
800
|
`[noy-db] .join() ${side} side is at ${pct}% of the ${maxRows}-row ceiling for target "${target}" (${rows} rows). Streaming joins over scan() are not yet supported for collections that need to exceed this.`
|
|
250
801
|
);
|
|
251
802
|
}
|
|
252
|
-
function applyJoins(rows, joins, context) {
|
|
803
|
+
function applyJoins(rows, joins, context, locale) {
|
|
253
804
|
if (joins.length === 0) return [...rows];
|
|
254
805
|
let result = [...rows];
|
|
255
806
|
for (const leg of joins) {
|
|
256
|
-
result = applyOneJoin(result, leg, context);
|
|
807
|
+
result = applyOneJoin(result, leg, context, locale);
|
|
257
808
|
}
|
|
258
809
|
return result;
|
|
259
810
|
}
|
|
260
|
-
function applyOneJoin(leftRows, leg, context) {
|
|
811
|
+
function applyOneJoin(leftRows, leg, context, locale) {
|
|
261
812
|
if (leg.isDictJoin) {
|
|
262
813
|
const dictSource = context.resolveDictSource?.(leg.field);
|
|
263
814
|
if (!dictSource) {
|
|
@@ -312,24 +863,27 @@ function applyOneJoin(leftRows, leg, context) {
|
|
|
312
863
|
if (rightSnapshot.length > maxRows * JOIN_WARN_FRACTION) {
|
|
313
864
|
warnCeilingApproaching(leg.target, "right", rightSnapshot.length, maxRows);
|
|
314
865
|
}
|
|
866
|
+
const effLocale = locale ?? context.defaultLocale;
|
|
867
|
+
const i18nResolve = effLocale !== void 0 && source.i18nFields !== void 0 ? (right) => right !== null && typeof right === "object" ? applyI18nLocale(right, source.i18nFields, effLocale, void 0, "join") : right : void 0;
|
|
315
868
|
const strategy = leg.strategy ?? (source.lookupById ? "nested" : "hash");
|
|
316
869
|
if (strategy === "nested" && source.lookupById) {
|
|
317
870
|
const lookup = (id) => source.lookupById?.(id);
|
|
318
|
-
return nestedLoopJoin(leftRows, leg, lookup);
|
|
871
|
+
return nestedLoopJoin(leftRows, leg, lookup, i18nResolve);
|
|
319
872
|
}
|
|
320
|
-
return hashJoin(leftRows, leg, rightSnapshot);
|
|
873
|
+
return hashJoin(leftRows, leg, rightSnapshot, i18nResolve);
|
|
321
874
|
}
|
|
322
|
-
function nestedLoopJoin(leftRows, leg, lookupById) {
|
|
875
|
+
function nestedLoopJoin(leftRows, leg, lookupById, i18nResolve) {
|
|
323
876
|
const out = [];
|
|
324
877
|
for (const left of leftRows) {
|
|
325
878
|
const rawId = readPath(left, leg.field);
|
|
326
879
|
const key = coerceRefKey(rawId);
|
|
327
|
-
|
|
880
|
+
let right = key === null ? void 0 : lookupById(key);
|
|
881
|
+
if (i18nResolve && right !== void 0) right = i18nResolve(right);
|
|
328
882
|
out.push(attachJoin(left, leg, right, rawId));
|
|
329
883
|
}
|
|
330
884
|
return out;
|
|
331
885
|
}
|
|
332
|
-
function hashJoin(leftRows, leg, rightSnapshot) {
|
|
886
|
+
function hashJoin(leftRows, leg, rightSnapshot, i18nResolve) {
|
|
333
887
|
const rightMap = /* @__PURE__ */ new Map();
|
|
334
888
|
for (const record of rightSnapshot) {
|
|
335
889
|
const rawId = readPath(record, "id");
|
|
@@ -342,7 +896,8 @@ function hashJoin(leftRows, leg, rightSnapshot) {
|
|
|
342
896
|
for (const left of leftRows) {
|
|
343
897
|
const rawId = readPath(left, leg.field);
|
|
344
898
|
const key = coerceRefKey(rawId);
|
|
345
|
-
|
|
899
|
+
let right = key === null ? void 0 : rightMap.get(key);
|
|
900
|
+
if (i18nResolve && right !== void 0) right = i18nResolve(right);
|
|
346
901
|
out.push(attachJoin(left, leg, right, rawId));
|
|
347
902
|
}
|
|
348
903
|
return out;
|
|
@@ -466,6 +1021,386 @@ var NO_AGGREGATE = {
|
|
|
466
1021
|
}
|
|
467
1022
|
};
|
|
468
1023
|
|
|
1024
|
+
// src/money/money-reducer.ts
|
|
1025
|
+
function toScaledIntFromAny(v, scale) {
|
|
1026
|
+
if (typeof v === "bigint") return v;
|
|
1027
|
+
if (typeof v === "number") {
|
|
1028
|
+
const r = parseToScaledInt(v, scale);
|
|
1029
|
+
return r.ok ? r.value : null;
|
|
1030
|
+
}
|
|
1031
|
+
if (typeof v === "string") {
|
|
1032
|
+
if (!v.includes(".")) {
|
|
1033
|
+
try {
|
|
1034
|
+
return BigInt(v);
|
|
1035
|
+
} catch {
|
|
1036
|
+
return null;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
const r = parseToScaledInt(v, scale);
|
|
1040
|
+
return r.ok ? r.value : null;
|
|
1041
|
+
}
|
|
1042
|
+
return null;
|
|
1043
|
+
}
|
|
1044
|
+
function readMoney(record, field, desc) {
|
|
1045
|
+
const raw = readPath(record, field);
|
|
1046
|
+
if (raw === null || raw === void 0) return null;
|
|
1047
|
+
if (desc.mode === "fixed") {
|
|
1048
|
+
const cur = desc.fixedCurrency;
|
|
1049
|
+
const value2 = toScaledIntFromAny(raw, desc.scaleFor(cur));
|
|
1050
|
+
return value2 === null ? null : { currency: cur, value: value2 };
|
|
1051
|
+
}
|
|
1052
|
+
if (typeof raw !== "object") return null;
|
|
1053
|
+
const o = raw;
|
|
1054
|
+
if (typeof o.currency !== "string") return null;
|
|
1055
|
+
const scale = desc.allows(o.currency) ? desc.scaleFor(o.currency) : 0;
|
|
1056
|
+
const value = toScaledIntFromAny(o.amount, scale);
|
|
1057
|
+
return value === null ? null : { currency: o.currency, value };
|
|
1058
|
+
}
|
|
1059
|
+
function targetScaleFor(desc, currency) {
|
|
1060
|
+
if (desc.allows(currency)) return desc.scaleFor(currency);
|
|
1061
|
+
const s = scaleForCurrency(currency);
|
|
1062
|
+
if (s === null) {
|
|
1063
|
+
throw new Error(`money: cannot determine scale for conversion target "${currency}"`);
|
|
1064
|
+
}
|
|
1065
|
+
return s;
|
|
1066
|
+
}
|
|
1067
|
+
function parseRate(rate) {
|
|
1068
|
+
const s = String(rate).trim();
|
|
1069
|
+
const neg = s.startsWith("-");
|
|
1070
|
+
const body = neg ? s.slice(1) : s;
|
|
1071
|
+
const dot = body.indexOf(".");
|
|
1072
|
+
const intPart = dot === -1 ? body : body.slice(0, dot);
|
|
1073
|
+
const fracPart = dot === -1 ? "" : body.slice(dot + 1);
|
|
1074
|
+
const int = BigInt((intPart === "" ? "0" : intPart) + fracPart);
|
|
1075
|
+
return { int: neg ? -int : int, scale: fracPart.length };
|
|
1076
|
+
}
|
|
1077
|
+
function divRoundHalfEven(n, d) {
|
|
1078
|
+
const q = n / d;
|
|
1079
|
+
const r = n % d;
|
|
1080
|
+
const twiceR = (r < 0n ? -r : r) * 2n;
|
|
1081
|
+
if (twiceR < d) return q;
|
|
1082
|
+
if (twiceR > d) return q + (n < 0n ? -1n : 1n);
|
|
1083
|
+
return q % 2n === 0n ? q : q + (n < 0n ? -1n : 1n);
|
|
1084
|
+
}
|
|
1085
|
+
function convertScaled(value, srcScale, rate, targetScale) {
|
|
1086
|
+
const { int: rateInt, scale: rateScale } = parseRate(rate);
|
|
1087
|
+
const product = value * rateInt;
|
|
1088
|
+
const curScale = srcScale + rateScale;
|
|
1089
|
+
if (curScale === targetScale) return product;
|
|
1090
|
+
if (curScale < targetScale) return product * 10n ** BigInt(targetScale - curScale);
|
|
1091
|
+
return divRoundHalfEven(product, 10n ** BigInt(curScale - targetScale));
|
|
1092
|
+
}
|
|
1093
|
+
function finalizeSum(state, desc, convertTo, fx) {
|
|
1094
|
+
if (convertTo !== void 0) {
|
|
1095
|
+
if (fx === void 0) {
|
|
1096
|
+
throw new Error(`money: sum convertTo "${convertTo}" requires an fx rate map`);
|
|
1097
|
+
}
|
|
1098
|
+
const targetScale = targetScaleFor(desc, convertTo);
|
|
1099
|
+
let total = 0n;
|
|
1100
|
+
for (const [cur, v] of state) {
|
|
1101
|
+
if (cur === convertTo) {
|
|
1102
|
+
total += convertScaled(v, desc.scaleFor(cur), 1, targetScale);
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
1105
|
+
const rate = fx[`${cur}->${convertTo}`];
|
|
1106
|
+
if (rate === void 0) {
|
|
1107
|
+
throw new Error(`money: no fx rate for "${cur}->${convertTo}"`);
|
|
1108
|
+
}
|
|
1109
|
+
total += convertScaled(v, desc.scaleFor(cur), rate, targetScale);
|
|
1110
|
+
}
|
|
1111
|
+
return formatScaledInt(total, targetScale);
|
|
1112
|
+
}
|
|
1113
|
+
if (desc.mode === "fixed") {
|
|
1114
|
+
const cur = desc.fixedCurrency;
|
|
1115
|
+
return formatScaledInt(state.get(cur) ?? 0n, desc.scaleFor(cur));
|
|
1116
|
+
}
|
|
1117
|
+
const out = {};
|
|
1118
|
+
for (const [cur, v] of state) out[cur] = formatScaledInt(v, desc.scaleFor(cur));
|
|
1119
|
+
return out;
|
|
1120
|
+
}
|
|
1121
|
+
function moneySumReducer(field, desc, convertTo, fx) {
|
|
1122
|
+
return {
|
|
1123
|
+
op: "sum",
|
|
1124
|
+
field,
|
|
1125
|
+
init: () => /* @__PURE__ */ new Map(),
|
|
1126
|
+
step: (state, record) => {
|
|
1127
|
+
const m = readMoney(record, field, desc);
|
|
1128
|
+
if (m) state.set(m.currency, (state.get(m.currency) ?? 0n) + m.value);
|
|
1129
|
+
return state;
|
|
1130
|
+
},
|
|
1131
|
+
remove: (state, record) => {
|
|
1132
|
+
const m = readMoney(record, field, desc);
|
|
1133
|
+
if (m) state.set(m.currency, (state.get(m.currency) ?? 0n) - m.value);
|
|
1134
|
+
return state;
|
|
1135
|
+
},
|
|
1136
|
+
finalize: (state) => finalizeSum(state, desc, convertTo, fx)
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
function extremum(values, op) {
|
|
1140
|
+
let out = values[0];
|
|
1141
|
+
for (let i = 1; i < values.length; i++) {
|
|
1142
|
+
const v = values[i];
|
|
1143
|
+
if (op === "min" ? v < out : v > out) out = v;
|
|
1144
|
+
}
|
|
1145
|
+
return out;
|
|
1146
|
+
}
|
|
1147
|
+
function moneyMinMaxReducer(op, field, desc) {
|
|
1148
|
+
return {
|
|
1149
|
+
op,
|
|
1150
|
+
field,
|
|
1151
|
+
init: () => /* @__PURE__ */ new Map(),
|
|
1152
|
+
step: (state, record) => {
|
|
1153
|
+
const m = readMoney(record, field, desc);
|
|
1154
|
+
if (m) {
|
|
1155
|
+
const arr = state.get(m.currency);
|
|
1156
|
+
if (arr) arr.push(m.value);
|
|
1157
|
+
else state.set(m.currency, [m.value]);
|
|
1158
|
+
}
|
|
1159
|
+
return state;
|
|
1160
|
+
},
|
|
1161
|
+
remove: (state, record) => {
|
|
1162
|
+
const m = readMoney(record, field, desc);
|
|
1163
|
+
if (m) {
|
|
1164
|
+
const arr = state.get(m.currency);
|
|
1165
|
+
if (arr) {
|
|
1166
|
+
const idx = arr.indexOf(m.value);
|
|
1167
|
+
if (idx >= 0) arr.splice(idx, 1);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
return state;
|
|
1171
|
+
},
|
|
1172
|
+
finalize: (state) => {
|
|
1173
|
+
if (desc.mode === "fixed") {
|
|
1174
|
+
const cur = desc.fixedCurrency;
|
|
1175
|
+
const arr = state.get(cur);
|
|
1176
|
+
if (!arr || arr.length === 0) return null;
|
|
1177
|
+
return formatScaledInt(extremum(arr, op), desc.scaleFor(cur));
|
|
1178
|
+
}
|
|
1179
|
+
const out = {};
|
|
1180
|
+
for (const [cur, arr] of state) {
|
|
1181
|
+
if (arr.length > 0) out[cur] = formatScaledInt(extremum(arr, op), desc.scaleFor(cur));
|
|
1182
|
+
}
|
|
1183
|
+
return out;
|
|
1184
|
+
}
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
function wrapMoneyReducers(spec, moneyFields) {
|
|
1188
|
+
let changed = false;
|
|
1189
|
+
const out = {};
|
|
1190
|
+
for (const [key, reducer] of Object.entries(spec)) {
|
|
1191
|
+
const field = reducer.field;
|
|
1192
|
+
const desc = field ? moneyFields[field] : void 0;
|
|
1193
|
+
if (desc && reducer.op === "avg") {
|
|
1194
|
+
throw new MoneyUnsupportedError(
|
|
1195
|
+
field,
|
|
1196
|
+
`avg() is not supported on money field "${field}" in v1 \u2014 use sum() and count() and divide at the boundary.`
|
|
1197
|
+
);
|
|
1198
|
+
}
|
|
1199
|
+
if (desc && (reducer.op === "sum" || reducer.op === "min" || reducer.op === "max")) {
|
|
1200
|
+
changed = true;
|
|
1201
|
+
out[key] = reducer.op === "sum" ? moneySumReducer(field, desc, reducer.convertTo, reducer.fx) : moneyMinMaxReducer(reducer.op, field, desc);
|
|
1202
|
+
} else {
|
|
1203
|
+
out[key] = reducer;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
return changed ? out : spec;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
// src/money/paths.ts
|
|
1210
|
+
var SEGMENT_RE = /^(\*|[^.[\]*]+)(\[\])?$/;
|
|
1211
|
+
var parseCache = /* @__PURE__ */ new Map();
|
|
1212
|
+
function parseMoneyPath(path) {
|
|
1213
|
+
const cached = parseCache.get(path);
|
|
1214
|
+
if (cached) return cached;
|
|
1215
|
+
if (typeof path !== "string" || path.length === 0) {
|
|
1216
|
+
throw new ValidationError("moneyFields: path must be a non-empty string");
|
|
1217
|
+
}
|
|
1218
|
+
const segments = [];
|
|
1219
|
+
for (const part of path.split(".")) {
|
|
1220
|
+
const m = SEGMENT_RE.exec(part);
|
|
1221
|
+
if (!m) {
|
|
1222
|
+
throw new ValidationError(
|
|
1223
|
+
`moneyFields: invalid path "${path}" \u2014 segment "${part}" must be a key, "key[]", "*", or "*[]"`
|
|
1224
|
+
);
|
|
1225
|
+
}
|
|
1226
|
+
const array = m[2] === "[]";
|
|
1227
|
+
segments.push(
|
|
1228
|
+
m[1] === "*" ? { kind: "wildcard", array } : { kind: "key", key: m[1], array }
|
|
1229
|
+
);
|
|
1230
|
+
}
|
|
1231
|
+
parseCache.set(path, segments);
|
|
1232
|
+
return segments;
|
|
1233
|
+
}
|
|
1234
|
+
function isSimpleMoneyPath(path) {
|
|
1235
|
+
return !path.includes(".") && !path.includes("[") && !path.includes("*");
|
|
1236
|
+
}
|
|
1237
|
+
function transformAtMoneyPath(node, path, segments, index, visit, lenient) {
|
|
1238
|
+
if (node === null || node === void 0) return node;
|
|
1239
|
+
const seg = segments[index];
|
|
1240
|
+
const last = index === segments.length - 1;
|
|
1241
|
+
if (seg.kind === "key") {
|
|
1242
|
+
if (typeof node !== "object" || Array.isArray(node)) {
|
|
1243
|
+
if (lenient) return node;
|
|
1244
|
+
throw new ValidationError(
|
|
1245
|
+
`moneyFields: path "${path}" expected an object at segment "${seg.key}", got ${Array.isArray(node) ? "an array" : typeof node}`
|
|
1246
|
+
);
|
|
1247
|
+
}
|
|
1248
|
+
const obj2 = node;
|
|
1249
|
+
if (!(seg.key in obj2) || obj2[seg.key] === null || obj2[seg.key] === void 0) return node;
|
|
1250
|
+
if (seg.array) {
|
|
1251
|
+
const arr = obj2[seg.key];
|
|
1252
|
+
if (!Array.isArray(arr)) {
|
|
1253
|
+
if (lenient) return node;
|
|
1254
|
+
throw new ValidationError(
|
|
1255
|
+
`moneyFields: path "${path}" declares "${seg.key}[]" but the value is not an array`
|
|
1256
|
+
);
|
|
1257
|
+
}
|
|
1258
|
+
const cloned = [...arr];
|
|
1259
|
+
if (last) {
|
|
1260
|
+
for (let i = 0; i < cloned.length; i++) visit(cloned, i);
|
|
1261
|
+
} else {
|
|
1262
|
+
for (let i = 0; i < cloned.length; i++) {
|
|
1263
|
+
cloned[i] = transformAtMoneyPath(cloned[i], path, segments, index + 1, visit, lenient);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
return { ...obj2, [seg.key]: cloned };
|
|
1267
|
+
}
|
|
1268
|
+
const clone2 = { ...obj2 };
|
|
1269
|
+
if (last) {
|
|
1270
|
+
visit(clone2, seg.key);
|
|
1271
|
+
} else {
|
|
1272
|
+
clone2[seg.key] = transformAtMoneyPath(clone2[seg.key], path, segments, index + 1, visit, lenient);
|
|
1273
|
+
}
|
|
1274
|
+
return clone2;
|
|
1275
|
+
}
|
|
1276
|
+
if (seg.array) {
|
|
1277
|
+
if (!Array.isArray(node)) {
|
|
1278
|
+
if (lenient) return node;
|
|
1279
|
+
throw new ValidationError(`moneyFields: path "${path}" declares "*[]" but the value is not an array`);
|
|
1280
|
+
}
|
|
1281
|
+
const cloned = [...node];
|
|
1282
|
+
if (last) {
|
|
1283
|
+
for (let i = 0; i < cloned.length; i++) visit(cloned, i);
|
|
1284
|
+
} else {
|
|
1285
|
+
for (let i = 0; i < cloned.length; i++) {
|
|
1286
|
+
cloned[i] = transformAtMoneyPath(cloned[i], path, segments, index + 1, visit, lenient);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
return cloned;
|
|
1290
|
+
}
|
|
1291
|
+
if (typeof node !== "object" || Array.isArray(node)) {
|
|
1292
|
+
if (lenient) return node;
|
|
1293
|
+
throw new ValidationError(
|
|
1294
|
+
`moneyFields: path "${path}" applies "*" to a non-object (${Array.isArray(node) ? 'array \u2014 use "*[]"' : typeof node})`
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
const obj = node;
|
|
1298
|
+
const clone = { ...obj };
|
|
1299
|
+
for (const key of Object.keys(obj)) {
|
|
1300
|
+
const v = clone[key];
|
|
1301
|
+
if (v === null || v === void 0) continue;
|
|
1302
|
+
if (last) visit(clone, key);
|
|
1303
|
+
else clone[key] = transformAtMoneyPath(v, path, segments, index + 1, visit, lenient);
|
|
1304
|
+
}
|
|
1305
|
+
return clone;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
// src/money/normalize.ts
|
|
1309
|
+
function isMoneyValueObject2(v) {
|
|
1310
|
+
return typeof v === "object" && v !== null && "currency" in v;
|
|
1311
|
+
}
|
|
1312
|
+
function formatCurrency(decimal, currency, scale, locale) {
|
|
1313
|
+
const fmt = new Intl.NumberFormat(locale, {
|
|
1314
|
+
style: "currency",
|
|
1315
|
+
currency,
|
|
1316
|
+
minimumFractionDigits: scale,
|
|
1317
|
+
maximumFractionDigits: scale
|
|
1318
|
+
});
|
|
1319
|
+
return fmt.format(decimal);
|
|
1320
|
+
}
|
|
1321
|
+
function moneyScaledValue(stored, desc) {
|
|
1322
|
+
let raw;
|
|
1323
|
+
if (desc.mode === "fixed") {
|
|
1324
|
+
raw = stored;
|
|
1325
|
+
} else {
|
|
1326
|
+
if (!isMoneyValueObject2(stored)) return null;
|
|
1327
|
+
raw = stored.amount;
|
|
1328
|
+
}
|
|
1329
|
+
if (typeof raw !== "string" && typeof raw !== "number") return null;
|
|
1330
|
+
try {
|
|
1331
|
+
return BigInt(String(raw));
|
|
1332
|
+
} catch {
|
|
1333
|
+
return null;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
function decodeValue(stored, desc) {
|
|
1337
|
+
let currency;
|
|
1338
|
+
let scaledIntString;
|
|
1339
|
+
if (desc.mode === "fixed") {
|
|
1340
|
+
if (typeof stored !== "string" && typeof stored !== "number") return null;
|
|
1341
|
+
currency = desc.fixedCurrency;
|
|
1342
|
+
scaledIntString = String(stored);
|
|
1343
|
+
} else {
|
|
1344
|
+
if (!isMoneyValueObject2(stored)) return null;
|
|
1345
|
+
const amount = stored.amount;
|
|
1346
|
+
if (typeof stored.currency !== "string" || typeof amount !== "string" && typeof amount !== "number") return null;
|
|
1347
|
+
currency = stored.currency;
|
|
1348
|
+
scaledIntString = String(amount);
|
|
1349
|
+
}
|
|
1350
|
+
const scale = desc.scaleFor(currency);
|
|
1351
|
+
let decimal;
|
|
1352
|
+
try {
|
|
1353
|
+
decimal = formatScaledInt(BigInt(scaledIntString), scale);
|
|
1354
|
+
} catch {
|
|
1355
|
+
return null;
|
|
1356
|
+
}
|
|
1357
|
+
return {
|
|
1358
|
+
decoded: desc.mode === "fixed" ? decimal : { amount: decimal, currency },
|
|
1359
|
+
decimal,
|
|
1360
|
+
currency,
|
|
1361
|
+
scale
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
function decodeMoneyFields(record, moneyFields, locale) {
|
|
1365
|
+
let out = { ...record };
|
|
1366
|
+
const format = locale !== "raw";
|
|
1367
|
+
const fmtLocale = typeof locale === "string" && locale !== "raw" ? locale : "en-US";
|
|
1368
|
+
for (const [path, desc] of Object.entries(moneyFields)) {
|
|
1369
|
+
if (isSimpleMoneyPath(path)) {
|
|
1370
|
+
const stored = out[path];
|
|
1371
|
+
if (stored === null || stored === void 0) continue;
|
|
1372
|
+
const r = decodeValue(stored, desc);
|
|
1373
|
+
if (r === null) continue;
|
|
1374
|
+
out[path] = r.decoded;
|
|
1375
|
+
if (format) {
|
|
1376
|
+
out[`${path}Formatted`] = formatCurrency(r.decimal, r.currency, r.scale, fmtLocale);
|
|
1377
|
+
out[`${path}Number`] = Number(r.decimal);
|
|
1378
|
+
}
|
|
1379
|
+
continue;
|
|
1380
|
+
}
|
|
1381
|
+
out = transformAtMoneyPath(
|
|
1382
|
+
out,
|
|
1383
|
+
path,
|
|
1384
|
+
parseMoneyPath(path),
|
|
1385
|
+
0,
|
|
1386
|
+
(container, key) => {
|
|
1387
|
+
const stored = container[key];
|
|
1388
|
+
if (stored === null || stored === void 0) return;
|
|
1389
|
+
const r = decodeValue(stored, desc);
|
|
1390
|
+
if (r === null) return;
|
|
1391
|
+
container[key] = r.decoded;
|
|
1392
|
+
if (format && typeof key === "string" && !Array.isArray(container)) {
|
|
1393
|
+
container[`${key}Formatted`] = formatCurrency(r.decimal, r.currency, r.scale, fmtLocale);
|
|
1394
|
+
container[`${key}Number`] = Number(r.decimal);
|
|
1395
|
+
}
|
|
1396
|
+
},
|
|
1397
|
+
/* lenient */
|
|
1398
|
+
true
|
|
1399
|
+
);
|
|
1400
|
+
}
|
|
1401
|
+
return out;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
469
1404
|
// src/query/builder.ts
|
|
470
1405
|
var EMPTY_PLAN = {
|
|
471
1406
|
clauses: [],
|
|
@@ -474,6 +1409,7 @@ var EMPTY_PLAN = {
|
|
|
474
1409
|
offset: 0,
|
|
475
1410
|
joins: []
|
|
476
1411
|
};
|
|
1412
|
+
var DEFAULT_CROSS_JOIN_MAX_ROWS = 5e4;
|
|
477
1413
|
var Query = class _Query {
|
|
478
1414
|
source;
|
|
479
1415
|
plan;
|
|
@@ -506,7 +1442,7 @@ var Query = class _Query {
|
|
|
506
1442
|
/**
|
|
507
1443
|
* @internal — clone this Query with a declared-predicate map
|
|
508
1444
|
* attached. Used by the materialized-view registry to enable
|
|
509
|
-
* `.wherePredicate(name, ctx?)` for the MV's query callback
|
|
1445
|
+
* `.wherePredicate(name, ctx?)` for the MV's query callback.
|
|
510
1446
|
* Consumers don't call this directly.
|
|
511
1447
|
*/
|
|
512
1448
|
_withPredicates(predicates) {
|
|
@@ -519,7 +1455,7 @@ var Query = class _Query {
|
|
|
519
1455
|
);
|
|
520
1456
|
}
|
|
521
1457
|
/**
|
|
522
|
-
* Filter by a registered deterministic predicate
|
|
1458
|
+
* Filter by a registered deterministic predicate. Requires
|
|
523
1459
|
* the Query to have been augmented with a predicates map (typically
|
|
524
1460
|
* via the materialized-view registry — bare Queries constructed
|
|
525
1461
|
* outside an MV throw on `.wherePredicate()`).
|
|
@@ -557,9 +1493,18 @@ var Query = class _Query {
|
|
|
557
1493
|
this.predicates
|
|
558
1494
|
);
|
|
559
1495
|
}
|
|
560
|
-
/**
|
|
1496
|
+
/**
|
|
1497
|
+
* Add a field comparison. Multiple where() calls are AND-combined.
|
|
1498
|
+
*
|
|
1499
|
+
* A declared money field compares in MAJOR units (#336): the operand
|
|
1500
|
+
* (`10000`, `'10000.00'`, or `{ amount, currency }` in multi mode) is
|
|
1501
|
+
* quantized into stored scaled-int space at build time and evaluated
|
|
1502
|
+
* BigInt-exact per record. A malformed operand or a string operator
|
|
1503
|
+
* (`contains`/`startsWith`) throws here, at the call site.
|
|
1504
|
+
*/
|
|
561
1505
|
where(field, op, value) {
|
|
562
|
-
const
|
|
1506
|
+
const desc = this.source.moneyFields?.[field];
|
|
1507
|
+
const clause = desc ? moneyFieldClause(field, op, value, desc) : { type: "field", field, op, value };
|
|
563
1508
|
return new _Query(
|
|
564
1509
|
this.source,
|
|
565
1510
|
{ ...this.plan, clauses: [...this.plan.clauses, clause] },
|
|
@@ -625,11 +1570,16 @@ var Query = class _Query {
|
|
|
625
1570
|
this.predicates
|
|
626
1571
|
);
|
|
627
1572
|
}
|
|
628
|
-
/**
|
|
629
|
-
|
|
1573
|
+
/**
|
|
1574
|
+
* Sort by a field. Subsequent calls are tie-breakers. Pass
|
|
1575
|
+
* `{ by: 'label' }` to sort a `dictKey`/`staticDict` field by its resolved
|
|
1576
|
+
* label at the query locale instead of the stored code (#285).
|
|
1577
|
+
*/
|
|
1578
|
+
orderBy(field, direction = "asc", opts) {
|
|
1579
|
+
const entry = opts?.by === "label" ? { field, direction, by: "label" } : { field, direction };
|
|
630
1580
|
return new _Query(
|
|
631
1581
|
this.source,
|
|
632
|
-
{ ...this.plan, orderBy: [...this.plan.orderBy,
|
|
1582
|
+
{ ...this.plan, orderBy: [...this.plan.orderBy, entry] },
|
|
633
1583
|
this.joinContext,
|
|
634
1584
|
this.aggregateStrategy,
|
|
635
1585
|
this.predicates
|
|
@@ -755,25 +1705,119 @@ var Query = class _Query {
|
|
|
755
1705
|
this.predicates
|
|
756
1706
|
);
|
|
757
1707
|
}
|
|
1708
|
+
/**
|
|
1709
|
+
* Cartesian-product cross-join against `target` collection. Each result row
|
|
1710
|
+
* carries the original `T` fields plus `result[as]` populated from every
|
|
1711
|
+
* right-side row (or the filtered subset when `on:` is supplied).
|
|
1712
|
+
*
|
|
1713
|
+
* **Order matters:** `.where().crossJoin()` filters BEFORE expanding (cheaper);
|
|
1714
|
+
* `.crossJoin().where('alias.field', ...)` filters AFTER (required when the
|
|
1715
|
+
* where clause references the aliased fields).
|
|
1716
|
+
*
|
|
1717
|
+
* **Cost ceiling:** `CrossJoinTooLargeError` fires before allocation when
|
|
1718
|
+
* `leftRows × rightRows` (or the cumulative lateral count) exceeds the limit.
|
|
1719
|
+
* Default: 50,000 rows. Override per-clause with `{ maxRows: N }`.
|
|
1720
|
+
*
|
|
1721
|
+
* **`on:` shapes:**
|
|
1722
|
+
* - `on: (left) => TTarget[]` — subset form (most efficient)
|
|
1723
|
+
* - `on: (left) => (right) => boolean` — predicate form
|
|
1724
|
+
* - `on: { predicate: 'name' }` — MV-safe, hash-tracked form
|
|
1725
|
+
* (requires the Query to have been augmented via `_withPredicates`)
|
|
1726
|
+
*
|
|
1727
|
+
* Requires a JoinContext (constructed via `collection.query()`).
|
|
1728
|
+
*/
|
|
1729
|
+
crossJoin(target, opts) {
|
|
1730
|
+
if (!this.joinContext) {
|
|
1731
|
+
throw new Error(
|
|
1732
|
+
`Query.crossJoin("${target}"): requires a join context. Use collection.query() to construct a cross-join-capable Query instead of the Query constructor directly.`
|
|
1733
|
+
);
|
|
1734
|
+
}
|
|
1735
|
+
let onFn;
|
|
1736
|
+
let onPredicateName;
|
|
1737
|
+
if (opts.on !== void 0) {
|
|
1738
|
+
if (typeof opts.on === "function") {
|
|
1739
|
+
onFn = opts.on;
|
|
1740
|
+
if (this.predicates) {
|
|
1741
|
+
console.warn(
|
|
1742
|
+
`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.`
|
|
1743
|
+
);
|
|
1744
|
+
}
|
|
1745
|
+
} else {
|
|
1746
|
+
const predName = opts.on.predicate;
|
|
1747
|
+
if (!this.predicates) {
|
|
1748
|
+
throw new Error(
|
|
1749
|
+
`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 } }.`
|
|
1750
|
+
);
|
|
1751
|
+
}
|
|
1752
|
+
const decl = this.predicates.get(predName);
|
|
1753
|
+
if (!decl) {
|
|
1754
|
+
throw new Error(
|
|
1755
|
+
`Query.crossJoin("${target}"): predicate "${predName}" not registered. Available: ${[...this.predicates.keys()].join(", ") || "(none)"}.`
|
|
1756
|
+
);
|
|
1757
|
+
}
|
|
1758
|
+
const as = opts.as;
|
|
1759
|
+
const predicateFn = decl.fn;
|
|
1760
|
+
onFn = (_left) => (right) => predicateFn({ ..._left, [as]: right });
|
|
1761
|
+
onPredicateName = predName;
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
const clause = {
|
|
1765
|
+
type: "crossJoin",
|
|
1766
|
+
target,
|
|
1767
|
+
as: opts.as,
|
|
1768
|
+
...onFn !== void 0 && { on: onFn },
|
|
1769
|
+
...onPredicateName !== void 0 && { onPredicateName },
|
|
1770
|
+
...opts.maxRows !== void 0 && { maxRows: opts.maxRows }
|
|
1771
|
+
};
|
|
1772
|
+
return new _Query(
|
|
1773
|
+
this.source,
|
|
1774
|
+
{ ...this.plan, clauses: [...this.plan.clauses, clause] },
|
|
1775
|
+
this.joinContext,
|
|
1776
|
+
this.aggregateStrategy,
|
|
1777
|
+
this.predicates
|
|
1778
|
+
);
|
|
1779
|
+
}
|
|
758
1780
|
/**
|
|
759
1781
|
* Execute the plan and return the matching records. When the plan
|
|
760
1782
|
* carries any join legs, they are applied after `where` / `orderBy`
|
|
761
1783
|
* / `limit` / `offset` narrow the left set. See the `.join()` doc
|
|
762
1784
|
* for the ordering rationale.
|
|
1785
|
+
*
|
|
1786
|
+
* `opts.locale` (#285 §3) resolves JOINED right-side i18n fields at the
|
|
1787
|
+
* `join` layer to that locale; without it, the owning collection's default
|
|
1788
|
+
* locale applies, and a locale-less query leaves joined i18n fields raw.
|
|
1789
|
+
* (Left/base i18n fields are resolved by `get`/`list`, not here.)
|
|
763
1790
|
*/
|
|
764
|
-
toArray() {
|
|
765
|
-
const base = executePlanWithSource(this.source, this.plan);
|
|
1791
|
+
toArray(opts) {
|
|
1792
|
+
const base = this.decodeMoney(executePlanWithSource(this.source, this.plan, this.joinContext, opts?.locale));
|
|
766
1793
|
if (this.plan.joins.length === 0) return base;
|
|
767
1794
|
if (!this.joinContext) {
|
|
768
1795
|
throw new Error(
|
|
769
1796
|
`Query.toArray(): plan carries ${this.plan.joins.length} join leg(s) but no JoinContext is attached. This usually means the Query was constructed via the raw Query constructor with a plan that had joins pre-populated. Use collection.query().join(...) instead.`
|
|
770
1797
|
);
|
|
771
1798
|
}
|
|
772
|
-
return applyJoins(base, this.plan.joins, this.joinContext);
|
|
1799
|
+
return applyJoins(base, this.plan.joins, this.joinContext, opts?.locale);
|
|
773
1800
|
}
|
|
774
|
-
/**
|
|
775
|
-
|
|
776
|
-
|
|
1801
|
+
/**
|
|
1802
|
+
* Decode this source's money fields on read (stored scaled-int → canonical
|
|
1803
|
+
* decimal), so `query().toArray()` agrees with `get()`/`sum()` on the value.
|
|
1804
|
+
* No-op when the source declares no money fields.
|
|
1805
|
+
*
|
|
1806
|
+
* The query layer carries no locale context, so we decode with `'raw'` —
|
|
1807
|
+
* canonical decimal, WITHOUT fabricating locale-formatted `<field>Formatted`
|
|
1808
|
+
* / `<field>Number` virtuals. Producing a guessed-locale string here would
|
|
1809
|
+
* just reintroduce #322's "two read paths disagree" failure on the virtual
|
|
1810
|
+
* field (e.g. it-IT via `get()` vs en-US here). Consumers who need formatted
|
|
1811
|
+
* money read through `get()`/`list()` with a locale.
|
|
1812
|
+
*/
|
|
1813
|
+
decodeMoney(records) {
|
|
1814
|
+
const moneyFields = this.source.moneyFields;
|
|
1815
|
+
if (!moneyFields || Object.keys(moneyFields).length === 0) return records;
|
|
1816
|
+
return records.map((r) => decodeMoneyFields(r, moneyFields, "raw"));
|
|
1817
|
+
}
|
|
1818
|
+
/** Return the first matching record, or null. Joins are applied. `opts.locale` resolves joined i18n fields (#285 §3). */
|
|
1819
|
+
first(opts) {
|
|
1820
|
+
const arr = this.limit(1).toArray(opts);
|
|
777
1821
|
return arr[0] ?? null;
|
|
778
1822
|
}
|
|
779
1823
|
/**
|
|
@@ -786,9 +1830,17 @@ var Query = class _Query {
|
|
|
786
1830
|
* intent is purely to count.
|
|
787
1831
|
*/
|
|
788
1832
|
count() {
|
|
1833
|
+
if (this.plan.clauses.some((c) => c.type === "crossJoin")) {
|
|
1834
|
+
if (!this.joinContext) {
|
|
1835
|
+
throw new Error(
|
|
1836
|
+
`Query.count(): plan contains crossJoin clauses but no JoinContext is attached.`
|
|
1837
|
+
);
|
|
1838
|
+
}
|
|
1839
|
+
return executeClausePipeline(this.source, this.plan.clauses, this.joinContext).length;
|
|
1840
|
+
}
|
|
789
1841
|
const { candidates, remainingClauses } = candidateRecords(this.source, this.plan.clauses);
|
|
790
1842
|
if (remainingClauses.length === 0) return candidates.length;
|
|
791
|
-
return filterRecords(candidates, remainingClauses).length;
|
|
1843
|
+
return filterRecords(candidates, remainingClauses, fnViewDecoder(this.source)).length;
|
|
792
1844
|
}
|
|
793
1845
|
/**
|
|
794
1846
|
* Reduce the matching records through a named set of reducers.
|
|
@@ -831,11 +1883,21 @@ var Query = class _Query {
|
|
|
831
1883
|
* partition boundaries without an API break.
|
|
832
1884
|
*/
|
|
833
1885
|
aggregate(spec) {
|
|
1886
|
+
const moneyFields = this.source.moneyFields;
|
|
1887
|
+
if (moneyFields) {
|
|
1888
|
+
spec = wrapMoneyReducers(spec, moneyFields);
|
|
1889
|
+
}
|
|
834
1890
|
const source = this.source;
|
|
835
1891
|
const clauses = this.plan.clauses;
|
|
1892
|
+
const joinCtx = this.joinContext;
|
|
1893
|
+
const hasCrossJoins = clauses.some((c) => c.type === "crossJoin");
|
|
836
1894
|
const executeRecords = () => {
|
|
1895
|
+
if (hasCrossJoins) {
|
|
1896
|
+
if (!joinCtx) throw new Error("Query.aggregate(): crossJoin requires a join context");
|
|
1897
|
+
return executeClausePipeline(source, clauses, joinCtx);
|
|
1898
|
+
}
|
|
837
1899
|
const { candidates, remainingClauses } = candidateRecords(source, clauses);
|
|
838
|
-
return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses);
|
|
1900
|
+
return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
|
|
839
1901
|
};
|
|
840
1902
|
const upstreams = [];
|
|
841
1903
|
if (source.subscribe) {
|
|
@@ -850,9 +1912,15 @@ var Query = class _Query {
|
|
|
850
1912
|
}
|
|
851
1913
|
const source = this.source;
|
|
852
1914
|
const clauses = this.plan.clauses;
|
|
1915
|
+
const joinCtx = this.joinContext;
|
|
1916
|
+
const hasCrossJoins = clauses.some((c) => c.type === "crossJoin");
|
|
853
1917
|
const executeRecords = () => {
|
|
1918
|
+
if (hasCrossJoins) {
|
|
1919
|
+
if (!joinCtx) throw new Error("Query.groupBy(): crossJoin requires a join context");
|
|
1920
|
+
return executeClausePipeline(source, clauses, joinCtx);
|
|
1921
|
+
}
|
|
854
1922
|
const { candidates, remainingClauses } = candidateRecords(source, clauses);
|
|
855
|
-
return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses);
|
|
1923
|
+
return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
|
|
856
1924
|
};
|
|
857
1925
|
const upstreams = [];
|
|
858
1926
|
if (source.subscribe) {
|
|
@@ -866,13 +1934,15 @@ var Query = class _Query {
|
|
|
866
1934
|
executeRecords,
|
|
867
1935
|
field,
|
|
868
1936
|
upstreams,
|
|
869
|
-
dictLabelResolver
|
|
1937
|
+
dictLabelResolver,
|
|
1938
|
+
this.source.moneyFields
|
|
870
1939
|
);
|
|
871
1940
|
}
|
|
872
1941
|
return this.aggregateStrategy.groupByN(
|
|
873
1942
|
executeRecords,
|
|
874
1943
|
fields,
|
|
875
|
-
upstreams
|
|
1944
|
+
upstreams,
|
|
1945
|
+
this.source.moneyFields
|
|
876
1946
|
);
|
|
877
1947
|
}
|
|
878
1948
|
/**
|
|
@@ -964,6 +2034,21 @@ var Query = class _Query {
|
|
|
964
2034
|
}
|
|
965
2035
|
}
|
|
966
2036
|
}
|
|
2037
|
+
if (this.joinContext) {
|
|
2038
|
+
const subscribedCross = /* @__PURE__ */ new Set();
|
|
2039
|
+
for (const clause of this.plan.clauses) {
|
|
2040
|
+
if (clause.type !== "crossJoin") continue;
|
|
2041
|
+
if (subscribedCross.has(clause.target)) continue;
|
|
2042
|
+
subscribedCross.add(clause.target);
|
|
2043
|
+
const rightSource = this.joinContext.resolveSource(clause.target);
|
|
2044
|
+
if (rightSource?.subscribe) {
|
|
2045
|
+
const rightSubscribe = rightSource.subscribe.bind(rightSource);
|
|
2046
|
+
upstreams.push({
|
|
2047
|
+
subscribe: (cb) => rightSubscribe(cb)
|
|
2048
|
+
});
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
967
2052
|
return buildLiveQuery(() => this.toArray(), upstreams);
|
|
968
2053
|
}
|
|
969
2054
|
/**
|
|
@@ -975,11 +2060,23 @@ var Query = class _Query {
|
|
|
975
2060
|
return serializePlan(this.plan);
|
|
976
2061
|
}
|
|
977
2062
|
};
|
|
978
|
-
function executePlanWithSource(source, plan) {
|
|
979
|
-
const
|
|
980
|
-
let result
|
|
2063
|
+
function executePlanWithSource(source, plan, joinContext, locale) {
|
|
2064
|
+
const hasCrossJoins = plan.clauses.some((c) => c.type === "crossJoin");
|
|
2065
|
+
let result;
|
|
2066
|
+
if (hasCrossJoins) {
|
|
2067
|
+
if (!joinContext) {
|
|
2068
|
+
throw new Error(
|
|
2069
|
+
`Query.toArray(): plan contains crossJoin clauses but no JoinContext is attached. Use collection.query() instead of new Query() for cross-join support.`
|
|
2070
|
+
);
|
|
2071
|
+
}
|
|
2072
|
+
result = executeClausePipeline(source, plan.clauses, joinContext);
|
|
2073
|
+
} else {
|
|
2074
|
+
const { candidates, remainingClauses } = candidateRecords(source, plan.clauses);
|
|
2075
|
+
result = remainingClauses.length === 0 ? [...candidates] : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
|
|
2076
|
+
}
|
|
981
2077
|
if (plan.orderBy.length > 0) {
|
|
982
|
-
|
|
2078
|
+
const labelMaps = buildOrderLabelMaps(plan.orderBy, joinContext, locale);
|
|
2079
|
+
result = sortRecords(result, plan.orderBy, source.moneyFields, labelMaps);
|
|
983
2080
|
}
|
|
984
2081
|
if (plan.offset > 0) {
|
|
985
2082
|
result = result.slice(plan.offset);
|
|
@@ -999,6 +2096,7 @@ function candidateRecords(source, clauses) {
|
|
|
999
2096
|
const clause = clauses[i];
|
|
1000
2097
|
if (clause.type !== "field") continue;
|
|
1001
2098
|
if (!indexes.has(clause.field)) continue;
|
|
2099
|
+
if (clause.money?.mode === "multi") continue;
|
|
1002
2100
|
let ids = null;
|
|
1003
2101
|
if (clause.op === "==") {
|
|
1004
2102
|
ids = indexes.lookupEqual(clause.field, clause.value);
|
|
@@ -1027,6 +2125,11 @@ function materializeIds(ids, lookupById) {
|
|
|
1027
2125
|
return out;
|
|
1028
2126
|
}
|
|
1029
2127
|
function executePlan(records, plan) {
|
|
2128
|
+
if (plan.clauses.some((c) => c.type === "crossJoin")) {
|
|
2129
|
+
throw new Error(
|
|
2130
|
+
`executePlan(): does not support crossJoin clauses. executePlan is a stateless pure function \u2014 it cannot resolve cross-join right-side collections. Use Query.toArray() (via collection.query()) instead.`
|
|
2131
|
+
);
|
|
2132
|
+
}
|
|
1030
2133
|
let result = filterRecords(records, plan.clauses);
|
|
1031
2134
|
if (plan.orderBy.length > 0) {
|
|
1032
2135
|
result = sortRecords(result, plan.orderBy);
|
|
@@ -1039,13 +2142,20 @@ function executePlan(records, plan) {
|
|
|
1039
2142
|
}
|
|
1040
2143
|
return result;
|
|
1041
2144
|
}
|
|
1042
|
-
function
|
|
2145
|
+
function fnViewDecoder(source) {
|
|
2146
|
+
const mf = source.moneyFields;
|
|
2147
|
+
if (!mf || Object.keys(mf).length === 0) return void 0;
|
|
2148
|
+
return (r) => decodeMoneyFields(r, mf, "raw");
|
|
2149
|
+
}
|
|
2150
|
+
function filterRecords(records, clauses, decodeForFns) {
|
|
1043
2151
|
if (clauses.length === 0) return [...records];
|
|
2152
|
+
const needsFnView = decodeForFns !== void 0 && hasFnClause(clauses);
|
|
1044
2153
|
const out = [];
|
|
1045
2154
|
for (const r of records) {
|
|
2155
|
+
const fnView = needsFnView ? decodeForFns(r) : void 0;
|
|
1046
2156
|
let matches = true;
|
|
1047
2157
|
for (const clause of clauses) {
|
|
1048
|
-
if (!evaluateClause(r, clause)) {
|
|
2158
|
+
if (!evaluateClause(r, clause, fnView)) {
|
|
1049
2159
|
matches = false;
|
|
1050
2160
|
break;
|
|
1051
2161
|
}
|
|
@@ -1054,17 +2164,124 @@ function filterRecords(records, clauses) {
|
|
|
1054
2164
|
}
|
|
1055
2165
|
return out;
|
|
1056
2166
|
}
|
|
1057
|
-
function
|
|
2167
|
+
function executeClausePipeline(source, clauses, joinContext) {
|
|
2168
|
+
let rel = [...source.snapshot()];
|
|
2169
|
+
let filterBatch = [];
|
|
2170
|
+
const decodeForFns = fnViewDecoder(source);
|
|
2171
|
+
for (const clause of clauses) {
|
|
2172
|
+
if (clause.type === "crossJoin") {
|
|
2173
|
+
if (filterBatch.length > 0) {
|
|
2174
|
+
rel = filterRecords(rel, filterBatch, decodeForFns);
|
|
2175
|
+
filterBatch = [];
|
|
2176
|
+
}
|
|
2177
|
+
const rightSource = joinContext.resolveSource(clause.target);
|
|
2178
|
+
if (!rightSource) {
|
|
2179
|
+
throw new CrossJoinSourceUnknownError(clause.target, joinContext.leftCollection);
|
|
2180
|
+
}
|
|
2181
|
+
rel = applyCrossJoin(rel, clause, rightSource);
|
|
2182
|
+
} else {
|
|
2183
|
+
filterBatch.push(clause);
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
if (filterBatch.length > 0) {
|
|
2187
|
+
rel = filterRecords(rel, filterBatch, decodeForFns);
|
|
2188
|
+
}
|
|
2189
|
+
return rel;
|
|
2190
|
+
}
|
|
2191
|
+
function applyCrossJoin(leftRel, clause, rightSource) {
|
|
2192
|
+
const rightRows = rightSource.snapshot();
|
|
2193
|
+
const maxRows = clause.maxRows ?? DEFAULT_CROSS_JOIN_MAX_ROWS;
|
|
2194
|
+
const { as } = clause;
|
|
2195
|
+
if (!clause.on) {
|
|
2196
|
+
const product = leftRel.length * rightRows.length;
|
|
2197
|
+
if (product > maxRows) {
|
|
2198
|
+
throw new CrossJoinTooLargeError({ target: clause.target, expected: product, limit: maxRows });
|
|
2199
|
+
}
|
|
2200
|
+
const expanded2 = [];
|
|
2201
|
+
for (const left of leftRel) {
|
|
2202
|
+
const leftObj = left;
|
|
2203
|
+
for (const right of rightRows) {
|
|
2204
|
+
expanded2.push({ ...leftObj, [as]: right });
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
return expanded2;
|
|
2208
|
+
}
|
|
2209
|
+
const expanded = [];
|
|
2210
|
+
let cumulative = 0;
|
|
2211
|
+
for (const left of leftRel) {
|
|
2212
|
+
const callbackResult = clause.on(left);
|
|
2213
|
+
let filteredRight;
|
|
2214
|
+
if (Array.isArray(callbackResult)) {
|
|
2215
|
+
filteredRight = callbackResult;
|
|
2216
|
+
} else {
|
|
2217
|
+
filteredRight = rightRows.filter(
|
|
2218
|
+
callbackResult
|
|
2219
|
+
);
|
|
2220
|
+
}
|
|
2221
|
+
cumulative += filteredRight.length;
|
|
2222
|
+
if (cumulative > maxRows) {
|
|
2223
|
+
throw new CrossJoinTooLargeError({
|
|
2224
|
+
target: clause.target,
|
|
2225
|
+
expected: cumulative,
|
|
2226
|
+
limit: maxRows
|
|
2227
|
+
});
|
|
2228
|
+
}
|
|
2229
|
+
const leftObj = left;
|
|
2230
|
+
for (const right of filteredRight) {
|
|
2231
|
+
expanded.push({ ...leftObj, [as]: right });
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
return expanded;
|
|
2235
|
+
}
|
|
2236
|
+
function sortRecords(records, orderBy, moneyFields, labelMaps) {
|
|
1058
2237
|
return [...records].sort((a, b) => {
|
|
1059
|
-
for (const { field, direction } of orderBy) {
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
const
|
|
2238
|
+
for (const { field, direction, by } of orderBy) {
|
|
2239
|
+
let av = readField(a, field);
|
|
2240
|
+
let bv = readField(b, field);
|
|
2241
|
+
const labelMap = by === "label" ? labelMaps?.get(field) : void 0;
|
|
2242
|
+
if (labelMap) {
|
|
2243
|
+
av = (typeof av === "string" ? labelMap.get(av) : void 0) ?? av;
|
|
2244
|
+
bv = (typeof bv === "string" ? labelMap.get(bv) : void 0) ?? bv;
|
|
2245
|
+
const cmp2 = compareValues(av, bv);
|
|
2246
|
+
if (cmp2 !== 0) return direction === "asc" ? cmp2 : -cmp2;
|
|
2247
|
+
continue;
|
|
2248
|
+
}
|
|
2249
|
+
const desc = moneyFields?.[field];
|
|
2250
|
+
const cmp = desc ? compareMoney(av, bv, desc) : compareValues(av, bv);
|
|
1063
2251
|
if (cmp !== 0) return direction === "asc" ? cmp : -cmp;
|
|
1064
2252
|
}
|
|
1065
2253
|
return 0;
|
|
1066
2254
|
});
|
|
1067
2255
|
}
|
|
2256
|
+
function buildOrderLabelMaps(orderBy, joinContext, locale) {
|
|
2257
|
+
if (!joinContext?.resolveDictSource) return void 0;
|
|
2258
|
+
const resolveDict = joinContext.resolveDictSource.bind(joinContext);
|
|
2259
|
+
let maps;
|
|
2260
|
+
for (const { field, by } of orderBy) {
|
|
2261
|
+
if (by !== "label") continue;
|
|
2262
|
+
const dictSource = resolveDict(field);
|
|
2263
|
+
if (!dictSource) continue;
|
|
2264
|
+
const loc = locale ?? dictSource.displayLocale;
|
|
2265
|
+
if (loc === void 0) continue;
|
|
2266
|
+
const codeToLabel = /* @__PURE__ */ new Map();
|
|
2267
|
+
for (const entry of dictSource.snapshot()) {
|
|
2268
|
+
const k = entry["key"];
|
|
2269
|
+
const labels = entry["labels"];
|
|
2270
|
+
const label = labels?.[loc];
|
|
2271
|
+
if (typeof k === "string" && typeof label === "string") codeToLabel.set(k, label);
|
|
2272
|
+
}
|
|
2273
|
+
;
|
|
2274
|
+
(maps ??= /* @__PURE__ */ new Map()).set(field, codeToLabel);
|
|
2275
|
+
}
|
|
2276
|
+
return maps;
|
|
2277
|
+
}
|
|
2278
|
+
function compareMoney(a, b, desc) {
|
|
2279
|
+
const av = moneyScaledValue(a, desc);
|
|
2280
|
+
const bv = moneyScaledValue(b, desc);
|
|
2281
|
+
if (av === null) return bv === null ? 0 : 1;
|
|
2282
|
+
if (bv === null) return -1;
|
|
2283
|
+
return av < bv ? -1 : av > bv ? 1 : 0;
|
|
2284
|
+
}
|
|
1068
2285
|
function readField(record, field) {
|
|
1069
2286
|
if (record === null || record === void 0) return void 0;
|
|
1070
2287
|
if (!field.includes(".")) {
|
|
@@ -1116,6 +2333,16 @@ function serializeClause(clause) {
|
|
|
1116
2333
|
clauses: clause.clauses.map(serializeClause)
|
|
1117
2334
|
};
|
|
1118
2335
|
}
|
|
2336
|
+
if (clause.type === "crossJoin") {
|
|
2337
|
+
return {
|
|
2338
|
+
type: "crossJoin",
|
|
2339
|
+
target: clause.target,
|
|
2340
|
+
as: clause.as,
|
|
2341
|
+
on: clause.on ? "[function]" : void 0,
|
|
2342
|
+
onPredicateName: clause.onPredicateName,
|
|
2343
|
+
maxRows: clause.maxRows
|
|
2344
|
+
};
|
|
2345
|
+
}
|
|
1119
2346
|
return clause;
|
|
1120
2347
|
}
|
|
1121
2348
|
function canonicalCtxHash(ctx) {
|
|
@@ -1141,6 +2368,7 @@ function buildDictLabelResolver(joinCtx, field) {
|
|
|
1141
2368
|
const dictSource = joinCtx.resolveDictSource(field);
|
|
1142
2369
|
if (!dictSource) return void 0;
|
|
1143
2370
|
const snapshot = dictSource.snapshot();
|
|
2371
|
+
const displayLocale = dictSource.displayLocale;
|
|
1144
2372
|
const dictMap = /* @__PURE__ */ new Map();
|
|
1145
2373
|
for (const entry of snapshot) {
|
|
1146
2374
|
const k = entry["key"];
|
|
@@ -1150,9 +2378,11 @@ function buildDictLabelResolver(joinCtx, field) {
|
|
|
1150
2378
|
}
|
|
1151
2379
|
}
|
|
1152
2380
|
return async (key, locale, fallback) => {
|
|
2381
|
+
const effLocale = locale || displayLocale;
|
|
2382
|
+
if (!effLocale) return void 0;
|
|
1153
2383
|
const labels = dictMap.get(key);
|
|
1154
2384
|
if (!labels) return void 0;
|
|
1155
|
-
if (labels[
|
|
2385
|
+
if (labels[effLocale] !== void 0) return labels[effLocale];
|
|
1156
2386
|
const chain = Array.isArray(fallback) ? fallback : fallback ? [fallback] : [];
|
|
1157
2387
|
for (const fb of chain) {
|
|
1158
2388
|
if (fb === "any") {
|
|
@@ -1295,26 +2525,37 @@ function count(opts) {
|
|
|
1295
2525
|
const _seed = opts?.seed;
|
|
1296
2526
|
void _seed;
|
|
1297
2527
|
return {
|
|
2528
|
+
op: "count",
|
|
1298
2529
|
init: () => 0,
|
|
1299
2530
|
step: (state) => state + 1,
|
|
1300
2531
|
remove: (state) => state - 1,
|
|
1301
|
-
finalize: (state) => state
|
|
2532
|
+
finalize: (state) => state,
|
|
2533
|
+
merge: (a, b) => a + b
|
|
1302
2534
|
};
|
|
1303
2535
|
}
|
|
1304
2536
|
function sum(field, opts) {
|
|
1305
2537
|
const _seed = opts?.seed;
|
|
1306
2538
|
void _seed;
|
|
1307
2539
|
return {
|
|
2540
|
+
op: "sum",
|
|
2541
|
+
field,
|
|
2542
|
+
// Money-only metadata, read by `wrapMoneyReducers`. No effect on a
|
|
2543
|
+
// generic numeric sum.
|
|
2544
|
+
...opts?.convertTo !== void 0 ? { convertTo: opts.convertTo } : {},
|
|
2545
|
+
...opts?.fx !== void 0 ? { fx: opts.fx } : {},
|
|
1308
2546
|
init: () => 0,
|
|
1309
2547
|
step: (state, record) => state + readNumber(record, field),
|
|
1310
2548
|
remove: (state, record) => state - readNumber(record, field),
|
|
1311
|
-
finalize: (state) => state
|
|
2549
|
+
finalize: (state) => state,
|
|
2550
|
+
merge: (a, b) => a + b
|
|
1312
2551
|
};
|
|
1313
2552
|
}
|
|
1314
2553
|
function avg(field, opts) {
|
|
1315
2554
|
const _seed = opts?.seed;
|
|
1316
2555
|
void _seed;
|
|
1317
2556
|
return {
|
|
2557
|
+
op: "avg",
|
|
2558
|
+
field,
|
|
1318
2559
|
init: () => ({ sum: 0, count: 0 }),
|
|
1319
2560
|
step: (state, record) => ({
|
|
1320
2561
|
sum: state.sum + readNumber(record, field),
|
|
@@ -1324,7 +2565,8 @@ function avg(field, opts) {
|
|
|
1324
2565
|
sum: state.sum - readNumber(record, field),
|
|
1325
2566
|
count: state.count - 1
|
|
1326
2567
|
}),
|
|
1327
|
-
finalize: (state) => state.count === 0 ? null : state.sum / state.count
|
|
2568
|
+
finalize: (state) => state.count === 0 ? null : state.sum / state.count,
|
|
2569
|
+
merge: (a, b) => ({ sum: a.sum + b.sum, count: a.count + b.count })
|
|
1328
2570
|
};
|
|
1329
2571
|
}
|
|
1330
2572
|
function pushValue(state, value) {
|
|
@@ -1341,6 +2583,8 @@ function min(field, opts) {
|
|
|
1341
2583
|
const _seed = opts?.seed;
|
|
1342
2584
|
void _seed;
|
|
1343
2585
|
return {
|
|
2586
|
+
op: "min",
|
|
2587
|
+
field,
|
|
1344
2588
|
init: () => ({ values: [] }),
|
|
1345
2589
|
step: (state, record) => pushValue(state, readNumber(record, field)),
|
|
1346
2590
|
remove: (state, record) => removeValue(state, readNumber(record, field)),
|
|
@@ -1352,13 +2596,16 @@ function min(field, opts) {
|
|
|
1352
2596
|
if (v < out) out = v;
|
|
1353
2597
|
}
|
|
1354
2598
|
return out;
|
|
1355
|
-
}
|
|
2599
|
+
},
|
|
2600
|
+
merge: (a, b) => ({ values: [...a.values, ...b.values] })
|
|
1356
2601
|
};
|
|
1357
2602
|
}
|
|
1358
2603
|
function max(field, opts) {
|
|
1359
2604
|
const _seed = opts?.seed;
|
|
1360
2605
|
void _seed;
|
|
1361
2606
|
return {
|
|
2607
|
+
op: "max",
|
|
2608
|
+
field,
|
|
1362
2609
|
init: () => ({ values: [] }),
|
|
1363
2610
|
step: (state, record) => pushValue(state, readNumber(record, field)),
|
|
1364
2611
|
remove: (state, record) => removeValue(state, readNumber(record, field)),
|
|
@@ -1370,11 +2617,17 @@ function max(field, opts) {
|
|
|
1370
2617
|
if (v > out) out = v;
|
|
1371
2618
|
}
|
|
1372
2619
|
return out;
|
|
1373
|
-
}
|
|
2620
|
+
},
|
|
2621
|
+
merge: (a, b) => ({ values: [...a.values, ...b.values] })
|
|
1374
2622
|
};
|
|
1375
2623
|
}
|
|
1376
2624
|
function readNumber(record, field) {
|
|
1377
2625
|
const value = readPath(record, field);
|
|
2626
|
+
if (typeof value === "object" && value !== null && "amount" in value && "currency" in value) {
|
|
2627
|
+
throw new Error(
|
|
2628
|
+
`aggregate: field "${field}" holds a money value but was not money-aware \u2014 declare it in the collection's moneyFields so sum/min/max stay exact`
|
|
2629
|
+
);
|
|
2630
|
+
}
|
|
1378
2631
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1379
2632
|
}
|
|
1380
2633
|
|
|
@@ -1530,15 +2783,17 @@ function resetGroupByWarnings() {
|
|
|
1530
2783
|
warnedCardinalityFields.clear();
|
|
1531
2784
|
}
|
|
1532
2785
|
var GroupedQueryBase = class {
|
|
1533
|
-
constructor(executeRecords, fieldOrFields, upstreams, dictLabelResolver) {
|
|
2786
|
+
constructor(executeRecords, fieldOrFields, upstreams, dictLabelResolver, moneyFields) {
|
|
1534
2787
|
this.executeRecords = executeRecords;
|
|
1535
2788
|
this.upstreams = upstreams;
|
|
1536
2789
|
this.dictLabelResolver = dictLabelResolver;
|
|
2790
|
+
this.moneyFields = moneyFields;
|
|
1537
2791
|
this.fields = typeof fieldOrFields === "string" ? [fieldOrFields] : [...fieldOrFields];
|
|
1538
2792
|
}
|
|
1539
2793
|
executeRecords;
|
|
1540
2794
|
upstreams;
|
|
1541
2795
|
dictLabelResolver;
|
|
2796
|
+
moneyFields;
|
|
1542
2797
|
/**
|
|
1543
2798
|
* Field set this grouped query buckets on. Stored in declaration
|
|
1544
2799
|
* order — the same order is preserved on every result row by
|
|
@@ -1546,6 +2801,10 @@ var GroupedQueryBase = class {
|
|
|
1546
2801
|
* `[field]`.
|
|
1547
2802
|
*/
|
|
1548
2803
|
fields;
|
|
2804
|
+
/** Apply money-aware reducer rewriting when money fields are declared. */
|
|
2805
|
+
wrapSpec(spec) {
|
|
2806
|
+
return this.moneyFields ? wrapMoneyReducers(spec, this.moneyFields) : spec;
|
|
2807
|
+
}
|
|
1549
2808
|
};
|
|
1550
2809
|
var GroupedQuery = class extends GroupedQueryBase {
|
|
1551
2810
|
/**
|
|
@@ -1558,7 +2817,7 @@ var GroupedQuery = class extends GroupedQueryBase {
|
|
|
1558
2817
|
return new GroupedAggregation(
|
|
1559
2818
|
this.executeRecords,
|
|
1560
2819
|
this.fields,
|
|
1561
|
-
spec,
|
|
2820
|
+
this.wrapSpec(spec),
|
|
1562
2821
|
this.upstreams,
|
|
1563
2822
|
this.dictLabelResolver
|
|
1564
2823
|
);
|
|
@@ -1569,17 +2828,20 @@ var GroupedQueryN = class extends GroupedQueryBase {
|
|
|
1569
2828
|
return new GroupedAggregation(
|
|
1570
2829
|
this.executeRecords,
|
|
1571
2830
|
this.fields,
|
|
1572
|
-
spec,
|
|
2831
|
+
this.wrapSpec(spec),
|
|
1573
2832
|
this.upstreams,
|
|
1574
2833
|
this.dictLabelResolver
|
|
1575
2834
|
);
|
|
1576
2835
|
}
|
|
1577
2836
|
};
|
|
1578
|
-
function groupAndReduce(records, fieldOrFields, spec) {
|
|
2837
|
+
function groupAndReduce(records, fieldOrFields, spec, moneyFields) {
|
|
1579
2838
|
const fields = typeof fieldOrFields === "string" ? [fieldOrFields] : fieldOrFields;
|
|
1580
2839
|
if (fields.length === 0) {
|
|
1581
2840
|
throw new Error(".groupBy() requires at least one field");
|
|
1582
2841
|
}
|
|
2842
|
+
if (moneyFields) {
|
|
2843
|
+
spec = wrapMoneyReducers(spec, moneyFields);
|
|
2844
|
+
}
|
|
1583
2845
|
const buckets = /* @__PURE__ */ new Map();
|
|
1584
2846
|
const fieldLabel = fields.length === 1 ? fields[0] : `[${fields.join(", ")}]`;
|
|
1585
2847
|
for (const record of records) {
|
|
@@ -1641,9 +2903,29 @@ var GroupedAggregation = class {
|
|
|
1641
2903
|
upstreams;
|
|
1642
2904
|
dictLabelResolver;
|
|
1643
2905
|
fields;
|
|
1644
|
-
/**
|
|
1645
|
-
|
|
1646
|
-
|
|
2906
|
+
/**
|
|
2907
|
+
* Execute the query, group, reduce, and return an array of rows.
|
|
2908
|
+
*
|
|
2909
|
+
* `opts` (#285 query-form MV grouping): when a `locale` + `i18nFields` are
|
|
2910
|
+
* given, the declared group-key `i18nText` fields are resolved to that locale
|
|
2911
|
+
* at the `mv` layer BEFORE bucketing — so an i18n group key is a stable string
|
|
2912
|
+
* instead of a raw `{locale}` map. The MV executor passes the MV's
|
|
2913
|
+
* `i18nLocale`/`i18nFields`; ordinary `.run()` callers pass nothing and are
|
|
2914
|
+
* unaffected.
|
|
2915
|
+
*/
|
|
2916
|
+
run(opts) {
|
|
2917
|
+
let records = this.executeRecords();
|
|
2918
|
+
if (opts?.locale !== void 0 && opts.i18nFields !== void 0) {
|
|
2919
|
+
const groupI18n = {};
|
|
2920
|
+
for (const f of this.fields) {
|
|
2921
|
+
const d = opts.i18nFields[f];
|
|
2922
|
+
if (d !== void 0) groupI18n[f] = d;
|
|
2923
|
+
}
|
|
2924
|
+
if (Object.keys(groupI18n).length > 0) {
|
|
2925
|
+
records = records.map((r) => applyI18nLocale(r, groupI18n, opts.locale, void 0, "mv"));
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
return groupAndReduce(records, this.fields, this.spec);
|
|
1647
2929
|
}
|
|
1648
2930
|
/**
|
|
1649
2931
|
* Execute the query, group, reduce, and resolve `<field>Label` for
|
|
@@ -1723,12 +3005,29 @@ var ScanBuilder = class _ScanBuilder {
|
|
|
1723
3005
|
* context throws with an actionable error.
|
|
1724
3006
|
*/
|
|
1725
3007
|
joinContext;
|
|
1726
|
-
|
|
3008
|
+
/**
|
|
3009
|
+
* Money field descriptors for the backing collection. When present, yielded
|
|
3010
|
+
* records are decoded (stored scaled-int → canonical decimal) so `scan()`
|
|
3011
|
+
* agrees with `get()`/`list()`/`query().toArray()` — #322. Decoded with
|
|
3012
|
+
* `'raw'` (canonical decimal, no locale-formatted virtuals) since the scan
|
|
3013
|
+
* stream carries no locale context, mirroring `Query.toArray()`.
|
|
3014
|
+
*/
|
|
3015
|
+
moneyFields;
|
|
3016
|
+
constructor(pageProvider, pageSize = DEFAULT_SCAN_PAGE_SIZE, clauses = [], joins = [], joinContext, moneyFields) {
|
|
1727
3017
|
this.pageProvider = pageProvider;
|
|
1728
3018
|
this.pageSize = pageSize;
|
|
1729
3019
|
this.clauses = clauses;
|
|
1730
3020
|
this.joins = joins;
|
|
1731
3021
|
this.joinContext = joinContext;
|
|
3022
|
+
this.moneyFields = moneyFields;
|
|
3023
|
+
}
|
|
3024
|
+
/**
|
|
3025
|
+
* Decode this scan's money fields on a record (stored scaled-int → canonical
|
|
3026
|
+
* decimal). No-op when no money fields are declared. See {@link moneyFields}.
|
|
3027
|
+
*/
|
|
3028
|
+
decodeMoney(record) {
|
|
3029
|
+
if (!this.moneyFields || Object.keys(this.moneyFields).length === 0) return record;
|
|
3030
|
+
return decodeMoneyFields(record, this.moneyFields, "raw");
|
|
1732
3031
|
}
|
|
1733
3032
|
/**
|
|
1734
3033
|
* Add a field comparison. Runs per record as the scan stream
|
|
@@ -1744,13 +3043,15 @@ var ScanBuilder = class _ScanBuilder {
|
|
|
1744
3043
|
* evaluates clauses per record in O(1) per clause.
|
|
1745
3044
|
*/
|
|
1746
3045
|
where(field, op, value) {
|
|
1747
|
-
const
|
|
3046
|
+
const desc = this.moneyFields?.[field];
|
|
3047
|
+
const clause = desc ? moneyFieldClause(field, op, value, desc) : { type: "field", field, op, value };
|
|
1748
3048
|
return new _ScanBuilder(
|
|
1749
3049
|
this.pageProvider,
|
|
1750
3050
|
this.pageSize,
|
|
1751
3051
|
[...this.clauses, clause],
|
|
1752
3052
|
this.joins,
|
|
1753
|
-
this.joinContext
|
|
3053
|
+
this.joinContext,
|
|
3054
|
+
this.moneyFields
|
|
1754
3055
|
);
|
|
1755
3056
|
}
|
|
1756
3057
|
/**
|
|
@@ -1769,7 +3070,8 @@ var ScanBuilder = class _ScanBuilder {
|
|
|
1769
3070
|
this.pageSize,
|
|
1770
3071
|
[...this.clauses, clause],
|
|
1771
3072
|
this.joins,
|
|
1772
|
-
this.joinContext
|
|
3073
|
+
this.joinContext,
|
|
3074
|
+
this.moneyFields
|
|
1773
3075
|
);
|
|
1774
3076
|
}
|
|
1775
3077
|
/**
|
|
@@ -1880,7 +3182,8 @@ var ScanBuilder = class _ScanBuilder {
|
|
|
1880
3182
|
this.pageSize,
|
|
1881
3183
|
this.clauses,
|
|
1882
3184
|
[...this.joins, leg],
|
|
1883
|
-
this.joinContext
|
|
3185
|
+
this.joinContext,
|
|
3186
|
+
this.moneyFields
|
|
1884
3187
|
);
|
|
1885
3188
|
}
|
|
1886
3189
|
/**
|
|
@@ -1897,10 +3200,11 @@ var ScanBuilder = class _ScanBuilder {
|
|
|
1897
3200
|
while (true) {
|
|
1898
3201
|
for (const record of page.items) {
|
|
1899
3202
|
if (!this.recordMatches(record)) continue;
|
|
3203
|
+
const decoded = this.decodeMoney(record);
|
|
1900
3204
|
if (joinResolvers === null) {
|
|
1901
|
-
yield
|
|
3205
|
+
yield decoded;
|
|
1902
3206
|
} else {
|
|
1903
|
-
let attached =
|
|
3207
|
+
let attached = decoded;
|
|
1904
3208
|
for (const resolver of joinResolvers) {
|
|
1905
3209
|
attached = this.applyOneJoinStreaming(attached, resolver);
|
|
1906
3210
|
}
|
|
@@ -2086,8 +3390,9 @@ var ScanBuilder = class _ScanBuilder {
|
|
|
2086
3390
|
*/
|
|
2087
3391
|
recordMatches(record) {
|
|
2088
3392
|
if (this.clauses.length === 0) return true;
|
|
3393
|
+
const fnView = this.moneyFields && Object.keys(this.moneyFields).length > 0 && hasFnClause(this.clauses) ? this.decodeMoney(record) : void 0;
|
|
2089
3394
|
for (const clause of this.clauses) {
|
|
2090
|
-
if (!evaluateClause(record, clause)) return false;
|
|
3395
|
+
if (!evaluateClause(record, clause, fnView)) return false;
|
|
2091
3396
|
}
|
|
2092
3397
|
return true;
|
|
2093
3398
|
}
|
|
@@ -2102,6 +3407,9 @@ function coerceRefKey2(value) {
|
|
|
2102
3407
|
0 && (module.exports = {
|
|
2103
3408
|
Aggregation,
|
|
2104
3409
|
CollectionIndexes,
|
|
3410
|
+
CrossJoinSourceUnknownError,
|
|
3411
|
+
CrossJoinTooLargeError,
|
|
3412
|
+
DEFAULT_CROSS_JOIN_MAX_ROWS,
|
|
2105
3413
|
DEFAULT_JOIN_MAX_ROWS,
|
|
2106
3414
|
DanglingReferenceError,
|
|
2107
3415
|
GROUPBY_MAX_CARDINALITY,
|