4.4 KiB
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, plusSortCodeSchemaand the three literal-union enums). - Default values applied via
.default(...)so omitted fields land at their canonical state at parse time (e.g.statusdefaults to4active,balancedefaults to0). - Numeric guards:
account_numberandsort_codeare positive integers;amounton every fund movement isz.number().positive()so zero-/negative-amount requests reject at the boundary before reaching the service.
Usage: Basic
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:
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):
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 totable:idstrings).CreateBankSchema/CreateBankAccountSchema— required + optional input fields, withstatus/balance/overdraftdefaulted.UpdateBankSchema/UpdateBankAccountSchema—.partial()over a whitelisted subset of writable fields. Any field outside the whitelist is rejected by the repository'svalidateFieldNameguard rather than silently dropped.TransferFundsSchema,FundsOperationSchema,UpdateAccountStatusSchema— fund-movement and status-change request bodies.SortCodeSchema—XX-XX-XXUK-style sort-code string used byGET /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.