@amboras-dev/money 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +401 -2
- package/dist/ai.cjs +185 -0
- package/dist/ai.d.cts +97 -0
- package/dist/ai.d.ts +97 -0
- package/dist/ai.js +175 -0
- package/dist/allocate-CZRg4TOP.cjs +222 -0
- package/dist/allocate-X7_jAtOj.js +199 -0
- package/dist/amount-BV0O-aN8.cjs +241 -0
- package/dist/amount-BoYBMgK9.js +200 -0
- package/dist/arithmetic-BhNwisjb.js +132 -0
- package/dist/arithmetic-DjT4Kzgt.cjs +233 -0
- package/dist/billing.cjs +184 -0
- package/dist/billing.d.cts +81 -0
- package/dist/billing.d.ts +81 -0
- package/dist/billing.js +175 -0
- package/dist/commerce.cjs +536 -0
- package/dist/commerce.d.cts +293 -0
- package/dist/commerce.d.ts +293 -0
- package/dist/commerce.js +516 -0
- package/dist/format-BdPnijvn.js +99 -0
- package/dist/format-DLhno7eX.cjs +128 -0
- package/dist/fx.cjs +407 -0
- package/dist/fx.d.cts +157 -0
- package/dist/fx.d.ts +157 -0
- package/dist/fx.js +374 -0
- package/dist/index.cjs +352 -0
- package/dist/index.d.cts +300 -0
- package/dist/index.d.ts +300 -0
- package/dist/index.js +276 -0
- package/dist/input.cjs +163 -0
- package/dist/input.d.cts +203 -0
- package/dist/input.d.ts +203 -0
- package/dist/input.js +153 -0
- package/dist/iso-minor-CqkWDxHS.js +48 -0
- package/dist/iso-minor-wSwJN57T.cjs +65 -0
- package/dist/medusa-4K8N1pUc.d.cts +87 -0
- package/dist/medusa-DBXJXXvF.d.ts +87 -0
- package/dist/medusa-DqJY1qpj.js +116 -0
- package/dist/medusa-lRY9xlsf.cjs +175 -0
- package/dist/money-B-3aFjNJ.d.cts +248 -0
- package/dist/money-B-3aFjNJ.d.ts +248 -0
- package/dist/money-B3wH_Z4S.cjs +3990 -0
- package/dist/money-WhnGiI00.js +3583 -0
- package/dist/number-text-BWu67jUq.cjs +145 -0
- package/dist/number-text-DE-q4jO5.js +134 -0
- package/dist/own-D7544D3k.js +4 -0
- package/dist/own-oP9DXFWv.cjs +9 -0
- package/dist/percent-C0InC2t2.cjs +43 -0
- package/dist/percent-D1VB9xXG.js +20 -0
- package/dist/rate-CVeMii4a.cjs +217 -0
- package/dist/rate-DBPiYgT1.d.ts +63 -0
- package/dist/rate-DicStBue.js +152 -0
- package/dist/rate-tqK8ijG0.d.cts +63 -0
- package/dist/reporting-CG-ai2sM.d.cts +31 -0
- package/dist/reporting-tpGJbqYp.d.ts +31 -0
- package/dist/retail-1mu888J1.js +69 -0
- package/dist/retail-C_BPLvMj.d.cts +31 -0
- package/dist/retail-Cml8YSKg.cjs +86 -0
- package/dist/retail-DRUTTQoJ.d.ts +31 -0
- package/dist/rounding-DHBtNCHG.cjs +79 -0
- package/dist/rounding-g9rvye0k.js +50 -0
- package/dist/sql.cjs +290 -0
- package/dist/sql.d.cts +147 -0
- package/dist/sql.d.ts +147 -0
- package/dist/sql.js +271 -0
- package/dist/testing.cjs +61995 -0
- package/dist/testing.d.cts +125 -0
- package/dist/testing.d.ts +125 -0
- package/dist/testing.js +61986 -0
- package/dist/wire-Bhuut8cW.cjs +2097 -0
- package/dist/wire-CaUN9Vf_.d.ts +309 -0
- package/dist/wire-sW0yzqth.js +1888 -0
- package/dist/wire-xjYXv4go.d.cts +309 -0
- package/dist/wire.cjs +37 -0
- package/dist/wire.d.cts +2 -0
- package/dist/wire.d.ts +2 -0
- package/dist/wire.js +2 -0
- package/package.json +158 -3
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
const require_money = require("./money-B3wH_Z4S.cjs");
|
|
2
|
+
//#region src/internal/number-text.ts
|
|
3
|
+
const MINUS = /^[-−]/;
|
|
4
|
+
const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5
|
+
const unparseable = Object.freeze({ reason: "unparseable" });
|
|
6
|
+
/**
|
|
7
|
+
* THE digit grouping a whole part may use, as a regex source over the separator source `s`:
|
|
8
|
+
* thousands (`\d{1,3}(s\d{3})+`: `1,234,567`) or Indian lakh (`\d{1,2}(s\d{2})+s\d{3}`:
|
|
9
|
+
* three digits on the right, then pairs: `1,00,000`, `12,34,567`). Valid only when every
|
|
10
|
+
* group fits one of the two, so `1,2345` and `12,3,456` are not grouping.
|
|
11
|
+
*/
|
|
12
|
+
function digitGroupingSource(s) {
|
|
13
|
+
return `\\d{1,3}(?:${s}\\d{3})+|\\d{1,2}(?:${s}\\d{2})+${s}\\d{3}`;
|
|
14
|
+
}
|
|
15
|
+
const GROUPED = new RegExp(`^(?:${digitGroupingSource(",")})$`);
|
|
16
|
+
/** Whether `groups`, the digit runs between the separators read as grouping, are valid digit grouping. */
|
|
17
|
+
const isDigitGrouping = (groups) => GROUPED.test(groups.join(","));
|
|
18
|
+
function stripCurrency(text, info) {
|
|
19
|
+
return require_money.currencyMarks(info).sort((a, b) => b.length - a.length).reduce((rest, token) => rest.replace(new RegExp(escapeRegExp(token), "gi"), ""), text);
|
|
20
|
+
}
|
|
21
|
+
function value(negative, whole, fraction, decimalMark) {
|
|
22
|
+
const digits = whole + fraction;
|
|
23
|
+
if (digits.length > 400) return unparseable;
|
|
24
|
+
const units = BigInt(digits);
|
|
25
|
+
return {
|
|
26
|
+
sign: negative && units !== 0n ? -1 : 1,
|
|
27
|
+
units,
|
|
28
|
+
scale: fraction.length,
|
|
29
|
+
decimalMark
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** `'auto'`: decide which of `.` and `,` is the decimal separator (see `NumberTextOptions.decimalSeparator`). */
|
|
33
|
+
function readAuto(negative, body, info, rounding) {
|
|
34
|
+
if (!/^[\d.,]+$/.test(body) || !/\d/.test(body)) return unparseable;
|
|
35
|
+
if (info?.storageDigits === 0) {
|
|
36
|
+
const groups = body.split(/[.,]/);
|
|
37
|
+
const last = groups.length > 1 ? groups[groups.length - 1] : null;
|
|
38
|
+
const isDecimal = last !== null && last.length !== 3;
|
|
39
|
+
const decimalMark = isDecimal ? body[body.length - last.length - 1] : null;
|
|
40
|
+
const wholeGroups = isDecimal ? groups.slice(0, -1) : groups;
|
|
41
|
+
if (wholeGroups.length > 1 && (!isDigitGrouping(wholeGroups) || last === "")) return unparseable;
|
|
42
|
+
const read = value(negative, wholeGroups.join("") || "0", isDecimal ? last : "", decimalMark);
|
|
43
|
+
if ("reason" in read || rounding === null) return read;
|
|
44
|
+
const rounded = require_money.roundToScale({
|
|
45
|
+
units: BigInt(read.sign) * read.units,
|
|
46
|
+
scale: read.scale
|
|
47
|
+
}, 0, rounding);
|
|
48
|
+
const units = rounded.units < 0n ? -rounded.units : rounded.units;
|
|
49
|
+
return {
|
|
50
|
+
sign: read.sign < 0 && units !== 0n ? -1 : 1,
|
|
51
|
+
units,
|
|
52
|
+
scale: 0,
|
|
53
|
+
decimalMark
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const lastDot = body.lastIndexOf(".");
|
|
57
|
+
const lastComma = body.lastIndexOf(",");
|
|
58
|
+
if (lastDot >= 0 && lastComma >= 0) {
|
|
59
|
+
const at = Math.max(lastDot, lastComma);
|
|
60
|
+
const groups = body.slice(0, at).split(/[.,]/);
|
|
61
|
+
const fraction = body.slice(at + 1);
|
|
62
|
+
if (!isDigitGrouping(groups) || fraction === "") return unparseable;
|
|
63
|
+
return value(negative, groups.join(""), fraction, body[at]);
|
|
64
|
+
}
|
|
65
|
+
const mark = lastDot >= 0 ? "." : lastComma >= 0 ? "," : null;
|
|
66
|
+
if (mark === null) return value(negative, body, "", null);
|
|
67
|
+
const at = body.indexOf(mark);
|
|
68
|
+
if (at !== body.lastIndexOf(mark)) {
|
|
69
|
+
const groups = body.split(mark);
|
|
70
|
+
return isDigitGrouping(groups) ? value(negative, groups.join(""), "", null) : unparseable;
|
|
71
|
+
}
|
|
72
|
+
const whole = body.slice(0, at);
|
|
73
|
+
const fraction = body.slice(at + 1);
|
|
74
|
+
if (fraction.length !== 3 || /^0*$/.test(whole) || isThreeDecimalMark(mark, info)) return value(negative, whole || "0", fraction, mark);
|
|
75
|
+
return isDigitGrouping([whole, fraction]) ? value(negative, whole + fraction, "", null) : unparseable;
|
|
76
|
+
}
|
|
77
|
+
/** Whether `mark` is the decimal mark of a currency displaying three decimals: `','` for TND (`commaDecimal`), else `'.'`. */
|
|
78
|
+
function isThreeDecimalMark(mark, info) {
|
|
79
|
+
return info?.displayDigits === 3 && mark === (info.commaDecimal ? "," : ".");
|
|
80
|
+
}
|
|
81
|
+
/** An explicit decimal separator, with grouping validated in 3-digit groups. */
|
|
82
|
+
function readExplicit(negative, body, decimal, group, info) {
|
|
83
|
+
const d = escapeRegExp(decimal);
|
|
84
|
+
if (info?.storageDigits === 0 && group !== null && new RegExp(`^\\d{1,3}(?:${d}\\d{3})+$`).test(body)) return value(negative, body.split(decimal).join(""), "", null);
|
|
85
|
+
const [whole, fraction, extra] = body.split(decimal);
|
|
86
|
+
if (extra !== void 0 || fraction === "" || fraction !== void 0 && !/^\d+$/.test(fraction)) return unparseable;
|
|
87
|
+
const decimalMark = fraction === void 0 ? null : decimal;
|
|
88
|
+
if (/^\d+$/.test(whole)) return value(negative, whole, fraction ?? "", decimalMark);
|
|
89
|
+
if (whole === "" && group === null && fraction !== void 0) return value(negative, "0", fraction, decimalMark);
|
|
90
|
+
if (group === null || !new RegExp(`^(?:${digitGroupingSource(escapeRegExp(group))})$`).test(whole)) return unparseable;
|
|
91
|
+
return value(negative, whole.split(group).join(""), fraction ?? "", decimalMark);
|
|
92
|
+
}
|
|
93
|
+
/** The first separator written between two digits that is not the decimal mark. */
|
|
94
|
+
function groupMarkOf(written, decimalMark) {
|
|
95
|
+
for (const [, mark] of written.matchAll(/\d(\D)(?=\d)/g)) if (mark !== decimalMark) return mark;
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Amount text to sign, digits and scale, exactly; or why not (`'empty'`, `'unparseable'`).
|
|
100
|
+
* Whitespace of every kind (NBSP, thin, narrow NBSP) is grouping and removed; in `'auto'`
|
|
101
|
+
* mode apostrophes are too. A leading `-` or U+2212 is the sign. No exponent, no hex; an
|
|
102
|
+
* explicit separator also refuses `'1.'` and, unless `groupSeparator` is null, `'.5'` ('auto'
|
|
103
|
+
* reads them as 1 and 0.5). Never throws for bad text; an unknown `currency` throws `currency_invalid`.
|
|
104
|
+
*/
|
|
105
|
+
function readNumberText(text, options = {}) {
|
|
106
|
+
const info = options.currency === void 0 ? null : require_money.resolveCurrency(options.currency, { allowHistoric: true });
|
|
107
|
+
const decimal = options.decimalSeparator ?? ".";
|
|
108
|
+
const auto = decimal === "auto";
|
|
109
|
+
const group = options.groupSeparator === void 0 ? decimal === "." ? "," : decimal === "," ? "." : null : options.groupSeparator;
|
|
110
|
+
if (!auto && (decimal.length !== 1 || group === decimal)) throw new TypeError(`Invalid separators: decimal ${require_money.describeValue(decimal)}, group ${require_money.describeValue(group)}`);
|
|
111
|
+
if (typeof text !== "string") return unparseable;
|
|
112
|
+
const written = info ? stripCurrency(text.trim(), info) : text.trim();
|
|
113
|
+
let rest = written.replace(auto ? /[\s'’]/g : /\s/g, "");
|
|
114
|
+
if (rest === "") return { reason: "empty" };
|
|
115
|
+
let negative = false;
|
|
116
|
+
if (options.allowTrailingText) {
|
|
117
|
+
const separators = auto ? ".," : escapeRegExp(`${decimal}${group ?? ""}`);
|
|
118
|
+
const run = new RegExp(`([-\\u2212]?)(\\d[\\d${separators}]*)`).exec(rest);
|
|
119
|
+
if (!run) return unparseable;
|
|
120
|
+
negative = run[1] !== "";
|
|
121
|
+
rest = run[2].replace(new RegExp(`[${separators}]+$`), "");
|
|
122
|
+
} else if (MINUS.test(rest)) {
|
|
123
|
+
negative = true;
|
|
124
|
+
rest = rest.slice(1);
|
|
125
|
+
}
|
|
126
|
+
const read = auto ? readAuto(negative, rest, info, options.keepFraction ? null : options.rounding ?? "half-up") : readExplicit(negative, rest, decimal, group, info);
|
|
127
|
+
if ("reason" in read) return read;
|
|
128
|
+
return {
|
|
129
|
+
...read,
|
|
130
|
+
groupMark: groupMarkOf(written, read.decimalMark)
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
Object.defineProperty(exports, "digitGroupingSource", {
|
|
135
|
+
enumerable: true,
|
|
136
|
+
get: function() {
|
|
137
|
+
return digitGroupingSource;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
Object.defineProperty(exports, "readNumberText", {
|
|
141
|
+
enumerable: true,
|
|
142
|
+
get: function() {
|
|
143
|
+
return readNumberText;
|
|
144
|
+
}
|
|
145
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { $ as describeValue, E as resolveCurrency, dt as roundToScale, m as currencyMarks } from "./money-WhnGiI00.js";
|
|
2
|
+
//#region src/internal/number-text.ts
|
|
3
|
+
const MINUS = /^[-−]/;
|
|
4
|
+
const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5
|
+
const unparseable = Object.freeze({ reason: "unparseable" });
|
|
6
|
+
/**
|
|
7
|
+
* THE digit grouping a whole part may use, as a regex source over the separator source `s`:
|
|
8
|
+
* thousands (`\d{1,3}(s\d{3})+`: `1,234,567`) or Indian lakh (`\d{1,2}(s\d{2})+s\d{3}`:
|
|
9
|
+
* three digits on the right, then pairs: `1,00,000`, `12,34,567`). Valid only when every
|
|
10
|
+
* group fits one of the two, so `1,2345` and `12,3,456` are not grouping.
|
|
11
|
+
*/
|
|
12
|
+
function digitGroupingSource(s) {
|
|
13
|
+
return `\\d{1,3}(?:${s}\\d{3})+|\\d{1,2}(?:${s}\\d{2})+${s}\\d{3}`;
|
|
14
|
+
}
|
|
15
|
+
const GROUPED = new RegExp(`^(?:${digitGroupingSource(",")})$`);
|
|
16
|
+
/** Whether `groups`, the digit runs between the separators read as grouping, are valid digit grouping. */
|
|
17
|
+
const isDigitGrouping = (groups) => GROUPED.test(groups.join(","));
|
|
18
|
+
function stripCurrency(text, info) {
|
|
19
|
+
return currencyMarks(info).sort((a, b) => b.length - a.length).reduce((rest, token) => rest.replace(new RegExp(escapeRegExp(token), "gi"), ""), text);
|
|
20
|
+
}
|
|
21
|
+
function value(negative, whole, fraction, decimalMark) {
|
|
22
|
+
const digits = whole + fraction;
|
|
23
|
+
if (digits.length > 400) return unparseable;
|
|
24
|
+
const units = BigInt(digits);
|
|
25
|
+
return {
|
|
26
|
+
sign: negative && units !== 0n ? -1 : 1,
|
|
27
|
+
units,
|
|
28
|
+
scale: fraction.length,
|
|
29
|
+
decimalMark
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** `'auto'`: decide which of `.` and `,` is the decimal separator (see `NumberTextOptions.decimalSeparator`). */
|
|
33
|
+
function readAuto(negative, body, info, rounding) {
|
|
34
|
+
if (!/^[\d.,]+$/.test(body) || !/\d/.test(body)) return unparseable;
|
|
35
|
+
if (info?.storageDigits === 0) {
|
|
36
|
+
const groups = body.split(/[.,]/);
|
|
37
|
+
const last = groups.length > 1 ? groups[groups.length - 1] : null;
|
|
38
|
+
const isDecimal = last !== null && last.length !== 3;
|
|
39
|
+
const decimalMark = isDecimal ? body[body.length - last.length - 1] : null;
|
|
40
|
+
const wholeGroups = isDecimal ? groups.slice(0, -1) : groups;
|
|
41
|
+
if (wholeGroups.length > 1 && (!isDigitGrouping(wholeGroups) || last === "")) return unparseable;
|
|
42
|
+
const read = value(negative, wholeGroups.join("") || "0", isDecimal ? last : "", decimalMark);
|
|
43
|
+
if ("reason" in read || rounding === null) return read;
|
|
44
|
+
const rounded = roundToScale({
|
|
45
|
+
units: BigInt(read.sign) * read.units,
|
|
46
|
+
scale: read.scale
|
|
47
|
+
}, 0, rounding);
|
|
48
|
+
const units = rounded.units < 0n ? -rounded.units : rounded.units;
|
|
49
|
+
return {
|
|
50
|
+
sign: read.sign < 0 && units !== 0n ? -1 : 1,
|
|
51
|
+
units,
|
|
52
|
+
scale: 0,
|
|
53
|
+
decimalMark
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const lastDot = body.lastIndexOf(".");
|
|
57
|
+
const lastComma = body.lastIndexOf(",");
|
|
58
|
+
if (lastDot >= 0 && lastComma >= 0) {
|
|
59
|
+
const at = Math.max(lastDot, lastComma);
|
|
60
|
+
const groups = body.slice(0, at).split(/[.,]/);
|
|
61
|
+
const fraction = body.slice(at + 1);
|
|
62
|
+
if (!isDigitGrouping(groups) || fraction === "") return unparseable;
|
|
63
|
+
return value(negative, groups.join(""), fraction, body[at]);
|
|
64
|
+
}
|
|
65
|
+
const mark = lastDot >= 0 ? "." : lastComma >= 0 ? "," : null;
|
|
66
|
+
if (mark === null) return value(negative, body, "", null);
|
|
67
|
+
const at = body.indexOf(mark);
|
|
68
|
+
if (at !== body.lastIndexOf(mark)) {
|
|
69
|
+
const groups = body.split(mark);
|
|
70
|
+
return isDigitGrouping(groups) ? value(negative, groups.join(""), "", null) : unparseable;
|
|
71
|
+
}
|
|
72
|
+
const whole = body.slice(0, at);
|
|
73
|
+
const fraction = body.slice(at + 1);
|
|
74
|
+
if (fraction.length !== 3 || /^0*$/.test(whole) || isThreeDecimalMark(mark, info)) return value(negative, whole || "0", fraction, mark);
|
|
75
|
+
return isDigitGrouping([whole, fraction]) ? value(negative, whole + fraction, "", null) : unparseable;
|
|
76
|
+
}
|
|
77
|
+
/** Whether `mark` is the decimal mark of a currency displaying three decimals: `','` for TND (`commaDecimal`), else `'.'`. */
|
|
78
|
+
function isThreeDecimalMark(mark, info) {
|
|
79
|
+
return info?.displayDigits === 3 && mark === (info.commaDecimal ? "," : ".");
|
|
80
|
+
}
|
|
81
|
+
/** An explicit decimal separator, with grouping validated in 3-digit groups. */
|
|
82
|
+
function readExplicit(negative, body, decimal, group, info) {
|
|
83
|
+
const d = escapeRegExp(decimal);
|
|
84
|
+
if (info?.storageDigits === 0 && group !== null && new RegExp(`^\\d{1,3}(?:${d}\\d{3})+$`).test(body)) return value(negative, body.split(decimal).join(""), "", null);
|
|
85
|
+
const [whole, fraction, extra] = body.split(decimal);
|
|
86
|
+
if (extra !== void 0 || fraction === "" || fraction !== void 0 && !/^\d+$/.test(fraction)) return unparseable;
|
|
87
|
+
const decimalMark = fraction === void 0 ? null : decimal;
|
|
88
|
+
if (/^\d+$/.test(whole)) return value(negative, whole, fraction ?? "", decimalMark);
|
|
89
|
+
if (whole === "" && group === null && fraction !== void 0) return value(negative, "0", fraction, decimalMark);
|
|
90
|
+
if (group === null || !new RegExp(`^(?:${digitGroupingSource(escapeRegExp(group))})$`).test(whole)) return unparseable;
|
|
91
|
+
return value(negative, whole.split(group).join(""), fraction ?? "", decimalMark);
|
|
92
|
+
}
|
|
93
|
+
/** The first separator written between two digits that is not the decimal mark. */
|
|
94
|
+
function groupMarkOf(written, decimalMark) {
|
|
95
|
+
for (const [, mark] of written.matchAll(/\d(\D)(?=\d)/g)) if (mark !== decimalMark) return mark;
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Amount text to sign, digits and scale, exactly; or why not (`'empty'`, `'unparseable'`).
|
|
100
|
+
* Whitespace of every kind (NBSP, thin, narrow NBSP) is grouping and removed; in `'auto'`
|
|
101
|
+
* mode apostrophes are too. A leading `-` or U+2212 is the sign. No exponent, no hex; an
|
|
102
|
+
* explicit separator also refuses `'1.'` and, unless `groupSeparator` is null, `'.5'` ('auto'
|
|
103
|
+
* reads them as 1 and 0.5). Never throws for bad text; an unknown `currency` throws `currency_invalid`.
|
|
104
|
+
*/
|
|
105
|
+
function readNumberText(text, options = {}) {
|
|
106
|
+
const info = options.currency === void 0 ? null : resolveCurrency(options.currency, { allowHistoric: true });
|
|
107
|
+
const decimal = options.decimalSeparator ?? ".";
|
|
108
|
+
const auto = decimal === "auto";
|
|
109
|
+
const group = options.groupSeparator === void 0 ? decimal === "." ? "," : decimal === "," ? "." : null : options.groupSeparator;
|
|
110
|
+
if (!auto && (decimal.length !== 1 || group === decimal)) throw new TypeError(`Invalid separators: decimal ${describeValue(decimal)}, group ${describeValue(group)}`);
|
|
111
|
+
if (typeof text !== "string") return unparseable;
|
|
112
|
+
const written = info ? stripCurrency(text.trim(), info) : text.trim();
|
|
113
|
+
let rest = written.replace(auto ? /[\s'’]/g : /\s/g, "");
|
|
114
|
+
if (rest === "") return { reason: "empty" };
|
|
115
|
+
let negative = false;
|
|
116
|
+
if (options.allowTrailingText) {
|
|
117
|
+
const separators = auto ? ".," : escapeRegExp(`${decimal}${group ?? ""}`);
|
|
118
|
+
const run = new RegExp(`([-\\u2212]?)(\\d[\\d${separators}]*)`).exec(rest);
|
|
119
|
+
if (!run) return unparseable;
|
|
120
|
+
negative = run[1] !== "";
|
|
121
|
+
rest = run[2].replace(new RegExp(`[${separators}]+$`), "");
|
|
122
|
+
} else if (MINUS.test(rest)) {
|
|
123
|
+
negative = true;
|
|
124
|
+
rest = rest.slice(1);
|
|
125
|
+
}
|
|
126
|
+
const read = auto ? readAuto(negative, rest, info, options.keepFraction ? null : options.rounding ?? "half-up") : readExplicit(negative, rest, decimal, group, info);
|
|
127
|
+
if ("reason" in read) return read;
|
|
128
|
+
return {
|
|
129
|
+
...read,
|
|
130
|
+
groupMark: groupMarkOf(written, read.decimalMark)
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
export { readNumberText as n, digitGroupingSource as t };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const require_money = require("./money-B3wH_Z4S.cjs");
|
|
2
|
+
//#region src/internal/percent.ts
|
|
3
|
+
/** 100% for a percent written with `scale` fraction digits: the denominator of its units. */
|
|
4
|
+
function hundredPercent(scale) {
|
|
5
|
+
return 100n * require_money.pow10(scale);
|
|
6
|
+
}
|
|
7
|
+
/** `percent` percent of `units`, rounded once by `mode`. */
|
|
8
|
+
function percentOfUnits(units, percent, mode) {
|
|
9
|
+
return require_money.mulDiv(units, percent.units, hundredPercent(percent.scale), mode);
|
|
10
|
+
}
|
|
11
|
+
/** `bps` basis points of `units / denominator` (default a whole number of units), rounded once by `mode`. */
|
|
12
|
+
function basisPointsOf(units, bps, mode, denominator = 1n) {
|
|
13
|
+
return require_money.mulDiv(units, bps, 10000n * denominator, mode);
|
|
14
|
+
}
|
|
15
|
+
/** `a / b` in percent with `digits` fraction digits, half-up (`b` must not be zero). */
|
|
16
|
+
function percentRatio(a, b, digits) {
|
|
17
|
+
return require_money.ratioToDecimal(a * 100n, b, digits);
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
Object.defineProperty(exports, "basisPointsOf", {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
get: function() {
|
|
23
|
+
return basisPointsOf;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
Object.defineProperty(exports, "hundredPercent", {
|
|
27
|
+
enumerable: true,
|
|
28
|
+
get: function() {
|
|
29
|
+
return hundredPercent;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
Object.defineProperty(exports, "percentOfUnits", {
|
|
33
|
+
enumerable: true,
|
|
34
|
+
get: function() {
|
|
35
|
+
return percentOfUnits;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
Object.defineProperty(exports, "percentRatio", {
|
|
39
|
+
enumerable: true,
|
|
40
|
+
get: function() {
|
|
41
|
+
return percentRatio;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { X as mulDiv, Z as ratioToDecimal, ut as pow10 } from "./money-WhnGiI00.js";
|
|
2
|
+
//#region src/internal/percent.ts
|
|
3
|
+
/** 100% for a percent written with `scale` fraction digits: the denominator of its units. */
|
|
4
|
+
function hundredPercent(scale) {
|
|
5
|
+
return 100n * pow10(scale);
|
|
6
|
+
}
|
|
7
|
+
/** `percent` percent of `units`, rounded once by `mode`. */
|
|
8
|
+
function percentOfUnits(units, percent, mode) {
|
|
9
|
+
return mulDiv(units, percent.units, hundredPercent(percent.scale), mode);
|
|
10
|
+
}
|
|
11
|
+
/** `bps` basis points of `units / denominator` (default a whole number of units), rounded once by `mode`. */
|
|
12
|
+
function basisPointsOf(units, bps, mode, denominator = 1n) {
|
|
13
|
+
return mulDiv(units, bps, 10000n * denominator, mode);
|
|
14
|
+
}
|
|
15
|
+
/** `a / b` in percent with `digits` fraction digits, half-up (`b` must not be zero). */
|
|
16
|
+
function percentRatio(a, b, digits) {
|
|
17
|
+
return ratioToDecimal(a * 100n, b, digits);
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { percentRatio as i, hundredPercent as n, percentOfUnits as r, basisPointsOf as t };
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
const require_money = require("./money-B3wH_Z4S.cjs");
|
|
2
|
+
const require_own = require("./own-oP9DXFWv.cjs");
|
|
3
|
+
//#region src/fx/rate.ts
|
|
4
|
+
/** Denominator of every locked rate (`store_currency_rate.rate_denominator` default): 7 decimals. */
|
|
5
|
+
const RATE_REF = 10000000n;
|
|
6
|
+
/** Inclusive bounds of a locked rate's value: `lockedRateNumerator` (n >= 1 at RATE_REF, value <= 1e9). */
|
|
7
|
+
const RATE_BOUNDS = {
|
|
8
|
+
min: "0.0000001",
|
|
9
|
+
max: "1000000000"
|
|
10
|
+
};
|
|
11
|
+
const MIN_RATE = {
|
|
12
|
+
units: 1n,
|
|
13
|
+
scale: 7
|
|
14
|
+
};
|
|
15
|
+
const MAX_RATE = {
|
|
16
|
+
units: 1000000000n,
|
|
17
|
+
scale: 0
|
|
18
|
+
};
|
|
19
|
+
const invalid = (message) => new require_money.MoneyError("rate_invalid", message);
|
|
20
|
+
/** A positive integer part (safe integer number, bigint or digit string), or null. */
|
|
21
|
+
function partOf(value) {
|
|
22
|
+
let part = null;
|
|
23
|
+
if (typeof value === "bigint") part = value;
|
|
24
|
+
else if (typeof value === "number" && Number.isSafeInteger(value)) part = BigInt(value);
|
|
25
|
+
else if (typeof value === "string") part = require_money.parseDecimal(value, "integer")?.units ?? null;
|
|
26
|
+
return part !== null && part >= 1n ? part : null;
|
|
27
|
+
}
|
|
28
|
+
const withinBounds = (n, d) => require_money.compareRatio(n, d, MIN_RATE.units, require_money.pow10(MIN_RATE.scale)) >= 0 && require_money.compareRatio(n, d, MAX_RATE.units, 1n) <= 0;
|
|
29
|
+
/**
|
|
30
|
+
* A Rate from its parts: integers >= 1 (safe integer number, bigint or digit string) whose
|
|
31
|
+
* value lies within `RATE_BOUNDS`. Anything else throws `rate_invalid`. Not reduced: a
|
|
32
|
+
* RATE_REF denominator is written back as given.
|
|
33
|
+
*/
|
|
34
|
+
function rate(numerator, denominator) {
|
|
35
|
+
const n = partOf(numerator);
|
|
36
|
+
const d = partOf(denominator);
|
|
37
|
+
if (n === null || d === null) throw invalid(`Rate parts must be whole numbers >= 1, got ${require_money.describeValue(numerator)}/${require_money.describeValue(denominator)}`);
|
|
38
|
+
if (!withinBounds(n, d)) throw invalid(`Rate ${n}/${d} is outside ${RATE_BOUNDS.min}..${RATE_BOUNDS.max}`);
|
|
39
|
+
return {
|
|
40
|
+
numerator: n,
|
|
41
|
+
denominator: d
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** A `store_currency_rate` row's rate, or null for missing, non-integer, non-positive or out-of-bounds parts. Never throws. */
|
|
45
|
+
function rateFromRow(row) {
|
|
46
|
+
if (typeof row !== "object" || row === null) return null;
|
|
47
|
+
const n = partOf(row.rate_numerator);
|
|
48
|
+
const d = partOf(row.rate_denominator);
|
|
49
|
+
return n !== null && d !== null && withinBounds(n, d) ? {
|
|
50
|
+
numerator: n,
|
|
51
|
+
denominator: d
|
|
52
|
+
} : null;
|
|
53
|
+
}
|
|
54
|
+
/** The exact decimal of a rate input; anything that is not a finite decimal throws `rate_invalid`. */
|
|
55
|
+
function rateDecimal(value) {
|
|
56
|
+
try {
|
|
57
|
+
return require_money.decimalOf(value);
|
|
58
|
+
} catch {
|
|
59
|
+
throw invalid(`Rate must be a finite decimal, got ${require_money.describeValue(value)}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** `floor(log10(v))` of a positive decimal, from its digits (no float log). */
|
|
63
|
+
function exponentOf(v) {
|
|
64
|
+
return v.units.toString().length - 1 - v.scale;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* A major-unit rate given as a number or decimal text (`83.42`, `'0.000029175'`) as a Rate,
|
|
68
|
+
* rounded half-up once. Numbers are taken by their shortest text, so `83.42` is exactly
|
|
69
|
+
* 834200000 / 1e7. See `RateFromDecimalOptions` for the two encodings.
|
|
70
|
+
*/
|
|
71
|
+
function rateFromDecimal(value, options = {}) {
|
|
72
|
+
const v = rateDecimal(value);
|
|
73
|
+
if (options.encoding === "precise") {
|
|
74
|
+
if (v.units <= 0n) throw invalid(`Rate must be greater than 0, got ${require_money.describeValue(value)}`);
|
|
75
|
+
const exponent = Math.max(7, 9 - exponentOf(v));
|
|
76
|
+
return {
|
|
77
|
+
numerator: require_money.roundToScale(v, exponent, "half-up").units,
|
|
78
|
+
denominator: require_money.pow10(exponent)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (require_money.compareRatio(v.units, require_money.pow10(v.scale), MAX_RATE.units, 1n) > 0) throw invalid(`Rate must not be greater than ${RATE_BOUNDS.max}, got ${require_money.describeValue(value)}`);
|
|
82
|
+
const numerator = require_money.roundToScale(v, 7, "half-up").units;
|
|
83
|
+
if (numerator < 1n) throw invalid(`Rate must be greater than 0, got ${require_money.describeValue(value)}`);
|
|
84
|
+
return {
|
|
85
|
+
numerator,
|
|
86
|
+
denominator: RATE_REF
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** Throws `rate_invalid` unless `r` has positive integer parts. */
|
|
90
|
+
function checkRate(r) {
|
|
91
|
+
if (typeof r !== "object" || r === null || typeof r.numerator !== "bigint" || typeof r.denominator !== "bigint" || r.numerator < 1n || r.denominator < 1n) throw invalid(`Not a rate: ${require_money.describeValue(r)}`);
|
|
92
|
+
return r;
|
|
93
|
+
}
|
|
94
|
+
function checkDecimals(decimals) {
|
|
95
|
+
if (!Number.isSafeInteger(decimals) || decimals < 0 || decimals > 30) throw new require_money.MoneyError("amount_invalid", `decimals must be an integer from 0 to 30, got ${require_money.describeValue(decimals)}`);
|
|
96
|
+
return decimals;
|
|
97
|
+
}
|
|
98
|
+
/** `r` as text rounded half-up to `decimals` fraction digits (default 12: persisted `fx_rate`, `draft_market_rate`). */
|
|
99
|
+
function rateToDecimal(r, options = {}) {
|
|
100
|
+
const { numerator, denominator } = checkRate(r);
|
|
101
|
+
const decimals = checkDecimals(options.decimals ?? 12);
|
|
102
|
+
return require_money.toDecimalText(require_money.ratioToDecimal(numerator, denominator, decimals));
|
|
103
|
+
}
|
|
104
|
+
/** `r` as a number: rounded half-up to 7 decimals by default (Medusa `toMajorRate`), or `'exact'` = `Number(n) / Number(d)`. */
|
|
105
|
+
function rateToNumber(r, options = {}) {
|
|
106
|
+
const { numerator, denominator } = checkRate(r);
|
|
107
|
+
if (options.decimals === "exact") return Number(numerator) / Number(denominator);
|
|
108
|
+
return Number(rateToDecimal(r, { decimals: options.decimals ?? 7 }));
|
|
109
|
+
}
|
|
110
|
+
/** 1 quote = d/n base. */
|
|
111
|
+
function invertRate(r) {
|
|
112
|
+
const { numerator, denominator } = checkRate(r);
|
|
113
|
+
return {
|
|
114
|
+
numerator: denominator,
|
|
115
|
+
denominator: numerator
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const ONE = {
|
|
119
|
+
numerator: 1n,
|
|
120
|
+
denominator: 1n
|
|
121
|
+
};
|
|
122
|
+
function lookup(table, code) {
|
|
123
|
+
const lower = code.toLowerCase();
|
|
124
|
+
const found = require_own.hasOwnKey(table, lower) ? table[lower] : require_own.hasOwnKey(table, code.toUpperCase()) ? table[code.toUpperCase()] : void 0;
|
|
125
|
+
return found === void 0 ? void 0 : checkRate(found);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 1 `from` = ? `to`, from a table of rates that each say what 1 `base` is worth (keys any
|
|
129
|
+
* case; `base` itself counts as 1/1). Same code (any case) gives 1/1; a missing side gives
|
|
130
|
+
* null. Exact on bigint (`{ b.n x a.d, b.d x a.n }`), never overflows.
|
|
131
|
+
*/
|
|
132
|
+
function crossRate(table, from, to, base) {
|
|
133
|
+
const f = from.toLowerCase();
|
|
134
|
+
const t = to.toLowerCase();
|
|
135
|
+
const b = base.toLowerCase();
|
|
136
|
+
if (f === t) return ONE;
|
|
137
|
+
const a = f === b ? ONE : lookup(table, f);
|
|
138
|
+
const c = t === b ? ONE : lookup(table, t);
|
|
139
|
+
if (!a || !c) return null;
|
|
140
|
+
return {
|
|
141
|
+
numerator: c.numerator * a.denominator,
|
|
142
|
+
denominator: c.denominator * a.numerator
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/** Same value (cross-multiplied), whatever the parts: 2/4 equals 1/2. */
|
|
146
|
+
function rateEquals(a, b) {
|
|
147
|
+
const x = checkRate(a);
|
|
148
|
+
const y = checkRate(b);
|
|
149
|
+
return require_money.compareRatio(x.numerator, x.denominator, y.numerator, y.denominator) === 0;
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
Object.defineProperty(exports, "RATE_BOUNDS", {
|
|
153
|
+
enumerable: true,
|
|
154
|
+
get: function() {
|
|
155
|
+
return RATE_BOUNDS;
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
Object.defineProperty(exports, "RATE_REF", {
|
|
159
|
+
enumerable: true,
|
|
160
|
+
get: function() {
|
|
161
|
+
return RATE_REF;
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
Object.defineProperty(exports, "checkRate", {
|
|
165
|
+
enumerable: true,
|
|
166
|
+
get: function() {
|
|
167
|
+
return checkRate;
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
Object.defineProperty(exports, "crossRate", {
|
|
171
|
+
enumerable: true,
|
|
172
|
+
get: function() {
|
|
173
|
+
return crossRate;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
Object.defineProperty(exports, "invertRate", {
|
|
177
|
+
enumerable: true,
|
|
178
|
+
get: function() {
|
|
179
|
+
return invertRate;
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
Object.defineProperty(exports, "rate", {
|
|
183
|
+
enumerable: true,
|
|
184
|
+
get: function() {
|
|
185
|
+
return rate;
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
Object.defineProperty(exports, "rateEquals", {
|
|
189
|
+
enumerable: true,
|
|
190
|
+
get: function() {
|
|
191
|
+
return rateEquals;
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
Object.defineProperty(exports, "rateFromDecimal", {
|
|
195
|
+
enumerable: true,
|
|
196
|
+
get: function() {
|
|
197
|
+
return rateFromDecimal;
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
Object.defineProperty(exports, "rateFromRow", {
|
|
201
|
+
enumerable: true,
|
|
202
|
+
get: function() {
|
|
203
|
+
return rateFromRow;
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
Object.defineProperty(exports, "rateToDecimal", {
|
|
207
|
+
enumerable: true,
|
|
208
|
+
get: function() {
|
|
209
|
+
return rateToDecimal;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
Object.defineProperty(exports, "rateToNumber", {
|
|
213
|
+
enumerable: true,
|
|
214
|
+
get: function() {
|
|
215
|
+
return rateToNumber;
|
|
216
|
+
}
|
|
217
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { R as DecimalText } from "./money-B-3aFjNJ.js";
|
|
2
|
+
//#region src/fx/rate.d.ts
|
|
3
|
+
/** 1 base unit = numerator / denominator quote units, both in major units. Positive integers. */
|
|
4
|
+
interface Rate {
|
|
5
|
+
readonly numerator: bigint;
|
|
6
|
+
readonly denominator: bigint;
|
|
7
|
+
}
|
|
8
|
+
/** A `store_currency_rate` row as the database driver returns it (bigint columns usually as text). */
|
|
9
|
+
interface RateRow {
|
|
10
|
+
readonly rate_numerator: unknown;
|
|
11
|
+
readonly rate_denominator: unknown;
|
|
12
|
+
readonly currency_code?: unknown;
|
|
13
|
+
}
|
|
14
|
+
/** Denominator of every locked rate (`store_currency_rate.rate_denominator` default): 7 decimals. */
|
|
15
|
+
declare const RATE_REF = 10000000n;
|
|
16
|
+
/** Inclusive bounds of a locked rate's value: `lockedRateNumerator` (n >= 1 at RATE_REF, value <= 1e9). */
|
|
17
|
+
declare const RATE_BOUNDS: {
|
|
18
|
+
readonly min: "0.0000001";
|
|
19
|
+
readonly max: "1000000000";
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* A Rate from its parts: integers >= 1 (safe integer number, bigint or digit string) whose
|
|
23
|
+
* value lies within `RATE_BOUNDS`. Anything else throws `rate_invalid`. Not reduced: a
|
|
24
|
+
* RATE_REF denominator is written back as given.
|
|
25
|
+
*/
|
|
26
|
+
declare function rate(numerator: number | bigint | string, denominator: number | bigint | string): Rate;
|
|
27
|
+
/** A `store_currency_rate` row's rate, or null for missing, non-integer, non-positive or out-of-bounds parts. Never throws. */
|
|
28
|
+
declare function rateFromRow(row: RateRow | null | undefined): Rate | null;
|
|
29
|
+
interface RateFromDecimalOptions {
|
|
30
|
+
/**
|
|
31
|
+
* `'locked'` (default): `n = round_half_up(value x 1e7)`, `d = RATE_REF` (orch
|
|
32
|
+
* `lockedRateNumerator`); `n < 1` or `value > 1e9` throws `rate_invalid`.
|
|
33
|
+
* `'precise'`: about ten significant digits, `d = 10^max(7, 9 - floor(log10(value)))`
|
|
34
|
+
* (orch `precisePriceRate`, for one-off conversions); `value <= 0` throws `rate_invalid`.
|
|
35
|
+
*/
|
|
36
|
+
encoding?: 'locked' | 'precise';
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A major-unit rate given as a number or decimal text (`83.42`, `'0.000029175'`) as a Rate,
|
|
40
|
+
* rounded half-up once. Numbers are taken by their shortest text, so `83.42` is exactly
|
|
41
|
+
* 834200000 / 1e7. See `RateFromDecimalOptions` for the two encodings.
|
|
42
|
+
*/
|
|
43
|
+
declare function rateFromDecimal(value: number | string, options?: RateFromDecimalOptions): Rate;
|
|
44
|
+
/** `r` as text rounded half-up to `decimals` fraction digits (default 12: persisted `fx_rate`, `draft_market_rate`). */
|
|
45
|
+
declare function rateToDecimal(r: Rate, options?: {
|
|
46
|
+
decimals?: number;
|
|
47
|
+
}): DecimalText;
|
|
48
|
+
/** `r` as a number: rounded half-up to 7 decimals by default (Medusa `toMajorRate`), or `'exact'` = `Number(n) / Number(d)`. */
|
|
49
|
+
declare function rateToNumber(r: Rate, options?: {
|
|
50
|
+
decimals?: number | 'exact';
|
|
51
|
+
}): number;
|
|
52
|
+
/** 1 quote = d/n base. */
|
|
53
|
+
declare function invertRate(r: Rate): Rate;
|
|
54
|
+
/**
|
|
55
|
+
* 1 `from` = ? `to`, from a table of rates that each say what 1 `base` is worth (keys any
|
|
56
|
+
* case; `base` itself counts as 1/1). Same code (any case) gives 1/1; a missing side gives
|
|
57
|
+
* null. Exact on bigint (`{ b.n x a.d, b.d x a.n }`), never overflows.
|
|
58
|
+
*/
|
|
59
|
+
declare function crossRate(table: Readonly<Record<string, Rate>>, from: string, to: string, base: string): Rate | null;
|
|
60
|
+
/** Same value (cross-multiplied), whatever the parts: 2/4 equals 1/2. */
|
|
61
|
+
declare function rateEquals(a: Rate, b: Rate): boolean;
|
|
62
|
+
//#endregion
|
|
63
|
+
export { RateRow as a, rate as c, rateFromRow as d, rateToDecimal as f, RateFromDecimalOptions as i, rateEquals as l, RATE_REF as n, crossRate as o, rateToNumber as p, Rate as r, invertRate as s, RATE_BOUNDS as t, rateFromDecimal as u };
|