subunit-money 2.1.1 → 3.0.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/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" where
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
  /**
package/dist/money.js CHANGED
@@ -19,14 +19,11 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
19
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
20
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
21
21
  };
22
- var _Money_instances, _a, _Money_value, _Money_currencyDef, _Money_parseAmount, _Money_assertSameCurrency, _Money_getInternalValue, _Money_roundedDivide, _Money_createFromInternal, _Money_formatInternalValue;
22
+ var _Money_instances, _a, _Money_subunits, _Money_currencyDef, _Money_parseAmount, _Money_assertSameCurrency, _Money_getInternalValue, _Money_parseFactor, _Money_roundedDivide, _Money_createFromSubunits, _Money_formatSubunits;
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
24
  exports.Money = void 0;
25
25
  const errors_js_1 = require("./errors.js");
26
26
  const currency_js_1 = require("./currency.js");
27
- // Internal precision: 8 decimal places (supports Bitcoin and most cryptocurrencies)
28
- const INTERNAL_PRECISION = 8;
29
- const PRECISION_MULTIPLIER = 10n ** BigInt(INTERNAL_PRECISION);
30
27
  /**
31
28
  * Money class - represents a monetary amount in a specific currency.
32
29
  *
@@ -50,8 +47,8 @@ class Money {
50
47
  */
51
48
  constructor(currency, amount) {
52
49
  _Money_instances.add(this);
53
- // Private BigInt storage - not exposed to prevent precision leaks
54
- _Money_value.set(this, void 0);
50
+ // Private BigInt storage - stores currency native subunits directly
51
+ _Money_subunits.set(this, void 0);
55
52
  _Money_currencyDef.set(this, void 0);
56
53
  const currencyDef = (0, currency_js_1.getCurrency)(currency);
57
54
  if (!currencyDef) {
@@ -59,7 +56,7 @@ class Money {
59
56
  }
60
57
  this.currency = currency;
61
58
  __classPrivateFieldSet(this, _Money_currencyDef, currencyDef, "f");
62
- __classPrivateFieldSet(this, _Money_value, __classPrivateFieldGet(this, _Money_instances, "m", _Money_parseAmount).call(this, amount), "f");
59
+ __classPrivateFieldSet(this, _Money_subunits, __classPrivateFieldGet(this, _Money_instances, "m", _Money_parseAmount).call(this, amount), "f");
63
60
  }
64
61
  /**
65
62
  * The amount as a formatted string with correct decimal places.
@@ -69,10 +66,8 @@ class Money {
69
66
  */
70
67
  get amount() {
71
68
  const decimals = __classPrivateFieldGet(this, _Money_currencyDef, "f").decimalDigits;
72
- const divisor = 10n ** BigInt(INTERNAL_PRECISION - decimals);
73
- const adjusted = __classPrivateFieldGet(this, _Money_value, "f") / divisor;
74
- const isNegative = adjusted < 0n;
75
- const abs = isNegative ? -adjusted : adjusted;
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;
76
71
  if (decimals === 0) {
77
72
  return `${isNegative ? '-' : ''}${abs}`;
78
73
  }
@@ -89,8 +84,8 @@ class Money {
89
84
  */
90
85
  add(other) {
91
86
  __classPrivateFieldGet(this, _Money_instances, "m", _Money_assertSameCurrency).call(this, other);
92
- const result = __classPrivateFieldGet(this, _Money_value, "f") + __classPrivateFieldGet(other, _Money_instances, "m", _Money_getInternalValue).call(other);
93
- return __classPrivateFieldGet(_a, _a, "m", _Money_createFromInternal).call(_a, result, this.currency, __classPrivateFieldGet(this, _Money_currencyDef, "f"));
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"));
94
89
  }
95
90
  /**
96
91
  * Subtract another Money amount.
@@ -98,30 +93,24 @@ class Money {
98
93
  */
99
94
  subtract(other) {
100
95
  __classPrivateFieldGet(this, _Money_instances, "m", _Money_assertSameCurrency).call(this, other);
101
- const result = __classPrivateFieldGet(this, _Money_value, "f") - __classPrivateFieldGet(other, _Money_instances, "m", _Money_getInternalValue).call(other);
102
- return __classPrivateFieldGet(_a, _a, "m", _Money_createFromInternal).call(_a, result, this.currency, __classPrivateFieldGet(this, _Money_currencyDef, "f"));
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"));
103
98
  }
104
99
  /**
105
100
  * Multiply by a factor.
106
101
  *
107
102
  * DESIGN: Rounds immediately after multiplication using banker's rounding
108
- * (round half-to-even). This prevents the "split penny problem" where
109
- * line-item rounding differs from deferred rounding:
110
- * Per-item: $1.65 tax × 10 items = $16.50 ✓ (matches receipt)
111
- * Deferred: 10 × $1.649175 = $16.49 ✗ (missing penny)
112
- *
113
- * For chained calculations without intermediate rounding, perform arithmetic
114
- * in Number space first, then create a Money object with the final result.
103
+ * (round half-to-even). This prevents the "split penny problem".
115
104
  */
116
105
  multiply(factor) {
117
106
  if (typeof factor !== 'number' || !Number.isFinite(factor)) {
118
107
  throw new TypeError(`Factor must be a finite number, got: ${factor}`);
119
108
  }
120
- const factorStr = factor.toFixed(INTERNAL_PRECISION);
121
- const factorBigInt = BigInt(factorStr.replace('.', ''));
122
- const product = __classPrivateFieldGet(this, _Money_value, "f") * factorBigInt;
123
- const result = __classPrivateFieldGet(_a, _a, "m", _Money_roundedDivide).call(_a, product, PRECISION_MULTIPLIER);
124
- return __classPrivateFieldGet(_a, _a, "m", _Money_createFromInternal).call(_a, result, this.currency, __classPrivateFieldGet(this, _Money_currencyDef, "f"));
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"));
125
114
  }
126
115
  /**
127
116
  * Allocate this amount proportionally.
@@ -129,10 +118,6 @@ class Money {
129
118
  *
130
119
  * @param proportions - Array of proportions (e.g., [1, 1, 1] for three-way split)
131
120
  * @returns Array of Money objects that sum to the original amount
132
- *
133
- * @example
134
- * new Money('USD', '100').allocate([1, 1, 1])
135
- * // Returns: [Money('33.34'), Money('33.33'), Money('33.33')]
136
121
  */
137
122
  allocate(proportions) {
138
123
  if (!Array.isArray(proportions) || proportions.length === 0) {
@@ -147,10 +132,7 @@ class Money {
147
132
  if (total <= 0) {
148
133
  throw new TypeError('Sum of proportions must be positive');
149
134
  }
150
- // Work in currency subunits to avoid precision loss
151
- const decimals = __classPrivateFieldGet(this, _Money_currencyDef, "f").decimalDigits;
152
- const subunitMultiplier = 10n ** BigInt(INTERNAL_PRECISION - decimals);
153
- const totalSubunits = __classPrivateFieldGet(this, _Money_value, "f") / subunitMultiplier;
135
+ const totalSubunits = __classPrivateFieldGet(this, _Money_subunits, "f");
154
136
  // Calculate base allocations
155
137
  const allocations = proportions.map((p) => {
156
138
  return (totalSubunits * BigInt(Math.round(p * 1000000))) / BigInt(Math.round(total * 1000000));
@@ -170,8 +152,7 @@ class Money {
170
152
  }
171
153
  // Convert back to Money objects
172
154
  return allocations.map((subunits) => {
173
- const internalValue = subunits * subunitMultiplier;
174
- return __classPrivateFieldGet(_a, _a, "m", _Money_createFromInternal).call(_a, internalValue, this.currency, __classPrivateFieldGet(this, _Money_currencyDef, "f"));
155
+ return __classPrivateFieldGet(_a, _a, "m", _Money_createFromSubunits).call(_a, subunits, this.currency, __classPrivateFieldGet(this, _Money_currencyDef, "f"));
175
156
  });
176
157
  }
177
158
  // ============ Comparison Operations ============
@@ -181,7 +162,7 @@ class Money {
181
162
  */
182
163
  equalTo(other) {
183
164
  __classPrivateFieldGet(this, _Money_instances, "m", _Money_assertSameCurrency).call(this, other);
184
- return __classPrivateFieldGet(this, _Money_value, "f") === __classPrivateFieldGet(other, _Money_instances, "m", _Money_getInternalValue).call(other);
165
+ return __classPrivateFieldGet(this, _Money_subunits, "f") === __classPrivateFieldGet(other, _Money_instances, "m", _Money_getInternalValue).call(other);
185
166
  }
186
167
  /**
187
168
  * Check if this amount is greater than another.
@@ -189,7 +170,7 @@ class Money {
189
170
  */
190
171
  greaterThan(other) {
191
172
  __classPrivateFieldGet(this, _Money_instances, "m", _Money_assertSameCurrency).call(this, other);
192
- return __classPrivateFieldGet(this, _Money_value, "f") > __classPrivateFieldGet(other, _Money_instances, "m", _Money_getInternalValue).call(other);
173
+ return __classPrivateFieldGet(this, _Money_subunits, "f") > __classPrivateFieldGet(other, _Money_instances, "m", _Money_getInternalValue).call(other);
193
174
  }
194
175
  /**
195
176
  * Check if this amount is less than another.
@@ -197,7 +178,7 @@ class Money {
197
178
  */
198
179
  lessThan(other) {
199
180
  __classPrivateFieldGet(this, _Money_instances, "m", _Money_assertSameCurrency).call(this, other);
200
- return __classPrivateFieldGet(this, _Money_value, "f") < __classPrivateFieldGet(other, _Money_instances, "m", _Money_getInternalValue).call(other);
181
+ return __classPrivateFieldGet(this, _Money_subunits, "f") < __classPrivateFieldGet(other, _Money_instances, "m", _Money_getInternalValue).call(other);
201
182
  }
202
183
  /**
203
184
  * Check if this amount is greater than or equal to another.
@@ -205,7 +186,7 @@ class Money {
205
186
  */
206
187
  greaterThanOrEqual(other) {
207
188
  __classPrivateFieldGet(this, _Money_instances, "m", _Money_assertSameCurrency).call(this, other);
208
- return __classPrivateFieldGet(this, _Money_value, "f") >= __classPrivateFieldGet(other, _Money_instances, "m", _Money_getInternalValue).call(other);
189
+ return __classPrivateFieldGet(this, _Money_subunits, "f") >= __classPrivateFieldGet(other, _Money_instances, "m", _Money_getInternalValue).call(other);
209
190
  }
210
191
  /**
211
192
  * Check if this amount is less than or equal to another.
@@ -213,25 +194,25 @@ class Money {
213
194
  */
214
195
  lessThanOrEqual(other) {
215
196
  __classPrivateFieldGet(this, _Money_instances, "m", _Money_assertSameCurrency).call(this, other);
216
- return __classPrivateFieldGet(this, _Money_value, "f") <= __classPrivateFieldGet(other, _Money_instances, "m", _Money_getInternalValue).call(other);
197
+ return __classPrivateFieldGet(this, _Money_subunits, "f") <= __classPrivateFieldGet(other, _Money_instances, "m", _Money_getInternalValue).call(other);
217
198
  }
218
199
  /**
219
200
  * Check if this amount is zero.
220
201
  */
221
202
  isZero() {
222
- return __classPrivateFieldGet(this, _Money_value, "f") === 0n;
203
+ return __classPrivateFieldGet(this, _Money_subunits, "f") === 0n;
223
204
  }
224
205
  /**
225
206
  * Check if this amount is positive (greater than zero).
226
207
  */
227
208
  isPositive() {
228
- return __classPrivateFieldGet(this, _Money_value, "f") > 0n;
209
+ return __classPrivateFieldGet(this, _Money_subunits, "f") > 0n;
229
210
  }
230
211
  /**
231
212
  * Check if this amount is negative (less than zero).
232
213
  */
233
214
  isNegative() {
234
- return __classPrivateFieldGet(this, _Money_value, "f") < 0n;
215
+ return __classPrivateFieldGet(this, _Money_subunits, "f") < 0n;
235
216
  }
236
217
  // ============ Serialization ============
237
218
  /**
@@ -261,9 +242,7 @@ class Money {
261
242
  * Useful for database storage (Stripe-style integer storage).
262
243
  */
263
244
  toSubunits() {
264
- const decimals = __classPrivateFieldGet(this, _Money_currencyDef, "f").decimalDigits;
265
- const divisor = 10n ** BigInt(INTERNAL_PRECISION - decimals);
266
- return __classPrivateFieldGet(this, _Money_value, "f") / divisor;
245
+ return __classPrivateFieldGet(this, _Money_subunits, "f");
267
246
  }
268
247
  // ============ Static Factory Methods ============
269
248
  /**
@@ -282,9 +261,7 @@ class Money {
282
261
  throw new errors_js_1.CurrencyUnknownError(currency);
283
262
  }
284
263
  const bigintSubunits = typeof subunits === 'number' ? BigInt(subunits) : subunits;
285
- const multiplier = 10n ** BigInt(INTERNAL_PRECISION - currencyDef.decimalDigits);
286
- const internalValue = bigintSubunits * multiplier;
287
- return __classPrivateFieldGet(_a, _a, "m", _Money_createFromInternal).call(_a, internalValue, currency, currencyDef);
264
+ return __classPrivateFieldGet(_a, _a, "m", _Money_createFromSubunits).call(_a, bigintSubunits, currency, currencyDef);
288
265
  }
289
266
  /**
290
267
  * Compare two Money objects (for use with Array.sort).
@@ -310,21 +287,17 @@ class Money {
310
287
  }
311
288
  }
312
289
  exports.Money = Money;
313
- _a = Money, _Money_value = new WeakMap(), _Money_currencyDef = new WeakMap(), _Money_instances = new WeakSet(), _Money_parseAmount = function _Money_parseAmount(amount) {
314
- // Convert to string for consistent parsing
290
+ _a = Money, _Money_subunits = new WeakMap(), _Money_currencyDef = new WeakMap(), _Money_instances = new WeakSet(), _Money_parseAmount = function _Money_parseAmount(amount) {
315
291
  const str = typeof amount === 'number' ? String(amount) : amount;
316
- // Validate format: optional minus, digits, optional decimal part
317
292
  const match = str.match(/^(-)?(\d+)(?:\.(\d+))?$/);
318
293
  if (!match) {
319
294
  throw new errors_js_1.AmountError(amount);
320
295
  }
321
296
  const [, sign, whole, frac = ''] = match;
322
- // Check decimal places don't exceed currency limit
323
297
  if (frac.length > __classPrivateFieldGet(this, _Money_currencyDef, "f").decimalDigits) {
324
298
  throw new errors_js_1.SubunitError(this.currency, __classPrivateFieldGet(this, _Money_currencyDef, "f").decimalDigits);
325
299
  }
326
- // Pad fraction to internal precision and combine
327
- const paddedFrac = frac.padEnd(INTERNAL_PRECISION, '0');
300
+ const paddedFrac = frac.padEnd(__classPrivateFieldGet(this, _Money_currencyDef, "f").decimalDigits, '0');
328
301
  const combined = BigInt(whole + paddedFrac);
329
302
  return sign === '-' ? -combined : combined;
330
303
  }, _Money_assertSameCurrency = function _Money_assertSameCurrency(other) {
@@ -332,7 +305,26 @@ _a = Money, _Money_value = new WeakMap(), _Money_currencyDef = new WeakMap(), _M
332
305
  throw new errors_js_1.CurrencyMismatchError(this.currency, other.currency);
333
306
  }
334
307
  }, _Money_getInternalValue = function _Money_getInternalValue() {
335
- return __classPrivateFieldGet(this, _Money_value, "f");
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
+ }
336
328
  }, _Money_roundedDivide = function _Money_roundedDivide(numerator, denominator) {
337
329
  if (denominator === 1n)
338
330
  return numerator;
@@ -353,22 +345,12 @@ _a = Money, _Money_value = new WeakMap(), _Money_currencyDef = new WeakMap(), _M
353
345
  return numerator < 0n ? quotient - 1n : quotient + 1n;
354
346
  }
355
347
  return quotient;
356
- }, _Money_createFromInternal = function _Money_createFromInternal(value, currency, currencyDef) {
357
- const instance = Object.create(_a.prototype);
358
- // Use Object.defineProperties for proper initialization
359
- Object.defineProperties(instance, {
360
- currency: { value: currency, enumerable: true, writable: false },
361
- });
362
- // Access private fields via the class mechanism
363
- // This is a workaround since we can't directly set #private fields on Object.create instances
364
- // Instead, we'll use a different approach: call a private static method
365
- return new _a(currency, __classPrivateFieldGet(_a, _a, "m", _Money_formatInternalValue).call(_a, value, currencyDef));
366
- }, _Money_formatInternalValue = function _Money_formatInternalValue(value, currencyDef) {
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) {
367
351
  const decimals = currencyDef.decimalDigits;
368
- const divisor = 10n ** BigInt(INTERNAL_PRECISION - decimals);
369
- const adjusted = __classPrivateFieldGet(_a, _a, "m", _Money_roundedDivide).call(_a, value, divisor);
370
- const isNegative = adjusted < 0n;
371
- const abs = isNegative ? -adjusted : adjusted;
352
+ const abs = subunits < 0n ? -subunits : subunits;
353
+ const isNegative = subunits < 0n;
372
354
  if (decimals === 0) {
373
355
  return `${isNegative ? '-' : ''}${abs}`;
374
356
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "subunit-money",
3
- "version": "2.1.1",
3
+ "version": "3.0.0",
4
4
  "description": "A type-safe value object for monetary amounts with currency conversion support",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -23,6 +23,7 @@
23
23
  "lint": "tsc --noEmit",
24
24
  "check-clean": "git diff --quiet && git diff --cached --quiet",
25
25
  "preversion": "npm run check-clean && npm test",
26
+ "version": "npm run build && git add dist",
26
27
  "postversion": "git push && git push --tags",
27
28
  "prepublishOnly": "npm run check-clean && npm run build",
28
29
  "release:patch": "npm version patch && npm publish",