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