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

5.8 KiB

EcbProvider

(no submodules)

Purpose

EcbProvider is the @suaveplan/currency rate-source implementation for the European Central Bank's daily reference-rates XML feed. It is the lowest- friction fiat provider in the Suaveplan catalogue: free, no API key required, universal (runs in browsers, Web Workers, Node.js, and Bun), and built on the slim @suaveplan/currency-providers plugin core so every error path, abort-signal composition, and HTTP wrapping is shared with every other sibling provider in the family.

The package exposes a single class, EcbProvider, with stable identity flags (name === "ecb", supportsFiat === true, supportsCrypto === false). It is not auto-registered with any registry — consumers wire it through either new CurrencyConverter({ provider: new EcbProvider() }) or registry.register(new EcbProvider()) against a CurrencyProviderRegistry from @suaveplan/currency-providers.

Features

  • Free, no authentication — public ECB daily XML feed.
  • ~30 fiat currencies (every major plus many minor European / global pairs).
  • Updates around 16:00 CET each business day; weekends and bank holidays return the last published snapshot.
  • Default base is EUR; any non-EUR base is synthesised by inverting through the EUR rates so callers never have to triangulate manually.
  • Optional targets filter so consumers iterating a small basket avoid caching the full 30-currency table.
  • Typed CurrencyProviderError on every failure path: HTTP_ERROR for non- 2xx upstream responses, PARSE_ERROR for an unparseable body, UNSUPPORTED_BASE when the requested base is missing from the EUR-quoted table, UNKNOWN_CURRENCY when every requested target is absent.
  • AbortSignal propagation — RateFetchOptions.signal is forwarded through the slim core's httpRequest() so callers can cancel in-flight requests.
  • Injectable FetchLike (from @suaveplan/types) — tests pass a recording closure; production accepts the default globalThis.fetch.
  • Zero runtime dependencies beyond @suaveplan/currency-providers / @suaveplan/currency / @suaveplan/types / tslib.

Usage

Basic Example

import { CurrencyConverter } from "@suaveplan/currency";
import { EcbProvider } from "@suaveplan/currency-provider-ecb";

const converter = new CurrencyConverter({
    provider: new EcbProvider(),
});

const rate = await converter.getRate("EUR", "USD");
console.log(`1 EUR = ${rate} USD`);

Advanced Example

Wiring through the slim core's registry with a custom baseUrl (e.g. an internal proxy that caches the upstream XML at the edge) and a caller-side AbortSignal so a UI cancellation aborts the in-flight HTTP request.

import { CurrencyConverter } from "@suaveplan/currency";
import { CurrencyProviderRegistry } from "@suaveplan/currency-providers";
import { EcbProvider } from "@suaveplan/currency-provider-ecb";

const registry = new CurrencyProviderRegistry();
registry.register(
    new EcbProvider({
        baseUrl: "https://cdn.internal.example.com/ecb/daily.xml",
    }),
);

const controller = new AbortController();
setTimeout(() => controller.abort(new Error("user navigated away")), 5_000);

const provider = registry.get("ecb");
const result = await provider.fetchRates({
    base: "USD",
    targets: ["EUR", "GBP", "JPY"],
    signal: controller.signal,
});

console.log(result.base, result.rates, result.timestamp);

API Reference

class EcbProvider implements CurrencyProvider

constructor(config?: NoKeyConfig)

  • config.baseUrl?: string — override the default ECB endpoint (https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml).
  • config.fetch?: FetchLike — injectable fetch implementation; defaults to globalThis.fetch resolved at call time.

Read-only fields

  • name: "ecb" — stable identifier surfaced as CurrencyProvider.name.
  • supportsFiat: true
  • supportsCrypto: false

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

Fetches and returns the latest ECB rates. options.base defaults to "EUR"; when set to any other currency the result is synthesised by inverting through the EUR-quoted feed. options.targets filters the returned map. options.signal is forwarded to the underlying HTTP call.

Throws CurrencyProviderError with one of:

  • HTTP_ERROR — non-2xx response (raised by the slim core's httpRequest).
  • PARSE_ERROR — response body contained no parseable currency tags.
  • UNSUPPORTED_BASE — caller asked for a non-EUR base that is not in the EUR-quoted feed.
  • UNKNOWN_CURRENCY — every entry in options.targets was absent from the resolved rate map.

Implementation Notes

  • The ECB endpoint always publishes EUR-based rates. Non-EUR bases are derived purely arithmetically — there is no second HTTP call. This is the same algorithm the upstream @suaveplan/currency-providers shipped before the 2026-05-17 slim refactor split it into siblings.
  • XML parsing uses a regex (currency='([A-Z]+)'\s+rate='([\d.]+)') rather than a full XML parser to keep the package zero-runtime-dep beyond the slim core and @suaveplan/currency. The ECB feed shape is stable; if the upstream format changes, the failure surfaces immediately as PARSE_ERROR.
  • Rates are not available on weekends or ECB bank holidays; the feed returns the last published snapshot. Consumers that need same-day freshness should layer @suaveplan/currency's staleDurationMs on top.
  • globalThis.fetch is resolved at call time, not constructor time, so test setups that replace the global between construction and the first fetchRates() call still work.
  • The package declares "sideEffects": false and ships explicit named barrels per AGENTS.md §6a; tree-shaking unused exports is safe.