# routes/build-routes _(no submodules)_ ## 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`. 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`.