Files
2026-05-27 23:29:53 +01:00

5.3 KiB

Add @suaveplan/api-openapi — OpenAPI registry + route factory (3 of 5)

Package: @suaveplan/api-openapi (NEW, server-tier) Status: PROPOSED Type: new-package (split from upstream @suaveplan/api)

  • Spec delta: ./specs/api-openapi/spec.md
  • Companions: add-api-plugin, add-api-tenant, add-api-migrations, enterprise-api
  • Position in chain: depends on add-api-plugin (consumers register routes via plugin onInit)

Why

Upstream @suaveplan/api's openapi/ (1,066 LOC) ships an OpenAPIRegistry, a doc merger, a typed RouteBuilder, and a global SchemaRegistry. Splitting it lets @suaveplan/api-schemas (the Zod boundary) and downstream api-plugin-* packages compose a focused OpenAPI surface without dragging the Hono app orchestrator along.

The upstream layer has two notable issues genesis fixes: (1) the OpenAPIRegistry and Hono's OpenAPIHono.doc() are not connected — registry tracks routes for introspection but the actual doc is generated by Hono, so drift between the two is silent; (2) getSchemaRegistry() is a global singleton, untestable in parallel and conflicting with multi-app deployments.

What changes

Net-new package at 0.1.0. ADDED:

  • OpenAPIRegistry class — single source of truth. Tracks info, servers, securitySchemes, tags, routes. Methods: registerSecurityScheme, registerTag, registerRoute, getRoutes, getRoutesByPlugin, getRoutesByTag, buildDocument(): OpenAPIDocument (returns the full 3.1.0 doc, not just info+servers).
  • Hono integration: OpenAPIRegistry.applyTo(app: OpenAPIHono) — registers a single app.doc(specPath, ...) handler that serves the registry-built document. Eliminates the drift between registry state and Hono doc output.
  • RouteBuilder fluent API — type-preserving via RouteBuilder<TParams, TQuery, TBody, TResponse> generics; .params(z)/.query(z)/.body(z)/.response(z) thread types through; .build() returns TypedRouteConfig compatible with @hono/zod-openapi.
  • route(method, path) factory + shorthands GET/POST/PUT/PATCH/DELETE(path).
  • StandardResponses — 400/401/403/404/422/500/503 (widened from upstream's 400/401/403/404/500 to cover the full SuaveplanError statusCode range).
  • ErrorResponseSchema — Zod schema matching @suaveplan/error wire shape ({ success: false, error: { code, message, details?, requestId?, timestamp, cause? } }).
  • successResponse(T), paginatedResponse(T), errorResponse(desc) — response wrapper schemas.
  • SchemaRegistry class — local instance (no global singleton). Methods: register, get, has, getNames, getAll, remove, clear, extend(baseName, name, ext), partial(baseName, name), pick(baseName, name, keys), omit(baseName, name, keys). defineSchema(schema, meta, registry) requires an explicit registry instance.
  • CommonSchemas — uuid, email, url, datetime, positiveInt, nonNegativeInt, page, limit, sortOrder (Zod schemas with .openapi() metadata).
  • OpenAPIDocument + PathItem + Operation types — structural shape for cross-tool merging.
  • mergeOpenAPIDocuments(base, ...docs) — same path-merge / components-spread / tag-dedup semantics as upstream.
  • prefixPaths(doc, prefix), filterPathsByTag(doc, tags), removeInternalRoutes(doc) — doc transforms.
  • BearerAuthScheme, ApiKeyScheme security-scheme presets.
  • OpenAPIError extends SuaveplanError<OpenAPIErrorCode> — codes OPENAPI_INVALID_INPUT, OPENAPI_SCHEMA_NOT_FOUND, OPENAPI_INVALID_BASE_SCHEMA (e.g. extend on a non-ZodObject).
  • Plugin integration: definePluginRoutes(routes: TypedRouteConfig[], registry: OpenAPIRegistry) helper that plugins call from onInit to register routes against both Hono (app.openapi) and the registry.

Out of scope

  • OpenAPI 3.0 support. Genesis ships 3.1.0 only (matches Hono OpenAPIHono.getOpenAPI31Document). A back-compat 3.0 transform is a follow-up.
  • YAML / TOML emit. Doc is emitted as JSON. YAML adapter belongs in a separate package.
  • Spec validation against the 3.1 schema. We trust @hono/zod-openapi's output structure; we don't reparse our own emitted spec. A validator package is out of scope.
  • OpenAPI UI rendering (Swagger / Redoc / Scalar / Altair). These ship as separate plugins (api-openapi-ui-swagger, etc — all in the in-flight change set).

Source mapping

Capability Upstream source Notes
OpenAPIRegistry ../suaveplan/packages/server/api/src/openapi/openapi-registry.ts L1-L238 port + add buildDocument() that emits the full doc + applyTo(app) Hono integration
Doc merging + transforms ../suaveplan/packages/server/api/src/openapi/openapi-merger.ts L1-L271 verbatim port
RouteBuilder + factories + StandardResponses + helpers ../suaveplan/packages/server/api/src/openapi/route-factory.ts L1-L298 port + widen StandardResponses to include 422 and 503
SchemaRegistry + CommonSchemas ../suaveplan/packages/server/api/src/openapi/schema-registry.ts L1-L255 port + drop global singleton; require explicit registry instance

Impact

Affected dependents

Depends on add-api-plugin (for the PluginDescriptor/PluginContext types) and indirectly on @suaveplan/api-schemas (existing on genesis). Will be consumed by @suaveplan/api (orchestrator) and every @suaveplan/api-plugin-* package that ships routes.