Generates a typed client, valibot schemas and Hono middleware from an OpenAPI document.
pnpm exec openapi-codegen -i openapi.json -o src/generatedThe generated files import these packages. They are declared as
peerDependencies here, so installing this generator reports what is missing —
but the generated code usually lives in a different package from the one
that depends on the generator, and nothing can add them to that package's
package.json for you. Add them wherever the generated output is compiled and
run.
| package | version | needed by |
|---|---|---|
@block65/rest-client |
^14.2.0 |
commands.ts, main.ts, hono.ts |
type-fest |
^5.10.0 |
commands.ts, main.ts, types.ts |
valibot |
^1.5.0 |
valibot.ts |
hono |
^4.13.8 |
hono.ts |
@hono/standard-validator |
^0.4.0 |
hono.ts |
commands-validated.ts and enums.ts import nothing external.
A client that never serves requests can skip hono and
@hono/standard-validator by leaving hono.ts out of its build. A client that
never validates responses can skip valibot the same way.
Generated hono.ts validates the query exactly as Hono parsed it. It decodes
nothing, so these encodings reach the schema in a shape the schema rejects:
| sent | Hono gives the validator | schema wants |
|---|---|---|
?tags=cat |
"cat" |
an array |
?tags=a,b |
"a,b" |
["a", "b"] |
?tags=a%20b |
"a b" |
["a", "b"] |
| `?tags=a | b` | `"a |
?at[gt]=1 |
key "at[gt]" |
{ at: { gt: 1 } } |
A single value for a repeated-key array is the common one, and it is what a client sends for a one-item array.
A rejected query throws PublicValidationError, so the status a caller sees
is whatever its own error handler returns for that error.
A consumer whose clients send any of these decodes the query before the
generated middleware validates it, or supplies its own middleware in place of
the generated one. The per-operation query spec is exported for that purpose,
carrying the style and explode each parameter declares:
import { listPetsQueryParams } from "./generated/hono.ts";The client half is already handled: a generated command names the
@block65/rest-client serializer for the style its document states, and
inherits formExplodeSerializer — form with explode, the OpenAPI default —
when it names none. One serializer covers a whole operation, so an operation
whose query parameters need two of them stops generation.
The generated JSDoc is transcribed from the OpenAPI document, so the prose is the document author's and the comment rules judge it unfairly. Query parameter names, schema nesting and the optionality of inputs are the document's contract too.
After writing, the generator runs your project's own oxlint over the output, with your config. Each file gets a directive that turns off only the rules on that exemption list which fired in it:
// oxlint-disable block65/no-narrative-comment, block65/prefer-exact-optionalAn object schema the document leaves open is never exempt: it stays a lint
error until the document sets additionalProperties: false. A header schema is
the exception, because a request carries headers the document does not name.
Where the rule fires on one, the generator brackets that declaration with a
disable and enable naming the reason.
No lint config is needed for generated code. Anything else the output trips is a bug in the generator, so report it rather than turning the rule off.
Every run relints every file, so the directives follow your current lint config. Without oxlint in the project, or with a config that fails to load, the files get no directive.
additionalProperties: false emits v.strictObject, which rejects unknown
keys. An absent additionalProperties emits v.looseObject, because JSON
Schema allows unknown keys by default and the generated client follows the
document. Declare additionalProperties: false on a schema that is meant to be
closed.
Path and query parameters are always strict, because an OpenAPI parameter list is exhaustive.