5.4 KiB
Add @suaveplan/currency-provider-open-exchange-rates
Package: @suaveplan/currency-provider-open-exchange-rates
Status: PROPOSED
Type: new-package
- Spec delta: ./specs/currency-provider-open-exchange-rates/spec.md
Why
The 2026-05-17 slim of @suaveplan/currency-providers removed the nine bundled
provider implementations and committed to landing each as an independent
sibling package on the foundation-with-sibling-plugins shape established by
@suaveplan/network-plugin-* and @suaveplan/drive-plugin-*. This change
lands the Open Exchange Rates (OXR) provider so consumers that only need OXR
install one small package instead of the bundled umbrella, and OXR can iterate
on its own version cadence when the upstream API changes (free-plan base-only,
historical endpoints, currency-list updates).
What changes
- ADDED:
OpenExchangeRatesProvider— class implementingCurrencyProviderfrom@suaveplan/currency; consumeshttpRequest()from@suaveplan/currency-providers; raisesCurrencyProviderErrorwith codesHTTP_ERROR,PARSE_ERROR,UPSTREAM_ERROR,MISSING_CONFIG,UNSUPPORTED_BASE,UNKNOWN_CURRENCY. - ADDED: subpath export
./provider/providerfor tree-shakeable import. - ADDED: package root barrel re-exporting
OpenExchangeRatesProviderand the type aliasOpenExchangeRatesProviderConfig(=ApiKeyConfigfrom@suaveplan/currency-providers).
Out of scope
- Historical-rate endpoint (
/historical/<date>.json) — deferred to a follow-up change. The upstream slim refactor's## Out of scopeblock explicitly defers historical rates for every provider in the wave; this package keeps parity with that boundary. - Retry / backoff / rate-limit-header surfacing / OTel — deferred to the same cross-cutting follow-up that the slim core deferred them to.
- The five other OXR endpoints (
/currencies.json,/usage.json,/convert/{value}/{from}/{to},/ohlc.json,/time-series.json) — theCurrencyProvidercontract only exposes bulk-fetch via/latest.json; the rest are out of contract.
Source mapping
This change derives from ../suaveplan/enterprise/currency-providers.md and
the upstream OXR implementation at
../suaveplan/packages/finance/currency-providers/src/fiat/open-exchange-rates.ts.
| Requirement | Source lines | Notes |
|---|---|---|
OXR-001 implements CurrencyProvider with supportsFiat=true, supportsCrypto=false, name="open-exchange-rates" |
open-exchange-rates.ts L15-L19 |
verbatim, plus tier purity (no Node-only APIs) |
OXR-002 fetches /latest.json?app_id=<key>&base=<base> via httpRequest() |
open-exchange-rates.ts L28-L37 |
reworded — upstream called fetch() directly; genesis routes through slim-core httpRequest() per slim spec |
OXR-003 maps response { base, rates, timestamp } to RateFetchResult with timestamp * 1000 to convert OXR's Unix seconds to ms |
open-exchange-rates.ts L39-L43, L56-L60 |
verbatim |
OXR-004 honours RateFetchOptions.targets by filtering rates map |
open-exchange-rates.ts L46-L54 |
verbatim |
OXR-005 free-tier limits base to USD; explicit non-USD base raises UNSUPPORTED_BASE |
enterprise audit L12, OXR.md L11-L13, L41-L43 | extended — upstream silently let upstream API reject; genesis pre-rejects with a stable error code (the UNSUPPORTED_BASE code is already registered in slim core) |
Requirements with no source line
| Requirement | Justification |
|---|---|
OXR-006 raises MISSING_CONFIG on empty apiKey |
MISSING_CONFIG code exists in slim-core registry per archived slim spec; surfacing it from constructor is the genesis baseline for all api-key providers |
OXR-007 raises PARSE_ERROR when upstream payload is not the expected shape |
Defensive validation — upstream library threw a generic JSON-parse error; genesis maps to typed PARSE_ERROR |
OXR-008 raises UPSTREAM_ERROR when upstream returns 2xx with error: true body |
OXR documented error/status/message payload shape; upstream library missed it. Genesis surfaces typed UPSTREAM_ERROR with upstreamMessage populated |
OXR-009 raises UNKNOWN_CURRENCY when options.targets requests a code not in upstream response |
UNKNOWN_CURRENCY code exists in slim-core registry; surfacing it instead of silently dropping the target gives callers a typed handle |
OXR-010 honours RateFetchOptions.signal for cancellation |
Universal-tier abort propagation — upstream did not honour signal at all; genesis composes via httpRequest() which forwards to the injected fetch |
| OXR-011 universal-tier purity — no Node APIs, no DOM APIs | Genesis-wide tier policy |
OXR-012 100% test coverage, sibling .md on every implementation file |
Genesis-wide quality policy |
Verification approach
Every requirement carries a **Verified by:** line in the spec delta pointing
at concrete it(...) titles. The test surface is:
src/provider/provider.test.ts— covers OXR-001 through OXR-011 with injectedFetchLikerecording calls and returning canned responses.tests/dep-graph.test.ts— pins dependency invariants (only consumes@suaveplan/currency,@suaveplan/currency-providers,@suaveplan/error,@suaveplan/types).
Migration impact
None — additive only. No current consumers reference this package; the slim
core's ## Affected dependents block listed this package as one of nine
sibling consumers that would land in the follow-up wave.