4.1 KiB
4.1 KiB
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 taggedvaultfor grouped doc display. - Per-handler upstream
requireAuth()invocation — every route is gated; anonymous calls produceAUTH_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 infinallyso 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
import { buildVaultRoutes } from "@suaveplan/api-plugin-vault";
const entries = buildVaultRoutes(vaultService, config);
definePluginRoutes(entries, {
app,
registry: openAPIRegistry,
pluginName: "vault",
});
Usage: Advanced
// 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 builtRouteConfigcarrying the full Zod schema chain.handler— async function taking a HonoContextand returningPromise<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 includesmeta: { total }). The shared error envelope is contributed by theStandardResponsesblock from@suaveplan/api-openapi— handlers never construct an error envelope themselves; theythrowaVaultPluginErrorand 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 stringlengthwhich counts UTF-16 code units).- The defensive null-check on
c.get("user")insidewithOpenVaultexists becauserequireAuth()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
requireAuthThenresponse === undefinedbranch exists for TypeScript narrowing; the unit tests assert it raisesVAULT_PLUGIN_FORBIDDENby injecting a stubbedrequireAuththat returns without invokingnext.