@f-o-t/money 1.1.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/README.md +689 -0
- package/dist/index.d.ts +904 -0
- package/dist/index.js +521 -0
- package/dist/operators/index.d.ts +69 -0
- package/dist/operators/index.js +39 -0
- package/dist/shared/chunk-jf6gzvp1.js +852 -0
- package/package.json +72 -0
package/README.md
ADDED
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
# @f-o-t/money
|
|
2
|
+
|
|
3
|
+
Type-safe money handling library with BigInt precision and ISO 4217 currency support.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@f-o-t/money)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **Precision-First**: Uses BigInt for exact arithmetic, eliminating floating-point errors
|
|
11
|
+
- **Type Safety**: Full TypeScript support with Zod schema validation
|
|
12
|
+
- **ISO 4217**: Built-in support for all standard currencies with correct decimal places
|
|
13
|
+
- **Immutable**: All operations return new instances, preventing accidental mutations
|
|
14
|
+
- **Framework Agnostic**: Works with any JavaScript/TypeScript project
|
|
15
|
+
- **Banker's Rounding**: IEEE 754 compliant rounding for fair financial calculations
|
|
16
|
+
- **Rich API**: Comprehensive operations for arithmetic, comparison, allocation, and aggregation
|
|
17
|
+
- **Locale Support**: Format and parse amounts in any locale
|
|
18
|
+
- **Extensible**: Register custom currencies for specialized use cases
|
|
19
|
+
- **Condition Evaluator**: Built-in operators for rule-based systems
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# npm
|
|
25
|
+
npm install @f-o-t/money
|
|
26
|
+
|
|
27
|
+
# bun
|
|
28
|
+
bun add @f-o-t/money
|
|
29
|
+
|
|
30
|
+
# yarn
|
|
31
|
+
yarn add @f-o-t/money
|
|
32
|
+
|
|
33
|
+
# pnpm
|
|
34
|
+
pnpm add @f-o-t/money
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { of, add, multiply, format } from "@f-o-t/money";
|
|
41
|
+
|
|
42
|
+
// Create money values
|
|
43
|
+
const price = of("19.99", "USD");
|
|
44
|
+
const quantity = 3;
|
|
45
|
+
|
|
46
|
+
// Calculate total
|
|
47
|
+
const subtotal = multiply(price, quantity); // $59.97
|
|
48
|
+
const tax = multiply(subtotal, 0.08); // $4.80
|
|
49
|
+
const total = add(subtotal, tax); // $64.77
|
|
50
|
+
|
|
51
|
+
// Format for display
|
|
52
|
+
console.log(format(total, "en-US")); // "$64.77"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Core Concepts
|
|
56
|
+
|
|
57
|
+
### Money Object
|
|
58
|
+
|
|
59
|
+
The `Money` type represents a monetary value with three properties:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
type Money = {
|
|
63
|
+
amount: bigint; // Amount in minor units (e.g., cents)
|
|
64
|
+
currency: string; // ISO 4217 currency code
|
|
65
|
+
scale: number; // Number of decimal places
|
|
66
|
+
};
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Important**: Money objects are immutable. All operations return new instances.
|
|
70
|
+
|
|
71
|
+
### Precision
|
|
72
|
+
|
|
73
|
+
Uses BigInt internally to avoid floating-point errors:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { of, add, toDecimal } from "@f-o-t/money";
|
|
77
|
+
|
|
78
|
+
const a = of("0.1", "USD");
|
|
79
|
+
const b = of("0.2", "USD");
|
|
80
|
+
const result = add(a, b);
|
|
81
|
+
|
|
82
|
+
console.log(toDecimal(result)); // "0.30" ✓ (not 0.30000000000000004)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Currency Support
|
|
86
|
+
|
|
87
|
+
All ISO 4217 currencies are supported with correct decimal places:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { of } from "@f-o-t/money";
|
|
91
|
+
|
|
92
|
+
const usd = of("10.50", "USD"); // 2 decimal places
|
|
93
|
+
const jpy = of("1050", "JPY"); // 0 decimal places
|
|
94
|
+
const kwd = of("10.500", "KWD"); // 3 decimal places
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## API Reference
|
|
98
|
+
|
|
99
|
+
### Factory Functions
|
|
100
|
+
|
|
101
|
+
Create money values from various inputs:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { of, ofRounded, fromMinorUnits, fromMajorUnits, zero } from "@f-o-t/money";
|
|
105
|
+
|
|
106
|
+
// From major units (dollars, euros, etc.)
|
|
107
|
+
const money1 = of("123.45", "USD");
|
|
108
|
+
const money2 = of(123.45, "USD"); // Also accepts numbers
|
|
109
|
+
const money3 = fromMajorUnits("123.45", "USD"); // Alias for of()
|
|
110
|
+
|
|
111
|
+
// From minor units (cents, pence, etc.)
|
|
112
|
+
const money4 = fromMinorUnits(12345, "USD"); // $123.45
|
|
113
|
+
const money5 = fromMinorUnits(12345n, "USD"); // Also accepts BigInt
|
|
114
|
+
|
|
115
|
+
// Zero value
|
|
116
|
+
const money6 = zero("USD"); // $0.00
|
|
117
|
+
|
|
118
|
+
// Handle excess decimal places
|
|
119
|
+
const truncated = of("10.999", "USD"); // $10.99 (truncated)
|
|
120
|
+
const rounded = of("10.999", "USD", "round"); // $11.00 (rounded)
|
|
121
|
+
const rounded2 = ofRounded("10.999", "USD"); // $11.00 (convenience function)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Arithmetic Operations
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { add, subtract, multiply, divide, percentage, negate, absolute } from "@f-o-t/money";
|
|
128
|
+
|
|
129
|
+
const a = of("100.00", "USD");
|
|
130
|
+
const b = of("25.50", "USD");
|
|
131
|
+
|
|
132
|
+
add(a, b); // $125.50
|
|
133
|
+
subtract(a, b); // $74.50
|
|
134
|
+
multiply(a, 1.5); // $150.00 - accepts numbers
|
|
135
|
+
multiply(a, "1.5"); // $150.00 - strings for precision
|
|
136
|
+
divide(a, 4); // $25.00
|
|
137
|
+
percentage(a, 15); // $15.00 - 15% of $100
|
|
138
|
+
negate(a); // -$100.00
|
|
139
|
+
absolute(of("-50", "USD")); // $50.00
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Note**: Division uses banker's rounding (round half to even) for fair distribution.
|
|
143
|
+
|
|
144
|
+
### Comparison Operations
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import {
|
|
148
|
+
equals,
|
|
149
|
+
greaterThan,
|
|
150
|
+
greaterThanOrEqual,
|
|
151
|
+
lessThan,
|
|
152
|
+
lessThanOrEqual,
|
|
153
|
+
isPositive,
|
|
154
|
+
isNegative,
|
|
155
|
+
isZero,
|
|
156
|
+
compare
|
|
157
|
+
} from "@f-o-t/money";
|
|
158
|
+
|
|
159
|
+
const a = of("100.00", "USD");
|
|
160
|
+
const b = of("50.00", "USD");
|
|
161
|
+
|
|
162
|
+
equals(a, b); // false
|
|
163
|
+
greaterThan(a, b); // true
|
|
164
|
+
greaterThanOrEqual(a, a); // true
|
|
165
|
+
lessThan(b, a); // true
|
|
166
|
+
lessThanOrEqual(a, a); // true
|
|
167
|
+
|
|
168
|
+
isPositive(a); // true
|
|
169
|
+
isNegative(of("-10", "USD")); // true
|
|
170
|
+
isZero(zero("USD")); // true
|
|
171
|
+
|
|
172
|
+
compare(a, b); // 1 (a > b)
|
|
173
|
+
compare(b, a); // -1 (b < a)
|
|
174
|
+
compare(a, a); // 0 (a === b)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Note**: All comparison operations throw `CurrencyMismatchError` if currencies differ.
|
|
178
|
+
|
|
179
|
+
### Allocation
|
|
180
|
+
|
|
181
|
+
Distribute money amounts proportionally with exact precision:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
import { allocate, split } from "@f-o-t/money";
|
|
185
|
+
|
|
186
|
+
// Allocate by ratios (e.g., revenue sharing)
|
|
187
|
+
const revenue = of("100.00", "USD");
|
|
188
|
+
const shares = allocate(revenue, [60, 25, 15]);
|
|
189
|
+
// shares[0]: $60.00 (60%)
|
|
190
|
+
// shares[1]: $25.00 (25%)
|
|
191
|
+
// shares[2]: $15.00 (15%)
|
|
192
|
+
|
|
193
|
+
// Handle remainders fairly using largest remainder method
|
|
194
|
+
const amount = of("10.00", "USD");
|
|
195
|
+
const parts = allocate(amount, [1, 1, 1]); // Split in thirds
|
|
196
|
+
// parts[0]: $3.34
|
|
197
|
+
// parts[1]: $3.33
|
|
198
|
+
// parts[2]: $3.33
|
|
199
|
+
// Total: $10.00 ✓
|
|
200
|
+
|
|
201
|
+
// Split evenly into N parts
|
|
202
|
+
const total = of("100.00", "USD");
|
|
203
|
+
const quarters = split(total, 4);
|
|
204
|
+
// Each part: $25.00
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Aggregation
|
|
208
|
+
|
|
209
|
+
Perform calculations on arrays of money values:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import { sum, sumOrZero, min, max, average, median } from "@f-o-t/money";
|
|
213
|
+
|
|
214
|
+
const amounts = [
|
|
215
|
+
of("10.00", "USD"),
|
|
216
|
+
of("20.00", "USD"),
|
|
217
|
+
of("30.00", "USD")
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
sum(amounts); // $60.00
|
|
221
|
+
sumOrZero([], "USD"); // $0.00 - safe for empty arrays
|
|
222
|
+
min(amounts); // $10.00
|
|
223
|
+
max(amounts); // $30.00
|
|
224
|
+
average(amounts); // $20.00
|
|
225
|
+
median(amounts); // $20.00
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Formatting
|
|
229
|
+
|
|
230
|
+
Display money values in human-readable formats:
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
import { format, formatCompact, formatAmount, toDecimal } from "@f-o-t/money";
|
|
234
|
+
|
|
235
|
+
const money = of("1234.56", "USD");
|
|
236
|
+
|
|
237
|
+
// Standard formatting
|
|
238
|
+
format(money, "en-US"); // "$1,234.56"
|
|
239
|
+
format(money, "pt-BR"); // "US$ 1.234,56"
|
|
240
|
+
format(money, "ja-JP"); // "$1,234.56"
|
|
241
|
+
|
|
242
|
+
// Compact notation (for large amounts)
|
|
243
|
+
const large = of("1234567.89", "USD");
|
|
244
|
+
formatCompact(large, "en-US"); // "$1.2M"
|
|
245
|
+
|
|
246
|
+
// Amount only (no currency symbol)
|
|
247
|
+
formatAmount(money, "en-US"); // "1,234.56"
|
|
248
|
+
|
|
249
|
+
// Hide currency symbol
|
|
250
|
+
format(money, "en-US", { hideSymbol: true }); // "1,234.56"
|
|
251
|
+
|
|
252
|
+
// Plain decimal string
|
|
253
|
+
toDecimal(money); // "1234.56"
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Parsing
|
|
257
|
+
|
|
258
|
+
Parse formatted strings back into money values:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import { parse } from "@f-o-t/money";
|
|
262
|
+
|
|
263
|
+
// US format
|
|
264
|
+
parse("$1,234.56", "en-US", "USD");
|
|
265
|
+
|
|
266
|
+
// Brazilian format
|
|
267
|
+
parse("R$ 1.234,56", "pt-BR", "BRL");
|
|
268
|
+
|
|
269
|
+
// Negative amounts (supports parentheses or minus)
|
|
270
|
+
parse("($1,234.56)", "en-US", "USD"); // -$1,234.56
|
|
271
|
+
parse("-$1,234.56", "en-US", "USD"); // -$1,234.56
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Serialization
|
|
275
|
+
|
|
276
|
+
Convert money to and from various formats:
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
import {
|
|
280
|
+
toJSON,
|
|
281
|
+
fromJSON,
|
|
282
|
+
toDatabase,
|
|
283
|
+
fromDatabase,
|
|
284
|
+
serialize,
|
|
285
|
+
deserialize,
|
|
286
|
+
toMinorUnits,
|
|
287
|
+
toMinorUnitsBigInt,
|
|
288
|
+
toMajorUnits,
|
|
289
|
+
toMajorUnitsString,
|
|
290
|
+
toMinorUnitsString
|
|
291
|
+
} from "@f-o-t/money";
|
|
292
|
+
|
|
293
|
+
const money = of("123.45", "USD");
|
|
294
|
+
|
|
295
|
+
// JSON (for APIs)
|
|
296
|
+
const json = toJSON(money);
|
|
297
|
+
// { amount: "123.45", currency: "USD" }
|
|
298
|
+
fromJSON(json); // Recreates Money object
|
|
299
|
+
|
|
300
|
+
// Database storage (same as JSON)
|
|
301
|
+
const db = toDatabase(money);
|
|
302
|
+
// { amount: "123.45", currency: "USD" }
|
|
303
|
+
fromDatabase(db);
|
|
304
|
+
|
|
305
|
+
// String serialization
|
|
306
|
+
serialize(money); // "123.45 USD"
|
|
307
|
+
deserialize("123.45 USD"); // Recreates Money object
|
|
308
|
+
|
|
309
|
+
// Unit conversions
|
|
310
|
+
toMinorUnits(money); // 12345 (number)
|
|
311
|
+
toMinorUnitsBigInt(money); // 12345n (BigInt)
|
|
312
|
+
toMajorUnitsString(money); // "123.45" (string, precision-safe)
|
|
313
|
+
toMajorUnits(money); // 123.45 (number, deprecated - may lose precision)
|
|
314
|
+
toMinorUnitsString(money); // "12345" (string)
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Currency Registry
|
|
318
|
+
|
|
319
|
+
Access and manage currency information:
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
import {
|
|
323
|
+
getCurrency,
|
|
324
|
+
registerCurrency,
|
|
325
|
+
hasCurrency,
|
|
326
|
+
getAllCurrencies,
|
|
327
|
+
clearCustomCurrencies,
|
|
328
|
+
ISO_4217_CURRENCIES
|
|
329
|
+
} from "@f-o-t/money";
|
|
330
|
+
|
|
331
|
+
// Get currency info
|
|
332
|
+
const usd = getCurrency("USD");
|
|
333
|
+
// {
|
|
334
|
+
// code: "USD",
|
|
335
|
+
// numericCode: 840,
|
|
336
|
+
// name: "US Dollar",
|
|
337
|
+
// decimalPlaces: 2,
|
|
338
|
+
// symbol: "$"
|
|
339
|
+
// }
|
|
340
|
+
|
|
341
|
+
// Check if currency exists
|
|
342
|
+
hasCurrency("USD"); // true
|
|
343
|
+
hasCurrency("XXX"); // false
|
|
344
|
+
|
|
345
|
+
// Register custom currency
|
|
346
|
+
registerCurrency({
|
|
347
|
+
code: "BTC",
|
|
348
|
+
numericCode: 0,
|
|
349
|
+
name: "Bitcoin",
|
|
350
|
+
decimalPlaces: 8,
|
|
351
|
+
symbol: "₿"
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// Get all currencies
|
|
355
|
+
const all = getAllCurrencies();
|
|
356
|
+
|
|
357
|
+
// Clear custom currencies (useful for testing)
|
|
358
|
+
clearCustomCurrencies();
|
|
359
|
+
|
|
360
|
+
// Access raw ISO 4217 data
|
|
361
|
+
console.log(ISO_4217_CURRENCIES.USD);
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Zod Schemas
|
|
365
|
+
|
|
366
|
+
Validate money data with Zod schemas:
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
import {
|
|
370
|
+
MoneySchema,
|
|
371
|
+
MoneyInputSchema,
|
|
372
|
+
CurrencyCodeSchema,
|
|
373
|
+
DatabaseMoneySchema,
|
|
374
|
+
AllocationRatiosSchema,
|
|
375
|
+
FormatOptionsSchema
|
|
376
|
+
} from "@f-o-t/money";
|
|
377
|
+
|
|
378
|
+
// Validate JSON input
|
|
379
|
+
const result = MoneySchema.safeParse({
|
|
380
|
+
amount: "123.45",
|
|
381
|
+
currency: "USD"
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
if (result.success) {
|
|
385
|
+
const money = fromJSON(result.data);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// User input (accepts strings or numbers)
|
|
389
|
+
MoneyInputSchema.parse({
|
|
390
|
+
amount: 123.45, // number is OK
|
|
391
|
+
currency: "USD"
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
// Currency code validation
|
|
395
|
+
CurrencyCodeSchema.parse("USD"); // ✓
|
|
396
|
+
CurrencyCodeSchema.parse("usd"); // ✗ - must be uppercase
|
|
397
|
+
|
|
398
|
+
// Allocation ratios
|
|
399
|
+
AllocationRatiosSchema.parse([60, 25, 15]); // ✓
|
|
400
|
+
AllocationRatiosSchema.parse([]); // ✗ - empty array
|
|
401
|
+
AllocationRatiosSchema.parse([-1, 1]); // ✗ - negative values
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Error Handling
|
|
405
|
+
|
|
406
|
+
The library provides specific error types:
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
import {
|
|
410
|
+
MoneyError,
|
|
411
|
+
CurrencyMismatchError,
|
|
412
|
+
ScaleMismatchError,
|
|
413
|
+
InvalidAmountError,
|
|
414
|
+
DivisionByZeroError,
|
|
415
|
+
UnknownCurrencyError,
|
|
416
|
+
OverflowError
|
|
417
|
+
} from "@f-o-t/money";
|
|
418
|
+
|
|
419
|
+
try {
|
|
420
|
+
add(of("10", "USD"), of("10", "EUR"));
|
|
421
|
+
} catch (error) {
|
|
422
|
+
if (error instanceof CurrencyMismatchError) {
|
|
423
|
+
console.log("Cannot add different currencies");
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Scale mismatch (same currency with different decimal places)
|
|
428
|
+
try {
|
|
429
|
+
const a = of("10.00", "USD");
|
|
430
|
+
const b = { amount: 1000n, currency: "USD", scale: 4 }; // Invalid scale
|
|
431
|
+
add(a, b);
|
|
432
|
+
} catch (error) {
|
|
433
|
+
if (error instanceof ScaleMismatchError) {
|
|
434
|
+
console.log("Scale mismatch:", error.scaleA, "vs", error.scaleB);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// All error types extend MoneyError
|
|
439
|
+
try {
|
|
440
|
+
divide(of("10", "USD"), 0);
|
|
441
|
+
} catch (error) {
|
|
442
|
+
if (error instanceof MoneyError) {
|
|
443
|
+
console.log("Money operation failed:", error.message);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
## Advanced Usage
|
|
449
|
+
|
|
450
|
+
### Condition Evaluator Integration
|
|
451
|
+
|
|
452
|
+
Use money operators with the `@f-o-t/condition-evaluator` package for rule-based systems:
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
import { createEvaluator } from "@f-o-t/condition-evaluator";
|
|
456
|
+
import { moneyOperators } from "@f-o-t/money/operators";
|
|
457
|
+
|
|
458
|
+
const evaluator = createEvaluator({
|
|
459
|
+
operators: moneyOperators
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// Evaluate money conditions
|
|
463
|
+
const result = evaluator.evaluate(
|
|
464
|
+
{
|
|
465
|
+
type: "custom",
|
|
466
|
+
field: "transactionAmount",
|
|
467
|
+
operator: "money_gt",
|
|
468
|
+
value: { amount: "100.00", currency: "USD" }
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
data: {
|
|
472
|
+
transactionAmount: { amount: "150.00", currency: "USD" }
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
);
|
|
476
|
+
// result: true
|
|
477
|
+
|
|
478
|
+
// Available operators:
|
|
479
|
+
// - money_eq, money_neq
|
|
480
|
+
// - money_gt, money_gte, money_lt, money_lte
|
|
481
|
+
// - money_between
|
|
482
|
+
// - money_positive, money_negative, money_zero
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### Custom Currencies
|
|
486
|
+
|
|
487
|
+
Register currencies not in ISO 4217:
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
import { registerCurrency, of } from "@f-o-t/money";
|
|
491
|
+
|
|
492
|
+
// Register cryptocurrency
|
|
493
|
+
registerCurrency({
|
|
494
|
+
code: "BTC",
|
|
495
|
+
numericCode: 0,
|
|
496
|
+
name: "Bitcoin",
|
|
497
|
+
decimalPlaces: 8,
|
|
498
|
+
symbol: "₿",
|
|
499
|
+
subunitName: "satoshi"
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
// Now you can use it
|
|
503
|
+
const bitcoin = of("0.00123456", "BTC");
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### Assertions
|
|
507
|
+
|
|
508
|
+
Use assertions for type narrowing and validation:
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
import { assertSameCurrency, assertAllSameCurrency } from "@f-o-t/money";
|
|
512
|
+
|
|
513
|
+
function addMany(amounts: Money[]): Money {
|
|
514
|
+
assertAllSameCurrency(amounts);
|
|
515
|
+
// TypeScript now knows all amounts have the same currency
|
|
516
|
+
return sum(amounts);
|
|
517
|
+
}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
### Low-Level Utilities
|
|
521
|
+
|
|
522
|
+
For advanced use cases:
|
|
523
|
+
|
|
524
|
+
```typescript
|
|
525
|
+
import {
|
|
526
|
+
bankersRound,
|
|
527
|
+
EXTENDED_PRECISION,
|
|
528
|
+
PRECISION_FACTOR,
|
|
529
|
+
createMoney,
|
|
530
|
+
parseDecimalToMinorUnits,
|
|
531
|
+
minorUnitsToDecimal
|
|
532
|
+
} from "@f-o-t/money";
|
|
533
|
+
|
|
534
|
+
// Banker's rounding
|
|
535
|
+
bankersRound(150n, 100n); // 200n (rounds 1.5 to 2, even)
|
|
536
|
+
bankersRound(250n, 100n); // 200n (rounds 2.5 to 2, even)
|
|
537
|
+
|
|
538
|
+
// Extended precision for intermediate calculations
|
|
539
|
+
console.log(EXTENDED_PRECISION); // 1000000n
|
|
540
|
+
console.log(PRECISION_FACTOR); // 100n
|
|
541
|
+
|
|
542
|
+
// Direct money creation (bypasses validation)
|
|
543
|
+
createMoney(12345n, "USD", 2);
|
|
544
|
+
|
|
545
|
+
// Low-level parsing
|
|
546
|
+
parseDecimalToMinorUnits("123.45", 2); // 12345n
|
|
547
|
+
minorUnitsToDecimal(12345n, 2); // "123.45"
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
## Best Practices
|
|
551
|
+
|
|
552
|
+
### 1. Always Use Strings for Precision
|
|
553
|
+
|
|
554
|
+
```typescript
|
|
555
|
+
// Good - exact representation
|
|
556
|
+
const price = of("19.99", "USD");
|
|
557
|
+
const result = multiply(price, "1.08");
|
|
558
|
+
|
|
559
|
+
// Avoid - floating point errors may occur
|
|
560
|
+
const price = of(19.99, "USD");
|
|
561
|
+
const result = multiply(price, 1.08);
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### 2. Handle Currency Mismatches
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
import { CurrencyMismatchError } from "@f-o-t/money";
|
|
568
|
+
|
|
569
|
+
function safeAdd(a: Money, b: Money): Money | null {
|
|
570
|
+
try {
|
|
571
|
+
return add(a, b);
|
|
572
|
+
} catch (error) {
|
|
573
|
+
if (error instanceof CurrencyMismatchError) {
|
|
574
|
+
return null; // Or handle conversion
|
|
575
|
+
}
|
|
576
|
+
throw error;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
### 3. Use Allocation for Fair Distribution
|
|
582
|
+
|
|
583
|
+
```typescript
|
|
584
|
+
// Don't use division for splitting money
|
|
585
|
+
const total = of("10.00", "USD");
|
|
586
|
+
const bad = divide(total, 3); // Loses precision
|
|
587
|
+
|
|
588
|
+
// Use split instead
|
|
589
|
+
const good = split(total, 3); // Ensures total is preserved
|
|
590
|
+
const sum = good.reduce((acc, m) => add(acc, m));
|
|
591
|
+
// sum equals total ✓
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### 4. Store as Database-Friendly Format
|
|
595
|
+
|
|
596
|
+
```typescript
|
|
597
|
+
import { toDatabase, fromDatabase } from "@f-o-t/money";
|
|
598
|
+
|
|
599
|
+
// In your database model
|
|
600
|
+
interface Product {
|
|
601
|
+
id: string;
|
|
602
|
+
name: string;
|
|
603
|
+
price: { amount: string; currency: string };
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// When saving
|
|
607
|
+
const product: Product = {
|
|
608
|
+
id: "123",
|
|
609
|
+
name: "Widget",
|
|
610
|
+
price: toDatabase(of("19.99", "USD"))
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
// When loading
|
|
614
|
+
const price = fromDatabase(product.price);
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
### 5. Validate API Input with Zod
|
|
618
|
+
|
|
619
|
+
```typescript
|
|
620
|
+
import { MoneyInputSchema, fromJSON } from "@f-o-t/money";
|
|
621
|
+
import { z } from "zod";
|
|
622
|
+
|
|
623
|
+
const CreateProductSchema = z.object({
|
|
624
|
+
name: z.string(),
|
|
625
|
+
price: MoneyInputSchema
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
function createProduct(input: unknown) {
|
|
629
|
+
const validated = CreateProductSchema.parse(input);
|
|
630
|
+
const price = fromJSON({
|
|
631
|
+
amount: String(validated.price.amount),
|
|
632
|
+
currency: validated.price.currency
|
|
633
|
+
});
|
|
634
|
+
// Use price...
|
|
635
|
+
}
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
## Performance
|
|
639
|
+
|
|
640
|
+
The library is optimized for high-performance financial calculations:
|
|
641
|
+
|
|
642
|
+
- **10,000 Money creations**: < 500ms
|
|
643
|
+
- **10,000 additions**: < 200ms
|
|
644
|
+
- **10,000 comparisons**: < 100ms
|
|
645
|
+
- **10,000 formats (with caching)**: < 500ms
|
|
646
|
+
- **1,000 allocations**: < 500ms
|
|
647
|
+
|
|
648
|
+
All tests run on modern hardware with Bun runtime.
|
|
649
|
+
|
|
650
|
+
## TypeScript
|
|
651
|
+
|
|
652
|
+
Full TypeScript support with strict types:
|
|
653
|
+
|
|
654
|
+
```typescript
|
|
655
|
+
import type { Money, MoneyJSON, Currency, FormatOptions } from "@f-o-t/money";
|
|
656
|
+
|
|
657
|
+
// Money is the core type
|
|
658
|
+
const money: Money = of("100", "USD");
|
|
659
|
+
|
|
660
|
+
// MoneyJSON for API contracts
|
|
661
|
+
const json: MoneyJSON = { amount: "100.00", currency: "USD" };
|
|
662
|
+
|
|
663
|
+
// Currency metadata
|
|
664
|
+
const currency: Currency = getCurrency("USD");
|
|
665
|
+
|
|
666
|
+
// Format configuration
|
|
667
|
+
const options: FormatOptions = {
|
|
668
|
+
notation: "compact",
|
|
669
|
+
hideSymbol: true
|
|
670
|
+
};
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
## Contributing
|
|
674
|
+
|
|
675
|
+
Contributions are welcome! Please check the repository for guidelines.
|
|
676
|
+
|
|
677
|
+
## License
|
|
678
|
+
|
|
679
|
+
MIT License - see [LICENSE](./LICENSE) file for details.
|
|
680
|
+
|
|
681
|
+
## Credits
|
|
682
|
+
|
|
683
|
+
Built by the Finance Tracker team as part of the Montte NX monorepo.
|
|
684
|
+
|
|
685
|
+
## Links
|
|
686
|
+
|
|
687
|
+
- [GitHub Repository](https://github.com/F-O-T/montte-nx)
|
|
688
|
+
- [Issue Tracker](https://github.com/F-O-T/montte-nx/issues)
|
|
689
|
+
- [NPM Package](https://www.npmjs.com/package/@f-o-t/money)
|