Skip to content

feat: support zod 4 and zod-to-openapi v8#22

Merged
bengotow merged 2 commits into
mainfrom
sean/zod-4
May 26, 2026
Merged

feat: support zod 4 and zod-to-openapi v8#22
bengotow merged 2 commits into
mainfrom
sean/zod-4

Conversation

@seancrossettie

@seancrossettie seancrossettie commented May 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Upgrades peer zod to ^4 (breaking) and @asteasolutions/zod-to-openapi to ^8.5.0
  • Migrates the small surface that depended on removed/renamed zod 4 APIs (ZodEffects, SafeParseReturnType)
  • Fixes validated-middleware type inference so req.params/body/query infer correctly under zod 4's stricter input/output split — without this, every downstream openAPIRoute(...) handler loses its inferred request types
  • Replaces the old _def.openapi._internal.refId introspection with the public getRefId() helper from zod-to-openapi v8
  • Pins packageManager to yarn@1.22.22 to match the existing v1 lockfile (corepack now auto-selects the right yarn)

BREAKING CHANGE: peer dependency on zod is now ^4. Drop zod 3 support.

- Bump peer `zod` to ^4 and dep `@asteasolutions/zod-to-openapi` to ^8.5.0
- Replace `z.SafeParseReturnType` with `z.ZodSafeParseResult` (renamed in zod 4)
- Validated middleware now casts to `z.output<T>` so `req.params/body/query`
  infer correctly under zod 4's stricter input/output split
- Replace removed `ZodEffects` ref-resolution branch with `getRefId()` from
  zod-to-openapi v8 — pipes/transforms on registered schemas still resolve
  back to their `$ref`
- Update test assertions for zod 4's new "Invalid input: expected X, received Y"
  default error format
- Pin packageManager to yarn@1.22.22 to match the existing v1 lockfile

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@seancrossettie seancrossettie requested a review from bengotow May 22, 2026 17:59
Three follow-up fixes after the initial zod 4 bump:

- Replace `TBody extends ZodTypeAny` (which collapses to `unknown` under
  zod 4's stricter variance) with bare `TBody`/`TQuery`/etc. generics and a
  conditional `InferOutput<T>` helper that reads `T["_zod"]["output"]`. This
  restores proper inference for `req.body`, `req.query`, and `req.params`
  inside `openAPIRoute(...)` handlers.
- `InferOutput<T>` falls back to `any` when no schema is declared, matching
  the previous behavior so routes without explicit schemas don't suddenly
  break.
- Re-export `extendZodWithOpenApi` and `ZodOpenAPIMetadata` from the
  package root so the `.openapi()` augmentation is more discoverable for
  consumers; under `module: "nodenext"` callers may still need to import
  `@asteasolutions/zod-to-openapi` themselves to fully activate it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@souterjk

Copy link
Copy Markdown

this looks good to me, i'll let @bengotow weigh in as well.

@bengotow bengotow merged commit 77835fd into main May 26, 2026
1 check passed
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 7.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants