Files
suaveplan/packages/money/finance/currency/src/schemas/schemas.md
2026-05-27 23:47:53 +01:00

4.5 KiB

schemas — Zod boundary for @suaveplan/currency

(no submodules)

Purpose

The schemas module is the package's input-validation boundary. Every public options bundle that crosses into a CurrencyConverter method or a reference provider is parsed against a Zod schema first; failures raise a typed CurrencyError with code CURRENCY.INVALID_CONFIG (constructor arguments, virtual-currency entries) or CURRENCY.INVALID_OPTIONS (per-call options, RateFetchOptions). The Zod issue array is attached to error.context.issues so downstream loggers and APIs can surface field-level diagnostics without re-running validation.

Centralising validation here keeps the converter and provider implementations focused on behaviour: by the time control enters convert or fetchRates, every option is already the shape the implementation expects, and the implementation can rely on TypeScript's structural typing without runtime double-checks.

Features

  • ConverterConfigSchema — validates the constructor argument (provider shape, optional fallback array, positive integer TTL, currency-code default base, virtual-currency record).
  • ConvertOptionsSchema — validates per-call options (rounding enum, optional fresh: boolean, optional AbortSignal).
  • VirtualCurrencyConfigSchema — rate > 0, non-empty name / symbol, optional exponent ∈ [0, 18] / issuerId.
  • RateFetchOptionsSchema — optional base, targets array of currency codes, optional AbortSignal.
  • parseConverterConfig / parseConvertOptions / parseVirtualCurrencyConfig / parseRateFetchOptions — safe-parse helpers that raise the appropriate CurrencyError on failure.

Usage: Basic Example

import { parseConvertOptions } from "@suaveplan/currency";

const options = parseConvertOptions({ rounding: "half-up", fresh: false });
// options is typed as ConvertOptions; bad shapes raise CURRENCY.INVALID_OPTIONS.

Usage: Advanced Example

A consumer wraps API input through the schemas before delegating to the converter, surfacing Zod issues as user-facing field errors:

import {
    CurrencyError,
    CURRENCY_CODES,
    parseConverterConfig,
    CurrencyConverter,
} from "@suaveplan/currency";

export function makeConverter(raw: unknown) {
    try {
        const config = parseConverterConfig(raw);
        return new CurrencyConverter(config);
    } catch (err) {
        if (
            err instanceof CurrencyError &&
            err.code === CURRENCY_CODES.INVALID_CONFIG
        ) {
            const issues = (err.context?.issues as { path: (string | number)[]; message: string }[] | undefined) ?? [];
            throw new Error(
                `Bad converter config: ${issues
                    .map((i) => `${i.path.join(".")}: ${i.message}`)
                    .join(", ")}`
            );
        }
        throw err;
    }
}

API Reference

Export Kind Description
ConverterConfigSchema Zod schema Validates ConverterConfig.
ConvertOptionsSchema Zod schema Validates ConvertOptions.
VirtualCurrencyConfigSchema Zod schema Validates a single VirtualCurrencyConfig.
RateFetchOptionsSchema Zod schema Validates RateFetchOptions.
parseConverterConfig(input) (unknown) => ConverterConfig Raises CurrencyError({ code: CURRENCY.INVALID_CONFIG }) on failure.
parseConvertOptions(input) (unknown) => ConvertOptions Raises CurrencyError({ code: CURRENCY.INVALID_OPTIONS }) on failure.
parseVirtualCurrencyConfig(input) (unknown) => VirtualCurrencyConfig Raises CurrencyError({ code: CURRENCY.INVALID_CONFIG }) on failure.
parseRateFetchOptions(input) (unknown) => RateFetchOptions Raises CurrencyError({ code: CURRENCY.INVALID_OPTIONS }) on failure.

Implementation Notes

The signal field uses a z.custom<AbortSignal> because AbortSignal is a host object — Zod cannot introspect it. The custom predicate accepts any object exposing an aborted property, which matches both the platform AbortSignal and consumer-side polyfills. provider is validated structurally (name, supportsCrypto, supportsFiat, optional supportsVirtual, function-typed fetchRates); we deliberately do not descend into the function body. Currency codes are validated as non-empty strings — the converter checks against the @suaveplan/money registry at use time, so unknown codes raise CURRENCY.UNSUPPORTED_CURRENCY (via getCurrencyOrThrow) rather than failing schema validation.