# Banking Zod schemas ## Purpose This module owns every Zod schema for the public boundary of `@suaveplan/api-plugin-banking`: bank records, bank-account records, the request bodies for create / update / status / transfer / fund-op routes, and the literal-union status/type enums. The schemas are the single source of truth for both runtime validation at the route boundary and the TypeScript types consumed by the service and repository layers. ## Features - 11 Zod schemas covering the full public surface (`BankSchema`, `CreateBankSchema`, `UpdateBankSchema`, `BankAccountSchema`, `CreateBankAccountSchema`, `UpdateBankAccountSchema`, `TransferFundsSchema`, `FundsOperationSchema`, `UpdateAccountStatusSchema`, plus `SortCodeSchema` and the three literal-union enums). - Default values applied via `.default(...)` so omitted fields land at their canonical state at parse time (e.g. `status` defaults to `4` active, `balance` defaults to `0`). - Numeric guards: `account_number` and `sort_code` are positive integers; `amount` on every fund movement is `z.number().positive()` so zero-/negative-amount requests reject at the boundary before reaching the service. ## Usage: Basic ```ts import { CreateBankSchema } from "@suaveplan/api-plugin-banking"; const parsed = CreateBankSchema.parse({ name: "Bank of Genesis", sort_code: 123456, }); ``` ## Usage: Advanced The route layer wires each schema to `@hono/zod-openapi` so the OpenAPI document is generated from the Zod types directly: ```ts import { createRoute, OpenAPIHono } from "@hono/zod-openapi"; import { CreateBankAccountSchema, BankAccountSchema, TransferFundsSchema, } from "@suaveplan/api-plugin-banking"; const createAccountRoute = createRoute({ method: "post", path: "/", request: { body: { content: { "application/json": { schema: CreateBankAccountSchema }, }, }, }, responses: { 201: { description: "Created", content: { "application/json": { schema: BankAccountSchema }, }, }, }, }); ``` For typed service-layer parsing (without re-parsing in routes): ```ts import { TransferFundsSchema, type TransferFunds } from "@suaveplan/api-plugin-banking"; const cmd: TransferFunds = TransferFundsSchema.parse(raw); await service.transfer(cmd); ``` ## API Reference - `BankStatusSchema` / `BankAccountStatusSchema` / `BankAccountTypeSchema` — literal unions over the integer status / type codes. - `BankSchema` / `BankAccountSchema` — full normalised record shape returned by repositories (RecordIds flattened to `table:id` strings). - `CreateBankSchema` / `CreateBankAccountSchema` — required + optional input fields, with `status`/`balance`/`overdraft` defaulted. - `UpdateBankSchema` / `UpdateBankAccountSchema` — `.partial()` over a whitelisted subset of writable fields. Any field outside the whitelist is rejected by the repository's `validateFieldName` guard rather than silently dropped. - `TransferFundsSchema`, `FundsOperationSchema`, `UpdateAccountStatusSchema` — fund-movement and status-change request bodies. - `SortCodeSchema` — `XX-XX-XX` UK-style sort-code string used by `GET /banks/sort-code/:sortCode`. Every schema exports a matching `z.infer<>` alias under the same name prefixed with the bare type (e.g. `Bank`, `CreateBank`, `TransferFunds`). ## Implementation Notes Banking schemas are deliberately **local** to this package rather than re-exported from `@suaveplan/api-schemas`: the upstream monorepo kept them in the universal schemas package, but the genesis convention (rule 4 in `CLAUDE.md`) is "NEVER re-export external deps". Owning them here also lets the package evolve the schemas (e.g. multi-currency amount, ledger entries) without forcing a schemas-package version bump on every consumer. The `RecordIdSchema` and `TimestampSchema` placeholders are kept intentionally narrow (non-empty strings) because the SurrealDB layer normalises both formats before they cross this boundary. Stricter validation would reject legitimate records. `balance` is `z.number().default(0)`, not `z.number().nonnegative()` — the overdraft mechanism intentionally allows negative balances up to the per-account `overdraft` ceiling. `INSUFFICIENT_FUNDS` is enforced in the service layer, not at the schema boundary, because the threshold depends on `account.overdraft` which is not visible to the schema.