5.9 KiB
OpenExchangeRatesProvider
(no submodules)
Purpose
OpenExchangeRatesProvider is the @suaveplan/currency provider for
openexchangerates.org. It wraps the
/latest.json bulk-fetch endpoint, validates the upstream payload, and
surfaces every failure mode through the typed CurrencyProviderError
hierarchy from @suaveplan/currency-providers. The provider is universal
— it relies only on globalThis.fetch (or an injected FetchLike) and the
slim-core httpRequest() helper, so it runs unchanged in Node, Bun, browsers,
and Web Workers.
Features
- Implements
CurrencyProviderwithname === "open-exchange-rates",supportsFiat === true,supportsCrypto === false. - Constructor takes the OXR
app_idasapiKey. Empty string raisesCurrencyProviderError(MISSING_CONFIG)synchronously. - Free-tier safe by default: any
RateFetchOptions.base !== "USD"raisesCurrencyProviderError(UNSUPPORTED_BASE)before any network call. Paid-plan callers opt in withallowNonUsdBase: true. - Routes every request through
@suaveplan/currency-providershttpRequest()so non-2xx responses surfaceCurrencyProviderError(HTTP_ERROR)withstatusandproviderNamepopulated. CallerAbortSignalfromRateFetchOptions.signalis forwarded. - Validates the upstream
{ base, rates, timestamp }shape. Mismatches raiseCurrencyProviderError(PARSE_ERROR). - Detects the OXR 2xx error-payload shape (
{ error: true, status, message }) and raisesCurrencyProviderError(UPSTREAM_ERROR)withupstreamMessagecarrying the upstreammessagefield. - Filters the rates map to
RateFetchOptions.targets. Missing target codes raiseCurrencyProviderError(UNKNOWN_CURRENCY). - Converts the OXR Unix-seconds timestamp to milliseconds.
- Injectable
FetchLikefor offline testing; defaults toglobalThis.fetch.
Usage: Basic Example
import { OpenExchangeRatesProvider } from "@suaveplan/currency-provider-open-exchange-rates";
const provider = new OpenExchangeRatesProvider({
apiKey: process.env.OXR_APP_ID ?? "",
});
const result = await provider.fetchRates({ base: "USD" });
console.log(result.base, result.rates.EUR, result.timestamp);
Usage: Advanced Example
Wiring the provider into CurrencyConverter with a paid-tier app_id, custom
baseUrl for staging, and AbortSignal-driven cancellation:
import { CurrencyConverter } from "@suaveplan/currency";
import { OpenExchangeRatesProvider } from "@suaveplan/currency-provider-open-exchange-rates";
const oxr = new OpenExchangeRatesProvider({
apiKey: process.env.OXR_APP_ID!,
allowNonUsdBase: true,
baseUrl: process.env.OXR_BASE_URL ?? "https://openexchangerates.org/api",
});
const converter = new CurrencyConverter({
provider: oxr,
staleDurationMs: 30 * 60 * 1000, // 30 minutes
});
const controller = new AbortController();
setTimeout(() => controller.abort(new Error("timeout")), 5000);
const rates = await oxr.fetchRates({
base: "EUR",
targets: ["USD", "GBP", "JPY"],
signal: controller.signal,
});
The slim-core CurrencyProviderRegistry is also supported when the consumer
wires multiple providers behind a single lookup surface — see
@suaveplan/currency-providers README.
API Reference
OpenExchangeRatesProviderConfig
Extends ApiKeyConfig from @suaveplan/currency-providers:
apiKey: string— required. The OXRapp_id.baseUrl?: string— defaults to"https://openexchangerates.org/api".fetch?: FetchLike— defaults toglobalThis.fetch.allowNonUsdBase?: boolean— defaults tofalse. Whenfalse, any non-USD base raisesUNSUPPORTED_BASEbefore any network call.
class OpenExchangeRatesProvider
Properties
readonly name: "open-exchange-rates"readonly supportsFiat: truereadonly supportsCrypto: false
constructor(config: OpenExchangeRatesProviderConfig)
Throws CurrencyProviderError(MISSING_CONFIG) when config.apiKey === "".
fetchRates(options?: RateFetchOptions): Promise<RateFetchResult>
Issues GET ${baseUrl}/latest.json?app_id=<apiKey>&base=<base> via
httpRequest(). Returns { base, rates, timestamp } with timestamp in
milliseconds.
Throws:
MISSING_CONFIG— from the constructor whenapiKey === "".UNSUPPORTED_BASE— whenoptions.base !== "USD"andallowNonUsdBaseis not set totrue.HTTP_ERROR— viahttpRequest()for any non-2xx response.PARSE_ERROR— when the response body is not JSON, not an object, or is missing one ofbase/rates/timestampin the expected shape.UPSTREAM_ERROR— when the response is 2xx but the body is the OXR error shape ({ error: true, status, message }).UNKNOWN_CURRENCY— whenoptions.targetscontains a code not present in the upstreamratesmap.
Implementation Notes
- The OXR free tier restricts the base currency to USD; the provider enforces
this client-side so the failure surfaces as a typed
UNSUPPORTED_BASErather than a generic upstream error. Paid plans passallowNonUsdBase: trueto opt out. - OXR returns
timestampin Unix seconds; the provider multiplies by 1000 so everyCurrencyProvideranswer is in milliseconds, matchingRateFetchResult.timestamp's contract. - OXR's error-payload shape is returned with a 2xx HTTP status, which would
otherwise pass
httpRequest()'s non-2xx check silently. The provider detects{ error: true, … }after parsing and surfacesUPSTREAM_ERRORwithstatusandupstreamMessagepopulated. - The provider does not retry, dedupe, or rate-limit by itself — the slim
core's
## Out of scopedefers those concerns to a cross-cutting follow-up wave so every sibling provider gains them in lockstep. apiKeyisencodeURIComponent-wrapped in the query string to prevent injection when callers pass keys containing reserved characters.