170 lines
9.0 KiB
Markdown
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.
|