# route-factory _(no submodules)_ ## Purpose Typed route construction utilities for `@hono/zod-openapi`. The `RouteBuilder` is a fluent generic that threads `TParams`, `TQuery`, `TBody`, and `TResponse` from `.params/.query/.body/.response` calls through to the final `.build()` output. The resulting `TypedRouteConfig` is assignable to `RouteConfig`, so `app.openapi(route, handler)` infers `c.req.valid("params" | "query" | "json")` to the exact Zod type without `unknown` widening. Also exports the wire-shape `ErrorResponseSchema`, the wider `StandardResponses` (covering every status code a `SuaveplanError` can emit — 400/401/403/404/409/422/429/500/503/504), and the `successResponse` / `paginatedResponse` / `errorResponse` envelope helpers. ## Features - `RouteBuilder` fluent generic. - `route(method, path)` factory + `GET`/`POST`/`PUT`/`PATCH`/`DELETE` shorthands. - `StandardResponses` widened to cover the full SuaveplanError status range. - `ErrorResponseSchema` carrying the `cause` field from `SuaveplanError.toJSON()`. - `successResponse(schema)`, `paginatedResponse(itemSchema)`, `errorResponse(description)` helpers. ## Usage: Basic ```ts import z from "zod"; import { GET, successResponse } from "@suaveplan/api-openapi/route-factory"; const ListUsers = GET("/users") .tags("users") .operationId("listUsers") .response(successResponse(z.array(z.object({ id: z.string() })))) .build(); ``` ## Usage: Advanced ```ts import { OpenAPIHono } from "@hono/zod-openapi"; import z from "zod"; import { POST, paginatedResponse, successResponse, } from "@suaveplan/api-openapi/route-factory"; const UserParam = z.object({ id: z.string().uuid() }); const UserCreate = z.object({ email: z.string().email() }); const User = z.object({ id: z.string(), email: z.string() }); const createUser = POST("/users/:id") .tags("users") .operationId("createUser") .deprecated() .security([{ Bearer: [] }]) .params(UserParam) .body(UserCreate) .response(successResponse(User)) .build(); const listUsers = POST("/users/list") .tags("users") .response(paginatedResponse(User)) .build(); const app = new OpenAPIHono(); app.openapi(createUser, async (c) => { const params = c.req.valid("param"); // typed as z.infer const body = c.req.valid("json"); // typed as z.infer return c.json({ success: true, data: { id: params.id, email: body.email } }); }); ``` ## API Reference - `type RouteMethod = "get"|"post"|"put"|"patch"|"delete"` - `interface RouteOptions` - `const ErrorResponseSchema` + `type ErrorResponse` - `function successResponse(dataSchema: T)` - `function errorResponse(description: string)` - `function paginatedResponse(itemSchema: T)` - `const StandardResponses` — 400/401/403/404/409/422/429/500/503/504 each pointing at `ErrorResponseSchema` - `type TypedRouteConfig` - `class RouteBuilder` - `function route(method, path): RouteBuilder` - `const GET/POST/PUT/PATCH/DELETE` ## Implementation Notes `TypedRouteConfig` uses `Omit & { request: … }` so the `request` property is non-optional. Hono's `InputType*` conditional types only match when `request` is present, so making it optional silently downgrades inference to `unknown`. The builder records state in a private object rather than directly on `this` so the four `.params/.query/.body/.response` overloads can re-cast the receiver to a new generic instantiation without TypeScript losing track of the actual stored values. `StandardResponses` is widened to cover every status `SuaveplanError` emits. Upstream omitted 422 (validation), 429 (rate limit), 503 (service unavailable), and 504 (gateway timeout), forcing every plugin that wanted to document those responses to redefine them locally — a common source of doc drift. The widened set is now the single source. `ErrorResponseSchema` adds the `cause` field on top of upstream's shape. SuaveplanError supports a cause chain, and serialising it gives clients enough context to diagnose without requesting logs. `cause` is typed `z.unknown()` because the chain may be heterogeneous — SuaveplanError descendants, native `Error`s, or serialised payloads. `responses` always merges `200: Success` first and then spreads `StandardResponses` — so consumers can override `200` by passing `.response(…)` but cannot accidentally drop one of the standard error slots.