Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
# 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.