first commit
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
# plugin-integration
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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.
|
||||
Reference in New Issue
Block a user