soff-money 0.1.1 → 0.2.1

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 (58) hide show
  1. package/README.md +203 -39
  2. package/dist/core/index.cjs +1 -243
  3. package/dist/core/index.cjs.map +1 -1
  4. package/dist/core/index.d.cts +51 -0
  5. package/dist/core/index.d.ts +51 -0
  6. package/dist/core/index.js +1 -241
  7. package/dist/core/index.js.map +1 -1
  8. package/dist/index.cjs +1 -308
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +4 -0
  11. package/dist/index.d.ts +4 -0
  12. package/dist/index.js +1 -300
  13. package/dist/index.js.map +1 -1
  14. package/dist/locales/ar.cjs +1 -15
  15. package/dist/locales/ar.cjs.map +1 -1
  16. package/dist/locales/ar.js +1 -13
  17. package/dist/locales/ar.js.map +1 -1
  18. package/dist/locales/br.cjs +1 -15
  19. package/dist/locales/br.cjs.map +1 -1
  20. package/dist/locales/br.js +1 -13
  21. package/dist/locales/br.js.map +1 -1
  22. package/dist/locales/cl.cjs +2 -0
  23. package/dist/locales/cl.cjs.map +1 -0
  24. package/dist/locales/cl.d.cts +15 -0
  25. package/dist/locales/cl.d.ts +15 -0
  26. package/dist/locales/cl.js +2 -0
  27. package/dist/locales/cl.js.map +1 -0
  28. package/dist/locales/co.cjs +1 -20
  29. package/dist/locales/co.cjs.map +1 -1
  30. package/dist/locales/co.js +1 -17
  31. package/dist/locales/co.js.map +1 -1
  32. package/dist/locales/eu.cjs +2 -0
  33. package/dist/locales/eu.cjs.map +1 -0
  34. package/dist/locales/eu.d.cts +9 -0
  35. package/dist/locales/eu.d.ts +9 -0
  36. package/dist/locales/eu.js +2 -0
  37. package/dist/locales/eu.js.map +1 -0
  38. package/dist/locales/mx.cjs +1 -15
  39. package/dist/locales/mx.cjs.map +1 -1
  40. package/dist/locales/mx.js +1 -13
  41. package/dist/locales/mx.js.map +1 -1
  42. package/dist/locales/pe.cjs +2 -0
  43. package/dist/locales/pe.cjs.map +1 -0
  44. package/dist/locales/pe.d.cts +9 -0
  45. package/dist/locales/pe.d.ts +9 -0
  46. package/dist/locales/pe.js +2 -0
  47. package/dist/locales/pe.js.map +1 -0
  48. package/dist/locales/us.cjs +1 -15
  49. package/dist/locales/us.cjs.map +1 -1
  50. package/dist/locales/us.js +1 -13
  51. package/dist/locales/us.js.map +1 -1
  52. package/dist/locales/uy.cjs +2 -0
  53. package/dist/locales/uy.cjs.map +1 -0
  54. package/dist/locales/uy.d.cts +9 -0
  55. package/dist/locales/uy.d.ts +9 -0
  56. package/dist/locales/uy.js +2 -0
  57. package/dist/locales/uy.js.map +1 -0
  58. package/package.json +41 -1
package/README.md CHANGED
@@ -1,25 +1,53 @@
1
- # soff-money 💸
2
-
3
- Safe money handling for JavaScript with integer-based arithmetic and LATAM locale formatting.
4
-
5
- ## The Problem
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">
8
+
9
+ [![npm](https://img.shields.io/npm/v/soff-money)](https://www.npmjs.com/package/soff-money)
10
+ [![License](https://img.shields.io/github/license/bledxs/soff-monorepo)](LICENSE)
11
+ [![Build Status](https://github.com/bledxs/soff-monorepo/actions/workflows/ci.yml/badge.svg)](https://github.com/bledxs/soff-monorepo/actions)
12
+ [![codecov](https://codecov.io/gh/bledxs/soff-monorepo/branch/master/graph/badge.svg)](https://codecov.io/gh/bledxs/soff-monorepo)
13
+ [![minzipped size](https://img.shields.io/bundlephobia/minzip/soff-money)](https://bundlephobia.com/package/soff-money)
14
+
15
+ </div>
16
+
17
+ ---
18
+
19
+ **Zero dependencies** · **TypeScript** · **~8KB core**
20
+
21
+ ## Table of Contents
22
+
23
+ - [Soff Money](#soff-money)
24
+ - [Table of Contents](#table-of-contents)
25
+ - [Why?](#why)
26
+ - [Install](#install)
27
+ - [Quick Start](#quick-start)
28
+ - [Fair Distribution](#fair-distribution)
29
+ - [Proportional Distribution](#proportional-distribution)
30
+ - [Available Locales](#available-locales)
31
+ - [API Reference](#api-reference)
32
+ - [Creating Money](#creating-money)
33
+ - [Arithmetic Operations](#arithmetic-operations)
34
+ - [Percentage Operations](#percentage-operations)
35
+ - [Min/Max Operations](#minmax-operations)
36
+ - [Comparisons](#comparisons)
37
+ - [Formatting](#formatting)
38
+ - [Static Methods](#static-methods)
39
+ - [Bundle Size](#bundle-size)
40
+ - [Contributing](#contributing)
41
+ - [License](#license)
42
+ - [Documentation](#documentation)
43
+
44
+ ## Why?
6
45
 
7
46
  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?
8
47
 
9
- ## The Solution
10
-
11
- A library that handles money using integers (Safe Money pattern) and formats according to the country's locale.
12
-
13
- ## Features
48
+ This library handles money using integers (Safe Money pattern) and formats according to the country's locale.
14
49
 
15
- - 🔢 **Integer-based arithmetic** - No floating point errors
16
- - 🌎 **LATAM-first locales** - CO, MX, AR, BR, US
17
- - 🌳 **Tree-shakeable** - Import only what you need
18
- - 📦 **Tiny bundle** - Core is < 2KB gzipped
19
- - 💯 **TypeScript** - Full type safety
20
- - ⚡ **Zero dependencies** - Pure JavaScript
21
-
22
- ## Installation
50
+ ## Install
23
51
 
24
52
  ```bash
25
53
  npm install soff-money
@@ -28,30 +56,30 @@ npm install soff-money
28
56
  ## Quick Start
29
57
 
30
58
  ```typescript
31
- import { Money } from 'soff-money';
32
- import { COP } from 'soff-money/locales/co';
59
+ import { Money, COP, USD } from 'soff-money';
33
60
 
34
- // Create money from cents (safe)
35
- const price = Money.fromCents(10000, COP); // $100.00 COP
61
+ // Create money from decimal (safe - converted to cents internally)
62
+ const price = Money.fromDecimal(1500000, COP);
36
63
 
37
- // Arithmetic operations
38
- const total = price.add(Money.fromCents(5000, COP)); // $150.00
39
- const discounted = total.multiply(0.9); // $135.00
64
+ // Arithmetic operations (all return new Money instances)
65
+ const withTax = price.addPercentage(19); // Add 19% tax
66
+ const discounted = withTax.subtractPercentage(10); // 10% discount
40
67
 
41
68
  // Format for display
42
- console.log(total.format()); // "$ 150,00" or "$150.00" depending on locale
69
+ console.log(price.format()); // "$ 1.500.000,00"
70
+ console.log(discounted.format()); // "$ 1.606.500,00"
43
71
 
44
- // Safe distribution (no lost cents!)
45
- const [part1, part2, part3] = Money.fromCents(10000, COP).distribute(3);
46
- // 33.34 + 33.33 + 33.33 = 100.00 ✓
72
+ // Safe comparisons
73
+ price.equals(Money.fromDecimal(1500000, COP)); // true
74
+ price.greaterThan(discounted); // false
47
75
  ```
48
76
 
49
- ## Killer Feature: Fair Distribution
77
+ ## Fair Distribution
50
78
 
51
79
  When splitting money, you never lose cents:
52
80
 
53
81
  ```typescript
54
- const bill = Money.fromCents(10000, COP); // $100.00
82
+ const bill = Money.fromDecimal(100, USD);
55
83
  const [alice, bob, charlie] = bill.distribute(3);
56
84
 
57
85
  // alice: $33.34
@@ -60,16 +88,152 @@ const [alice, bob, charlie] = bill.distribute(3);
60
88
  // Total: $100.00 ✓ (not $99.99!)
61
89
  ```
62
90
 
91
+ The extra cent goes to the first person - no money is lost!
92
+
93
+ ### Proportional Distribution
94
+
95
+ ```typescript
96
+ const total = Money.fromDecimal(100, USD);
97
+ const [share1, share2, share3] = total.distributeByRatios([1, 2, 2]);
98
+
99
+ // share1: $20.00 (20%)
100
+ // share2: $40.00 (40%)
101
+ // share3: $40.00 (40%)
102
+ ```
103
+
63
104
  ## Available Locales
64
105
 
65
- | Locale | Currency | Symbol | Format |
66
- | ------ | -------- | ------ | ----------- |
67
- | `co` | COP | $ | $ 1.000,00 |
68
- | `mx` | MXN | $ | $1,000.00 |
69
- | `ar` | ARS | $ | $ 1.000,00 |
70
- | `br` | BRL | R$ | R$ 1.000,00 |
71
- | `us` | USD | $ | $1,000.00 |
106
+ | Locale | Import | Currency | Symbol | Format |
107
+ | ------------ | ----------------------- | -------- | ------ | ----------- |
108
+ | 🇨🇴 Colombia | `soff-money/locales/co` | COP | $ | $ 1.500.000 |
109
+ | 🇲🇽 México | `soff-money/locales/mx` | MXN | $ | $1,500.00 |
110
+ | 🇦🇷 Argentina | `soff-money/locales/ar` | ARS | $ | $ 1.500,00 |
111
+ | 🇧🇷 Brasil | `soff-money/locales/br` | BRL | R$ | R$ 1.500,00 |
112
+ | 🇺🇸 USA | `soff-money/locales/us` | USD | $ | $1,500.00 |
113
+ | 🇨🇱 Chile | `soff-money/locales/cl` | CLP | $ | $ 1.500 |
114
+ | 🇵🇪 Perú | `soff-money/locales/pe` | PEN | S/ | S/ 1,500.00 |
115
+ | 🇺🇾 Uruguay | `soff-money/locales/uy` | UYU | $ | $ 1.500,00 |
116
+ | 🇪🇺 Euro | `soff-money/locales/eu` | EUR | € | 1.500,00 € |
117
+
118
+ ## API Reference
119
+
120
+ ### Creating Money
121
+
122
+ ```typescript
123
+ // From decimal (recommended)
124
+ Money.fromDecimal(1500.5, COP);
125
+
126
+ // From cents (when you already have cents)
127
+ Money.fromCents(150050, COP);
128
+
129
+ // Zero
130
+ Money.zero(COP);
131
+ ```
132
+
133
+ ### Arithmetic Operations
134
+
135
+ All operations return new Money instances (immutable):
136
+
137
+ ```typescript
138
+ const a = Money.fromDecimal(100, USD);
139
+ const b = Money.fromDecimal(50, USD);
140
+
141
+ a.add(b); // $150
142
+ a.subtract(b); // $50
143
+ a.multiply(2); // $200
144
+ a.multiply(0.5); // $50
145
+ a.divide(2); // $50
146
+ a.negate(); // -$100
147
+ a.abs(); // $100 (absolute value)
148
+ ```
149
+
150
+ ### Percentage Operations
151
+
152
+ ```typescript
153
+ const price = Money.fromDecimal(100, USD);
154
+
155
+ price.percentage(10); // $10.00 (10% of price)
156
+ price.addPercentage(19); // $119.00 (price + 19% tax)
157
+ price.subtractPercentage(10); // $90.00 (price - 10% discount)
158
+ ```
159
+
160
+ ### Min/Max Operations
161
+
162
+ ```typescript
163
+ const a = Money.fromDecimal(100, USD);
164
+ const b = Money.fromDecimal(50, USD);
165
+
166
+ a.min(b); // $50 (minimum of a and b)
167
+ a.max(b); // $100 (maximum of a and b)
168
+
169
+ const min = Money.fromDecimal(10, USD);
170
+ const max = Money.fromDecimal(100, USD);
171
+ a.clamp(min, max); // $100 (clamp a between min and max)
172
+
173
+ a.isBetween(min, max); // true (check if a is in range)
174
+ ```
175
+
176
+ ### Comparisons
177
+
178
+ ```typescript
179
+ a.equals(b); // false
180
+ a.greaterThan(b); // true
181
+ a.greaterThanOrEqual(b); // true
182
+ a.lessThan(b); // false
183
+ a.lessThanOrEqual(b); // false
184
+ a.isZero(); // false
185
+ a.isPositive(); // true
186
+ a.isNegative(); // false
187
+ ```
188
+
189
+ ### Formatting
190
+
191
+ ```typescript
192
+ const price = Money.fromDecimal(1500.5, USD);
193
+
194
+ price.format(); // "$1,500.50"
195
+ price.format({ showSymbol: false }); // "1,500.50"
196
+ price.format({ showDecimals: false }); // "$1,501"
197
+ price.format({ symbolPosition: 'after' }); // "1,500.50 $"
198
+ price.toDecimal(); // 1500.50
199
+ price.toCents(); // 150050
200
+ price.toJSON(); // { cents: 150050, currency: 'USD' }
201
+ ```
202
+
203
+ ## Static Methods
204
+
205
+ ```typescript
206
+ // Sum multiple values
207
+ const items = [Money.fromDecimal(100, USD), Money.fromDecimal(50, USD), Money.fromDecimal(25, USD)];
208
+
209
+ Money.sum(items); // $175.00
210
+
211
+ // Get min/max from array
212
+ Money.minimum(items); // $25.00
213
+ Money.maximum(items); // $100.00
214
+
215
+ // Calculate average
216
+ Money.average(items); // $58.33
217
+ ```
218
+
219
+ ## Bundle Size
220
+
221
+ | Import | Size (minified) |
222
+ | ------------ | --------------- |
223
+ | `core` | ~8.8KB |
224
+ | `locales/*` | ~0.3KB each |
225
+ | Full package | ~10.6KB |
226
+
227
+ Tree-shaking ensures you only ship what you import.
228
+
229
+ ## Contributing
230
+
231
+ Please read [CONTRIBUTING.md](../../CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests.
72
232
 
73
233
  ## License
74
234
 
75
- MIT
235
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
236
+
237
+ ## Documentation
238
+
239
+ - [Español](docs/README.es.md)
@@ -1,244 +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
- * Convert to JSON-serializable object
208
- */
209
- toJSON() {
210
- return {
211
- cents: this.cents,
212
- currency: this.currency.code
213
- };
214
- }
215
- /**
216
- * String representation
217
- */
218
- toString() {
219
- return this.format();
220
- }
221
- formatInteger(intStr) {
222
- const parts = [];
223
- let remaining = intStr;
224
- while (remaining.length > 3) {
225
- parts.unshift(remaining.slice(-3));
226
- remaining = remaining.slice(0, -3);
227
- }
228
- if (remaining) {
229
- parts.unshift(remaining);
230
- }
231
- return parts.join(this.currency.thousandsSeparator);
232
- }
233
- assertSameCurrency(other) {
234
- if (this.currency.code !== other.currency.code) {
235
- throw new Error(
236
- `Cannot perform operation with different currencies: ${this.currency.code} and ${other.currency.code}`
237
- );
238
- }
239
- }
240
- };
241
-
242
- exports.Money = Money;
243
- //# 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
244
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 * 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":"aAKO,IAAMA,CAAAA,CAAN,MAAMC,CAAwB,CAI3B,YAAYC,CAAAA,CAAeC,CAAAA,CAAoB,CACrD,GAAI,CAAC,MAAA,CAAO,UAAUD,CAAK,CAAA,CACzB,MAAM,IAAI,KAAA,CAAM,uDAAuD,EAEzE,IAAA,CAAK,KAAA,CAAQA,CAAAA,CACb,IAAA,CAAK,QAAA,CAAWC,CAAAA,CAChB,OAAO,MAAA,CAAO,IAAI,EACpB,CAKA,OAAO,UAAUD,CAAAA,CAAeC,CAAAA,CAA2B,CACzD,OAAO,IAAIF,CAAAA,CAAM,KAAK,KAAA,CAAMC,CAAK,CAAA,CAAGC,CAAQ,CAC9C,CAMA,OAAO,WAAA,CAAYC,CAAAA,CAAgBD,CAAAA,CAA2B,CAC5D,IAAME,CAAAA,CAAa,KAAK,GAAA,CAAI,EAAA,CAAIF,EAAS,QAAQ,CAAA,CAC3CD,EAAQ,IAAA,CAAK,KAAA,CAAME,CAAAA,CAASC,CAAU,CAAA,CAC5C,OAAO,IAAIJ,CAAAA,CAAMC,CAAAA,CAAOC,CAAQ,CAClC,CAKA,OAAO,KAAKA,CAAAA,CAA2B,CACrC,OAAO,IAAIF,CAAAA,CAAM,CAAA,CAAGE,CAAQ,CAC9B,CAKA,GAAA,CAAIG,CAAAA,CAAsB,CACxB,OAAA,IAAA,CAAK,mBAAmBA,CAAK,CAAA,CACtB,IAAIL,CAAAA,CAAM,IAAA,CAAK,KAAA,CAAQK,EAAM,KAAA,CAAO,IAAA,CAAK,QAAQ,CAC1D,CAKA,QAAA,CAASA,EAAsB,CAC7B,OAAA,IAAA,CAAK,kBAAA,CAAmBA,CAAK,CAAA,CACtB,IAAIL,EAAM,IAAA,CAAK,KAAA,CAAQK,EAAM,KAAA,CAAO,IAAA,CAAK,QAAQ,CAC1D,CAKA,QAAA,CAASC,CAAAA,CAAuB,CAC9B,OAAO,IAAIN,CAAAA,CAAM,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,KAAA,CAAQM,CAAM,EAAG,IAAA,CAAK,QAAQ,CACjE,CAKA,MAAA,CAAOC,CAAAA,CAAwB,CAC7B,GAAIA,CAAAA,GAAY,CAAA,CACd,MAAM,IAAI,KAAA,CAAM,uBAAuB,CAAA,CAEzC,OAAO,IAAIP,CAAAA,CAAM,IAAA,CAAK,KAAA,CAAM,KAAK,KAAA,CAAQO,CAAO,CAAA,CAAG,IAAA,CAAK,QAAQ,CAClE,CAOA,UAAA,CAAWC,CAAAA,CAAwB,CACjC,GAAIA,CAAAA,EAAS,CAAA,EAAK,CAAC,MAAA,CAAO,SAAA,CAAUA,CAAK,CAAA,CACvC,MAAM,IAAI,KAAA,CAAM,kCAAkC,CAAA,CAGpD,IAAMC,CAAAA,CAAW,IAAA,CAAK,MAAM,IAAA,CAAK,KAAA,CAAQD,CAAK,CAAA,CACxCE,CAAAA,CAAY,IAAA,CAAK,MAAQF,CAAAA,CAEzBG,CAAAA,CAAkB,EAAC,CACzB,IAAA,IAASC,CAAAA,CAAI,EAAGA,CAAAA,CAAIJ,CAAAA,CAAOI,CAAAA,EAAAA,CAAK,CAE9B,IAAMC,CAAAA,CAAQD,EAAIF,CAAAA,CAAY,CAAA,CAAI,CAAA,CAClCC,CAAAA,CAAO,IAAA,CAAK,IAAIX,EAAMS,CAAAA,CAAWI,CAAAA,CAAO,IAAA,CAAK,QAAQ,CAAC,EACxD,CAEA,OAAOF,CACT,CAMA,kBAAA,CAAmBG,CAAAA,CAA2B,CAC5C,GAAIA,CAAAA,CAAO,MAAA,GAAW,EACpB,MAAM,IAAI,MAAM,8BAA8B,CAAA,CAGhD,IAAMC,CAAAA,CAAQD,CAAAA,CAAO,MAAA,CAAO,CAACE,CAAAA,CAAKC,CAAAA,GAAMD,CAAAA,CAAMC,CAAAA,CAAG,CAAC,CAAA,CAClD,GAAIF,CAAAA,EAAS,CAAA,CACX,MAAM,IAAI,KAAA,CAAM,gCAAgC,EAGlD,IAAIG,CAAAA,CAAY,IAAA,CAAK,KAAA,CACfP,CAAAA,CAAkB,GAExB,IAAA,IAASC,CAAAA,CAAI,CAAA,CAAGA,CAAAA,CAAIE,CAAAA,CAAO,MAAA,CAAQF,IACjC,GAAIA,CAAAA,GAAME,CAAAA,CAAO,MAAA,CAAS,CAAA,CAExBH,CAAAA,CAAO,KAAK,IAAIX,CAAAA,CAAMkB,CAAAA,CAAW,IAAA,CAAK,QAAQ,CAAC,OAC1C,CACL,IAAMC,EAAQ,IAAA,CAAK,KAAA,CAAO,KAAK,KAAA,CAAQL,CAAAA,CAAOF,CAAC,CAAA,CAAKG,CAAK,CAAA,CACzDJ,EAAO,IAAA,CAAK,IAAIX,CAAAA,CAAMmB,CAAAA,CAAO,IAAA,CAAK,QAAQ,CAAC,CAAA,CAC3CD,CAAAA,EAAaC,EACf,CAGF,OAAOR,CACT,CAKA,MAAA,CAAOS,CAAAA,CAAyB,EAAC,CAAW,CAC1C,GAAM,CAAE,UAAA,CAAAC,CAAAA,CAAa,IAAA,CAAM,YAAA,CAAAC,CAAAA,CAAe,IAAA,CAAM,eAAAC,CAAe,CAAA,CAAIH,CAAAA,CAE7DI,CAAAA,CAAU,IAAA,CAAK,SAAA,GACfC,CAAAA,CAAW,IAAA,CAAK,GAAA,CAAID,CAAO,CAAA,CAC3BE,CAAAA,CAAaF,EAAU,CAAA,CAGzBG,CAAAA,CACJ,GAAIL,CAAAA,EAAgB,IAAA,CAAK,SAAS,QAAA,CAAW,CAAA,CAAG,CAC9C,GAAM,CAACM,CAAAA,CAASC,CAAO,CAAA,CAAIJ,CAAAA,CAAS,OAAA,CAAQ,IAAA,CAAK,QAAA,CAAS,QAAQ,EAAE,KAAA,CAAM,GAAG,CAAA,CAE7EE,CAAAA,CAAY,CAAA,EADS,IAAA,CAAK,cAAcC,CAAO,CACpB,CAAA,EAAG,IAAA,CAAK,QAAA,CAAS,gBAAgB,GAAGC,CAAO,CAAA,EACxE,CAAA,KACEF,CAAAA,CAAY,IAAA,CAAK,aAAA,CAAc,KAAK,KAAA,CAAMF,CAAQ,CAAA,CAAE,QAAA,EAAU,CAAA,CAShE,GALIC,CAAAA,GACFC,CAAAA,CAAY,CAAA,CAAA,EAAIA,CAAS,CAAA,CAAA,CAAA,CAIvBN,CAAAA,CAAY,CACd,IAAMS,CAAAA,CAAMP,GAAkB,IAAA,CAAK,QAAA,CAAS,eACtCQ,CAAAA,CAAQ,IAAA,CAAK,QAAA,CAAS,aAAA,CAAgB,GAAA,CAAM,EAAA,CAE9CD,IAAQ,QAAA,CACVH,CAAAA,CAAY,CAAA,EAAG,IAAA,CAAK,QAAA,CAAS,MAAM,GAAGI,CAAK,CAAA,EAAGJ,CAAS,CAAA,CAAA,CAEvDA,CAAAA,CAAY,CAAA,EAAGA,CAAS,CAAA,EAAGI,CAAK,CAAA,EAAG,IAAA,CAAK,QAAA,CAAS,MAAM,GAE3D,CAEA,OAAOJ,CACT,CAKA,SAAA,EAAoB,CAClB,OAAO,IAAA,CAAK,KAAA,CAAQ,IAAA,CAAK,GAAA,CAAI,EAAA,CAAI,IAAA,CAAK,SAAS,QAAQ,CACzD,CAKA,MAAA,CAAOtB,CAAAA,CAAwB,CAC7B,OAAO,IAAA,CAAK,KAAA,GAAUA,EAAM,KAAA,EAAS,IAAA,CAAK,SAAS,IAAA,GAASA,CAAAA,CAAM,QAAA,CAAS,IAC7E,CAKA,WAAA,CAAYA,EAAwB,CAClC,OAAA,IAAA,CAAK,kBAAA,CAAmBA,CAAK,CAAA,CACtB,IAAA,CAAK,MAAQA,CAAAA,CAAM,KAC5B,CAKA,QAAA,CAASA,CAAAA,CAAwB,CAC/B,YAAK,kBAAA,CAAmBA,CAAK,CAAA,CACtB,IAAA,CAAK,KAAA,CAAQA,CAAAA,CAAM,KAC5B,CAKA,kBAAA,CAAmBA,CAAAA,CAAwB,CACzC,OAAA,IAAA,CAAK,kBAAA,CAAmBA,CAAK,CAAA,CACtB,IAAA,CAAK,KAAA,EAASA,CAAAA,CAAM,KAC7B,CAKA,gBAAgBA,CAAAA,CAAwB,CACtC,OAAA,IAAA,CAAK,kBAAA,CAAmBA,CAAK,CAAA,CACtB,KAAK,KAAA,EAASA,CAAAA,CAAM,KAC7B,CAKA,MAAA,EAAkB,CAChB,OAAO,IAAA,CAAK,KAAA,GAAU,CACxB,CAKA,UAAA,EAAsB,CACpB,OAAO,IAAA,CAAK,KAAA,CAAQ,CACtB,CAKA,UAAA,EAAsB,CACpB,OAAO,IAAA,CAAK,KAAA,CAAQ,CACtB,CAKA,GAAA,EAAa,CACX,OAAO,IAAIL,CAAAA,CAAM,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,KAAK,CAAA,CAAG,IAAA,CAAK,QAAQ,CACtD,CAKA,MAAA,EAAgB,CACd,OAAO,IAAIA,CAAAA,CAAM,CAAC,IAAA,CAAK,KAAA,CAAO,KAAK,QAAQ,CAC7C,CAMA,UAAA,CAAWgC,CAAAA,CAAwB,CACjC,OAAO,IAAIhC,CAAAA,CAAM,KAAK,KAAA,CAAO,IAAA,CAAK,MAAQgC,CAAAA,CAAW,GAAG,CAAA,CAAG,IAAA,CAAK,QAAQ,CAC1E,CAMA,aAAA,CAAcA,CAAAA,CAAwB,CACpC,OAAO,IAAA,CAAK,GAAA,CAAI,KAAK,UAAA,CAAWA,CAAO,CAAC,CAC1C,CAMA,kBAAA,CAAmBA,EAAwB,CACzC,OAAO,IAAA,CAAK,QAAA,CAAS,IAAA,CAAK,UAAA,CAAWA,CAAO,CAAC,CAC/C,CAKA,GAAA,CAAI3B,CAAAA,CAAsB,CACxB,YAAK,kBAAA,CAAmBA,CAAK,CAAA,CACtB,IAAA,CAAK,KAAA,EAASA,CAAAA,CAAM,MAAQ,IAAA,CAAO,IAAIL,CAAAA,CAAMK,CAAAA,CAAM,KAAA,CAAO,IAAA,CAAK,QAAQ,CAChF,CAKA,IAAIA,CAAAA,CAAsB,CACxB,YAAK,kBAAA,CAAmBA,CAAK,CAAA,CACtB,IAAA,CAAK,KAAA,EAASA,CAAAA,CAAM,MAAQ,IAAA,CAAO,IAAIL,CAAAA,CAAMK,CAAAA,CAAM,KAAA,CAAO,IAAA,CAAK,QAAQ,CAChF,CAKA,KAAA,CAAM4B,CAAAA,CAAkBC,CAAAA,CAAyB,CAC/C,OAAO,IAAA,CAAK,GAAA,CAAID,CAAQ,CAAA,CAAE,GAAA,CAAIC,CAAQ,CACxC,CAKA,OAAA,EAAkB,CAChB,OAAO,IAAA,CAAK,KACd,CAKA,SAAA,CAAUC,CAAAA,CAAaC,CAAAA,CAAsB,CAC3C,OAAA,IAAA,CAAK,kBAAA,CAAmBD,CAAG,CAAA,CAC3B,IAAA,CAAK,kBAAA,CAAmBC,CAAG,CAAA,CACpB,IAAA,CAAK,OAASD,CAAAA,CAAI,KAAA,EAAS,KAAK,KAAA,EAASC,CAAAA,CAAI,KACtD,CAKA,OAAO,GAAA,CAAIC,CAAAA,CAAyB,CAClC,GAAIA,EAAO,MAAA,GAAW,CAAA,CACpB,MAAM,IAAI,KAAA,CAAM,wBAAwB,EAE1C,IAAMnC,CAAAA,CAAWmC,CAAAA,CAAO,CAAC,CAAA,CAAE,QAAA,CACrBtB,EAAQsB,CAAAA,CAAO,MAAA,CAAO,CAACrB,CAAAA,CAAKsB,CAAAA,GAAM,CACtC,GAAIA,CAAAA,CAAE,QAAA,CAAS,IAAA,GAASpC,CAAAA,CAAS,IAAA,CAC/B,MAAM,IAAI,KAAA,CAAM,iCAAiC,CAAA,CAEnD,OAAOc,CAAAA,CAAMsB,CAAAA,CAAE,KACjB,CAAA,CAAG,CAAC,CAAA,CACJ,OAAO,IAAItC,CAAAA,CAAMe,EAAOb,CAAQ,CAClC,CAKA,OAAO,OAAA,CAAQmC,EAAyB,CACtC,GAAIA,CAAAA,CAAO,MAAA,GAAW,CAAA,CACpB,MAAM,IAAI,KAAA,CAAM,mCAAmC,CAAA,CAErD,OAAOA,CAAAA,CAAO,MAAA,CAAO,CAACF,CAAAA,CAAKG,CAAAA,GAAOA,CAAAA,CAAE,KAAA,CAAQH,CAAAA,CAAI,KAAA,CAAQG,EAAIH,CAAI,CAClE,CAKA,OAAO,OAAA,CAAQE,CAAAA,CAAyB,CACtC,GAAIA,CAAAA,CAAO,MAAA,GAAW,CAAA,CACpB,MAAM,IAAI,MAAM,mCAAmC,CAAA,CAErD,OAAOA,CAAAA,CAAO,MAAA,CAAO,CAACD,EAAKE,CAAAA,GAAOA,CAAAA,CAAE,KAAA,CAAQF,CAAAA,CAAI,KAAA,CAAQE,CAAAA,CAAIF,CAAI,CAClE,CAKA,OAAO,OAAA,CAAQC,CAAAA,CAAyB,CACtC,GAAIA,CAAAA,CAAO,MAAA,GAAW,CAAA,CACpB,MAAM,IAAI,MAAM,yCAAyC,CAAA,CAG3D,OADcrC,CAAAA,CAAM,GAAA,CAAIqC,CAAM,EACjB,MAAA,CAAOA,CAAAA,CAAO,MAAM,CACnC,CAKA,MAAA,EAA8C,CAC5C,OAAO,CACL,KAAA,CAAO,IAAA,CAAK,KAAA,CACZ,QAAA,CAAU,KAAK,QAAA,CAAS,IAC1B,CACF,CAKA,QAAA,EAAmB,CACjB,OAAO,IAAA,CAAK,MAAA,EACd,CAEQ,aAAA,CAAcE,CAAAA,CAAwB,CAC5C,IAAM/B,CAAAA,CAAkB,EAAC,CACrBU,CAAAA,CAAYqB,CAAAA,CAEhB,KAAOrB,CAAAA,CAAU,MAAA,CAAS,GACxBV,CAAAA,CAAM,OAAA,CAAQU,EAAU,KAAA,CAAM,EAAE,CAAC,CAAA,CACjCA,CAAAA,CAAYA,CAAAA,CAAU,MAAM,CAAA,CAAG,EAAE,CAAA,CAGnC,OAAIA,CAAAA,EACFV,CAAAA,CAAM,QAAQU,CAAS,CAAA,CAGlBV,CAAAA,CAAM,IAAA,CAAK,IAAA,CAAK,QAAA,CAAS,kBAAkB,CACpD,CAEQ,kBAAA,CAAmBH,CAAAA,CAAqB,CAC9C,GAAI,KAAK,QAAA,CAAS,IAAA,GAASA,CAAAA,CAAM,QAAA,CAAS,IAAA,CACxC,MAAM,IAAI,KAAA,CACR,CAAA,oDAAA,EAAuD,IAAA,CAAK,QAAA,CAAS,IAAI,CAAA,KAAA,EAAQA,EAAM,QAAA,CAAS,IAAI,CAAA,CACtG,CAEJ,CACF","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"]}
@@ -163,6 +163,57 @@ declare class Money implements IMoney {
163
163
  * Negate the value
164
164
  */
165
165
  negate(): Money;
166
+ /**
167
+ * Calculate a percentage of this money
168
+ * @example money.percentage(10) returns 10% of the amount
169
+ */
170
+ percentage(percent: number): Money;
171
+ /**
172
+ * Add a percentage to this money
173
+ * @example money.addPercentage(19) adds 19% (like tax)
174
+ */
175
+ addPercentage(percent: number): Money;
176
+ /**
177
+ * Subtract a percentage from this money
178
+ * @example money.subtractPercentage(10) subtracts 10% (like discount)
179
+ */
180
+ subtractPercentage(percent: number): Money;
181
+ /**
182
+ * Get the minimum of this and another money value
183
+ */
184
+ min(other: IMoney): Money;
185
+ /**
186
+ * Get the maximum of this and another money value
187
+ */
188
+ max(other: IMoney): Money;
189
+ /**
190
+ * Clamp this money between a minimum and maximum
191
+ */
192
+ clamp(minValue: IMoney, maxValue: IMoney): Money;
193
+ /**
194
+ * Get cents (smallest unit)
195
+ */
196
+ toCents(): number;
197
+ /**
198
+ * Check if within a range (inclusive)
199
+ */
200
+ isBetween(min: IMoney, max: IMoney): boolean;
201
+ /**
202
+ * Sum multiple money values
203
+ */
204
+ static sum(values: IMoney[]): Money;
205
+ /**
206
+ * Get the minimum from an array of money values
207
+ */
208
+ static minimum(values: IMoney[]): Money;
209
+ /**
210
+ * Get the maximum from an array of money values
211
+ */
212
+ static maximum(values: IMoney[]): Money;
213
+ /**
214
+ * Calculate the average of money values
215
+ */
216
+ static average(values: IMoney[]): Money;
166
217
  /**
167
218
  * Convert to JSON-serializable object
168
219
  */