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

170 lines
9.0 KiB
Markdown

# 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.