soff-money 0.2.0 → 0.2.2

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.
Files changed (46) hide show
  1. package/README.md +42 -11
  2. package/dist/core/index.cjs +1 -342
  3. package/dist/core/index.cjs.map +1 -1
  4. package/dist/core/index.js +1 -340
  5. package/dist/core/index.js.map +1 -1
  6. package/dist/index.cjs +1 -460
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.js +1 -447
  9. package/dist/index.js.map +1 -1
  10. package/dist/locales/ar.cjs +1 -15
  11. package/dist/locales/ar.cjs.map +1 -1
  12. package/dist/locales/ar.js +1 -13
  13. package/dist/locales/ar.js.map +1 -1
  14. package/dist/locales/br.cjs +1 -15
  15. package/dist/locales/br.cjs.map +1 -1
  16. package/dist/locales/br.js +1 -13
  17. package/dist/locales/br.js.map +1 -1
  18. package/dist/locales/cl.cjs +1 -20
  19. package/dist/locales/cl.cjs.map +1 -1
  20. package/dist/locales/cl.js +1 -17
  21. package/dist/locales/cl.js.map +1 -1
  22. package/dist/locales/co.cjs +1 -20
  23. package/dist/locales/co.cjs.map +1 -1
  24. package/dist/locales/co.js +1 -17
  25. package/dist/locales/co.js.map +1 -1
  26. package/dist/locales/eu.cjs +1 -15
  27. package/dist/locales/eu.cjs.map +1 -1
  28. package/dist/locales/eu.js +1 -13
  29. package/dist/locales/eu.js.map +1 -1
  30. package/dist/locales/mx.cjs +1 -15
  31. package/dist/locales/mx.cjs.map +1 -1
  32. package/dist/locales/mx.js +1 -13
  33. package/dist/locales/mx.js.map +1 -1
  34. package/dist/locales/pe.cjs +1 -15
  35. package/dist/locales/pe.cjs.map +1 -1
  36. package/dist/locales/pe.js +1 -13
  37. package/dist/locales/pe.js.map +1 -1
  38. package/dist/locales/us.cjs +1 -15
  39. package/dist/locales/us.cjs.map +1 -1
  40. package/dist/locales/us.js +1 -13
  41. package/dist/locales/us.js.map +1 -1
  42. package/dist/locales/uy.cjs +1 -15
  43. package/dist/locales/uy.cjs.map +1 -1
  44. package/dist/locales/uy.js +1 -13
  45. package/dist/locales/uy.js.map +1 -1
  46. package/package.json +1 -1
package/README.md CHANGED
@@ -1,4 +1,10 @@
1
- # Soff Money
1
+ <div align="center">
2
+ <img src="https://raw.githubusercontent.com/bledxs/soff-monorepo/master/assets/logo.png" alt="Soff Logo" width="100" height="100">
3
+ <h1>Soff Money</h1>
4
+ <p>Safe money handling for JavaScript with integer-based arithmetic and LATAM locale formatting.</p>
5
+ </div>
6
+
7
+ <div align="center">
2
8
 
3
9
  [![npm](https://img.shields.io/npm/v/soff-money)](https://www.npmjs.com/package/soff-money)
4
10
  [![License](https://img.shields.io/github/license/bledxs/soff-monorepo)](LICENSE)
@@ -6,7 +12,9 @@
6
12
  [![codecov](https://codecov.io/gh/bledxs/soff-monorepo/branch/master/graph/badge.svg)](https://codecov.io/gh/bledxs/soff-monorepo)
7
13
  [![minzipped size](https://img.shields.io/bundlephobia/minzip/soff-money)](https://bundlephobia.com/package/soff-money)
8
14
 
9
- Safe money handling for JavaScript with integer-based arithmetic and LATAM locale formatting.
15
+ </div>
16
+
17
+ ---
10
18
 
11
19
  **Zero dependencies** · **TypeScript** · **~8KB core**
12
20
 
@@ -33,35 +41,58 @@ Safe money handling for JavaScript with integer-based arithmetic and LATAM local
33
41
  - [License](#license)
34
42
  - [Documentation](#documentation)
35
43
 
36
- ## Why?
44
+ ## 🤔 Why?
45
+
46
+ In JavaScript, `0.1 + 0.2 === 0.30000000000000004`. This is **fatal** for e-commerce or financial applications. 🚨
47
+
48
+ Additionally, formatting currencies in Latin America is painful:
37
49
 
38
- In JavaScript, `0.1 + 0.2 === 0.30000000000000004`. This is fatal for e-commerce or financial applications. Additionally, formatting currencies in Latin America is painful - does the symbol go before or after? Dots or commas for thousands?
50
+ - Does the symbol go before or after? 🤔
51
+ - Dots or commas for thousands?
52
+ - How many decimals?
39
53
 
40
- This library handles money using integers (Safe Money pattern) and formats according to the country's locale.
54
+ This library solves both problems:
41
55
 
42
- ## Install
56
+ | Problem | Solution |
57
+ | ---------------------------- | -------------------------------------------------- |
58
+ | 🐞 **Floating point errors** | Uses **Safe Money Pattern** (integer cents) |
59
+ | 🌎 **LATAM formatting** | Locale-aware formatting (COP, MXN, ARS, BRL, etc.) |
60
+ | 🧩 **Lost cents** | Fair distribution algorithm (no money lost!) |
61
+ | ⚔️ **Math operations** | Immutable Money objects with safe arithmetic |
62
+
63
+ ## 📦 Install
43
64
 
44
65
  ```bash
66
+ # npm
45
67
  npm install soff-money
68
+
69
+ # pnpm
70
+ pnpm add soff-money
71
+
72
+ # yarn
73
+ yarn add soff-money
74
+
75
+ # bun
76
+ bun add soff-money
46
77
  ```
47
78
 
48
- ## Quick Start
79
+ ## 🚀 Quick Start
49
80
 
50
81
  ```typescript
51
82
  import { Money, COP, USD } from 'soff-money';
52
83
 
53
- // Create money from decimal (safe - converted to cents internally)
84
+ // 💵 Create money from decimal (safe - converted to cents internally)
54
85
  const price = Money.fromDecimal(1500000, COP);
55
86
 
56
- // Arithmetic operations (all return new Money instances)
87
+ // 🧮 Arithmetic operations (all return new Money instances)
57
88
  const withTax = price.addPercentage(19); // Add 19% tax
58
89
  const discounted = withTax.subtractPercentage(10); // 10% discount
59
90
 
60
- // Format for display
91
+ // 🎨 Format for display
61
92
  console.log(price.format()); // "$ 1.500.000,00"
62
93
  console.log(discounted.format()); // "$ 1.606.500,00"
63
94
 
64
- // Safe comparisons
95
+ // ⚖️ Safe comparisons
65
96
  price.equals(Money.fromDecimal(1500000, COP)); // true
66
97
  price.greaterThan(discounted); // false
67
98
  ```
@@ -1,343 +1,2 @@
1
- 'use strict';
2
-
3
- // src/core/money.ts
4
- var Money = class _Money {
5
- constructor(cents, currency) {
6
- if (!Number.isInteger(cents)) {
7
- throw new Error("Money must be created with an integer number of cents");
8
- }
9
- this.cents = cents;
10
- this.currency = currency;
11
- Object.freeze(this);
12
- }
13
- /**
14
- * Create money from cents (smallest unit)
15
- */
16
- static fromCents(cents, currency) {
17
- return new _Money(Math.round(cents), currency);
18
- }
19
- /**
20
- * Create money from a decimal amount
21
- * @example Money.fromDecimal(100.50, USD) creates $100.50
22
- */
23
- static fromDecimal(amount, currency) {
24
- const multiplier = Math.pow(10, currency.decimals);
25
- const cents = Math.round(amount * multiplier);
26
- return new _Money(cents, currency);
27
- }
28
- /**
29
- * Create zero money
30
- */
31
- static zero(currency) {
32
- return new _Money(0, currency);
33
- }
34
- /**
35
- * Add another money value (must be same currency)
36
- */
37
- add(other) {
38
- this.assertSameCurrency(other);
39
- return new _Money(this.cents + other.cents, this.currency);
40
- }
41
- /**
42
- * Subtract another money value (must be same currency)
43
- */
44
- subtract(other) {
45
- this.assertSameCurrency(other);
46
- return new _Money(this.cents - other.cents, this.currency);
47
- }
48
- /**
49
- * Multiply by a factor (rounds to nearest cent)
50
- */
51
- multiply(factor) {
52
- return new _Money(Math.round(this.cents * factor), this.currency);
53
- }
54
- /**
55
- * Divide by a divisor (rounds to nearest cent)
56
- */
57
- divide(divisor) {
58
- if (divisor === 0) {
59
- throw new Error("Cannot divide by zero");
60
- }
61
- return new _Money(Math.round(this.cents / divisor), this.currency);
62
- }
63
- /**
64
- * Distribute money evenly without losing cents
65
- * The remainder is distributed to the first parts
66
- * @example $100 / 3 = [$33.34, $33.33, $33.33]
67
- */
68
- distribute(parts) {
69
- if (parts <= 0 || !Number.isInteger(parts)) {
70
- throw new Error("Parts must be a positive integer");
71
- }
72
- const quotient = Math.floor(this.cents / parts);
73
- const remainder = this.cents % parts;
74
- const result = [];
75
- for (let i = 0; i < parts; i++) {
76
- const extra = i < remainder ? 1 : 0;
77
- result.push(new _Money(quotient + extra, this.currency));
78
- }
79
- return result;
80
- }
81
- /**
82
- * Distribute money according to ratios
83
- * @example $100 with ratios [1, 2, 2] = [$20, $40, $40]
84
- */
85
- distributeByRatios(ratios) {
86
- if (ratios.length === 0) {
87
- throw new Error("Ratios array cannot be empty");
88
- }
89
- const total = ratios.reduce((sum, r) => sum + r, 0);
90
- if (total <= 0) {
91
- throw new Error("Sum of ratios must be positive");
92
- }
93
- let remaining = this.cents;
94
- const result = [];
95
- for (let i = 0; i < ratios.length; i++) {
96
- if (i === ratios.length - 1) {
97
- result.push(new _Money(remaining, this.currency));
98
- } else {
99
- const share = Math.round(this.cents * ratios[i] / total);
100
- result.push(new _Money(share, this.currency));
101
- remaining -= share;
102
- }
103
- }
104
- return result;
105
- }
106
- /**
107
- * Format money for display
108
- */
109
- format(options = {}) {
110
- const { showSymbol = true, showDecimals = true, symbolPosition } = options;
111
- const decimal = this.toDecimal();
112
- const absValue = Math.abs(decimal);
113
- const isNegative = decimal < 0;
114
- let formatted;
115
- if (showDecimals && this.currency.decimals > 0) {
116
- const [intPart, decPart] = absValue.toFixed(this.currency.decimals).split(".");
117
- const intFormatted = this.formatInteger(intPart);
118
- formatted = `${intFormatted}${this.currency.decimalSeparator}${decPart}`;
119
- } else {
120
- formatted = this.formatInteger(Math.round(absValue).toString());
121
- }
122
- if (isNegative) {
123
- formatted = `-${formatted}`;
124
- }
125
- if (showSymbol) {
126
- const pos = symbolPosition ?? this.currency.symbolPosition;
127
- const space = this.currency.symbolSpacing ? " " : "";
128
- if (pos === "before") {
129
- formatted = `${this.currency.symbol}${space}${formatted}`;
130
- } else {
131
- formatted = `${formatted}${space}${this.currency.symbol}`;
132
- }
133
- }
134
- return formatted;
135
- }
136
- /**
137
- * Get the decimal representation
138
- */
139
- toDecimal() {
140
- return this.cents / Math.pow(10, this.currency.decimals);
141
- }
142
- /**
143
- * Check equality with another money value
144
- */
145
- equals(other) {
146
- return this.cents === other.cents && this.currency.code === other.currency.code;
147
- }
148
- /**
149
- * Check if greater than another money value
150
- */
151
- greaterThan(other) {
152
- this.assertSameCurrency(other);
153
- return this.cents > other.cents;
154
- }
155
- /**
156
- * Check if less than another money value
157
- */
158
- lessThan(other) {
159
- this.assertSameCurrency(other);
160
- return this.cents < other.cents;
161
- }
162
- /**
163
- * Check if greater than or equal
164
- */
165
- greaterThanOrEqual(other) {
166
- this.assertSameCurrency(other);
167
- return this.cents >= other.cents;
168
- }
169
- /**
170
- * Check if less than or equal
171
- */
172
- lessThanOrEqual(other) {
173
- this.assertSameCurrency(other);
174
- return this.cents <= other.cents;
175
- }
176
- /**
177
- * Check if zero
178
- */
179
- isZero() {
180
- return this.cents === 0;
181
- }
182
- /**
183
- * Check if positive
184
- */
185
- isPositive() {
186
- return this.cents > 0;
187
- }
188
- /**
189
- * Check if negative
190
- */
191
- isNegative() {
192
- return this.cents < 0;
193
- }
194
- /**
195
- * Get absolute value
196
- */
197
- abs() {
198
- return new _Money(Math.abs(this.cents), this.currency);
199
- }
200
- /**
201
- * Negate the value
202
- */
203
- negate() {
204
- return new _Money(-this.cents, this.currency);
205
- }
206
- /**
207
- * Calculate a percentage of this money
208
- * @example money.percentage(10) returns 10% of the amount
209
- */
210
- percentage(percent) {
211
- return new _Money(Math.round(this.cents * percent / 100), this.currency);
212
- }
213
- /**
214
- * Add a percentage to this money
215
- * @example money.addPercentage(19) adds 19% (like tax)
216
- */
217
- addPercentage(percent) {
218
- return this.add(this.percentage(percent));
219
- }
220
- /**
221
- * Subtract a percentage from this money
222
- * @example money.subtractPercentage(10) subtracts 10% (like discount)
223
- */
224
- subtractPercentage(percent) {
225
- return this.subtract(this.percentage(percent));
226
- }
227
- /**
228
- * Get the minimum of this and another money value
229
- */
230
- min(other) {
231
- this.assertSameCurrency(other);
232
- return this.cents <= other.cents ? this : new _Money(other.cents, this.currency);
233
- }
234
- /**
235
- * Get the maximum of this and another money value
236
- */
237
- max(other) {
238
- this.assertSameCurrency(other);
239
- return this.cents >= other.cents ? this : new _Money(other.cents, this.currency);
240
- }
241
- /**
242
- * Clamp this money between a minimum and maximum
243
- */
244
- clamp(minValue, maxValue) {
245
- return this.max(minValue).min(maxValue);
246
- }
247
- /**
248
- * Get cents (smallest unit)
249
- */
250
- toCents() {
251
- return this.cents;
252
- }
253
- /**
254
- * Check if within a range (inclusive)
255
- */
256
- isBetween(min, max) {
257
- this.assertSameCurrency(min);
258
- this.assertSameCurrency(max);
259
- return this.cents >= min.cents && this.cents <= max.cents;
260
- }
261
- /**
262
- * Sum multiple money values
263
- */
264
- static sum(values) {
265
- if (values.length === 0) {
266
- throw new Error("Cannot sum empty array");
267
- }
268
- const currency = values[0].currency;
269
- const total = values.reduce((sum, m) => {
270
- if (m.currency.code !== currency.code) {
271
- throw new Error("Cannot sum different currencies");
272
- }
273
- return sum + m.cents;
274
- }, 0);
275
- return new _Money(total, currency);
276
- }
277
- /**
278
- * Get the minimum from an array of money values
279
- */
280
- static minimum(values) {
281
- if (values.length === 0) {
282
- throw new Error("Cannot get minimum of empty array");
283
- }
284
- return values.reduce((min, m) => m.cents < min.cents ? m : min);
285
- }
286
- /**
287
- * Get the maximum from an array of money values
288
- */
289
- static maximum(values) {
290
- if (values.length === 0) {
291
- throw new Error("Cannot get maximum of empty array");
292
- }
293
- return values.reduce((max, m) => m.cents > max.cents ? m : max);
294
- }
295
- /**
296
- * Calculate the average of money values
297
- */
298
- static average(values) {
299
- if (values.length === 0) {
300
- throw new Error("Cannot calculate average of empty array");
301
- }
302
- const total = _Money.sum(values);
303
- return total.divide(values.length);
304
- }
305
- /**
306
- * Convert to JSON-serializable object
307
- */
308
- toJSON() {
309
- return {
310
- cents: this.cents,
311
- currency: this.currency.code
312
- };
313
- }
314
- /**
315
- * String representation
316
- */
317
- toString() {
318
- return this.format();
319
- }
320
- formatInteger(intStr) {
321
- const parts = [];
322
- let remaining = intStr;
323
- while (remaining.length > 3) {
324
- parts.unshift(remaining.slice(-3));
325
- remaining = remaining.slice(0, -3);
326
- }
327
- if (remaining) {
328
- parts.unshift(remaining);
329
- }
330
- return parts.join(this.currency.thousandsSeparator);
331
- }
332
- assertSameCurrency(other) {
333
- if (this.currency.code !== other.currency.code) {
334
- throw new Error(
335
- `Cannot perform operation with different currencies: ${this.currency.code} and ${other.currency.code}`
336
- );
337
- }
338
- }
339
- };
340
-
341
- exports.Money = Money;
342
- //# sourceMappingURL=index.cjs.map
1
+ 'use strict';var y=class r{constructor(e,t){if(!Number.isInteger(e))throw new Error("Money must be created with an integer number of cents");this.cents=e,this.currency=t,Object.freeze(this);}static fromCents(e,t){return new r(Math.round(e),t)}static fromDecimal(e,t){let n=Math.pow(10,t.decimals),c=Math.round(e*n);return new r(c,t)}static zero(e){return new r(0,e)}add(e){return this.assertSameCurrency(e),new r(this.cents+e.cents,this.currency)}subtract(e){return this.assertSameCurrency(e),new r(this.cents-e.cents,this.currency)}multiply(e){return new r(Math.round(this.cents*e),this.currency)}divide(e){if(e===0)throw new Error("Cannot divide by zero");return new r(Math.round(this.cents/e),this.currency)}distribute(e){if(e<=0||!Number.isInteger(e))throw new Error("Parts must be a positive integer");let t=Math.floor(this.cents/e),n=this.cents%e,c=[];for(let s=0;s<e;s++){let o=s<n?1:0;c.push(new r(t+o,this.currency));}return c}distributeByRatios(e){if(e.length===0)throw new Error("Ratios array cannot be empty");let t=e.reduce((s,o)=>s+o,0);if(t<=0)throw new Error("Sum of ratios must be positive");let n=this.cents,c=[];for(let s=0;s<e.length;s++)if(s===e.length-1)c.push(new r(n,this.currency));else {let o=Math.round(this.cents*e[s]/t);c.push(new r(o,this.currency)),n-=o;}return c}format(e={}){let{showSymbol:t=true,showDecimals:n=true,symbolPosition:c}=e,s=this.toDecimal(),o=Math.abs(s),h=s<0,i;if(n&&this.currency.decimals>0){let[a,u]=o.toFixed(this.currency.decimals).split(".");i=`${this.formatInteger(a)}${this.currency.decimalSeparator}${u}`;}else i=this.formatInteger(Math.round(o).toString());if(h&&(i=`-${i}`),t){let a=c??this.currency.symbolPosition,u=this.currency.symbolSpacing?" ":"";a==="before"?i=`${this.currency.symbol}${u}${i}`:i=`${i}${u}${this.currency.symbol}`;}return i}toDecimal(){return this.cents/Math.pow(10,this.currency.decimals)}equals(e){return this.cents===e.cents&&this.currency.code===e.currency.code}greaterThan(e){return this.assertSameCurrency(e),this.cents>e.cents}lessThan(e){return this.assertSameCurrency(e),this.cents<e.cents}greaterThanOrEqual(e){return this.assertSameCurrency(e),this.cents>=e.cents}lessThanOrEqual(e){return this.assertSameCurrency(e),this.cents<=e.cents}isZero(){return this.cents===0}isPositive(){return this.cents>0}isNegative(){return this.cents<0}abs(){return new r(Math.abs(this.cents),this.currency)}negate(){return new r(-this.cents,this.currency)}percentage(e){return new r(Math.round(this.cents*e/100),this.currency)}addPercentage(e){return this.add(this.percentage(e))}subtractPercentage(e){return this.subtract(this.percentage(e))}min(e){return this.assertSameCurrency(e),this.cents<=e.cents?this:new r(e.cents,this.currency)}max(e){return this.assertSameCurrency(e),this.cents>=e.cents?this:new r(e.cents,this.currency)}clamp(e,t){return this.max(e).min(t)}toCents(){return this.cents}isBetween(e,t){return this.assertSameCurrency(e),this.assertSameCurrency(t),this.cents>=e.cents&&this.cents<=t.cents}static sum(e){if(e.length===0)throw new Error("Cannot sum empty array");let t=e[0].currency,n=e.reduce((c,s)=>{if(s.currency.code!==t.code)throw new Error("Cannot sum different currencies");return c+s.cents},0);return new r(n,t)}static minimum(e){if(e.length===0)throw new Error("Cannot get minimum of empty array");return e.reduce((t,n)=>n.cents<t.cents?n:t)}static maximum(e){if(e.length===0)throw new Error("Cannot get maximum of empty array");return e.reduce((t,n)=>n.cents>t.cents?n:t)}static average(e){if(e.length===0)throw new Error("Cannot calculate average of empty array");return r.sum(e).divide(e.length)}toJSON(){return {cents:this.cents,currency:this.currency.code}}toString(){return this.format()}formatInteger(e){let t=[],n=e;for(;n.length>3;)t.unshift(n.slice(-3)),n=n.slice(0,-3);return n&&t.unshift(n),t.join(this.currency.thousandsSeparator)}assertSameCurrency(e){if(this.currency.code!==e.currency.code)throw new Error(`Cannot perform operation with different currencies: ${this.currency.code} and ${e.currency.code}`)}};exports.Money=y;//# sourceMappingURL=index.cjs.map
343
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/money.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Currency, FormatOptions, IMoney } from './types.js';\n\n/**\n * Immutable Money class that uses integer arithmetic for precision\n */\nexport class Money implements IMoney {\n readonly cents: number;\n readonly currency: Currency;\n\n private constructor(cents: number, currency: Currency) {\n if (!Number.isInteger(cents)) {\n throw new Error('Money must be created with an integer number of cents');\n }\n this.cents = cents;\n this.currency = currency;\n Object.freeze(this);\n }\n\n /**\n * Create money from cents (smallest unit)\n */\n static fromCents(cents: number, currency: Currency): Money {\n return new Money(Math.round(cents), currency);\n }\n\n /**\n * Create money from a decimal amount\n * @example Money.fromDecimal(100.50, USD) creates $100.50\n */\n static fromDecimal(amount: number, currency: Currency): Money {\n const multiplier = Math.pow(10, currency.decimals);\n const cents = Math.round(amount * multiplier);\n return new Money(cents, currency);\n }\n\n /**\n * Create zero money\n */\n static zero(currency: Currency): Money {\n return new Money(0, currency);\n }\n\n /**\n * Add another money value (must be same currency)\n */\n add(other: IMoney): Money {\n this.assertSameCurrency(other);\n return new Money(this.cents + other.cents, this.currency);\n }\n\n /**\n * Subtract another money value (must be same currency)\n */\n subtract(other: IMoney): Money {\n this.assertSameCurrency(other);\n return new Money(this.cents - other.cents, this.currency);\n }\n\n /**\n * Multiply by a factor (rounds to nearest cent)\n */\n multiply(factor: number): Money {\n return new Money(Math.round(this.cents * factor), this.currency);\n }\n\n /**\n * Divide by a divisor (rounds to nearest cent)\n */\n divide(divisor: number): Money {\n if (divisor === 0) {\n throw new Error('Cannot divide by zero');\n }\n return new Money(Math.round(this.cents / divisor), this.currency);\n }\n\n /**\n * Distribute money evenly without losing cents\n * The remainder is distributed to the first parts\n * @example $100 / 3 = [$33.34, $33.33, $33.33]\n */\n distribute(parts: number): Money[] {\n if (parts <= 0 || !Number.isInteger(parts)) {\n throw new Error('Parts must be a positive integer');\n }\n\n const quotient = Math.floor(this.cents / parts);\n const remainder = this.cents % parts;\n\n const result: Money[] = [];\n for (let i = 0; i < parts; i++) {\n // Add 1 cent to the first 'remainder' parts\n const extra = i < remainder ? 1 : 0;\n result.push(new Money(quotient + extra, this.currency));\n }\n\n return result;\n }\n\n /**\n * Distribute money according to ratios\n * @example $100 with ratios [1, 2, 2] = [$20, $40, $40]\n */\n distributeByRatios(ratios: number[]): Money[] {\n if (ratios.length === 0) {\n throw new Error('Ratios array cannot be empty');\n }\n\n const total = ratios.reduce((sum, r) => sum + r, 0);\n if (total <= 0) {\n throw new Error('Sum of ratios must be positive');\n }\n\n let remaining = this.cents;\n const result: Money[] = [];\n\n for (let i = 0; i < ratios.length; i++) {\n if (i === ratios.length - 1) {\n // Last part gets whatever is remaining to avoid rounding errors\n result.push(new Money(remaining, this.currency));\n } else {\n const share = Math.round((this.cents * ratios[i]) / total);\n result.push(new Money(share, this.currency));\n remaining -= share;\n }\n }\n\n return result;\n }\n\n /**\n * Format money for display\n */\n format(options: FormatOptions = {}): string {\n const { showSymbol = true, showDecimals = true, symbolPosition } = options;\n\n const decimal = this.toDecimal();\n const absValue = Math.abs(decimal);\n const isNegative = decimal < 0;\n\n // Format the number\n let formatted: string;\n if (showDecimals && this.currency.decimals > 0) {\n const [intPart, decPart] = absValue.toFixed(this.currency.decimals).split('.');\n const intFormatted = this.formatInteger(intPart);\n formatted = `${intFormatted}${this.currency.decimalSeparator}${decPart}`;\n } else {\n formatted = this.formatInteger(Math.round(absValue).toString());\n }\n\n // Add negative sign\n if (isNegative) {\n formatted = `-${formatted}`;\n }\n\n // Add symbol\n if (showSymbol) {\n const pos = symbolPosition ?? this.currency.symbolPosition;\n const space = this.currency.symbolSpacing ? ' ' : '';\n\n if (pos === 'before') {\n formatted = `${this.currency.symbol}${space}${formatted}`;\n } else {\n formatted = `${formatted}${space}${this.currency.symbol}`;\n }\n }\n\n return formatted;\n }\n\n /**\n * Get the decimal representation\n */\n toDecimal(): number {\n return this.cents / Math.pow(10, this.currency.decimals);\n }\n\n /**\n * Check equality with another money value\n */\n equals(other: IMoney): boolean {\n return this.cents === other.cents && this.currency.code === other.currency.code;\n }\n\n /**\n * Check if greater than another money value\n */\n greaterThan(other: IMoney): boolean {\n this.assertSameCurrency(other);\n return this.cents > other.cents;\n }\n\n /**\n * Check if less than another money value\n */\n lessThan(other: IMoney): boolean {\n this.assertSameCurrency(other);\n return this.cents < other.cents;\n }\n\n /**\n * Check if greater than or equal\n */\n greaterThanOrEqual(other: IMoney): boolean {\n this.assertSameCurrency(other);\n return this.cents >= other.cents;\n }\n\n /**\n * Check if less than or equal\n */\n lessThanOrEqual(other: IMoney): boolean {\n this.assertSameCurrency(other);\n return this.cents <= other.cents;\n }\n\n /**\n * Check if zero\n */\n isZero(): boolean {\n return this.cents === 0;\n }\n\n /**\n * Check if positive\n */\n isPositive(): boolean {\n return this.cents > 0;\n }\n\n /**\n * Check if negative\n */\n isNegative(): boolean {\n return this.cents < 0;\n }\n\n /**\n * Get absolute value\n */\n abs(): Money {\n return new Money(Math.abs(this.cents), this.currency);\n }\n\n /**\n * Negate the value\n */\n negate(): Money {\n return new Money(-this.cents, this.currency);\n }\n\n /**\n * Calculate a percentage of this money\n * @example money.percentage(10) returns 10% of the amount\n */\n percentage(percent: number): Money {\n return new Money(Math.round((this.cents * percent) / 100), this.currency);\n }\n\n /**\n * Add a percentage to this money\n * @example money.addPercentage(19) adds 19% (like tax)\n */\n addPercentage(percent: number): Money {\n return this.add(this.percentage(percent));\n }\n\n /**\n * Subtract a percentage from this money\n * @example money.subtractPercentage(10) subtracts 10% (like discount)\n */\n subtractPercentage(percent: number): Money {\n return this.subtract(this.percentage(percent));\n }\n\n /**\n * Get the minimum of this and another money value\n */\n min(other: IMoney): Money {\n this.assertSameCurrency(other);\n return this.cents <= other.cents ? this : new Money(other.cents, this.currency);\n }\n\n /**\n * Get the maximum of this and another money value\n */\n max(other: IMoney): Money {\n this.assertSameCurrency(other);\n return this.cents >= other.cents ? this : new Money(other.cents, this.currency);\n }\n\n /**\n * Clamp this money between a minimum and maximum\n */\n clamp(minValue: IMoney, maxValue: IMoney): Money {\n return this.max(minValue).min(maxValue);\n }\n\n /**\n * Get cents (smallest unit)\n */\n toCents(): number {\n return this.cents;\n }\n\n /**\n * Check if within a range (inclusive)\n */\n isBetween(min: IMoney, max: IMoney): boolean {\n this.assertSameCurrency(min);\n this.assertSameCurrency(max);\n return this.cents >= min.cents && this.cents <= max.cents;\n }\n\n /**\n * Sum multiple money values\n */\n static sum(values: IMoney[]): Money {\n if (values.length === 0) {\n throw new Error('Cannot sum empty array');\n }\n const currency = values[0].currency;\n const total = values.reduce((sum, m) => {\n if (m.currency.code !== currency.code) {\n throw new Error('Cannot sum different currencies');\n }\n return sum + m.cents;\n }, 0);\n return new Money(total, currency);\n }\n\n /**\n * Get the minimum from an array of money values\n */\n static minimum(values: IMoney[]): Money {\n if (values.length === 0) {\n throw new Error('Cannot get minimum of empty array');\n }\n return values.reduce((min, m) => (m.cents < min.cents ? m : min)) as Money;\n }\n\n /**\n * Get the maximum from an array of money values\n */\n static maximum(values: IMoney[]): Money {\n if (values.length === 0) {\n throw new Error('Cannot get maximum of empty array');\n }\n return values.reduce((max, m) => (m.cents > max.cents ? m : max)) as Money;\n }\n\n /**\n * Calculate the average of money values\n */\n static average(values: IMoney[]): Money {\n if (values.length === 0) {\n throw new Error('Cannot calculate average of empty array');\n }\n const total = Money.sum(values);\n return total.divide(values.length);\n }\n\n /**\n * Convert to JSON-serializable object\n */\n toJSON(): { cents: number; currency: string } {\n return {\n cents: this.cents,\n currency: this.currency.code,\n };\n }\n\n /**\n * String representation\n */\n toString(): string {\n return this.format();\n }\n\n private formatInteger(intStr: string): string {\n const parts: string[] = [];\n let remaining = intStr;\n\n while (remaining.length > 3) {\n parts.unshift(remaining.slice(-3));\n remaining = remaining.slice(0, -3);\n }\n\n if (remaining) {\n parts.unshift(remaining);\n }\n\n return parts.join(this.currency.thousandsSeparator);\n }\n\n private assertSameCurrency(other: IMoney): void {\n if (this.currency.code !== other.currency.code) {\n throw new Error(\n `Cannot perform operation with different currencies: ${this.currency.code} and ${other.currency.code}`\n );\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/core/money.ts"],"names":["Money","_Money","cents","currency","amount","multiplier","other","factor","divisor","parts","quotient","remainder","result","i","extra","ratios","total","sum","r","remaining","share","options","showSymbol","showDecimals","symbolPosition","decimal","absValue","isNegative","formatted","intPart","decPart","pos","space","percent","minValue","maxValue","min","max","values","m","intStr"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Currency, FormatOptions, IMoney } from './types.js';\n\n/**\n * Immutable Money class that uses integer arithmetic for precision\n */\nexport class Money implements IMoney {\n readonly cents: number;\n readonly currency: Currency;\n\n private constructor(cents: number, currency: Currency) {\n if (!Number.isInteger(cents)) {\n throw new Error('Money must be created with an integer number of cents');\n }\n this.cents = cents;\n this.currency = currency;\n Object.freeze(this);\n }\n\n /**\n * Create money from cents (smallest unit)\n */\n static fromCents(cents: number, currency: Currency): Money {\n return new Money(Math.round(cents), currency);\n }\n\n /**\n * Create money from a decimal amount\n * @example Money.fromDecimal(100.50, USD) creates $100.50\n */\n static fromDecimal(amount: number, currency: Currency): Money {\n const multiplier = Math.pow(10, currency.decimals);\n const cents = Math.round(amount * multiplier);\n return new Money(cents, currency);\n }\n\n /**\n * Create zero money\n */\n static zero(currency: Currency): Money {\n return new Money(0, currency);\n }\n\n /**\n * Add another money value (must be same currency)\n */\n add(other: IMoney): Money {\n this.assertSameCurrency(other);\n return new Money(this.cents + other.cents, this.currency);\n }\n\n /**\n * Subtract another money value (must be same currency)\n */\n subtract(other: IMoney): Money {\n this.assertSameCurrency(other);\n return new Money(this.cents - other.cents, this.currency);\n }\n\n /**\n * Multiply by a factor (rounds to nearest cent)\n */\n multiply(factor: number): Money {\n return new Money(Math.round(this.cents * factor), this.currency);\n }\n\n /**\n * Divide by a divisor (rounds to nearest cent)\n */\n divide(divisor: number): Money {\n if (divisor === 0) {\n throw new Error('Cannot divide by zero');\n }\n return new Money(Math.round(this.cents / divisor), this.currency);\n }\n\n /**\n * Distribute money evenly without losing cents\n * The remainder is distributed to the first parts\n * @example $100 / 3 = [$33.34, $33.33, $33.33]\n */\n distribute(parts: number): Money[] {\n if (parts <= 0 || !Number.isInteger(parts)) {\n throw new Error('Parts must be a positive integer');\n }\n\n const quotient = Math.floor(this.cents / parts);\n const remainder = this.cents % parts;\n\n const result: Money[] = [];\n for (let i = 0; i < parts; i++) {\n // Add 1 cent to the first 'remainder' parts\n const extra = i < remainder ? 1 : 0;\n result.push(new Money(quotient + extra, this.currency));\n }\n\n return result;\n }\n\n /**\n * Distribute money according to ratios\n * @example $100 with ratios [1, 2, 2] = [$20, $40, $40]\n */\n distributeByRatios(ratios: number[]): Money[] {\n if (ratios.length === 0) {\n throw new Error('Ratios array cannot be empty');\n }\n\n const total = ratios.reduce((sum, r) => sum + r, 0);\n if (total <= 0) {\n throw new Error('Sum of ratios must be positive');\n }\n\n let remaining = this.cents;\n const result: Money[] = [];\n\n for (let i = 0; i < ratios.length; i++) {\n if (i === ratios.length - 1) {\n // Last part gets whatever is remaining to avoid rounding errors\n result.push(new Money(remaining, this.currency));\n } else {\n const share = Math.round((this.cents * ratios[i]) / total);\n result.push(new Money(share, this.currency));\n remaining -= share;\n }\n }\n\n return result;\n }\n\n /**\n * Format money for display\n */\n format(options: FormatOptions = {}): string {\n const { showSymbol = true, showDecimals = true, symbolPosition } = options;\n\n const decimal = this.toDecimal();\n const absValue = Math.abs(decimal);\n const isNegative = decimal < 0;\n\n // Format the number\n let formatted: string;\n if (showDecimals && this.currency.decimals > 0) {\n const [intPart, decPart] = absValue.toFixed(this.currency.decimals).split('.');\n const intFormatted = this.formatInteger(intPart);\n formatted = `${intFormatted}${this.currency.decimalSeparator}${decPart}`;\n } else {\n formatted = this.formatInteger(Math.round(absValue).toString());\n }\n\n // Add negative sign\n if (isNegative) {\n formatted = `-${formatted}`;\n }\n\n // Add symbol\n if (showSymbol) {\n const pos = symbolPosition ?? this.currency.symbolPosition;\n const space = this.currency.symbolSpacing ? ' ' : '';\n\n if (pos === 'before') {\n formatted = `${this.currency.symbol}${space}${formatted}`;\n } else {\n formatted = `${formatted}${space}${this.currency.symbol}`;\n }\n }\n\n return formatted;\n }\n\n /**\n * Get the decimal representation\n */\n toDecimal(): number {\n return this.cents / Math.pow(10, this.currency.decimals);\n }\n\n /**\n * Check equality with another money value\n */\n equals(other: IMoney): boolean {\n return this.cents === other.cents && this.currency.code === other.currency.code;\n }\n\n /**\n * Check if greater than another money value\n */\n greaterThan(other: IMoney): boolean {\n this.assertSameCurrency(other);\n return this.cents > other.cents;\n }\n\n /**\n * Check if less than another money value\n */\n lessThan(other: IMoney): boolean {\n this.assertSameCurrency(other);\n return this.cents < other.cents;\n }\n\n /**\n * Check if greater than or equal\n */\n greaterThanOrEqual(other: IMoney): boolean {\n this.assertSameCurrency(other);\n return this.cents >= other.cents;\n }\n\n /**\n * Check if less than or equal\n */\n lessThanOrEqual(other: IMoney): boolean {\n this.assertSameCurrency(other);\n return this.cents <= other.cents;\n }\n\n /**\n * Check if zero\n */\n isZero(): boolean {\n return this.cents === 0;\n }\n\n /**\n * Check if positive\n */\n isPositive(): boolean {\n return this.cents > 0;\n }\n\n /**\n * Check if negative\n */\n isNegative(): boolean {\n return this.cents < 0;\n }\n\n /**\n * Get absolute value\n */\n abs(): Money {\n return new Money(Math.abs(this.cents), this.currency);\n }\n\n /**\n * Negate the value\n */\n negate(): Money {\n return new Money(-this.cents, this.currency);\n }\n\n /**\n * Calculate a percentage of this money\n * @example money.percentage(10) returns 10% of the amount\n */\n percentage(percent: number): Money {\n return new Money(Math.round((this.cents * percent) / 100), this.currency);\n }\n\n /**\n * Add a percentage to this money\n * @example money.addPercentage(19) adds 19% (like tax)\n */\n addPercentage(percent: number): Money {\n return this.add(this.percentage(percent));\n }\n\n /**\n * Subtract a percentage from this money\n * @example money.subtractPercentage(10) subtracts 10% (like discount)\n */\n subtractPercentage(percent: number): Money {\n return this.subtract(this.percentage(percent));\n }\n\n /**\n * Get the minimum of this and another money value\n */\n min(other: IMoney): Money {\n this.assertSameCurrency(other);\n return this.cents <= other.cents ? this : new Money(other.cents, this.currency);\n }\n\n /**\n * Get the maximum of this and another money value\n */\n max(other: IMoney): Money {\n this.assertSameCurrency(other);\n return this.cents >= other.cents ? this : new Money(other.cents, this.currency);\n }\n\n /**\n * Clamp this money between a minimum and maximum\n */\n clamp(minValue: IMoney, maxValue: IMoney): Money {\n return this.max(minValue).min(maxValue);\n }\n\n /**\n * Get cents (smallest unit)\n */\n toCents(): number {\n return this.cents;\n }\n\n /**\n * Check if within a range (inclusive)\n */\n isBetween(min: IMoney, max: IMoney): boolean {\n this.assertSameCurrency(min);\n this.assertSameCurrency(max);\n return this.cents >= min.cents && this.cents <= max.cents;\n }\n\n /**\n * Sum multiple money values\n */\n static sum(values: IMoney[]): Money {\n if (values.length === 0) {\n throw new Error('Cannot sum empty array');\n }\n const currency = values[0].currency;\n const total = values.reduce((sum, m) => {\n if (m.currency.code !== currency.code) {\n throw new Error('Cannot sum different currencies');\n }\n return sum + m.cents;\n }, 0);\n return new Money(total, currency);\n }\n\n /**\n * Get the minimum from an array of money values\n */\n static minimum(values: IMoney[]): Money {\n if (values.length === 0) {\n throw new Error('Cannot get minimum of empty array');\n }\n return values.reduce((min, m) => (m.cents < min.cents ? m : min)) as Money;\n }\n\n /**\n * Get the maximum from an array of money values\n */\n static maximum(values: IMoney[]): Money {\n if (values.length === 0) {\n throw new Error('Cannot get maximum of empty array');\n }\n return values.reduce((max, m) => (m.cents > max.cents ? m : max)) as Money;\n }\n\n /**\n * Calculate the average of money values\n */\n static average(values: IMoney[]): Money {\n if (values.length === 0) {\n throw new Error('Cannot calculate average of empty array');\n }\n const total = Money.sum(values);\n return total.divide(values.length);\n }\n\n /**\n * Convert to JSON-serializable object\n */\n toJSON(): { cents: number; currency: string } {\n return {\n cents: this.cents,\n currency: this.currency.code,\n };\n }\n\n /**\n * String representation\n */\n toString(): string {\n return this.format();\n }\n\n private formatInteger(intStr: string): string {\n const parts: string[] = [];\n let remaining = intStr;\n\n while (remaining.length > 3) {\n parts.unshift(remaining.slice(-3));\n remaining = remaining.slice(0, -3);\n }\n\n if (remaining) {\n parts.unshift(remaining);\n }\n\n return parts.join(this.currency.thousandsSeparator);\n }\n\n private assertSameCurrency(other: IMoney): void {\n if (this.currency.code !== other.currency.code) {\n throw new Error(\n `Cannot perform operation with different currencies: ${this.currency.code} and ${other.currency.code}`\n );\n }\n }\n}\n"]}