first commit
This commit is contained in:
103
docs/api-stack/api/api-plugin-vault/src/routes/build-routes.md
Normal file
103
docs/api-stack/api/api-plugin-vault/src/routes/build-routes.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# routes/build-routes
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Builds the 5 OpenAPI route entries that `createVaultPlugin` mounts on
|
||||
the host's Hono app: list, create, read, update, delete. Each entry is
|
||||
a `PluginRouteEntry` — a `RouteConfig` plus a handler — with full Zod
|
||||
inference through the `RouteBuilder` chain. The OpenAPI document and
|
||||
the runtime routing surface cannot drift because both are produced from
|
||||
the same builder output.
|
||||
|
||||
## Features
|
||||
|
||||
- Five routes — `GET /vault/items`, `POST /vault/items`,
|
||||
`GET /vault/items/:id`, `PATCH /vault/items/:id`,
|
||||
`DELETE /vault/items/:id` — each tagged `vault` for grouped doc
|
||||
display.
|
||||
- Per-handler upstream `requireAuth()` invocation — every route is
|
||||
gated; anonymous calls produce `AUTH_UNAUTHENTICATED` (401) via the
|
||||
auth plugin's middleware.
|
||||
- Per-request open/close lifecycle: every route opens the principal's
|
||||
vault via `vaultService.openForPrincipal`, performs the operation,
|
||||
and closes the handle in `finally` so the vault is locked again
|
||||
before the response returns.
|
||||
- Anti-enumeration policy baked in: any reference to an item id that
|
||||
does not exist OR is owned by another principal raises
|
||||
`VAULT_PLUGIN_ITEM_NOT_FOUND` (404) — NEVER 403. This prevents an
|
||||
attacker from probing the existence of item ids owned by other
|
||||
principals.
|
||||
- Size enforcement at the handler boundary, not at the Zod schema, so
|
||||
the error message can include the exact byte count and the configured
|
||||
`maxItemSizeBytes`.
|
||||
- 4 OTel counters + 1 histogram emitted per operation (success +
|
||||
failure outcomes).
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { buildVaultRoutes } from "@suaveplan/api-plugin-vault";
|
||||
|
||||
const entries = buildVaultRoutes(vaultService, config);
|
||||
definePluginRoutes(entries, {
|
||||
app,
|
||||
registry: openAPIRegistry,
|
||||
pluginName: "vault",
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
// Drop one of the 5 routes by filtering before registration — operators
|
||||
// running a read-only mirror can elide the mutation routes.
|
||||
const entries = buildVaultRoutes(vaultService, config).filter((e) =>
|
||||
e.route.method === "get"
|
||||
);
|
||||
definePluginRoutes(entries, { app, registry, pluginName: "vault" });
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `buildVaultRoutes(vaultService, config): PluginRouteEntry[]`
|
||||
|
||||
Returns 5 entries in registration order: list, create, get, update,
|
||||
delete. Every entry has:
|
||||
|
||||
- `route` — a built `RouteConfig` carrying the full Zod schema chain.
|
||||
- `handler` — async function taking a Hono `Context` and returning
|
||||
`Promise<Response>`.
|
||||
|
||||
The handler wraps the actual operation in a `requireAuth()` gate; the
|
||||
auth plugin's `Principal` is read from `c.get("user")`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The `RouteBuilder` `.params(VaultItemParamsSchema)` registers the
|
||||
`{id}` URL parameter on the OpenAPI doc using the OpenAPI convention
|
||||
(`{id}`); Hono's runtime router consumes the same builder output, so
|
||||
the path matches both surfaces.
|
||||
- The handler explicitly normalises the wire envelope: every success
|
||||
response is `{ success: true, data }` (list also includes
|
||||
`meta: { total }`). The shared error envelope is contributed by the
|
||||
`StandardResponses` block from `@suaveplan/api-openapi` — handlers
|
||||
never construct an error envelope themselves; they `throw` a
|
||||
`VaultPluginError` and let the host's error-handler middleware
|
||||
serialise it.
|
||||
- `Buffer.byteLength(body, "utf8")` is used for size enforcement so
|
||||
the byte count matches what an HTTP client would actually transmit
|
||||
(rather than the JavaScript string `length` which counts UTF-16
|
||||
code units).
|
||||
- The defensive null-check on `c.get("user")` inside `withOpenVault`
|
||||
exists because `requireAuth()` runs in a separate middleware step
|
||||
and TypeScript cannot narrow across the await boundary. Production
|
||||
cannot reach the branch (requireAuth has already thrown), but the
|
||||
branch is reachable from unit tests that bypass the guard.
|
||||
- Coverage note: the `requireAuthThen` `response === undefined` branch
|
||||
exists for TypeScript narrowing; the unit tests assert it raises
|
||||
`VAULT_PLUGIN_FORBIDDEN` by injecting a stubbed `requireAuth` that
|
||||
returns without invoking `next`.
|
||||
Reference in New Issue
Block a user