Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,123 @@
# route-factory
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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<TParams, TQuery, TBody, TResponse>` 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<typeof UserParam>
const body = c.req.valid("json"); // typed as z.infer<typeof UserCreate>
return c.json({ success: true, data: { id: params.id, email: body.email } });
});
```
## API Reference
- `type RouteMethod = "get"|"post"|"put"|"patch"|"delete"`
- `interface RouteOptions<TParams, TQuery, TBody, TResponse>`
- `const ErrorResponseSchema` + `type ErrorResponse`
- `function successResponse<T>(dataSchema: T)`
- `function errorResponse(description: string)`
- `function paginatedResponse<T>(itemSchema: T)`
- `const StandardResponses` — 400/401/403/404/409/422/429/500/503/504 each pointing at `ErrorResponseSchema`
- `type TypedRouteConfig<TParams, TQuery, TBody, TResponse>`
- `class RouteBuilder<TParams, TQuery, TBody, TResponse>`
- `function route(method, path): RouteBuilder`
- `const GET/POST/PUT/PATCH/DELETE`
## Implementation Notes
`TypedRouteConfig` uses `Omit<RouteConfig, "request"> & { 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.