5.6 KiB
OpenSpec Change: enterprise-money
- Change ID: enterprise-money
- Package:
@suaveplan/money - Category: finance
- Target path:
finance/currency/money - Status: PROPOSED
- Date: 2026-04-24
- Reference spec:
/mnt/s/web/suaveplan/enterprise/money.md
Summary
Deliver @suaveplan/money on the genesis branch from scratch at enterprise quality. The package provides precise currency / money arithmetic using BigInt minor-unit representation. It ships the immutable Money class, a CurrencyRegistry (constructor-injectable, with a default global instance), ISO 4217 metadata, virtual-currency registration, Intl-backed formatting, allocation helpers, aggregation helpers, MoneyRange, locale-aware parse, BigInt-safe JSON serialization, Zod schemas, property-based tests, and a typed error hierarchy. Zero runtime dependencies beyond tslib and zod.
Current-state audit (genesis)
Does not exist on genesis — fresh build.
Available workspace deps on genesis
| Package | Role |
|---|---|
@suaveplan/error (workspace:^) |
Base for MoneyError hierarchy |
@suaveplan/types (workspace:^) |
LoggerLike (not used at runtime; types only) |
zod (catalog:forms) |
MoneyLike / CurrencyCode schemas |
tslib (catalog:build) |
Runtime helpers |
Dev dep: fast-check catalog:testing.
Public API surface (from main branch, fully preserved + genesis additions)
// Core
class Money {
static fromMajor(value: number | string, currency: string, registry?: CurrencyRegistry): Money
static fromMinor(minorUnits: bigint, currency: string, registry?: CurrencyRegistry): Money
static zero(currency: string, registry?: CurrencyRegistry): Money
static withRegistry(registry: CurrencyRegistry): typeof Money
get amount(): bigint // minor units
get currency(): string
add(other: Money): Money
subtract(other: Money): Money
multiply(factor: number): Money
divide(divisor: number, mode?: RoundingMode): Money
compare(other: Money): -1 | 0 | 1
equals(other: Money): boolean
abs(): Money
negate(): Money
isZero(): boolean
isPositive(): boolean
isNegative(): boolean
toMajor(): number
toDecimalString(): string
toJSON(): { amount: string; currency: string }
}
// Registry
class CurrencyRegistry {
static global(): CurrencyRegistry
getCurrency(code: string): CurrencyInfo | undefined
getCurrencyOrThrow(code: string): CurrencyInfo
getAllCurrencyCodes(): string[]
registerCurrency(info: CurrencyInfo): void
getVirtualCurrencies(): CurrencyInfo[]
isVirtualCurrency(code: string): boolean
minorUnitMultiplier(code: string): bigint
}
function getCurrency(code: string): CurrencyInfo | undefined // delegates to global
function getCurrencyOrThrow(code: string): CurrencyInfo
function getAllCurrencyCodes(): string[]
function registerCurrency(info: CurrencyInfo): void
function getVirtualCurrencies(): CurrencyInfo[]
function isVirtualCurrency(code: string): boolean
function minorUnitMultiplier(code: string): bigint
// Formatting
function formatMoney(money: Money, options?: FormatOptions): string
function formatMoneyCompact(money: Money, options?: FormatOptions): string
function parseMoney(input: string, currency: string, locale?: string): Money
// Allocation
function allocateEvenly(money: Money, parts: number): Money[]
function allocateByRatios(money: Money, ratios: number[]): Money[]
function allocateByPercentages(money: Money, percentages: number[]): Money[]
function sumMoney(moneys: Money[]): Money
function minMoney(...moneys: Money[]): Money
function maxMoney(...moneys: Money[]): Money
function applyRounding(money: Money, mode: RoundingMode): Money
// MoneyRange (genesis addition)
class MoneyRange {
constructor(min: Money, max: Money)
contains(money: Money): boolean
intersect(other: MoneyRange): MoneyRange | null
union(other: MoneyRange): MoneyRange
toArray(step: Money): Money[]
}
// Serialization
function moneyToJSON(money: Money): { amount: string; currency: string }
function moneyFromJSON(json: { amount: string; currency: string }): Money
// Zod schemas
const MoneyLikeSchema: z.ZodType<MoneyLike>
const CurrencyCodeSchema: z.ZodType<CurrencyCode>
const FormatOptionsSchema: z.ZodType<FormatOptions>
// Error types
class MoneyError extends SuaveplanError { }
class CurrencyMismatchError extends MoneyError { }
class UnknownCurrencyError extends MoneyError { }
class AllocationError extends MoneyError { }
class ParseError extends MoneyError { }
File layout
finance/currency/money/
package.json
tsconfig.json
vitest.config.ts
src/
index.ts
error-codes.ts
constants/index.ts
types/
money.types.ts
money.types.md
core/
money/
money.ts / money.md / money.types.ts / money.test.ts / index.ts
currency-registry/
currency-registry.ts / .md / .types.ts / .test.ts / index.ts
currencies/
currencies.ts / currencies.md / currencies.test.ts / index.ts
money-range/
money-range.ts / .md / .test.ts / index.ts
formatting/
formatter/
formatter.ts / formatter.md / formatter.test.ts / index.ts
allocation/
allocator/
allocator.ts / allocator.md / allocator.test.ts / index.ts
schemas/
money-schemas/
money-schemas.ts / money-schemas.md / money-schemas.test.ts / index.ts
serialization/
json/
json.ts / json.md / json.test.ts / index.ts
tests/
property.test.ts
Impact
Downstream: @suaveplan/currency (uses MoneyLike), @suaveplan/currency-providers, payment and billing packages.
Prerequisites on Verdaccio: @suaveplan/error, zod.