first commit
This commit is contained in:
223
docs/api-stack/api/api-openapi/README.md
Normal file
223
docs/api-stack/api/api-openapi/README.md
Normal file
@@ -0,0 +1,223 @@
|
||||
# @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
|
||||
|
||||
Reference in New Issue
Block a user