@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
package/dist/input.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { B as decimalOf, Q as MoneyError, at as fractionDigitsOf, dt as roundToScale, it as formatDecimal, pt as toDecimalText, s as storedDecimal, tt as compareDecimal } from "./money-WhnGiI00.js";
|
|
2
|
+
import { n as readNumberText } from "./number-text-DE-q4jO5.js";
|
|
3
|
+
import { c as rateFromDecimal, o as rate } from "./rate-DicStBue.js";
|
|
4
|
+
import { a as inputStep, i as inputPlaceholder, n as formatAmountInput, o as localeSeparators, r as groupDigits, s as parseAmountInput, t as decimalOfText } from "./amount-BoYBMgK9.js";
|
|
5
|
+
//#region src/input/import.ts
|
|
6
|
+
/**
|
|
7
|
+
* Amount text from a file or a third party to canonical DecimalText (major units, exactly as
|
|
8
|
+
* written: `'1.299,00'` gives `'1299'`), or null when it is not an amount (empty, exponent,
|
|
9
|
+
* hex, garbage, negative without `allowNegative`). Never 0 for bad input.
|
|
10
|
+
*/
|
|
11
|
+
function parseImportAmount(text, options = {}) {
|
|
12
|
+
const read = readNumberText(text, {
|
|
13
|
+
decimalSeparator: options.decimalSeparator ?? "auto",
|
|
14
|
+
currency: options.currency,
|
|
15
|
+
allowTrailingText: options.allowTrailingText
|
|
16
|
+
});
|
|
17
|
+
if ("reason" in read || read.sign < 0 && !options.allowNegative) return null;
|
|
18
|
+
return toDecimalText(decimalOfText(read));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Strict decimal entry: `.` decimal, no grouping, at most `maxDecimals` fraction digits (cost
|
|
22
|
+
* entry at 4, metafields). `'4.5'` gives `'4.5'`, `'.5'` gives `'0.5'`; `'1,000'` is `'unparseable'`.
|
|
23
|
+
*/
|
|
24
|
+
function parseDecimalInput(text, options) {
|
|
25
|
+
const read = readNumberText(text, {
|
|
26
|
+
decimalSeparator: ".",
|
|
27
|
+
groupSeparator: null
|
|
28
|
+
});
|
|
29
|
+
if ("reason" in read) return {
|
|
30
|
+
ok: false,
|
|
31
|
+
reason: read.reason
|
|
32
|
+
};
|
|
33
|
+
const value = decimalOfText(read);
|
|
34
|
+
if (fractionDigitsOf(value) > options.maxDecimals) return {
|
|
35
|
+
ok: false,
|
|
36
|
+
reason: "too_many_decimals"
|
|
37
|
+
};
|
|
38
|
+
if (value.units < 0n && !options.allowNegative) return {
|
|
39
|
+
ok: false,
|
|
40
|
+
reason: "negative"
|
|
41
|
+
};
|
|
42
|
+
if (options.min !== void 0 && compareDecimal(value, decimalOf(options.min)) < 0) return {
|
|
43
|
+
ok: false,
|
|
44
|
+
reason: "below_min"
|
|
45
|
+
};
|
|
46
|
+
if (options.max !== void 0 && compareDecimal(value, decimalOf(options.max)) > 0) return {
|
|
47
|
+
ok: false,
|
|
48
|
+
reason: "above_max"
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
ok: true,
|
|
52
|
+
value: toDecimalText(value)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/input/percent-rate.ts
|
|
57
|
+
/**
|
|
58
|
+
* A typed percent (`'20'`, `'12.5'`, `'12,5 %'` in de-DE) as DecimalText. A `%` before or after
|
|
59
|
+
* the number is allowed. Default range 0..100 with 2 decimals; below 0 (with a non-negative
|
|
60
|
+
* `min`) is `'negative'`.
|
|
61
|
+
*/
|
|
62
|
+
function parsePercentInput(text, options = {}) {
|
|
63
|
+
const bare = typeof text === "string" ? text.trim().replace(/^%\s*|\s*%$/g, "") : text;
|
|
64
|
+
const read = readNumberText(bare, localeSeparators(options.locale));
|
|
65
|
+
if ("reason" in read) return {
|
|
66
|
+
ok: false,
|
|
67
|
+
reason: read.reason
|
|
68
|
+
};
|
|
69
|
+
const value = decimalOfText(read);
|
|
70
|
+
const min = decimalOf(options.min ?? 0, true, "min");
|
|
71
|
+
const max = decimalOf(options.max ?? 100, true, "max");
|
|
72
|
+
if (fractionDigitsOf(value) > (options.maxDecimals ?? 2)) return {
|
|
73
|
+
ok: false,
|
|
74
|
+
reason: "too_many_decimals"
|
|
75
|
+
};
|
|
76
|
+
if (compareDecimal(value, min) < 0) return {
|
|
77
|
+
ok: false,
|
|
78
|
+
reason: value.units < 0n && min.units >= 0n ? "negative" : "below_min"
|
|
79
|
+
};
|
|
80
|
+
if (compareDecimal(value, max) > 0) return {
|
|
81
|
+
ok: false,
|
|
82
|
+
reason: "above_max"
|
|
83
|
+
};
|
|
84
|
+
return {
|
|
85
|
+
ok: true,
|
|
86
|
+
value: toDecimalText(value)
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* A typed exchange rate ("1 store-currency unit equals this many units") as a precise Rate
|
|
91
|
+
* (`fx.rateFromDecimal(value, { encoding: 'precise' })`), or null when the text is not a
|
|
92
|
+
* positive number or the rate is outside `RATE_BOUNDS` (0.0000001 to 1000000000).
|
|
93
|
+
*/
|
|
94
|
+
function parseRateInput(text, options = {}) {
|
|
95
|
+
const read = readNumberText(text, localeSeparators(options.locale));
|
|
96
|
+
if ("reason" in read || read.sign < 0 || read.units === 0n) return null;
|
|
97
|
+
try {
|
|
98
|
+
const precise = rateFromDecimal(formatDecimal(decimalOfText(read)), { encoding: "precise" });
|
|
99
|
+
return rate(precise.numerator, precise.denominator);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (error instanceof MoneyError && error.code === "rate_invalid") return null;
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/input/template.ts
|
|
107
|
+
const DEFAULT_TEMPLATE = Object.freeze({
|
|
108
|
+
prefix: "",
|
|
109
|
+
suffix: "",
|
|
110
|
+
fractionDigits: 2,
|
|
111
|
+
decimalSeparator: ".",
|
|
112
|
+
groupSeparator: ","
|
|
113
|
+
});
|
|
114
|
+
const NUMBER_RUN = /\d[\d.,'’\s]*\d|\d/u;
|
|
115
|
+
const counterpart = (separator) => separator === "," ? "." : ",";
|
|
116
|
+
/**
|
|
117
|
+
* The print template of one captured literal (cloner `analyzeMoneyFormat`): prefix and suffix
|
|
118
|
+
* around the number, the decimal separator and digit count of its fraction (1 or 2 digits
|
|
119
|
+
* read as 2, 3 as 3, none as 0), the separator it groups thousands with (else the decimal's
|
|
120
|
+
* counterpart), all as the separator engine's `'auto'` rule reads them. A
|
|
121
|
+
* sample with no number gives `{ prefix: '', suffix: '', fractionDigits: 2, '.', ',' }`.
|
|
122
|
+
*/
|
|
123
|
+
function moneyTemplateOf(sample) {
|
|
124
|
+
const text = typeof sample === "string" ? sample : "";
|
|
125
|
+
const run = NUMBER_RUN.exec(text);
|
|
126
|
+
if (!run) return DEFAULT_TEMPLATE;
|
|
127
|
+
const read = readNumberText(run[0], { decimalSeparator: "auto" });
|
|
128
|
+
if ("reason" in read) return DEFAULT_TEMPLATE;
|
|
129
|
+
const decimalSeparator = read.decimalMark ?? counterpart(read.groupMark ?? ",");
|
|
130
|
+
return Object.freeze({
|
|
131
|
+
prefix: text.slice(0, run.index),
|
|
132
|
+
suffix: text.slice(run.index + run[0].length),
|
|
133
|
+
fractionDigits: read.scale === 0 ? 0 : read.scale === 3 ? 3 : 2,
|
|
134
|
+
decimalSeparator,
|
|
135
|
+
groupSeparator: read.groupMark ?? counterpart(decimalSeparator)
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Money printed with a captured template: the exact major amount (stored / storageScale)
|
|
140
|
+
* rounded half-up to the template's digits, grouped, the sign after the prefix
|
|
141
|
+
* (`moneyTemplateOf('Rp 199.000')` prints IDR 2500050 as `'Rp 25.001'`).
|
|
142
|
+
*/
|
|
143
|
+
function formatWithTemplate(m, template) {
|
|
144
|
+
const { value } = storedDecimal(m);
|
|
145
|
+
const rounded = formatDecimal(roundToScale(value, template.fractionDigits, "half-up"));
|
|
146
|
+
const negative = rounded.startsWith("-");
|
|
147
|
+
const [whole, fraction] = (negative ? rounded.slice(1) : rounded).split(".");
|
|
148
|
+
const grouped = groupDigits(whole, template.groupSeparator);
|
|
149
|
+
const tail = fraction === void 0 ? "" : `${template.decimalSeparator}${fraction}`;
|
|
150
|
+
return `${template.prefix}${negative ? "-" : ""}${grouped}${tail}${template.suffix}`;
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|
|
153
|
+
export { formatAmountInput, formatWithTemplate, inputPlaceholder, inputStep, moneyTemplateOf, parseAmountInput, parseDecimalInput, parseImportAmount, parsePercentInput, parseRateInput };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { $ as describeValue, E as resolveCurrency, Q as MoneyError, X as mulDiv, k as storageExponent, lt as parseDecimal, s as storedDecimal, t as boundedMoney, ut as pow10 } from "./money-WhnGiI00.js";
|
|
2
|
+
//#region src/iso-minor.ts
|
|
3
|
+
/**
|
|
4
|
+
* Internal: stored units per ISO minor unit at `digits`, `storageScale / 10^digits` (100 for
|
|
5
|
+
* a zero-digit count on a x100 currency, 1 when the digits match the storage exponent).
|
|
6
|
+
* `digits` above the storage exponent has no whole factor and throws `precision`.
|
|
7
|
+
*/
|
|
8
|
+
function isoMinorFactor(info, digits) {
|
|
9
|
+
if (digits > storageExponent(info)) throw new MoneyError("precision", `${info.code} stores ${storageExponent(info)} implied decimals, fewer than ${digits}`);
|
|
10
|
+
return BigInt(info.storageScale) / pow10(digits);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* ISO minor units at the display digits: `round(major x 10^displayDigits)` with `rounding`
|
|
14
|
+
* (default half-up). JPY 150000 gives 1500, KWD 1005 gives 1005, IQD 10000 gives 10, HUF
|
|
15
|
+
* 435050 gives 4351. Not for payment providers (use `/wire`).
|
|
16
|
+
*/
|
|
17
|
+
function toIsoMinor(m, options = {}) {
|
|
18
|
+
const { info } = storedDecimal(m);
|
|
19
|
+
return Number(mulDiv(BigInt(m.amount), 1n, isoMinorFactor(info, info.displayDigits), options.rounding ?? "half-up"));
|
|
20
|
+
}
|
|
21
|
+
/** Integer text as a number (unsafe ones included, refused by the caller), else the text itself. */
|
|
22
|
+
function integerTextValue(text) {
|
|
23
|
+
const parsed = parseDecimal(text, "integer");
|
|
24
|
+
return parsed === null ? text : Number(parsed.units);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* ISO minor units at the display digits (or `digits`) as Money. The value must be a safe
|
|
28
|
+
* integer (number or integer text). With `digits` above the storage exponent the value must
|
|
29
|
+
* land on a whole storage unit, else `precision`.
|
|
30
|
+
*/
|
|
31
|
+
function fromIsoMinor(value, currency, options = {}) {
|
|
32
|
+
const info = resolveCurrency(currency, { allowHistoric: true });
|
|
33
|
+
const integer = typeof value === "string" ? integerTextValue(value) : value;
|
|
34
|
+
if (typeof integer !== "number" || !Number.isSafeInteger(integer)) throw new MoneyError("amount_invalid", `ISO minor amount must be a safe integer, got ${describeValue(value)}`);
|
|
35
|
+
const digits = options.digits ?? info.displayDigits;
|
|
36
|
+
if (![
|
|
37
|
+
0,
|
|
38
|
+
2,
|
|
39
|
+
3
|
|
40
|
+
].includes(digits)) throw new MoneyError("amount_invalid", `digits must be 0, 2 or 3, got ${describeValue(digits)}`);
|
|
41
|
+
const exponent = storageExponent(info);
|
|
42
|
+
if (digits <= exponent) return boundedMoney(BigInt(integer) * isoMinorFactor(info, digits), info);
|
|
43
|
+
const excess = pow10(digits - exponent);
|
|
44
|
+
if (BigInt(integer) % excess !== 0n) throw new MoneyError("precision", `${integer} at ${digits} digits is not a whole ${info.code} storage unit`);
|
|
45
|
+
return boundedMoney(BigInt(integer) / excess, info);
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { isoMinorFactor as n, toIsoMinor as r, fromIsoMinor as t };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const require_money = require("./money-B3wH_Z4S.cjs");
|
|
2
|
+
//#region src/iso-minor.ts
|
|
3
|
+
/**
|
|
4
|
+
* Internal: stored units per ISO minor unit at `digits`, `storageScale / 10^digits` (100 for
|
|
5
|
+
* a zero-digit count on a x100 currency, 1 when the digits match the storage exponent).
|
|
6
|
+
* `digits` above the storage exponent has no whole factor and throws `precision`.
|
|
7
|
+
*/
|
|
8
|
+
function isoMinorFactor(info, digits) {
|
|
9
|
+
if (digits > require_money.storageExponent(info)) throw new require_money.MoneyError("precision", `${info.code} stores ${require_money.storageExponent(info)} implied decimals, fewer than ${digits}`);
|
|
10
|
+
return BigInt(info.storageScale) / require_money.pow10(digits);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* ISO minor units at the display digits: `round(major x 10^displayDigits)` with `rounding`
|
|
14
|
+
* (default half-up). JPY 150000 gives 1500, KWD 1005 gives 1005, IQD 10000 gives 10, HUF
|
|
15
|
+
* 435050 gives 4351. Not for payment providers (use `/wire`).
|
|
16
|
+
*/
|
|
17
|
+
function toIsoMinor(m, options = {}) {
|
|
18
|
+
const { info } = require_money.storedDecimal(m);
|
|
19
|
+
return Number(require_money.mulDiv(BigInt(m.amount), 1n, isoMinorFactor(info, info.displayDigits), options.rounding ?? "half-up"));
|
|
20
|
+
}
|
|
21
|
+
/** Integer text as a number (unsafe ones included, refused by the caller), else the text itself. */
|
|
22
|
+
function integerTextValue(text) {
|
|
23
|
+
const parsed = require_money.parseDecimal(text, "integer");
|
|
24
|
+
return parsed === null ? text : Number(parsed.units);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* ISO minor units at the display digits (or `digits`) as Money. The value must be a safe
|
|
28
|
+
* integer (number or integer text). With `digits` above the storage exponent the value must
|
|
29
|
+
* land on a whole storage unit, else `precision`.
|
|
30
|
+
*/
|
|
31
|
+
function fromIsoMinor(value, currency, options = {}) {
|
|
32
|
+
const info = require_money.resolveCurrency(currency, { allowHistoric: true });
|
|
33
|
+
const integer = typeof value === "string" ? integerTextValue(value) : value;
|
|
34
|
+
if (typeof integer !== "number" || !Number.isSafeInteger(integer)) throw new require_money.MoneyError("amount_invalid", `ISO minor amount must be a safe integer, got ${require_money.describeValue(value)}`);
|
|
35
|
+
const digits = options.digits ?? info.displayDigits;
|
|
36
|
+
if (![
|
|
37
|
+
0,
|
|
38
|
+
2,
|
|
39
|
+
3
|
|
40
|
+
].includes(digits)) throw new require_money.MoneyError("amount_invalid", `digits must be 0, 2 or 3, got ${require_money.describeValue(digits)}`);
|
|
41
|
+
const exponent = require_money.storageExponent(info);
|
|
42
|
+
if (digits <= exponent) return require_money.boundedMoney(BigInt(integer) * isoMinorFactor(info, digits), info);
|
|
43
|
+
const excess = require_money.pow10(digits - exponent);
|
|
44
|
+
if (BigInt(integer) % excess !== 0n) throw new require_money.MoneyError("precision", `${integer} at ${digits} digits is not a whole ${info.code} storage unit`);
|
|
45
|
+
return require_money.boundedMoney(BigInt(integer) / excess, info);
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
Object.defineProperty(exports, "fromIsoMinor", {
|
|
49
|
+
enumerable: true,
|
|
50
|
+
get: function() {
|
|
51
|
+
return fromIsoMinor;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
Object.defineProperty(exports, "isoMinorFactor", {
|
|
55
|
+
enumerable: true,
|
|
56
|
+
get: function() {
|
|
57
|
+
return isoMinorFactor;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
Object.defineProperty(exports, "toIsoMinor", {
|
|
61
|
+
enumerable: true,
|
|
62
|
+
get: function() {
|
|
63
|
+
return toIsoMinor;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { R as DecimalText, a as ReadStoredOptions, i as Money, o as StoredAmount } from "./money-B-3aFjNJ.cjs";
|
|
2
|
+
//#region src/medusa.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* `row['raw_' + key]` when it is not null/undefined (a failure to read it is final: no
|
|
5
|
+
* fallback to the lossy plain key), else `row[key]`, read like `readStored`.
|
|
6
|
+
*/
|
|
7
|
+
declare function readStoredField(row: unknown, key: string, currency: string, options: ReadStoredOptions & {
|
|
8
|
+
mode: 'lenient';
|
|
9
|
+
}): Money | null;
|
|
10
|
+
declare function readStoredField(row: unknown, key: string, currency: string, options?: ReadStoredOptions & {
|
|
11
|
+
mode?: 'strict';
|
|
12
|
+
}): Money;
|
|
13
|
+
declare function readStoredField(row: unknown, key: string, currency: string, options?: ReadStoredOptions): Money | null;
|
|
14
|
+
interface ReadStoredDecimalOptions {
|
|
15
|
+
/** `'strict'` (default) throws `amount_invalid`; `'lenient'` returns null. Never 0. */
|
|
16
|
+
mode?: 'strict' | 'lenient';
|
|
17
|
+
/** Allow negative values. Default true. */
|
|
18
|
+
signed?: boolean;
|
|
19
|
+
/** Names the input field in errors. */
|
|
20
|
+
field?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Like `readStored` but keeps fractions (Medusa fractional totals, ledger literals) and needs
|
|
24
|
+
* no currency: the canonical DecimalText (`{ value: '107.81750000000000000000' }` gives
|
|
25
|
+
* `'107.8175'`). Same shape precedence.
|
|
26
|
+
*/
|
|
27
|
+
declare function readStoredDecimal(raw: unknown, options: ReadStoredDecimalOptions & {
|
|
28
|
+
mode: 'lenient';
|
|
29
|
+
}): DecimalText | null;
|
|
30
|
+
declare function readStoredDecimal(raw: unknown, options?: ReadStoredDecimalOptions & {
|
|
31
|
+
mode?: 'strict';
|
|
32
|
+
}): DecimalText;
|
|
33
|
+
declare function readStoredDecimal(raw: unknown, options?: ReadStoredDecimalOptions): DecimalText | null;
|
|
34
|
+
/**
|
|
35
|
+
* A Medusa amount's literal, verbatim (`'4300.00'` stays `'4300.00'`), for SQL and ledger columns:
|
|
36
|
+
* `.value ?? .numeric ?? raw` when it is an unsigned decimal literal without leading zeros
|
|
37
|
+
* (`isDecimalLiteral(text, { leadingZeros: false })`), else null. A number counts only while all
|
|
38
|
+
* its digits are there (finite; a safe integer when whole) and is spelled `String(n)`. Unlike
|
|
39
|
+
* `readStoredDecimal` it neither canonicalizes nor reads `.raw.value`, a bigint or a BigNumber's
|
|
40
|
+
* `valueOf`: the literal is passed on as Medusa holds it.
|
|
41
|
+
*/
|
|
42
|
+
declare function readStoredLiteral(raw: unknown): string | null;
|
|
43
|
+
/**
|
|
44
|
+
* A variant's `calculated_price` (a number, or an object read through
|
|
45
|
+
* `raw_calculated_amount` / `calculated_amount`) as Money in
|
|
46
|
+
* `firstCurrency(calculated_price.currency_code, fallbackCurrency)`. Null on anything
|
|
47
|
+
* missing or unreadable.
|
|
48
|
+
*/
|
|
49
|
+
declare function readCalculatedPrice(variant: unknown, fallbackCurrency?: string): Money | null;
|
|
50
|
+
/** Medusa's raw amount column shape. */
|
|
51
|
+
interface MedusaRaw {
|
|
52
|
+
readonly value: string;
|
|
53
|
+
readonly precision: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* `{ value, precision }` for a Medusa raw column: `value` is plain decimal text, never an
|
|
57
|
+
* exponent (`1e21` gives `'1000000000000000000000'`). NaN and Infinity throw `amount_invalid`.
|
|
58
|
+
*/
|
|
59
|
+
declare function toMedusaRaw(value: Money | DecimalText | number | bigint, precision?: number): MedusaRaw;
|
|
60
|
+
/** A Money for a Medusa amount input: `'0'` for zero (Medusa drops numeric 0 on order-edit updates), else the number. */
|
|
61
|
+
declare function toMedusaAmountInput(m: Money): number | string;
|
|
62
|
+
/** A per-currency setting: upper-case currency code to stored amount. Build one with `moneyByCurrency`. */
|
|
63
|
+
type MoneyByCurrency = {
|
|
64
|
+
readonly [code: string]: StoredAmount;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Validates an object of `code -> stored amount` (keys through `currencyCode`, values
|
|
68
|
+
* non-negative safe integers), upper-cases the keys and freezes it. A key given twice in
|
|
69
|
+
* different cases throws `currency_invalid`.
|
|
70
|
+
*/
|
|
71
|
+
declare function moneyByCurrency(input: unknown): MoneyByCurrency;
|
|
72
|
+
/** A Medusa price row, or anything shaped like one. */
|
|
73
|
+
interface PriceLike {
|
|
74
|
+
readonly amount: unknown;
|
|
75
|
+
readonly currency_code: string;
|
|
76
|
+
readonly price_list_id?: string | null;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The amount for `currency` from a MoneyByCurrency or a Medusa price list (base prices only:
|
|
80
|
+
* rows with a `price_list_id` are skipped). Exact currency match, any case; the amount is read
|
|
81
|
+
* strictly. Null when there is none.
|
|
82
|
+
*/
|
|
83
|
+
declare function moneyFor(source: MoneyByCurrency | readonly PriceLike[], currency: string): Money | null;
|
|
84
|
+
/** Whether unknown Medusa input holds an amount above zero. `''`, null and garbage are false, never a throw. */
|
|
85
|
+
declare function isPositiveAmount(raw: unknown): boolean;
|
|
86
|
+
//#endregion
|
|
87
|
+
export { isPositiveAmount as a, readCalculatedPrice as c, readStoredLiteral as d, toMedusaAmountInput as f, ReadStoredDecimalOptions as i, readStoredDecimal as l, MoneyByCurrency as n, moneyByCurrency as o, toMedusaRaw as p, PriceLike as r, moneyFor as s, MedusaRaw as t, readStoredField as u };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { R as DecimalText, a as ReadStoredOptions, i as Money, o as StoredAmount } from "./money-B-3aFjNJ.js";
|
|
2
|
+
//#region src/medusa.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* `row['raw_' + key]` when it is not null/undefined (a failure to read it is final: no
|
|
5
|
+
* fallback to the lossy plain key), else `row[key]`, read like `readStored`.
|
|
6
|
+
*/
|
|
7
|
+
declare function readStoredField(row: unknown, key: string, currency: string, options: ReadStoredOptions & {
|
|
8
|
+
mode: 'lenient';
|
|
9
|
+
}): Money | null;
|
|
10
|
+
declare function readStoredField(row: unknown, key: string, currency: string, options?: ReadStoredOptions & {
|
|
11
|
+
mode?: 'strict';
|
|
12
|
+
}): Money;
|
|
13
|
+
declare function readStoredField(row: unknown, key: string, currency: string, options?: ReadStoredOptions): Money | null;
|
|
14
|
+
interface ReadStoredDecimalOptions {
|
|
15
|
+
/** `'strict'` (default) throws `amount_invalid`; `'lenient'` returns null. Never 0. */
|
|
16
|
+
mode?: 'strict' | 'lenient';
|
|
17
|
+
/** Allow negative values. Default true. */
|
|
18
|
+
signed?: boolean;
|
|
19
|
+
/** Names the input field in errors. */
|
|
20
|
+
field?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Like `readStored` but keeps fractions (Medusa fractional totals, ledger literals) and needs
|
|
24
|
+
* no currency: the canonical DecimalText (`{ value: '107.81750000000000000000' }` gives
|
|
25
|
+
* `'107.8175'`). Same shape precedence.
|
|
26
|
+
*/
|
|
27
|
+
declare function readStoredDecimal(raw: unknown, options: ReadStoredDecimalOptions & {
|
|
28
|
+
mode: 'lenient';
|
|
29
|
+
}): DecimalText | null;
|
|
30
|
+
declare function readStoredDecimal(raw: unknown, options?: ReadStoredDecimalOptions & {
|
|
31
|
+
mode?: 'strict';
|
|
32
|
+
}): DecimalText;
|
|
33
|
+
declare function readStoredDecimal(raw: unknown, options?: ReadStoredDecimalOptions): DecimalText | null;
|
|
34
|
+
/**
|
|
35
|
+
* A Medusa amount's literal, verbatim (`'4300.00'` stays `'4300.00'`), for SQL and ledger columns:
|
|
36
|
+
* `.value ?? .numeric ?? raw` when it is an unsigned decimal literal without leading zeros
|
|
37
|
+
* (`isDecimalLiteral(text, { leadingZeros: false })`), else null. A number counts only while all
|
|
38
|
+
* its digits are there (finite; a safe integer when whole) and is spelled `String(n)`. Unlike
|
|
39
|
+
* `readStoredDecimal` it neither canonicalizes nor reads `.raw.value`, a bigint or a BigNumber's
|
|
40
|
+
* `valueOf`: the literal is passed on as Medusa holds it.
|
|
41
|
+
*/
|
|
42
|
+
declare function readStoredLiteral(raw: unknown): string | null;
|
|
43
|
+
/**
|
|
44
|
+
* A variant's `calculated_price` (a number, or an object read through
|
|
45
|
+
* `raw_calculated_amount` / `calculated_amount`) as Money in
|
|
46
|
+
* `firstCurrency(calculated_price.currency_code, fallbackCurrency)`. Null on anything
|
|
47
|
+
* missing or unreadable.
|
|
48
|
+
*/
|
|
49
|
+
declare function readCalculatedPrice(variant: unknown, fallbackCurrency?: string): Money | null;
|
|
50
|
+
/** Medusa's raw amount column shape. */
|
|
51
|
+
interface MedusaRaw {
|
|
52
|
+
readonly value: string;
|
|
53
|
+
readonly precision: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* `{ value, precision }` for a Medusa raw column: `value` is plain decimal text, never an
|
|
57
|
+
* exponent (`1e21` gives `'1000000000000000000000'`). NaN and Infinity throw `amount_invalid`.
|
|
58
|
+
*/
|
|
59
|
+
declare function toMedusaRaw(value: Money | DecimalText | number | bigint, precision?: number): MedusaRaw;
|
|
60
|
+
/** A Money for a Medusa amount input: `'0'` for zero (Medusa drops numeric 0 on order-edit updates), else the number. */
|
|
61
|
+
declare function toMedusaAmountInput(m: Money): number | string;
|
|
62
|
+
/** A per-currency setting: upper-case currency code to stored amount. Build one with `moneyByCurrency`. */
|
|
63
|
+
type MoneyByCurrency = {
|
|
64
|
+
readonly [code: string]: StoredAmount;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Validates an object of `code -> stored amount` (keys through `currencyCode`, values
|
|
68
|
+
* non-negative safe integers), upper-cases the keys and freezes it. A key given twice in
|
|
69
|
+
* different cases throws `currency_invalid`.
|
|
70
|
+
*/
|
|
71
|
+
declare function moneyByCurrency(input: unknown): MoneyByCurrency;
|
|
72
|
+
/** A Medusa price row, or anything shaped like one. */
|
|
73
|
+
interface PriceLike {
|
|
74
|
+
readonly amount: unknown;
|
|
75
|
+
readonly currency_code: string;
|
|
76
|
+
readonly price_list_id?: string | null;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The amount for `currency` from a MoneyByCurrency or a Medusa price list (base prices only:
|
|
80
|
+
* rows with a `price_list_id` are skipped). Exact currency match, any case; the amount is read
|
|
81
|
+
* strictly. Null when there is none.
|
|
82
|
+
*/
|
|
83
|
+
declare function moneyFor(source: MoneyByCurrency | readonly PriceLike[], currency: string): Money | null;
|
|
84
|
+
/** Whether unknown Medusa input holds an amount above zero. `''`, null and garbage are false, never a throw. */
|
|
85
|
+
declare function isPositiveAmount(raw: unknown): boolean;
|
|
86
|
+
//#endregion
|
|
87
|
+
export { isPositiveAmount as a, readCalculatedPrice as c, readStoredLiteral as d, toMedusaAmountInput as f, ReadStoredDecimalOptions as i, readStoredDecimal as l, MoneyByCurrency as n, moneyByCurrency as o, toMedusaRaw as p, PriceLike as r, moneyFor as s, MedusaRaw as t, readStoredField as u };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { $ as describeValue, B as decimalOf, D as sameCurrency, E as resolveCurrency, G as isDecimalLiteral, Q as MoneyError, a as money, b as firstCurrency, d as currencyCode, ft as strictNumberDecimal, i as medusaAmountDecimal, o as readStored, ot as isPositiveDecimal, pt as toDecimalText, r as isStoredAmount, s as storedDecimal } from "./money-WhnGiI00.js";
|
|
2
|
+
import { t as hasOwnKey } from "./own-D7544D3k.js";
|
|
3
|
+
//#region src/medusa.ts
|
|
4
|
+
const isObject = (value) => typeof value === "object" && value !== null;
|
|
5
|
+
/** Internal: `row['raw_' + key]` when it is not null/undefined, else `row[key]` (undefined for a non-object row). */
|
|
6
|
+
function storedFieldValue(row, key) {
|
|
7
|
+
if (!isObject(row)) return void 0;
|
|
8
|
+
const raw = row[`raw_${key}`];
|
|
9
|
+
return raw !== null && raw !== void 0 ? raw : row[key];
|
|
10
|
+
}
|
|
11
|
+
function readStoredField(row, key, currency, options = {}) {
|
|
12
|
+
return readStored(storedFieldValue(row, key), currency, {
|
|
13
|
+
...options,
|
|
14
|
+
field: options.field ?? key
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
function readStoredDecimal(raw, options = {}) {
|
|
18
|
+
const value = medusaAmountDecimal(raw);
|
|
19
|
+
if (value && (options.signed !== false || value.units >= 0n)) return toDecimalText(value);
|
|
20
|
+
if (options.mode === "lenient") return null;
|
|
21
|
+
throw new MoneyError("amount_invalid", `Invalid stored amount: ${describeValue(raw)}`, { field: options.field });
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A Medusa amount's literal, verbatim (`'4300.00'` stays `'4300.00'`), for SQL and ledger columns:
|
|
25
|
+
* `.value ?? .numeric ?? raw` when it is an unsigned decimal literal without leading zeros
|
|
26
|
+
* (`isDecimalLiteral(text, { leadingZeros: false })`), else null. A number counts only while all
|
|
27
|
+
* its digits are there (finite; a safe integer when whole) and is spelled `String(n)`. Unlike
|
|
28
|
+
* `readStoredDecimal` it neither canonicalizes nor reads `.raw.value`, a bigint or a BigNumber's
|
|
29
|
+
* `valueOf`: the literal is passed on as Medusa holds it.
|
|
30
|
+
*/
|
|
31
|
+
function readStoredLiteral(raw) {
|
|
32
|
+
const picked = isObject(raw) ? raw.value ?? raw.numeric ?? raw : raw;
|
|
33
|
+
let literal = null;
|
|
34
|
+
if (typeof picked === "string") literal = picked;
|
|
35
|
+
else if (typeof picked === "number" && strictNumberDecimal(picked) !== null) literal = String(picked);
|
|
36
|
+
return literal !== null && isDecimalLiteral(literal, { leadingZeros: false }) ? literal : null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A variant's `calculated_price` (a number, or an object read through
|
|
40
|
+
* `raw_calculated_amount` / `calculated_amount`) as Money in
|
|
41
|
+
* `firstCurrency(calculated_price.currency_code, fallbackCurrency)`. Null on anything
|
|
42
|
+
* missing or unreadable.
|
|
43
|
+
*/
|
|
44
|
+
function readCalculatedPrice(variant, fallbackCurrency) {
|
|
45
|
+
const price = isObject(variant) ? variant.calculated_price : void 0;
|
|
46
|
+
const priced = isObject(price) ? price : void 0;
|
|
47
|
+
let currency;
|
|
48
|
+
try {
|
|
49
|
+
currency = firstCurrency(priced?.currency_code, fallbackCurrency);
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return priced ? readStoredField(priced, "calculated_amount", currency, { mode: "lenient" }) : readStored(price, currency, { mode: "lenient" });
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* `{ value, precision }` for a Medusa raw column: `value` is plain decimal text, never an
|
|
57
|
+
* exponent (`1e21` gives `'1000000000000000000000'`). NaN and Infinity throw `amount_invalid`.
|
|
58
|
+
*/
|
|
59
|
+
function toMedusaRaw(value, precision = 20) {
|
|
60
|
+
if (!Number.isSafeInteger(precision) || precision < 1) throw new MoneyError("amount_invalid", `precision must be a positive integer, got ${describeValue(precision)}`);
|
|
61
|
+
let decimal;
|
|
62
|
+
if (isObject(value)) {
|
|
63
|
+
storedDecimal(value);
|
|
64
|
+
decimal = {
|
|
65
|
+
units: BigInt(value.amount),
|
|
66
|
+
scale: 0
|
|
67
|
+
};
|
|
68
|
+
} else decimal = decimalOf(value);
|
|
69
|
+
return {
|
|
70
|
+
value: toDecimalText(decimal),
|
|
71
|
+
precision
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** A Money for a Medusa amount input: `'0'` for zero (Medusa drops numeric 0 on order-edit updates), else the number. */
|
|
75
|
+
function toMedusaAmountInput(m) {
|
|
76
|
+
storedDecimal(m);
|
|
77
|
+
return m.amount === 0 ? "0" : m.amount;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Validates an object of `code -> stored amount` (keys through `currencyCode`, values
|
|
81
|
+
* non-negative safe integers), upper-cases the keys and freezes it. A key given twice in
|
|
82
|
+
* different cases throws `currency_invalid`.
|
|
83
|
+
*/
|
|
84
|
+
function moneyByCurrency(input) {
|
|
85
|
+
if (!isObject(input) || Array.isArray(input)) throw new MoneyError("amount_invalid", `Expected an object of currency code to stored amount, got ${describeValue(input)}`);
|
|
86
|
+
const out = {};
|
|
87
|
+
for (const key of Object.keys(input)) {
|
|
88
|
+
const code = currencyCode(key, { field: key });
|
|
89
|
+
if (hasOwnKey(out, code)) throw new MoneyError("currency_invalid", `${code} is given more than once`, { field: key });
|
|
90
|
+
const amount = input[key];
|
|
91
|
+
if (!isStoredAmount(amount)) throw new MoneyError("amount_invalid", `Stored amount must be a non-negative safe integer, got ${describeValue(amount)}`, { field: key });
|
|
92
|
+
out[code] = amount === 0 ? 0 : amount;
|
|
93
|
+
}
|
|
94
|
+
return Object.freeze(out);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The amount for `currency` from a MoneyByCurrency or a Medusa price list (base prices only:
|
|
98
|
+
* rows with a `price_list_id` are skipped). Exact currency match, any case; the amount is read
|
|
99
|
+
* strictly. Null when there is none.
|
|
100
|
+
*/
|
|
101
|
+
function moneyFor(source, currency) {
|
|
102
|
+
const info = resolveCurrency(currency, { allowHistoric: true });
|
|
103
|
+
if (Array.isArray(source)) {
|
|
104
|
+
const row = source.find((p) => isObject(p) && (p.price_list_id === null || p.price_list_id === void 0) && sameCurrency(p.currency_code, info.code));
|
|
105
|
+
return row ? readStored(row.amount, info.code, { field: "amount" }) : null;
|
|
106
|
+
}
|
|
107
|
+
const map = source;
|
|
108
|
+
return hasOwnKey(map, info.code) ? money(map[info.code], info.code) : null;
|
|
109
|
+
}
|
|
110
|
+
/** Whether unknown Medusa input holds an amount above zero. `''`, null and garbage are false, never a throw. */
|
|
111
|
+
function isPositiveAmount(raw) {
|
|
112
|
+
const value = medusaAmountDecimal(raw);
|
|
113
|
+
return value !== null && isPositiveDecimal(value);
|
|
114
|
+
}
|
|
115
|
+
//#endregion
|
|
116
|
+
export { readStoredDecimal as a, storedFieldValue as c, readCalculatedPrice as i, toMedusaAmountInput as l, moneyByCurrency as n, readStoredField as o, moneyFor as r, readStoredLiteral as s, isPositiveAmount as t, toMedusaRaw as u };
|