Files
suaveplan/openspec/changes/enterprise-currency/proposal.md
2026-05-27 23:29:53 +01:00

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.ts using @suaveplan/retry
  • convertBatch — coalesces required rate fetches
  • setVirtualRate + change events via @suaveplan/events
  • AbortSignal on convert
  • 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.