Releases: block65/openapi-codegen
Release list
v14.0.0
Breaking
@block65/rest-client16 is required. Generated commands use its sequential media commands and parse hooks. Upgrade the peer dependency, then regenerate.- Validated commands set an instance field. A validated subclass now sets
responseSchema(ordataSchemafor a stream) as an instance field instead ofstatic responseSchema, because that is where rest-client 16's parse hooks look. text/event-streamwithout anitemSchemais a plainCommand. It is no longer typed asUint8Arraychunks.- A bodiless success is
undefined, and no success isnever. A 2xx response without content types the output asundefined, which is whatjson()andsend()resolve with. An operation documenting no 2xx response at all getsnever. Both used to beunknown. - Open values are
JsonValueon both sides. An empty schema, an untyped record value or an array withoutitemsis typedJsonValueinstead ofJsonifiable, and valibot checks it recursively instead of acceptingunknown. - Only local
$refs are read. A$refto another file or a URL stops generation and names the ref; it used to be fetched at codegen time.@apidevtools/json-schema-ref-parseris no longer a dependency.
Sequential media commands
A 2xx response whose media type has an OAS 3.2 itemSchema becomes a sequential command. For text/event-stream it extends rest-client's EventStreamCommand, whose output is the union of each item's data contentSchema, so client.stream() yields typed, parsed events. A contentMediaType of text/plain sets the text data transformer, and JSON is the default. A construct that doesn't fit stops generation and names the operation, as does an itemSchema in a document older than 3.2.
Documents are read as OAS 3.2, and a 3.1 document is read unchanged. A schema may be true or false, and a media type may be a $ref.
Recursive schemas
A schema that contains itself, or schemas that refer to each other, used to fail with "Cyclic dependency". They now generate, with v.lazy on the valibot side. An acyclic document generates byte-identical output.
$ref fixes
- A
$refthat escapes a name as a JSON pointer does (~1,~0), is percent-encoded, or points into part of a component now resolves. A ref into part of a component is inlined, keeping any keywords beside the$ref. - A
$refto a schema the document doesn't define stops generation, giving the ref's JSON pointer. It used to fail later as "ref used before available". - Component order follows every nested
$ref, and only refs in subschemas count. A$ref-shaped key insideexample,default,constorenumis data.
Type fixes
- Nullable object, array and string types admit
null, as their valibot schemas always did. requiredis honoured on object schemas that omittype.additionalPropertiesrecords are keyed bystring, notstring | number.
Faster generation
Large modules are built in chunks. Output is byte-identical, and the openai document generates in 8s instead of 59s.
v13.0.0
Breaking
@block65/openapi-codegen/oxlintis removed. It pointed at a.tsfile, which node refuses to load fromnode_modules, so importing it broke the consumer's lint. Delete the override from your lint config and regenerate.in: cookiestops generation. No command or validator reads a cookie, so the parameter used to be dropped without a word. Generation now throws naming the operation and the parameter, asin: querystringdoes.- Open object schemas are lint errors.
prefer-strict-objectis never exempt: an object the document leaves open fails the consumer's lint until the document setsadditionalProperties: false.
Lint directives in generated files
Every run lints every generated file with the project's own oxlint and config. Each file gets one // oxlint-disable line naming only the exempt rules that fired in it, so reportUnusedDisableDirectives stays satisfied and the directives follow the current config, including after an upgrade. A file that lands on its recorded revision keeps its bytes on disk. Without oxlint in the project the files get no directive.
A header schema is v.object() on purpose, because a request carries headers the document does not name. Where prefer-strict-object fires on one, its declaration is bracketed by a disable and enable that say so.
Dependencies
oxlint plugin 0.11.0 and shared-config 0.5.0. The plugin deletes snake-case-wire-keys, so it is no longer exempt.
v12.0.0
Generated commands name the @block65/rest-client 15 query serializer for the style their document states, so the peer dependency moves to ^15.0.0. This is the breaking change.
- A command whose query parameters use a style other than form with explode overrides
querySerializerwith the matching serializer, imported by name. One that states none inheritsformExplodeSerializer, the OpenAPI default. An operation whose parameters need two serializers stops generation with the pair named. - A one-member
anyOforoneOfis emitted as its member instead ofv.union([member]), and an empty one asv.unknown(). - Generated modules lint clean under
@block65/shared-config0.4.0 with the valibot rule group enabled.defineOverridesturns offprefer-strict-object,prefer-exact-optionalandsnake-case-wire-keysfor generated files, with the reason for each in the shipped source.
What's Changed
- Clear the lint backlog and refresh dependencies by @maxholman[bot] in #21
- refactor: pass each phase a context instead of shared loop state by @maxholman[bot] in #22
- feat(query): emit the document's query serializer on rest-client 15 by @maxholman[bot] in #23
Full Changelog: v11.0.0...v12.0.0
v11.0.0
Breaking
- The generated Hono integration is now
hono.ts(washono-valibot.ts), validating requests through Standard Schema (~standard.validate) instead of valibot directly. - Generated output requires
@block65/rest-client>= 14, which replacesPublicValibotHonoErrorwithPublicValidationError.
New
- Generated files carry a content-hash stamp in the banner and are left untouched when regeneration produces unchanged content — no more date-only churn, robust against downstream formatters.
v10.2.0
feat: narrow generated types and validators to the real data contract
- RFC 3339 string formats (date, date-time, time, duration) now emit template-literal types plus matching regex validators. Previously only date-time was handled, as the opaque `Jsonify`.
- integer/int64 now maps to `bigint` (range exceeds Number.MAX_SAFE_INTEGER).
- enums emit a bare picklist/literal/union, including null and non-string members.
Breaking for consumers: `DateTime` is now a template-literal string instead of `Jsonify`, and int64 fields are `bigint` instead of `number`.
Also: CI runs on node 26; test target typechecks lib/ and bin/.
v10.1.0
What's new
Features
minProperties/maxPropertiessupport — object schemas now emitv.minEntries()/v.maxEntries()constraints (plain objects, empty-property records, andallOfcompositions). Previously these JSON Schema keywords were silently dropped.ResponseValidationErrorre-export — generatedmain.tsnow re-exportsResponseValidationErrorfrom@block65/rest-client, so consumers getinstanceofnarrowing without adding a direct dependency.
Maintenance
- Regenerated all fixtures.
- Bumped dependencies to latest (
@block65/rest-client13.1.0,undici8.4.0, and others). - Tests pin
retries: 0via an explicit fetcher and disable timeouts (vitest.config.ts); fixed the petstore mock path for explode-style array query serialization.
Full Changelog: v10.0.6...v10.1.0
v10.0.6
- main.ts emits file-level `import type { … }` instead of per-name `import { type X }` for type-only imports.
- Optional query/header params no longer carry a redundant `| undefined` union — optionality is conveyed by `?:` alone.
- Pairs with `@block65/rest-client@13.0.5` `CommandQuery` constraint relaxation; generated query types satisfy it while remaining `JsonObject`-clean.
- Adds regression tests covering import shape, optional query types, and `AllInputs` membership.
v10.0.5
Fix: optional handling now matches direction of data flow
The previous exact / coerced split conflated two orthogonal concerns — whether to coerce wire strings and whether to tolerate undefined. The result: server-side hono middleware and client-side response parsing were both wrapping optional fields in v.optional(...), which permits undefined even though JSON-parsed wire data can never carry one. Handlers had to defensively guard against { field: undefined } cases that were structurally impossible.
Realigned the schemas along direction of data flow:
-
inputXxxSchema— for outgoing TS-side values (request bodies beforeJSON.stringify, pre-flight validation). Usesv.optional(...). No wire coercion (types are already TS-native). Tolerates the destructure-with-default{ foo: undefined }pattern. -
xxxSchema(wire) — for incoming JSON-parsed values (hono middleware, rest-client response parsing). Usesv.exactOptional(...)— field is present-with-value or absent, never undefined. Includes bigint / number coercion for HTTP wire formats.
For server handlers, the win is concrete: c.req.valid(\"json\") now returns { name: string, nickname?: string } rather than { name: string, nickname: string | undefined }. No phantom-undefined to guard against.
Renames
| Before | After |
|---|---|
exactPetSchema |
inputPetSchema |
petSchema (with v.optional + coercion) |
petSchema (with v.exactOptional + coercion) |
--exact-only |
--input-only |
CodegenOptions.exactOnly |
CodegenOptions.inputOnly |
SchemaMode "exact" / "coerced" |
"input" / "wire" |
Behavioral changes
petSchema(wire variant, same name as before): optional fields now usev.exactOptionalinstead ofv.optional. Strictly correct for JSON-parsed data; would reject{ field: undefined }if it ever appeared (it can't, afterJSON.parse).exactPetSchema→inputPetSchema: optional handling flipped fromv.exactOptionaltov.optional. Use this for client-side pre-flight validation where TS callers may pass undefineds.
Versioning note
Strictly a major bump (renames + flipped semantics). Released as patch given a single pre-launch consumer.
v10.0.4
Highlights
-
New: two-file commands output. Codegen now emits
commands.ts(lean, no schema imports) alongsidecommands-validated.ts(subclasses that attachstatic responseSchema). Consumers swap files (or bundler-alias dev → validated) to opt into response validation without paying bundle cost in prod. Pairs with@block65/rest-client13.0.4 which drops thevalidateResponsesflag and validates whenever a schema is present. -
Fix: inline 2xx response schemas no longer silently skipped. Operations whose 2xx body is an inline object with nested
\$refs(e.g.{ user: { \$ref: \"...\" } }) now correctly emit a per-command response schema. Previously the codegen fell through andvalidateResponses: truewas a silent no-op for those commands — a real production leak class. -
Fix: array responses now validate as arrays. Endpoints like
Pet[]previously hadstatic responseSchema = petSchema(single Pet, wrong — the response is the whole array). Now emitv.array(petSchema).
Removed
- Static
bodySchema,paramsSchema,querySchemaare no longer attached to generated Command classes. rest-client never read them, and Hono middleware imports schemas directly fromvalibot.tsalready — they were pure bundle overhead.
Generated output
- Namespace imports (
import * as commands/import * as schemas) in the validated file keep diffs stable across regenerations.
Dependencies
- Peer dep:
@block65/rest-client^13.0.3 (required for schema-presence-driven validation). - Misc dep rollforward: valibot 1.4, hono 4.12.18, etc.
Note on versioning
Strictly this should be a major bump (peer dep range moved 12 → 13, generated output changed). Released as a patch given a single pre-launch consumer.