Skip to content

fix(types): infer response type from last handler in app.on 9-/10-handler overloads#4865

Merged
yusukebe merged 1 commit intohonojs:mainfrom
T4ko0522:fix/types-app-on-9-10-handlers-response-inference
Apr 8, 2026
Merged

fix(types): infer response type from last handler in app.on 9-/10-handler overloads#4865
yusukebe merged 1 commit intohonojs:mainfrom
T4ko0522:fix/types-app-on-9-10-handlers-response-inference

Conversation

@T4ko0522
Copy link
Copy Markdown
Contributor

@T4ko0522 T4ko0522 commented Apr 8, 2026

Summary

The 9- and 10-handler overloads of OnHandlerInterface declared a type parameter R on the final handler position but discarded it from the schema computation, hardcoding MergeTypedResponse<HandlerResponse<any>> instead. As a result, the inferred output collapses to {} | Promise<void> and outputFormat to string regardless of what the response handler actually returns, so the RPC client resolves r.json() as Promise<unknown>.

This is a write-omission that was introduced in #1735 (2023-11) and carried through subsequent refactors without being noticed — the surrounding x1–x8 overloads are all using MergeTypedResponse<R> correctly.

Affected overloads

This affects only the x9 / x10 overloads of OnHandlerInterface.
The x1–x8 overloads already use MergeTypedResponse<R> correctly.

Reproduction

import { Hono } from 'hono'
import { hc } from 'hono/client'

const noop = async (_c: any, next: any) => { await next() }

const app = new Hono().on(
  ['GET'], '/api/users/:id',
  noop, noop, noop, noop, noop, noop, noop, noop, noop, // 9 middlewares
  (c) => c.json({ id: '1', name: 'Alice' })             // response handler
)

const client = hc<typeof app>('/')
const data = await client.api.users[':id']
  .$get({ param: { id: '1' } })
  .then((r) => r.json())

// Before this PR: data is `unknown`
// After this PR:  data is `{ id: string; name: string }`

Reducing the chain to 8 handlers makes the inference work correctly even on main, because the x8 overload is not affected by the bug. This bizarre cliff at the x9 boundary is the symptom.

Fix

3-line change in src/types.ts:

- S & ToSchema<M, MergePath<BasePath, P>, I10, MergeTypedResponse<HandlerResponse<any>>>,
+ S & ToSchema<M, MergePath<BasePath, P>, I10, MergeTypedResponse<R>>,

R was already declared in the type parameter list and properly captured by the last H<E11, MergedPath, I10, R> element of the handler tuple — only the schema computation was failing to use it.

Test plan

Added 3 regression tests under describe('OnHandlerInterface', ...) in src/types.test.ts, one per fixed overload. Each test passes 9 or 10 handlers and asserts that the schema's output matches the response handler's return type.

These tests fail on main (the schema's output is {} | Promise<void>) and pass after this fix.

  • bun run format:fix && bun run lint:fix
  • bun run test (full type check + vitest)
  • Added regression tests covering all 3 fixed overloads

Related history

This is not related to #3485 / the .then() arrow-function inference issue, which is a separate TypeScript-level limitation in H = Handler | MiddlewareHandler union inference. This PR fixes an unrelated, mechanical write-omission.

commit date note
b80895a — feat(types): Support types until 10 handlers (#1735) 2023-11-28 introduced the hardcoded HandlerResponse<any> in the new x9 / x10 overloads
9732a7b — feat(types): improve ToSchema & WebSocket Helper types (#2588) 2024-05-03 renamed MergeTypedResponseDataMergeTypedResponse, hardcode preserved
37c3809 — fix(types): fix app.on method array type inference (#4578) 2025-12-14 adjacent fix (Ms[number]M), hardcode preserved

I am using a translation tool. I apologize if any of my expressions cause misunderstanding.

…-handler overloads

The 9- and 10-handler `app.on` overloads declared `R` on the last handler position but discarded it, hardcoding `MergeTypedResponse<HandlerResponse<any>>` in the schema instead. This caused the inferred `output` to collapse to `{} | Promise<void>` regardless of what the response handler returned.

  Affected overloads:
  - app.on(method,   path, handler x10)
  - app.on(method[], path, handler x9)
  - app.on(method[], path, handler x10)

  The 8-handler variants were already using `MergeTypedResponse<R>` correctly.
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 8, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.89%. Comparing base (c37ba26) to head (ba6561a).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4865   +/-   ##
=======================================
  Coverage   92.89%   92.89%           
=======================================
  Files         177      177           
  Lines       11797    11797           
  Branches     3515     3514    -1     
=======================================
  Hits        10959    10959           
  Misses        837      837           
  Partials        1        1           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Member

@yusukebe yusukebe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@yusukebe
Copy link
Copy Markdown
Member

yusukebe commented Apr 8, 2026

Hi @T4ko0522

The implementation and test are good. Thank you for your contribution!

@yusukebe yusukebe merged commit 1aa32fb into honojs:main Apr 8, 2026
20 checks passed
@T4ko0522 T4ko0522 deleted the fix/types-app-on-9-10-handlers-response-inference branch April 8, 2026 11:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants