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, requiresapiKey, EUR-base on free tier. - ADDED:
OpenExchangeRatesProvider— OXR API, requiresapiKey, USD-base on free tier, supportstargetsfiltering. - ADDED:
BinanceProvider— Binance spot ticker, no API key, USDT-base by default, real-time prices. - ADDED:
CoinGeckoProvider+getCoinGeckoIdhelper — CoinGeckosimple/priceendpoint, no API key, 16-symbol built-in registry. - ADDED:
CoinMarketCapProvider— CMCcryptocurrency/quotes/latest, requiresapiKey, supports configurable target symbols. - ADDED:
CryptoCompareProvider— CryptoComparepricemulti, no API key, 15-symbol default list. - ADDED:
KrakenProvider— KrakenTicker, no API key, maps Kraken's non-standard pair names back to canonical symbols. - ADDED:
VirtualCurrencyProvider— operator-defined game / virtual currencies with runtimeaddCurrency/updateRate/removeCurrency/getRate/getCurrencyCodes, anchored against a fiat base, registers every currency with@suaveplan/money's currency registry. - ADDED:
ApiKeyConfig/NoKeyConfig/HttpFetchOptionsconfig types (named interfaces, no inline shapes per AGENTS.md §11). - ADDED:
FetchLikeinjection on every provider — everyfetchRatesimplementation accepts an optionalfetchin its constructor config, defaulting toglobalThis.fetch. Tests inject a customFetchLikeinstead of mocking globals. - ADDED:
CURRENCY_PROVIDER_CODESregistry viadefineCodes("CURRENCY_PROVIDER", [...])— codesHTTP_ERROR,PARSE_ERROR,UPSTREAM_ERROR,UNSUPPORTED_BASE,MISSING_CONFIG,UNKNOWN_CURRENCY. - ADDED:
CurrencyProviderError extends SuaveplanError<CurrencyProviderErrorCode>carrying optionalproviderName/status/upstreamMessagediscriminants.
Out of scope
- Rate-limit-aware
RateLimitedProviderwrapper — pending@suaveplan/rate-limiter(exists upstream, not yet migrated to genesis). - Retry + backoff wrapper — pending
@suaveplan/retryaudit 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 coordinatedCurrencyProviderinterface 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/cryptocurrencychain 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-schedulerintegration. - 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, custombaseUrl,FetchLikeinjection.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,targetsfiltering, timestamp conversion.src/providers/binance/binance.test.ts— symbol-suffix matching, zero-price skip, target filtering, custombaseUrl.src/providers/coingecko/coingecko.test.ts—getCoinGeckoIdcase-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— everyCURRENCY_PROVIDER.*code reachable;SuaveplanErrorcontract 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.