Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,171 @@
# 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)
```ts
// 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
```text
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`.

View File

@@ -0,0 +1,86 @@
# Tasks: enterprise-money
**Change ID**: enterprise-money
**Package**: `@suaveplan/money`
**Target path**: `finance/currency/money`
**Prerequisites on Verdaccio**: `@suaveplan/error`, `zod`
---
## 0. Pre-flight (non-negotiable)
- [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each)
- [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
- [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest.
## 1. Scaffold
- [ ] Create full directory tree per `proposal.md`.
- [ ] `package.json` at `0.1.0`, `"sideEffects": false`, exports `"."` + `core/money` + `core/currency-registry` + `core/currencies` + `core/money-range` + `formatting` + `allocation` + `schemas` + `serialization`.
- [ ] Deps: `@suaveplan/error workspace:^`, `zod catalog:forms`, `tslib catalog:build`.
- [ ] DevDeps: `vitest catalog:testing`, `@vitest/coverage-istanbul catalog:testing`, `fast-check catalog:testing`, `@suaveplan/testing workspace:^`.
- [ ] `tsconfig.json` extending `library.json`; `vitest.config.ts` with `createTestConfig()`.
- [ ] Scripts: `lint`, `typecheck`, `test`, `test:coverage`, `build`, `docs:check`.
## 2. Error hierarchy
- [ ] `src/error-codes.ts`: define `MoneyErrorCode` union; implement `MoneyError`, `CurrencyMismatchError`, `UnknownCurrencyError`, `AllocationError`, `ParseError` extending `SuaveplanError`; `registerMoneyErrorCodes()`; eager call.
## 3. Currency registry + ISO data
- [ ] Implement `CurrencyRegistry` in `src/core/currency-registry/currency-registry.ts`: internal `Map<string, CurrencyInfo>`; all methods per API surface.
- [ ] `src/core/currencies/currencies.ts`: ISO 4217 data (≥50 common currencies minimum, full list preferred); seed the global `CurrencyRegistry.global()` on module load.
- [ ] Write tests: register/get/virtual/multiplier; `getCurrencyOrThrow` throws `UnknownCurrencyError`; virtual vs ISO separation.
## 4. Money class
- [ ] Implement `Money` in `src/core/money/money.ts`:
- `fromMajor(value, currency, registry?)`: use `BigInt(Math.round(Number(value) * Number(multiplier)))` for number; use exact decimal-string parse for string inputs (no float intermediate).
- All arithmetic: BigInt operations; throw `CurrencyMismatchError` on mismatched currencies.
- `toDecimalString()`: format as exact decimal (e.g. `"12.34"` for USD with 2 minor units).
- `toJSON()` / `Money.withRegistry(registry)`.
- Negative-zero guard: ensure `0n` never becomes `-0n`.
- [ ] Write `money.test.ts` (≤500 lines): all operators, edge cases, currency mismatch, negative-zero, decimal-string round-trip.
## 5. Formatting + parse
- [ ] Implement `formatMoney`, `formatMoneyCompact`, `parseMoney` in `src/formatting/formatter/formatter.ts`.
- [ ] `parseMoney`: handle comma decimals, grouped separators, currency symbol removal via `Intl.NumberFormat`.
- [ ] Write `formatter.test.ts`: multiple locales, compact, parseMoney round-trip.
## 6. Allocation
- [ ] Implement all five allocators + `sumMoney` + `minMoney` + `maxMoney` + `applyRounding` in `src/allocation/allocator/allocator.ts`.
- [ ] Remainder redistribution: no minor unit lost or invented.
- [ ] Write `allocator.test.ts`: even/ratio/percentage edge cases, remainder, sum invariant.
## 7. MoneyRange
- [ ] Implement `MoneyRange` in `src/core/money-range/money-range.ts`: constructor validates `min <= max`; all methods per API surface.
- [ ] Write `money-range.test.ts`: contains, intersect (null on no overlap), union, toArray.
## 8. Zod schemas + serialization
- [ ] Implement `MoneyLikeSchema`, `CurrencyCodeSchema`, `FormatOptionsSchema` in `src/schemas/money-schemas/money-schemas.ts`.
- [ ] Implement `moneyToJSON` / `moneyFromJSON` in `src/serialization/json/json.ts`.
- [ ] Write tests for each.
## 9. Property-based tests
- [ ] `tests/property.test.ts` (fast-check, `{ timeout: 30000 }`):
- `a + b - b = a` for arbitrary same-currency `Money` pairs.
- `allocateEvenly(m, n)` sums to `m`.
- `allocateByRatios(m, rs)` sums to `m`.
- `toDecimalString` round-trip identity.
- `parseMoney(formatMoney(m, locale), m.currency, locale)` equals `m` for common locales.
- `MoneyRange` `intersect` is commutative and associative on overlapping ranges.
## 10. Docs + verify + publish
- [ ] All `.md` files per layout; root `README.md` with arithmetic quick-start, allocation example, MoneyRange, API table.
- [ ] `bunx turbo lint --filter=@suaveplan/money` — zero errors.
- [ ] `bunx turbo typecheck --filter=@suaveplan/money` — zero errors.
- [ ] `bunx turbo test --filter=@suaveplan/money` — all pass.
- [ ] `bunx turbo test:coverage --filter=@suaveplan/money` — 100%.
- [ ] `bun scripts/publish.ts --package @suaveplan/money`.
- [ ] Update `CLAUDE.md`; archive to `openspec/archive/2026-04-24-enterprise-money/`.