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

7.0 KiB
Raw Permalink Blame History

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).
  • FixerProvider class implementing the CurrencyProvider contract from @suaveplan/currency (name = "fixer", supportsFiat = true, supportsCrypto = false). Constructor takes ApiKeyConfig (from the slim core's ./types/configs). Optional baseUrl override defaults to https://data.fixer.io/api. Optional injectable FetchLike (from @suaveplan/types) defaults to globalThis.fetch.
  • fetchRates(options?) delegates HTTP to the slim core's httpRequest helper (caller-signal composition, non-2xx → typed HTTP_ERROR, response pass-through), then parses the upstream JSON envelope into the universal RateFetchResult shape ({ base, rates, timestamp }).
  • Typed error mapping from upstream { success: false, error: { code, type, info } } envelopes to the slim core's CURRENCY_PROVIDER_* codes:
    • empty apiKey at construction → MISSING_CONFIG
    • non-2xx HTTP response → HTTP_ERROR (raised by httpRequest)
    • malformed / non-object JSON → PARSE_ERROR
    • upstream code 105 (base_currency_access_restricted) → UNSUPPORTED_BASE
    • upstream code 201 (invalid_base_currency) or 202 (invalid_currency_codes) → UNKNOWN_CURRENCY
    • any other success: false envelope → UPSTREAM_ERROR carrying upstreamMessage
  • No auto-register. The package exports the class; callers wire it via new FixerProvider({ apiKey }) or registry.register(new FixerProvider({ apiKey })) per the slim-core design. There is no top-level side effect.

KEPT (unchanged from slim core)

  • The CurrencyProvider contract, RateFetchOptions, RateFetchResult remain owned by @suaveplan/currency.
  • CURRENCY_PROVIDER_CODES, CurrencyProviderError, httpRequest, composeAbortSignal, and ApiKeyConfig remain 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 with CurrencyProviderError carrying stable codes from the slim core's registry (upstream fixer.ts L42 / L57 → MISSING_CONFIG / PARSE_ERROR / UNSUPPORTED_BASE / UNKNOWN_CURRENCY / UPSTREAM_ERROR).
  • Routes HTTP through the slim core's shared httpRequest so caller-supplied AbortSignal from RateFetchOptions.signal is honoured (upstream version did not).
  • Validates apiKey at construction (upstream silently accepted an empty string and let the upstream API reject it on the wire).
  • Adds the FetchLike injection seam from @suaveplan/types (upstream used the bare globalThis.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-providers is 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 — the CurrencyProvider contract today exposes only fetchRates; 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.