Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View 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`.