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
CurrencyProviderwith stable identity flags (name = "fixer",supportsFiat = true,supportsCrypto = false). - Constructor takes the slim core's shared
ApiKeyConfig(apiKey, optionalbaseUrl, optional injectablefetch). An emptyapiKeyraisesCURRENCY_PROVIDER.MISSING_CONFIGsynchronously before any network call is issued, preventing wasted quota. - Delegates HTTP transport to the slim core's
httpRequesthelper —AbortSignalcomposition, non-2xx → typedHTTP_ERRORmapping, andproviderName/statuscarry 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 upstreamerror.infois preserved onupstreamMessageso diagnostics survive. - Distinguishes parse failures (non-JSON, non-object JSON, success
envelope missing required fields) under a single
PARSE_ERRORcode. - 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 = falsereadonly 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 whenapiKeyis empty.HTTP_ERROR— non-2xx upstream response (viahttpRequest).PARSE_ERROR— body is not JSON, parsed value is not an object, or asuccess: trueenvelope is missingbase/rates/timestamp.UNSUPPORTED_BASE— upstream code105(free-tier base restriction).UNKNOWN_CURRENCY— upstream code201(invalid_base_currency) or202(invalid_currency_codes).UPSTREAM_ERROR— any othersuccess: falseenvelope, including the case whereerroris 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.codeinteger is the discriminator, noterror.type— the integer is one comparison and avoids string allocation. The human-readableerror.infois preserved onupstreamMessagefor diagnostics. - The
apiKeynon-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. fetchRatesforwardsoptions.signalthroughhttpRequestso 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 withCurrencyProviderRegistry.