Initial Commit
This commit is contained in:
169
openspec/archive/2026-05-17-add-currency-providers/proposal.md
Normal file
169
openspec/archive/2026-05-17-add-currency-providers/proposal.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user