7.0 KiB
Add @suaveplan/currency-provider-fixer — Fixer.io fiat-rates sibling provider
Package: @suaveplan/currency-provider-fixer
Status: PROPOSED
Type: new package (genesis-original sibling of slimmed @suaveplan/currency-providers core)
- Design: ./design.md
- Spec delta: ./specs/currency-provider-fixer/spec.md
Why
The 2026-05-17 slim refactor of @suaveplan/currency-providers
(openspec/archive/2026-05-17-slim-currency-providers/) split the nine
concrete providers out of the umbrella package and committed to landing each
as a dedicated @suaveplan/currency-provider-<name> sibling consuming the
slim core (registry / factory / httpRequest / composeAbortSignal / configs
/ CurrencyProviderError). This change lands the Fixer.io sibling — the
first API-keyed fiat-rates provider in the follow-up wave.
Fixer.io is the most widely deployed third-party fiat-rates API after ECB
(Reuters / payment-aggregator backbone for years); shipping it early gives
downstream commerce / accounting consumers an API-keyed alternative to the
key-free ECB feed (@suaveplan/currency-provider-ecb, landing alongside)
that supports 170+ currencies and is gateway-friendly.
What changes
ADDED
- New package
@suaveplan/currency-provider-fixer(universal-tier,0.1.0,sideEffects: false). FixerProviderclass implementing theCurrencyProvidercontract from@suaveplan/currency(name = "fixer",supportsFiat = true,supportsCrypto = false). Constructor takesApiKeyConfig(from the slim core's./types/configs). OptionalbaseUrloverride defaults tohttps://data.fixer.io/api. Optional injectableFetchLike(from@suaveplan/types) defaults toglobalThis.fetch.fetchRates(options?)delegates HTTP to the slim core'shttpRequesthelper (caller-signal composition, non-2xx → typedHTTP_ERROR, response pass-through), then parses the upstream JSON envelope into the universalRateFetchResultshape ({ base, rates, timestamp }).- Typed error mapping from upstream
{ success: false, error: { code, type, info } }envelopes to the slim core'sCURRENCY_PROVIDER_*codes:- empty
apiKeyat construction →MISSING_CONFIG - non-2xx HTTP response →
HTTP_ERROR(raised byhttpRequest) - malformed / non-object JSON →
PARSE_ERROR - upstream code
105(base_currency_access_restricted) →UNSUPPORTED_BASE - upstream code
201(invalid_base_currency) or202(invalid_currency_codes) →UNKNOWN_CURRENCY - any other
success: falseenvelope →UPSTREAM_ERRORcarryingupstreamMessage
- empty
- No auto-register. The package exports the class; callers wire it via
new FixerProvider({ apiKey })orregistry.register(new FixerProvider({ apiKey }))per the slim-core design. There is no top-level side effect.
KEPT (unchanged from slim core)
- The
CurrencyProvidercontract,RateFetchOptions,RateFetchResultremain owned by@suaveplan/currency. CURRENCY_PROVIDER_CODES,CurrencyProviderError,httpRequest,composeAbortSignal, andApiKeyConfigremain owned by@suaveplan/currency-providers.
Source mapping
This package is a migration of the upstream FixerProvider previously
bundled in @suaveplan/currency-providers. The source was carved out by the
2026-05-17 slim refactor and is reconstituted here against the slim-core
contracts.
| Component | Source |
|---|---|
FixerProvider class skeleton |
../suaveplan/packages/finance/currency-providers/src/fiat/fixer.ts L1–L67 |
| README quick-start example | upstream fixer.md L17–L37 |
| Free-tier EUR-only constraint | upstream fixer.md L52–L54 + Fixer.io API docs error code 105 |
Upstream JSON envelope shape (success / error.code / error.info / base / rates / timestamp) |
upstream fixer.ts L47–L53 + Fixer.io public docs |
| HTTP / abort-signal / non-2xx mapping | @suaveplan/currency-providers/http/http (slim core) |
| Error code surface | @suaveplan/currency-providers/error-codes/error-codes (slim core) |
The genesis-native upgrades over the upstream skeleton:
- Replaces the bare
throw new Error(...)in upstream withCurrencyProviderErrorcarrying stable codes from the slim core's registry (upstreamfixer.tsL42 / L57 →MISSING_CONFIG/PARSE_ERROR/UNSUPPORTED_BASE/UNKNOWN_CURRENCY/UPSTREAM_ERROR). - Routes HTTP through the slim core's shared
httpRequestso caller-suppliedAbortSignalfromRateFetchOptions.signalis honoured (upstream version did not). - Validates
apiKeyat construction (upstream silently accepted an empty string and let the upstream API reject it on the wire). - Adds the
FetchLikeinjection seam from@suaveplan/types(upstream used the bareglobalThis.fetch).
Parity Checklist
| Capability | Upstream | Migrated | Notes |
|---|---|---|---|
name/supportsFiat/supportsCrypto identity flags |
✅ | ✅ | |
ApiKeyConfig constructor (apiKey + optional baseUrl) |
✅ | ✅ | Inherited from slim core. |
Default URL https://data.fixer.io/api |
✅ | ✅ | |
fetchRates({ base, targets }) query construction |
✅ | ✅ | access_key, base, symbols. |
Default base EUR when none supplied |
✅ | ✅ | |
success: false envelope detection |
✅ | ⬆️ | Now mapped to typed codes via upstream error.code discriminant instead of a bare throw new Error(). |
| Non-2xx → typed error | ✅ | ⬆️ | Routed through slim core's httpRequest; raises HTTP_ERROR with populated status + providerName. |
| Timestamp seconds → ms conversion | ✅ | ✅ | data.timestamp * 1000. |
Symbol filter via targets |
✅ | ✅ | |
apiKey non-empty validation at construction |
🚫 | ⬆️ | New: raises MISSING_CONFIG instead of letting the API reject on the wire. |
AbortSignal propagation via RateFetchOptions.signal |
🚫 | ⬆️ | New: caller signal composed by slim-core httpRequest. |
FetchLike injection seam |
🚫 | ⬆️ | New: optional config.fetch honoured for testability and proxying. |
No upstream capability is dropped. Every 🚫 row is an ADDITION on top of upstream.
Impact
Affected dependents
Run bunx turbo ls --filter=...@suaveplan/currency-provider-fixer to enumerate.
- No current dependents (package is new). The slim core
@suaveplan/currency-providersis depended on by this package, not the reverse — no consumer rewrite is required.
Out of scope
- Other sibling providers (ECB, OXR, Binance, CoinGecko, CMC, CryptoCompare, Kraken, Virtual) — each ships under its own OpenSpec change per AGENTS.md §0c.
- Historical rates (
/{date}endpoint), time-series (/timeseries), fluctuation (/fluctuation), conversion (/convert) endpoints — theCurrencyProvidercontract today exposes onlyfetchRates; the additional endpoints will land once the contract is extended (carried over from the 2026-05-17 archive's## Out of scope). - Rate-limit / retry / aggregator wrappers — same deferral as the slim core.