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.
@@ -1,174 +0,0 @@
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
- };
@@ -1,82 +0,0 @@
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
- }
@@ -1,172 +0,0 @@
1
- "use strict";
2
- /**
3
- * Money Converter - Safe cross-currency operations.
4
- *
5
- * Bridges Money objects with ExchangeRateService to enable:
6
- * - Currency conversion
7
- * - Multi-currency arithmetic
8
- * - Percentage calculations across currencies
9
- */
10
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
11
- if (kind === "m") throw new TypeError("Private method is not writable");
12
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
13
- 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");
14
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
15
- };
16
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
17
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
18
- 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");
19
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
20
- };
21
- var _MoneyConverter_instances, _MoneyConverter_rateService, _MoneyConverter_bankersRound;
22
- Object.defineProperty(exports, "__esModule", { value: true });
23
- exports.MoneyConverter = void 0;
24
- const money_js_1 = require("./money.js");
25
- const errors_js_1 = require("./errors.js");
26
- const currency_js_1 = require("./currency.js");
27
- /**
28
- * Converter for performing operations between different currencies.
29
- *
30
- * @example
31
- * const rates = new ExchangeRateService()
32
- * rates.setRate('USD', 'EUR', 0.92)
33
- *
34
- * const converter = new MoneyConverter(rates)
35
- * const euros = converter.convert(new Money('USD', '100'), 'EUR')
36
- * console.log(euros.toString()) // "92.00 EUR"
37
- */
38
- class MoneyConverter {
39
- constructor(rateService) {
40
- _MoneyConverter_instances.add(this);
41
- _MoneyConverter_rateService.set(this, void 0);
42
- __classPrivateFieldSet(this, _MoneyConverter_rateService, rateService, "f");
43
- }
44
- /**
45
- * Convert a Money amount to another currency.
46
- *
47
- * @param money - The amount to convert
48
- * @param targetCurrency - The target currency code
49
- * @returns A new Money in the target currency
50
- * @throws {ExchangeRateError} If no rate is available
51
- */
52
- convert(money, targetCurrency) {
53
- if (money.currency === targetCurrency) {
54
- return money;
55
- }
56
- const currencyDef = (0, currency_js_1.getCurrency)(targetCurrency);
57
- if (!currencyDef) {
58
- throw new errors_js_1.CurrencyUnknownError(targetCurrency);
59
- }
60
- const rate = __classPrivateFieldGet(this, _MoneyConverter_rateService, "f").getRate(money.currency, targetCurrency);
61
- if (!rate) {
62
- throw new errors_js_1.ExchangeRateError(money.currency, targetCurrency);
63
- }
64
- const sourceCurrencyDef = (0, currency_js_1.getCurrency)(money.currency);
65
- const sourceSubunits = money.toSubunits();
66
- const sourceMultiplier = 10n ** BigInt(sourceCurrencyDef.decimalDigits);
67
- const targetMultiplier = 10n ** BigInt(currencyDef.decimalDigits);
68
- const RATE_PRECISION = 15n;
69
- const rateMultiplier = 10n ** RATE_PRECISION;
70
- const rateValue = Number(rate.rate);
71
- const rateBigInt = BigInt(Math.round(rateValue * Number(rateMultiplier)));
72
- const product = sourceSubunits * rateBigInt * targetMultiplier;
73
- const divisor = rateMultiplier * sourceMultiplier;
74
- const targetSubunits = __classPrivateFieldGet(this, _MoneyConverter_instances, "m", _MoneyConverter_bankersRound).call(this, product, divisor);
75
- return money_js_1.Money.fromSubunits(targetSubunits, targetCurrency);
76
- }
77
- /**
78
- * Add two Money amounts, converting as needed.
79
- *
80
- * @param a - First amount
81
- * @param b - Second amount
82
- * @param resultCurrency - Currency for the result (must be one of the input currencies)
83
- * @returns Sum in the result currency
84
- */
85
- add(a, b, resultCurrency) {
86
- const aConverted = this.convert(a, resultCurrency);
87
- const bConverted = this.convert(b, resultCurrency);
88
- return aConverted.add(bConverted);
89
- }
90
- /**
91
- * Subtract two Money amounts, converting as needed.
92
- *
93
- * @param a - Amount to subtract from
94
- * @param b - Amount to subtract
95
- * @param resultCurrency - Currency for the result
96
- * @returns Difference in the result currency
97
- */
98
- subtract(a, b, resultCurrency) {
99
- const aConverted = this.convert(a, resultCurrency);
100
- const bConverted = this.convert(b, resultCurrency);
101
- return aConverted.subtract(bConverted);
102
- }
103
- /**
104
- * Calculate what percentage one amount is of another.
105
- * Converts both to the same currency before comparison.
106
- *
107
- * @param part - The partial amount
108
- * @param whole - The whole amount
109
- * @returns Percentage as a number (e.g., 25 for 25%)
110
- */
111
- percentageOf(part, whole) {
112
- // Convert both to the 'whole' currency for comparison
113
- const partConverted = this.convert(part, whole.currency);
114
- return (partConverted.toNumber() / whole.toNumber()) * 100;
115
- }
116
- /**
117
- * Sum multiple Money amounts, converting all to a target currency.
118
- *
119
- * @param amounts - Array of Money objects (can be different currencies)
120
- * @param targetCurrency - Currency for the result
121
- * @returns Total in the target currency
122
- */
123
- sum(amounts, targetCurrency) {
124
- let total = money_js_1.Money.zero(targetCurrency);
125
- for (const amount of amounts) {
126
- const converted = this.convert(amount, targetCurrency);
127
- total = total.add(converted);
128
- }
129
- return total;
130
- }
131
- /**
132
- * Compare two Money amounts across currencies.
133
- * Returns negative if a < b, zero if equal, positive if a > b.
134
- *
135
- * @param a - First amount
136
- * @param b - Second amount
137
- * @returns Comparison result
138
- */
139
- compare(a, b) {
140
- // Convert b to a's currency for comparison
141
- const bConverted = this.convert(b, a.currency);
142
- return money_js_1.Money.compare(a, bConverted);
143
- }
144
- /**
145
- * Get the exchange rate service (for direct rate access).
146
- */
147
- get rateService() {
148
- return __classPrivateFieldGet(this, _MoneyConverter_rateService, "f");
149
- }
150
- }
151
- exports.MoneyConverter = MoneyConverter;
152
- _MoneyConverter_rateService = new WeakMap(), _MoneyConverter_instances = new WeakSet(), _MoneyConverter_bankersRound = function _MoneyConverter_bankersRound(numerator, denominator) {
153
- if (denominator === 1n)
154
- return numerator;
155
- const quotient = numerator / denominator;
156
- const remainder = numerator % denominator;
157
- if (remainder === 0n)
158
- return quotient;
159
- const halfDenominator = denominator / 2n;
160
- const absRemainder = remainder < 0n ? -remainder : remainder;
161
- if (absRemainder > halfDenominator) {
162
- return numerator < 0n ? quotient - 1n : quotient + 1n;
163
- }
164
- if (absRemainder === halfDenominator) {
165
- const isQuotientEven = quotient % 2n === 0n;
166
- if (isQuotientEven) {
167
- return quotient;
168
- }
169
- return numerator < 0n ? quotient - 1n : quotient + 1n;
170
- }
171
- return quotient;
172
- };
package/dist/money.d.ts DELETED
@@ -1,146 +0,0 @@
1
- /**
2
- * Money - An immutable value object for monetary amounts.
3
- *
4
- * Design principles:
5
- * - Immutable: all operations return new instances
6
- * - Type-safe: currency mismatches are caught at compile time (when possible) and runtime
7
- * - Precise: uses BigInt internally to avoid floating-point errors
8
- * - String-based API: amounts are strings to preserve precision in JSON/DB round-trips
9
- */
10
- /**
11
- * Serialized form of a Money object, safe for JSON.
12
- */
13
- export interface MoneyObject<C extends string = string> {
14
- currency: C;
15
- amount: string;
16
- }
17
- /**
18
- * Money class - represents a monetary amount in a specific currency.
19
- *
20
- * @typeParam C - The currency code type (enables compile-time currency checking)
21
- *
22
- * @example
23
- * const price = new Money('USD', '19.99')
24
- * const tax = price.multiply(0.08)
25
- * const total = price.add(tax)
26
- * console.log(total.amount) // "21.59"
27
- */
28
- export declare class Money<C extends string = string> {
29
- #private;
30
- readonly currency: C;
31
- /**
32
- * Create a new Money instance.
33
- *
34
- * @param currency - ISO 4217 currency code (must be registered)
35
- * @param amount - The amount as a number or string
36
- * @throws {CurrencyUnknownError} If the currency is not registered
37
- * @throws {AmountError} If the amount is not a valid number
38
- * @throws {SubunitError} If the amount has more decimals than the currency allows
39
- */
40
- constructor(currency: C, amount: number | string);
41
- /**
42
- * The amount as a formatted string with correct decimal places.
43
- * @example
44
- * new Money('USD', 19.9).amount // "19.90"
45
- * new Money('JPY', 1000).amount // "1000"
46
- */
47
- get amount(): string;
48
- /**
49
- * Add another Money amount.
50
- * @throws {CurrencyMismatchError} If currencies don't match
51
- */
52
- add(other: Money<C>): Money<C>;
53
- /**
54
- * Subtract another Money amount.
55
- * @throws {CurrencyMismatchError} If currencies don't match
56
- */
57
- subtract(other: Money<C>): Money<C>;
58
- /**
59
- * Multiply by a factor.
60
- *
61
- * DESIGN: Rounds immediately after multiplication using banker's rounding
62
- * (round half-to-even). This prevents the "split penny problem".
63
- */
64
- multiply(factor: number): Money<C>;
65
- /**
66
- * Allocate this amount proportionally.
67
- * Handles remainder distribution to avoid losing pennies.
68
- *
69
- * @param proportions - Array of proportions (e.g., [1, 1, 1] for three-way split)
70
- * @returns Array of Money objects that sum to the original amount
71
- */
72
- allocate(proportions: number[]): Money<C>[];
73
- /**
74
- * Check if this amount equals another.
75
- * @throws {CurrencyMismatchError} If currencies don't match
76
- */
77
- equalTo(other: Money<C>): boolean;
78
- /**
79
- * Check if this amount is greater than another.
80
- * @throws {CurrencyMismatchError} If currencies don't match
81
- */
82
- greaterThan(other: Money<C>): boolean;
83
- /**
84
- * Check if this amount is less than another.
85
- * @throws {CurrencyMismatchError} If currencies don't match
86
- */
87
- lessThan(other: Money<C>): boolean;
88
- /**
89
- * Check if this amount is greater than or equal to another.
90
- * @throws {CurrencyMismatchError} If currencies don't match
91
- */
92
- greaterThanOrEqual(other: Money<C>): boolean;
93
- /**
94
- * Check if this amount is less than or equal to another.
95
- * @throws {CurrencyMismatchError} If currencies don't match
96
- */
97
- lessThanOrEqual(other: Money<C>): boolean;
98
- /**
99
- * Check if this amount is zero.
100
- */
101
- isZero(): boolean;
102
- /**
103
- * Check if this amount is positive (greater than zero).
104
- */
105
- isPositive(): boolean;
106
- /**
107
- * Check if this amount is negative (less than zero).
108
- */
109
- isNegative(): boolean;
110
- /**
111
- * Convert to a plain object (safe for JSON).
112
- */
113
- toJSON(): MoneyObject<C>;
114
- /**
115
- * Convert to string representation.
116
- */
117
- toString(): string;
118
- /**
119
- * Get the amount as a number (may lose precision for large values).
120
- * Use with caution - prefer string-based operations.
121
- */
122
- toNumber(): number;
123
- /**
124
- * Get the amount in subunits (e.g., cents for USD).
125
- * Useful for database storage (Stripe-style integer storage).
126
- */
127
- toSubunits(): bigint;
128
- /**
129
- * Create a Money instance from a plain object.
130
- */
131
- static fromObject<C extends string>(obj: MoneyObject<C>): Money<C>;
132
- /**
133
- * Create a Money instance from subunits (e.g., cents).
134
- * Useful for loading from database (Stripe-style integer storage).
135
- */
136
- static fromSubunits<C extends string>(subunits: bigint | number, currency: C): Money<C>;
137
- /**
138
- * Compare two Money objects (for use with Array.sort).
139
- * @throws {CurrencyMismatchError} If currencies don't match
140
- */
141
- static compare<C extends string>(a: Money<C>, b: Money<C>): -1 | 0 | 1;
142
- /**
143
- * Create a zero amount in the specified currency.
144
- */
145
- static zero<C extends string>(currency: C): Money<C>;
146
- }