5.1 KiB
OpenSpec Change: enterprise-currency
- Change ID: enterprise-currency
- Package:
@suaveplan/currency - Category: finance
- Target path:
finance/currency/currency - Status: PROPOSED
- Date: 2026-04-24
- Reference spec:
/mnt/s/web/suaveplan/enterprise/currency.md
Summary
Deliver @suaveplan/currency on the genesis branch from scratch at enterprise quality. The package is a currency conversion engine with pluggable CurrencyProvider interface, TTL-based RateCache, automatic BFS cross-rate triangulation (shortest-path over the cached-rate graph), inverse-rate calculation, per-provider circuit breaker, AbortSignal cancellation, batch convert, and a setVirtualRate live-update API. Returns typed ConversionResult with provenance (providerName, triangulatedThrough). Ships two reference providers: InMemoryProvider and createStaticProvider.
Current-state audit (genesis)
Does not exist on genesis — fresh build.
Available workspace deps on genesis
| Package | Role |
|---|---|
@suaveplan/error (workspace:^) |
Typed CURRENCY.* error codes |
@suaveplan/types (workspace:^) |
LoggerLike, EventBusLike |
@suaveplan/events (workspace:^) |
Virtual-rate change events |
@suaveplan/retry (workspace:^) |
Circuit-breaker + backoff |
@suaveplan/logger (workspace:^) |
Structured logs |
zod (catalog:forms) |
Options validation |
@opentelemetry/api (catalog:observability) |
Spans + counters |
tslib (catalog:build) |
Runtime helpers |
Dev dep: @suaveplan/money workspace:^ (for MoneyLike type in tests).
Public API surface (from main branch enterprise spec)
class CurrencyConverter {
constructor(config: ConverterConfig)
convert(from: string, to: string, amount: MoneyLike | number, options?: ConvertOptions): Promise<ConversionResult>
convertAmount(from: string, to: string, amount: number, options?: ConvertOptions): Promise<ConversionResult>
convertBatch(requests: Array<{from: string; to: string; amount: number}>): Promise<ConversionResult[]>
setVirtualRate(symbol: string, rate: number): void
invalidateCache(from?: string, to?: string): void
}
class RateCache {
get(from: string, to: string): ExchangeRate | undefined
set(from: string, to: string, rate: ExchangeRate): void
prune(): void
invalidate(from?: string, to?: string): void
}
function createStaticProvider(rates: Record<string, Record<string, number>>): CurrencyProvider
class InMemoryProvider implements CurrencyProvider { setRates(rates: ExchangeRate[]): void }
interface ConversionResult {
amount: number
rate: number
inverseRate: number
rateAgeMs: number
providerName: string
triangulatedThrough: string[] | null
}
interface ConverterConfig {
providers: CurrencyProvider[]
defaultBase?: string
cacheTtlMs?: number
maxRetries?: number
logger?: LoggerLike
}
interface CurrencyProvider {
name: string
supportsFiat: boolean
supportsCrypto: boolean
fetchRates(options: RateFetchOptions): Promise<RateFetchResult>
isHealthy?(): boolean
}
Error codes
type CurrencyErrorCode =
| 'CURRENCY_PROVIDER_UNAVAILABLE'
| 'CURRENCY_UNSUPPORTED_PAIR'
| 'CURRENCY_TRIANGULATION_IMPOSSIBLE'
| 'CURRENCY_CONVERT_ABORTED'
| 'CURRENCY_INVALID_CONFIG'
function registerCurrencyErrorCodes(): void
New additions for genesis
- BFS triangulation in
src/engine/triangulate.ts - Circuit breaker per provider in
src/engine/circuit-breaker.tsusing@suaveplan/retry convertBatch— coalesces required rate fetchessetVirtualRate+ change events via@suaveplan/eventsAbortSignalonconvert- Zod schemas for
ConverterConfig,ConvertOptions - OTel spans per convert, cache-hit counter, triangulation-path histogram
File layout
finance/currency/currency/
package.json
tsconfig.json
vitest.config.ts
src/
index.ts
error-codes.ts
constants/index.ts
types/
currency.types.ts
currency.types.md
engine/
converter/
converter.ts
converter.md
converter.types.ts
converter.test.ts
index.ts
rate-cache/
rate-cache.ts
rate-cache.md
rate-cache.test.ts
index.ts
triangulate/
triangulate.ts
triangulate.md
triangulate.test.ts
index.ts
circuit-breaker/
circuit-breaker.ts
circuit-breaker.md
circuit-breaker.test.ts
index.ts
provider/
in-memory-provider/
in-memory-provider.ts
in-memory-provider.md
in-memory-provider.test.ts
index.ts
static-provider/
static-provider.ts
static-provider.md
static-provider.test.ts
index.ts
tests/
integration.test.ts
property.test.ts
Impact
Downstream: @suaveplan/currency-providers (implements CurrencyProvider), @suaveplan/money (integration for MoneyLike).
Prerequisites on Verdaccio: @suaveplan/error, @suaveplan/types, @suaveplan/events, @suaveplan/retry, @suaveplan/logger.