@noy-db/hub 0.2.0-pre.14 → 0.2.0-pre.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aggregate/index.cjs +160 -64
- package/dist/aggregate/index.cjs.map +1 -1
- package/dist/aggregate/index.d.cts +2 -2
- package/dist/aggregate/index.d.ts +2 -2
- package/dist/aggregate/index.js +3 -3
- package/dist/attestation/index.cjs.map +1 -1
- package/dist/attestation/index.d.cts +5 -5
- package/dist/attestation/index.d.ts +5 -5
- package/dist/attestation/index.js +4 -4
- package/dist/blobs/index.cjs.map +1 -1
- package/dist/blobs/index.d.cts +6 -6
- package/dist/blobs/index.d.ts +6 -6
- package/dist/blobs/index.js +3 -3
- package/dist/bundle/index.cjs +607 -114
- package/dist/bundle/index.cjs.map +1 -1
- package/dist/bundle/index.d.cts +7 -7
- package/dist/bundle/index.d.ts +7 -7
- package/dist/bundle/index.js +7 -7
- package/dist/{chunk-BIYRQQV6.js → chunk-3YWP3WBP.js} +3 -3
- package/dist/{chunk-VU7SWWT5.js → chunk-42FEUPZQ.js} +10 -6
- package/dist/chunk-42FEUPZQ.js.map +1 -0
- package/dist/{chunk-ACKFRSAH.js → chunk-667MB6AH.js} +132 -54
- package/dist/chunk-667MB6AH.js.map +1 -0
- package/dist/{chunk-A5ZOOZFB.js → chunk-6H2ZUNR7.js} +2 -2
- package/dist/{chunk-7HT2MEZ5.js → chunk-7BQ4QWYX.js} +3 -3
- package/dist/{chunk-DQU36Q7I.js → chunk-7Z7KSVA5.js} +13 -4
- package/dist/chunk-7Z7KSVA5.js.map +1 -0
- package/dist/{chunk-WBAYSNUQ.js → chunk-BI6ETQPF.js} +2 -2
- package/dist/{chunk-56DJ7JVK.js → chunk-BR3AMFGS.js} +2 -2
- package/dist/chunk-CJORTUJ2.js +524 -0
- package/dist/chunk-CJORTUJ2.js.map +1 -0
- package/dist/{chunk-YNTBADIY.js → chunk-CZI2A4MQ.js} +2 -2
- package/dist/{chunk-COFPAMX6.js → chunk-DLZ2ONOD.js} +3 -3
- package/dist/{chunk-KGCORI4L.js → chunk-DUREQF5W.js} +266 -66
- package/dist/chunk-DUREQF5W.js.map +1 -0
- package/dist/{chunk-PE4AQGFH.js → chunk-E2CDVKMH.js} +3 -3
- package/dist/{chunk-GC4V7RU7.js → chunk-F3BPIPLS.js} +1 -1
- package/dist/{chunk-GC4V7RU7.js.map → chunk-F3BPIPLS.js.map} +1 -1
- package/dist/{chunk-L2FE64BU.js → chunk-FFXM3ZIF.js} +2 -2
- package/dist/{chunk-5LQG6ZO2.js → chunk-G4SCICH5.js} +8 -3
- package/dist/chunk-G4SCICH5.js.map +1 -0
- package/dist/{chunk-WGHU7BLI.js → chunk-GNI5STXQ.js} +2 -2
- package/dist/{chunk-UWNYBOOO.js → chunk-HBXJ37ZY.js} +11 -5
- package/dist/chunk-HBXJ37ZY.js.map +1 -0
- package/dist/{chunk-NP6EZT44.js → chunk-IQLVUT37.js} +2 -2
- package/dist/{chunk-7PS7EOCF.js → chunk-IXBIFDEW.js} +2 -2
- package/dist/{chunk-LX3CB26H.js → chunk-KABJXG2F.js} +2 -2
- package/dist/{chunk-3EWA37FV.js → chunk-L2BNJ6HM.js} +32 -276
- package/dist/chunk-L2BNJ6HM.js.map +1 -0
- package/dist/{chunk-DKO2QFSA.js → chunk-OB2ZJQ2D.js} +2 -2
- package/dist/{chunk-4PEFEETV.js → chunk-OMAMZKKD.js} +2 -2
- package/dist/{chunk-EGD5DXFT.js → chunk-OQSRJG6A.js} +13 -1
- package/dist/chunk-OQSRJG6A.js.map +1 -0
- package/dist/{chunk-KI6HAJWL.js → chunk-QSUK7YWK.js} +2 -2
- package/dist/{chunk-YHPM5D7Y.js → chunk-QVIEAYTP.js} +61 -2
- package/dist/chunk-QVIEAYTP.js.map +1 -0
- package/dist/{chunk-NSCVNK5K.js → chunk-SCJPI4Z5.js} +3 -3
- package/dist/{chunk-ZWTNWAO4.js → chunk-TKIY625R.js} +13 -3
- package/dist/chunk-TKIY625R.js.map +1 -0
- package/dist/{chunk-OHVFWCJP.js → chunk-VLMPU56Q.js} +48 -18
- package/dist/chunk-VLMPU56Q.js.map +1 -0
- package/dist/{chunk-6AJBSQU4.js → chunk-XL35NSEN.js} +2 -2
- package/dist/{chunk-WIBHRONM.js → chunk-XWH4MXIU.js} +2 -2
- package/dist/consent/index.d.cts +6 -6
- package/dist/consent/index.d.ts +6 -6
- package/dist/derivations/index.cjs +24 -3
- package/dist/derivations/index.cjs.map +1 -1
- package/dist/derivations/index.d.cts +7 -7
- package/dist/derivations/index.d.ts +7 -7
- package/dist/derivations/index.js +2 -2
- package/dist/{dev-unlock-BF4OSxRv.d.cts → dev-unlock-8XzcD2Z4.d.cts} +1 -1
- package/dist/{dev-unlock-DV7ujTCI.d.ts → dev-unlock-DR3upLd1.d.ts} +1 -1
- package/dist/executor-AZLS3KBK.js +11 -0
- package/dist/{fanout-sidecar-N6OJX6QR.js → fanout-sidecar-67CMI3UT.js} +2 -2
- package/dist/guards/index.cjs +9 -5
- package/dist/guards/index.cjs.map +1 -1
- package/dist/guards/index.d.cts +7 -7
- package/dist/guards/index.d.ts +7 -7
- package/dist/guards/index.js +1 -1
- package/dist/{hash-DswxkLtW.d.ts → hash-CDjye9KV.d.ts} +1 -1
- package/dist/{hash-BcF5WQXl.d.cts → hash-DuQ88_5W.d.cts} +1 -1
- package/dist/history/index.cjs.map +1 -1
- package/dist/history/index.d.cts +7 -7
- package/dist/history/index.d.ts +7 -7
- package/dist/history/index.js +2 -2
- package/dist/i18n/index.cjs.map +1 -1
- package/dist/i18n/index.d.cts +6 -6
- package/dist/i18n/index.d.ts +6 -6
- package/dist/i18n/index.js +3 -3
- package/dist/{immutable-guard-7KqslW2K.d.cts → immutable-guard-CRPvu24K.d.cts} +16 -1
- package/dist/{immutable-guard-C8IYdzfu.d.ts → immutable-guard-Dov3WvwF.d.ts} +16 -1
- package/dist/{index-Cqzp4tt9.d.ts → index-C8Bk3-VF.d.cts} +11 -3
- package/dist/{index-CUVOMtgg.d.cts → index-nP99bXLg.d.ts} +11 -3
- package/dist/index.cjs +840 -122
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +146 -15
- package/dist/index.d.ts +146 -15
- package/dist/index.js +153 -35
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.cjs +92 -31
- 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 +3 -3
- package/dist/{issue-ADVS4OVP.js → issue-RZP3VI6O.js} +4 -4
- package/dist/{lazy-builder-D5GU14TS.d.ts → lazy-builder-ChSqcF5t.d.ts} +1 -1
- package/dist/{lazy-builder-Ci5_YG73.d.cts → lazy-builder-eYZzLEL1.d.cts} +1 -1
- package/dist/{ledger-CWSE3BLF.js → ledger-A3LL253R.js} +3 -3
- package/dist/materialized-views/index.cjs +409 -7
- package/dist/materialized-views/index.cjs.map +1 -1
- package/dist/materialized-views/index.d.cts +7 -7
- package/dist/materialized-views/index.d.ts +7 -7
- package/dist/materialized-views/index.js +6 -6
- package/dist/noydb-WCMY2ZOW.js +35 -0
- package/dist/overlay-views/index.cjs +47 -17
- package/dist/overlay-views/index.cjs.map +1 -1
- package/dist/overlay-views/index.d.cts +28 -10
- package/dist/overlay-views/index.d.ts +28 -10
- package/dist/overlay-views/index.js +1 -1
- package/dist/periods/index.cjs.map +1 -1
- package/dist/periods/index.d.cts +6 -6
- package/dist/periods/index.d.ts +6 -6
- package/dist/periods/index.js +3 -3
- package/dist/{predicate-Bt5ft-9c.d.cts → predicate-BmhBSPCH.d.cts} +59 -2
- package/dist/{predicate-Bt5ft-9c.d.ts → predicate-BmhBSPCH.d.ts} +59 -2
- package/dist/{public-envelope-SYHEYQ3X.js → public-envelope-YP2UWMLG.js} +3 -3
- package/dist/query/index.cjs +604 -205
- package/dist/query/index.cjs.map +1 -1
- package/dist/query/index.d.cts +3 -3
- package/dist/query/index.d.ts +3 -3
- package/dist/query/index.js +5 -5
- package/dist/{registry-XGLNADIE.js → registry-EB6SISTA.js} +2 -2
- package/dist/{registry-DK5YWAAA.js → registry-UTA4CLQS.js} +2 -2
- package/dist/{revoke-ZDFKMR5E.js → revoke-HNMQZSCL.js} +4 -4
- package/dist/session/index.d.cts +7 -7
- package/dist/session/index.d.ts +7 -7
- package/dist/shadow/index.d.cts +6 -6
- package/dist/shadow/index.d.ts +6 -6
- package/dist/{signer-P5D7Y72U.js → signer-DCMNKXSF.js} +3 -3
- package/dist/snapshots/index.d.cts +6 -6
- package/dist/snapshots/index.d.ts +6 -6
- package/dist/snapshots/index.js +3 -3
- package/dist/{stale-7FRJVHN6.js → stale-W5PQTRYH.js} +2 -2
- package/dist/store/index.d.cts +6 -6
- package/dist/store/index.d.ts +6 -6
- package/dist/{strategy-CrS7PnbE.d.cts → strategy-BtW8fAjz.d.cts} +2 -2
- package/dist/{strategy-CrS7PnbE.d.ts → strategy-BtW8fAjz.d.ts} +2 -2
- package/dist/sync/index.cjs.map +1 -1
- package/dist/sync/index.d.cts +5 -5
- package/dist/sync/index.d.ts +5 -5
- package/dist/sync/index.js +2 -2
- package/dist/team/index.cjs.map +1 -1
- package/dist/team/index.d.cts +6 -6
- package/dist/team/index.d.ts +6 -6
- package/dist/team/index.js +5 -5
- package/dist/tx/index.cjs +66 -3
- package/dist/tx/index.cjs.map +1 -1
- package/dist/tx/index.d.cts +24 -8
- package/dist/tx/index.d.ts +24 -8
- package/dist/tx/index.js +7 -3
- package/dist/tx/index.js.map +1 -1
- package/dist/{types-V5R2-pd4.d.cts → types-Bze6vkwm.d.cts} +391 -144
- package/dist/{types-BFHQUjdy.d.ts → types-DrmBTscX.d.ts} +391 -144
- package/dist/{ulid-p2nKiiKg.d.ts → ulid-DbBVrNSt.d.ts} +1 -1
- package/dist/{ulid-CwNf9e6-.d.cts → ulid-DfZlAh0u.d.cts} +1 -1
- package/dist/{vault-group-W7QC4UYW.js → vault-group-DX2HFQMX.js} +3 -3
- package/dist/{with-derivation-C9K43BOB.d.cts → with-derivation-CCqAchD5.d.cts} +1 -1
- package/dist/{with-derivation-Ds9yZgCj.d.ts → with-derivation-_lySGdlm.d.ts} +1 -1
- package/dist/{with-materialized-view-DgQcAjYv.d.cts → with-materialized-view--4PsvMDu.d.cts} +1 -1
- package/dist/{with-materialized-view-DwR4jkV5.d.ts → with-materialized-view-QT1Tp7NO.d.ts} +1 -1
- package/dist/{with-overlayed-view-ByyhHdVr.d.ts → with-overlayed-view-BEXfpzSb.d.ts} +1 -1
- package/dist/{with-overlayed-view-7-rUB3vD.d.cts → with-overlayed-view-DlH5qmeB.d.cts} +1 -1
- package/package.json +3 -3
- package/dist/chunk-3EWA37FV.js.map +0 -1
- package/dist/chunk-5LQG6ZO2.js.map +0 -1
- package/dist/chunk-ACKFRSAH.js.map +0 -1
- package/dist/chunk-DQU36Q7I.js.map +0 -1
- package/dist/chunk-EGD5DXFT.js.map +0 -1
- package/dist/chunk-KGCORI4L.js.map +0 -1
- package/dist/chunk-OHVFWCJP.js.map +0 -1
- package/dist/chunk-TV3YZ35S.js +0 -90
- package/dist/chunk-TV3YZ35S.js.map +0 -1
- package/dist/chunk-UWNYBOOO.js.map +0 -1
- package/dist/chunk-VU7SWWT5.js.map +0 -1
- package/dist/chunk-YHPM5D7Y.js.map +0 -1
- package/dist/chunk-ZWTNWAO4.js.map +0 -1
- package/dist/executor-723ZP6TH.js +0 -11
- package/dist/noydb-VZ4JVW55.js +0 -35
- /package/dist/{chunk-BIYRQQV6.js.map → chunk-3YWP3WBP.js.map} +0 -0
- /package/dist/{chunk-A5ZOOZFB.js.map → chunk-6H2ZUNR7.js.map} +0 -0
- /package/dist/{chunk-7HT2MEZ5.js.map → chunk-7BQ4QWYX.js.map} +0 -0
- /package/dist/{chunk-WBAYSNUQ.js.map → chunk-BI6ETQPF.js.map} +0 -0
- /package/dist/{chunk-56DJ7JVK.js.map → chunk-BR3AMFGS.js.map} +0 -0
- /package/dist/{chunk-YNTBADIY.js.map → chunk-CZI2A4MQ.js.map} +0 -0
- /package/dist/{chunk-COFPAMX6.js.map → chunk-DLZ2ONOD.js.map} +0 -0
- /package/dist/{chunk-PE4AQGFH.js.map → chunk-E2CDVKMH.js.map} +0 -0
- /package/dist/{chunk-L2FE64BU.js.map → chunk-FFXM3ZIF.js.map} +0 -0
- /package/dist/{chunk-WGHU7BLI.js.map → chunk-GNI5STXQ.js.map} +0 -0
- /package/dist/{chunk-NP6EZT44.js.map → chunk-IQLVUT37.js.map} +0 -0
- /package/dist/{chunk-7PS7EOCF.js.map → chunk-IXBIFDEW.js.map} +0 -0
- /package/dist/{chunk-LX3CB26H.js.map → chunk-KABJXG2F.js.map} +0 -0
- /package/dist/{chunk-DKO2QFSA.js.map → chunk-OB2ZJQ2D.js.map} +0 -0
- /package/dist/{chunk-4PEFEETV.js.map → chunk-OMAMZKKD.js.map} +0 -0
- /package/dist/{chunk-KI6HAJWL.js.map → chunk-QSUK7YWK.js.map} +0 -0
- /package/dist/{chunk-NSCVNK5K.js.map → chunk-SCJPI4Z5.js.map} +0 -0
- /package/dist/{chunk-6AJBSQU4.js.map → chunk-XL35NSEN.js.map} +0 -0
- /package/dist/{chunk-WIBHRONM.js.map → chunk-XWH4MXIU.js.map} +0 -0
- /package/dist/{executor-723ZP6TH.js.map → executor-AZLS3KBK.js.map} +0 -0
- /package/dist/{fanout-sidecar-N6OJX6QR.js.map → fanout-sidecar-67CMI3UT.js.map} +0 -0
- /package/dist/{issue-ADVS4OVP.js.map → issue-RZP3VI6O.js.map} +0 -0
- /package/dist/{ledger-CWSE3BLF.js.map → ledger-A3LL253R.js.map} +0 -0
- /package/dist/{noydb-VZ4JVW55.js.map → noydb-WCMY2ZOW.js.map} +0 -0
- /package/dist/{public-envelope-SYHEYQ3X.js.map → public-envelope-YP2UWMLG.js.map} +0 -0
- /package/dist/{registry-DK5YWAAA.js.map → registry-EB6SISTA.js.map} +0 -0
- /package/dist/{registry-XGLNADIE.js.map → registry-UTA4CLQS.js.map} +0 -0
- /package/dist/{revoke-ZDFKMR5E.js.map → revoke-HNMQZSCL.js.map} +0 -0
- /package/dist/{signer-P5D7Y72U.js.map → signer-DCMNKXSF.js.map} +0 -0
- /package/dist/{stale-7FRJVHN6.js.map → stale-W5PQTRYH.js.map} +0 -0
- /package/dist/{vault-group-W7QC4UYW.js.map → vault-group-DX2HFQMX.js.map} +0 -0
package/dist/query/index.cjs
CHANGED
|
@@ -57,88 +57,156 @@ __export(query_exports, {
|
|
|
57
57
|
});
|
|
58
58
|
module.exports = __toCommonJS(query_exports);
|
|
59
59
|
|
|
60
|
-
// src/
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
if (!
|
|
64
|
-
|
|
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);
|
|
65
77
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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();
|
|
71
87
|
}
|
|
72
|
-
|
|
88
|
+
s = expandExponent(s);
|
|
89
|
+
if (s.startsWith("+")) s = s.slice(1);
|
|
90
|
+
if (!/^-?(\d+(\.\d*)?|\.\d+)$/.test(s)) return null;
|
|
91
|
+
return s;
|
|
73
92
|
}
|
|
74
|
-
function
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
case "
|
|
79
|
-
return actual === value;
|
|
80
|
-
case "!=":
|
|
81
|
-
return actual !== value;
|
|
82
|
-
case "<":
|
|
83
|
-
return isComparable(actual, value) && actual < value;
|
|
84
|
-
case "<=":
|
|
85
|
-
return isComparable(actual, value) && actual <= value;
|
|
86
|
-
case ">":
|
|
87
|
-
return isComparable(actual, value) && actual > value;
|
|
88
|
-
case ">=":
|
|
89
|
-
return isComparable(actual, value) && actual >= value;
|
|
90
|
-
case "in":
|
|
91
|
-
return Array.isArray(value) && value.includes(actual);
|
|
92
|
-
case "contains":
|
|
93
|
-
if (typeof actual === "string") return typeof value === "string" && actual.includes(value);
|
|
94
|
-
if (Array.isArray(actual)) return actual.includes(value);
|
|
95
|
-
return false;
|
|
96
|
-
case "startsWith":
|
|
97
|
-
return typeof actual === "string" && typeof value === "string" && actual.startsWith(value);
|
|
98
|
-
case "between": {
|
|
99
|
-
if (!Array.isArray(value) || value.length !== 2) return false;
|
|
100
|
-
const [lo, hi] = value;
|
|
101
|
-
if (!isComparable(actual, lo) || !isComparable(actual, hi)) return false;
|
|
102
|
-
return actual >= lo && actual <= hi;
|
|
103
|
-
}
|
|
104
|
-
default: {
|
|
105
|
-
const _exhaustive = op;
|
|
106
|
-
void _exhaustive;
|
|
93
|
+
function shouldRoundUp(negative, lastKeptDigit, firstDiscarded, hasMoreNonZeroAfterFirst, mode) {
|
|
94
|
+
switch (mode) {
|
|
95
|
+
case "up":
|
|
96
|
+
return true;
|
|
97
|
+
case "down":
|
|
107
98
|
return false;
|
|
108
|
-
|
|
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;
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
|
-
function
|
|
112
|
-
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
|
|
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 };
|
|
116
142
|
}
|
|
117
|
-
function
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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;
|
|
142
210
|
}
|
|
143
211
|
|
|
144
212
|
// src/errors.ts
|
|
@@ -151,6 +219,12 @@ var NoydbError = class extends Error {
|
|
|
151
219
|
this.code = code;
|
|
152
220
|
}
|
|
153
221
|
};
|
|
222
|
+
var ValidationError = class extends NoydbError {
|
|
223
|
+
constructor(message = "Validation error") {
|
|
224
|
+
super("VALIDATION_ERROR", message);
|
|
225
|
+
this.name = "ValidationError";
|
|
226
|
+
}
|
|
227
|
+
};
|
|
154
228
|
var GroupCardinalityError = class extends NoydbError {
|
|
155
229
|
/** The field being grouped on. */
|
|
156
230
|
field;
|
|
@@ -256,6 +330,244 @@ var DanglingReferenceError = class extends NoydbError {
|
|
|
256
330
|
}
|
|
257
331
|
};
|
|
258
332
|
|
|
333
|
+
// src/money/descriptor.ts
|
|
334
|
+
var MoneyUnsupportedError = class extends NoydbError {
|
|
335
|
+
constructor(field, message) {
|
|
336
|
+
super(
|
|
337
|
+
"MONEY_UNSUPPORTED",
|
|
338
|
+
message ?? `money: operation is not supported on field "${field}" \u2014 use sum() and count() and divide at the boundary`
|
|
339
|
+
);
|
|
340
|
+
this.field = field;
|
|
341
|
+
this.name = "MoneyUnsupportedError";
|
|
342
|
+
}
|
|
343
|
+
field;
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
// src/money/where.ts
|
|
347
|
+
function isMoneyValueObject(v) {
|
|
348
|
+
return typeof v === "object" && v !== null && "currency" in v;
|
|
349
|
+
}
|
|
350
|
+
function parseOperand(field, raw, desc) {
|
|
351
|
+
let amount;
|
|
352
|
+
let currency;
|
|
353
|
+
if (desc.mode === "fixed") {
|
|
354
|
+
currency = desc.fixedCurrency;
|
|
355
|
+
amount = raw;
|
|
356
|
+
} else if (isMoneyValueObject(raw)) {
|
|
357
|
+
currency = String(raw.currency);
|
|
358
|
+
amount = raw.amount;
|
|
359
|
+
} else {
|
|
360
|
+
const sole = desc.soleCurrency();
|
|
361
|
+
if (sole === void 0) {
|
|
362
|
+
throw new MoneyUnsupportedError(
|
|
363
|
+
`where("${field}"): field is multi-currency \u2014 compare against { amount, currency }, not a bare amount`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
currency = sole;
|
|
367
|
+
amount = raw;
|
|
368
|
+
}
|
|
369
|
+
if (typeof amount !== "number" && typeof amount !== "string") {
|
|
370
|
+
throw new MoneyUnsupportedError(
|
|
371
|
+
`where("${field}"): operand ${JSON.stringify(raw)} is not a money amount`
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
const r = parseToScaledInt(amount, desc.scaleFor(currency), desc.rounding);
|
|
375
|
+
if (!r.ok) {
|
|
376
|
+
throw new MoneyUnsupportedError(
|
|
377
|
+
`where("${field}"): operand ${JSON.stringify(amount)} is not a finite decimal`
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
return { scaled: r.value.toString(), currency };
|
|
381
|
+
}
|
|
382
|
+
function moneyFieldClause(field, op, value, desc) {
|
|
383
|
+
switch (op) {
|
|
384
|
+
case "==":
|
|
385
|
+
case "!=":
|
|
386
|
+
case "<":
|
|
387
|
+
case "<=":
|
|
388
|
+
case ">":
|
|
389
|
+
case ">=": {
|
|
390
|
+
const e = parseOperand(field, value, desc);
|
|
391
|
+
return withMoney(field, op, value, desc, [e]);
|
|
392
|
+
}
|
|
393
|
+
case "between": {
|
|
394
|
+
if (!Array.isArray(value) || value.length !== 2) {
|
|
395
|
+
throw new MoneyUnsupportedError(`where("${field}"): 'between' needs a [lo, hi] tuple`);
|
|
396
|
+
}
|
|
397
|
+
const lo = parseOperand(field, value[0], desc);
|
|
398
|
+
const hi = parseOperand(field, value[1], desc);
|
|
399
|
+
if (lo.currency !== hi.currency) {
|
|
400
|
+
throw new MoneyUnsupportedError(
|
|
401
|
+
`where("${field}"): 'between' bounds mix currencies (${lo.currency} vs ${hi.currency})`
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
return withMoney(field, op, value, desc, [lo, hi]);
|
|
405
|
+
}
|
|
406
|
+
case "in": {
|
|
407
|
+
if (!Array.isArray(value)) {
|
|
408
|
+
throw new MoneyUnsupportedError(`where("${field}"): 'in' needs an array of amounts`);
|
|
409
|
+
}
|
|
410
|
+
return withMoney(field, op, value, desc, value.map((v) => parseOperand(field, v, desc)));
|
|
411
|
+
}
|
|
412
|
+
default:
|
|
413
|
+
throw new MoneyUnsupportedError(
|
|
414
|
+
`where("${field}"): operator '${op}' is not supported on a money field`
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function withMoney(field, op, originalValue, desc, entries) {
|
|
419
|
+
const money = { mode: desc.mode, entries };
|
|
420
|
+
const value = desc.mode !== "fixed" ? originalValue : entries.length === 1 && op !== "in" && op !== "between" ? entries[0].scaled : entries.map((e) => e.scaled);
|
|
421
|
+
return { type: "field", field, op, value, money };
|
|
422
|
+
}
|
|
423
|
+
function readStored(actual, operand) {
|
|
424
|
+
let amount;
|
|
425
|
+
let currency;
|
|
426
|
+
if (operand.mode === "fixed") {
|
|
427
|
+
if (typeof actual !== "string" && typeof actual !== "number") return null;
|
|
428
|
+
amount = actual;
|
|
429
|
+
currency = operand.entries[0]?.currency ?? "";
|
|
430
|
+
} else {
|
|
431
|
+
if (!isMoneyValueObject(actual)) return null;
|
|
432
|
+
if (typeof actual.currency !== "string") return null;
|
|
433
|
+
amount = actual.amount;
|
|
434
|
+
currency = actual.currency;
|
|
435
|
+
}
|
|
436
|
+
if (typeof amount !== "string" && typeof amount !== "number") return null;
|
|
437
|
+
try {
|
|
438
|
+
return { scaled: BigInt(amount).toString(), currency };
|
|
439
|
+
} catch {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
function evaluateMoneyClause(actual, op, operand) {
|
|
444
|
+
const stored = readStored(actual, operand);
|
|
445
|
+
if (stored === null) return op === "!=";
|
|
446
|
+
const a = BigInt(stored.scaled);
|
|
447
|
+
if (op === "in") {
|
|
448
|
+
return operand.entries.some(
|
|
449
|
+
(e2) => e2.currency === stored.currency && BigInt(e2.scaled) === a
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
if (op === "between") {
|
|
453
|
+
const [lo, hi] = operand.entries;
|
|
454
|
+
if (!lo || !hi || lo.currency !== stored.currency) return false;
|
|
455
|
+
return a >= BigInt(lo.scaled) && a <= BigInt(hi.scaled);
|
|
456
|
+
}
|
|
457
|
+
const e = operand.entries[0];
|
|
458
|
+
if (!e) return false;
|
|
459
|
+
if (e.currency !== stored.currency) return op === "!=";
|
|
460
|
+
const b = BigInt(e.scaled);
|
|
461
|
+
switch (op) {
|
|
462
|
+
case "==":
|
|
463
|
+
return a === b;
|
|
464
|
+
case "!=":
|
|
465
|
+
return a !== b;
|
|
466
|
+
case "<":
|
|
467
|
+
return a < b;
|
|
468
|
+
case "<=":
|
|
469
|
+
return a <= b;
|
|
470
|
+
case ">":
|
|
471
|
+
return a > b;
|
|
472
|
+
case ">=":
|
|
473
|
+
return a >= b;
|
|
474
|
+
default:
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// src/query/predicate.ts
|
|
480
|
+
function readPath(record, path) {
|
|
481
|
+
if (record === null || record === void 0) return void 0;
|
|
482
|
+
if (!path.includes(".")) {
|
|
483
|
+
return record[path];
|
|
484
|
+
}
|
|
485
|
+
const segments = path.split(".");
|
|
486
|
+
let cursor = record;
|
|
487
|
+
for (const segment of segments) {
|
|
488
|
+
if (cursor === null || cursor === void 0) return void 0;
|
|
489
|
+
cursor = cursor[segment];
|
|
490
|
+
}
|
|
491
|
+
return cursor;
|
|
492
|
+
}
|
|
493
|
+
function evaluateFieldClause(record, clause) {
|
|
494
|
+
const actual = readPath(record, clause.field);
|
|
495
|
+
const { op, value } = clause;
|
|
496
|
+
if (clause.money) return evaluateMoneyClause(actual, op, clause.money);
|
|
497
|
+
switch (op) {
|
|
498
|
+
case "==":
|
|
499
|
+
return actual === value;
|
|
500
|
+
case "!=":
|
|
501
|
+
return actual !== value;
|
|
502
|
+
case "<":
|
|
503
|
+
return isComparable(actual, value) && actual < value;
|
|
504
|
+
case "<=":
|
|
505
|
+
return isComparable(actual, value) && actual <= value;
|
|
506
|
+
case ">":
|
|
507
|
+
return isComparable(actual, value) && actual > value;
|
|
508
|
+
case ">=":
|
|
509
|
+
return isComparable(actual, value) && actual >= value;
|
|
510
|
+
case "in":
|
|
511
|
+
return Array.isArray(value) && value.includes(actual);
|
|
512
|
+
case "contains":
|
|
513
|
+
if (typeof actual === "string") return typeof value === "string" && actual.includes(value);
|
|
514
|
+
if (Array.isArray(actual)) return actual.includes(value);
|
|
515
|
+
return false;
|
|
516
|
+
case "startsWith":
|
|
517
|
+
return typeof actual === "string" && typeof value === "string" && actual.startsWith(value);
|
|
518
|
+
case "between": {
|
|
519
|
+
if (!Array.isArray(value) || value.length !== 2) return false;
|
|
520
|
+
const [lo, hi] = value;
|
|
521
|
+
if (!isComparable(actual, lo) || !isComparable(actual, hi)) return false;
|
|
522
|
+
return actual >= lo && actual <= hi;
|
|
523
|
+
}
|
|
524
|
+
default: {
|
|
525
|
+
const _exhaustive = op;
|
|
526
|
+
void _exhaustive;
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
function isComparable(a, b) {
|
|
532
|
+
if (typeof a === "number" && typeof b === "number") return true;
|
|
533
|
+
if (typeof a === "string" && typeof b === "string") return true;
|
|
534
|
+
if (a instanceof Date && b instanceof Date) return true;
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
function evaluateClause(record, clause, fnRecord) {
|
|
538
|
+
switch (clause.type) {
|
|
539
|
+
case "field":
|
|
540
|
+
return evaluateFieldClause(record, clause);
|
|
541
|
+
case "filter":
|
|
542
|
+
return clause.fn(fnRecord !== void 0 ? fnRecord : record);
|
|
543
|
+
case "wherePredicate":
|
|
544
|
+
return clause.fn(fnRecord !== void 0 ? fnRecord : record, clause.ctx);
|
|
545
|
+
case "crossJoin":
|
|
546
|
+
throw new Error(
|
|
547
|
+
`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.`
|
|
548
|
+
);
|
|
549
|
+
case "group":
|
|
550
|
+
if (clause.op === "and") {
|
|
551
|
+
for (const child of clause.clauses) {
|
|
552
|
+
if (!evaluateClause(record, child, fnRecord)) return false;
|
|
553
|
+
}
|
|
554
|
+
return true;
|
|
555
|
+
} else {
|
|
556
|
+
for (const child of clause.clauses) {
|
|
557
|
+
if (evaluateClause(record, child, fnRecord)) return true;
|
|
558
|
+
}
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
function hasFnClause(clauses) {
|
|
564
|
+
for (const c of clauses) {
|
|
565
|
+
if (c.type === "filter" || c.type === "wherePredicate") return true;
|
|
566
|
+
if (c.type === "group" && hasFnClause(c.clauses)) return true;
|
|
567
|
+
}
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
|
|
259
571
|
// src/query/join.ts
|
|
260
572
|
var DEFAULT_JOIN_MAX_ROWS = 5e4;
|
|
261
573
|
var JOIN_WARN_FRACTION = 0.8;
|
|
@@ -501,97 +813,23 @@ var NO_AGGREGATE = {
|
|
|
501
813
|
}
|
|
502
814
|
};
|
|
503
815
|
|
|
504
|
-
// src/money/fixed-point.ts
|
|
505
|
-
function formatScaledInt(value, scale) {
|
|
506
|
-
const negative = value < 0n;
|
|
507
|
-
const abs = (negative ? -value : value).toString();
|
|
508
|
-
if (scale === 0) return (negative ? "-" : "") + abs;
|
|
509
|
-
const padded = abs.padStart(scale + 1, "0");
|
|
510
|
-
const cut = padded.length - scale;
|
|
511
|
-
const intPart = padded.slice(0, cut);
|
|
512
|
-
const fracPart = padded.slice(cut);
|
|
513
|
-
return (negative ? "-" : "") + intPart + "." + fracPart;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// src/money/iso4217.ts
|
|
517
|
-
var MINOR_UNITS = {
|
|
518
|
-
// 2-decimal majors
|
|
519
|
-
EUR: 2,
|
|
520
|
-
USD: 2,
|
|
521
|
-
GBP: 2,
|
|
522
|
-
CHF: 2,
|
|
523
|
-
CAD: 2,
|
|
524
|
-
AUD: 2,
|
|
525
|
-
NZD: 2,
|
|
526
|
-
SGD: 2,
|
|
527
|
-
HKD: 2,
|
|
528
|
-
CNY: 2,
|
|
529
|
-
INR: 2,
|
|
530
|
-
BRL: 2,
|
|
531
|
-
MXN: 2,
|
|
532
|
-
ZAR: 2,
|
|
533
|
-
RUB: 2,
|
|
534
|
-
TRY: 2,
|
|
535
|
-
PLN: 2,
|
|
536
|
-
SEK: 2,
|
|
537
|
-
NOK: 2,
|
|
538
|
-
DKK: 2,
|
|
539
|
-
CZK: 2,
|
|
540
|
-
HUF: 2,
|
|
541
|
-
RON: 2,
|
|
542
|
-
ILS: 2,
|
|
543
|
-
THB: 2,
|
|
544
|
-
PHP: 2,
|
|
545
|
-
MYR: 2,
|
|
546
|
-
IDR: 2,
|
|
547
|
-
AED: 2,
|
|
548
|
-
SAR: 2,
|
|
549
|
-
QAR: 2,
|
|
550
|
-
EGP: 2,
|
|
551
|
-
// 0-decimal
|
|
552
|
-
JPY: 0,
|
|
553
|
-
KRW: 0,
|
|
554
|
-
ISK: 0,
|
|
555
|
-
CLP: 0,
|
|
556
|
-
VND: 0,
|
|
557
|
-
XOF: 0,
|
|
558
|
-
XAF: 0,
|
|
559
|
-
PYG: 0,
|
|
560
|
-
// 3-decimal
|
|
561
|
-
BHD: 3,
|
|
562
|
-
KWD: 3,
|
|
563
|
-
OMR: 3,
|
|
564
|
-
TND: 3,
|
|
565
|
-
JOD: 3,
|
|
566
|
-
IQD: 3,
|
|
567
|
-
LYD: 3
|
|
568
|
-
};
|
|
569
|
-
function scaleForCurrency(code) {
|
|
570
|
-
const v = MINOR_UNITS[code];
|
|
571
|
-
return v === void 0 ? null : v;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
// src/money/descriptor.ts
|
|
575
|
-
var MoneyUnsupportedError = class extends NoydbError {
|
|
576
|
-
constructor(field, message) {
|
|
577
|
-
super(
|
|
578
|
-
"MONEY_UNSUPPORTED",
|
|
579
|
-
message ?? `money: operation is not supported on field "${field}" \u2014 use sum() and count() and divide at the boundary`
|
|
580
|
-
);
|
|
581
|
-
this.field = field;
|
|
582
|
-
this.name = "MoneyUnsupportedError";
|
|
583
|
-
}
|
|
584
|
-
field;
|
|
585
|
-
};
|
|
586
|
-
|
|
587
816
|
// src/money/money-reducer.ts
|
|
588
|
-
function
|
|
589
|
-
if (typeof v === "
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
817
|
+
function toScaledIntFromAny(v, scale) {
|
|
818
|
+
if (typeof v === "bigint") return v;
|
|
819
|
+
if (typeof v === "number") {
|
|
820
|
+
const r = parseToScaledInt(v, scale);
|
|
821
|
+
return r.ok ? r.value : null;
|
|
822
|
+
}
|
|
823
|
+
if (typeof v === "string") {
|
|
824
|
+
if (!v.includes(".")) {
|
|
825
|
+
try {
|
|
826
|
+
return BigInt(v);
|
|
827
|
+
} catch {
|
|
828
|
+
return null;
|
|
829
|
+
}
|
|
594
830
|
}
|
|
831
|
+
const r = parseToScaledInt(v, scale);
|
|
832
|
+
return r.ok ? r.value : null;
|
|
595
833
|
}
|
|
596
834
|
return null;
|
|
597
835
|
}
|
|
@@ -599,13 +837,15 @@ function readMoney(record, field, desc) {
|
|
|
599
837
|
const raw = readPath(record, field);
|
|
600
838
|
if (raw === null || raw === void 0) return null;
|
|
601
839
|
if (desc.mode === "fixed") {
|
|
602
|
-
const
|
|
603
|
-
|
|
840
|
+
const cur = desc.fixedCurrency;
|
|
841
|
+
const value2 = toScaledIntFromAny(raw, desc.scaleFor(cur));
|
|
842
|
+
return value2 === null ? null : { currency: cur, value: value2 };
|
|
604
843
|
}
|
|
605
844
|
if (typeof raw !== "object") return null;
|
|
606
845
|
const o = raw;
|
|
607
846
|
if (typeof o.currency !== "string") return null;
|
|
608
|
-
const
|
|
847
|
+
const scale = desc.allows(o.currency) ? desc.scaleFor(o.currency) : 0;
|
|
848
|
+
const value = toScaledIntFromAny(o.amount, scale);
|
|
609
849
|
return value === null ? null : { currency: o.currency, value };
|
|
610
850
|
}
|
|
611
851
|
function targetScaleFor(desc, currency) {
|
|
@@ -758,8 +998,107 @@ function wrapMoneyReducers(spec, moneyFields) {
|
|
|
758
998
|
return changed ? out : spec;
|
|
759
999
|
}
|
|
760
1000
|
|
|
1001
|
+
// src/money/paths.ts
|
|
1002
|
+
var SEGMENT_RE = /^(\*|[^.[\]*]+)(\[\])?$/;
|
|
1003
|
+
var parseCache = /* @__PURE__ */ new Map();
|
|
1004
|
+
function parseMoneyPath(path) {
|
|
1005
|
+
const cached = parseCache.get(path);
|
|
1006
|
+
if (cached) return cached;
|
|
1007
|
+
if (typeof path !== "string" || path.length === 0) {
|
|
1008
|
+
throw new ValidationError("moneyFields: path must be a non-empty string");
|
|
1009
|
+
}
|
|
1010
|
+
const segments = [];
|
|
1011
|
+
for (const part of path.split(".")) {
|
|
1012
|
+
const m = SEGMENT_RE.exec(part);
|
|
1013
|
+
if (!m) {
|
|
1014
|
+
throw new ValidationError(
|
|
1015
|
+
`moneyFields: invalid path "${path}" \u2014 segment "${part}" must be a key, "key[]", "*", or "*[]"`
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
const array = m[2] === "[]";
|
|
1019
|
+
segments.push(
|
|
1020
|
+
m[1] === "*" ? { kind: "wildcard", array } : { kind: "key", key: m[1], array }
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
parseCache.set(path, segments);
|
|
1024
|
+
return segments;
|
|
1025
|
+
}
|
|
1026
|
+
function isSimpleMoneyPath(path) {
|
|
1027
|
+
return !path.includes(".") && !path.includes("[") && !path.includes("*");
|
|
1028
|
+
}
|
|
1029
|
+
function transformAtMoneyPath(node, path, segments, index, visit, lenient) {
|
|
1030
|
+
if (node === null || node === void 0) return node;
|
|
1031
|
+
const seg = segments[index];
|
|
1032
|
+
const last = index === segments.length - 1;
|
|
1033
|
+
if (seg.kind === "key") {
|
|
1034
|
+
if (typeof node !== "object" || Array.isArray(node)) {
|
|
1035
|
+
if (lenient) return node;
|
|
1036
|
+
throw new ValidationError(
|
|
1037
|
+
`moneyFields: path "${path}" expected an object at segment "${seg.key}", got ${Array.isArray(node) ? "an array" : typeof node}`
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
const obj2 = node;
|
|
1041
|
+
if (!(seg.key in obj2) || obj2[seg.key] === null || obj2[seg.key] === void 0) return node;
|
|
1042
|
+
if (seg.array) {
|
|
1043
|
+
const arr = obj2[seg.key];
|
|
1044
|
+
if (!Array.isArray(arr)) {
|
|
1045
|
+
if (lenient) return node;
|
|
1046
|
+
throw new ValidationError(
|
|
1047
|
+
`moneyFields: path "${path}" declares "${seg.key}[]" but the value is not an array`
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
const cloned = [...arr];
|
|
1051
|
+
if (last) {
|
|
1052
|
+
for (let i = 0; i < cloned.length; i++) visit(cloned, i);
|
|
1053
|
+
} else {
|
|
1054
|
+
for (let i = 0; i < cloned.length; i++) {
|
|
1055
|
+
cloned[i] = transformAtMoneyPath(cloned[i], path, segments, index + 1, visit, lenient);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
return { ...obj2, [seg.key]: cloned };
|
|
1059
|
+
}
|
|
1060
|
+
const clone2 = { ...obj2 };
|
|
1061
|
+
if (last) {
|
|
1062
|
+
visit(clone2, seg.key);
|
|
1063
|
+
} else {
|
|
1064
|
+
clone2[seg.key] = transformAtMoneyPath(clone2[seg.key], path, segments, index + 1, visit, lenient);
|
|
1065
|
+
}
|
|
1066
|
+
return clone2;
|
|
1067
|
+
}
|
|
1068
|
+
if (seg.array) {
|
|
1069
|
+
if (!Array.isArray(node)) {
|
|
1070
|
+
if (lenient) return node;
|
|
1071
|
+
throw new ValidationError(`moneyFields: path "${path}" declares "*[]" but the value is not an array`);
|
|
1072
|
+
}
|
|
1073
|
+
const cloned = [...node];
|
|
1074
|
+
if (last) {
|
|
1075
|
+
for (let i = 0; i < cloned.length; i++) visit(cloned, i);
|
|
1076
|
+
} else {
|
|
1077
|
+
for (let i = 0; i < cloned.length; i++) {
|
|
1078
|
+
cloned[i] = transformAtMoneyPath(cloned[i], path, segments, index + 1, visit, lenient);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
return cloned;
|
|
1082
|
+
}
|
|
1083
|
+
if (typeof node !== "object" || Array.isArray(node)) {
|
|
1084
|
+
if (lenient) return node;
|
|
1085
|
+
throw new ValidationError(
|
|
1086
|
+
`moneyFields: path "${path}" applies "*" to a non-object (${Array.isArray(node) ? 'array \u2014 use "*[]"' : typeof node})`
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
const obj = node;
|
|
1090
|
+
const clone = { ...obj };
|
|
1091
|
+
for (const key of Object.keys(obj)) {
|
|
1092
|
+
const v = clone[key];
|
|
1093
|
+
if (v === null || v === void 0) continue;
|
|
1094
|
+
if (last) visit(clone, key);
|
|
1095
|
+
else clone[key] = transformAtMoneyPath(v, path, segments, index + 1, visit, lenient);
|
|
1096
|
+
}
|
|
1097
|
+
return clone;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
761
1100
|
// src/money/normalize.ts
|
|
762
|
-
function
|
|
1101
|
+
function isMoneyValueObject2(v) {
|
|
763
1102
|
return typeof v === "object" && v !== null && "currency" in v;
|
|
764
1103
|
}
|
|
765
1104
|
function formatCurrency(decimal, currency, scale, locale) {
|
|
@@ -771,33 +1110,70 @@ function formatCurrency(decimal, currency, scale, locale) {
|
|
|
771
1110
|
});
|
|
772
1111
|
return fmt.format(decimal);
|
|
773
1112
|
}
|
|
1113
|
+
function decodeValue(stored, desc) {
|
|
1114
|
+
let currency;
|
|
1115
|
+
let scaledIntString;
|
|
1116
|
+
if (desc.mode === "fixed") {
|
|
1117
|
+
if (typeof stored !== "string" && typeof stored !== "number") return null;
|
|
1118
|
+
currency = desc.fixedCurrency;
|
|
1119
|
+
scaledIntString = String(stored);
|
|
1120
|
+
} else {
|
|
1121
|
+
if (!isMoneyValueObject2(stored)) return null;
|
|
1122
|
+
const amount = stored.amount;
|
|
1123
|
+
if (typeof stored.currency !== "string" || typeof amount !== "string" && typeof amount !== "number") return null;
|
|
1124
|
+
currency = stored.currency;
|
|
1125
|
+
scaledIntString = String(amount);
|
|
1126
|
+
}
|
|
1127
|
+
const scale = desc.scaleFor(currency);
|
|
1128
|
+
let decimal;
|
|
1129
|
+
try {
|
|
1130
|
+
decimal = formatScaledInt(BigInt(scaledIntString), scale);
|
|
1131
|
+
} catch {
|
|
1132
|
+
return null;
|
|
1133
|
+
}
|
|
1134
|
+
return {
|
|
1135
|
+
decoded: desc.mode === "fixed" ? decimal : { amount: decimal, currency },
|
|
1136
|
+
decimal,
|
|
1137
|
+
currency,
|
|
1138
|
+
scale
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
774
1141
|
function decodeMoneyFields(record, moneyFields, locale) {
|
|
775
|
-
|
|
1142
|
+
let out = { ...record };
|
|
776
1143
|
const format = locale !== "raw";
|
|
777
1144
|
const fmtLocale = typeof locale === "string" && locale !== "raw" ? locale : "en-US";
|
|
778
|
-
for (const [
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
1145
|
+
for (const [path, desc] of Object.entries(moneyFields)) {
|
|
1146
|
+
if (isSimpleMoneyPath(path)) {
|
|
1147
|
+
const stored = out[path];
|
|
1148
|
+
if (stored === null || stored === void 0) continue;
|
|
1149
|
+
const r = decodeValue(stored, desc);
|
|
1150
|
+
if (r === null) continue;
|
|
1151
|
+
out[path] = r.decoded;
|
|
1152
|
+
if (format) {
|
|
1153
|
+
out[`${path}Formatted`] = formatCurrency(r.decimal, r.currency, r.scale, fmtLocale);
|
|
1154
|
+
out[`${path}Number`] = Number(r.decimal);
|
|
1155
|
+
}
|
|
1156
|
+
continue;
|
|
1157
|
+
}
|
|
1158
|
+
out = transformAtMoneyPath(
|
|
1159
|
+
out,
|
|
1160
|
+
path,
|
|
1161
|
+
parseMoneyPath(path),
|
|
1162
|
+
0,
|
|
1163
|
+
(container, key) => {
|
|
1164
|
+
const stored = container[key];
|
|
1165
|
+
if (stored === null || stored === void 0) return;
|
|
1166
|
+
const r = decodeValue(stored, desc);
|
|
1167
|
+
if (r === null) return;
|
|
1168
|
+
container[key] = r.decoded;
|
|
1169
|
+
if (format && typeof key === "string" && !Array.isArray(container)) {
|
|
1170
|
+
container[`${key}Formatted`] = formatCurrency(r.decimal, r.currency, r.scale, fmtLocale);
|
|
1171
|
+
container[`${key}Number`] = Number(r.decimal);
|
|
1172
|
+
}
|
|
1173
|
+
},
|
|
1174
|
+
/* lenient */
|
|
1175
|
+
true
|
|
1176
|
+
);
|
|
801
1177
|
}
|
|
802
1178
|
return out;
|
|
803
1179
|
}
|
|
@@ -894,9 +1270,18 @@ var Query = class _Query {
|
|
|
894
1270
|
this.predicates
|
|
895
1271
|
);
|
|
896
1272
|
}
|
|
897
|
-
/**
|
|
1273
|
+
/**
|
|
1274
|
+
* Add a field comparison. Multiple where() calls are AND-combined.
|
|
1275
|
+
*
|
|
1276
|
+
* A declared money field compares in MAJOR units (#336): the operand
|
|
1277
|
+
* (`10000`, `'10000.00'`, or `{ amount, currency }` in multi mode) is
|
|
1278
|
+
* quantized into stored scaled-int space at build time and evaluated
|
|
1279
|
+
* BigInt-exact per record. A malformed operand or a string operator
|
|
1280
|
+
* (`contains`/`startsWith`) throws here, at the call site.
|
|
1281
|
+
*/
|
|
898
1282
|
where(field, op, value) {
|
|
899
|
-
const
|
|
1283
|
+
const desc = this.source.moneyFields?.[field];
|
|
1284
|
+
const clause = desc ? moneyFieldClause(field, op, value, desc) : { type: "field", field, op, value };
|
|
900
1285
|
return new _Query(
|
|
901
1286
|
this.source,
|
|
902
1287
|
{ ...this.plan, clauses: [...this.plan.clauses, clause] },
|
|
@@ -1222,7 +1607,7 @@ var Query = class _Query {
|
|
|
1222
1607
|
}
|
|
1223
1608
|
const { candidates, remainingClauses } = candidateRecords(this.source, this.plan.clauses);
|
|
1224
1609
|
if (remainingClauses.length === 0) return candidates.length;
|
|
1225
|
-
return filterRecords(candidates, remainingClauses).length;
|
|
1610
|
+
return filterRecords(candidates, remainingClauses, fnViewDecoder(this.source)).length;
|
|
1226
1611
|
}
|
|
1227
1612
|
/**
|
|
1228
1613
|
* Reduce the matching records through a named set of reducers.
|
|
@@ -1279,7 +1664,7 @@ var Query = class _Query {
|
|
|
1279
1664
|
return executeClausePipeline(source, clauses, joinCtx);
|
|
1280
1665
|
}
|
|
1281
1666
|
const { candidates, remainingClauses } = candidateRecords(source, clauses);
|
|
1282
|
-
return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses);
|
|
1667
|
+
return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
|
|
1283
1668
|
};
|
|
1284
1669
|
const upstreams = [];
|
|
1285
1670
|
if (source.subscribe) {
|
|
@@ -1302,7 +1687,7 @@ var Query = class _Query {
|
|
|
1302
1687
|
return executeClausePipeline(source, clauses, joinCtx);
|
|
1303
1688
|
}
|
|
1304
1689
|
const { candidates, remainingClauses } = candidateRecords(source, clauses);
|
|
1305
|
-
return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses);
|
|
1690
|
+
return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
|
|
1306
1691
|
};
|
|
1307
1692
|
const upstreams = [];
|
|
1308
1693
|
if (source.subscribe) {
|
|
@@ -1454,7 +1839,7 @@ function executePlanWithSource(source, plan, joinContext) {
|
|
|
1454
1839
|
result = executeClausePipeline(source, plan.clauses, joinContext);
|
|
1455
1840
|
} else {
|
|
1456
1841
|
const { candidates, remainingClauses } = candidateRecords(source, plan.clauses);
|
|
1457
|
-
result = remainingClauses.length === 0 ? [...candidates] : filterRecords(candidates, remainingClauses);
|
|
1842
|
+
result = remainingClauses.length === 0 ? [...candidates] : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
|
|
1458
1843
|
}
|
|
1459
1844
|
if (plan.orderBy.length > 0) {
|
|
1460
1845
|
result = sortRecords(result, plan.orderBy);
|
|
@@ -1477,6 +1862,7 @@ function candidateRecords(source, clauses) {
|
|
|
1477
1862
|
const clause = clauses[i];
|
|
1478
1863
|
if (clause.type !== "field") continue;
|
|
1479
1864
|
if (!indexes.has(clause.field)) continue;
|
|
1865
|
+
if (clause.money?.mode === "multi") continue;
|
|
1480
1866
|
let ids = null;
|
|
1481
1867
|
if (clause.op === "==") {
|
|
1482
1868
|
ids = indexes.lookupEqual(clause.field, clause.value);
|
|
@@ -1522,13 +1908,20 @@ function executePlan(records, plan) {
|
|
|
1522
1908
|
}
|
|
1523
1909
|
return result;
|
|
1524
1910
|
}
|
|
1525
|
-
function
|
|
1911
|
+
function fnViewDecoder(source) {
|
|
1912
|
+
const mf = source.moneyFields;
|
|
1913
|
+
if (!mf || Object.keys(mf).length === 0) return void 0;
|
|
1914
|
+
return (r) => decodeMoneyFields(r, mf, "raw");
|
|
1915
|
+
}
|
|
1916
|
+
function filterRecords(records, clauses, decodeForFns) {
|
|
1526
1917
|
if (clauses.length === 0) return [...records];
|
|
1918
|
+
const needsFnView = decodeForFns !== void 0 && hasFnClause(clauses);
|
|
1527
1919
|
const out = [];
|
|
1528
1920
|
for (const r of records) {
|
|
1921
|
+
const fnView = needsFnView ? decodeForFns(r) : void 0;
|
|
1529
1922
|
let matches = true;
|
|
1530
1923
|
for (const clause of clauses) {
|
|
1531
|
-
if (!evaluateClause(r, clause)) {
|
|
1924
|
+
if (!evaluateClause(r, clause, fnView)) {
|
|
1532
1925
|
matches = false;
|
|
1533
1926
|
break;
|
|
1534
1927
|
}
|
|
@@ -1540,10 +1933,11 @@ function filterRecords(records, clauses) {
|
|
|
1540
1933
|
function executeClausePipeline(source, clauses, joinContext) {
|
|
1541
1934
|
let rel = [...source.snapshot()];
|
|
1542
1935
|
let filterBatch = [];
|
|
1936
|
+
const decodeForFns = fnViewDecoder(source);
|
|
1543
1937
|
for (const clause of clauses) {
|
|
1544
1938
|
if (clause.type === "crossJoin") {
|
|
1545
1939
|
if (filterBatch.length > 0) {
|
|
1546
|
-
rel = filterRecords(rel, filterBatch);
|
|
1940
|
+
rel = filterRecords(rel, filterBatch, decodeForFns);
|
|
1547
1941
|
filterBatch = [];
|
|
1548
1942
|
}
|
|
1549
1943
|
const rightSource = joinContext.resolveSource(clause.target);
|
|
@@ -1556,7 +1950,7 @@ function executeClausePipeline(source, clauses, joinContext) {
|
|
|
1556
1950
|
}
|
|
1557
1951
|
}
|
|
1558
1952
|
if (filterBatch.length > 0) {
|
|
1559
|
-
rel = filterRecords(rel, filterBatch);
|
|
1953
|
+
rel = filterRecords(rel, filterBatch, decodeForFns);
|
|
1560
1954
|
}
|
|
1561
1955
|
return rel;
|
|
1562
1956
|
}
|
|
@@ -2165,11 +2559,14 @@ var GroupedQueryN = class extends GroupedQueryBase {
|
|
|
2165
2559
|
);
|
|
2166
2560
|
}
|
|
2167
2561
|
};
|
|
2168
|
-
function groupAndReduce(records, fieldOrFields, spec) {
|
|
2562
|
+
function groupAndReduce(records, fieldOrFields, spec, moneyFields) {
|
|
2169
2563
|
const fields = typeof fieldOrFields === "string" ? [fieldOrFields] : fieldOrFields;
|
|
2170
2564
|
if (fields.length === 0) {
|
|
2171
2565
|
throw new Error(".groupBy() requires at least one field");
|
|
2172
2566
|
}
|
|
2567
|
+
if (moneyFields) {
|
|
2568
|
+
spec = wrapMoneyReducers(spec, moneyFields);
|
|
2569
|
+
}
|
|
2173
2570
|
const buckets = /* @__PURE__ */ new Map();
|
|
2174
2571
|
const fieldLabel = fields.length === 1 ? fields[0] : `[${fields.join(", ")}]`;
|
|
2175
2572
|
for (const record of records) {
|
|
@@ -2351,7 +2748,8 @@ var ScanBuilder = class _ScanBuilder {
|
|
|
2351
2748
|
* evaluates clauses per record in O(1) per clause.
|
|
2352
2749
|
*/
|
|
2353
2750
|
where(field, op, value) {
|
|
2354
|
-
const
|
|
2751
|
+
const desc = this.moneyFields?.[field];
|
|
2752
|
+
const clause = desc ? moneyFieldClause(field, op, value, desc) : { type: "field", field, op, value };
|
|
2355
2753
|
return new _ScanBuilder(
|
|
2356
2754
|
this.pageProvider,
|
|
2357
2755
|
this.pageSize,
|
|
@@ -2697,8 +3095,9 @@ var ScanBuilder = class _ScanBuilder {
|
|
|
2697
3095
|
*/
|
|
2698
3096
|
recordMatches(record) {
|
|
2699
3097
|
if (this.clauses.length === 0) return true;
|
|
3098
|
+
const fnView = this.moneyFields && Object.keys(this.moneyFields).length > 0 && hasFnClause(this.clauses) ? this.decodeMoney(record) : void 0;
|
|
2700
3099
|
for (const clause of this.clauses) {
|
|
2701
|
-
if (!evaluateClause(record, clause)) return false;
|
|
3100
|
+
if (!evaluateClause(record, clause, fnView)) return false;
|
|
2702
3101
|
}
|
|
2703
3102
|
return true;
|
|
2704
3103
|
}
|