Releases: block65/openapi-codegen
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.
v10.0.3
Full Changelog: v10.0.2...v10.0.3
v10.0.2
Fixed
- Generated Hono middleware validators now reference the coerced sibling schemas instead of the exact ones, so HTTP query, path, and header values (which always arrive as strings) validate correctly. Previously a request like
?limit=20would fail because"20"was not av.number(). Body validators are unaffected in practice — JSON-typed numbers still pass through the coerced union. UseexactOnlyto opt out.
v10.0.1
Fix
Honors x-typescript-hint on string schemas inside oneOf/anyOf/allOf. Previously the recursive string branch ignored the hint, so a schema like oneOf: [{ enum: ["native"] }, { type: string, x-typescript-hint: "EmbedUrl" }] collapsed to "native" | string. The fix mirrors the top-level handling in the recursive case.
Other
- Refactor: prefer
toSorted/toReversedandiifeblocks over deeply-nested ternaries. - Chore: drop leftover prettier config, the unused
@ts-morph/commondevDep, and the unicorn nested-ternary lint rule. - Chore: enable composite tsconfig; ignore
*.tsbuildinfo. - Style: fix indentation on the undici
@ts-expect-errorcomment block in the test fetch wrappers.
v10.0.0
Whats change
v9.x generated a single valibot schema per type that mixed concerns - sometimes strict, sometimes coercing. This made it impossible to cleanly separate server validation (reject bad input) from client-side convenience (accept realistic wire types, trim whitespace, coerce strings to numbers). You ended up needing stripUndefined calls and other runtime workarounds to satisfy types that were stricter than necessary for the context.
v10 generates two schema variants per type: exact (the API contract - what the spec says) and coerced (the DX helper - accepts what the wire actually gives you, produces spec-compliant values). The server uses exact schemas and validates strictly. The client uses coerced schemas and gets clean data without manual cleanup. Each side gets exactly the schema it needs.
Breaking
- All schemas now have
exactFooSchema(strict) +fooSchema(coerced) variants - consumers importing schema names directly will need to update - Hono middleware uses exact schemas - the server no longer silently coerces or trims input. Malformed data is rejected, not rescued
- Coerced schemas use
v.optional()instead ofv.exactOptional()—undefinedvalues are accepted in coerced mode since they can't survive JSON serialization anyway
New
- Dual schemas — exact validates that data is spec-compliant; coerced takes untrusted data and makes it spec-compliant. Coerced composes exact via pipe, so constraints are never duplicated
- int64 coercion — JSON can't represent bigint, so the coerced schema accepts
string | number | bigintand producesbigintviav.toBigint(). Only applied where the wire format genuinely forces a different type - HTTP param coercion — query and header params are always strings on the wire. Coerced schemas accept strings and coerce to the spec type via
v.toNumber()/v.toBigint(). Exact schemas expect the native type - String trimming — coerced mode applies
v.trim()before validation for user-typed fields (strings, emails, URLs). Not applied to machine-generated values (UUIDs, patterns, enums, const values, byte/binary/password). Exact mode never trims - Command static schema props — generated commands now carry
bodySchema,paramsSchema,querySchema,responseSchemaas static properties. rest-client will consume these for automatic response validation and input checking in a future release --exact-onlyCLI flag —openapi-codegen --exact-onlyemits only exact schemas, no coerced variants- Schema deduplication — when exact and coerced are structurally identical (enums, UUIDs, patterns, const values), the coerced schema is
export const fooSchema = exactFooSchemainstead of a full duplicate
Fixes
- Emit
unknownoutput type when a response has no JSON content — previously the Output type argument was omitted, shifting Query and Header generics into the wrong positions. This caused type errors for specs with streaming or non-JSON endpoints (e.g. Docker Engine API)
Tooling
- Replaced biome with oxlint + oxfmt
- Updated to undici 8, vitest 4, vite 8, TypeScript 6
v9.4.1
What's Changed
Fixes
- Use RegExp constructor for valibot pattern validation — Regex literal construction only escaped forward slashes; patterns with backslashes, quotes, or newlines would produce invalid source code.
new RegExp(JSON.stringify(pattern))handles all edge cases.
Full Changelog: v9.4.0...v9.4.1
v9.4.0
What's Changed
Features
- Zero-parameter command constructors — Commands with no path/query/header params (e.g.
ListBillingAccountsCommand) now generate a no-arg constructor withsuper(pathname)directly - Optional input for all-optional params — Commands where all parameters are optional no longer require an empty
{}argument (e.g.new FindPetsCommand()now works)
Fixes
- Skip response type for non-JSON endpoints — Endpoints without JSON response content no longer incorrectly get
unspecifiedKeywordas a response type argument - Remove
| undefinedfrom optional header types — Optional header properties now use?only, fixing compatibility with theCommandgeneric constraint (Record<string, string | number | boolean>)
Chores
- Bump
@block65/rest-clientpeer dependency to^12.1.0(4th generic for headers) - Add biome config
Full Changelog: v9.3.0...v9.4.0
v9.3.0
What's Changed
- fix: generate v.null() for type: "null" schemas in valibot by @maxholman in #16
- feat: wire header params into Command constructor by @maxholman in #17
Full Changelog: v9.2.2...v9.3.0
v9.2.2
What's Changed
- fix: resolve $ref schemas for query and header param coercion by @maxholman in #15
Full Changelog: v9.2.1...v9.2.2