Files
skills/docs/api-stack/api/api-openapi/README.md
2026-05-29 08:20:56 +01:00

224 lines
11 KiB
Markdown

# @suaveplan/api-openapi - OpenAPI 3.1 registry, typed RouteBuilder, schema registry, and document merger for the Suaveplan API. Single source of truth that wires into Hono via OpenAPIRegistry.applyTo(app) — eliminating drift between the introspection registry and the served /openapi.json. Ships StandardResponses spanning the full SuaveplanError statusCode range (400/401/403/404/409/422/429/500/503/504), an ErrorResponseSchema carrying the cause field of SuaveplanError.toJSON(), an instance-only SchemaRegistry (no global singleton) with extend/partial/pick/omit guarded by OPENAPI_INVALID_BASE_SCHEMA, mergeOpenAPIDocuments / prefixPaths / filterPathsByTag / removeInternalRoutes transforms, and a definePluginRoutes helper that registers atomically against both Hono and the registry.
OpenAPI 3.1 registry, typed `RouteBuilder`, schema registry, and
document merger for the Suaveplan API. Single source of truth that
wires into Hono via `OpenAPIRegistry.applyTo(app)`, eliminating drift
between registry introspection and the served `/openapi.json`.
<!-- begin:auto-docs -->
## Documentation tree
- [emitter](./src/emitter/emitter.md) — `createOpenapiEmitter` is the aggregator the spec calls for: it ingests every plugin's manifest, every registered route…
- [emitter](./src/emitter/emitter.md) — `createOpenapiEmitter` is the aggregator the spec calls for: it ingests every plugin's manifest, every registered route…
- [endpoints](./src/endpoints/endpoints.md) — `endpoints` mounts the four HTTP routes the design doc reserves for the OpenAPI surface — `/openapi.json`, `/openapi.ya…
- [endpoints](./src/endpoints/endpoints.md) — `endpoints` mounts the four HTTP routes the design doc reserves for the OpenAPI surface — `/openapi.json`, `/openapi.ya…
- [error-codes](./src/error-codes/error-codes.md) — Defines the stable error-code registry and typed error class for `@suaveplan/api-openapi`. Every public failure path in…
- [error-codes](./src/error-codes/error-codes.md) — Defines the stable error-code registry and typed error class for `@suaveplan/api-openapi`. Every public failure path in…
- [merger](./src/merger/merger.md) — OpenAPI document transforms — merge, prefix, filter, redact internal routes. Multi-plugin servers compose the final pub…
- [merger](./src/merger/merger.md) — OpenAPI document transforms — merge, prefix, filter, redact internal routes. Multi-plugin servers compose the final pub…
- [namespacing](./src/namespacing/namespacing.md) — The `namespacing` module turns a plugin's structural identity (`name`, `displayName`) plus a registered route's `(metho…
- [namespacing](./src/namespacing/namespacing.md) — The `namespacing` module turns a plugin's structural identity (`name`, `displayName`) plus a registered route's `(metho…
- [plugin-integration](./src/plugin-integration/plugin-integration.md) — `definePluginRoutes` is the single helper plugins call from their `onInit` to register routes. Every call atomically up…
- [plugin-integration](./src/plugin-integration/plugin-integration.md) — `definePluginRoutes` is the single helper plugins call from their `onInit` to register routes. Every call atomically up…
- [registry](./src/registry/registry.md) — Centralised OpenAPI 3.1 registry that holds `info`, `servers`, `securitySchemes`, `tags`, and `routes`. Acts as the sin…
- [registry](./src/registry/registry.md) — Centralised OpenAPI 3.1 registry that holds `info`, `servers`, `securitySchemes`, `tags`, and `routes`. Acts as the sin…
- [route-factory](./src/route-factory/route-factory.md) — Typed route construction utilities for `@hono/zod-openapi`. The `RouteBuilder` is a fluent generic that threads `TParam…
- [route-factory](./src/route-factory/route-factory.md) — Typed route construction utilities for `@hono/zod-openapi`. The `RouteBuilder` is a fluent generic that threads `TParam…
- [schema-registry](./src/schema-registry/schema-registry.md) — Instance-based registry for reusable Zod schemas. Each schema is bound to a stable name and may be derived into related…
- [common-schemas](./src/schema-registry/common-schemas.md) — Library of common Zod schemas decorated with OpenAPI `example` metadata. Covers the universal HTTP query / path / pagin…
- [schema-registry](./src/schema-registry/schema-registry.md) — Instance-based registry for reusable Zod schemas. Each schema is bound to a stable name and may be derived into related…
- [strictness](./src/strictness/strictness.md) — `strictness` resolves the host's missing-schema policy for the OpenAPI emitter and applies it consistently across every…
- [strictness](./src/strictness/strictness.md) — `strictness` resolves the host's missing-schema policy for the OpenAPI emitter and applies it consistently across every…
- [types](./src/types/openapi.md) — Structural TypeScript types for OpenAPI 3.1 documents, supporting shapes (info / server / security scheme), registered-…
- [openapi](./src/types/openapi.md) — Structural TypeScript types for OpenAPI 3.1 documents, supporting shapes (info / server / security scheme), registered-…
<!-- end:auto-docs -->
## Features
- **`OpenAPIRegistry`** — tracks `info`, `servers`, `securitySchemes`,
`tags`, `routes`. `buildDocument()` emits a full 3.1 document.
`applyTo(app)` wires a Hono GET handler that serves `buildDocument()`
output — no drift possible.
- **`RouteBuilder`** — fluent generic that threads
`TParams`/`TQuery`/`TBody`/`TResponse` through to the final
`TypedRouteConfig`. Hono's `app.openapi(route, handler)` then infers
`c.req.valid()` to the exact Zod type.
- **`StandardResponses`** — covers the full SuaveplanError statusCode
range (400/401/403/404/409/422/429/500/503/504).
- **`ErrorResponseSchema`** — 1:1 match for `SuaveplanError.toJSON()`
including the `cause` field.
- **`SchemaRegistry`** — instance-based (no singleton),
`extend/partial/pick/omit` derivation helpers that raise typed
`OPENAPI.SCHEMA_NOT_FOUND` / `OPENAPI.INVALID_BASE_SCHEMA` errors.
- **`CommonSchemas`** — `uuid`, `email`, `url`, `datetime`,
`positiveInt`, `nonNegativeInt`, `page`, `limit`, `sortOrder`.
- **`mergeOpenAPIDocuments` / `prefixPaths` / `filterPathsByTag` /
`removeInternalRoutes`** — pure document transforms.
- **`definePluginRoutes(routes, { app, registry, pluginName })`** —
atomic dual-surface registration; the only way a plugin should add
routes.
## Quick start
### Build a typed route
```ts
import z from "zod";
import { GET, successResponse } from "@suaveplan/api-openapi/route-factory";
const ListUsers = GET("/users")
.tags("users")
.operationId("listUsers")
.response(successResponse(z.array(z.object({ id: z.string() }))))
.build();
```
### With auth + body validation
```ts
import { POST, successResponse } from "@suaveplan/api-openapi/route-factory";
const CreateUser = POST("/users")
.tags("users")
.operationId("createUser")
.security([{ Bearer: [] }])
.body(z.object({ email: z.string().email() }))
.response(successResponse(z.object({ id: z.string() })))
.build();
```
### Paginated response
```ts
import { GET, paginatedResponse } from "@suaveplan/api-openapi/route-factory";
const ListUsersPaginated = GET("/users")
.tags("users")
.query(
z.object({
page: z.coerce.number().int().min(1).default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
})
)
.response(paginatedResponse(z.object({ id: z.string() })))
.build();
```
### Schema registry derivation
```ts
import z from "zod";
import { createSchemaRegistry } from "@suaveplan/api-openapi/schema-registry";
const reg = createSchemaRegistry();
reg.register(
"User",
z.object({
id: z.string().uuid(),
email: z.string().email(),
password: z.string(),
})
);
const UserPublic = reg.omit("User", "UserPublic", ["password"]);
const UserPatch = reg.partial("User", "UserPatch");
const UserAudit = reg.extend("User", "UserAudit", {
lastSeenAt: z.string().datetime(),
});
```
### Merge multiple plugin docs
```ts
import {
mergeOpenAPIDocuments,
prefixPaths,
removeInternalRoutes,
} from "@suaveplan/api-openapi/merger";
const final = removeInternalRoutes(
mergeOpenAPIDocuments(
baseDoc,
prefixPaths(usersDoc, "/v1"),
prefixPaths(projectsDoc, "/v1")
)
);
```
### Plugin pattern with `definePluginRoutes`
```ts
import { OpenAPIHono } from "@hono/zod-openapi";
import { createOpenAPIRegistry } from "@suaveplan/api-openapi/registry";
import { definePluginRoutes } from "@suaveplan/api-openapi/plugin-integration";
const app = new OpenAPIHono();
const registry = createOpenAPIRegistry({
info: { title: "Public API", version: "1.0.0" },
});
definePluginRoutes(
[
{
route: ListUsers,
handler: (c) => c.json({ success: true as const, data: [] }, 200),
},
{
route: CreateUser,
handler: (c) =>
c.json(
{ success: true as const, data: { id: "1" } },
200
),
},
],
{ app, registry, pluginName: "users" }
);
// Single line connects registry → Hono. No drift possible.
registry.applyTo(app);
```
## Tier
Server-tier (Hono-typed). Runs in Bun + Node.
## Modules
- **`./error-codes`** — stable `OPENAPI.*` registry (7 codes) +
`OpenAPIError` class.
- **`./types`** — structural `OpenAPIDocument`, `Operation`, `PathItem`,
`RegisteredRoute`, security-scheme types, `HTTP_METHODS`.
- **`./registry`** — `OpenAPIRegistry`, `createOpenAPIRegistry`,
`applyTo(app)` (Hono GET handler), default `BearerAuthScheme` /
`ApiKeyScheme`.
- **`./route-factory`** — fluent `RouteBuilder` (`GET`/`POST`/`PUT`/
`PATCH`/`DELETE`), `successResponse` / `errorResponse` /
`paginatedResponse`, `StandardResponses` (400-504),
`ErrorResponseSchema` matching `SuaveplanError.toJSON()`.
- **`./schema-registry`** — instance-based `SchemaRegistry` with
`extend`/`partial`/`pick`/`omit` derivation + `CommonSchemas`.
- **`./merger`** — `mergeOpenAPIDocuments`, `prefixPaths`,
`filterPathsByTag`, `removeInternalRoutes`, `extractOpenAPIDoc`.
- **`./plugin-integration`** — `definePluginRoutes` (atomic
dual-surface registration; the only way a plugin should add routes).
- **`./namespacing`** — `assignOperationIds`, `tagFor`,
`componentPrefix`, `normaliseRoutePath`. Deterministic operationId
(`<plugin>.<method>.<oasPath>`) with `OPENAPI.OPERATION_ID_COLLISION`
on duplicates.
- **`./strictness`** — `resolveStrictness`, `reportMissingSchema`,
`getMissingSchemaCounter`. `warn` vs `refuse-install` policy +
`api.openapi.missing_schema_total` OTel counter.
- **`./emitter`** — `createOpenapiEmitter({ manifests, routes,
authAdapter, hostConfig })`. The Stage-8.6 aggregator: namespacing +
strictness + security inheritance + memoised JSON/YAML/filtered
output. Emits `api.openapi.emission_total` and
`api.openapi.conversion_error_total` OTel counters.
- **`./endpoints`** — `createOpenapiRouter` / `mountOpenapiEndpoints`.
Mounts `GET /openapi.json`, `/openapi.yaml`, `/docs`, `/docs/:plugin`.
## License
MIT