# 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 ```ts 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. ```ts 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` 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.