50 lines
3.6 KiB
Markdown
50 lines
3.6 KiB
Markdown
# `@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.
|