Files
skills/docs/api-stack/api/api-openapi/src/plugin-integration/plugin-integration.md
2026-05-29 08:20:56 +01:00

4.6 KiB

plugin-integration

(no submodules)

Purpose

definePluginRoutes is the single helper plugins call from their onInit to register routes. Every call atomically updates both the Hono OpenAPIHono app (via app.openapi(route, handler)) and the OpenAPIRegistry (via registry.registerRoute(...)). The two surfaces cannot drift — there is no path through the helper that touches only one of them.

Eliminates a class of upstream bugs where a plugin would either register against Hono but forget the registry (operation served but not discoverable) or the inverse (discoverable but not served).

Features

  • Atomically registers each route against both Hono and the registry.
  • Records pluginName on every registry entry for later registry.getRoutesByPlugin(name) lookups.
  • Carries operationId, summary, description, deprecated, and security through into the registry record.
  • Returns the list of RegisteredRoute entries for caller-side post-processing.
  • Defensive-copies tags and security arrays so the route config remains immutable from the registry's perspective.

Usage: Basic

import { OpenAPIHono, z } from "@hono/zod-openapi";
import { createOpenAPIRegistry } from "@suaveplan/api-openapi/registry";
import { GET, successResponse } from "@suaveplan/api-openapi/route-factory";
import { definePluginRoutes } from "@suaveplan/api-openapi/plugin-integration";

const app = new OpenAPIHono();
const registry = createOpenAPIRegistry({
    info: { title: "X", version: "1.0.0" },
});

const ListUsers = GET("/users")
    .tags("users")
    .operationId("listUsers")
    .response(successResponse(z.array(z.object({ id: z.string() }))))
    .build();

definePluginRoutes(
    [
        {
            route: ListUsers,
            handler: (c) => c.json({ success: true as const, data: [] }, 200),
        },
    ],
    { app, registry, pluginName: "users" }
);

Usage: Advanced

import { definePluginRoutes } from "@suaveplan/api-openapi/plugin-integration";
import { definePlugin } from "@suaveplan/api-plugin";

interface MyServices {
    app: OpenAPIHono;
    registry: OpenAPIRegistry;
}

export const projectsPlugin = definePlugin<{
    /* config shape */
}>({
    name: "projects",
    version: "0.1.0",
    onInit(ctx) {
        const app = ctx.getService("app") as MyServices["app"];
        const registry = ctx.getService("registry") as MyServices["registry"];
        const created = definePluginRoutes(
            [
                /* { route, handler } entries built with RouteBuilder */
            ],
            { app, registry, pluginName: "projects" }
        );
        ctx.logger.info(`projects: registered ${created.length} routes`);
    },
});

API Reference

  • type RouteHandler = (c: Context) => Response | Promise<Response> | unknown
  • interface PluginRouteEntry { route: RouteConfig; handler: RouteHandler }
  • interface DefinePluginRoutesOptions { app: OpenAPIHono; registry: OpenAPIRegistry; pluginName: string }
  • function definePluginRoutes(entries, options): RegisteredRoute[]

Implementation Notes

The helper iterates the entries sequentially — there is no parallelism. Hono's app.openapi is synchronous and side-effecting, so concurrency would gain nothing. Sequential iteration also gives well-defined ordering in registry.getRoutesByPlugin(name): the array reflects the order routes were declared in the plugin source, which is the order authors expect when consuming the registry for documentation or introspection.

The RegisteredRoute record copies the route's tags and security arrays defensively. A plugin could otherwise mutate the array on its RouteConfig post-registration and silently alter the registry's introspection view.

route.method is passed through to the registry as-is. Hono normalises methods to lowercase, so the registry receives lowercase, matching the expectations of OpenAPIRegistry.buildDocument().

The helper is intentionally a free function (not a method on the registry). Decoupling it from the registry class means plugins can substitute their own "register-both-surfaces" implementation in tests without touching the registry contract.

The handler type is (c: Context) => Response | Promise<Response> | unknown — Hono's actual handler signature is broader (typed under generics that depend on the specific RouteConfig). Trying to surface that inference at the helper boundary would require every caller to thread the same generics, defeating the helper's purpose. We accept the loose signature and trust the call site of app.openapi to type-check the handler against the route's contract.