Files
skills/docs/money/finance/currency-provider-fixer/src/fixer/fixer.md
2026-05-29 08:20:56 +01:00

6.2 KiB

FixerProvider

(no submodules)

Purpose

FixerProvider is the Fixer.io sibling provider for the slim @suaveplan/currency-providers core. It implements the CurrencyProvider contract from @suaveplan/currency, fetches the latest fiat exchange rates from the Fixer /latest REST endpoint, and maps the upstream JSON envelope into the canonical RateFetchResult shape the converter expects.

Fixer.io requires an API key on every request. Free-tier accounts are restricted to base = "EUR" and a fixed monthly request quota; paid plans unlock arbitrary base currencies and higher throughput. The provider surfaces the free-tier restriction as a typed UNSUPPORTED_BASE error so consumers can dispatch on the discriminator rather than parsing the upstream message string.

Features

  • Implements CurrencyProvider with stable identity flags (name = "fixer", supportsFiat = true, supportsCrypto = false).
  • Constructor takes the slim core's shared ApiKeyConfig (apiKey, optional baseUrl, optional injectable fetch). An empty apiKey raises CURRENCY_PROVIDER.MISSING_CONFIG synchronously before any network call is issued, preventing wasted quota.
  • Delegates HTTP transport to the slim core's httpRequest helper — AbortSignal composition, non-2xx → typed HTTP_ERROR mapping, and providerName / status carry on the raised error.
  • Maps the upstream { success: false, error: { code, info } } envelope to typed codes: 105 → UNSUPPORTED_BASE, 201 / 202 → UNKNOWN_CURRENCY, any other code → UPSTREAM_ERROR. The upstream error.info is preserved on upstreamMessage so diagnostics survive.
  • Distinguishes parse failures (non-JSON, non-object JSON, success envelope missing required fields) under a single PARSE_ERROR code.
  • Universal — no node: / bun: / DOM imports; runs unchanged under Node 18+, Bun, modern browsers, and Web Workers.

Usage: Basic

import { FixerProvider } from "@suaveplan/currency-provider-fixer";

const provider = new FixerProvider({ apiKey: process.env.FIXER_KEY! });

// Free-tier: base is implicitly EUR.
const result = await provider.fetchRates();
console.log(result.base, result.rates.GBP, result.timestamp);

Usage: Advanced

Wire the provider through the slim core's CurrencyProviderRegistry and use it as the primary source for a CurrencyConverter. The injected FetchLike makes the provider trivial to unit-test against a fixture fetch closure:

import { CurrencyConverter } from "@suaveplan/currency";
import { CurrencyProviderRegistry } from "@suaveplan/currency-providers";
import { FixerProvider } from "@suaveplan/currency-provider-fixer";

const registry = new CurrencyProviderRegistry();
registry.register(
    new FixerProvider({
        apiKey: process.env.FIXER_KEY!,
        // Optional: proxy through your own gateway for caching / auditing.
        baseUrl: "https://rates.internal/fixer/api",
        // Optional: inject a wrapped fetch for retries or logging.
        fetch: async (url, init) => {
            const response = await globalThis.fetch(url, init);
            return response;
        },
    }),
);

const converter = new CurrencyConverter({
    provider: registry.get("fixer"),
    staleDurationMs: 3_600_000,
});

// Free-tier accounts MUST keep base = "EUR" — supplying any other base
// raises CurrencyProviderError(UNSUPPORTED_BASE) via the typed mapping
// from upstream error code 105.
const rate = await converter.getRate("EUR", "GBP");

API Reference

class FixerProvider implements CurrencyProvider

  • readonly name = "fixer"
  • readonly supportsCrypto = false
  • readonly supportsFiat = true

constructor(config: ApiKeyConfig)

Validates config.apiKey is non-empty (throws CurrencyProviderError(MISSING_CONFIG) otherwise) and caches config.baseUrl ?? "https://data.fixer.io/api" and config.fetch ?? globalThis.fetch.

Throws: CurrencyProviderError(MISSING_CONFIG) when apiKey === "".

fetchRates(options?: RateFetchOptions): Promise<RateFetchResult>

Builds ?access_key=…&base=…[&symbols=…], delegates to the slim core's httpRequest, parses the JSON envelope, and returns the canonical RateFetchResult with timestamp converted from Unix seconds to milliseconds.

Parameters (all optional): base (default "EUR"), targets (array of currency codes — joined into symbols), signal (AbortSignal forwarded to the underlying fetch).

Returns: { base, rates, timestamp } where timestamp is milliseconds since epoch.

Throws (all CurrencyProviderError):

  • MISSING_CONFIG — at construction when apiKey is empty.
  • HTTP_ERROR — non-2xx upstream response (via httpRequest).
  • PARSE_ERROR — body is not JSON, parsed value is not an object, or a success: true envelope is missing base / rates / timestamp.
  • UNSUPPORTED_BASE — upstream code 105 (free-tier base restriction).
  • UNKNOWN_CURRENCY — upstream code 201 (invalid_base_currency) or 202 (invalid_currency_codes).
  • UPSTREAM_ERROR — any other success: false envelope, including the case where error is absent.

Implementation Notes

  • Fixer.io returns HTTP 200 even for application-level errors; the provider must therefore parse every body and discriminate on success. The non-2xx path (HTTP_ERROR) is reserved for genuine network / gateway failures.
  • The upstream error.code integer is the discriminator, not error.type — the integer is one comparison and avoids string allocation. The human-readable error.info is preserved on upstreamMessage for diagnostics.
  • The apiKey non-empty guard at construction is suaveplan-native — the upstream skeleton allowed an empty string and let the API reject on the wire, wasting the request against the monthly quota.
  • fetchRates forwards options.signal through httpRequest so the caller can cancel an in-flight request from outside the provider; this too is suaveplan-native (upstream did not propagate the signal).
  • The package is universal and free of side effects (sideEffects: false). Importing the package never mutates any global registry — the caller decides whether to register the instance with CurrencyProviderRegistry.