@wentools/money
v0.1.2
Published
TypeScript money handling library
Maintainers
Readme
Money Library
Type-safe money handling for financial applications. No more floating-point errors.
Why You Need This
Working with money in JavaScript is dangerous:
// ❌ JavaScript's problems with money
0.1 + 0.2 // 0.30000000000000004 (precision error!)
const price = 10.5 // 10.5 what? Kronor? Ören? Ambiguous!
const total = price + '50' // "10.550" (silent string concatenation!)This library makes money handling safe and precise:
// ✅ With this library
const sek = money.for(SEK)
const price = sek.create.fromMinor(1050) // Explicitly 1050 ören = 10.50 kr
const total = sek.calculate.add(price, tax) // Precise calculation, no float errors
sek.format.standard(total) // "12,50 kr" - properly formattedQuick Start
import { money } from '@lib/money'
import { SEK } from '@lib/money/config/registry'
// Create a money API for your currency
const sek = money.for(SEK)
// Create money values
const price = sek.create.fromMinor(9999) // 99.99 kr (from ören)
const salary = sek.create.fromMajor(25000) // 25,000 kr (from kronor)
// Safe calculations - no floating-point errors!
const total = sek.calculate.add(price, salary)
if (total.isOk()) {
console.log(sek.format.standard(total.value)) // "25 099,99 kr"
}
// Format for display
sek.format.standard(price) // "99,99 kr"
sek.format.accounting(price) // "SEK 99.99"
sek.format.input(price) // "99.99" (for input fields)Core Concepts
1. Minor Units (The Secret to Precision)
We store money as integers in the smallest unit (ören, cents) to avoid floating-point errors:
// API sends/receives minor units
const apiResponse = { amountInMinorUnits: 50000 } // 500.00 kr
const money = sek.create.fromMinor(apiResponse.amountInMinorUnits)
// Convert back for API
const payload = {
amountInMinorUnits: sek.transform.toMinor(money.value),
}2. Type Safety
Branded types prevent mixing different units:
const minor: AmountInMinorUnits = 1050
const major: AmountInMajorUnits = 10.5
// minor + major // ❌ TypeScript error - can't mix units!3. Result Pattern (No Exceptions)
All operations return Result types for explicit error handling:
const result = sek.calculate.divide(amount, 0)
if (result.isErr()) {
console.log(result.error.type) // "DIVISION_BY_ZERO"
}Common Use Cases
Processing API Responses
// Backend sends amounts in minor units (ören)
const apiData = {
loanAmount: 50000000, // 500,000 kr
processingFee: 195000, // 1,950 kr
}
// Convert to Money objects
const loan = sek.create.fromMinor(apiData.loanAmount)
const fee = sek.create.fromMinor(apiData.processingFee)
// Calculate and display
if (loan.isOk() && fee.isOk()) {
const total = sek.calculate.add(loan.value, fee.value)
if (total.isOk()) {
console.log(sek.format.standard(total.value)) // "501 950,00 kr"
}
}Handling User Input
// User enters amount in a form
const userInput = '5000.50'
const amount = sek.create.fromMajor(parseFloat(userInput))
if (amount.isOk()) {
// Validate minimum amount
const minimum = sek.create.fromMajor(100)
if (minimum.isOk()) {
const isValid = sek.compare.isGreaterOrEqual(amount.value, minimum.value)
if (isValid.isOk() && isValid.value) {
// Send to API in minor units
const payload = {
amountInMinorUnits: sek.transform.toMinor(amount.value),
currency: 'SEK',
}
}
}
}Parsing Different Formats
import { PARSE_RULES } from '@lib/money'
// Swedish format: "1 234,56"
const swedishAmount = sek.parse.withRules(PARSE_RULES['sv-SE'])('1 234,56')
// Numeric format: "1234.56"
const numericAmount = sek.parse.numeric('1234.56')Available Operations
Create
fromMinor(1050)- Create from minor units (ören/cents)fromMajor(10.50)- Create from major units (kronor/dollars)zero()- Create zero value
Calculate
add(a, b)- Additionsubtract(a, b)- Subtractionmultiply(money, factor)- Multiplicationdivide(money, divisor)- Divisionpercentage(money, percent)- Calculate percentagesum(moneys[])- Sum array of money
Format
standard(money)- Locale-specific format: "1 234,56 kr"accounting(money)- Accounting format: "SEK 1,234.56"plain(money)- Plain number: "1234.56"input(money)- For input fields: "1234.56"compact(money)- Compact format: "1.2k kr"words(money)- Words: "one thousand two hundred kronor"
Compare
isEqual(a, b)- Check equalityisGreater(a, b)- Check if a > bisGreaterOrEqual(a, b)- Check if a ≥ bisLess(a, b)- Check if a < bisLessOrEqual(a, b)- Check if a ≤ bisNegative(money)- Check if negativeisZero(money)- Check if zero
Transform
toMinor(money)- Get minor units valuetoMajor(money)- Get major units valueabs(money)- Absolute valuenegate(money)- Negate valueroundToMajor(money)- Round to nearest major unit
What This Library Does NOT Do
This is a frontend-focused library. It does NOT handle:
- ❌ Currency conversion (backend provides rates)
- ❌ Interest calculations (backend business logic)
- ❌ Payment processing (backend handles transactions)
- ❌ Complex financial calculations (backend's domain)
This separation keeps the frontend focused on presentation and user interaction while the backend handles business logic.
Supported Currencies
import { SEK, USD, EUR, GBP, JPY, NOK, DKK } from '@lib/money/config/registry'Or define your own:
const customCurrency: CurrencyConfig = {
code: 'XXX',
decimals: 2,
minorToMajor: 100,
symbol: '¤',
// ... other config
}
const xxx = money.for(customCurrency)Error Handling
All operations return Result types. Always check for errors:
const result = sek.calculate.add(amount1, amount2)
if (result.isOk()) {
console.log('Success:', result.value)
} else {
console.error('Error:', result.error.type)
// Error types: CURRENCY_MISMATCH, DIVISION_BY_ZERO,
// CALCULATION_OVERFLOW, INVALID_FORMAT, etc.
}Installation
The library is already included in this project at @lib/money.
License
Internal library for this project.
