first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
# `schemas` — Zod boundary for `@suaveplan/currency`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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<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.