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,122 @@
# Banking routes
## Purpose
This module builds the OpenAPIHono sub-app exposed by the banking
plugin. It takes a `BankingRoutesDeps` containing the
`BankingService` and an optional typed `emitEvent` callback, and
returns a Hono app that mounts `/banks` and `/accounts` route trees.
Every route consumes the Zod schemas from `schemas/schemas.ts` so the
generated OpenAPI document is correct, every error path maps a
`BankingError` to a precise HTTP status, and every state-changing
route fires a typed event through `emitEvent` when supplied.
## Features
- 14 HTTP routes total: 7 on `/banks` (list, create, by-id,
by-sort-code, search, update, soft-delete) and 7 on `/accounts`
(list, create, by-id, by-number, by-user, get-owner, update,
delete, status, transfer, add-funds, subtract-funds — list above
combines the count).
- Uniform error mapping: a single `errorBody(err)` helper maps every
thrown `BankingError` to the canonical status; non-`BankingError`
throws become `500`.
- Typed events emitted on every state transition; the `emitEvent`
callback's signature is constrained by the `BankingEventMap` union
so there are no casts at emit sites.
- Discriminated `RepositoryResponse` outcomes from `transferFunds`
and `subtractFunds` are flattened to `(status, body)` pairs without
exception-driven control flow.
## Usage: Basic
```ts
import { createBankingRoutes, BankingService } from "@suaveplan/api-plugin-banking";
const service = new BankingService(adapter, config);
const app = createBankingRoutes({ service });
host.route("/banking", app);
```
## Usage: Advanced
Wire the event emitter to a typed event bus so audit, projection, and
notification plugins observe every transition:
```ts
import { createBankingRoutes } from "@suaveplan/api-plugin-banking";
import type { BankingEventMap } from "@suaveplan/api-plugin-banking";
import { EventBus } from "@suaveplan/events";
const bus = new EventBus<BankingEventMap>();
bus.on("banking:transfer:succeeded", (e) => audit.log(e));
const app = createBankingRoutes({
service,
emitEvent: (type, payload) => bus.emit(type, payload),
});
```
The OpenAPI spec is generated by `@hono/zod-openapi` from each route's
`request` / `responses` Zod schemas — call `OpenAPIHono.doc("/openapi.json", { ... })` on the parent app to expose it.
## API Reference
### `interface BankingRoutesDeps`
- `service: BankingService` — the coordinator; routes resolve every
repository call through it so policy and event emission stay in
one place.
- `emitEvent?: <K extends keyof BankingEventMap>(type: K, payload) => void` —
optional typed event emitter; if absent, every state transition
silently proceeds without fan-out.
### `createBankingRoutes(deps): OpenAPIHono`
Returns the sub-app. Mount it at any prefix; the route paths
themselves are relative.
### Route catalogue
`GET /banks` — list (filterable by `status`, `site_id`, paginated).
`POST /banks` — create. Emits `banking:bank:created`.
`GET /banks/{id}` — by id (404 on miss).
`PATCH /banks/{id}` — update. Emits `banking:bank:updated`.
`DELETE /banks/{id}` — soft-delete. Emits `banking:bank:soft-deleted`.
`GET /banks/sort-code/{sortCode}` — by `XX-XX-XX` sort code.
`GET /banks/search/{query}` — case-insensitive name search.
`GET /accounts` — list (filterable by `user_id`, `bank_id`, `status`).
`POST /accounts` — create. Emits `banking:account:created`.
`GET /accounts/{id}` — by id.
`PUT /accounts/{id}` — update. Emits `banking:account:updated`.
`DELETE /accounts/{id}` — delete. Emits `banking:account:deleted`.
`PATCH /accounts/{id}/status` — status FSM. Emits
`banking:account:status-changed`.
`POST /accounts/transfer` — fund transfer. Emits
`banking:transfer:succeeded` or `banking:transfer:failed`.
`POST /accounts/{id}/add-funds` — add funds.
`POST /accounts/{id}/subtract-funds` — subtract funds.
`GET /accounts/by-number/{accountNumber}` — by numeric account-number.
`GET /accounts/user/{userId}` — list a user's accounts.
`GET /accounts/{id}/owner` — owner user-id lookup.
## Implementation Notes
Routes that may produce both 200/204 success and an error status code
return `c.json(body, statusNumber)` rather than relying on the Hono
default — this lets the `errorBody` helper map every
`BankingError.code` to a precise status without re-running the
narrowing logic in each route handler.
`emitEvent` is invoked **after** the underlying repository write
succeeds. Failure-mode events (`banking:transfer:failed`) are
emitted on the negative branch so projections can record the attempt
even when no balance changed. This matches the upstream package's
contract that "every transfer attempt is observable".
The route layer does NOT re-parse request bodies with `safeParse` —
`@hono/zod-openapi` already validates via `c.req.valid("json")` before
the handler runs, and any validation failure short-circuits with a
400 before the handler executes. Catching at the handler level only
sees thrown `BankingError`s and unexpected database failures.