Initial Commit
This commit is contained in:
122
packages/api/api-plugin-banking/src/routes/build-routes.md
Normal file
122
packages/api/api-plugin-banking/src/routes/build-routes.md
Normal 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.
|
||||
Reference in New Issue
Block a user