subunit-money 3.1.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 +1314 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +416 -0
- package/dist/index.d.ts +405 -11
- package/dist/index.js +1273 -36
- package/dist/index.js.map +1 -0
- package/package.json +14 -7
- package/dist/cjs/currency.d.ts +0 -38
- package/dist/cjs/currency.js +0 -56
- package/dist/cjs/errors.d.ts +0 -52
- package/dist/cjs/errors.js +0 -80
- package/dist/cjs/exchange-rate-service.d.ts +0 -96
- package/dist/cjs/exchange-rate-service.js +0 -174
- package/dist/cjs/index.d.ts +0 -22
- package/dist/cjs/index.js +0 -58
- package/dist/cjs/money-converter.d.ts +0 -82
- package/dist/cjs/money-converter.js +0 -172
- package/dist/cjs/money.d.ts +0 -146
- package/dist/cjs/money.js +0 -362
- package/dist/currency.d.ts +0 -38
- package/dist/currency.js +0 -48
- package/dist/errors.d.ts +0 -52
- package/dist/errors.js +0 -72
- package/dist/exchange-rate-service.d.ts +0 -96
- package/dist/exchange-rate-service.js +0 -170
- package/dist/money-converter.d.ts +0 -82
- package/dist/money-converter.js +0 -168
- package/dist/money.d.ts +0 -146
- package/dist/money.js +0 -358
package/dist/cjs/money.js
DELETED
|
@@ -1,362 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Money - An immutable value object for monetary amounts.
|
|
4
|
-
*
|
|
5
|
-
* Design principles:
|
|
6
|
-
* - Immutable: all operations return new instances
|
|
7
|
-
* - Type-safe: currency mismatches are caught at compile time (when possible) and runtime
|
|
8
|
-
* - Precise: uses BigInt internally to avoid floating-point errors
|
|
9
|
-
* - String-based API: amounts are strings to preserve precision in JSON/DB round-trips
|
|
10
|
-
*/
|
|
11
|
-
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
12
|
-
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
13
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
14
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
15
|
-
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
16
|
-
};
|
|
17
|
-
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
18
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
19
|
-
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");
|
|
20
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
21
|
-
};
|
|
22
|
-
var _Money_instances, _a, _Money_subunits, _Money_currencyDef, _Money_parseAmount, _Money_assertSameCurrency, _Money_getInternalValue, _Money_parseFactor, _Money_roundedDivide, _Money_createFromSubunits, _Money_formatSubunits;
|
|
23
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.Money = void 0;
|
|
25
|
-
const errors_js_1 = require("./errors.js");
|
|
26
|
-
const currency_js_1 = require("./currency.js");
|
|
27
|
-
/**
|
|
28
|
-
* Money class - represents a monetary amount in a specific currency.
|
|
29
|
-
*
|
|
30
|
-
* @typeParam C - The currency code type (enables compile-time currency checking)
|
|
31
|
-
*
|
|
32
|
-
* @example
|
|
33
|
-
* const price = new Money('USD', '19.99')
|
|
34
|
-
* const tax = price.multiply(0.08)
|
|
35
|
-
* const total = price.add(tax)
|
|
36
|
-
* console.log(total.amount) // "21.59"
|
|
37
|
-
*/
|
|
38
|
-
class Money {
|
|
39
|
-
/**
|
|
40
|
-
* Create a new Money instance.
|
|
41
|
-
*
|
|
42
|
-
* @param currency - ISO 4217 currency code (must be registered)
|
|
43
|
-
* @param amount - The amount as a number or string
|
|
44
|
-
* @throws {CurrencyUnknownError} If the currency is not registered
|
|
45
|
-
* @throws {AmountError} If the amount is not a valid number
|
|
46
|
-
* @throws {SubunitError} If the amount has more decimals than the currency allows
|
|
47
|
-
*/
|
|
48
|
-
constructor(currency, amount) {
|
|
49
|
-
_Money_instances.add(this);
|
|
50
|
-
// Private BigInt storage - stores currency native subunits directly
|
|
51
|
-
_Money_subunits.set(this, void 0);
|
|
52
|
-
_Money_currencyDef.set(this, void 0);
|
|
53
|
-
const currencyDef = (0, currency_js_1.getCurrency)(currency);
|
|
54
|
-
if (!currencyDef) {
|
|
55
|
-
throw new errors_js_1.CurrencyUnknownError(currency);
|
|
56
|
-
}
|
|
57
|
-
this.currency = currency;
|
|
58
|
-
__classPrivateFieldSet(this, _Money_currencyDef, currencyDef, "f");
|
|
59
|
-
__classPrivateFieldSet(this, _Money_subunits, __classPrivateFieldGet(this, _Money_instances, "m", _Money_parseAmount).call(this, amount), "f");
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* The amount as a formatted string with correct decimal places.
|
|
63
|
-
* @example
|
|
64
|
-
* new Money('USD', 19.9).amount // "19.90"
|
|
65
|
-
* new Money('JPY', 1000).amount // "1000"
|
|
66
|
-
*/
|
|
67
|
-
get amount() {
|
|
68
|
-
const decimals = __classPrivateFieldGet(this, _Money_currencyDef, "f").decimalDigits;
|
|
69
|
-
const abs = __classPrivateFieldGet(this, _Money_subunits, "f") < 0n ? -__classPrivateFieldGet(this, _Money_subunits, "f") : __classPrivateFieldGet(this, _Money_subunits, "f");
|
|
70
|
-
const isNegative = __classPrivateFieldGet(this, _Money_subunits, "f") < 0n;
|
|
71
|
-
if (decimals === 0) {
|
|
72
|
-
return `${isNegative ? '-' : ''}${abs}`;
|
|
73
|
-
}
|
|
74
|
-
const multiplier = 10n ** BigInt(decimals);
|
|
75
|
-
const wholePart = abs / multiplier;
|
|
76
|
-
const fracPart = abs % multiplier;
|
|
77
|
-
const sign = isNegative ? '-' : '';
|
|
78
|
-
return `${sign}${wholePart}.${fracPart.toString().padStart(decimals, '0')}`;
|
|
79
|
-
}
|
|
80
|
-
// ============ Arithmetic Operations ============
|
|
81
|
-
/**
|
|
82
|
-
* Add another Money amount.
|
|
83
|
-
* @throws {CurrencyMismatchError} If currencies don't match
|
|
84
|
-
*/
|
|
85
|
-
add(other) {
|
|
86
|
-
__classPrivateFieldGet(this, _Money_instances, "m", _Money_assertSameCurrency).call(this, other);
|
|
87
|
-
const result = __classPrivateFieldGet(this, _Money_subunits, "f") + __classPrivateFieldGet(other, _Money_instances, "m", _Money_getInternalValue).call(other);
|
|
88
|
-
return __classPrivateFieldGet(_a, _a, "m", _Money_createFromSubunits).call(_a, result, this.currency, __classPrivateFieldGet(this, _Money_currencyDef, "f"));
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Subtract another Money amount.
|
|
92
|
-
* @throws {CurrencyMismatchError} If currencies don't match
|
|
93
|
-
*/
|
|
94
|
-
subtract(other) {
|
|
95
|
-
__classPrivateFieldGet(this, _Money_instances, "m", _Money_assertSameCurrency).call(this, other);
|
|
96
|
-
const result = __classPrivateFieldGet(this, _Money_subunits, "f") - __classPrivateFieldGet(other, _Money_instances, "m", _Money_getInternalValue).call(other);
|
|
97
|
-
return __classPrivateFieldGet(_a, _a, "m", _Money_createFromSubunits).call(_a, result, this.currency, __classPrivateFieldGet(this, _Money_currencyDef, "f"));
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Multiply by a factor.
|
|
101
|
-
*
|
|
102
|
-
* DESIGN: Rounds immediately after multiplication using banker's rounding
|
|
103
|
-
* (round half-to-even). This prevents the "split penny problem".
|
|
104
|
-
*/
|
|
105
|
-
multiply(factor) {
|
|
106
|
-
if (typeof factor !== 'number' || !Number.isFinite(factor)) {
|
|
107
|
-
throw new TypeError(`Factor must be a finite number, got: ${factor}`);
|
|
108
|
-
}
|
|
109
|
-
const { value: factorValue, scale } = __classPrivateFieldGet(_a, _a, "m", _Money_parseFactor).call(_a, factor);
|
|
110
|
-
const product = __classPrivateFieldGet(this, _Money_subunits, "f") * factorValue;
|
|
111
|
-
const divisor = 10n ** scale;
|
|
112
|
-
const result = __classPrivateFieldGet(_a, _a, "m", _Money_roundedDivide).call(_a, product, divisor);
|
|
113
|
-
return __classPrivateFieldGet(_a, _a, "m", _Money_createFromSubunits).call(_a, result, this.currency, __classPrivateFieldGet(this, _Money_currencyDef, "f"));
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Allocate this amount proportionally.
|
|
117
|
-
* Handles remainder distribution to avoid losing pennies.
|
|
118
|
-
*
|
|
119
|
-
* @param proportions - Array of proportions (e.g., [1, 1, 1] for three-way split)
|
|
120
|
-
* @returns Array of Money objects that sum to the original amount
|
|
121
|
-
*/
|
|
122
|
-
allocate(proportions) {
|
|
123
|
-
if (!Array.isArray(proportions) || proportions.length === 0) {
|
|
124
|
-
throw new TypeError('Proportions must be a non-empty array');
|
|
125
|
-
}
|
|
126
|
-
for (const p of proportions) {
|
|
127
|
-
if (typeof p !== 'number' || !Number.isFinite(p) || p < 0) {
|
|
128
|
-
throw new TypeError('All proportions must be non-negative finite numbers');
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
const total = proportions.reduce((sum, p) => sum + p, 0);
|
|
132
|
-
if (total <= 0) {
|
|
133
|
-
throw new TypeError('Sum of proportions must be positive');
|
|
134
|
-
}
|
|
135
|
-
const totalSubunits = __classPrivateFieldGet(this, _Money_subunits, "f");
|
|
136
|
-
// Calculate base allocations
|
|
137
|
-
const allocations = proportions.map((p) => {
|
|
138
|
-
return (totalSubunits * BigInt(Math.round(p * 1000000))) / BigInt(Math.round(total * 1000000));
|
|
139
|
-
});
|
|
140
|
-
// Distribute remainder
|
|
141
|
-
let remainder = totalSubunits - allocations.reduce((sum, a) => sum + a, 0n);
|
|
142
|
-
let i = 0;
|
|
143
|
-
while (remainder > 0n) {
|
|
144
|
-
allocations[i % allocations.length] += 1n;
|
|
145
|
-
remainder -= 1n;
|
|
146
|
-
i++;
|
|
147
|
-
}
|
|
148
|
-
while (remainder < 0n) {
|
|
149
|
-
allocations[i % allocations.length] -= 1n;
|
|
150
|
-
remainder += 1n;
|
|
151
|
-
i++;
|
|
152
|
-
}
|
|
153
|
-
// Convert back to Money objects
|
|
154
|
-
return allocations.map((subunits) => {
|
|
155
|
-
return __classPrivateFieldGet(_a, _a, "m", _Money_createFromSubunits).call(_a, subunits, this.currency, __classPrivateFieldGet(this, _Money_currencyDef, "f"));
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
// ============ Comparison Operations ============
|
|
159
|
-
/**
|
|
160
|
-
* Check if this amount equals another.
|
|
161
|
-
* @throws {CurrencyMismatchError} If currencies don't match
|
|
162
|
-
*/
|
|
163
|
-
equalTo(other) {
|
|
164
|
-
__classPrivateFieldGet(this, _Money_instances, "m", _Money_assertSameCurrency).call(this, other);
|
|
165
|
-
return __classPrivateFieldGet(this, _Money_subunits, "f") === __classPrivateFieldGet(other, _Money_instances, "m", _Money_getInternalValue).call(other);
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Check if this amount is greater than another.
|
|
169
|
-
* @throws {CurrencyMismatchError} If currencies don't match
|
|
170
|
-
*/
|
|
171
|
-
greaterThan(other) {
|
|
172
|
-
__classPrivateFieldGet(this, _Money_instances, "m", _Money_assertSameCurrency).call(this, other);
|
|
173
|
-
return __classPrivateFieldGet(this, _Money_subunits, "f") > __classPrivateFieldGet(other, _Money_instances, "m", _Money_getInternalValue).call(other);
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Check if this amount is less than another.
|
|
177
|
-
* @throws {CurrencyMismatchError} If currencies don't match
|
|
178
|
-
*/
|
|
179
|
-
lessThan(other) {
|
|
180
|
-
__classPrivateFieldGet(this, _Money_instances, "m", _Money_assertSameCurrency).call(this, other);
|
|
181
|
-
return __classPrivateFieldGet(this, _Money_subunits, "f") < __classPrivateFieldGet(other, _Money_instances, "m", _Money_getInternalValue).call(other);
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Check if this amount is greater than or equal to another.
|
|
185
|
-
* @throws {CurrencyMismatchError} If currencies don't match
|
|
186
|
-
*/
|
|
187
|
-
greaterThanOrEqual(other) {
|
|
188
|
-
__classPrivateFieldGet(this, _Money_instances, "m", _Money_assertSameCurrency).call(this, other);
|
|
189
|
-
return __classPrivateFieldGet(this, _Money_subunits, "f") >= __classPrivateFieldGet(other, _Money_instances, "m", _Money_getInternalValue).call(other);
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Check if this amount is less than or equal to another.
|
|
193
|
-
* @throws {CurrencyMismatchError} If currencies don't match
|
|
194
|
-
*/
|
|
195
|
-
lessThanOrEqual(other) {
|
|
196
|
-
__classPrivateFieldGet(this, _Money_instances, "m", _Money_assertSameCurrency).call(this, other);
|
|
197
|
-
return __classPrivateFieldGet(this, _Money_subunits, "f") <= __classPrivateFieldGet(other, _Money_instances, "m", _Money_getInternalValue).call(other);
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Check if this amount is zero.
|
|
201
|
-
*/
|
|
202
|
-
isZero() {
|
|
203
|
-
return __classPrivateFieldGet(this, _Money_subunits, "f") === 0n;
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* Check if this amount is positive (greater than zero).
|
|
207
|
-
*/
|
|
208
|
-
isPositive() {
|
|
209
|
-
return __classPrivateFieldGet(this, _Money_subunits, "f") > 0n;
|
|
210
|
-
}
|
|
211
|
-
/**
|
|
212
|
-
* Check if this amount is negative (less than zero).
|
|
213
|
-
*/
|
|
214
|
-
isNegative() {
|
|
215
|
-
return __classPrivateFieldGet(this, _Money_subunits, "f") < 0n;
|
|
216
|
-
}
|
|
217
|
-
// ============ Serialization ============
|
|
218
|
-
/**
|
|
219
|
-
* Convert to a plain object (safe for JSON).
|
|
220
|
-
*/
|
|
221
|
-
toJSON() {
|
|
222
|
-
return {
|
|
223
|
-
currency: this.currency,
|
|
224
|
-
amount: this.amount,
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* Convert to string representation.
|
|
229
|
-
*/
|
|
230
|
-
toString() {
|
|
231
|
-
return `${this.amount} ${this.currency}`;
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Get the amount as a number (may lose precision for large values).
|
|
235
|
-
* Use with caution - prefer string-based operations.
|
|
236
|
-
*/
|
|
237
|
-
toNumber() {
|
|
238
|
-
return Number(this.amount);
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Get the amount in subunits (e.g., cents for USD).
|
|
242
|
-
* Useful for database storage (Stripe-style integer storage).
|
|
243
|
-
*/
|
|
244
|
-
toSubunits() {
|
|
245
|
-
return __classPrivateFieldGet(this, _Money_subunits, "f");
|
|
246
|
-
}
|
|
247
|
-
// ============ Static Factory Methods ============
|
|
248
|
-
/**
|
|
249
|
-
* Create a Money instance from a plain object.
|
|
250
|
-
*/
|
|
251
|
-
static fromObject(obj) {
|
|
252
|
-
return new _a(obj.currency, obj.amount);
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
* Create a Money instance from subunits (e.g., cents).
|
|
256
|
-
* Useful for loading from database (Stripe-style integer storage).
|
|
257
|
-
*/
|
|
258
|
-
static fromSubunits(subunits, currency) {
|
|
259
|
-
const currencyDef = (0, currency_js_1.getCurrency)(currency);
|
|
260
|
-
if (!currencyDef) {
|
|
261
|
-
throw new errors_js_1.CurrencyUnknownError(currency);
|
|
262
|
-
}
|
|
263
|
-
const bigintSubunits = typeof subunits === 'number' ? BigInt(subunits) : subunits;
|
|
264
|
-
return __classPrivateFieldGet(_a, _a, "m", _Money_createFromSubunits).call(_a, bigintSubunits, currency, currencyDef);
|
|
265
|
-
}
|
|
266
|
-
/**
|
|
267
|
-
* Compare two Money objects (for use with Array.sort).
|
|
268
|
-
* @throws {CurrencyMismatchError} If currencies don't match
|
|
269
|
-
*/
|
|
270
|
-
static compare(a, b) {
|
|
271
|
-
if (a.currency !== b.currency) {
|
|
272
|
-
throw new errors_js_1.CurrencyMismatchError(a.currency, b.currency);
|
|
273
|
-
}
|
|
274
|
-
const aVal = __classPrivateFieldGet(a, _Money_instances, "m", _Money_getInternalValue).call(a);
|
|
275
|
-
const bVal = __classPrivateFieldGet(b, _Money_instances, "m", _Money_getInternalValue).call(b);
|
|
276
|
-
if (aVal < bVal)
|
|
277
|
-
return -1;
|
|
278
|
-
if (aVal > bVal)
|
|
279
|
-
return 1;
|
|
280
|
-
return 0;
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* Create a zero amount in the specified currency.
|
|
284
|
-
*/
|
|
285
|
-
static zero(currency) {
|
|
286
|
-
return new _a(currency, '0');
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
exports.Money = Money;
|
|
290
|
-
_a = Money, _Money_subunits = new WeakMap(), _Money_currencyDef = new WeakMap(), _Money_instances = new WeakSet(), _Money_parseAmount = function _Money_parseAmount(amount) {
|
|
291
|
-
const str = typeof amount === 'number' ? String(amount) : amount;
|
|
292
|
-
const match = str.match(/^(-)?(\d+)(?:\.(\d+))?$/);
|
|
293
|
-
if (!match) {
|
|
294
|
-
throw new errors_js_1.AmountError(amount);
|
|
295
|
-
}
|
|
296
|
-
const [, sign, whole, frac = ''] = match;
|
|
297
|
-
if (frac.length > __classPrivateFieldGet(this, _Money_currencyDef, "f").decimalDigits) {
|
|
298
|
-
throw new errors_js_1.SubunitError(this.currency, __classPrivateFieldGet(this, _Money_currencyDef, "f").decimalDigits);
|
|
299
|
-
}
|
|
300
|
-
const paddedFrac = frac.padEnd(__classPrivateFieldGet(this, _Money_currencyDef, "f").decimalDigits, '0');
|
|
301
|
-
const combined = BigInt(whole + paddedFrac);
|
|
302
|
-
return sign === '-' ? -combined : combined;
|
|
303
|
-
}, _Money_assertSameCurrency = function _Money_assertSameCurrency(other) {
|
|
304
|
-
if (this.currency !== other.currency) {
|
|
305
|
-
throw new errors_js_1.CurrencyMismatchError(this.currency, other.currency);
|
|
306
|
-
}
|
|
307
|
-
}, _Money_getInternalValue = function _Money_getInternalValue() {
|
|
308
|
-
return __classPrivateFieldGet(this, _Money_subunits, "f");
|
|
309
|
-
}, _Money_parseFactor = function _Money_parseFactor(factor) {
|
|
310
|
-
const str = String(factor);
|
|
311
|
-
const [base, exponent] = str.split('e');
|
|
312
|
-
const baseMatch = base.match(/^(-)?(\d+)(?:\.(\d+))?$/);
|
|
313
|
-
if (!baseMatch) {
|
|
314
|
-
// Fallback for unlikely cases, though String(number) should strictly produce valid formats
|
|
315
|
-
throw new TypeError(`Invalid factor format: ${str}`);
|
|
316
|
-
}
|
|
317
|
-
const [, sign, whole, frac = ''] = baseMatch;
|
|
318
|
-
const baseValue = BigInt((sign || '') + whole + frac);
|
|
319
|
-
const baseDecimals = frac.length;
|
|
320
|
-
const exp = exponent ? Number(exponent) : 0;
|
|
321
|
-
const netExp = exp - baseDecimals;
|
|
322
|
-
if (netExp >= 0) {
|
|
323
|
-
return { value: baseValue * 10n ** BigInt(netExp), scale: 0n };
|
|
324
|
-
}
|
|
325
|
-
else {
|
|
326
|
-
return { value: baseValue, scale: BigInt(-netExp) };
|
|
327
|
-
}
|
|
328
|
-
}, _Money_roundedDivide = function _Money_roundedDivide(numerator, denominator) {
|
|
329
|
-
if (denominator === 1n)
|
|
330
|
-
return numerator;
|
|
331
|
-
const quotient = numerator / denominator;
|
|
332
|
-
const remainder = numerator % denominator;
|
|
333
|
-
if (remainder === 0n)
|
|
334
|
-
return quotient;
|
|
335
|
-
const halfDenominator = denominator / 2n;
|
|
336
|
-
const absRemainder = remainder < 0n ? -remainder : remainder;
|
|
337
|
-
if (absRemainder > halfDenominator) {
|
|
338
|
-
return numerator < 0n ? quotient - 1n : quotient + 1n;
|
|
339
|
-
}
|
|
340
|
-
if (absRemainder === halfDenominator) {
|
|
341
|
-
const isQuotientEven = quotient % 2n === 0n;
|
|
342
|
-
if (isQuotientEven) {
|
|
343
|
-
return quotient;
|
|
344
|
-
}
|
|
345
|
-
return numerator < 0n ? quotient - 1n : quotient + 1n;
|
|
346
|
-
}
|
|
347
|
-
return quotient;
|
|
348
|
-
}, _Money_createFromSubunits = function _Money_createFromSubunits(subunits, currency, currencyDef) {
|
|
349
|
-
return new _a(currency, __classPrivateFieldGet(_a, _a, "m", _Money_formatSubunits).call(_a, subunits, currencyDef));
|
|
350
|
-
}, _Money_formatSubunits = function _Money_formatSubunits(subunits, currencyDef) {
|
|
351
|
-
const decimals = currencyDef.decimalDigits;
|
|
352
|
-
const abs = subunits < 0n ? -subunits : subunits;
|
|
353
|
-
const isNegative = subunits < 0n;
|
|
354
|
-
if (decimals === 0) {
|
|
355
|
-
return `${isNegative ? '-' : ''}${abs}`;
|
|
356
|
-
}
|
|
357
|
-
const multiplier = 10n ** BigInt(decimals);
|
|
358
|
-
const wholePart = abs / multiplier;
|
|
359
|
-
const fracPart = abs % multiplier;
|
|
360
|
-
const sign = isNegative ? '-' : '';
|
|
361
|
-
return `${sign}${wholePart}.${fracPart.toString().padStart(decimals, '0')}`;
|
|
362
|
-
};
|
package/dist/currency.d.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
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;
|
package/dist/currency.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Currency registry and types.
|
|
3
|
-
* Manages ISO 4217 currency definitions and custom currencies.
|
|
4
|
-
*/
|
|
5
|
-
// Internal registry - mutable for registerCurrency()
|
|
6
|
-
const currencies = new Map();
|
|
7
|
-
/**
|
|
8
|
-
* Register a new currency or update an existing one.
|
|
9
|
-
* @param code - ISO 4217 currency code (e.g., 'USD', 'EUR', 'BTC')
|
|
10
|
-
* @param decimalDigits - Number of decimal places (e.g., 2 for USD, 8 for BTC)
|
|
11
|
-
*/
|
|
12
|
-
export function registerCurrency(code, decimalDigits) {
|
|
13
|
-
currencies.set(code, { code, decimalDigits });
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Get a currency definition by code.
|
|
17
|
-
* @returns The currency definition, or undefined if not registered
|
|
18
|
-
*/
|
|
19
|
-
export function getCurrency(code) {
|
|
20
|
-
return currencies.get(code);
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Check if a currency is registered.
|
|
24
|
-
*/
|
|
25
|
-
export function hasCurrency(code) {
|
|
26
|
-
return currencies.has(code);
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Get all registered currencies, sorted by code.
|
|
30
|
-
*/
|
|
31
|
-
export function getAllCurrencies() {
|
|
32
|
-
return Array.from(currencies.values()).sort((a, b) => a.code.localeCompare(b.code));
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Load currencies from the legacy currencymap.json format.
|
|
36
|
-
* @param map - Object with currency codes as keys and {decimal_digits: number} as values
|
|
37
|
-
*/
|
|
38
|
-
export function loadCurrencyMap(map) {
|
|
39
|
-
for (const [code, data] of Object.entries(map)) {
|
|
40
|
-
registerCurrency(code, data.decimal_digits);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Clear all registered currencies. Useful for testing.
|
|
45
|
-
*/
|
|
46
|
-
export function clearCurrencies() {
|
|
47
|
-
currencies.clear();
|
|
48
|
-
}
|
package/dist/errors.d.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
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
|
-
}
|
package/dist/errors.js
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
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 class CurrencyMismatchError extends TypeError {
|
|
11
|
-
constructor(fromCurrency, toCurrency) {
|
|
12
|
-
super(`Cannot operate on ${fromCurrency} and ${toCurrency} - currencies must match`);
|
|
13
|
-
this.name = 'CurrencyMismatchError';
|
|
14
|
-
this.fromCurrency = fromCurrency;
|
|
15
|
-
this.toCurrency = toCurrency;
|
|
16
|
-
Error.captureStackTrace?.(this, CurrencyMismatchError);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Thrown when using an unregistered currency code.
|
|
21
|
-
* @example
|
|
22
|
-
* new Money('FAKE', 10) // throws CurrencyUnknownError
|
|
23
|
-
*/
|
|
24
|
-
export class CurrencyUnknownError extends TypeError {
|
|
25
|
-
constructor(currency) {
|
|
26
|
-
super(`Unknown currency '${currency}' - register it first with Money.registerCurrency()`);
|
|
27
|
-
this.name = 'CurrencyUnknownError';
|
|
28
|
-
this.currency = currency;
|
|
29
|
-
Error.captureStackTrace?.(this, CurrencyUnknownError);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Thrown when an amount has more decimal places than the currency allows.
|
|
34
|
-
* @example
|
|
35
|
-
* new Money('USD', '1.234') // throws SubunitError (USD only allows 2 decimals)
|
|
36
|
-
*/
|
|
37
|
-
export class SubunitError extends RangeError {
|
|
38
|
-
constructor(currency, maxDecimals) {
|
|
39
|
-
super(`${currency} only supports ${maxDecimals} decimal place(s)`);
|
|
40
|
-
this.name = 'SubunitError';
|
|
41
|
-
this.currency = currency;
|
|
42
|
-
this.maxDecimals = maxDecimals;
|
|
43
|
-
Error.captureStackTrace?.(this, SubunitError);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Thrown when an amount cannot be parsed as a valid number.
|
|
48
|
-
* @example
|
|
49
|
-
* new Money('USD', 'abc') // throws AmountError
|
|
50
|
-
*/
|
|
51
|
-
export class AmountError extends TypeError {
|
|
52
|
-
constructor(amount) {
|
|
53
|
-
super(`Invalid amount: ${JSON.stringify(amount)}`);
|
|
54
|
-
this.name = 'AmountError';
|
|
55
|
-
this.amount = amount;
|
|
56
|
-
Error.captureStackTrace?.(this, AmountError);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Thrown when an exchange rate is not available.
|
|
61
|
-
* @example
|
|
62
|
-
* converter.convert(usdMoney, 'XYZ') // throws ExchangeRateError if no USD->XYZ rate
|
|
63
|
-
*/
|
|
64
|
-
export class ExchangeRateError extends Error {
|
|
65
|
-
constructor(fromCurrency, toCurrency) {
|
|
66
|
-
super(`No exchange rate available from ${fromCurrency} to ${toCurrency}`);
|
|
67
|
-
this.name = 'ExchangeRateError';
|
|
68
|
-
this.fromCurrency = fromCurrency;
|
|
69
|
-
this.toCurrency = toCurrency;
|
|
70
|
-
Error.captureStackTrace?.(this, ExchangeRateError);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
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
|
-
}
|