generateOpenApi
Genera un documento OpenAPI 3.1 de los endpoints de API y RPC de la aplicación a partir de un registro explícito de schemas públicos.
import { generateOpenApi } from "@meekojs/meeko/server";generateOpenApi(options) recorre el manifiesto de servidor, carga cada endpoint de server/api y cada procedimiento de server/rpc, y escribe un documento OpenAPI 3.1 con sus rutas, parámetros, cuerpos, respuestas y seguridad. Sólo publica los schemas que el llamador registra en publicSchemas: un contrato que usa un schema fuera del registro es un error, así que ningún schema interno se filtra por accidente.
Es opcional y se ejecuta fuera del servidor, en un script o en CI. Meeko no depende de un conversor: toJsonSchema recibe el de la librería de schemas, como @valibot/to-json-schema.
Uso#
// 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: "API de la tienda", 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));Ejecútelo con un cargador de TypeScript (npx tsx scripts/openapi.ts): load importa los .ts de server/.
Tipo#
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
}Parámetros#
generateOpenApi(options) recibe un objeto OpenApiOptions:
| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
manifest |
ServerManifest |
obligatorio | Los endpoints, como los devuelve discoverServerManifest de @meekojs/meeko/build. |
info |
{ title: string; version: string } |
obligatorio | info del documento. |
publicSchemas |
Record<string, Schema> |
obligatorio | Los schemas que se pueden publicar, por nombre: el nombre es la clave de components.schemas. Un identificador de JavaScript; una misma instancia no puede aparecer dos veces. |
toJsonSchema |
(schema: Schema) => unknown |
obligatorio | Convierte un schema en JSON Schema; debe devolver un objeto serializable. |
load |
(file: string) => Promise<unknown> |
obligatorio | Carga el módulo de un archivo del manifiesto; su export por defecto es la definición. |
cookieName |
string |
"__Host-meeko.sid" |
La cookie del esquema de seguridad sessionCookie. |
Valor devuelto#
Una promesa con el documento como objeto JSON:
{
"openapi": "3.1.0",
"info": { "title": "API de la tienda", "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. Una operación por método, con
operationId<clave del cliente con _>_<método>. Los segmentos[id]pasan a{id}y sus schemas salen deparams;queryda los parámetrosin: "query";body, elrequestBody;status(200,201,202o204), la respuesta.auth: "required"añadesecurity: [{ sessionCookie: [] }]. - RPC. Un
POSTen su ruta (/_meeko/rpc/…) conoperationIdrpc_<id con _>,inputcomo cuerpo youtputcomo respuesta200.
Ejemplos#
El registro de schemas públicos#
// shared/schemas/public.ts: sólo lo que el documento puede mostrar
export { userParams, userSchema } from "./user";
export { orderInput, orderSchema } from "./order";Los schemas privados (los de un webhook interno, por ejemplo) se quedan fuera; si un endpoint los usa, generateOpenApi falla en lugar de publicarlos.
Comprobar el documento en CI#
npx tsx scripts/openapi.ts && npx @redocly/cli lint openapi.json
git diff --exit-code openapi.json # el contrato publicado no cambia sin revisiónErrores comunes#
Un schema fuera del registro#
// server/api/users/[id].get.ts usa userSchema, que no está en publicSchemas
await generateOpenApi({ ...options, publicSchemas: { orderSchema } });Error: OpenAPI <archivo> response uses a schema outside publicSchemas, con la ruta del archivo del endpoint (params, query, body, input u output según el campo).
await generateOpenApi({ ...options, publicSchemas: { orderSchema, userSchema, userParams } });Una ruta catch-all o una respuesta no JSON#
server/api/files/[...path].get.ts
server/api/report.get.ts con contentType: "response"Error: OpenAPI cannot represent catch-all route /api/files/[...path] y Error: OpenAPI non-JSON response requires an explicit representation: …. Documente esas rutas a mano o déjelas fuera del manifiesto que pasa.
Rutas con segmentos [id] y respuestas JSON con schemaSin 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) });Un conversor que no devuelve un objeto#
toJsonSchema: (schema) => JSON.stringify(convert(schema)),Error: OpenAPI schema userSchema is not representable as JSON Schema. Si el conversor lanza (un transform que JSON Schema no expresa), el error es OpenAPI schema userSchema cannot be converted: ….
toJsonSchema: (schema) => toJsonSchema(schema as never), // un objeto, no un textoNotas#
- Fuera del servidor. No se ejecuta en
meeko buildni en producción: llámelo desde un script. Carga los handlers, así que sus imports deben resolverse en ese proceso. - Seguridad. El registro explícito es la garantía de que el documento sólo muestra lo que decidió publicar; revise el diff de
openapi.jsoncomo cualquier contrato público. - Parámetros de ruta. Cada segmento
[nombre]debe existir en el schema deparams(un objeto conproperties); si falta,OpenAPI path parameter id is missing from … params schema. - Duplicados. Dos operaciones con el mismo método y ruta son un error (
OpenAPI duplicate operation: GET /api/users).