subunit-money 3.0.0 → 3.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/dist/index.cjs ADDED
@@ -0,0 +1,1314 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __typeError = (msg) => {
7
+ throw TypeError(msg);
8
+ };
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
23
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
24
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
25
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
26
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
27
+
28
+ // lib/index.ts
29
+ var index_exports = {};
30
+ __export(index_exports, {
31
+ AmountError: () => AmountError,
32
+ CurrencyMismatchError: () => CurrencyMismatchError,
33
+ CurrencyUnknownError: () => CurrencyUnknownError,
34
+ ExchangeRateError: () => ExchangeRateError,
35
+ ExchangeRateService: () => ExchangeRateService,
36
+ Money: () => Money,
37
+ MoneyConverter: () => MoneyConverter,
38
+ SubunitError: () => SubunitError,
39
+ clearCurrencies: () => clearCurrencies,
40
+ getAllCurrencies: () => getAllCurrencies,
41
+ getCurrency: () => getCurrency,
42
+ hasCurrency: () => hasCurrency,
43
+ loadCurrencyMap: () => loadCurrencyMap,
44
+ registerCurrency: () => registerCurrency
45
+ });
46
+ module.exports = __toCommonJS(index_exports);
47
+
48
+ // lib/errors.ts
49
+ var CurrencyMismatchError = class _CurrencyMismatchError extends TypeError {
50
+ constructor(fromCurrency, toCurrency) {
51
+ super(`Cannot operate on ${fromCurrency} and ${toCurrency} - currencies must match`);
52
+ this.name = "CurrencyMismatchError";
53
+ this.fromCurrency = fromCurrency;
54
+ this.toCurrency = toCurrency;
55
+ Error.captureStackTrace?.(this, _CurrencyMismatchError);
56
+ }
57
+ };
58
+ var CurrencyUnknownError = class _CurrencyUnknownError extends TypeError {
59
+ constructor(currency) {
60
+ super(`Unknown currency '${currency}' - register it first with Money.registerCurrency()`);
61
+ this.name = "CurrencyUnknownError";
62
+ this.currency = currency;
63
+ Error.captureStackTrace?.(this, _CurrencyUnknownError);
64
+ }
65
+ };
66
+ var SubunitError = class _SubunitError extends RangeError {
67
+ constructor(currency, maxDecimals) {
68
+ super(`${currency} only supports ${maxDecimals} decimal place(s)`);
69
+ this.name = "SubunitError";
70
+ this.currency = currency;
71
+ this.maxDecimals = maxDecimals;
72
+ Error.captureStackTrace?.(this, _SubunitError);
73
+ }
74
+ };
75
+ var AmountError = class _AmountError extends TypeError {
76
+ constructor(amount) {
77
+ super(`Invalid amount: ${JSON.stringify(amount)}`);
78
+ this.name = "AmountError";
79
+ this.amount = amount;
80
+ Error.captureStackTrace?.(this, _AmountError);
81
+ }
82
+ };
83
+ var ExchangeRateError = class _ExchangeRateError extends Error {
84
+ constructor(fromCurrency, toCurrency) {
85
+ super(`No exchange rate available from ${fromCurrency} to ${toCurrency}`);
86
+ this.name = "ExchangeRateError";
87
+ this.fromCurrency = fromCurrency;
88
+ this.toCurrency = toCurrency;
89
+ Error.captureStackTrace?.(this, _ExchangeRateError);
90
+ }
91
+ };
92
+
93
+ // lib/currency.ts
94
+ var currencies = /* @__PURE__ */ new Map();
95
+ function registerCurrency(code, decimalDigits, displayDecimals) {
96
+ currencies.set(code, { code, decimalDigits, displayDecimals });
97
+ }
98
+ function getCurrency(code) {
99
+ return currencies.get(code);
100
+ }
101
+ function hasCurrency(code) {
102
+ return currencies.has(code);
103
+ }
104
+ function getAllCurrencies() {
105
+ return Array.from(currencies.values()).sort((a, b) => a.code.localeCompare(b.code));
106
+ }
107
+ function loadCurrencyMap(map) {
108
+ for (const [code, data] of Object.entries(map)) {
109
+ registerCurrency(code, data.decimal_digits);
110
+ }
111
+ }
112
+ function clearCurrencies() {
113
+ currencies.clear();
114
+ }
115
+
116
+ // lib/money.ts
117
+ var _subunits, _currencyDef, _Money_instances, parseAmount_fn, _Money_static, formatForDisplay_fn, assertSameCurrency_fn, getInternalValue_fn, parseFactor_fn, roundedDivide_fn, createFromSubunits_fn, formatSubunits_fn;
118
+ var _Money = class _Money {
119
+ /**
120
+ * Create a new Money instance.
121
+ *
122
+ * @param currency - ISO 4217 currency code (must be registered)
123
+ * @param amount - The amount as a number or string
124
+ * @throws {CurrencyUnknownError} If the currency is not registered
125
+ * @throws {AmountError} If the amount is not a valid number
126
+ * @throws {SubunitError} If the amount has more decimals than the currency allows
127
+ */
128
+ constructor(currency, amount) {
129
+ __privateAdd(this, _Money_instances);
130
+ // Private BigInt storage - stores currency native subunits directly
131
+ __privateAdd(this, _subunits);
132
+ __privateAdd(this, _currencyDef);
133
+ var _a, _b;
134
+ const currencyDef = getCurrency(currency);
135
+ if (!currencyDef) {
136
+ throw new CurrencyUnknownError(currency);
137
+ }
138
+ this.currency = currency;
139
+ __privateSet(this, _currencyDef, currencyDef);
140
+ __privateSet(this, _subunits, __privateMethod(this, _Money_instances, parseAmount_fn).call(this, amount));
141
+ this.amount = __privateMethod(_a = _Money, _Money_static, formatSubunits_fn).call(_a, __privateGet(this, _subunits), currencyDef);
142
+ this.displayAmount = __privateMethod(_b = _Money, _Money_static, formatForDisplay_fn).call(_b, this.amount, currencyDef);
143
+ }
144
+ /**
145
+ * Custom console inspection for Node.js.
146
+ * Shows the amount and currency instead of just the class name.
147
+ */
148
+ [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
149
+ return `Money { amount: '${this.displayAmount}', currency: '${this.currency}' }`;
150
+ }
151
+ // ============ Arithmetic Operations ============
152
+ /**
153
+ * Add another Money amount.
154
+ * @throws {CurrencyMismatchError} If currencies don't match
155
+ */
156
+ add(other) {
157
+ var _a, _b;
158
+ __privateMethod(this, _Money_instances, assertSameCurrency_fn).call(this, other);
159
+ const result = __privateGet(this, _subunits) + __privateMethod(_a = other, _Money_instances, getInternalValue_fn).call(_a);
160
+ return __privateMethod(_b = _Money, _Money_static, createFromSubunits_fn).call(_b, result, this.currency, __privateGet(this, _currencyDef));
161
+ }
162
+ /**
163
+ * Subtract another Money amount.
164
+ * @throws {CurrencyMismatchError} If currencies don't match
165
+ */
166
+ subtract(other) {
167
+ var _a, _b;
168
+ __privateMethod(this, _Money_instances, assertSameCurrency_fn).call(this, other);
169
+ const result = __privateGet(this, _subunits) - __privateMethod(_a = other, _Money_instances, getInternalValue_fn).call(_a);
170
+ return __privateMethod(_b = _Money, _Money_static, createFromSubunits_fn).call(_b, result, this.currency, __privateGet(this, _currencyDef));
171
+ }
172
+ /**
173
+ * Multiply by a factor.
174
+ *
175
+ * DESIGN: Rounds immediately after multiplication using banker's rounding
176
+ * (round half-to-even). This prevents the "split penny problem".
177
+ */
178
+ multiply(factor) {
179
+ var _a, _b, _c;
180
+ if (typeof factor !== "number" || !Number.isFinite(factor)) {
181
+ throw new TypeError(`Factor must be a finite number, got: ${factor}`);
182
+ }
183
+ const { value: factorValue, scale } = __privateMethod(_a = _Money, _Money_static, parseFactor_fn).call(_a, factor);
184
+ const product = __privateGet(this, _subunits) * factorValue;
185
+ const divisor = 10n ** scale;
186
+ const result = __privateMethod(_b = _Money, _Money_static, roundedDivide_fn).call(_b, product, divisor);
187
+ return __privateMethod(_c = _Money, _Money_static, createFromSubunits_fn).call(_c, result, this.currency, __privateGet(this, _currencyDef));
188
+ }
189
+ /**
190
+ * Allocate this amount proportionally.
191
+ * Handles remainder distribution to avoid losing pennies.
192
+ *
193
+ * @param proportions - Array of proportions (e.g., [1, 1, 1] for three-way split)
194
+ * @returns Array of Money objects that sum to the original amount
195
+ */
196
+ allocate(proportions) {
197
+ if (!Array.isArray(proportions) || proportions.length === 0) {
198
+ throw new TypeError("Proportions must be a non-empty array");
199
+ }
200
+ for (const p of proportions) {
201
+ if (typeof p !== "number" || !Number.isFinite(p) || p < 0) {
202
+ throw new TypeError("All proportions must be non-negative finite numbers");
203
+ }
204
+ }
205
+ const total = proportions.reduce((sum, p) => sum + p, 0);
206
+ if (total <= 0) {
207
+ throw new TypeError("Sum of proportions must be positive");
208
+ }
209
+ const totalSubunits = __privateGet(this, _subunits);
210
+ const allocations = proportions.map((p) => {
211
+ return totalSubunits * BigInt(Math.round(p * 1e6)) / BigInt(Math.round(total * 1e6));
212
+ });
213
+ let remainder = totalSubunits - allocations.reduce((sum, a) => sum + a, 0n);
214
+ let i = 0;
215
+ while (remainder > 0n) {
216
+ allocations[i % allocations.length] += 1n;
217
+ remainder -= 1n;
218
+ i++;
219
+ }
220
+ while (remainder < 0n) {
221
+ allocations[i % allocations.length] -= 1n;
222
+ remainder += 1n;
223
+ i++;
224
+ }
225
+ return allocations.map((subunits) => {
226
+ var _a;
227
+ return __privateMethod(_a = _Money, _Money_static, createFromSubunits_fn).call(_a, subunits, this.currency, __privateGet(this, _currencyDef));
228
+ });
229
+ }
230
+ // ============ Comparison Operations ============
231
+ /**
232
+ * Check if this amount equals another.
233
+ * @throws {CurrencyMismatchError} If currencies don't match
234
+ */
235
+ equalTo(other) {
236
+ var _a;
237
+ __privateMethod(this, _Money_instances, assertSameCurrency_fn).call(this, other);
238
+ return __privateGet(this, _subunits) === __privateMethod(_a = other, _Money_instances, getInternalValue_fn).call(_a);
239
+ }
240
+ /**
241
+ * Check if this amount is greater than another.
242
+ * @throws {CurrencyMismatchError} If currencies don't match
243
+ */
244
+ greaterThan(other) {
245
+ var _a;
246
+ __privateMethod(this, _Money_instances, assertSameCurrency_fn).call(this, other);
247
+ return __privateGet(this, _subunits) > __privateMethod(_a = other, _Money_instances, getInternalValue_fn).call(_a);
248
+ }
249
+ /**
250
+ * Check if this amount is less than another.
251
+ * @throws {CurrencyMismatchError} If currencies don't match
252
+ */
253
+ lessThan(other) {
254
+ var _a;
255
+ __privateMethod(this, _Money_instances, assertSameCurrency_fn).call(this, other);
256
+ return __privateGet(this, _subunits) < __privateMethod(_a = other, _Money_instances, getInternalValue_fn).call(_a);
257
+ }
258
+ /**
259
+ * Check if this amount is greater than or equal to another.
260
+ * @throws {CurrencyMismatchError} If currencies don't match
261
+ */
262
+ greaterThanOrEqual(other) {
263
+ var _a;
264
+ __privateMethod(this, _Money_instances, assertSameCurrency_fn).call(this, other);
265
+ return __privateGet(this, _subunits) >= __privateMethod(_a = other, _Money_instances, getInternalValue_fn).call(_a);
266
+ }
267
+ /**
268
+ * Check if this amount is less than or equal to another.
269
+ * @throws {CurrencyMismatchError} If currencies don't match
270
+ */
271
+ lessThanOrEqual(other) {
272
+ var _a;
273
+ __privateMethod(this, _Money_instances, assertSameCurrency_fn).call(this, other);
274
+ return __privateGet(this, _subunits) <= __privateMethod(_a = other, _Money_instances, getInternalValue_fn).call(_a);
275
+ }
276
+ /**
277
+ * Check if this amount is zero.
278
+ */
279
+ isZero() {
280
+ return __privateGet(this, _subunits) === 0n;
281
+ }
282
+ /**
283
+ * Check if this amount is positive (greater than zero).
284
+ */
285
+ isPositive() {
286
+ return __privateGet(this, _subunits) > 0n;
287
+ }
288
+ /**
289
+ * Check if this amount is negative (less than zero).
290
+ */
291
+ isNegative() {
292
+ return __privateGet(this, _subunits) < 0n;
293
+ }
294
+ // ============ Serialization ============
295
+ /**
296
+ * Convert to a plain object (safe for JSON).
297
+ */
298
+ toJSON() {
299
+ return {
300
+ currency: this.currency,
301
+ amount: this.amount
302
+ };
303
+ }
304
+ /**
305
+ * Convert to string representation.
306
+ */
307
+ toString() {
308
+ return `${this.displayAmount} ${this.currency}`;
309
+ }
310
+ /**
311
+ * Get the amount as a number (may lose precision for large values).
312
+ * Use with caution - prefer string-based operations.
313
+ */
314
+ toNumber() {
315
+ return Number(this.amount);
316
+ }
317
+ /**
318
+ * Get the amount in subunits (e.g., cents for USD).
319
+ * Useful for database storage (Stripe-style integer storage).
320
+ */
321
+ toSubunits() {
322
+ return __privateGet(this, _subunits);
323
+ }
324
+ // ============ Static Factory Methods ============
325
+ /**
326
+ * Create a Money instance from a plain object.
327
+ */
328
+ static fromObject(obj) {
329
+ return new _Money(obj.currency, obj.amount);
330
+ }
331
+ /**
332
+ * Create a Money instance from subunits (e.g., cents).
333
+ * Useful for loading from database (Stripe-style integer storage).
334
+ */
335
+ static fromSubunits(subunits, currency) {
336
+ var _a;
337
+ const currencyDef = getCurrency(currency);
338
+ if (!currencyDef) {
339
+ throw new CurrencyUnknownError(currency);
340
+ }
341
+ const bigintSubunits = typeof subunits === "number" ? BigInt(subunits) : subunits;
342
+ return __privateMethod(_a = _Money, _Money_static, createFromSubunits_fn).call(_a, bigintSubunits, currency, currencyDef);
343
+ }
344
+ /**
345
+ * Compare two Money objects (for use with Array.sort).
346
+ * @throws {CurrencyMismatchError} If currencies don't match
347
+ */
348
+ static compare(a, b) {
349
+ var _a, _b;
350
+ if (a.currency !== b.currency) {
351
+ throw new CurrencyMismatchError(a.currency, b.currency);
352
+ }
353
+ const aVal = __privateMethod(_a = a, _Money_instances, getInternalValue_fn).call(_a);
354
+ const bVal = __privateMethod(_b = b, _Money_instances, getInternalValue_fn).call(_b);
355
+ if (aVal < bVal) return -1;
356
+ if (aVal > bVal) return 1;
357
+ return 0;
358
+ }
359
+ /**
360
+ * Create a zero amount in the specified currency.
361
+ */
362
+ static zero(currency) {
363
+ return new _Money(currency, "0");
364
+ }
365
+ };
366
+ _subunits = new WeakMap();
367
+ _currencyDef = new WeakMap();
368
+ _Money_instances = new WeakSet();
369
+ /**
370
+ * Parse an amount into native subunits.
371
+ */
372
+ parseAmount_fn = function(amount) {
373
+ const str = typeof amount === "number" ? String(amount) : amount;
374
+ const match = str.match(/^(-)?(\d+)(?:\.(\d+))?$/);
375
+ if (!match) {
376
+ throw new AmountError(amount);
377
+ }
378
+ const [, sign, whole, frac = ""] = match;
379
+ if (frac.length > __privateGet(this, _currencyDef).decimalDigits) {
380
+ throw new SubunitError(this.currency, __privateGet(this, _currencyDef).decimalDigits);
381
+ }
382
+ const paddedFrac = frac.padEnd(__privateGet(this, _currencyDef).decimalDigits, "0");
383
+ const combined = BigInt(whole + paddedFrac);
384
+ return sign === "-" ? -combined : combined;
385
+ };
386
+ _Money_static = new WeakSet();
387
+ formatForDisplay_fn = function(fullAmount, currencyDef) {
388
+ const preferredDecimals = currencyDef.displayDecimals ?? currencyDef.decimalDigits;
389
+ if (preferredDecimals === currencyDef.decimalDigits) {
390
+ return fullAmount;
391
+ }
392
+ const [whole, frac = ""] = fullAmount.split(".");
393
+ if (!frac) return whole;
394
+ let trimmedFrac = frac.replace(/0+$/, "");
395
+ if (trimmedFrac.length < preferredDecimals) {
396
+ trimmedFrac = trimmedFrac.padEnd(preferredDecimals, "0");
397
+ }
398
+ if (trimmedFrac === "" && preferredDecimals === 0) {
399
+ return whole;
400
+ }
401
+ return `${whole}.${trimmedFrac}`;
402
+ };
403
+ /**
404
+ * Ensure another Money has the same currency.
405
+ */
406
+ assertSameCurrency_fn = function(other) {
407
+ if (this.currency !== other.currency) {
408
+ throw new CurrencyMismatchError(this.currency, other.currency);
409
+ }
410
+ };
411
+ /**
412
+ * Get the internal BigInt value (for operations).
413
+ */
414
+ getInternalValue_fn = function() {
415
+ return __privateGet(this, _subunits);
416
+ };
417
+ parseFactor_fn = function(factor) {
418
+ const str = String(factor);
419
+ const [base, exponent] = str.split("e");
420
+ const baseMatch = base.match(/^(-)?(\d+)(?:\.(\d+))?$/);
421
+ if (!baseMatch) {
422
+ throw new TypeError(`Invalid factor format: ${str}`);
423
+ }
424
+ const [, sign, whole, frac = ""] = baseMatch;
425
+ const baseValue = BigInt((sign || "") + whole + frac);
426
+ const baseDecimals = frac.length;
427
+ const exp = exponent ? Number(exponent) : 0;
428
+ const netExp = exp - baseDecimals;
429
+ if (netExp >= 0) {
430
+ return { value: baseValue * 10n ** BigInt(netExp), scale: 0n };
431
+ } else {
432
+ return { value: baseValue, scale: BigInt(-netExp) };
433
+ }
434
+ };
435
+ roundedDivide_fn = function(numerator, denominator) {
436
+ if (denominator === 1n) return numerator;
437
+ const quotient = numerator / denominator;
438
+ const remainder = numerator % denominator;
439
+ if (remainder === 0n) return quotient;
440
+ const halfDenominator = denominator / 2n;
441
+ const absRemainder = remainder < 0n ? -remainder : remainder;
442
+ if (absRemainder > halfDenominator) {
443
+ return numerator < 0n ? quotient - 1n : quotient + 1n;
444
+ }
445
+ if (absRemainder === halfDenominator) {
446
+ const isQuotientEven = quotient % 2n === 0n;
447
+ if (isQuotientEven) {
448
+ return quotient;
449
+ }
450
+ return numerator < 0n ? quotient - 1n : quotient + 1n;
451
+ }
452
+ return quotient;
453
+ };
454
+ createFromSubunits_fn = function(subunits, currency, currencyDef) {
455
+ var _a;
456
+ return new _Money(currency, __privateMethod(_a = _Money, _Money_static, formatSubunits_fn).call(_a, subunits, currencyDef));
457
+ };
458
+ formatSubunits_fn = function(subunits, currencyDef) {
459
+ const decimals = currencyDef.decimalDigits;
460
+ const abs = subunits < 0n ? -subunits : subunits;
461
+ const isNegative = subunits < 0n;
462
+ if (decimals === 0) {
463
+ return `${isNegative ? "-" : ""}${abs}`;
464
+ }
465
+ const multiplier = 10n ** BigInt(decimals);
466
+ const wholePart = abs / multiplier;
467
+ const fracPart = abs % multiplier;
468
+ const sign = isNegative ? "-" : "";
469
+ return `${sign}${wholePart}.${fracPart.toString().padStart(decimals, "0")}`;
470
+ };
471
+ __privateAdd(_Money, _Money_static);
472
+ var Money = _Money;
473
+
474
+ // lib/exchange-rate-service.ts
475
+ var _rates, _ExchangeRateService_instances, key_fn;
476
+ var ExchangeRateService = class {
477
+ constructor() {
478
+ __privateAdd(this, _ExchangeRateService_instances);
479
+ __privateAdd(this, _rates, /* @__PURE__ */ new Map());
480
+ }
481
+ /**
482
+ * Set an exchange rate.
483
+ *
484
+ * @param from - Source currency code
485
+ * @param to - Target currency code
486
+ * @param rate - Exchange rate (1 unit of 'from' = rate units of 'to')
487
+ * @param source - Optional source identifier (e.g., 'ECB', 'Coinbase')
488
+ * @param autoInverse - If true, automatically create inverse rate (default: true)
489
+ */
490
+ setRate(from, to, rate, source, autoInverse = true) {
491
+ const rateStr = typeof rate === "number" ? rate.toPrecision(15) : rate;
492
+ __privateGet(this, _rates).set(__privateMethod(this, _ExchangeRateService_instances, key_fn).call(this, from, to), {
493
+ from,
494
+ to,
495
+ rate: rateStr,
496
+ timestamp: /* @__PURE__ */ new Date(),
497
+ source
498
+ });
499
+ if (autoInverse) {
500
+ const inverseKey = __privateMethod(this, _ExchangeRateService_instances, key_fn).call(this, to, from);
501
+ if (!__privateGet(this, _rates).has(inverseKey)) {
502
+ const inverseRate = 1 / Number(rateStr);
503
+ __privateGet(this, _rates).set(inverseKey, {
504
+ from: to,
505
+ to: from,
506
+ rate: inverseRate.toPrecision(15),
507
+ timestamp: /* @__PURE__ */ new Date(),
508
+ source: source ? `${source} (inverse)` : "(inverse)"
509
+ });
510
+ }
511
+ }
512
+ }
513
+ /**
514
+ * Get an exchange rate.
515
+ *
516
+ * @param from - Source currency code
517
+ * @param to - Target currency code
518
+ * @returns The exchange rate, or undefined if not set
519
+ */
520
+ getRate(from, to) {
521
+ if (from === to) {
522
+ return {
523
+ from,
524
+ to,
525
+ rate: "1",
526
+ timestamp: /* @__PURE__ */ new Date(),
527
+ source: "(identity)"
528
+ };
529
+ }
530
+ return __privateGet(this, _rates).get(__privateMethod(this, _ExchangeRateService_instances, key_fn).call(this, from, to));
531
+ }
532
+ /**
533
+ * Check if a rate exists.
534
+ */
535
+ hasRate(from, to) {
536
+ return from === to || __privateGet(this, _rates).has(__privateMethod(this, _ExchangeRateService_instances, key_fn).call(this, from, to));
537
+ }
538
+ /**
539
+ * Remove a rate.
540
+ */
541
+ removeRate(from, to) {
542
+ return __privateGet(this, _rates).delete(__privateMethod(this, _ExchangeRateService_instances, key_fn).call(this, from, to));
543
+ }
544
+ /**
545
+ * Get both forward and reverse rates, with discrepancy analysis.
546
+ * Useful for detecting rate inconsistencies.
547
+ *
548
+ * @param currencyA - First currency
549
+ * @param currencyB - Second currency
550
+ * @returns Rate pair with discrepancy, or undefined if either rate is missing
551
+ */
552
+ getRatePair(currencyA, currencyB) {
553
+ const forward = this.getRate(currencyA, currencyB);
554
+ const reverse = this.getRate(currencyB, currencyA);
555
+ if (!forward || !reverse) {
556
+ return void 0;
557
+ }
558
+ const product = Number(forward.rate) * Number(reverse.rate);
559
+ const discrepancy = Math.abs(1 - product);
560
+ return { forward, reverse, discrepancy };
561
+ }
562
+ /**
563
+ * Get all rates for a specific base currency.
564
+ *
565
+ * @param base - The base currency code
566
+ * @returns Array of rates from this currency
567
+ */
568
+ getRatesFrom(base) {
569
+ const rates = [];
570
+ for (const rate of __privateGet(this, _rates).values()) {
571
+ if (rate.from === base) {
572
+ rates.push(rate);
573
+ }
574
+ }
575
+ return rates.sort((a, b) => a.to.localeCompare(b.to));
576
+ }
577
+ /**
578
+ * Get all registered rates.
579
+ */
580
+ getAllRates() {
581
+ return Array.from(__privateGet(this, _rates).values()).sort((a, b) => {
582
+ const fromCompare = a.from.localeCompare(b.from);
583
+ return fromCompare !== 0 ? fromCompare : a.to.localeCompare(b.to);
584
+ });
585
+ }
586
+ /**
587
+ * Clear all rates.
588
+ */
589
+ clear() {
590
+ __privateGet(this, _rates).clear();
591
+ }
592
+ /**
593
+ * Load rates from a simple object.
594
+ *
595
+ * @param rates - Object where keys are "FROM:TO" and values are rates
596
+ * @param source - Optional source identifier
597
+ *
598
+ * @example
599
+ * service.loadRates({
600
+ * 'USD:EUR': 0.92,
601
+ * 'USD:GBP': 0.79,
602
+ * }, 'daily-update')
603
+ */
604
+ loadRates(rates, source) {
605
+ for (const [key, rate] of Object.entries(rates)) {
606
+ const [from, to] = key.split(":");
607
+ if (from && to) {
608
+ this.setRate(from, to, rate, source, false);
609
+ }
610
+ }
611
+ }
612
+ };
613
+ _rates = new WeakMap();
614
+ _ExchangeRateService_instances = new WeakSet();
615
+ /**
616
+ * Create a rate key for storage.
617
+ */
618
+ key_fn = function(from, to) {
619
+ return `${from}:${to}`;
620
+ };
621
+
622
+ // lib/money-converter.ts
623
+ var _rateService, _MoneyConverter_instances, bankersRound_fn;
624
+ var MoneyConverter = class {
625
+ constructor(rateService) {
626
+ __privateAdd(this, _MoneyConverter_instances);
627
+ __privateAdd(this, _rateService);
628
+ __privateSet(this, _rateService, rateService);
629
+ }
630
+ /**
631
+ * Convert a Money amount to another currency.
632
+ *
633
+ * @param money - The amount to convert
634
+ * @param targetCurrency - The target currency code
635
+ * @returns A new Money in the target currency
636
+ * @throws {ExchangeRateError} If no rate is available
637
+ */
638
+ convert(money, targetCurrency) {
639
+ if (money.currency === targetCurrency) {
640
+ return money;
641
+ }
642
+ const currencyDef = getCurrency(targetCurrency);
643
+ if (!currencyDef) {
644
+ throw new CurrencyUnknownError(targetCurrency);
645
+ }
646
+ const rate = __privateGet(this, _rateService).getRate(money.currency, targetCurrency);
647
+ if (!rate) {
648
+ throw new ExchangeRateError(money.currency, targetCurrency);
649
+ }
650
+ const sourceCurrencyDef = getCurrency(money.currency);
651
+ const sourceSubunits = money.toSubunits();
652
+ const sourceMultiplier = 10n ** BigInt(sourceCurrencyDef.decimalDigits);
653
+ const targetMultiplier = 10n ** BigInt(currencyDef.decimalDigits);
654
+ const RATE_PRECISION = 15n;
655
+ const rateMultiplier = 10n ** RATE_PRECISION;
656
+ const rateValue = Number(rate.rate);
657
+ const rateBigInt = BigInt(Math.round(rateValue * Number(rateMultiplier)));
658
+ const product = sourceSubunits * rateBigInt * targetMultiplier;
659
+ const divisor = rateMultiplier * sourceMultiplier;
660
+ const targetSubunits = __privateMethod(this, _MoneyConverter_instances, bankersRound_fn).call(this, product, divisor);
661
+ return Money.fromSubunits(targetSubunits, targetCurrency);
662
+ }
663
+ /**
664
+ * Add two Money amounts, converting as needed.
665
+ *
666
+ * @param a - First amount
667
+ * @param b - Second amount
668
+ * @param resultCurrency - Currency for the result (must be one of the input currencies)
669
+ * @returns Sum in the result currency
670
+ */
671
+ add(a, b, resultCurrency) {
672
+ const aConverted = this.convert(a, resultCurrency);
673
+ const bConverted = this.convert(b, resultCurrency);
674
+ return aConverted.add(bConverted);
675
+ }
676
+ /**
677
+ * Subtract two Money amounts, converting as needed.
678
+ *
679
+ * @param a - Amount to subtract from
680
+ * @param b - Amount to subtract
681
+ * @param resultCurrency - Currency for the result
682
+ * @returns Difference in the result currency
683
+ */
684
+ subtract(a, b, resultCurrency) {
685
+ const aConverted = this.convert(a, resultCurrency);
686
+ const bConverted = this.convert(b, resultCurrency);
687
+ return aConverted.subtract(bConverted);
688
+ }
689
+ /**
690
+ * Calculate what percentage one amount is of another.
691
+ * Converts both to the same currency before comparison.
692
+ *
693
+ * @param part - The partial amount
694
+ * @param whole - The whole amount
695
+ * @returns Percentage as a number (e.g., 25 for 25%)
696
+ */
697
+ percentageOf(part, whole) {
698
+ const partConverted = this.convert(part, whole.currency);
699
+ return partConverted.toNumber() / whole.toNumber() * 100;
700
+ }
701
+ /**
702
+ * Sum multiple Money amounts, converting all to a target currency.
703
+ *
704
+ * @param amounts - Array of Money objects (can be different currencies)
705
+ * @param targetCurrency - Currency for the result
706
+ * @returns Total in the target currency
707
+ */
708
+ sum(amounts, targetCurrency) {
709
+ let total = Money.zero(targetCurrency);
710
+ for (const amount of amounts) {
711
+ const converted = this.convert(amount, targetCurrency);
712
+ total = total.add(converted);
713
+ }
714
+ return total;
715
+ }
716
+ /**
717
+ * Compare two Money amounts across currencies.
718
+ * Returns negative if a < b, zero if equal, positive if a > b.
719
+ *
720
+ * @param a - First amount
721
+ * @param b - Second amount
722
+ * @returns Comparison result
723
+ */
724
+ compare(a, b) {
725
+ const bConverted = this.convert(b, a.currency);
726
+ return Money.compare(a, bConverted);
727
+ }
728
+ /**
729
+ * Get the exchange rate service (for direct rate access).
730
+ */
731
+ get rateService() {
732
+ return __privateGet(this, _rateService);
733
+ }
734
+ };
735
+ _rateService = new WeakMap();
736
+ _MoneyConverter_instances = new WeakSet();
737
+ bankersRound_fn = function(numerator, denominator) {
738
+ if (denominator === 1n) return numerator;
739
+ const quotient = numerator / denominator;
740
+ const remainder = numerator % denominator;
741
+ if (remainder === 0n) return quotient;
742
+ const halfDenominator = denominator / 2n;
743
+ const absRemainder = remainder < 0n ? -remainder : remainder;
744
+ if (absRemainder > halfDenominator) {
745
+ return numerator < 0n ? quotient - 1n : quotient + 1n;
746
+ }
747
+ if (absRemainder === halfDenominator) {
748
+ const isQuotientEven = quotient % 2n === 0n;
749
+ if (isQuotientEven) {
750
+ return quotient;
751
+ }
752
+ return numerator < 0n ? quotient - 1n : quotient + 1n;
753
+ }
754
+ return quotient;
755
+ };
756
+
757
+ // currencymap.json
758
+ var currencymap_default = {
759
+ AED: {
760
+ decimal_digits: 2
761
+ },
762
+ AFN: {
763
+ decimal_digits: 2
764
+ },
765
+ ALL: {
766
+ decimal_digits: 2
767
+ },
768
+ AMD: {
769
+ decimal_digits: 2
770
+ },
771
+ AOA: {
772
+ decimal_digits: 2
773
+ },
774
+ ARS: {
775
+ decimal_digits: 2
776
+ },
777
+ AUD: {
778
+ decimal_digits: 2
779
+ },
780
+ AWG: {
781
+ decimal_digits: 2
782
+ },
783
+ AZN: {
784
+ decimal_digits: 2
785
+ },
786
+ BAM: {
787
+ decimal_digits: 2
788
+ },
789
+ BBD: {
790
+ decimal_digits: 2
791
+ },
792
+ BDT: {
793
+ decimal_digits: 2
794
+ },
795
+ BHD: {
796
+ decimal_digits: 3
797
+ },
798
+ BIF: {
799
+ decimal_digits: 0
800
+ },
801
+ BMD: {
802
+ decimal_digits: 2
803
+ },
804
+ BND: {
805
+ decimal_digits: 2
806
+ },
807
+ BOB: {
808
+ decimal_digits: 2
809
+ },
810
+ BOV: {
811
+ decimal_digits: 2
812
+ },
813
+ BRL: {
814
+ decimal_digits: 2
815
+ },
816
+ BSD: {
817
+ decimal_digits: 2
818
+ },
819
+ BTN: {
820
+ decimal_digits: 2
821
+ },
822
+ BWP: {
823
+ decimal_digits: 2
824
+ },
825
+ BYN: {
826
+ decimal_digits: 2
827
+ },
828
+ BZD: {
829
+ decimal_digits: 2
830
+ },
831
+ CAD: {
832
+ decimal_digits: 2
833
+ },
834
+ CDF: {
835
+ decimal_digits: 2
836
+ },
837
+ CHE: {
838
+ decimal_digits: 2
839
+ },
840
+ CHF: {
841
+ decimal_digits: 2
842
+ },
843
+ CHW: {
844
+ decimal_digits: 2
845
+ },
846
+ CLF: {
847
+ decimal_digits: 4
848
+ },
849
+ CLP: {
850
+ decimal_digits: 0
851
+ },
852
+ CNY: {
853
+ decimal_digits: 2
854
+ },
855
+ COP: {
856
+ decimal_digits: 2
857
+ },
858
+ COU: {
859
+ decimal_digits: 2
860
+ },
861
+ CRC: {
862
+ decimal_digits: 2
863
+ },
864
+ CUP: {
865
+ decimal_digits: 2
866
+ },
867
+ CVE: {
868
+ decimal_digits: 2
869
+ },
870
+ CZK: {
871
+ decimal_digits: 2
872
+ },
873
+ DJF: {
874
+ decimal_digits: 0
875
+ },
876
+ DKK: {
877
+ decimal_digits: 2
878
+ },
879
+ DOP: {
880
+ decimal_digits: 2
881
+ },
882
+ DZD: {
883
+ decimal_digits: 2
884
+ },
885
+ EGP: {
886
+ decimal_digits: 2
887
+ },
888
+ ERN: {
889
+ decimal_digits: 2
890
+ },
891
+ ETB: {
892
+ decimal_digits: 2
893
+ },
894
+ EUR: {
895
+ decimal_digits: 2
896
+ },
897
+ FJD: {
898
+ decimal_digits: 2
899
+ },
900
+ FKP: {
901
+ decimal_digits: 2
902
+ },
903
+ GBP: {
904
+ decimal_digits: 2
905
+ },
906
+ GEL: {
907
+ decimal_digits: 2
908
+ },
909
+ GHS: {
910
+ decimal_digits: 2
911
+ },
912
+ GIP: {
913
+ decimal_digits: 2
914
+ },
915
+ GMD: {
916
+ decimal_digits: 2
917
+ },
918
+ GNF: {
919
+ decimal_digits: 0
920
+ },
921
+ GTQ: {
922
+ decimal_digits: 2
923
+ },
924
+ GYD: {
925
+ decimal_digits: 2
926
+ },
927
+ HKD: {
928
+ decimal_digits: 2
929
+ },
930
+ HNL: {
931
+ decimal_digits: 2
932
+ },
933
+ HTG: {
934
+ decimal_digits: 2
935
+ },
936
+ HUF: {
937
+ decimal_digits: 2
938
+ },
939
+ IDR: {
940
+ decimal_digits: 2
941
+ },
942
+ ILS: {
943
+ decimal_digits: 2
944
+ },
945
+ INR: {
946
+ decimal_digits: 2
947
+ },
948
+ IQD: {
949
+ decimal_digits: 3
950
+ },
951
+ IRR: {
952
+ decimal_digits: 2
953
+ },
954
+ ISK: {
955
+ decimal_digits: 0
956
+ },
957
+ JMD: {
958
+ decimal_digits: 2
959
+ },
960
+ JOD: {
961
+ decimal_digits: 3
962
+ },
963
+ JPY: {
964
+ decimal_digits: 0
965
+ },
966
+ KES: {
967
+ decimal_digits: 2
968
+ },
969
+ KGS: {
970
+ decimal_digits: 2
971
+ },
972
+ KHR: {
973
+ decimal_digits: 2
974
+ },
975
+ KMF: {
976
+ decimal_digits: 0
977
+ },
978
+ KPW: {
979
+ decimal_digits: 2
980
+ },
981
+ KRW: {
982
+ decimal_digits: 0
983
+ },
984
+ KWD: {
985
+ decimal_digits: 3
986
+ },
987
+ KYD: {
988
+ decimal_digits: 2
989
+ },
990
+ KZT: {
991
+ decimal_digits: 2
992
+ },
993
+ LAK: {
994
+ decimal_digits: 2
995
+ },
996
+ LBP: {
997
+ decimal_digits: 2
998
+ },
999
+ LKR: {
1000
+ decimal_digits: 2
1001
+ },
1002
+ LRD: {
1003
+ decimal_digits: 2
1004
+ },
1005
+ LSL: {
1006
+ decimal_digits: 2
1007
+ },
1008
+ LYD: {
1009
+ decimal_digits: 3
1010
+ },
1011
+ MAD: {
1012
+ decimal_digits: 2
1013
+ },
1014
+ MDL: {
1015
+ decimal_digits: 2
1016
+ },
1017
+ MGA: {
1018
+ decimal_digits: 2
1019
+ },
1020
+ MKD: {
1021
+ decimal_digits: 2
1022
+ },
1023
+ MMK: {
1024
+ decimal_digits: 2
1025
+ },
1026
+ MNT: {
1027
+ decimal_digits: 2
1028
+ },
1029
+ MOP: {
1030
+ decimal_digits: 2
1031
+ },
1032
+ MRU: {
1033
+ decimal_digits: 2
1034
+ },
1035
+ MUR: {
1036
+ decimal_digits: 2
1037
+ },
1038
+ MVR: {
1039
+ decimal_digits: 2
1040
+ },
1041
+ MWK: {
1042
+ decimal_digits: 2
1043
+ },
1044
+ MXN: {
1045
+ decimal_digits: 2
1046
+ },
1047
+ MXV: {
1048
+ decimal_digits: 2
1049
+ },
1050
+ MYR: {
1051
+ decimal_digits: 2
1052
+ },
1053
+ MZN: {
1054
+ decimal_digits: 2
1055
+ },
1056
+ NAD: {
1057
+ decimal_digits: 2
1058
+ },
1059
+ NGN: {
1060
+ decimal_digits: 2
1061
+ },
1062
+ NIO: {
1063
+ decimal_digits: 2
1064
+ },
1065
+ NOK: {
1066
+ decimal_digits: 2
1067
+ },
1068
+ NPR: {
1069
+ decimal_digits: 2
1070
+ },
1071
+ NZD: {
1072
+ decimal_digits: 2
1073
+ },
1074
+ OMR: {
1075
+ decimal_digits: 3
1076
+ },
1077
+ PAB: {
1078
+ decimal_digits: 2
1079
+ },
1080
+ PEN: {
1081
+ decimal_digits: 2
1082
+ },
1083
+ PGK: {
1084
+ decimal_digits: 2
1085
+ },
1086
+ PHP: {
1087
+ decimal_digits: 2
1088
+ },
1089
+ PKR: {
1090
+ decimal_digits: 2
1091
+ },
1092
+ PLN: {
1093
+ decimal_digits: 2
1094
+ },
1095
+ PYG: {
1096
+ decimal_digits: 0
1097
+ },
1098
+ QAR: {
1099
+ decimal_digits: 2
1100
+ },
1101
+ RON: {
1102
+ decimal_digits: 2
1103
+ },
1104
+ RSD: {
1105
+ decimal_digits: 2
1106
+ },
1107
+ RUB: {
1108
+ decimal_digits: 2
1109
+ },
1110
+ RWF: {
1111
+ decimal_digits: 0
1112
+ },
1113
+ SAR: {
1114
+ decimal_digits: 2
1115
+ },
1116
+ SBD: {
1117
+ decimal_digits: 2
1118
+ },
1119
+ SCR: {
1120
+ decimal_digits: 2
1121
+ },
1122
+ SDG: {
1123
+ decimal_digits: 2
1124
+ },
1125
+ SEK: {
1126
+ decimal_digits: 2
1127
+ },
1128
+ SGD: {
1129
+ decimal_digits: 2
1130
+ },
1131
+ SHP: {
1132
+ decimal_digits: 2
1133
+ },
1134
+ SLE: {
1135
+ decimal_digits: 2
1136
+ },
1137
+ SOS: {
1138
+ decimal_digits: 2
1139
+ },
1140
+ SRD: {
1141
+ decimal_digits: 2
1142
+ },
1143
+ SSP: {
1144
+ decimal_digits: 2
1145
+ },
1146
+ STN: {
1147
+ decimal_digits: 2
1148
+ },
1149
+ SVC: {
1150
+ decimal_digits: 2
1151
+ },
1152
+ SYP: {
1153
+ decimal_digits: 2
1154
+ },
1155
+ SZL: {
1156
+ decimal_digits: 2
1157
+ },
1158
+ THB: {
1159
+ decimal_digits: 2
1160
+ },
1161
+ TJS: {
1162
+ decimal_digits: 2
1163
+ },
1164
+ TMT: {
1165
+ decimal_digits: 2
1166
+ },
1167
+ TND: {
1168
+ decimal_digits: 3
1169
+ },
1170
+ TOP: {
1171
+ decimal_digits: 2
1172
+ },
1173
+ TRY: {
1174
+ decimal_digits: 2
1175
+ },
1176
+ TTD: {
1177
+ decimal_digits: 2
1178
+ },
1179
+ TWD: {
1180
+ decimal_digits: 2
1181
+ },
1182
+ TZS: {
1183
+ decimal_digits: 2
1184
+ },
1185
+ UAH: {
1186
+ decimal_digits: 2
1187
+ },
1188
+ UGX: {
1189
+ decimal_digits: 0
1190
+ },
1191
+ USD: {
1192
+ decimal_digits: 2
1193
+ },
1194
+ USN: {
1195
+ decimal_digits: 2
1196
+ },
1197
+ UYI: {
1198
+ decimal_digits: 0
1199
+ },
1200
+ UYU: {
1201
+ decimal_digits: 2
1202
+ },
1203
+ UYW: {
1204
+ decimal_digits: 4
1205
+ },
1206
+ UZS: {
1207
+ decimal_digits: 2
1208
+ },
1209
+ VED: {
1210
+ decimal_digits: 2
1211
+ },
1212
+ VES: {
1213
+ decimal_digits: 2
1214
+ },
1215
+ VND: {
1216
+ decimal_digits: 0
1217
+ },
1218
+ VUV: {
1219
+ decimal_digits: 0
1220
+ },
1221
+ WST: {
1222
+ decimal_digits: 2
1223
+ },
1224
+ XAD: {
1225
+ decimal_digits: 2
1226
+ },
1227
+ XAF: {
1228
+ decimal_digits: 0
1229
+ },
1230
+ XAG: {
1231
+ decimal_digits: 0
1232
+ },
1233
+ XAU: {
1234
+ decimal_digits: 0
1235
+ },
1236
+ XBA: {
1237
+ decimal_digits: 0
1238
+ },
1239
+ XBB: {
1240
+ decimal_digits: 0
1241
+ },
1242
+ XBC: {
1243
+ decimal_digits: 0
1244
+ },
1245
+ XBD: {
1246
+ decimal_digits: 0
1247
+ },
1248
+ XCD: {
1249
+ decimal_digits: 2
1250
+ },
1251
+ XCG: {
1252
+ decimal_digits: 2
1253
+ },
1254
+ XDR: {
1255
+ decimal_digits: 0
1256
+ },
1257
+ XOF: {
1258
+ decimal_digits: 0
1259
+ },
1260
+ XPD: {
1261
+ decimal_digits: 0
1262
+ },
1263
+ XPF: {
1264
+ decimal_digits: 0
1265
+ },
1266
+ XPT: {
1267
+ decimal_digits: 0
1268
+ },
1269
+ XSU: {
1270
+ decimal_digits: 0
1271
+ },
1272
+ XTS: {
1273
+ decimal_digits: 0
1274
+ },
1275
+ XUA: {
1276
+ decimal_digits: 0
1277
+ },
1278
+ XXX: {
1279
+ decimal_digits: 0
1280
+ },
1281
+ YER: {
1282
+ decimal_digits: 2
1283
+ },
1284
+ ZAR: {
1285
+ decimal_digits: 2
1286
+ },
1287
+ ZMW: {
1288
+ decimal_digits: 2
1289
+ },
1290
+ ZWG: {
1291
+ decimal_digits: 2
1292
+ }
1293
+ };
1294
+
1295
+ // lib/index.ts
1296
+ loadCurrencyMap(currencymap_default);
1297
+ // Annotate the CommonJS export names for ESM import in node:
1298
+ 0 && (module.exports = {
1299
+ AmountError,
1300
+ CurrencyMismatchError,
1301
+ CurrencyUnknownError,
1302
+ ExchangeRateError,
1303
+ ExchangeRateService,
1304
+ Money,
1305
+ MoneyConverter,
1306
+ SubunitError,
1307
+ clearCurrencies,
1308
+ getAllCurrencies,
1309
+ getCurrency,
1310
+ hasCurrency,
1311
+ loadCurrencyMap,
1312
+ registerCurrency
1313
+ });
1314
+ //# sourceMappingURL=index.cjs.map