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
targetsfilter so consumers iterating a small basket avoid caching the full 30-currency table. - Typed
CurrencyProviderErroron every failure path:HTTP_ERRORfor non- 2xx upstream responses,PARSE_ERRORfor an unparseable body,UNSUPPORTED_BASEwhen the requested base is missing from the EUR-quoted table,UNKNOWN_CURRENCYwhen every requested target is absent. AbortSignalpropagation —RateFetchOptions.signalis forwarded through the slim core'shttpRequest()so callers can cancel in-flight requests.- Injectable
FetchLike(from@suaveplan/types) — tests pass a recording closure; production accepts the defaultglobalThis.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— injectablefetchimplementation; defaults toglobalThis.fetchresolved at call time.
Read-only fields
name: "ecb"— stable identifier surfaced asCurrencyProvider.name.supportsFiat: truesupportsCrypto: 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'shttpRequest).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 inoptions.targetswas 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-providersshipped 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 asPARSE_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'sstaleDurationMson top. globalThis.fetchis resolved at call time, not constructor time, so test setups that replace the global between construction and the firstfetchRates()call still work.- The package declares
"sideEffects": falseand ships explicit named barrels per AGENTS.md §6a; tree-shaking unused exports is safe.