# @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`. ## 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-… ## 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 (`..`) 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