Skip to content

feat: improve param type inference#1217

Open
luxass wants to merge 21 commits intoh3js:mainfrom
luxass:feat/infer-route-params
Open

feat: improve param type inference#1217
luxass wants to merge 21 commits intoh3js:mainfrom
luxass:feat/infer-route-params

Conversation

@luxass
Copy link
Contributor

@luxass luxass commented Oct 11, 2025

This PR resolves #1053 by adding automatic type inference for route parameters. When you define a route with parameters using app.get(), app.post(), or any other HTTP method, TypeScript now knows exactly what parameters are available in event.context.params.

Previously, event.context.params was always typed as Record<string, string> | undefined, even when the route pattern clearly defined specific parameters. Now the route pattern is parsed at the type level to extract parameter names and provide full type safety.

Route parameters are now fully typed based on the route pattern you define:

// Before: params were loosely typed
app.get("/user/:id", (event) => {
  const id = event.context.params.id; // string | undefined
});

// After: params are inferred from the route
app.get("/user/:id", (event) => {
  const id = event.context.params.id; // string ✨
});

This works with multiple parameters too:

app.get("/user/:userId/post/:postId", (event) => {
  event.context.params.userId;  // string
  event.context.params.postId;  // string
});

The existing helper functions (getRouterParam and getRouterParams) also benefit from this:

app.get("/hello/:name", (event) => {
  const params = getRouterParams(event); // { name: string }
  const name = getRouterParam(event, "name"); // string
});

Type inference works across all route registration methods like app.get(), app.post(), app.put(), app.delete(), and app.on(). Routes without parameters have params typed as undefined, so you'll know when there are no parameters available.

When you use defineHandler directly (outside of a route), the route pattern isn't available yet, so params remain untyped as Record<string, string> | undefined. You can still manually type them using the routerParams field in EventHandlerRequest if needed. However, when you inline defineHandler with a route, the params are fully typed automatically:

app.get("/hello/:name", defineHandler((event) => {
  event.context.params.name; // string - fully typed!
  
  const params = getRouterParams(event); // { name: string }
  const name = getRouterParam(event, "name"); // string
}));

The implementation leverages InferRouteParams from rou3 (h3js/rou3#168) for route pattern parsing. I have tried to make the types backward compatible, so if you catch something that doesn't work as before, just tell me and i'll fix it 😅

Summary by CodeRabbit

  • New Features

    • Added strongly-typed route parameter support for route handlers with automatic type inference based on dynamic segments.
    • Introduced helper functions for accessing route parameters with IDE autocomplete support.
  • Tests

    • Added comprehensive type-level tests validating route parameter inference and helper usage across various route patterns.

✏️ Tip: You can customize this high-level summary in your review settings.

@luxass luxass force-pushed the feat/infer-route-params branch from 0471273 to a46b3c6 Compare October 11, 2025 06:10
@pi0
Copy link
Member

pi0 commented Oct 11, 2025

This is an awesome start. Wondering if we could pair it with InferRouteParams from rou3 (h3js/rou3#168) for app.[method] somehow

@luxass
Copy link
Contributor Author

luxass commented Oct 12, 2025

This is an awesome start. Wondering if we could pair it with InferRouteParams from rou3 (h3js/rou3#168) for app.[method] somehow

Yea, i have already started working on it locally. But ran into some beahviour issues which i am trying to figure out first 👍🏻

Will include it in this PR when i am done.

@luxass
Copy link
Contributor Author

luxass commented Oct 12, 2025

@pi0 This PR should be ready for a quick review when you have a moment - happy to adjust anything if needed 😊

I have tried cleaning the overloads from f7c9ece (#1217) up in 42a9512 (#1217), let me know if i should revert that to the multiple inline overloads approach 👍🏻

@luxass luxass marked this pull request as ready for review October 13, 2025 04:10
@luxass luxass requested a review from pi0 as a code owner October 13, 2025 04:10
@pi0
Copy link
Member

pi0 commented Oct 23, 2025

Sorry, it got delayed @luxass, we will try to review soon (also added @danielroe)

@pi0 pi0 requested a review from danielroe October 23, 2025 13:43
luxass added 16 commits October 27, 2025 11:38
* Updated the `H3EventContext` interface to accept a generic type parameter `TParams` for `params`.
* This change enhances type safety and flexibility for router parameter handling.
* Updated the `context` property in `H3Event` to infer `routerParams` more effectively.
* Added tests to verify the inference of router parameters from `EventHandlerRequest`.
* Ensured default behavior for cases without specified `routerParams`.
This feels cleaner to have the optionality of the params in the context.
The previous implementation in the pull request, removed access to the optional properties.
* Added `RouteParams` type to simplify route parameter inference.
* Updated `on`, `get`, `post`, `put`, `delete`, `patch`, `head`, `options`, `connect`, and `trace` methods to utilize the new `RouteParams` type for better type safety.
* Improved type definitions for event handlers to include inferred route parameters.
This will hopefully clean the types up a bit.
* Removed redundant `const` keyword from route type parameters in `on`, `get`, `post`, `put`, `delete`, `patch`, `head`, `options`, `connect`, and `trace` methods.
* Improved type inference for route parameters.
This should make the h3 declared class not look too complex with the overloads.

I couldn't get the `all` to use the H3HandlerInterface, some type errors in H3Core appeared 😅
@luxass luxass force-pushed the feat/infer-route-params branch from 537000c to 931281a Compare October 27, 2025 10:47
@pi0
Copy link
Member

pi0 commented Oct 28, 2025

Dear @luxass you don't need to rebase PR on all commits. I can take care of rebase before merge 👍🏼

Copilot AI review requested due to automatic review settings December 30, 2025 14:05
@coderabbitai
Copy link

coderabbitai bot commented Dec 30, 2025

📝 Walkthrough

Walkthrough

This PR implements type-safe route parameter inference for H3, enabling developers to access dynamically extracted route parameters (e.g., from /:user/id) with proper type checking. The changes span the type system, event context, handler interfaces, and utility functions to ensure route parameters are accurately inferred and typed throughout the framework.

Changes

Cohort / File(s) Summary
Type System Foundation
src/types/_utils.ts, src/types/context.ts
Added Simplify<T> and RouteParams<T> types for extracting and normalizing route parameters from route strings. Made H3EventContext generic with TParams parameter (defaults to Record<string, string>) to support custom parameter shapes.
Event and Handler Typing
src/event.ts, src/h3.ts, src/types/h3.ts
Made H3Event.context generic-aware with conditional params property. Introduced H3HandlerInterface<This> for strongly-typed route handlers. Added generic on<Route>() and all<Route>() methods. Refactored HTTP verb methods (get, post, put, delete, patch, head, options, connect, trace) from individual methods to properties of type H3HandlerInterface<this>.
Utility Functions
src/utils/request.ts
Added overloads for getRouterParams() and getRouterParam() functions to support proper type inference for both H3Event and HTTPEvent generics with precise key typing.
Type Tests
test/unit/types.test-d.ts
Extended type-level tests to validate routerParams inference across various route patterns, helpers usage with autocomplete, and parameter typing with static vs. dynamic routes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 Route params now sing with types so clear,
Dynamic segments whisper what they'll share,
No more guessing what the route will bear,
Generics dance where params appear,
Type-safe paths for all the web-folk near!

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: improve param type inference' accurately captures the main objective: adding type inference for route parameters so that event.context.params is typed with specific parameter keys extracted from route patterns.
Linked Issues check ✅ Passed The PR fully addresses the requirements in issue #1053: implements automatic TypeScript type inference for route parameters across all HTTP methods and helper functions, with params typed as specific keys rather than generic Record<string, string>.
Out of Scope Changes check ✅ Passed All code changes are directly scoped to route parameter type inference: generic H3EventContext, typed RouteParams utility, enhanced handler overloads, and tests validating parameter inference and helper function autocomplete.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fdbbb5f and 075d557.

📒 Files selected for processing (7)
  • src/event.ts
  • src/h3.ts
  • src/types/_utils.ts
  • src/types/context.ts
  • src/types/h3.ts
  • src/utils/request.ts
  • test/unit/types.test-d.ts
🧰 Additional context used
🧬 Code graph analysis (4)
src/event.ts (1)
src/types/context.ts (1)
  • H3EventContext (5-37)
src/types/h3.ts (3)
src/index.ts (4)
  • HTTPMethod (9-9)
  • H3 (19-19)
  • EventHandler (35-35)
  • HTTPHandler (47-47)
src/types/handler.ts (2)
  • EventHandler (12-18)
  • HTTPHandler (8-8)
src/types/_utils.ts (1)
  • RouteParams (6-11)
src/h3.ts (6)
src/event.ts (1)
  • method (145-147)
src/types/h3.ts (2)
  • HTTPMethod (24-24)
  • RouteOptions (96-99)
src/index.ts (5)
  • HTTPMethod (9-9)
  • EventHandler (35-35)
  • RouteOptions (11-11)
  • HTTPHandler (47-47)
  • toEventHandler (56-56)
src/types/handler.ts (2)
  • EventHandler (12-18)
  • HTTPHandler (8-8)
src/types/_utils.ts (1)
  • RouteParams (6-11)
src/handler.ts (1)
  • toEventHandler (193-207)
test/unit/types.test-d.ts (2)
src/utils/request.ts (2)
  • getRouterParams (161-179)
  • getRouterParam (294-302)
src/h3.ts (3)
  • H3 (115-252)
  • H3 (254-254)
  • handler (54-68)
🔇 Additional comments (11)
src/utils/request.ts (2)

143-150: Well-structured overloads for type inference.

The overloads correctly distinguish between H3Event (with inferred route params) and generic HTTPEvent (with fallback type). This enables proper type narrowing when using typed events from route handlers.


269-283: Good overload design for getRouterParam.

The generic constraint on Key ensures autocomplete for valid parameter names when using typed events, while the HTTPEvent overload provides flexibility for untyped contexts.

src/event.ts (1)

58-62: Effective use of intersection type for precise params typing.

The conditional type _RequestT["routerParams"] extends undefined ? undefined : _RequestT["routerParams"] correctly handles the distinction between routes with and without parameters, ensuring params is undefined for static routes and strongly typed for dynamic routes.

src/types/context.ts (1)

5-13: Clean generic extension maintaining backward compatibility.

The default TParams = Record<string, string> preserves existing behavior for consumers not using typed route parameters, while enabling precise typing when needed.

src/types/_utils.ts (1)

5-11: Well-designed type utility for route parameter inference.

The RouteParams type correctly:

  1. Uses Simplify to flatten the inferred type for better IDE tooltips
  2. Returns undefined for static routes (empty params) via keyof _Simplified extends never
  3. Preserves the inferred parameter shape for dynamic routes

This aligns well with the typing in H3Event.context.params.

src/h3.ts (2)

171-205: Correct overload structure for route parameter inference.

The overload ordering is correct: the generic Route extends string overload is listed first and will match when TypeScript can infer a literal string type. The fallback string overload handles dynamic route strings. The cast on line 200 is necessary since the implementation must handle both handler types.


207-209: The typed overload for the all() method already exists in src/types/h3.ts (lines 214-220) with proper generic type support and a fallback overload. No changes are needed.

Likely an incorrect or invalid review comment.

test/unit/types.test-d.ts (2)

131-195: Comprehensive type tests for router params inference.

The tests thoroughly cover:

  • Explicit routerParams in EventHandlerRequest
  • Default fallback to Record<string, string> | undefined
  • Helper function type inference (getRouterParams, getRouterParam)
  • Autocomplete constraint on getRouterParam key parameter

197-309: Good coverage for app route inference scenarios.

The tests validate inference across HTTP methods (get, post, on), multiple dynamic segments, static routes (correctly typed as undefined), and the important edge case of reusable handlers maintaining generic types while inline handlers get full inference.

src/types/h3.ts (2)

26-49: Well-documented interface design for method handlers.

The H3HandlerInterface pattern with the This extends H3 generic constraint is a clever solution to maintain proper this typing. The documentation (lines 36-38) explaining why H3 isn't used directly in the return type is helpful for maintainability.


223-231: HTTP method properties enable route-typed overloads.

Changing from method declarations to H3HandlerInterface<this> properties allows the two-signature callable interface to work correctly, providing both typed inference for literal route strings and fallback for dynamic routes.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds automatic type inference for route parameters based on route patterns defined in H3 application methods. Previously, event.context.params was always typed as Record<string, string> | undefined. Now, TypeScript extracts parameter names from route patterns (e.g., :id, :userId) and provides specific types for them.

Key changes:

  • Route parameters are now inferred from route patterns in app.get(), app.post(), and other HTTP method handlers
  • Helper functions getRouterParams and getRouterParam now return properly typed parameters
  • Routes without parameters have params typed as undefined instead of an optional record

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
test/unit/types.test-d.ts Adds comprehensive type-level tests for router parameter inference across different route patterns and helper functions
src/utils/request.ts Updates getRouterParams and getRouterParam with function overloads to support typed parameter inference
src/types/h3.ts Adds H3HandlerInterface and updates HTTP method signatures to infer route parameters from route patterns
src/types/context.ts Makes H3EventContext generic to accept custom parameter types instead of fixed Record<string, string>
src/types/_utils.ts Introduces RouteParams type helper using InferRouteParams from rou3 for route pattern parsing
src/h3.ts Implements runtime overloads for the on method to support typed route parameter handlers
src/event.ts Updates H3Event.context to use the inferred router params type from the request

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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.

infer route params

2 participants