Files
2026-05-27 23:29:53 +01:00

9.0 KiB

Add @suaveplan/currency-providers — concrete CurrencyProvider plugins

Package: @suaveplan/currency-providers Status: PROPOSED Type: migration (upstream ../suaveplan/packages/finance/currency-providers)

  • Design: ./design.md
  • Spec delta: ./specs/currency-providers/spec.md

Why

STATUS.md lists @suaveplan/currency-providers as Pending. With @suaveplan/currency shipped on genesis (2026-05-17), the provider plugins are the next link in the commerce chain: they are the concrete CurrencyProvider implementations that real applications wire into CurrencyConverter. Every fiat conversion (ECB / Fixer / Open Exchange Rates), every crypto quote (CoinGecko / CoinMarketCap / Binance / Kraken / CryptoCompare), and every operator-defined virtual-currency (game gold, gems, etc.) depends on this package. Without it, @suaveplan/currency is an empty engine; with it, the commerce stack has rate data flowing end-to-end.

The migration preserves the upstream public surface verbatim — same provider class names, same config shapes (ApiKeyConfig / NoKeyConfig), same fetchRates semantics — and lifts the package to the genesis quality bar: every plain throw new Error(...) becomes a typed CurrencyProviderError carrying a stable CURRENCY_PROVIDER.* code; HTTP is performed through an injectable FetchLike (default globalThis.fetch) so tests are real fakes not mocks; every provider is universal-tier with zero browser/Node APIs; folder layout matches the §10 no-prefix rule (providers/ecb/ecb.ts, not providers/ecb-provider/ecb-provider.ts); every implementation file has a sibling .md; every public module has a subpath export.

The enterprise audit (../suaveplan/enterprise/currency-providers.md) lists nine "feature-rich enhancements" — rate-limited wrappers, historical-rate endpoints, WebSocket streaming, on-chain oracles, aggregator providers, health monitors, multi-base normalization, deterministic replay, SLO tracking. Those land in follow-up changes once their dependency packages (@suaveplan/retry, @suaveplan/rate-limiter, @suaveplan/events, @suaveplan/cryptocurrency's chain registry) are wired through this surface; the migration baseline matches the upstream behaviour bit-for-bit, fills the three test-coverage gaps the audit flagged (Fixer / OXR / CoinMarketCap had no tests upstream), and unblocks every downstream commerce consumer.

What changes

  • ADDED: EcbProvider — ECB daily XML feed, no API key, EUR-base with automatic conversion to any requested base.
  • ADDED: FixerProvider — Fixer.io API, requires apiKey, EUR-base on free tier.
  • ADDED: OpenExchangeRatesProvider — OXR API, requires apiKey, USD-base on free tier, supports targets filtering.
  • ADDED: BinanceProvider — Binance spot ticker, no API key, USDT-base by default, real-time prices.
  • ADDED: CoinGeckoProvider + getCoinGeckoId helper — CoinGecko simple/price endpoint, no API key, 16-symbol built-in registry.
  • ADDED: CoinMarketCapProvider — CMC cryptocurrency/quotes/latest, requires apiKey, supports configurable target symbols.
  • ADDED: CryptoCompareProvider — CryptoCompare pricemulti, no API key, 15-symbol default list.
  • ADDED: KrakenProvider — Kraken Ticker, no API key, maps Kraken's non-standard pair names back to canonical symbols.
  • ADDED: VirtualCurrencyProvider — operator-defined game / virtual currencies with runtime addCurrency / updateRate / removeCurrency / getRate / getCurrencyCodes, anchored against a fiat base, registers every currency with @suaveplan/money's currency registry.
  • ADDED: ApiKeyConfig / NoKeyConfig / HttpFetchOptions config types (named interfaces, no inline shapes per AGENTS.md §11).
  • ADDED: FetchLike injection on every provider — every fetchRates implementation accepts an optional fetch in its constructor config, defaulting to globalThis.fetch. Tests inject a custom FetchLike instead of mocking globals.
  • ADDED: CURRENCY_PROVIDER_CODES registry via defineCodes("CURRENCY_PROVIDER", [...]) — codes HTTP_ERROR, PARSE_ERROR, UPSTREAM_ERROR, UNSUPPORTED_BASE, MISSING_CONFIG, UNKNOWN_CURRENCY.
  • ADDED: CurrencyProviderError extends SuaveplanError<CurrencyProviderErrorCode> carrying optional providerName / status / upstreamMessage discriminants.

Out of scope

  • Rate-limit-aware RateLimitedProvider wrapper — pending @suaveplan/rate-limiter (exists upstream, not yet migrated to genesis).
  • Retry + backoff wrapper — pending @suaveplan/retry audit for genesis parity (the upstream is migrated but its integration shape with these providers is a follow-up).
  • Historical-rate endpoints (fetchHistoricalRates(date)) — additive surface that requires a coordinated CurrencyProvider interface extension in @suaveplan/currency.
  • WebSocket streaming adapters (Binance / Kraken) — additive surface; spec lives in a separate change.
  • On-chain oracle adapter (Chainlink / Pyth) — pending @suaveplan/cryptocurrency chain registry.
  • AggregatorProvider (volume-weighted multi-source average) — depends on the historical-rate surface and rate-limit wrapper.
  • Provider health-monitor background job — depends on @suaveplan/job-scheduler integration.
  • OpenTelemetry per-request spans / counters — pending coordinated observability surface in @suaveplan/currency.
  • Stablecoin redirect routing — additive optimisation, no public-surface change.

Source mapping

This change derives from ../suaveplan/enterprise/currency-providers.md.

Requirement Source lines Notes
9 providers (3 fiat + 5 crypto + 1 virtual) L17-L31 verbatim
ApiKeyConfig / NoKeyConfig / HttpFetchOptions L17-L19, L30 verbatim
getCoinGeckoId helper L17, L25 verbatim
VirtualCurrencyProvider runtime mutators L29, L70-L86 of upstream virtual.ts verbatim
Test coverage parity for Fixer / OXR / CoinMarketCap L50-L51 upstream had 6/9 covered; genesis ships 9/9
Typed error hierarchy (replace plain Error) L52-L53 enhanced: CurrencyProviderError + 6 codes
Injectable FetchLike per provider L40-L41, L66-L67 enhanced: enables real-fake tests, removes vi.stubGlobal
Universal-tier purity L60-L69 verbatim (genesis cross-cutting baseline)
Subpath exports per provider upstream package.json exports verbatim
Sibling .md per file L61-L63 verbatim (genesis cross-cutting)
100% Istanbul coverage L61, L101 verbatim (genesis cross-cutting)

Requirements with no source line

Requirement Justification
Folder-naming rule §10 (providers/ecb/ecb.ts) Genesis-wide structural rule from openspec/AGENTS.md §10; upstream used flat fiat/ecb.ts / crypto/binance.ts which collides once a 10th provider arrives.
@suaveplan/testing/runner import for tests Genesis-wide CI gate from openspec/AGENTS.md §7 / CLAUDE.md rule 11.
Zero export * in feature barrels Genesis-wide rule §6a / CLAUDE.md rule 10.
defineCodes registry under @suaveplan/error Genesis-wide foundation pattern; upstream threw plain Error.

Verification approach

Every requirement in the spec delta has a **Verified by:** line naming concrete it(...) titles in these files:

  • src/providers/ecb/ecb.test.ts — ECB XML parsing, base conversion, filtering, error handling, custom baseUrl, FetchLike injection.
  • src/providers/fixer/fixer.test.ts — Fixer API call, success-payload handling, error-payload handling, params construction, custom base / targets.
  • src/providers/open-exchange-rates/open-exchange-rates.test.ts — OXR endpoint construction, targets filtering, timestamp conversion.
  • src/providers/binance/binance.test.ts — symbol-suffix matching, zero-price skip, target filtering, custom baseUrl.
  • src/providers/coingecko/coingecko.test.ts — getCoinGeckoId case-insensitivity, ID→symbol mapping, non-USD base.
  • src/providers/coinmarketcap/coinmarketcap.test.ts — API-key header, quotes parsing, target list construction.
  • src/providers/cryptocompare/cryptocompare.test.ts — invert-price, zero-price skip, default targets.
  • src/providers/kraken/kraken.test.ts — pair→symbol reverse mapping, Kraken error array, target filtering.
  • src/providers/virtual/virtual.test.ts — addCurrency / updateRate / removeCurrency / getCurrencyCodes / getRate, virtual-base conversion.
  • src/error-codes/error-codes.test.ts — every CURRENCY_PROVIDER.* code reachable; SuaveplanError contract preserved.

Impact

Affected dependents

Run bunx turbo ls --filter=...@suaveplan/currency-providers to enumerate. This is a brand-new package on genesis, so no current dependents exist:

  • No dependents (verified by bunx turbo ls --filter=...@suaveplan/currency-providers).

Downstream packages that will consume this once they migrate (@suaveplan/payment, @suaveplan/booking, api-plugin-virtual-currency, etc.) are out of scope here.

Migration impact

New package — additive only. No existing consumers; nothing breaks.