@thesmilingsloth/money-utils v1.0.0
@thesmilingsloth/money-utils
A robust, type-safe money handling utility for JavaScript/TypeScript applications with support for both fiat and cryptocurrency operations.
Overview
@thesmilingsloth/money-utils
is a comprehensive solution for handling monetary calculations in JavaScript/TypeScript applications. It provides precise decimal arithmetic, supports both fiat and cryptocurrencies, and ensures type safety throughout your financial operations.
Features
- 🎯 Type-safe: Written in TypeScript with comprehensive type definitions
- 💰 Precise calculations: Uses decimal.js for accurate decimal arithmetic
- 🌍 Internationalization: Built-in support for formatting and localization
- 🔄 Currency conversion: Support for both fiat and cryptocurrencies
- 🛡️ Immutable operations: All operations return new instances
- 🎨 Customizable: Configurable decimal places, rounding modes, and formatting options
- 🌲 Tree-shakeable: Import only what you need
- 0️⃣ Minimal dependencies: Only includes decimal.js for precise calculations
Table of Contents
- Installation
- Quick Start
- Core Concepts
- Default Options & Configurations
- Advanced Usage
- API Documentation
- Contributing
- Testing
- License
Installation
# Using npm
npm install @thesmilingsloth/money-utils
# Using yarn
yarn add @thesmilingsloth/money-utils
# Using pnpm
pnpm add @thesmilingsloth/money-utils
Note:
decimal.js
is included as a dependency and will be automatically installed. No need to install it separately.
Quick Start
import { Money, Currency } from "@thesmilingsloth/money-utils";
// Create money instances
const price = new Money("99.99", "USD");
const quantity = 2;
// Perform calculations
const subtotal = price.multiply(quantity);
const tax = subtotal.multiply("0.2"); // 20% tax
const total = subtotal.add(tax);
// Format output
console.log(total.toString()); // "$239.98"
console.log(total.toLocaleString("de-DE")); // "239,98 $"
// Work with different currencies
const euro = new Money("100", "EUR");
const yen = new Money("10000", "JPY");
// Compare amounts (same currency)
console.log(price.lessThan(total)); // true
Core Concepts
Money Class
The Money
class is immutable and handles all monetary operations:
// Basic operations
const amount = new Money("100.50", "USD");
const doubled = amount.multiply(2);
const withTax = doubled.add(doubled.multiply("0.1")); // Add 10% tax
// Comparison
const isExpensive = withTax.greaterThan(new Money("200", "USD"));
// Formatting
console.log(withTax.toString()); // "$221.10"
console.log(withTax.toLocaleString("ja-JP")); // "¥221.10"
Currency Management
Manage currencies using the Currency
class:
// Built-in currencies
const usd = Currency.USD;
const eur = Currency.EUR;
const btc = Currency.BTC;
// Custom currency registration
Currency.register({
name: "Custom Token",
code: "CTK",
symbol: "⚡",
symbolPosition: "prefix",
decimals: 6,
minorUnits: "1000000",
decimalSeparator: ".",
thousandsSeparator: ",",
isCrypto: true,
});
// Use custom currency
const tokenAmount = new Money("1000.123456", "CTK");
Default Options & Configurations
Money Options
When creating a new Money
instance, you can provide options to customize its behavior. Here are the default values:
const defaultMoneyOptions = {
decimals: undefined, // Uses the currency's default decimals
displayDecimals: undefined, // Uses the currency's default decimals
roundingMode: ROUNDING_MODE.ROUND_HALF_UP,
symbol: undefined, // Uses the currency's default symbol
};
// Example with custom options
const amount = new Money("100.555", "USD", {
decimals: 3, // Override default decimal places
displayDecimals: 2, // Show only 2 decimals in formatting
roundingMode: ROUNDING_MODE.ROUND_DOWN,
symbol: "USD", // Custom symbol instead of "$"
});
Currency Defaults
Built-in currencies come with pre-configured defaults. Here are some examples:
// USD Configuration
const USDDefaults = {
name: "US Dollar",
code: "USD",
symbol: "$",
symbolPosition: "prefix",
decimals: 2,
minorUnits: "100",
decimalSeparator: ".",
thousandsSeparator: ",",
isCrypto: false,
};
// BTC Configuration
const BTCDefaults = {
name: "Bitcoin",
code: "BTC",
symbol: "₿",
symbolPosition: "prefix",
decimals: 8,
minorUnits: "100000000",
decimalSeparator: ".",
thousandsSeparator: ",",
isCrypto: true,
};
// Example of using defaults vs custom options
const defaultUSD = new Money("100", "USD"); // Uses USD defaults
const customUSD = new Money("100", "USD", {
decimals: 4, // Override default 2 decimals
symbol: "US$", // Override default "$" symbol
});
Formatting Defaults
The library uses the following default formatting behavior:
// Default toString() behavior
const amount = new Money("1234.56", "USD");
console.log(amount.toString()); // "$1,234.56"
// Default toLocaleString() options
console.log(amount.toLocaleString()); // Uses browser's default locale
console.log(amount.toLocaleString("en-US")); // "$1,234.56"
console.log(amount.toLocaleString("de-DE")); // "1.234,56 $"
// Custom format options
console.log(
amount.toLocaleString("en-US", {
style: "currency",
currencyDisplay: "name", // "1,234.56 US dollars"
})
);
Rounding Defaults
// Default rounding mode is ROUND_HALF_UP
const amount = new Money("100.555", "USD");
console.log(amount.toString()); // "$100.56"
// Different rounding modes
const roundDown = new Money("100.555", "USD", {
roundingMode: ROUNDING_MODE.ROUND_DOWN,
});
console.log(roundDown.toString()); // "$100.55"
const roundUp = new Money("100.555", "USD", {
roundingMode: ROUNDING_MODE.ROUND_UP,
});
console.log(roundUp.toString()); // "$100.56"
Advanced Usage
Rounding Modes
import { Money, ROUNDING_MODE } from "@thesmilingsloth/money-utils";
const amount = new Money("100.555", "USD", {
roundingMode: ROUNDING_MODE.ROUND_HALF_UP,
decimals: 2,
});
// Different rounding behaviors
console.log(amount.toString()); // "$100.56"
console.log(amount.round(1).toString()); // "$100.60"
Allocation
Split amounts while handling remainders:
const total = new Money("100", "USD");
// Split by ratios
const shares = total.allocate([2, 3, 5]); // 20:30:50 ratio
console.log(shares.map((s) => s.toString()));
// ["$20.00", "$30.00", "$50.00"]
// Equal distribution
const equalShares = total.allocate([1, 1, 1]);
// ["$33.34", "$33.33", "$33.33"]
Formatting
Flexible formatting options:
const amount = new Money("1234567.89", "USD");
// Basic formatting
console.log(amount.toString()); // "$1,234,567.89"
console.log(amount.formattedValue()); // "1,234,567.89"
console.log(amount.formattedValueWithSymbol()); // "$1,234,567.89"
// Localized formatting
console.log(amount.toLocaleString("en-US")); // "$1,234,567.89"
console.log(amount.toLocaleString("de-DE")); // "1.234.567,89 $"
console.log(amount.toLocaleString("ja-JP")); // "¥1,234,567.89"
// Custom format options
console.log(
amount.toLocaleString("en-US", {
style: "currency",
currencyDisplay: "name",
})
); // "1,234,567.89 US dollars"
Comparison Operations
Compare money instances:
const amount1 = new Money("100.00", "USD");
const amount2 = new Money("200.00", "USD");
// Simple comparisons
const isEqual = amount1.equals(amount2); // false
const isGreater = amount1.greaterThan(amount2); // false
const isLess = amount1.lessThan(amount2); // true
// Combined comparison
const comparison = amount1.compare(amount2);
console.log(comparison); // { equal: false, greaterThan: false, lessThan: true }
// Value checks
const isZero = amount1.isZero(); // false
const isPositive = amount1.isPositive(); // true
const isNegative = amount1.isNegative(); // false
Value Extraction
Access the underlying values:
const amount = new Money("1234.56", "USD");
// Get raw values
console.log(amount.value()); // "1234.56"
console.log(amount.absoluteValue()); // "1234.56"
console.log(amount.negatedValue()); // "-1234.56"
// Get formatted values
console.log(amount.formattedValue()); // "1,234.56"
console.log(amount.formattedValueWithSymbol()); // "$1,234.56"
Serialization
Convert to and from JSON:
const amount = new Money("1234.56", "USD");
// Convert to JSON
const json = amount.toJSON();
console.log(json);
// {
// currency: "USD",
// symbol: "$",
// decimals: 2,
// displayDecimals: 2,
// value: "1234.56",
// prettyValue: "$1,234.56",
// negative: false
// }
// Use in JSON.stringify()
const jsonString = JSON.stringify({ price: amount });
API Documentation
Money Class API
Constructor
constructor(value: string | number, currency: string, options?: MoneyOptions)
Creates a new Money instance.
Parameters:
value
: The monetary value (string or number)currency
: Currency code (e.g., "USD", "EUR")options
: Optional configuration objectinterface MoneyOptions { symbol?: string; // Custom currency symbol decimals?: number; // Decimal places for calculations displayDecimals?: number; // Decimal places for display roundingMode?: RoundingMode; // Rounding mode for calculations }
Static Methods
static zero(currency: string, options?: MoneyOptions): Money
Creates a zero-value Money instance.
static from(value: string | number, currency: string, options?: MoneyOptions): Money
Alternative constructor method.
Arithmetic Methods
add(other: Money): Money // Add two Money instances
subtract(other: Money): Money // Subtract Money instances
multiply(factor: number): Money // Multiply by a number
divide(divisor: number): Money // Divide by a number
allocate(ratios: number[]): Money[] // Split into proportional parts
Comparison Methods
equals(other: Money): boolean
greaterThan(other: Money): boolean
lessThan(other: Money): boolean
greaterThanOrEqual(other: Money): boolean
lessThanOrEqual(other: Money): boolean
compare(other: Money): MoneyComparisonResult
Value Methods
value(): string // Raw value as string
absoluteValue(): string // Absolute value
negatedValue(): string // Negated value
isZero(): boolean // Check if zero
isPositive(): boolean // Check if positive
isNegative(): boolean // Check if negative
Formatting Methods
toString(): string // Format with symbol
formattedValue(): string // Format without symbol
formattedValueWithSymbol(): string // Format with symbol
toLocaleString(locale?: string, options?: Intl.NumberFormatOptions): string
Currency Class API
Static Methods
static register(currency: CurrencyConfig | CurrencyConfig[]): void
Register new currency configurations.
static unregister(currency: CurrencyConfig | CurrencyConfig[]): void
Remove currency configurations.
static getCurrency(code: string): CurrencyConfig | undefined
Get currency configuration by code.
static initialize(currencies?: CurrencyConfig[]): void
Initialize currency registry.
Static Properties
static readonly USD: CurrencyConfig // US Dollar configuration
static readonly EUR: CurrencyConfig // Euro configuration
static readonly GBP: CurrencyConfig // British Pound configuration
static readonly JPY: CurrencyConfig // Japanese Yen configuration
static readonly BTC: CurrencyConfig // Bitcoin configuration
static readonly ETH: CurrencyConfig // Ethereum configuration
Types
CurrencyConfig
interface CurrencyConfig {
name: string; // Full currency name
code: string; // Currency code
symbol: string; // Currency symbol
symbolPosition: string; // 'prefix' or 'suffix'
decimalSeparator: string; // Decimal point character
decimals: number; // Number of decimal places
minorUnits: string; // Minor units in main unit
thousandsSeparator: string; // Thousands separator
isCrypto?: boolean; // Is cryptocurrency
}
MoneyComparisonResult
interface MoneyComparisonResult {
equal: boolean;
greaterThan: boolean;
lessThan: boolean;
}
Constants
ROUNDING_MODE
const ROUNDING_MODE = {
ROUND_UP: 0, // Round away from zero
ROUND_DOWN: 1, // Round toward zero
ROUND_CEIL: 2, // Round toward +Infinity
ROUND_FLOOR: 3, // Round toward -Infinity
ROUND_HALF_UP: 4, // Round half away from zero
ROUND_HALF_DOWN: 5, // Round half toward zero
ROUND_HALF_EVEN: 6, // Round half to even
ROUND_HALF_CEIL: 7, // Round half toward +Infinity
ROUND_HALF_FLOOR: 8, // Round half toward -Infinity
} as const;
SYMBOL_POSITION
const SYMBOL_POSITION = {
PREFIX: "prefix", // Symbol before amount
SUFFIX: "suffix", // Symbol after amount
} as const;
Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
Development
# Install dependencies
pnpm install
# Run tests
pnpm test
# Build the project
pnpm build
# Run linter
pnpm lint
Testing
We use Vitest for testing. Run the test suite:
# Run tests
pnpm test
# Run tests with coverage
pnpm test:coverage
License
MIT © Smiling Sloth
Acknowledgments
- decimal.js for precise decimal arithmetic
- Inspired by various money handling libraries in the JavaScript ecosystem