OpenAPI

Generate an OpenAPI 3.1 spec + Swagger-UI docs from your defineRestRoute descriptors and (opt-in) your rpc procedures. Your routes ARE the API docs — nothing hand-maintained.

@voltro/plugin-openapi turns your defineRestRoute descriptors — and, opt-in, your rpc procedures (queries / mutations / actions / streams) — into a live OpenAPI 3.1 document + a Swagger-UI page. The descriptors already carry method, path, input/output Schemas, summary and deprecation; the rpc procedures carry input/output/error Schemas. The plugin reads them, so there is zero hand-written API documentation to drift.

Wiring

// app.config.ts
import { openapiPlugin } from '@voltro/plugin-openapi'
import listCustomers from './routes/v1/customers.list.route'
import createCharge from './routes/v1/charge.route'

export default {
  type: 'api' as const, name: 'api',
  restRoutes: [listCustomers, createCharge],
  plugins: [
    openapiPlugin({
      routes: [listCustomers, createCharge],          // the same descriptors
      info: { title: 'Payments API', version: '1.0.0' },
      // specPath: '/openapi.json',  docsPath: '/docs'  (defaults; docsPath:false disables the UI)
      // token: process.env.OPENAPI_DOCS_TOKEN,  // opt-in Bearer gate (see Permissions)
    }),
  ],
}

Mounts GET /openapi.json (the spec) and GET /docs (Swagger UI).

What it maps

  • Each route → an OpenAPI path + operation, keyed by method.
  • The input Schema's query fields → parameters (in: query); params → path params; body → a JSON requestBody.
  • The output Schema → the 200 JSON response.
  • summary / description → operation metadata; deprecateddeprecated: true; example.request / example.response → request-body / 200-response examples; sunset → the x-sunset extension.
  • The serve pipeline's real error statuses are documented per operation — 400 (input decode, when the route has an input schema), 401/403 (when it has guards), 410 (when sunset is set), 500 (always) — all referencing one shared components.schemas.VoltroHttpError envelope.
  • When any route declares guards, the document publishes components.securitySchemes.bearerAuth and guarded operations carry security: [{ bearerAuth: [] }].

Schemas are converted with effect's JSONSchema.make, so your effect/Schema definitions become JSON Schema verbatim. Pure: generateOpenApiSpec(routes, info) is exported + unit-tested if you want the spec without serving it.

rpc procedures in the spec

Pass your rpc procedure descriptors via the procedures option and each query / mutation / action / stream is projected into the same spec. The framework's rpc surface is a batched JSON-RPC protocol over one endpoint (POST /rpc) — not a REST-per-resource API — so the projection is honest about that: every procedure becomes a POST /rpc/<name> operation whose body is the rpc payload (the input Schema), tagged rpc and flagged with an x-voltro-rpc extension. The operation description states the real transport; the path is a documentation projection, not a REST route you call directly.

import { openapiPlugin } from '@voltro/plugin-openapi'
import listOrders from './orders.list.query'
import createOrder from './orders.create.mutation'
import charge from './billing.charge.action'
import runAgent from './agent.run.stream'

openapiPlugin({
  routes: [listCustomers],                        // REST routes (optional)
  procedures: [listOrders, createOrder, charge, runAgent],  // rpc procedures
  info: { title: 'API', version: '1.0.0' },
})

Per procedure kind:

  • queryPOST /rpc/<name>; input → requestBody, output → the 200 response (documented as the first snapshot; the WebSocket rpc channel then pushes reactive updates).
  • mutation / actioninput → requestBody, output200.
  • stream → the element Schema is documented as a streaming response (application/x-ndjson, one element per line) — not faked as a single JSON value.
  • The procedure's typed error channel (its Schema.TaggedError union) becomes a 422 response referencing the error schema; a procedure with no declared error documents only 200 + 500.

Mixing rpc + REST in one spec is fine — the rpc operations are grouped under the rpc tag and flagged x-voltro-rpc, so a consumer can tell them apart from real REST routes. procedures is opt-in: omit it to keep the spec REST-only.

Access control

Both routes publish your full API contract (every path, param, and schema), and both are unauthenticated by default — mount them only where that's intended. Two levers narrow the surface:

  • docsPath: false hides only the Swagger-UI viewer; the spec at /openapi.json stays served.
  • token (or the OPENAPI_DOCS_TOKEN env var) requires Authorization: Bearer <token> on both routes — a missing or mismatched header gets a 401. The same shape as @voltro/plugin-prometheus's gate. Leave it unset behind a trusted network boundary; set it for a public-internet deployment.
openapiPlugin({
  routes: [listCustomers, createCharge],
  token: process.env.OPENAPI_DOCS_TOKEN,   // unset → both routes are public
})

Permissions

The plugin declares no runtime permissions — it only mounts the two GET routes built from descriptors you already declared.