4.9 KiB
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 thrownBankingErrorto the canonical status; non-BankingErrorthrows become500. - Typed events emitted on every state transition; the
emitEventcallback's signature is constrained by theBankingEventMapunion so there are no casts at emit sites. - Discriminated
RepositoryResponseoutcomes fromtransferFundsandsubtractFundsare flattened to(status, body)pairs without exception-driven control flow.
Usage: Basic
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:
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 BankingErrors and unexpected database failures.