subunit-money 2.1.1 → 3.1.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/cjs/currency.d.ts +38 -0
- package/dist/cjs/currency.js +56 -0
- package/dist/cjs/errors.d.ts +52 -0
- package/dist/cjs/errors.js +80 -0
- package/dist/cjs/exchange-rate-service.d.ts +96 -0
- package/dist/cjs/exchange-rate-service.js +174 -0
- package/dist/cjs/index.d.ts +22 -0
- package/dist/cjs/index.js +58 -0
- package/dist/cjs/money-converter.d.ts +82 -0
- package/dist/cjs/money-converter.js +172 -0
- package/dist/cjs/money.d.ts +146 -0
- package/dist/cjs/money.js +362 -0
- package/dist/currency.js +6 -14
- package/dist/errors.js +5 -13
- package/dist/exchange-rate-service.js +1 -5
- package/dist/index.js +8 -28
- package/dist/money-converter.js +11 -15
- package/dist/money.d.ts +1 -11
- package/dist/money.js +66 -88
- package/package.json +3 -2
package/dist/currency.js
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Currency registry and types.
|
|
4
3
|
* Manages ISO 4217 currency definitions and custom currencies.
|
|
5
4
|
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.registerCurrency = registerCurrency;
|
|
8
|
-
exports.getCurrency = getCurrency;
|
|
9
|
-
exports.hasCurrency = hasCurrency;
|
|
10
|
-
exports.getAllCurrencies = getAllCurrencies;
|
|
11
|
-
exports.loadCurrencyMap = loadCurrencyMap;
|
|
12
|
-
exports.clearCurrencies = clearCurrencies;
|
|
13
5
|
// Internal registry - mutable for registerCurrency()
|
|
14
6
|
const currencies = new Map();
|
|
15
7
|
/**
|
|
@@ -17,33 +9,33 @@ const currencies = new Map();
|
|
|
17
9
|
* @param code - ISO 4217 currency code (e.g., 'USD', 'EUR', 'BTC')
|
|
18
10
|
* @param decimalDigits - Number of decimal places (e.g., 2 for USD, 8 for BTC)
|
|
19
11
|
*/
|
|
20
|
-
function registerCurrency(code, decimalDigits) {
|
|
12
|
+
export function registerCurrency(code, decimalDigits) {
|
|
21
13
|
currencies.set(code, { code, decimalDigits });
|
|
22
14
|
}
|
|
23
15
|
/**
|
|
24
16
|
* Get a currency definition by code.
|
|
25
17
|
* @returns The currency definition, or undefined if not registered
|
|
26
18
|
*/
|
|
27
|
-
function getCurrency(code) {
|
|
19
|
+
export function getCurrency(code) {
|
|
28
20
|
return currencies.get(code);
|
|
29
21
|
}
|
|
30
22
|
/**
|
|
31
23
|
* Check if a currency is registered.
|
|
32
24
|
*/
|
|
33
|
-
function hasCurrency(code) {
|
|
25
|
+
export function hasCurrency(code) {
|
|
34
26
|
return currencies.has(code);
|
|
35
27
|
}
|
|
36
28
|
/**
|
|
37
29
|
* Get all registered currencies, sorted by code.
|
|
38
30
|
*/
|
|
39
|
-
function getAllCurrencies() {
|
|
31
|
+
export function getAllCurrencies() {
|
|
40
32
|
return Array.from(currencies.values()).sort((a, b) => a.code.localeCompare(b.code));
|
|
41
33
|
}
|
|
42
34
|
/**
|
|
43
35
|
* Load currencies from the legacy currencymap.json format.
|
|
44
36
|
* @param map - Object with currency codes as keys and {decimal_digits: number} as values
|
|
45
37
|
*/
|
|
46
|
-
function loadCurrencyMap(map) {
|
|
38
|
+
export function loadCurrencyMap(map) {
|
|
47
39
|
for (const [code, data] of Object.entries(map)) {
|
|
48
40
|
registerCurrency(code, data.decimal_digits);
|
|
49
41
|
}
|
|
@@ -51,6 +43,6 @@ function loadCurrencyMap(map) {
|
|
|
51
43
|
/**
|
|
52
44
|
* Clear all registered currencies. Useful for testing.
|
|
53
45
|
*/
|
|
54
|
-
function clearCurrencies() {
|
|
46
|
+
export function clearCurrencies() {
|
|
55
47
|
currencies.clear();
|
|
56
48
|
}
|
package/dist/errors.js
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Custom error types for Money operations.
|
|
4
3
|
* All errors extend built-in Error types for proper instanceof checks.
|
|
5
4
|
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.ExchangeRateError = exports.AmountError = exports.SubunitError = exports.CurrencyUnknownError = exports.CurrencyMismatchError = void 0;
|
|
8
5
|
/**
|
|
9
6
|
* Thrown when attempting operations between different currencies.
|
|
10
7
|
* @example
|
|
11
8
|
* new Money('USD', 10).add(new Money('EUR', 5)) // throws CurrencyMismatchError
|
|
12
9
|
*/
|
|
13
|
-
class CurrencyMismatchError extends TypeError {
|
|
10
|
+
export class CurrencyMismatchError extends TypeError {
|
|
14
11
|
constructor(fromCurrency, toCurrency) {
|
|
15
12
|
super(`Cannot operate on ${fromCurrency} and ${toCurrency} - currencies must match`);
|
|
16
13
|
this.name = 'CurrencyMismatchError';
|
|
@@ -19,13 +16,12 @@ class CurrencyMismatchError extends TypeError {
|
|
|
19
16
|
Error.captureStackTrace?.(this, CurrencyMismatchError);
|
|
20
17
|
}
|
|
21
18
|
}
|
|
22
|
-
exports.CurrencyMismatchError = CurrencyMismatchError;
|
|
23
19
|
/**
|
|
24
20
|
* Thrown when using an unregistered currency code.
|
|
25
21
|
* @example
|
|
26
22
|
* new Money('FAKE', 10) // throws CurrencyUnknownError
|
|
27
23
|
*/
|
|
28
|
-
class CurrencyUnknownError extends TypeError {
|
|
24
|
+
export class CurrencyUnknownError extends TypeError {
|
|
29
25
|
constructor(currency) {
|
|
30
26
|
super(`Unknown currency '${currency}' - register it first with Money.registerCurrency()`);
|
|
31
27
|
this.name = 'CurrencyUnknownError';
|
|
@@ -33,13 +29,12 @@ class CurrencyUnknownError extends TypeError {
|
|
|
33
29
|
Error.captureStackTrace?.(this, CurrencyUnknownError);
|
|
34
30
|
}
|
|
35
31
|
}
|
|
36
|
-
exports.CurrencyUnknownError = CurrencyUnknownError;
|
|
37
32
|
/**
|
|
38
33
|
* Thrown when an amount has more decimal places than the currency allows.
|
|
39
34
|
* @example
|
|
40
35
|
* new Money('USD', '1.234') // throws SubunitError (USD only allows 2 decimals)
|
|
41
36
|
*/
|
|
42
|
-
class SubunitError extends RangeError {
|
|
37
|
+
export class SubunitError extends RangeError {
|
|
43
38
|
constructor(currency, maxDecimals) {
|
|
44
39
|
super(`${currency} only supports ${maxDecimals} decimal place(s)`);
|
|
45
40
|
this.name = 'SubunitError';
|
|
@@ -48,13 +43,12 @@ class SubunitError extends RangeError {
|
|
|
48
43
|
Error.captureStackTrace?.(this, SubunitError);
|
|
49
44
|
}
|
|
50
45
|
}
|
|
51
|
-
exports.SubunitError = SubunitError;
|
|
52
46
|
/**
|
|
53
47
|
* Thrown when an amount cannot be parsed as a valid number.
|
|
54
48
|
* @example
|
|
55
49
|
* new Money('USD', 'abc') // throws AmountError
|
|
56
50
|
*/
|
|
57
|
-
class AmountError extends TypeError {
|
|
51
|
+
export class AmountError extends TypeError {
|
|
58
52
|
constructor(amount) {
|
|
59
53
|
super(`Invalid amount: ${JSON.stringify(amount)}`);
|
|
60
54
|
this.name = 'AmountError';
|
|
@@ -62,13 +56,12 @@ class AmountError extends TypeError {
|
|
|
62
56
|
Error.captureStackTrace?.(this, AmountError);
|
|
63
57
|
}
|
|
64
58
|
}
|
|
65
|
-
exports.AmountError = AmountError;
|
|
66
59
|
/**
|
|
67
60
|
* Thrown when an exchange rate is not available.
|
|
68
61
|
* @example
|
|
69
62
|
* converter.convert(usdMoney, 'XYZ') // throws ExchangeRateError if no USD->XYZ rate
|
|
70
63
|
*/
|
|
71
|
-
class ExchangeRateError extends Error {
|
|
64
|
+
export class ExchangeRateError extends Error {
|
|
72
65
|
constructor(fromCurrency, toCurrency) {
|
|
73
66
|
super(`No exchange rate available from ${fromCurrency} to ${toCurrency}`);
|
|
74
67
|
this.name = 'ExchangeRateError';
|
|
@@ -77,4 +70,3 @@ class ExchangeRateError extends Error {
|
|
|
77
70
|
Error.captureStackTrace?.(this, ExchangeRateError);
|
|
78
71
|
}
|
|
79
72
|
}
|
|
80
|
-
exports.ExchangeRateError = ExchangeRateError;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Exchange Rate Service - Central authority for currency conversion rates.
|
|
4
3
|
*
|
|
@@ -14,8 +13,6 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
14
13
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
15
14
|
};
|
|
16
15
|
var _ExchangeRateService_instances, _ExchangeRateService_rates, _ExchangeRateService_key;
|
|
17
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
exports.ExchangeRateService = void 0;
|
|
19
16
|
/**
|
|
20
17
|
* Service for managing exchange rates between currencies.
|
|
21
18
|
*
|
|
@@ -24,7 +21,7 @@ exports.ExchangeRateService = void 0;
|
|
|
24
21
|
* rates.setRate('USD', 'EUR', 0.92, 'ECB')
|
|
25
22
|
* rates.getRate('USD', 'EUR') // { from: 'USD', to: 'EUR', rate: '0.92', ... }
|
|
26
23
|
*/
|
|
27
|
-
class ExchangeRateService {
|
|
24
|
+
export class ExchangeRateService {
|
|
28
25
|
constructor() {
|
|
29
26
|
_ExchangeRateService_instances.add(this);
|
|
30
27
|
_ExchangeRateService_rates.set(this, new Map()
|
|
@@ -168,7 +165,6 @@ class ExchangeRateService {
|
|
|
168
165
|
}
|
|
169
166
|
}
|
|
170
167
|
}
|
|
171
|
-
exports.ExchangeRateService = ExchangeRateService;
|
|
172
168
|
_ExchangeRateService_rates = new WeakMap(), _ExchangeRateService_instances = new WeakSet(), _ExchangeRateService_key = function _ExchangeRateService_key(from, to) {
|
|
173
169
|
return `${from}:${to}`;
|
|
174
170
|
};
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* subunit-money - A type-safe value object for monetary amounts
|
|
4
3
|
*
|
|
@@ -16,33 +15,14 @@
|
|
|
16
15
|
* const converter = new MoneyConverter(rates)
|
|
17
16
|
* const euros = converter.convert(total, 'EUR')
|
|
18
17
|
*/
|
|
19
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
20
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
21
|
-
};
|
|
22
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
-
exports.clearCurrencies = exports.loadCurrencyMap = exports.getAllCurrencies = exports.hasCurrency = exports.getCurrency = exports.registerCurrency = exports.ExchangeRateError = exports.AmountError = exports.SubunitError = exports.CurrencyUnknownError = exports.CurrencyMismatchError = exports.MoneyConverter = exports.ExchangeRateService = exports.Money = void 0;
|
|
24
18
|
// Core classes
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
Object.defineProperty(exports, "ExchangeRateService", { enumerable: true, get: function () { return exchange_rate_service_js_1.ExchangeRateService; } });
|
|
29
|
-
var money_converter_js_1 = require("./money-converter.js");
|
|
30
|
-
Object.defineProperty(exports, "MoneyConverter", { enumerable: true, get: function () { return money_converter_js_1.MoneyConverter; } });
|
|
19
|
+
export { Money } from './money.js';
|
|
20
|
+
export { ExchangeRateService } from './exchange-rate-service.js';
|
|
21
|
+
export { MoneyConverter } from './money-converter.js';
|
|
31
22
|
// Error types
|
|
32
|
-
|
|
33
|
-
Object.defineProperty(exports, "CurrencyMismatchError", { enumerable: true, get: function () { return errors_js_1.CurrencyMismatchError; } });
|
|
34
|
-
Object.defineProperty(exports, "CurrencyUnknownError", { enumerable: true, get: function () { return errors_js_1.CurrencyUnknownError; } });
|
|
35
|
-
Object.defineProperty(exports, "SubunitError", { enumerable: true, get: function () { return errors_js_1.SubunitError; } });
|
|
36
|
-
Object.defineProperty(exports, "AmountError", { enumerable: true, get: function () { return errors_js_1.AmountError; } });
|
|
37
|
-
Object.defineProperty(exports, "ExchangeRateError", { enumerable: true, get: function () { return errors_js_1.ExchangeRateError; } });
|
|
23
|
+
export { CurrencyMismatchError, CurrencyUnknownError, SubunitError, AmountError, ExchangeRateError, } from './errors.js';
|
|
38
24
|
// Currency utilities
|
|
39
|
-
|
|
40
|
-
Object.defineProperty(exports, "registerCurrency", { enumerable: true, get: function () { return currency_js_1.registerCurrency; } });
|
|
41
|
-
Object.defineProperty(exports, "getCurrency", { enumerable: true, get: function () { return currency_js_1.getCurrency; } });
|
|
42
|
-
Object.defineProperty(exports, "hasCurrency", { enumerable: true, get: function () { return currency_js_1.hasCurrency; } });
|
|
43
|
-
Object.defineProperty(exports, "getAllCurrencies", { enumerable: true, get: function () { return currency_js_1.getAllCurrencies; } });
|
|
44
|
-
Object.defineProperty(exports, "loadCurrencyMap", { enumerable: true, get: function () { return currency_js_1.loadCurrencyMap; } });
|
|
45
|
-
Object.defineProperty(exports, "clearCurrencies", { enumerable: true, get: function () { return currency_js_1.clearCurrencies; } });
|
|
25
|
+
export { registerCurrency, getCurrency, hasCurrency, getAllCurrencies, loadCurrencyMap, clearCurrencies, } from './currency.js';
|
|
46
26
|
// Auto-load default currencies
|
|
47
27
|
// The currencymap.json file is the official ISO 4217 currency list (List One) as of 2026-01-01,
|
|
48
28
|
// sourced from SIX Financial Information AG (the ISO 4217 Maintenance Agency).
|
|
@@ -53,6 +33,6 @@ Object.defineProperty(exports, "clearCurrencies", { enumerable: true, get: funct
|
|
|
53
33
|
//
|
|
54
34
|
// Note: This excludes historical currencies, supranational funds, and precious metals,
|
|
55
35
|
// keeping only active national and regional currencies for practical use.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
36
|
+
import { loadCurrencyMap } from './currency.js';
|
|
37
|
+
import currencyMap from '../currencymap.json';
|
|
38
|
+
loadCurrencyMap(currencyMap);
|
package/dist/money-converter.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Money Converter - Safe cross-currency operations.
|
|
4
3
|
*
|
|
@@ -19,11 +18,9 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
19
18
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
20
19
|
};
|
|
21
20
|
var _MoneyConverter_instances, _MoneyConverter_rateService, _MoneyConverter_bankersRound;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const errors_js_1 = require("./errors.js");
|
|
26
|
-
const currency_js_1 = require("./currency.js");
|
|
21
|
+
import { Money } from './money.js';
|
|
22
|
+
import { ExchangeRateError, CurrencyUnknownError } from './errors.js';
|
|
23
|
+
import { getCurrency } from './currency.js';
|
|
27
24
|
/**
|
|
28
25
|
* Converter for performing operations between different currencies.
|
|
29
26
|
*
|
|
@@ -35,7 +32,7 @@ const currency_js_1 = require("./currency.js");
|
|
|
35
32
|
* const euros = converter.convert(new Money('USD', '100'), 'EUR')
|
|
36
33
|
* console.log(euros.toString()) // "92.00 EUR"
|
|
37
34
|
*/
|
|
38
|
-
class MoneyConverter {
|
|
35
|
+
export class MoneyConverter {
|
|
39
36
|
constructor(rateService) {
|
|
40
37
|
_MoneyConverter_instances.add(this);
|
|
41
38
|
_MoneyConverter_rateService.set(this, void 0);
|
|
@@ -53,15 +50,15 @@ class MoneyConverter {
|
|
|
53
50
|
if (money.currency === targetCurrency) {
|
|
54
51
|
return money;
|
|
55
52
|
}
|
|
56
|
-
const currencyDef =
|
|
53
|
+
const currencyDef = getCurrency(targetCurrency);
|
|
57
54
|
if (!currencyDef) {
|
|
58
|
-
throw new
|
|
55
|
+
throw new CurrencyUnknownError(targetCurrency);
|
|
59
56
|
}
|
|
60
57
|
const rate = __classPrivateFieldGet(this, _MoneyConverter_rateService, "f").getRate(money.currency, targetCurrency);
|
|
61
58
|
if (!rate) {
|
|
62
|
-
throw new
|
|
59
|
+
throw new ExchangeRateError(money.currency, targetCurrency);
|
|
63
60
|
}
|
|
64
|
-
const sourceCurrencyDef =
|
|
61
|
+
const sourceCurrencyDef = getCurrency(money.currency);
|
|
65
62
|
const sourceSubunits = money.toSubunits();
|
|
66
63
|
const sourceMultiplier = 10n ** BigInt(sourceCurrencyDef.decimalDigits);
|
|
67
64
|
const targetMultiplier = 10n ** BigInt(currencyDef.decimalDigits);
|
|
@@ -72,7 +69,7 @@ class MoneyConverter {
|
|
|
72
69
|
const product = sourceSubunits * rateBigInt * targetMultiplier;
|
|
73
70
|
const divisor = rateMultiplier * sourceMultiplier;
|
|
74
71
|
const targetSubunits = __classPrivateFieldGet(this, _MoneyConverter_instances, "m", _MoneyConverter_bankersRound).call(this, product, divisor);
|
|
75
|
-
return
|
|
72
|
+
return Money.fromSubunits(targetSubunits, targetCurrency);
|
|
76
73
|
}
|
|
77
74
|
/**
|
|
78
75
|
* Add two Money amounts, converting as needed.
|
|
@@ -121,7 +118,7 @@ class MoneyConverter {
|
|
|
121
118
|
* @returns Total in the target currency
|
|
122
119
|
*/
|
|
123
120
|
sum(amounts, targetCurrency) {
|
|
124
|
-
let total =
|
|
121
|
+
let total = Money.zero(targetCurrency);
|
|
125
122
|
for (const amount of amounts) {
|
|
126
123
|
const converted = this.convert(amount, targetCurrency);
|
|
127
124
|
total = total.add(converted);
|
|
@@ -139,7 +136,7 @@ class MoneyConverter {
|
|
|
139
136
|
compare(a, b) {
|
|
140
137
|
// Convert b to a's currency for comparison
|
|
141
138
|
const bConverted = this.convert(b, a.currency);
|
|
142
|
-
return
|
|
139
|
+
return Money.compare(a, bConverted);
|
|
143
140
|
}
|
|
144
141
|
/**
|
|
145
142
|
* Get the exchange rate service (for direct rate access).
|
|
@@ -148,7 +145,6 @@ class MoneyConverter {
|
|
|
148
145
|
return __classPrivateFieldGet(this, _MoneyConverter_rateService, "f");
|
|
149
146
|
}
|
|
150
147
|
}
|
|
151
|
-
exports.MoneyConverter = MoneyConverter;
|
|
152
148
|
_MoneyConverter_rateService = new WeakMap(), _MoneyConverter_instances = new WeakSet(), _MoneyConverter_bankersRound = function _MoneyConverter_bankersRound(numerator, denominator) {
|
|
153
149
|
if (denominator === 1n)
|
|
154
150
|
return numerator;
|
package/dist/money.d.ts
CHANGED
|
@@ -59,13 +59,7 @@ export declare class Money<C extends string = string> {
|
|
|
59
59
|
* Multiply by a factor.
|
|
60
60
|
*
|
|
61
61
|
* DESIGN: Rounds immediately after multiplication using banker's rounding
|
|
62
|
-
* (round half-to-even). This prevents the "split penny problem"
|
|
63
|
-
* line-item rounding differs from deferred rounding:
|
|
64
|
-
* Per-item: $1.65 tax × 10 items = $16.50 ✓ (matches receipt)
|
|
65
|
-
* Deferred: 10 × $1.649175 = $16.49 ✗ (missing penny)
|
|
66
|
-
*
|
|
67
|
-
* For chained calculations without intermediate rounding, perform arithmetic
|
|
68
|
-
* in Number space first, then create a Money object with the final result.
|
|
62
|
+
* (round half-to-even). This prevents the "split penny problem".
|
|
69
63
|
*/
|
|
70
64
|
multiply(factor: number): Money<C>;
|
|
71
65
|
/**
|
|
@@ -74,10 +68,6 @@ export declare class Money<C extends string = string> {
|
|
|
74
68
|
*
|
|
75
69
|
* @param proportions - Array of proportions (e.g., [1, 1, 1] for three-way split)
|
|
76
70
|
* @returns Array of Money objects that sum to the original amount
|
|
77
|
-
*
|
|
78
|
-
* @example
|
|
79
|
-
* new Money('USD', '100').allocate([1, 1, 1])
|
|
80
|
-
* // Returns: [Money('33.34'), Money('33.33'), Money('33.33')]
|
|
81
71
|
*/
|
|
82
72
|
allocate(proportions: number[]): Money<C>[];
|
|
83
73
|
/**
|