/** * Copyright (c) 2026 AzerothJS. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /** * The feature record's third exporter. * * A feature record already produces a server registration and a typed client from one * declaration; this module produces the OpenAPI 3.1 document from the same declaration - * three consumers, one truth, drift structurally impossible for everything derived. The * exporter is a PURE function: it reads the declarations and each schema's * self-description (SchemaMeta) and never touches runtime behavior - `docs` on a * route is display-only by contract. * * Determinism is a tested promise: paths in declaration order (two insertion-ordered * Object.entries walks), canonical key order inside every object, component names taken * from the first use and disambiguated in declaration order when two names collapse - * two builds of the same record are byte-identical, so specs diff cleanly in CI. * * Honest degradations: a refinement cannot be expressed as JSON Schema, so it becomes a * description note; a QUERY route (RFC 10008) has no OpenAPI method, so it is excluded * from `paths` and listed, machine-readably, under the `x-azerothjs-query` extension; a * schema without metadata maps to the permissive `{}` with a note; a `raw` route's body * degrades to what its spec declared and never an invented schema. The exporter never * claims a constraint the validator does not enforce. */ import type { Schema } from '@azerothjs/schema'; import type { App, AzerothPlugin } from '../index.ts'; import type { Feature } from './declare.ts'; /** The subset of an OpenAPI document this exporter emits (structurally 3.1-valid). */ export type OpenApiDocument = Record; /** Everything a machine cannot derive from the contract - and nothing it can. */ export interface ToOpenApiOptions { /** OpenAPI `info` - title and version are the spec's only required fields. */ info: { title: string; version: string; description?: string; }; /** OpenAPI `servers` (base URLs); omit for a relative-path spec. */ servers?: ReadonlyArray<{ url: string; description?: string; }>; /** The mount prefix routes are served under; must match `mountApi`. Default '/api'. */ prefix?: string; /** Raw OpenAPI securityScheme objects by name (referenced from route docs.security). */ securitySchemes?: Record; /** The document-wide security requirement (route docs.security overrides per route). */ security?: ReadonlyArray>; /** * The wire shape of error responses, when the app replaces the framework envelope * (a custom `serializeError`). Declared once, referenced by every derived and * declared error response - so the spec tells the truth about YOUR errors. * Default: the framework's `{ error: { code, message, details } }` envelope. */ errorSchema?: Schema; } /** * Derives the OpenAPI 3.1 document from a feature record. Pure and deterministic: the same * record always produces the byte-identical document. Everything derivable is derived * (paths, params, bodies, the framework's 422/415/500 envelope responses, operation ids * and tags from the record keys); `docs` adds only what a machine cannot know; QUERY routes * are listed under `x-azerothjs-query` because OpenAPI has no such method; `raw` and * `stream` routes appear in `paths` - the +N routes the old contract silently omitted. */ export declare function toOpenApi(features: Record, options: ToOpenApiOptions): OpenApiDocument; /** Options for {@link openapiPlugin}: the export options plus the features and routes. */ export interface OpenApiPluginOptions extends ToOpenApiOptions { /** The registered feature record the served document describes - pass what `register` returned. */ features: Record; /** Where the document is served. Default '/openapi.json'. */ route?: string; /** * Where the docs page is served, or `false` for spec-only. Default '/docs'. */ docs?: string | false; /** * Register both routes outside development. Off by default: the document enumerates * every contracted route's shape and constraints, and the docs page carries a try-it * panel a developer pastes a real token into - so an app that wants that surface on * the public internet says so in one word, and an app that forgets leaks nothing. * The environment check is POSITIVE: without this, the routes register only when * `NODE_ENV` is exactly `development` - unset, `staging`, and `Production` all * count as not-development. */ public?: boolean; /** * Which viewer the docs page carries. Default `'azeroth'`: the house explorer - one * fully self-contained page (inline styles/script, zero external requests, works * offline) in the AzerothJS design language, try-it included. `'scalar'`: a tiny shell * that loads the Scalar reference from a CDN in the browser - best-in-class UI, at the * price of internet while viewing and third-party code your browser runs on the page * you paste tokens into. */ viewer?: 'scalar' | 'azeroth'; } /** * Serves the contract's OpenAPI document - generated once at install (contracts are * immutable values), served from cached bytes - and, unless `docs: false`, the house * explorer page beside it. An ordinary plugin: two GET routes, nothing else. External * viewers (Scalar, Redoc, Swagger UI) read the document route directly. * * Both routes are development surfaces: unless `public: true` says otherwise, the plugin * registers nothing except under exactly `NODE_ENV=development`. */ export declare function openapiPlugin(options: OpenApiPluginOptions): AzerothPlugin; /** * The coverage report for partial adoption: every route registered on the app that the * feature record does NOT cover (compared under `prefix`). Call it AFTER all registration - * an honest list for the migration burndown, never a guess. raw/stream/form routes count as * covered now, so the output shrinks to what is genuinely outside the system. */ export declare function uncontracted(app: App, features: Record, prefix?: string): string[];