generateOpenApi
Generates an OpenAPI 3.1 document for the application's API and RPC endpoints from an explicit registry of public schemas.
import { generateOpenApi } from "@meekojs/meeko/server";generateOpenApi(options) traverses the server manifest, loads each server/api endpoint and server/rpc procedure, and writes an OpenAPI 3.1 document with their paths, parameters, bodies, responses and security. It only publishes schemas registered by the caller in publicSchemas: a contract using a schema outside the registry is an error, so no internal schema leaks accidentally.
It is optional and runs outside the server, in a script or CI. Meeko does not depend on a converter: toJsonSchema receives the one from the schema library, such as @valibot/to-json-schema.
Usage#
// scripts/openapi.ts
import { writeFile } from "node:fs/promises";
import { pathToFileURL } from "node:url";
import { toJsonSchema } from "@valibot/to-json-schema";
import { discoverServerManifest } from "@meekojs/meeko/build";
import { generateOpenApi } from "@meekojs/meeko/server";
import type { Schema } from "@meekojs/meeko/server";
import * as schemas from "../shared/schemas/public";
const document = await generateOpenApi({
manifest: await discoverServerManifest(process.cwd()),
info: { title: "Store API", version: "1.4.0" },
publicSchemas: schemas,
toJsonSchema: (schema: Schema) => toJsonSchema(schema as never),
load: (file) => import(pathToFileURL(file).href),
});
await writeFile("openapi.json", JSON.stringify(document, null, 2));Run it with a TypeScript loader (npx tsx scripts/openapi.ts): load imports .ts files from server/.
Type#
function generateOpenApi(options: OpenApiOptions): Promise<Record<string, unknown>>interface OpenApiOptions {
manifest: ServerManifest
info: { title: string; version: string }
publicSchemas: Readonly<Record<string, Schema>>
toJsonSchema(schema: Schema): unknown
load: ServerModuleLoader
cookieName?: string
}Parameters#
generateOpenApi(options) takes an OpenApiOptions object:
| Option | Type | Default | Description |
|---|---|---|---|
manifest |
ServerManifest |
required | Endpoints, as returned by discoverServerManifest from @meekojs/meeko/build. |
info |
{ title: string; version: string } |
required | The document's info. |
publicSchemas |
Record<string, Schema> |
required | Schemas that may be published, by name: the name is the components.schemas key. A JavaScript identifier; the same instance cannot appear twice. |
toJsonSchema |
(schema: Schema) => unknown |
required | Converts a schema to JSON Schema; must return a serializable object. |
load |
(file: string) => Promise<unknown> |
required | Loads a manifest file's module; its default export is the definition. |
cookieName |
string |
"__Host-meeko.sid" |
Cookie for the sessionCookie security scheme. |
Return value#
A promise with the document as a JSON object:
{
"openapi": "3.1.0",
"info": { "title": "Store API", "version": "1.4.0" },
"paths": {
"/api/users/{id}": {
"get": {
"operationId": "users_byId_get",
"parameters": [{ "in": "path", "name": "id", "required": true, "schema": { "type": "string" } }],
"responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/userSchema" } } } } }
}
}
},
"components": {
"schemas": { "userSchema": { "type": "object" } },
"securitySchemes": { "sessionCookie": { "type": "apiKey", "in": "cookie", "name": "__Host-meeko.sid" } }
}
}- API. One operation per method, with
operationId<clave del cliente con _>_<método>.[id]segments become{id}and their schemas come fromparams;queryprovidesin: "query"parameters;bodyprovidesrequestBody;status(200,201,202or204) provides the response.auth: "required"addssecurity: [{ sessionCookie: [] }]. - RPC. A
POSTat its path (/_meeko/rpc/…) withoperationIdrpc_<id con _>,inputas the body andoutputas the200response.
Examples#
The public schema registry#
// shared/schemas/public.ts: only what the document can expose
export { userParams, userSchema } from "./user";
export { orderInput, orderSchema } from "./order";Private schemas (for an internal webhook, for example) stay outside; if an endpoint uses them, generateOpenApi fails instead of publishing them.
Check the document in CI#
npx tsx scripts/openapi.ts && npx @redocly/cli lint openapi.json
git diff --exit-code openapi.json # the published contract does not change without reviewCommon mistakes#
A schema outside the registry#
// server/api/users/[id].get.ts uses userSchema, which is not in publicSchemas
await generateOpenApi({ ...options, publicSchemas: { orderSchema } });Error: OpenAPI <archivo> response uses a schema outside publicSchemas, with the endpoint file's path (params, query, body, input or output depending on the field).
await generateOpenApi({ ...options, publicSchemas: { orderSchema, userSchema, userParams } });A catch-all route or non-JSON response#
server/api/files/[...path].get.ts
server/api/report.get.ts with contentType: "response"Error: OpenAPI cannot represent catch-all route /api/files/[...path] and Error: OpenAPI non-JSON response requires an explicit representation: …. Document these routes manually or leave them out of the manifest you pass.
Routes with [id] segments and JSON responses with schemasNo load#
await generateOpenApi({ manifest, info, publicSchemas, toJsonSchema });TypeError: OpenAPI generation requires a module loader.
await generateOpenApi({ manifest, info, publicSchemas, toJsonSchema, load: (file) => import(pathToFileURL(file).href) });A converter that does not return an object#
toJsonSchema: (schema) => JSON.stringify(convert(schema)),Error: OpenAPI schema userSchema is not representable as JSON Schema. If the converter throws (a transform JSON Schema cannot express), the error is OpenAPI schema userSchema cannot be converted: ….
toJsonSchema: (schema) => toJsonSchema(schema as never), // an object, not a stringNotes#
- Outside the server. Does not run in
meeko buildor production: call it from a script. It loads handlers, so their imports must resolve in that process. - Security. The explicit registry guarantees the document only shows what you chose to publish; review the
openapi.jsondiff like any public contract. - Route parameters. Each
[nombre]segment must exist in theparamsschema (an object withproperties); if missing,OpenAPI path parameter id is missing from … params schema. - Duplicates. Two operations with the same method and path are an error (
OpenAPI duplicate operation: GET /api/users).