SIGN IN SIGN UP

feat(zod-validator): surface the default 400 on the no-hook overload and keep the return assignable to MiddlewareHandler (#1881)

* fix(zod-validator): surface the default 400 failure response on the no-hook overload

When `zValidator(target, schema)` is used without a hook, the implementation
falls through to `c.json(result, 400)` on validation failure, but the
declared return type was `MiddlewareHandler<E, P, V>` (R defaults to
`Response`). As a result, `MergeMiddlewareResponse<M_k>` in honojs/hono
extracted only `Response`, which `ExtractTypedResponseOnly<Response>` maps
to `never`, and the failing 400 never reached the RPC schema.

Add `ZodValidatorFailureBody<T>` (the failure variant of
`safeParseAsync`'s result, supporting both Zod v3 and v4) and widen the
no-hook overload to
`MiddlewareHandler<E, P, V, TypedResponse<ZodValidatorFailureBody<T>, 400, 'json'>>`.
Combined with honojs/hono#4393 and #4906, the failure response now flows
through to `hc<typeof app>` so clients can narrow `res.status === 400` and
read the typed `SafeParseError` body.

The with-hook overload is left unchanged to avoid introducing a phantom
JSON 400 entry for callers whose hook always returns its own response;
they continue to opt in via `ExtractValidationResponse<HookFn>`.

Refs: honojs/hono#3746

* fix(zod-validator): make zValidator return type assignable to a generic MiddlewareHandler

ExtractValidationResponse and ZodValidatorFailureResponse returned bare
TypedResponse<...> values, which do not extend Response. Assigning a
zValidator middleware to a plain MiddlewareHandler therefore failed with
TS2322. Intersect both with Response (Response & TypedResponse<...>) so
the no-hook and with-hook paths are symmetrically assignable.

Also:
- Collapse the two no-hook overloads into a single signature taking
  `hook?: undefined`
- Extract the options parameter shape into ValidationFunctionOption<T, Target>
- Add generic-MiddlewareHandler assignment tests to the Basic and With
  Hook describes for both v3 and v4
- Update the changeset to describe the full set of zod-validator changes

* fix(zod-validator): bump peerDependencies.hono to >=4.10.0

The no-hook overload returns MiddlewareHandler with four type
arguments, which only exists from hono v4.10.0. The RPC inference
pipeline (ExtractHandlerResponse / MergeMiddlewareResponse) that
consumes the fourth argument is also new in 4.10.0, so older peers
cannot surface the typed 400 branch through hc<typeof app>.

Verified by running tsc against the PR sources:
- hono@4.10.0: clean
- hono@4.9.9: TS2707 "MiddlewareHandler requires between 0 and 3 type arguments"

* fix(zod-openapi): bump peerDependencies.hono to >=4.10.0

@hono/zod-openapi has @hono/zod-validator as a direct dependency,
so its peer range must be at least as strict as zod-validator's.
After zod-validator's peerDependencies.hono was bumped to >=4.10.0
for the typed-400 fix, leaving zod-openapi at >=4.3.6 lets users
install it against e.g. hono@4.9.9, where the bundled zod-validator
types reference the 4-argument MiddlewareHandler<E, P, I, R> (new
in Hono v4.10.0) and fail to compile.

Reproduced the cascade in a sandbox with hono@4.9.9:
- npm install: ERESOLVE peer dep conflict
- tsc (after --legacy-peer-deps): TS2707 from
  node_modules/@hono/zod-validator/dist/index.d.ts

* chore: update lockfile

* fix(zod-openapi): update changeset bump level to minor

* fix(zod-validator): update changeset bump level to minor
T
tako._.v committed
e90e4fb30877f3e3f4b0588bdb2bbfc337efbf67
Parent: 0d88669
Committed by GitHub <noreply@github.com> on 5/9/2026, 10:17:44 AM