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
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Currency registry and types.
|
|
3
|
+
* Manages ISO 4217 currency definitions and custom currencies.
|
|
4
|
+
*/
|
|
5
|
+
export interface CurrencyDefinition {
|
|
6
|
+
code: string;
|
|
7
|
+
decimalDigits: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Register a new currency or update an existing one.
|
|
11
|
+
* @param code - ISO 4217 currency code (e.g., 'USD', 'EUR', 'BTC')
|
|
12
|
+
* @param decimalDigits - Number of decimal places (e.g., 2 for USD, 8 for BTC)
|
|
13
|
+
*/
|
|
14
|
+
export declare function registerCurrency(code: string, decimalDigits: number): void;
|
|
15
|
+
/**
|
|
16
|
+
* Get a currency definition by code.
|
|
17
|
+
* @returns The currency definition, or undefined if not registered
|
|
18
|
+
*/
|
|
19
|
+
export declare function getCurrency(code: string): CurrencyDefinition | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Check if a currency is registered.
|
|
22
|
+
*/
|
|
23
|
+
export declare function hasCurrency(code: string): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Get all registered currencies, sorted by code.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getAllCurrencies(): CurrencyDefinition[];
|
|
28
|
+
/**
|
|
29
|
+
* Load currencies from the legacy currencymap.json format.
|
|
30
|
+
* @param map - Object with currency codes as keys and {decimal_digits: number} as values
|
|
31
|
+
*/
|
|
32
|
+
export declare function loadCurrencyMap(map: Record<string, {
|
|
33
|
+
decimal_digits: number;
|
|
34
|
+
}>): void;
|
|
35
|
+
/**
|
|
36
|
+
* Clear all registered currencies. Useful for testing.
|
|
37
|
+
*/
|
|
38
|
+
export declare function clearCurrencies(): void;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Currency registry and types.
|
|
4
|
+
* Manages ISO 4217 currency definitions and custom currencies.
|
|
5
|
+
*/
|
|
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
|
+
// Internal registry - mutable for registerCurrency()
|
|
14
|
+
const currencies = new Map();
|
|
15
|
+
/**
|
|
16
|
+
* Register a new currency or update an existing one.
|
|
17
|
+
* @param code - ISO 4217 currency code (e.g., 'USD', 'EUR', 'BTC')
|
|
18
|
+
* @param decimalDigits - Number of decimal places (e.g., 2 for USD, 8 for BTC)
|
|
19
|
+
*/
|
|
20
|
+
function registerCurrency(code, decimalDigits) {
|
|
21
|
+
currencies.set(code, { code, decimalDigits });
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get a currency definition by code.
|
|
25
|
+
* @returns The currency definition, or undefined if not registered
|
|
26
|
+
*/
|
|
27
|
+
function getCurrency(code) {
|
|
28
|
+
return currencies.get(code);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Check if a currency is registered.
|
|
32
|
+
*/
|
|
33
|
+
function hasCurrency(code) {
|
|
34
|
+
return currencies.has(code);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get all registered currencies, sorted by code.
|
|
38
|
+
*/
|
|
39
|
+
function getAllCurrencies() {
|
|
40
|
+
return Array.from(currencies.values()).sort((a, b) => a.code.localeCompare(b.code));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Load currencies from the legacy currencymap.json format.
|
|
44
|
+
* @param map - Object with currency codes as keys and {decimal_digits: number} as values
|
|
45
|
+
*/
|
|
46
|
+
function loadCurrencyMap(map) {
|
|
47
|
+
for (const [code, data] of Object.entries(map)) {
|
|
48
|
+
registerCurrency(code, data.decimal_digits);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Clear all registered currencies. Useful for testing.
|
|
53
|
+
*/
|
|
54
|
+
function clearCurrencies() {
|
|
55
|
+
currencies.clear();
|
|
56
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error types for Money operations.
|
|
3
|
+
* All errors extend built-in Error types for proper instanceof checks.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Thrown when attempting operations between different currencies.
|
|
7
|
+
* @example
|
|
8
|
+
* new Money('USD', 10).add(new Money('EUR', 5)) // throws CurrencyMismatchError
|
|
9
|
+
*/
|
|
10
|
+
export declare class CurrencyMismatchError extends TypeError {
|
|
11
|
+
readonly fromCurrency: string;
|
|
12
|
+
readonly toCurrency: string;
|
|
13
|
+
constructor(fromCurrency: string, toCurrency: string);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Thrown when using an unregistered currency code.
|
|
17
|
+
* @example
|
|
18
|
+
* new Money('FAKE', 10) // throws CurrencyUnknownError
|
|
19
|
+
*/
|
|
20
|
+
export declare class CurrencyUnknownError extends TypeError {
|
|
21
|
+
readonly currency: string;
|
|
22
|
+
constructor(currency: string);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Thrown when an amount has more decimal places than the currency allows.
|
|
26
|
+
* @example
|
|
27
|
+
* new Money('USD', '1.234') // throws SubunitError (USD only allows 2 decimals)
|
|
28
|
+
*/
|
|
29
|
+
export declare class SubunitError extends RangeError {
|
|
30
|
+
readonly currency: string;
|
|
31
|
+
readonly maxDecimals: number;
|
|
32
|
+
constructor(currency: string, maxDecimals: number);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Thrown when an amount cannot be parsed as a valid number.
|
|
36
|
+
* @example
|
|
37
|
+
* new Money('USD', 'abc') // throws AmountError
|
|
38
|
+
*/
|
|
39
|
+
export declare class AmountError extends TypeError {
|
|
40
|
+
readonly amount: unknown;
|
|
41
|
+
constructor(amount: unknown);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Thrown when an exchange rate is not available.
|
|
45
|
+
* @example
|
|
46
|
+
* converter.convert(usdMoney, 'XYZ') // throws ExchangeRateError if no USD->XYZ rate
|
|
47
|
+
*/
|
|
48
|
+
export declare class ExchangeRateError extends Error {
|
|
49
|
+
readonly fromCurrency: string;
|
|
50
|
+
readonly toCurrency: string;
|
|
51
|
+
constructor(fromCurrency: string, toCurrency: string);
|
|
52
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Custom error types for Money operations.
|
|
4
|
+
* All errors extend built-in Error types for proper instanceof checks.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.ExchangeRateError = exports.AmountError = exports.SubunitError = exports.CurrencyUnknownError = exports.CurrencyMismatchError = void 0;
|
|
8
|
+
/**
|
|
9
|
+
* Thrown when attempting operations between different currencies.
|
|
10
|
+
* @example
|
|
11
|
+
* new Money('USD', 10).add(new Money('EUR', 5)) // throws CurrencyMismatchError
|
|
12
|
+
*/
|
|
13
|
+
class CurrencyMismatchError extends TypeError {
|
|
14
|
+
constructor(fromCurrency, toCurrency) {
|
|
15
|
+
super(`Cannot operate on ${fromCurrency} and ${toCurrency} - currencies must match`);
|
|
16
|
+
this.name = 'CurrencyMismatchError';
|
|
17
|
+
this.fromCurrency = fromCurrency;
|
|
18
|
+
this.toCurrency = toCurrency;
|
|
19
|
+
Error.captureStackTrace?.(this, CurrencyMismatchError);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.CurrencyMismatchError = CurrencyMismatchError;
|
|
23
|
+
/**
|
|
24
|
+
* Thrown when using an unregistered currency code.
|
|
25
|
+
* @example
|
|
26
|
+
* new Money('FAKE', 10) // throws CurrencyUnknownError
|
|
27
|
+
*/
|
|
28
|
+
class CurrencyUnknownError extends TypeError {
|
|
29
|
+
constructor(currency) {
|
|
30
|
+
super(`Unknown currency '${currency}' - register it first with Money.registerCurrency()`);
|
|
31
|
+
this.name = 'CurrencyUnknownError';
|
|
32
|
+
this.currency = currency;
|
|
33
|
+
Error.captureStackTrace?.(this, CurrencyUnknownError);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.CurrencyUnknownError = CurrencyUnknownError;
|
|
37
|
+
/**
|
|
38
|
+
* Thrown when an amount has more decimal places than the currency allows.
|
|
39
|
+
* @example
|
|
40
|
+
* new Money('USD', '1.234') // throws SubunitError (USD only allows 2 decimals)
|
|
41
|
+
*/
|
|
42
|
+
class SubunitError extends RangeError {
|
|
43
|
+
constructor(currency, maxDecimals) {
|
|
44
|
+
super(`${currency} only supports ${maxDecimals} decimal place(s)`);
|
|
45
|
+
this.name = 'SubunitError';
|
|
46
|
+
this.currency = currency;
|
|
47
|
+
this.maxDecimals = maxDecimals;
|
|
48
|
+
Error.captureStackTrace?.(this, SubunitError);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.SubunitError = SubunitError;
|
|
52
|
+
/**
|
|
53
|
+
* Thrown when an amount cannot be parsed as a valid number.
|
|
54
|
+
* @example
|
|
55
|
+
* new Money('USD', 'abc') // throws AmountError
|
|
56
|
+
*/
|
|
57
|
+
class AmountError extends TypeError {
|
|
58
|
+
constructor(amount) {
|
|
59
|
+
super(`Invalid amount: ${JSON.stringify(amount)}`);
|
|
60
|
+
this.name = 'AmountError';
|
|
61
|
+
this.amount = amount;
|
|
62
|
+
Error.captureStackTrace?.(this, AmountError);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.AmountError = AmountError;
|
|
66
|
+
/**
|
|
67
|
+
* Thrown when an exchange rate is not available.
|
|
68
|
+
* @example
|
|
69
|
+
* converter.convert(usdMoney, 'XYZ') // throws ExchangeRateError if no USD->XYZ rate
|
|
70
|
+
*/
|
|
71
|
+
class ExchangeRateError extends Error {
|
|
72
|
+
constructor(fromCurrency, toCurrency) {
|
|
73
|
+
super(`No exchange rate available from ${fromCurrency} to ${toCurrency}`);
|
|
74
|
+
this.name = 'ExchangeRateError';
|
|
75
|
+
this.fromCurrency = fromCurrency;
|
|
76
|
+
this.toCurrency = toCurrency;
|
|
77
|
+
Error.captureStackTrace?.(this, ExchangeRateError);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.ExchangeRateError = ExchangeRateError;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exchange Rate Service - Central authority for currency conversion rates.
|
|
3
|
+
*
|
|
4
|
+
* Design principles:
|
|
5
|
+
* - Rates are stored as strings to preserve precision
|
|
6
|
+
* - Timestamps track when rates were set
|
|
7
|
+
* - Optional source tracking for audit trails
|
|
8
|
+
* - Inverse rates can be auto-generated or explicitly set
|
|
9
|
+
*/
|
|
10
|
+
export interface ExchangeRate {
|
|
11
|
+
from: string;
|
|
12
|
+
to: string;
|
|
13
|
+
rate: string;
|
|
14
|
+
timestamp: Date;
|
|
15
|
+
source?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface RatePair {
|
|
18
|
+
forward: ExchangeRate;
|
|
19
|
+
reverse: ExchangeRate;
|
|
20
|
+
/** Relative discrepancy between forward and 1/reverse rates */
|
|
21
|
+
discrepancy: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Service for managing exchange rates between currencies.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const rates = new ExchangeRateService()
|
|
28
|
+
* rates.setRate('USD', 'EUR', 0.92, 'ECB')
|
|
29
|
+
* rates.getRate('USD', 'EUR') // { from: 'USD', to: 'EUR', rate: '0.92', ... }
|
|
30
|
+
*/
|
|
31
|
+
export declare class ExchangeRateService {
|
|
32
|
+
#private;
|
|
33
|
+
/**
|
|
34
|
+
* Set an exchange rate.
|
|
35
|
+
*
|
|
36
|
+
* @param from - Source currency code
|
|
37
|
+
* @param to - Target currency code
|
|
38
|
+
* @param rate - Exchange rate (1 unit of 'from' = rate units of 'to')
|
|
39
|
+
* @param source - Optional source identifier (e.g., 'ECB', 'Coinbase')
|
|
40
|
+
* @param autoInverse - If true, automatically create inverse rate (default: true)
|
|
41
|
+
*/
|
|
42
|
+
setRate(from: string, to: string, rate: number | string, source?: string, autoInverse?: boolean): void;
|
|
43
|
+
/**
|
|
44
|
+
* Get an exchange rate.
|
|
45
|
+
*
|
|
46
|
+
* @param from - Source currency code
|
|
47
|
+
* @param to - Target currency code
|
|
48
|
+
* @returns The exchange rate, or undefined if not set
|
|
49
|
+
*/
|
|
50
|
+
getRate(from: string, to: string): ExchangeRate | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Check if a rate exists.
|
|
53
|
+
*/
|
|
54
|
+
hasRate(from: string, to: string): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Remove a rate.
|
|
57
|
+
*/
|
|
58
|
+
removeRate(from: string, to: string): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Get both forward and reverse rates, with discrepancy analysis.
|
|
61
|
+
* Useful for detecting rate inconsistencies.
|
|
62
|
+
*
|
|
63
|
+
* @param currencyA - First currency
|
|
64
|
+
* @param currencyB - Second currency
|
|
65
|
+
* @returns Rate pair with discrepancy, or undefined if either rate is missing
|
|
66
|
+
*/
|
|
67
|
+
getRatePair(currencyA: string, currencyB: string): RatePair | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* Get all rates for a specific base currency.
|
|
70
|
+
*
|
|
71
|
+
* @param base - The base currency code
|
|
72
|
+
* @returns Array of rates from this currency
|
|
73
|
+
*/
|
|
74
|
+
getRatesFrom(base: string): ExchangeRate[];
|
|
75
|
+
/**
|
|
76
|
+
* Get all registered rates.
|
|
77
|
+
*/
|
|
78
|
+
getAllRates(): ExchangeRate[];
|
|
79
|
+
/**
|
|
80
|
+
* Clear all rates.
|
|
81
|
+
*/
|
|
82
|
+
clear(): void;
|
|
83
|
+
/**
|
|
84
|
+
* Load rates from a simple object.
|
|
85
|
+
*
|
|
86
|
+
* @param rates - Object where keys are "FROM:TO" and values are rates
|
|
87
|
+
* @param source - Optional source identifier
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* service.loadRates({
|
|
91
|
+
* 'USD:EUR': 0.92,
|
|
92
|
+
* 'USD:GBP': 0.79,
|
|
93
|
+
* }, 'daily-update')
|
|
94
|
+
*/
|
|
95
|
+
loadRates(rates: Record<string, number | string>, source?: string): void;
|
|
96
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Exchange Rate Service - Central authority for currency conversion rates.
|
|
4
|
+
*
|
|
5
|
+
* Design principles:
|
|
6
|
+
* - Rates are stored as strings to preserve precision
|
|
7
|
+
* - Timestamps track when rates were set
|
|
8
|
+
* - Optional source tracking for audit trails
|
|
9
|
+
* - Inverse rates can be auto-generated or explicitly set
|
|
10
|
+
*/
|
|
11
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
12
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
13
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
14
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
15
|
+
};
|
|
16
|
+
var _ExchangeRateService_instances, _ExchangeRateService_rates, _ExchangeRateService_key;
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.ExchangeRateService = void 0;
|
|
19
|
+
/**
|
|
20
|
+
* Service for managing exchange rates between currencies.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const rates = new ExchangeRateService()
|
|
24
|
+
* rates.setRate('USD', 'EUR', 0.92, 'ECB')
|
|
25
|
+
* rates.getRate('USD', 'EUR') // { from: 'USD', to: 'EUR', rate: '0.92', ... }
|
|
26
|
+
*/
|
|
27
|
+
class ExchangeRateService {
|
|
28
|
+
constructor() {
|
|
29
|
+
_ExchangeRateService_instances.add(this);
|
|
30
|
+
_ExchangeRateService_rates.set(this, new Map()
|
|
31
|
+
/**
|
|
32
|
+
* Create a rate key for storage.
|
|
33
|
+
*/
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Set an exchange rate.
|
|
38
|
+
*
|
|
39
|
+
* @param from - Source currency code
|
|
40
|
+
* @param to - Target currency code
|
|
41
|
+
* @param rate - Exchange rate (1 unit of 'from' = rate units of 'to')
|
|
42
|
+
* @param source - Optional source identifier (e.g., 'ECB', 'Coinbase')
|
|
43
|
+
* @param autoInverse - If true, automatically create inverse rate (default: true)
|
|
44
|
+
*/
|
|
45
|
+
setRate(from, to, rate, source, autoInverse = true) {
|
|
46
|
+
const rateStr = typeof rate === 'number' ? rate.toPrecision(15) : rate;
|
|
47
|
+
__classPrivateFieldGet(this, _ExchangeRateService_rates, "f").set(__classPrivateFieldGet(this, _ExchangeRateService_instances, "m", _ExchangeRateService_key).call(this, from, to), {
|
|
48
|
+
from,
|
|
49
|
+
to,
|
|
50
|
+
rate: rateStr,
|
|
51
|
+
timestamp: new Date(),
|
|
52
|
+
source,
|
|
53
|
+
});
|
|
54
|
+
// Auto-create inverse rate if requested and not already explicitly set
|
|
55
|
+
if (autoInverse) {
|
|
56
|
+
const inverseKey = __classPrivateFieldGet(this, _ExchangeRateService_instances, "m", _ExchangeRateService_key).call(this, to, from);
|
|
57
|
+
if (!__classPrivateFieldGet(this, _ExchangeRateService_rates, "f").has(inverseKey)) {
|
|
58
|
+
const inverseRate = 1 / Number(rateStr);
|
|
59
|
+
__classPrivateFieldGet(this, _ExchangeRateService_rates, "f").set(inverseKey, {
|
|
60
|
+
from: to,
|
|
61
|
+
to: from,
|
|
62
|
+
rate: inverseRate.toPrecision(15),
|
|
63
|
+
timestamp: new Date(),
|
|
64
|
+
source: source ? `${source} (inverse)` : '(inverse)',
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Get an exchange rate.
|
|
71
|
+
*
|
|
72
|
+
* @param from - Source currency code
|
|
73
|
+
* @param to - Target currency code
|
|
74
|
+
* @returns The exchange rate, or undefined if not set
|
|
75
|
+
*/
|
|
76
|
+
getRate(from, to) {
|
|
77
|
+
// Same currency = rate of 1
|
|
78
|
+
if (from === to) {
|
|
79
|
+
return {
|
|
80
|
+
from,
|
|
81
|
+
to,
|
|
82
|
+
rate: '1',
|
|
83
|
+
timestamp: new Date(),
|
|
84
|
+
source: '(identity)',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return __classPrivateFieldGet(this, _ExchangeRateService_rates, "f").get(__classPrivateFieldGet(this, _ExchangeRateService_instances, "m", _ExchangeRateService_key).call(this, from, to));
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Check if a rate exists.
|
|
91
|
+
*/
|
|
92
|
+
hasRate(from, to) {
|
|
93
|
+
return from === to || __classPrivateFieldGet(this, _ExchangeRateService_rates, "f").has(__classPrivateFieldGet(this, _ExchangeRateService_instances, "m", _ExchangeRateService_key).call(this, from, to));
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Remove a rate.
|
|
97
|
+
*/
|
|
98
|
+
removeRate(from, to) {
|
|
99
|
+
return __classPrivateFieldGet(this, _ExchangeRateService_rates, "f").delete(__classPrivateFieldGet(this, _ExchangeRateService_instances, "m", _ExchangeRateService_key).call(this, from, to));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Get both forward and reverse rates, with discrepancy analysis.
|
|
103
|
+
* Useful for detecting rate inconsistencies.
|
|
104
|
+
*
|
|
105
|
+
* @param currencyA - First currency
|
|
106
|
+
* @param currencyB - Second currency
|
|
107
|
+
* @returns Rate pair with discrepancy, or undefined if either rate is missing
|
|
108
|
+
*/
|
|
109
|
+
getRatePair(currencyA, currencyB) {
|
|
110
|
+
const forward = this.getRate(currencyA, currencyB);
|
|
111
|
+
const reverse = this.getRate(currencyB, currencyA);
|
|
112
|
+
if (!forward || !reverse) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
// Calculate discrepancy: how far is forward * reverse from 1.0?
|
|
116
|
+
const product = Number(forward.rate) * Number(reverse.rate);
|
|
117
|
+
const discrepancy = Math.abs(1 - product);
|
|
118
|
+
return { forward, reverse, discrepancy };
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get all rates for a specific base currency.
|
|
122
|
+
*
|
|
123
|
+
* @param base - The base currency code
|
|
124
|
+
* @returns Array of rates from this currency
|
|
125
|
+
*/
|
|
126
|
+
getRatesFrom(base) {
|
|
127
|
+
const rates = [];
|
|
128
|
+
for (const rate of __classPrivateFieldGet(this, _ExchangeRateService_rates, "f").values()) {
|
|
129
|
+
if (rate.from === base) {
|
|
130
|
+
rates.push(rate);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return rates.sort((a, b) => a.to.localeCompare(b.to));
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Get all registered rates.
|
|
137
|
+
*/
|
|
138
|
+
getAllRates() {
|
|
139
|
+
return Array.from(__classPrivateFieldGet(this, _ExchangeRateService_rates, "f").values()).sort((a, b) => {
|
|
140
|
+
const fromCompare = a.from.localeCompare(b.from);
|
|
141
|
+
return fromCompare !== 0 ? fromCompare : a.to.localeCompare(b.to);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Clear all rates.
|
|
146
|
+
*/
|
|
147
|
+
clear() {
|
|
148
|
+
__classPrivateFieldGet(this, _ExchangeRateService_rates, "f").clear();
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Load rates from a simple object.
|
|
152
|
+
*
|
|
153
|
+
* @param rates - Object where keys are "FROM:TO" and values are rates
|
|
154
|
+
* @param source - Optional source identifier
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* service.loadRates({
|
|
158
|
+
* 'USD:EUR': 0.92,
|
|
159
|
+
* 'USD:GBP': 0.79,
|
|
160
|
+
* }, 'daily-update')
|
|
161
|
+
*/
|
|
162
|
+
loadRates(rates, source) {
|
|
163
|
+
for (const [key, rate] of Object.entries(rates)) {
|
|
164
|
+
const [from, to] = key.split(':');
|
|
165
|
+
if (from && to) {
|
|
166
|
+
this.setRate(from, to, rate, source, false); // Don't auto-inverse when batch loading
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
exports.ExchangeRateService = ExchangeRateService;
|
|
172
|
+
_ExchangeRateService_rates = new WeakMap(), _ExchangeRateService_instances = new WeakSet(), _ExchangeRateService_key = function _ExchangeRateService_key(from, to) {
|
|
173
|
+
return `${from}:${to}`;
|
|
174
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* subunit-money - A type-safe value object for monetary amounts
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* import { Money, ExchangeRateService, MoneyConverter } from '@cbrunnkvist/subunit-money'
|
|
6
|
+
*
|
|
7
|
+
* // Basic usage
|
|
8
|
+
* const price = new Money('USD', '19.99')
|
|
9
|
+
* const tax = price.multiply(0.08)
|
|
10
|
+
* const total = price.add(tax)
|
|
11
|
+
*
|
|
12
|
+
* // Currency conversion
|
|
13
|
+
* const rates = new ExchangeRateService()
|
|
14
|
+
* rates.setRate('USD', 'EUR', 0.92)
|
|
15
|
+
* const converter = new MoneyConverter(rates)
|
|
16
|
+
* const euros = converter.convert(total, 'EUR')
|
|
17
|
+
*/
|
|
18
|
+
export { Money, type MoneyObject } from './money.js';
|
|
19
|
+
export { ExchangeRateService, type ExchangeRate, type RatePair } from './exchange-rate-service.js';
|
|
20
|
+
export { MoneyConverter } from './money-converter.js';
|
|
21
|
+
export { CurrencyMismatchError, CurrencyUnknownError, SubunitError, AmountError, ExchangeRateError, } from './errors.js';
|
|
22
|
+
export { registerCurrency, getCurrency, hasCurrency, getAllCurrencies, loadCurrencyMap, clearCurrencies, type CurrencyDefinition, } from './currency.js';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* subunit-money - A type-safe value object for monetary amounts
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* import { Money, ExchangeRateService, MoneyConverter } from '@cbrunnkvist/subunit-money'
|
|
7
|
+
*
|
|
8
|
+
* // Basic usage
|
|
9
|
+
* const price = new Money('USD', '19.99')
|
|
10
|
+
* const tax = price.multiply(0.08)
|
|
11
|
+
* const total = price.add(tax)
|
|
12
|
+
*
|
|
13
|
+
* // Currency conversion
|
|
14
|
+
* const rates = new ExchangeRateService()
|
|
15
|
+
* rates.setRate('USD', 'EUR', 0.92)
|
|
16
|
+
* const converter = new MoneyConverter(rates)
|
|
17
|
+
* const euros = converter.convert(total, 'EUR')
|
|
18
|
+
*/
|
|
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
|
+
// Core classes
|
|
25
|
+
var money_js_1 = require("./money.js");
|
|
26
|
+
Object.defineProperty(exports, "Money", { enumerable: true, get: function () { return money_js_1.Money; } });
|
|
27
|
+
var exchange_rate_service_js_1 = require("./exchange-rate-service.js");
|
|
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; } });
|
|
31
|
+
// Error types
|
|
32
|
+
var errors_js_1 = require("./errors.js");
|
|
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; } });
|
|
38
|
+
// Currency utilities
|
|
39
|
+
var currency_js_1 = require("./currency.js");
|
|
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; } });
|
|
46
|
+
// Auto-load default currencies
|
|
47
|
+
// The currencymap.json file is the official ISO 4217 currency list (List One) as of 2026-01-01,
|
|
48
|
+
// sourced from SIX Financial Information AG (the ISO 4217 Maintenance Agency).
|
|
49
|
+
//
|
|
50
|
+
// To regenerate:
|
|
51
|
+
// 1. Download list-one.xml from https://www.six-group.com/en/products-services/financial-information/data-standards.html
|
|
52
|
+
// 2. Convert with: yq -p xml -o json '.' list-one.xml | jq '.ISO_4217.CcyTbl.CcyNtry | map(select(.Ccy) | {(.Ccy): {decimal_digits: (if (.CcyMnrUnts == "N.A." or .CcyMnrUnts == null) then 0 else (.CcyMnrUnts | tonumber) end)}}) | add | to_entries | sort_by(.key) | from_entries' > currencymap.json
|
|
53
|
+
//
|
|
54
|
+
// Note: This excludes historical currencies, supranational funds, and precious metals,
|
|
55
|
+
// keeping only active national and regional currencies for practical use.
|
|
56
|
+
const currency_js_2 = require("./currency.js");
|
|
57
|
+
const currencymap_json_1 = __importDefault(require("../currencymap.json"));
|
|
58
|
+
(0, currency_js_2.loadCurrencyMap)(currencymap_json_1.default);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Money Converter - Safe cross-currency operations.
|
|
3
|
+
*
|
|
4
|
+
* Bridges Money objects with ExchangeRateService to enable:
|
|
5
|
+
* - Currency conversion
|
|
6
|
+
* - Multi-currency arithmetic
|
|
7
|
+
* - Percentage calculations across currencies
|
|
8
|
+
*/
|
|
9
|
+
import { Money } from './money.js';
|
|
10
|
+
import { ExchangeRateService } from './exchange-rate-service.js';
|
|
11
|
+
/**
|
|
12
|
+
* Converter for performing operations between different currencies.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const rates = new ExchangeRateService()
|
|
16
|
+
* rates.setRate('USD', 'EUR', 0.92)
|
|
17
|
+
*
|
|
18
|
+
* const converter = new MoneyConverter(rates)
|
|
19
|
+
* const euros = converter.convert(new Money('USD', '100'), 'EUR')
|
|
20
|
+
* console.log(euros.toString()) // "92.00 EUR"
|
|
21
|
+
*/
|
|
22
|
+
export declare class MoneyConverter {
|
|
23
|
+
#private;
|
|
24
|
+
constructor(rateService: ExchangeRateService);
|
|
25
|
+
/**
|
|
26
|
+
* Convert a Money amount to another currency.
|
|
27
|
+
*
|
|
28
|
+
* @param money - The amount to convert
|
|
29
|
+
* @param targetCurrency - The target currency code
|
|
30
|
+
* @returns A new Money in the target currency
|
|
31
|
+
* @throws {ExchangeRateError} If no rate is available
|
|
32
|
+
*/
|
|
33
|
+
convert<From extends string, To extends string>(money: Money<From>, targetCurrency: To): Money<To>;
|
|
34
|
+
/**
|
|
35
|
+
* Add two Money amounts, converting as needed.
|
|
36
|
+
*
|
|
37
|
+
* @param a - First amount
|
|
38
|
+
* @param b - Second amount
|
|
39
|
+
* @param resultCurrency - Currency for the result (must be one of the input currencies)
|
|
40
|
+
* @returns Sum in the result currency
|
|
41
|
+
*/
|
|
42
|
+
add<A extends string, B extends string, R extends A | B>(a: Money<A>, b: Money<B>, resultCurrency: R): Money<R>;
|
|
43
|
+
/**
|
|
44
|
+
* Subtract two Money amounts, converting as needed.
|
|
45
|
+
*
|
|
46
|
+
* @param a - Amount to subtract from
|
|
47
|
+
* @param b - Amount to subtract
|
|
48
|
+
* @param resultCurrency - Currency for the result
|
|
49
|
+
* @returns Difference in the result currency
|
|
50
|
+
*/
|
|
51
|
+
subtract<A extends string, B extends string, R extends A | B>(a: Money<A>, b: Money<B>, resultCurrency: R): Money<R>;
|
|
52
|
+
/**
|
|
53
|
+
* Calculate what percentage one amount is of another.
|
|
54
|
+
* Converts both to the same currency before comparison.
|
|
55
|
+
*
|
|
56
|
+
* @param part - The partial amount
|
|
57
|
+
* @param whole - The whole amount
|
|
58
|
+
* @returns Percentage as a number (e.g., 25 for 25%)
|
|
59
|
+
*/
|
|
60
|
+
percentageOf<A extends string, B extends string>(part: Money<A>, whole: Money<B>): number;
|
|
61
|
+
/**
|
|
62
|
+
* Sum multiple Money amounts, converting all to a target currency.
|
|
63
|
+
*
|
|
64
|
+
* @param amounts - Array of Money objects (can be different currencies)
|
|
65
|
+
* @param targetCurrency - Currency for the result
|
|
66
|
+
* @returns Total in the target currency
|
|
67
|
+
*/
|
|
68
|
+
sum<C extends string>(amounts: Money<string>[], targetCurrency: C): Money<C>;
|
|
69
|
+
/**
|
|
70
|
+
* Compare two Money amounts across currencies.
|
|
71
|
+
* Returns negative if a < b, zero if equal, positive if a > b.
|
|
72
|
+
*
|
|
73
|
+
* @param a - First amount
|
|
74
|
+
* @param b - Second amount
|
|
75
|
+
* @returns Comparison result
|
|
76
|
+
*/
|
|
77
|
+
compare<A extends string, B extends string>(a: Money<A>, b: Money<B>): -1 | 0 | 1;
|
|
78
|
+
/**
|
|
79
|
+
* Get the exchange rate service (for direct rate access).
|
|
80
|
+
*/
|
|
81
|
+
get rateService(): ExchangeRateService;
|
|
82
|
+
}
|