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

3.6 KiB

@suaveplan/api-openapi — OpenAPI 3.1 document emitter

Package: @suaveplan/api-openapi Status: PROPOSED Type: new-package Stage: 8 — see umbrella openspec/changes/api-plugin-foundation/

Why

Plugins author routes with Zod schemas (design doc §7). The API host needs to emit a single merged OpenAPI 3.1 document combining every plugin's routes, every manifest's metadata, the active auth adapter's securitySchemes, and host-level info. Without this, plugin authors would each write hand-maintained OpenAPI documents that drift from runtime behaviour. Auto-emitting from the Zod contract is the only sustainable answer.

What changes

  • ADDED: createOpenapiEmitter({ manifests, routes, authAdapter, hostConfig }) — reads each plugin's manifest (name, displayName, description, scopes, route prefix, publicRoutes), every registered route's Zod schemas, the active auth adapter's securitySchemes, and host.json (server URLs, contact, license). Returns a OpenapiEmitter with getJson() and getYaml().
  • ADDED: Zod-to-OpenAPI conversion via @asteasolutions/zod-to-openapi — established, OpenAPI 3.1, handles refs / discriminators / coercion. Plugins import z from zod directly (genesis convention).
  • ADDED: Namespacing rules — tags from plugin displayName; components/schemas prefixed (billing.Invoice); operationId = <pluginName>.<method>.<routePath>; security inherited from the active auth adapter except on publicRoutes.
  • ADDED: HTTP endpoints (mounted by the host): GET /openapi.json, GET /openapi.yaml, GET /docs, GET /docs/{plugin} (per-plugin filtered view).
  • ADDED: openapi.generation capability — optional. A plugin opts in by declaring openapi.contributes = true. Plugins NOT opting in still get their routes documented unless openapi: false is per-route.
  • ADDED: host.openapi.strictness ("warn" | "refuse-install") — when a route is missing a request/response schema, the emitter either logs a warning or the host refuses to install the plugin. Defaults to "warn" in dev, "refuse-install" in production.
  • ADDED: Error codes (OPENAPI_*): MISSING_SCHEMA, SCHEMA_CONVERSION_FAILED, OPERATION_ID_COLLISION, UNKNOWN_PLUGIN_IN_FILTER.

Out of scope

  • The UI itself — Scalar / Swagger / ReDoc each ship in their own sibling package.
  • Authoring OpenAPI by hand — plugins MUST use Zod.
  • Generating client SDKs from the emitted document — separate change.

Source mapping

Genesis-original. Source of design intent: docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md.

Requirement Design doc section
Auto-emit OpenAPI 3.1 from Zod §7
Endpoints /openapi.json, /openapi.yaml, /docs, /docs/{plugin} §7
Namespacing rules §7
host.openapi.strictness for missing schemas §7 (this proposal locks the policy)

Verification approach

  • src/emitter/emitter.test.ts — full emission against a fixture manifest + route set; assert tags, components, operationIds, security all correct.
  • src/emitter/emitter.spec-conformance.test.ts — strictness modes (warn logs but emits; refuse-install raises OPENAPI_MISSING_SCHEMA).
  • src/endpoints/endpoints.test.ts — every endpoint returns the right Content-Type and body shape (json vs yaml; filtered view excludes other plugins).
  • src/namespacing/namespacing.test.ts — operationId determinism + collision detection.
  • tests/dep-graph.test.ts — runtime deps (api-types, error, logger, zod, @asteasolutions/zod-to-openapi).

Migration impact

None — additive only. The host mounts the OpenAPI endpoints when this package is installed.