import { OpenAPIHono } from "@hono/zod-openapi"; import type { Hono } from "hono"; import type { LazyRoutesLoader } from "./lazy-routes.js"; /** * OpenAPI document generation for composed Voyant apps (voyant#2114). * * The app returned by `createApp`/`mountApp` is an `OpenAPIHono` under the hood * (typed as `Hono` for callers), so the helpers here cast to reach * `getOpenAPIDocument`. Only routes authored via `createRoute(...).openapi(...)` * contribute operations — plain routes are absent until migrated. * * Import these from `@voyant-travel/hono/openapi`, never from the package * barrel: `getOpenAPIDocument` pulls in `@asteasolutions/zod-to-openapi`, which * must stay out of the Worker runtime bundle. This module is meant for * build-time generation only. */ export interface OpenApiInfo { title: string; version: string; description?: string; /** OpenAPI spec extension fields (`x-*`), matching the doc's `InfoObject`. */ [extension: `x-${string}`]: unknown; } export interface OpenApiServer { url: string; description?: string; [extension: `x-${string}`]: unknown; } export interface GenerateOpenApiOptions { info: OpenApiInfo; servers?: OpenApiServer[]; } type AnyApp = Hono; /** * An OpenAPI 3.1 document, as produced by `OpenAPIHono`. Named via the direct * dependency's method type so the inferred type stays portable (TS2883). */ export type OpenApiDocument = ReturnType; /** * Generate a single OpenAPI 3.1 document covering every `.openapi()` route * mounted on the composed app, with module base paths already merged in * (honojs/middleware#952). */ export declare function generateOpenApiDocument(app: AnyApp, options: GenerateOpenApiOptions): OpenApiDocument; /** * Voyant's two published API surfaces, keyed by path prefix. Admin routes mount * under `/v1/admin/*` and storefront/public routes under `/v1/public/*`; the * legacy `/v1/*` surface is intentionally excluded from the published docs. */ export type ApiSurface = "admin" | "storefront"; /** * Narrow a composed document to a single surface by path prefix, producing the * `framework-admin` / `framework-storefront` documents that replace the * hand-authored specs. Components are carried over verbatim (the generator may * over-include shared component schemas, which is harmless). */ export declare function selectSurface(doc: OpenApiDocument, surface: ApiSurface): OpenApiDocument; /** * A lazy-mounted route family recorded by `mountApp` for build-time spec * merging. `prefix` is the absolute surface mount (`/v1/admin/`, * `/v1/public/...`) for relative-route loaders, or `"/"` for absolute * `lazyRoutes` loaders. `load` is the same loader the runtime dispatcher caches * — it constructs (but does not serve) the sub-app via `import(...)`. */ export interface LazyMount { prefix: string; load: LazyRoutesLoader; } /** * A single module route mount recorded by `mountApp` for build-time per-module * spec generation (voyant#2733). One module contributes several mounts (admin + * public, eager + lazy), all tagged with the same `moduleName` — so the module * boundary is the authoritative one from the registration, not a path-prefix * guess. `prefix` is the real absolute mount (base path included), so the * generated per-module doc carries correct absolute paths — including * `publicPath` overrides whose prefix isn't the module name (e.g. a module that * mounts its storefront routes under `/v1/public/booking-engine`). `load` * returns the sub-app without serving it (eager mounts wrap the already-built * app as `() => routes`; lazy mounts pass their loader). */ export interface ModuleMount { moduleName: string; prefix: string; load: () => AnyApp | Promise; } /** * Eager-merge lazy route families into a generated base document (voyant#2114). * * Lazy families mount at runtime as wildcard dispatch stubs (see * `lazy-routes.ts`), so their `.openapi()` operations never reach the composed * `OpenAPIHono` registry and are invisible to `generateOpenApiDocument`. This * runs each loader at build time, and for any that return an `OpenAPIHono`, * re-mounts it into a throwaway `OpenAPIHono` at its real prefix — reusing the * exact prefix-merge semantics `OpenAPIHono.route(...)` applies to eager mounts * — then shallow-merges the resulting `paths` + `components.*` into `base`. * * Plain `Hono` sub-apps (no `.openapi()` routes) carry no registry and are * skipped without error. A loader that throws is skipped with a warning so one * bad family can't break generation. `base` wins on path/component collisions * (which shouldn't happen given distinct prefixes), with a dev-time warning. * * Build-time only — same module-level constraint as the rest of this file * (`@asteasolutions/zod-to-openapi` stays out of the Worker bundle). */ export declare function mergeLazyOpenApiPaths(base: OpenApiDocument, mounts: readonly LazyMount[], options: GenerateOpenApiOptions): Promise; /** * Generate one self-contained OpenAPI document per module from its recorded * mounts (voyant#2733). * * Instead of building one giant composed document and splitting it by path * prefix, this generates each module's spec directly from the routes the module * registered — the authoritative module boundary. Each module's admin + public * (+ lazy) sub-apps are re-mounted into a throwaway `OpenAPIHono` at their real * absolute prefix (reusing the exact prefix-merge `OpenAPIHono.route(...)` * applies), then its `getOpenAPI31Document()` is read. The returned docs are * self-contained (each carries its own referenced components), so they render * and diff cleanly on their own — unlike a 7 MB aggregate. * * A mount whose loader throws, or that returns a plain `Hono` with no * `.openapi()` registry, is skipped without failing the module. Modules that * contribute no documented operation are omitted from the result entirely. * * Build-time only — same constraint as the rest of this module * (`@asteasolutions/zod-to-openapi` must stay out of the Worker bundle). */ export declare function generateModuleOpenApiDocuments(mounts: readonly ModuleMount[], options: GenerateOpenApiOptions): Promise>; /** * Build the authoritative path → module ownership map from the mount manifest. * * Generates each module's isolated doc (see `generateModuleOpenApiDocuments`) * only to learn which real absolute paths it owns — so `publicPath` overrides * (whose prefix isn't the module name, e.g. `/v1/public/booking-engine`) map to * the right module. Routes the manifest doesn't record (`additionalRoutes`, * directly-mounted routes) are absent here and fall back to their path segment. * * Build-time only. */ export declare function buildModulePathOwnership(mounts: readonly ModuleMount[], options: GenerateOpenApiOptions): Promise>; /** * Partition a composed document into one document per module, covering EVERY * admin/storefront path (voyant#2733). * * Uses the ownership map as the authoritative module owner, falling back to the * path's own segment for anything the manifest doesn't claim. * The full surface is therefore partitioned exactly: every `/v1/admin/*` and * `/v1/public/*` path lands in exactly one module document. Non-surface routes * (`/v1/` webhooks, legacy `/v1/*`) live only in the aggregate, as before. * * Each per-module document carries the aggregate's shared `components` verbatim * (there is ~one), so it stays a valid, self-contained OpenAPI document. */ export declare function partitionByModule(full: OpenApiDocument, owner: ReadonlyMap): Map; /** * Convenience: build the ownership map and partition in one call. Prefer the * two-step `buildModulePathOwnership` + `partitionByModule` when you also want * to `stampModuleMetadata` the aggregate from the same map (so it's built once). * * Build-time only. */ export declare function splitDocumentByModule(full: OpenApiDocument, mounts: readonly ModuleMount[], options: GenerateOpenApiOptions): Promise>; /** * Stamp every operation with the metadata standard OpenAPI tooling expects * (voyant#2733 / voyant#2729). All fields are non-destructive — a value a route * already declares is never overwritten: * - `operationId` — stable camelCase id from method + path, for readable * generated client method names. * - `summary` — the method + path signature, so viewers/linters have a title * for every operation. * - `tags: [module]` — so Swagger/Scalar group the sidebar by module (they * key grouping off `tags` and ignore `x-*`). * - `x-voyant-module` / `x-voyant-surface` — machine-readable owner + surface * for custom tooling that shouldn't re-derive them from path prefixes. * * The module is the authoritative owner from the manifest, so `publicPath` * overrides are labelled with their real owning module rather than their mount * prefix. Applied to the aggregate before it's split, so the per-module and * surface documents (all derived from it) inherit the stamps. Keys are appended, * keeping order deterministic for the drift gate. */ export declare function stampModuleMetadata(doc: OpenApiDocument, owner: ReadonlyMap): OpenApiDocument; export { diffOpenApiCoverage, type OpenApiCoverageDiff, type OperationKey, } from "./openapi-coverage.js";