Skip to content

fix(zod-openapi): reduce route type instantiations#1988

Open
thekiwi wants to merge 1 commit into
honojs:mainfrom
thekiwi:perf/optimise-openapi-types
Open

fix(zod-openapi): reduce route type instantiations#1988
thekiwi wants to merge 1 commit into
honojs:mainfrom
thekiwi:perf/optimise-openapi-types

Conversation

@thekiwi

@thekiwi thekiwi commented Jun 20, 2026

Copy link
Copy Markdown

Description

This PR reduces the cost of type-checking apps that use createRoute,.openapi() and RouteHandler by restructuring a few of the route-typing generics. There is no runtime change, the emitted JS is identical. The inferred route/handler/response/RPC types are also unchanged.

Relates to #1918.

Problem

The route-typing generics re-derive several expensive sub-expressions on every use:

  • ExtractStatusCode<Status> (which expands ranges like '5XX') is re-computed in every response branch, dragging a fresh TypedResponse cascade through each comparison.
  • the per-route input intersection (param/query/header/cookie/form/json) and the response-shape conditional are inlined verbatim at around 5 sites.
  • the request-body schema is walked (...['content'][keyof ...]['schema']) ~6 times per input type.

On large RPC apps, this makes type-checking more expensive than it needs to be.

Solution

Bind each expensive sub-expression once (infer ... extends ...) and share the duplicated pieces, with no change to the resulting types:

  • resolve ExtractStatusCode<Status> once per response branch and reuse the inferred Content (the bulk of the win)
  • share the input intersection via the existing ComputeInput<R> and the response-shape conditional via a new HandlerResponse<R> across RouteHandler / RouteHook / openapi() / HandlerFromRoute / HookFromRoute
  • bind the request/response schema once in InputTypeBase / InputTypeJson / InputTypeForm / ExtractContent
  • drop the duplicate RoutingPath (identical to the existing ConvertPathType)

Results

While the majority of the benchmarking was done with a sizeable closed-source project, I also generated a 100-route synthetic RPC app (CRUD + auth/upload/search/status) and measured with tsc --extendedDiagnostics. Instantiations & Types are deterministic; check time / memory are medians of 5 runs.

Metric TS 6.0.3 (before → after) TS 7.0.1-rc (before → after)
Instantiations 1,137,695 → 450,121 (-60%) 1,294,335 → 605,109 (-53%)
Types 284,671 → 199,940 (-30%) 379,680 → 297,250 (-22%)
Check time 3.00s → 1.10s (-63%) 0.587s → 0.266s (-55%)
Memory ~449 → ~295 MB (-34%) ~196 → ~171 MB (-13%)

The benchmark file is available here: https://gist.github.com/thekiwi/d85b3a0e5ff467c8ffcf21b9e96a8732

Safety / behaviour-neutral

  • Existing runtime + *.test-d.ts suites pass
  • Added type-level tests pinning the inferred types on the touched paths: text/plain / application/*+json / no-content / default responses, the 5XX status range, form/header/cookie input, and the typed-vs-raw-Response handler return
  • Since the change is type-only, the runtime suite can't catch a regression, so I verified the new type tests actually fail on a deliberate break (I manually mutation-tested the restructured branches, all mutations were caught).

The author should do the following, if applicable

  • Add tests
  • Run tests
  • yarn changeset at the top of this repo and push the changeset
  • Follow the contribution guide

@changeset-bot

changeset-bot Bot commented Jun 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d9561a8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@hono/zod-openapi Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@codecov

codecov Bot commented Jun 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.05%. Comparing base (a401fee) to head (d9561a8).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1988   +/-   ##
=======================================
  Coverage   92.05%   92.05%           
=======================================
  Files         115      115           
  Lines        4065     4065           
  Branches     1059     1059           
=======================================
  Hits         3742     3742           
  Misses        287      287           
  Partials       36       36           
Flag Coverage Δ
zod-openapi 94.90% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 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.

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.

1 participant