@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.
Files changed (78) hide show
  1. package/README.md +401 -2
  2. package/dist/ai.cjs +185 -0
  3. package/dist/ai.d.cts +97 -0
  4. package/dist/ai.d.ts +97 -0
  5. package/dist/ai.js +175 -0
  6. package/dist/allocate-CZRg4TOP.cjs +222 -0
  7. package/dist/allocate-X7_jAtOj.js +199 -0
  8. package/dist/amount-BV0O-aN8.cjs +241 -0
  9. package/dist/amount-BoYBMgK9.js +200 -0
  10. package/dist/arithmetic-BhNwisjb.js +132 -0
  11. package/dist/arithmetic-DjT4Kzgt.cjs +233 -0
  12. package/dist/billing.cjs +184 -0
  13. package/dist/billing.d.cts +81 -0
  14. package/dist/billing.d.ts +81 -0
  15. package/dist/billing.js +175 -0
  16. package/dist/commerce.cjs +536 -0
  17. package/dist/commerce.d.cts +293 -0
  18. package/dist/commerce.d.ts +293 -0
  19. package/dist/commerce.js +516 -0
  20. package/dist/format-BdPnijvn.js +99 -0
  21. package/dist/format-DLhno7eX.cjs +128 -0
  22. package/dist/fx.cjs +407 -0
  23. package/dist/fx.d.cts +157 -0
  24. package/dist/fx.d.ts +157 -0
  25. package/dist/fx.js +374 -0
  26. package/dist/index.cjs +352 -0
  27. package/dist/index.d.cts +300 -0
  28. package/dist/index.d.ts +300 -0
  29. package/dist/index.js +276 -0
  30. package/dist/input.cjs +163 -0
  31. package/dist/input.d.cts +203 -0
  32. package/dist/input.d.ts +203 -0
  33. package/dist/input.js +153 -0
  34. package/dist/iso-minor-CqkWDxHS.js +48 -0
  35. package/dist/iso-minor-wSwJN57T.cjs +65 -0
  36. package/dist/medusa-4K8N1pUc.d.cts +87 -0
  37. package/dist/medusa-DBXJXXvF.d.ts +87 -0
  38. package/dist/medusa-DqJY1qpj.js +116 -0
  39. package/dist/medusa-lRY9xlsf.cjs +175 -0
  40. package/dist/money-B-3aFjNJ.d.cts +248 -0
  41. package/dist/money-B-3aFjNJ.d.ts +248 -0
  42. package/dist/money-B3wH_Z4S.cjs +3990 -0
  43. package/dist/money-WhnGiI00.js +3583 -0
  44. package/dist/number-text-BWu67jUq.cjs +145 -0
  45. package/dist/number-text-DE-q4jO5.js +134 -0
  46. package/dist/own-D7544D3k.js +4 -0
  47. package/dist/own-oP9DXFWv.cjs +9 -0
  48. package/dist/percent-C0InC2t2.cjs +43 -0
  49. package/dist/percent-D1VB9xXG.js +20 -0
  50. package/dist/rate-CVeMii4a.cjs +217 -0
  51. package/dist/rate-DBPiYgT1.d.ts +63 -0
  52. package/dist/rate-DicStBue.js +152 -0
  53. package/dist/rate-tqK8ijG0.d.cts +63 -0
  54. package/dist/reporting-CG-ai2sM.d.cts +31 -0
  55. package/dist/reporting-tpGJbqYp.d.ts +31 -0
  56. package/dist/retail-1mu888J1.js +69 -0
  57. package/dist/retail-C_BPLvMj.d.cts +31 -0
  58. package/dist/retail-Cml8YSKg.cjs +86 -0
  59. package/dist/retail-DRUTTQoJ.d.ts +31 -0
  60. package/dist/rounding-DHBtNCHG.cjs +79 -0
  61. package/dist/rounding-g9rvye0k.js +50 -0
  62. package/dist/sql.cjs +290 -0
  63. package/dist/sql.d.cts +147 -0
  64. package/dist/sql.d.ts +147 -0
  65. package/dist/sql.js +271 -0
  66. package/dist/testing.cjs +61995 -0
  67. package/dist/testing.d.cts +125 -0
  68. package/dist/testing.d.ts +125 -0
  69. package/dist/testing.js +61986 -0
  70. package/dist/wire-Bhuut8cW.cjs +2097 -0
  71. package/dist/wire-CaUN9Vf_.d.ts +309 -0
  72. package/dist/wire-sW0yzqth.js +1888 -0
  73. package/dist/wire-xjYXv4go.d.cts +309 -0
  74. package/dist/wire.cjs +37 -0
  75. package/dist/wire.d.cts +2 -0
  76. package/dist/wire.d.ts +2 -0
  77. package/dist/wire.js +2 -0
  78. package/package.json +158 -3
@@ -0,0 +1,128 @@
1
+ const require_money = require("./money-B3wH_Z4S.cjs");
2
+ const require_rounding = require("./rounding-DHBtNCHG.cjs");
3
+ //#region src/format.ts
4
+ function intlDisplay(options) {
5
+ const display = options.currencyDisplay ?? "symbol";
6
+ return display === "symbol+code" ? "symbol" : display;
7
+ }
8
+ function finish(text, code, options) {
9
+ const withCode = options.currencyDisplay === "symbol+code" && !text.includes(code) ? `${text} ${code}` : text;
10
+ return options.minusSign === "−" ? withCode.replace("-", "−") : withCode;
11
+ }
12
+ const asIntlValue = (text) => text;
13
+ /**
14
+ * Money for people: `'$1,234.50'`, `'¥1,200'`, `'KWD 1.005'`, `'-€5.00'`. Locale, currency
15
+ * display, digits and sign are explicit options; the active app locale is the caller's to
16
+ * pass. If Intl throws, `'<CODE> <decimal>'`.
17
+ */
18
+ function formatMoney(m, options = {}) {
19
+ const { info, value } = require_money.storedDecimal(m);
20
+ const whole = require_money.fractionDigitsOf(value) === 0;
21
+ const digits = options.digits === "widen" ? Math.max(info.displayDigits, require_money.fractionDigitsOf(value)) : options.digits === "trim-zero" && whole ? 0 : info.displayDigits;
22
+ const text = require_money.formatDecimal(require_money.roundToScale(value, digits, "half-up"));
23
+ try {
24
+ return finish(require_money.numberFormat(require_money.resolveLocale(options.locale), {
25
+ style: "currency",
26
+ currency: info.code,
27
+ currencyDisplay: intlDisplay(options),
28
+ minimumFractionDigits: digits,
29
+ maximumFractionDigits: digits,
30
+ signDisplay: options.signDisplay ?? "auto"
31
+ }).format(asIntlValue(text)), info.code, options);
32
+ } catch {
33
+ return finish(`${info.code} ${text}`, info.code, options);
34
+ }
35
+ }
36
+ /**
37
+ * A stored amount that may be fractional (a Medusa total, 107.8175; a percentage promotion) for
38
+ * people: rounded half-up to a whole storage unit (`roundToUnit`), then `formatMoney` with `options`.
39
+ * A number must be finite; text is plain decimal; an unknown currency throws `currency_invalid`.
40
+ */
41
+ function formatStored(amount, currency, options = {}) {
42
+ return formatMoney(require_rounding.roundToUnit(amount, currency), options);
43
+ }
44
+ function compact(currency, major, options, significant) {
45
+ return require_money.numberFormat(require_money.resolveLocale(options.locale), {
46
+ style: "currency",
47
+ currency,
48
+ currencyDisplay: intlDisplay(options),
49
+ notation: "compact",
50
+ compactDisplay: "short",
51
+ maximumSignificantDigits: significant,
52
+ signDisplay: options.signDisplay ?? "auto"
53
+ }).format(asIntlValue(major));
54
+ }
55
+ /**
56
+ * Like `formatMoney` below `threshold` major units (default 10000, by magnitude); at or above
57
+ * it, compact short notation with `maximumSignificantDigits` (default 3): `'$12.3K'`.
58
+ */
59
+ function formatMoneyCompact(m, options = {}) {
60
+ const { info, value } = require_money.storedDecimal(m);
61
+ const threshold = require_money.decimalOf(options.threshold ?? 1e4, false, "threshold");
62
+ const magnitude = {
63
+ units: value.units < 0n ? -value.units : value.units,
64
+ scale: value.scale
65
+ };
66
+ if (require_money.compareDecimal(magnitude, threshold) < 0) return formatMoney(m, options);
67
+ try {
68
+ return finish(compact(info.code, require_money.formatDecimal(value), options, options.maximumSignificantDigits ?? 3), info.code, options);
69
+ } catch {
70
+ return formatMoney(m, options);
71
+ }
72
+ }
73
+ /** `'$10.00 – $20.00'` (en dash between spaces), `'$10.00+'` when `high` is null; zero amounts show `zeroLabel` when given. */
74
+ function formatMoneyRange(low, high, options = {}) {
75
+ const lowCode = require_money.storedDecimal(low).info.code;
76
+ const show = (m) => m.amount === 0 && options.zeroLabel !== void 0 ? options.zeroLabel : formatMoney(m, options);
77
+ if (high === null) return `${show(low)}+`;
78
+ const highCode = require_money.storedDecimal(high).info.code;
79
+ if (highCode !== lowCode) throw new require_money.MoneyError("currency_mismatch", `Cannot format a range from ${lowCode} to ${highCode}`);
80
+ return `${show(low)} – ${show(high)}`;
81
+ }
82
+ /**
83
+ * A chart tick formatter for values that already are major units: always compact, with the
84
+ * same rounding as `formatMoneyCompact` (`1234567` gives `'$1.23M'`).
85
+ */
86
+ function moneyAxisFormatter(currency, options = {}) {
87
+ const { code } = require_money.resolveCurrency(currency, { allowHistoric: true });
88
+ return (major) => {
89
+ if (!Number.isFinite(major)) return `${code} ${major}`;
90
+ const text = require_money.formatDecimal(require_money.decimalFromNumber(major));
91
+ try {
92
+ return finish(compact(code, text, options, 3), code, options);
93
+ } catch {
94
+ return `${code} ${text}`;
95
+ }
96
+ };
97
+ }
98
+ //#endregion
99
+ Object.defineProperty(exports, "formatMoney", {
100
+ enumerable: true,
101
+ get: function() {
102
+ return formatMoney;
103
+ }
104
+ });
105
+ Object.defineProperty(exports, "formatMoneyCompact", {
106
+ enumerable: true,
107
+ get: function() {
108
+ return formatMoneyCompact;
109
+ }
110
+ });
111
+ Object.defineProperty(exports, "formatMoneyRange", {
112
+ enumerable: true,
113
+ get: function() {
114
+ return formatMoneyRange;
115
+ }
116
+ });
117
+ Object.defineProperty(exports, "formatStored", {
118
+ enumerable: true,
119
+ get: function() {
120
+ return formatStored;
121
+ }
122
+ });
123
+ Object.defineProperty(exports, "moneyAxisFormatter", {
124
+ enumerable: true,
125
+ get: function() {
126
+ return moneyAxisFormatter;
127
+ }
128
+ });
package/dist/fx.cjs ADDED
@@ -0,0 +1,407 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_money = require("./money-B3wH_Z4S.cjs");
3
+ const require_rounding = require("./rounding-DHBtNCHG.cjs");
4
+ const require_own = require("./own-oP9DXFWv.cjs");
5
+ const require_medusa = require("./medusa-lRY9xlsf.cjs");
6
+ const require_rate = require("./rate-CVeMii4a.cjs");
7
+ const require_retail = require("./retail-Cml8YSKg.cjs");
8
+ //#region src/fx/convert.ts
9
+ const infoOf = (code) => require_money.resolveCurrency(code, { allowHistoric: true });
10
+ /**
11
+ * The factor taking a stored `from` amount to a stored `to` amount for the major rate `r`
12
+ * (1 from = r to): `{ r.n x storageScale(to), r.d x storageScale(from) }`. Replaces Medusa
13
+ * `storedRateFactor`, `regionSwitchFactor`, `conversionFactor`.
14
+ */
15
+ function convertFactor(from, to, r) {
16
+ const { numerator, denominator } = require_rate.checkRate(r);
17
+ return {
18
+ numerator: numerator * BigInt(infoOf(to).storageScale),
19
+ denominator: denominator * BigInt(infoOf(from).storageScale)
20
+ };
21
+ }
22
+ /**
23
+ * `m` in `to` at the major rate `r` (1 m.currency = r to): exact `m x convertFactor`, rounded
24
+ * once to `unit`, then `retail`. The same currency returns `m` unchanged.
25
+ */
26
+ function convert(m, to, r, options = {}) {
27
+ const { info: source } = require_money.storedDecimal(m);
28
+ const target = infoOf(to);
29
+ if (source.code === target.code) return m;
30
+ const { numerator, denominator } = convertFactor(source.code, target.code, r);
31
+ const units = require_rounding.mulDivToUnit(BigInt(m.amount), numerator, denominator, target, options.unit ?? "currency", options.rounding);
32
+ return require_money.boundedMoney(options.retail === void 0 ? units : require_retail.retailUnits(units, 1n, target, options.retail), target);
33
+ }
34
+ /**
35
+ * The major rate two prices imply, `major(b) / major(a)`, exactly and in lowest terms (JPY
36
+ * 150000 against USD 1000 gives 150). Both must be positive, else `rate_invalid`.
37
+ */
38
+ function impliedRate(a, b) {
39
+ const { info: left } = require_money.storedDecimal(a);
40
+ const { info: right } = require_money.storedDecimal(b);
41
+ if (a.amount <= 0 || b.amount <= 0) throw new require_money.MoneyError("rate_invalid", `An implied rate needs two positive amounts, got ${a.amount} and ${b.amount}`);
42
+ const numerator = BigInt(b.amount) * BigInt(left.storageScale);
43
+ const denominator = BigInt(a.amount) * BigInt(right.storageScale);
44
+ const divisor = require_money.gcd(numerator, denominator);
45
+ return {
46
+ numerator: numerator / divisor,
47
+ denominator: denominator / divisor
48
+ };
49
+ }
50
+ //#endregion
51
+ //#region src/fx/format-rate.ts
52
+ /** `floor(log10(n / d))` of a positive ratio, exactly. */
53
+ function exponentOf(n, d) {
54
+ let e = n.toString().length - d.toString().length;
55
+ const at = (k) => k >= 0 ? require_money.compareRatio(n, d, require_money.pow10(k), 1n) : require_money.compareRatio(n, d, 1n, require_money.pow10(-k));
56
+ if (at(e) < 0) e -= 1;
57
+ return e;
58
+ }
59
+ const asIntlValue = (text) => text;
60
+ /**
61
+ * `'1 USD = 83.42 INR'`. `decimals: 'auto'` (default) shows 4 significant digits with at
62
+ * least 2 and at most 8 fraction digits; a number fixes them. Rounded half-up exactly, then
63
+ * grouped for `locale` (default `'en-US'`).
64
+ */
65
+ function formatRate(r, from, to, options = {}) {
66
+ const { numerator, denominator } = require_rate.checkRate(r);
67
+ const fromCode = require_money.resolveCurrency(from, { allowHistoric: true }).code;
68
+ const toCode = require_money.resolveCurrency(to, { allowHistoric: true }).code;
69
+ const requested = options.decimals ?? "auto";
70
+ let digits;
71
+ if (requested === "auto") digits = Math.min(8, Math.max(2, 3 - exponentOf(numerator, denominator)));
72
+ else if (Number.isSafeInteger(requested) && requested >= 0 && requested <= 20) digits = requested;
73
+ else throw new require_money.MoneyError("amount_invalid", `decimals must be 'auto' or an integer from 0 to 20, got ${require_money.describeValue(requested)}`);
74
+ const value = require_money.ratioToDecimal(numerator, denominator, digits);
75
+ return `1 ${fromCode} = ${require_money.numberFormat(require_money.resolveLocale(options.locale), {
76
+ minimumFractionDigits: digits,
77
+ maximumFractionDigits: digits
78
+ }).format(asIntlValue(require_money.formatDecimal(value)))} ${toCode}`;
79
+ }
80
+ //#endregion
81
+ //#region src/fx/plausibility.ts
82
+ /** Medusa PRICE_SEED_RATE_MAX_DEVIATION's default. */
83
+ const DEFAULT_RATE_MAX_DEVIATION = 2;
84
+ /** chooseFactor's tolerance between the conventional factor and the observed median. */
85
+ const FACTOR_TOLERANCE = .02;
86
+ /** How far apart two positive rates are, as a factor >= 1 (2 = one is double the other). */
87
+ function deviationFactor(stored, reference) {
88
+ const ratio = stored / reference;
89
+ return ratio >= 1 ? ratio : 1 / ratio;
90
+ }
91
+ const isPositiveFinite$1 = (n) => typeof n === "number" && Number.isFinite(n) && n > 0;
92
+ /** A positive finite number as an exact Decimal; anything else throws `amount_invalid` naming `name`. */
93
+ function positiveDecimal(n, name) {
94
+ if (!isPositiveFinite$1(n)) throw new require_money.MoneyError("amount_invalid", `${name} must be a positive finite number, got ${require_money.describeValue(n)}`);
95
+ return require_money.decimalFromNumber(n);
96
+ }
97
+ function validRate(r) {
98
+ try {
99
+ return require_rate.checkRate(r);
100
+ } catch {
101
+ return null;
102
+ }
103
+ }
104
+ /**
105
+ * One locked rate against the reference, in Medusa `classifyRates` order: invalid (null or not
106
+ * a rate) is refused `invalid`; no reference is unverifiable `no_reference`; no positive
107
+ * reference for the currency is unverifiable `no_pair`; a deviation strictly above
108
+ * `maxDeviation` (default 2) is refused `implausible`; else plausible.
109
+ */
110
+ function classifyRate(r, code, reference, options = {}) {
111
+ const currency = code.toLowerCase();
112
+ const max = positiveDecimal(options.maxDeviation ?? 2, "maxDeviation");
113
+ const valid = validRate(r);
114
+ if (!valid) return {
115
+ status: "refused",
116
+ reason: "invalid",
117
+ currency,
118
+ stored: NaN
119
+ };
120
+ const stored = require_rate.rateToNumber(valid, { decimals: "exact" });
121
+ if (!reference) return {
122
+ status: "unverifiable",
123
+ reason: "no_reference",
124
+ currency,
125
+ stored
126
+ };
127
+ const ref = require_own.hasOwnKey(reference.rates, currency) ? reference.rates[currency] : void 0;
128
+ if (!isPositiveFinite$1(ref)) return {
129
+ status: "unverifiable",
130
+ reason: "no_pair",
131
+ currency,
132
+ stored
133
+ };
134
+ const refValue = require_money.decimalFromNumber(ref);
135
+ const top = valid.numerator * require_money.pow10(refValue.scale);
136
+ const bottom = valid.denominator * refValue.units;
137
+ const maxD = require_money.pow10(max.scale);
138
+ if (require_money.compareRatio(top, bottom, max.units, maxD) > 0 || require_money.compareRatio(bottom, top, max.units, maxD) > 0) return {
139
+ status: "refused",
140
+ reason: "implausible",
141
+ currency,
142
+ stored,
143
+ reference: ref,
144
+ factor: deviationFactor(stored, ref),
145
+ source: reference.source
146
+ };
147
+ return {
148
+ status: "plausible",
149
+ currency,
150
+ stored
151
+ };
152
+ }
153
+ /** `|value - c| / c <= tolerance`, exactly (`c` positive). */
154
+ function closeTo(value, c, tolerance) {
155
+ const scaledC = c.numerator * require_money.pow10(value.scale);
156
+ const gap = value.units * c.denominator - scaledC;
157
+ return require_money.compareRatio((gap < 0n ? -gap : gap) * require_money.pow10(tolerance.scale), 1n, tolerance.units * scaledC, 1n) <= 0;
158
+ }
159
+ /**
160
+ * The storage factor confirmed by the store's own prices (Medusa `chooseFactor`): the
161
+ * candidates `[c, c / 100, c x 100]` against the upper median of the finite positive observed
162
+ * ratios; the first within `tolerance` (default 0.02) wins, none gives null. No observations
163
+ * trusts `conventional`.
164
+ */
165
+ function confirmStoredFactor(conventional, observedRatios, options = {}) {
166
+ const c = require_rate.checkRate(conventional);
167
+ const tolerance = require_money.decimalFromNumber(nonNegative(options.tolerance ?? .02));
168
+ const ratios = observedRatios.filter(isPositiveFinite$1).sort((a, b) => a - b);
169
+ if (ratios.length === 0) return c;
170
+ const median = require_money.decimalFromNumber(ratios[Math.floor(ratios.length / 2)]);
171
+ return [
172
+ c,
173
+ {
174
+ numerator: c.numerator,
175
+ denominator: c.denominator * 100n
176
+ },
177
+ {
178
+ numerator: c.numerator * 100n,
179
+ denominator: c.denominator
180
+ }
181
+ ].find((candidate) => closeTo(median, candidate, tolerance)) ?? null;
182
+ }
183
+ function nonNegative(n) {
184
+ if (typeof n !== "number" || !Number.isFinite(n) || n < 0) throw new require_money.MoneyError("amount_invalid", `tolerance must be a non-negative finite number, got ${require_money.describeValue(n)}`);
185
+ return n;
186
+ }
187
+ /**
188
+ * The upper median of `rates` and which of them lie within `tolerance` (default 0.01) of it:
189
+ * accepted iff `|r / median - 1| <= tolerance`, exactly. No rates: `{ median: null, accepted: [] }`.
190
+ */
191
+ function rateConsensus(rates, options = {}) {
192
+ const valid = rates.map(require_rate.checkRate);
193
+ const tolerance = require_money.decimalFromNumber(nonNegative(options.tolerance ?? .01));
194
+ if (valid.length === 0) return {
195
+ median: null,
196
+ accepted: []
197
+ };
198
+ const sorted = [...valid].sort((a, b) => require_money.compareRatio(a.numerator, a.denominator, b.numerator, b.denominator));
199
+ const median = sorted[Math.floor(sorted.length / 2)];
200
+ return {
201
+ median,
202
+ accepted: valid.map((r) => {
203
+ const gap = r.numerator * median.denominator - median.numerator * r.denominator;
204
+ const scale = require_money.pow10(tolerance.scale);
205
+ return require_money.compareRatio((gap < 0n ? -gap : gap) * scale, 1n, tolerance.units * median.numerator * r.denominator, 1n) <= 0;
206
+ })
207
+ };
208
+ }
209
+ //#endregion
210
+ //#region src/fx/reporting.ts
211
+ const FACTOR_DIGITS = 20;
212
+ /**
213
+ * The reporting factors of a store whose locked rates say `1 default = n/d <code>`:
214
+ * `factor(code) = storageScale(default) / storageScale(code) x d / n`, rounded half-up to 20
215
+ * fraction digits; the default currency is `'1'`. Rows for the default currency, unknown
216
+ * codes or invalid rates (see `rateFromRow`) are skipped, so those currencies have no factor.
217
+ * Port of Medusa `storeTotalFactors`, exact (it measured the factor at a 1e9 reference).
218
+ */
219
+ function storeFactors(defaultCurrency, rows) {
220
+ const base = require_money.resolveCurrency(defaultCurrency, { allowHistoric: true });
221
+ const factors = { [base.code.toLowerCase()]: "1" };
222
+ for (const row of rows) {
223
+ const info = require_money.findCurrency(row?.currency_code, { allowHistoric: true });
224
+ const r = require_rate.rateFromRow(row);
225
+ if (!info || !r || info.code === base.code) continue;
226
+ const units = require_money.mulDiv(BigInt(base.storageScale) * r.denominator, require_money.pow10(FACTOR_DIGITS), BigInt(info.storageScale) * r.numerator, "half-up");
227
+ factors[info.code.toLowerCase()] = require_money.toDecimalText({
228
+ units,
229
+ scale: FACTOR_DIGITS
230
+ });
231
+ }
232
+ return {
233
+ base: base.code,
234
+ factors: Object.freeze(factors)
235
+ };
236
+ }
237
+ /**
238
+ * The sum of `items` in `table.base`: each converted with its currency's factor and rounded
239
+ * half-up to a storage unit (per item, as customer-stats rounds per order), then summed.
240
+ * Items whose currency has no factor are excluded and reported in `missing`.
241
+ */
242
+ function toReporting(items, table) {
243
+ const base = require_money.resolveCurrency(table.base, { allowHistoric: true });
244
+ const missing = /* @__PURE__ */ new Set();
245
+ let total = 0n;
246
+ for (const item of items) {
247
+ const { info } = require_money.storedDecimal(item);
248
+ const key = info.code.toLowerCase();
249
+ const text = require_own.hasOwnKey(table.factors, key) ? table.factors[key] : void 0;
250
+ if (text === void 0) {
251
+ missing.add(info.code);
252
+ continue;
253
+ }
254
+ const factor = require_money.decimalOf(text, false, `factors.${key}`);
255
+ total += require_money.mulDiv(BigInt(item.amount), factor.units, require_money.pow10(factor.scale), "half-up");
256
+ }
257
+ return {
258
+ total: require_money.boundedMoney(total, base),
259
+ complete: missing.size === 0,
260
+ missing: [...missing].sort()
261
+ };
262
+ }
263
+ //#endregion
264
+ //#region src/fx/settings.ts
265
+ const RATE_MODES = ["manual", "dynamic"];
266
+ const PRICE_ROUNDINGS = ["none", "nearest"];
267
+ /** `store_currency_rate.rate_mode` as readers coerce it: `'manual'` exactly, else `'dynamic'`. */
268
+ function parseRateMode(value) {
269
+ return value === "manual" ? "manual" : "dynamic";
270
+ }
271
+ /** `store_currency_rate.rounding` as readers coerce it: `'nearest'` exactly, else `'none'`. */
272
+ function parsePriceRounding(value) {
273
+ return value === "nearest" ? "nearest" : "none";
274
+ }
275
+ /**
276
+ * The amount a per-currency setting holds for `currency`: the listed entry, else the explicit
277
+ * fallback: `'refuse'` gives null; `'major'` is that many major units of `currency` itself
278
+ * (the store-credit cap `{ kind: 'major', amount: '15000' }`); `'convert'` converts the
279
+ * `from` entry at `rate` (null when there is no `from` entry). Never a silent default.
280
+ */
281
+ function resolveMoneyByCurrency(setting, currency, options) {
282
+ const info = require_money.resolveCurrency(currency, { allowHistoric: true });
283
+ const listed = require_medusa.moneyFor(setting, info.code);
284
+ if (listed) return {
285
+ money: listed,
286
+ source: "listed"
287
+ };
288
+ const fallback = options.fallback;
289
+ if (fallback.kind === "major") return {
290
+ money: require_money.fromMajor(fallback.amount, info.code),
291
+ source: "fallback"
292
+ };
293
+ if (fallback.kind === "convert") {
294
+ const base = require_medusa.moneyFor(setting, fallback.from);
295
+ return base ? {
296
+ money: convert(base, info.code, fallback.rate, { retail: fallback.retail }),
297
+ source: "fallback"
298
+ } : null;
299
+ }
300
+ return null;
301
+ }
302
+ //#endregion
303
+ //#region src/fx/tables.ts
304
+ /** 1 USD = rate units of the currency, exact (see data/pegs.ts for the sources). Upper-case keys. */
305
+ const USD_PEGS = Object.freeze(Object.fromEntries(Object.entries({
306
+ AED: [36725n, 10000n],
307
+ SAR: [375n, 100n],
308
+ QAR: [364n, 100n],
309
+ BHD: [376n, 1000n],
310
+ OMR: [3845n, 10000n]
311
+ }).map(([code, [numerator, denominator]]) => [code, Object.freeze({
312
+ numerator,
313
+ denominator
314
+ })])));
315
+ const isPositiveFinite = (n) => typeof n === "number" && Number.isFinite(n) && n > 0;
316
+ /** A feed's rate table with lower-case keys and only finite positive numbers kept (Medusa `lowerKeys`). Never throws. */
317
+ function sanitizeRateTable(raw) {
318
+ const out = {};
319
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return out;
320
+ for (const [key, value] of Object.entries(raw)) if (isPositiveFinite(value)) out[key.toLowerCase()] = value;
321
+ return out;
322
+ }
323
+ /** `{ ...fallback, ...primary }`: the primary feed (ECB) wins every currency it publishes. */
324
+ function mergeRateTables(primary, fallback) {
325
+ return {
326
+ ...fallback,
327
+ ...primary
328
+ };
329
+ }
330
+ function own(table, code) {
331
+ for (const key of [code.toUpperCase(), code.toLowerCase()]) if (require_own.hasOwnKey(table, key)) return table[key];
332
+ }
333
+ /** Units of `code` per 1 USD: the feed's finite positive value, else its peg, else undefined. */
334
+ function perUsd(code, ratesPerUsd, pegs) {
335
+ const live = own(ratesPerUsd, code);
336
+ if (isPositiveFinite(live)) {
337
+ const d = require_money.decimalFromNumber(live);
338
+ return {
339
+ numerator: d.units,
340
+ denominator: require_money.pow10(d.scale)
341
+ };
342
+ }
343
+ const peg = own(pegs, code);
344
+ return peg === void 0 ? void 0 : require_rate.checkRate(peg);
345
+ }
346
+ /**
347
+ * The rate 1 `from` = ? `to` when the live pair is unavailable, through the USD pegs
348
+ * (orch `resolveRateMajor`): a pegged `to` gives the peg from USD, else `(from -> USD) x peg`
349
+ * with `from -> USD` read from `ratesPerUsd` (units per 1 USD, any key case) or from `from`'s
350
+ * own peg; a pegged `from` into USD gives the inverse peg (new, symmetric). The same code
351
+ * gives 1/1; anything else null.
352
+ */
353
+ function pegFallback(from, to, ratesPerUsd, pegs = USD_PEGS) {
354
+ const f = from.toUpperCase();
355
+ const t = to.toUpperCase();
356
+ if (f === t) return {
357
+ numerator: 1n,
358
+ denominator: 1n
359
+ };
360
+ const toPeg = own(pegs, t);
361
+ if (toPeg !== void 0) {
362
+ const peg = require_rate.checkRate(toPeg);
363
+ if (f === "USD") return peg;
364
+ const source = perUsd(f, ratesPerUsd, pegs);
365
+ if (!source) return null;
366
+ return {
367
+ numerator: peg.numerator * source.denominator,
368
+ denominator: peg.denominator * source.numerator
369
+ };
370
+ }
371
+ const fromPeg = own(pegs, f);
372
+ return fromPeg !== void 0 && t === "USD" ? require_rate.invertRate(fromPeg) : null;
373
+ }
374
+ //#endregion
375
+ exports.DEFAULT_RATE_MAX_DEVIATION = DEFAULT_RATE_MAX_DEVIATION;
376
+ exports.FACTOR_TOLERANCE = FACTOR_TOLERANCE;
377
+ exports.PRICE_ROUNDINGS = PRICE_ROUNDINGS;
378
+ exports.RATE_BOUNDS = require_rate.RATE_BOUNDS;
379
+ exports.RATE_MODES = RATE_MODES;
380
+ exports.RATE_REF = require_rate.RATE_REF;
381
+ exports.USD_PEGS = USD_PEGS;
382
+ exports.classifyRate = classifyRate;
383
+ exports.confirmStoredFactor = confirmStoredFactor;
384
+ exports.convert = convert;
385
+ exports.convertFactor = convertFactor;
386
+ exports.crossRate = require_rate.crossRate;
387
+ exports.deviationFactor = deviationFactor;
388
+ exports.formatRate = formatRate;
389
+ exports.impliedRate = impliedRate;
390
+ exports.invertRate = require_rate.invertRate;
391
+ exports.mergeRateTables = mergeRateTables;
392
+ exports.parsePriceRounding = parsePriceRounding;
393
+ exports.parseRateMode = parseRateMode;
394
+ exports.pegFallback = pegFallback;
395
+ exports.priceRoundingParams = require_retail.priceRoundingParams;
396
+ exports.rate = require_rate.rate;
397
+ exports.rateConsensus = rateConsensus;
398
+ exports.rateEquals = require_rate.rateEquals;
399
+ exports.rateFromDecimal = require_rate.rateFromDecimal;
400
+ exports.rateFromRow = require_rate.rateFromRow;
401
+ exports.rateToDecimal = require_rate.rateToDecimal;
402
+ exports.rateToNumber = require_rate.rateToNumber;
403
+ exports.resolveMoneyByCurrency = resolveMoneyByCurrency;
404
+ exports.roundRetail = require_retail.roundRetail;
405
+ exports.sanitizeRateTable = sanitizeRateTable;
406
+ exports.storeFactors = storeFactors;
407
+ exports.toReporting = toReporting;