Skip to content

feat(context): Added typed context to h3 - #1379

Open
gulshan wants to merge 3 commits into
h3js:mainfrom
gulshan:typedContext
Open

gulshan wants to merge 3 commits into
h3js:mainfrom
gulshan:typedContext

Conversation

@gulshan

@gulshan gulshan commented May 5, 2026

Copy link
Copy Markdown
Contributor

Added typed context to h3, so that we can have better type inference for the context object in handlers.

  • Type of context has been made generic in H3Event and H3
  • A new method addEventContext has been added to H3 for attaching a typed context.
  • Necessary context type has been added to other types.

Code like this is type-safe now-

import { H3 } from "h3";

let app = new H3()
  .addEventContext("auth", (event) => event.req.headers.get("authorization"))
  .addEventContext("foo", "bar");

app.get("/", (event) => {
  console.log(event.context.auth); // context contains key 'auth' of type 'string | null'
  return 'Hello World';
});

app.use((event) => {
  console.log(event.context.foo); // context contains key 'foo' of type is 'string'
});

Feedback is welcome!

resolves #511

Summary by CodeRabbit

  • New Features
    • Added full generic context typing across the framework, enabling strongly-typed custom event context in handlers and middleware.
    • Introduced a new API to augment event context types with additional properties, propagating type information automatically.
    • Route registration and handler APIs now offer enhanced type safety through context-aware typing.

Added typed context to h3, so that we can have better type inference for
the context object in handlers.
@coderabbitai

coderabbitai Bot commented May 5, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR makes the H3 core context-aware by introducing a generic context type parameter across H3Event, H3, and handler/middleware types, updates public APIs and route signatures to use the generic, and adds a method to extend event context at runtime.

Changes

Context-Aware Generic Type System

Layer / File(s) Summary
Type Infrastructure
src/types/handler.ts
Made HTTPHandler, EventHandler, EventHandlerObject, and Middleware generic over _ContextT extends H3EventContext, and updated call signatures to accept H3Event<..., _ContextT>.
Core Event Type
src/event.ts
H3Event class now declares _ContextT extends H3EventContext = H3EventContext; context field and constructor parameter use _ContextT.
Application Class (implementation)
src/h3.ts
H3 class made generic (<_ContextT extends H3EventContext = H3EventContext>); request overloads accept context?: _ContextT; added extendContext(key: string, valOrFn: unknown): this which injects middleware to assign into event.context.
Public API Types / Declarations
src/types/h3.ts
Declaration of H3 made generic; request, use, on and all route convenience methods updated to accept HTTPHandler<_ContextT> or Middleware<_ContextT>; addEventContext overloads updated to return H3<_ContextT & Record<K, V>> and support a valFn variant.
Imports / Supporting Types
src/types/h3.ts, src/types/handler.ts
Added/adjusted imports (e.g., EventHandlerRequest, H3EventContext) to support new generics and updated type references across declarations.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I nibble types and stitch a seam,
Event contexts now chase a dream.
Generics hop from root to leaf,
Middleware plants the context leaf.
A tiny hop, a larger scheme 🥕✨

🚥 Pre-merge checks | ✅ 5
✅ 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(context): Added typed context to h3' accurately describes the main change: introducing generic typed context support throughout the h3 framework.
Linked Issues check ✅ Passed The PR successfully implements the core requirement from #511: making event context generically typed throughout the H3 class, H3Event, handlers, and middleware, enabling per-event/per-app typed context.
Out of Scope Changes check ✅ Passed All changes are focused on implementing generic context typing across H3Event, H3 class, handlers, and middleware as specified in #511; no unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

@pkg-pr-new

pkg-pr-new Bot commented May 5, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/h3@1379

commit: 44f1f3b

@gulshan
gulshan marked this pull request as ready for review May 6, 2026 13:58
@gulshan
gulshan requested a review from pi0 as a code owner May 6, 2026 13:58

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/h3.ts (1)

232-232: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve the generic type parameter in the exported H3 constructor.

The construct signature is missing the generic type parameter, preventing callers from writing new H3<MyContext>() even though the class is implemented as generic. Add <_ContextT extends H3EventContext = H3EventContext> to the construct signature so consumers can leverage the per-app typing this PR introduces.

Suggested fix
-})() as unknown as { new (config?: H3Config): H3Type };
+})() as unknown as {
+  new <_ContextT extends H3EventContext = H3EventContext>(
+    config?: H3Config,
+  ): H3Type<_ContextT>;
+};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/h3.ts` at line 232, The exported constructor cast currently drops the
generic parameter — update the construct signature cast on the IIFE result so it
preserves the generic type parameter; change the cast of the IIFE to include
"<_ContextT extends H3EventContext = H3EventContext>" on the new signature and
ensure the returned type uses H3Type<_ContextT>, keeping H3Config as the
constructor arg type; adjust the as unknown as { new (...) : H3Type } expression
to include these generic bounds so callers can write new H3<MyContext>().
🧹 Nitpick comments (1)
src/types/handler.ts (1)

7-7: ⚡ Quick win

Import H3EventContext from its concrete module, not the barrel.

../index.ts reintroduces a barrel import in a type-only module that already imports peers directly.

Suggested fix
-import type { H3EventContext } from "../index.ts";
+import type { H3EventContext } from "./context.ts";

As per coding guidelines, "Do not use barrel files — import directly from specific modules".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/types/handler.ts` at line 7, Replace the barrel import of H3EventContext
in src/types/handler.ts with a direct import from the concrete module that
exports H3EventContext (do not import from "../index.ts"); locate the import
line importing H3EventContext and change it to import from the specific module
that defines H3EventContext so the file imports the type directly rather than
through the barrel.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/types/h3.ts`:
- Around line 128-151: The request() context parameter is typed as the full
augmented _ContextT (including middleware-added keys from addEventContext),
forcing callers to provide runtime-populated keys; change request() to accept
only the seed context (i.e. _ContextT without middleware-added keys).
Concretely, update the H3 type signatures so addEventContext() continues to
return H3<_ContextT & Record<K,V>> but request(request, options?, context?) uses
a seed type (either introduce a separate generic like _SeedT or use
Omit<_ContextT, keyof MiddlewareAddedKeys>) so callers pass only the initial
context; keep event.context typing as the augmented type used during middleware
handling. Ensure to adjust the request(...) signature and related overloads to
reference the new seed/omitted type while leaving addEventContext, H3, and event
types unchanged otherwise.
- Around line 156-160: Route-level middleware loses context because RouteOptions
is non-generic and uses Middleware[]; make RouteOptions generic (e.g.,
RouteOptions<_ContextT>) and change its middleware property to
Middleware<_ContextT>[] so middleware can access the route's context. Thread
this generic through all routing APIs: update on(), all(), and verb helpers
(get, post, put, delete, patch, head, options, connect, trace) to accept opts?:
RouteOptions<_ContextT>, and adjust H3Route (or internal route record types) to
use H3Route<_ContextT> where routes store opts/handler so everything preserves
the _ContextT generic from HTTPHandler<_ContextT> to middleware.

In `@src/types/handler.ts`:
- Around line 25-33: EventHandlerObject currently defines middleware?:
Middleware[] which drops the generic _ContextT and prevents middleware from
inferring the custom context; update the type to propagate the context by
changing middleware?: Middleware[] to middleware?: Middleware<_ContextT>[] (and
adjust any related type imports/usages) so middleware functions receive the same
_ContextT as handler and restore proper type inference for context-aware
middleware.

---

Outside diff comments:
In `@src/h3.ts`:
- Line 232: The exported constructor cast currently drops the generic parameter
— update the construct signature cast on the IIFE result so it preserves the
generic type parameter; change the cast of the IIFE to include "<_ContextT
extends H3EventContext = H3EventContext>" on the new signature and ensure the
returned type uses H3Type<_ContextT>, keeping H3Config as the constructor arg
type; adjust the as unknown as { new (...) : H3Type } expression to include
these generic bounds so callers can write new H3<MyContext>().

---

Nitpick comments:
In `@src/types/handler.ts`:
- Line 7: Replace the barrel import of H3EventContext in src/types/handler.ts
with a direct import from the concrete module that exports H3EventContext (do
not import from "../index.ts"); locate the import line importing H3EventContext
and change it to import from the specific module that defines H3EventContext so
the file imports the type directly rather than through the barrel.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d0200294-bad2-4c28-89fb-71723cd0bba9

📥 Commits

Reviewing files that changed from the base of the PR and between 84244b4 and 5ea5d68.

📒 Files selected for processing (4)
  • src/event.ts
  • src/h3.ts
  • src/types/h3.ts
  • src/types/handler.ts

Comment thread src/types/h3.ts Outdated
Comment on lines +128 to +151
request(
request: ServerRequest | URL | string,
options?: RequestInit,
context?: H3EventContext,
context?: _ContextT,
): Response | Promise<Response>;

/**
* Register a global middleware.
*/
use(route: string, handler: Middleware | H3, opts?: MiddlewareOptions): this;
use(handler: Middleware | H3, opts?: MiddlewareOptions): this;
use(
route: string,
handler: Middleware<_ContextT> | H3<_ContextT>,
opts?: MiddlewareOptions,
): this;
use(handler: Middleware<_ContextT> | H3<_ContextT>, opts?: MiddlewareOptions): this;

/**
* Register a key in the event context
*/
addEventContext<K extends string, V>(
key: K,
valFn: (event: H3Event<EventHandlerRequest, _ContextT>) => V,
): H3<_ContextT & Record<K, V>>;
addEventContext<K extends string, V>(key: K, val: V): H3<_ContextT & Record<K, V>>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

❓ Verification inconclusive

Script executed:

#!/bin/bash
# Inspect the public request/addEventContext signatures together.
sed -n '117,151p' src/types/h3.ts

Repository: h3js/h3


Repository: h3js/h3
Exit code: 0

stdout:

export declare class H3<_ContextT extends H3EventContext = H3EventContext> extends H3Core {
  /** `@internal` */
  "~rou3": RouterContext;

  /**
   * A [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)-compatible API allowing to fetch app routes.
   *
   * Input can be a URL, relative path or standard [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object.
   *
   * Returned value is a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) Promise.
   */
  request(
    request: ServerRequest | URL | string,
    options?: RequestInit,
    context?: _ContextT,
  ): Response | Promise<Response>;

  /**
   * Register a global middleware.
   */
  use(
    route: string,
    handler: Middleware<_ContextT> | H3<_ContextT>,
    opts?: MiddlewareOptions,
  ): this;
  use(handler: Middleware<_ContextT> | H3<_ContextT>, opts?: MiddlewareOptions): this;

  /**
   * Register a key in the event context
   */
  addEventContext<K extends string, V>(
    key: K,
    valFn: (event: H3Event<EventHandlerRequest, _ContextT>) => V,
  ): H3<_ContextT & Record<K, V>>;
  addEventContext<K extends string, V>(key: K, val: V): H3<_ContextT & Record<K, V>>;


Script executed:

# Find and inspect the request() method implementation
fd -t f "\.ts$" src | head -20

Repository: h3js/h3


Repository: h3js/h3
Exit code: 0

stdout:

src/_deprecated.ts
src/_entries/_common.ts
src/_entries/bun.ts
src/_entries/cloudflare.ts
src/_entries/deno.ts
src/_entries/generic.ts
src/_entries/node.ts
src/_entries/service-worker.ts
src/adapters.ts
src/error.ts
src/event.ts
src/h3.ts
src/handler.ts
src/index.ts
src/middleware.ts
src/plugin.ts
src/response.ts
src/tracing.ts
src/types/_utils.ts
src/types/context.ts


Script executed:

# Search for the implementation of the request method
rg -A 15 "request\(" src --type ts | grep -A 15 "request(request:"

Repository: h3js/h3


Repository: h3js/h3
Exit code: 1


Script executed:

# Look for tests or examples using addEventContext and request together
rg "addEventContext" --type ts -B 2 -A 5

Repository: h3js/h3


Repository: h3js/h3
Exit code: 0

stdout:

src/types/h3.ts-   * Register a key in the event context
src/types/h3.ts-   */
src/types/h3.ts:  addEventContext<K extends string, V>(
src/types/h3.ts-    key: K,
src/types/h3.ts-    valFn: (event: H3Event<EventHandlerRequest, _ContextT>) => V,
src/types/h3.ts-  ): H3<_ContextT & Record<K, V>>;
src/types/h3.ts:  addEventContext<K extends string, V>(key: K, val: V): H3<_ContextT & Record<K, V>>;
src/types/h3.ts-
src/types/h3.ts-  /**
src/types/h3.ts-   * Register a route handler for the specified HTTP method and route.
src/types/h3.ts-   */
src/types/h3.ts-  on(
--
src/h3.ts-    }
src/h3.ts-
src/h3.ts:    addEventContext<K extends string, V>(
src/h3.ts-      key: K,
src/h3.ts-      valOrFn: unknown,
src/h3.ts-    ): H3Type<_ContextT & Record<K, V>> {
src/h3.ts-      this.use(((event) => {
src/h3.ts-        event.context[key] = typeof valOrFn === "function" ? valOrFn(event) : valOrFn;


Script executed:

# Check how context parameter is actually used in request implementation
rg "context\?" src --type ts -B 2 -A 2 | head -100

Repository: h3js/h3


Repository: h3js/h3
Exit code: 0

stdout:

src/types/h3.ts-
src/types/h3.ts-  /** `@internal` */
src/types/h3.ts:  "~request"(request: ServerRequest, context?: H3EventContext): Response | Promise<Response>;
src/types/h3.ts-
src/types/h3.ts-  /** `@internal` */
--
src/types/h3.ts-    request: ServerRequest | URL | string,
src/types/h3.ts-    options?: RequestInit,
src/types/h3.ts:    context?: _ContextT,
src/types/h3.ts-  ): Response | Promise<Response>;
src/types/h3.ts-
--
src/utils/session.ts-    get id() {
src/utils/session.ts-      const context = getEventContext<H3EventContext>(event);
src/utils/session.ts:      return context?.sessions?.[sessionName]?.id;
src/utils/session.ts-    },
src/utils/session.ts-    get data() {
--
src/utils/request.ts-  }
src/utils/request.ts-
src/utils/request.ts:  return (event.req.context?.clientAddress as string) || event.req.ip || undefined;
src/utils/request.ts-}
--
src/h3.ts-  }
src/h3.ts-
src/h3.ts:  "~request"(request: ServerRequest, context?: H3EventContext): Response | Promise<Response> {
src/h3.ts-    // Create a new event instance
src/h3.ts-    const event = new H3Event(request, context, this as unknown as H3Type);
--
src/h3.ts-      _req: ServerRequest | URL | string,
src/h3.ts-      _init?: RequestInit,
src/h3.ts:      context?: _ContextT,
src/h3.ts-    ): Response | Promise<Response> {
src/h3.ts-      return this["~request"](toRequest(_req, _init), context);
--
src/adapters.ts-
src/adapters.ts-/**
src/adapters.ts: * `@deprecated` Since h3 v2 you can directly use `app.fetch(request, init?, context?)`
src/adapters.ts- */
src/adapters.ts-export function toWebHandler(
src/adapters.ts-  app: H3,
src/adapters.ts:): (request: ServerRequest, context?: H3EventContext) => Promise<Response> {
src/adapters.ts-  return (request, context) => {
src/adapters.ts-    return Promise.resolve(app.request(request, undefined, context || request.context));
--
src/adapters.ts-
src/adapters.ts-export function fromWebHandler(
src/adapters.ts:  handler: (request: ServerRequest, context?: H3EventContext) => Promise<Response>,
src/adapters.ts-): EventHandler {
src/adapters.ts-  return function _webHandler(event) {
--
src/event.ts-  static __is_event__ = true;
src/event.ts-
src/event.ts:  constructor(req: ServerRequest, context?: _ContextT, app?: H3Core) {
src/event.ts-    this.context = context || req.context || new EmptyObject();
src/event.ts-    this.req = req;


request() context parameter should not include keys added by addEventContext() middleware.

The type signature currently requires that context parameter matches the full augmented type (_ContextT & Record<K, V>), but middleware-added keys are populated at runtime during request handling, not supplied as seed context by the caller. This breaks type safety for applications that use addEventContext().

Example:

const app = new H3<{ traceId: string }>()
  .addEventContext("auth", (event) => computeAuth(event));

// Type requires both traceId and auth, but auth is middleware-populated
app.request("/x", undefined, { traceId: "abc" }); // ❌ Type error: auth missing

The request() method should accept only the seed context type (_ContextT without middleware-added keys), while event.context is correctly enhanced during middleware execution.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/types/h3.ts` around lines 128 - 151, The request() context parameter is
typed as the full augmented _ContextT (including middleware-added keys from
addEventContext), forcing callers to provide runtime-populated keys; change
request() to accept only the seed context (i.e. _ContextT without
middleware-added keys). Concretely, update the H3 type signatures so
addEventContext() continues to return H3<_ContextT & Record<K,V>> but
request(request, options?, context?) uses a seed type (either introduce a
separate generic like _SeedT or use Omit<_ContextT, keyof MiddlewareAddedKeys>)
so callers pass only the initial context; keep event.context typing as the
augmented type used during middleware handling. Ensure to adjust the
request(...) signature and related overloads to reference the new seed/omitted
type while leaving addEventContext, H3, and event types unchanged otherwise.

Comment thread src/types/h3.ts
Comment on lines 156 to 160
on(
method: HTTPMethod | Lowercase<HTTPMethod> | "",
route: string,
handler: HTTPHandler,
handler: HTTPHandler<_ContextT>,
opts?: RouteOptions,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Show the route options definition and the updated route-registration signatures.
sed -n '63,66p' src/types/h3.ts
sed -n '153,190p' src/types/h3.ts

Repository: h3js/h3

Length of output: 1835


🏁 Script executed:

#!/bin/bash
# Find the Middleware type definition and related context types
rg "export type Middleware" src/types/ -A 3
rg "export type HTTPHandler" src/types/ -A 3
rg "export.*H3EventContext" src/types/ -A 2

Repository: h3js/h3

Length of output: 1638


🏁 Script executed:

#!/bin/bash
# Check all usages of RouteOptions and how it's threaded through the codebase
rg "RouteOptions" src/types/ -B 2 -A 1
rg "RouteOptions" src/ --include="*.ts" | grep -v test | head -30

Repository: h3js/h3

Length of output: 1936


🏁 Script executed:

#!/bin/bash
# Check H3Route definition and implementation details
rg "export.*type H3Route" src/types/ -A 5
rg "class H3" src/ -t ts -A 10 | head -40

Repository: h3js/h3

Length of output: 1462


🏁 Script executed:

#!/bin/bash
# Look for H3Route type definition
rg "type H3Route" src/types/ -B 2 -A 5

Repository: h3js/h3

Length of output: 33


🏁 Script executed:

#!/bin/bash
# Search for H3Route definition more broadly
rg "H3Route" src/types/ -B 1 -A 3
rg "interface H3Route" src/ -t ts -A 5

Repository: h3js/h3

Length of output: 2954


🏁 Script executed:

#!/bin/bash
# Check EventHandler definition
rg "export type EventHandler" src/types/ -A 3
# Check the actual implementation of on() method
sed -n '100,250p' src/h3.ts | head -80

Repository: h3js/h3

Length of output: 3357


🏁 Script executed:

#!/bin/bash
# Look at more of the on() implementation and check all verb methods
sed -n '100,160p' src/h3.ts | cat -n
# Also check what HTTPHandler signature expects
rg "export type HTTPHandler" src/types/handler.ts -A 5

Repository: h3js/h3

Length of output: 2646


Route-level middleware loses the typed context.

on() and all verb helpers accept handler: HTTPHandler<_ContextT> for context awareness, but opts remains plain RouteOptions with middleware?: Middleware[]. Since Middleware is already generic (Middleware<_ContextT>), route-level middleware cannot see the added context keys.

Make RouteOptions generic and thread it through all routing methods:

Suggested direction
-export type RouteOptions = {
-  middleware?: Middleware[];
+export type RouteOptions<_ContextT extends H3EventContext = H3EventContext> = {
+  middleware?: Middleware<_ContextT>[];
   meta?: H3RouteMeta;
 };

Then update on(), all(), and the verb helpers (get, post, put, delete, patch, head, options, connect, trace) to accept opts?: RouteOptions<_ContextT>. Mirror this on H3Route if needed for internal route records.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/types/h3.ts` around lines 156 - 160, Route-level middleware loses context
because RouteOptions is non-generic and uses Middleware[]; make RouteOptions
generic (e.g., RouteOptions<_ContextT>) and change its middleware property to
Middleware<_ContextT>[] so middleware can access the route's context. Thread
this generic through all routing APIs: update on(), all(), and verb helpers
(get, post, put, delete, patch, head, options, connect, trace) to accept opts?:
RouteOptions<_ContextT>, and adjust H3Route (or internal route record types) to
use H3Route<_ContextT> where routes store opts/handler so everything preserves
the _ContextT generic from HTTPHandler<_ContextT> to middleware.

Comment thread src/types/handler.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/types/h3.ts (2)

128-132: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

request() context should be the seed context, not the augmented one.

context?: _ContextT requires callers to supply the full augmented type that includes keys populated at runtime by extendContext-installed middleware. After chaining extendContext, _ContextT becomes _ContextT & Record<K, V>, so app.request(url, init, { /* seed only */ }) will fail type checking unless the caller passes the runtime-populated keys too.

Consider tracking "seed" and "augmented" contexts as two distinct generics on H3 — e.g. H3<_SeedT, _ContextT = _SeedT> — where extendContext only widens _ContextT, while request(... , context?: _SeedT) keeps using the seed type. event.context (typed via _ContextT) remains the augmented shape.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/types/h3.ts` around lines 128 - 132, The request(...) signature currently
types the third parameter as the augmented _ContextT, forcing callers to supply
runtime-added keys; change the type model to distinguish seed vs augmented
contexts by introducing two generics on H3 (e.g. H3<_SeedT, _ContextT =
_SeedT>), have extendContext widen only the _ContextT generic while keeping
request(..., context?: _SeedT) typed with the seed context, and ensure
event.context remains _ContextT (augmented) so runtime middleware keys are
preserved for handlers but not required when calling request; update the request
declaration, extendContext signature, and any H3 instantiations to follow this
two-generic pattern to fix the type mismatch.

63-66: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

RouteOptions is generic but not threaded through routing methods.

RouteOptions<_ContextT> was made generic, but on, all, and the verb helpers still declare opts?: RouteOptions (defaulting to H3EventContext). As a result, route-level middleware passed via opts.middleware falls back to the base Middleware<H3EventContext> and cannot see keys added by extendContext, which is exactly what the generic was introduced to enable.

   on(
     method: HTTPMethod | Lowercase<HTTPMethod> | "",
     route: string,
     handler: HTTPHandler<_ContextT>,
-    opts?: RouteOptions,
+    opts?: RouteOptions<_ContextT>,
   ): this;

-  all(route: string, handler: HTTPHandler<_ContextT>, opts?: RouteOptions): this;
-  get(route: string, handler: HTTPHandler<_ContextT>, opts?: RouteOptions): this;
-  post(route: string, handler: HTTPHandler<_ContextT>, opts?: RouteOptions): this;
-  put(route: string, handler: HTTPHandler<_ContextT>, opts?: RouteOptions): this;
-  delete(route: string, handler: HTTPHandler<_ContextT>, opts?: RouteOptions): this;
-  patch(route: string, handler: HTTPHandler<_ContextT>, opts?: RouteOptions): this;
-  head(route: string, handler: HTTPHandler<_ContextT>, opts?: RouteOptions): this;
-  options(route: string, handler: HTTPHandler<_ContextT>, opts?: RouteOptions): this;
-  connect(route: string, handler: HTTPHandler<_ContextT>, opts?: RouteOptions): this;
-  trace(route: string, handler: HTTPHandler<_ContextT>, opts?: RouteOptions): this;
+  all(route: string, handler: HTTPHandler<_ContextT>, opts?: RouteOptions<_ContextT>): this;
+  get(route: string, handler: HTTPHandler<_ContextT>, opts?: RouteOptions<_ContextT>): this;
+  post(route: string, handler: HTTPHandler<_ContextT>, opts?: RouteOptions<_ContextT>): this;
+  put(route: string, handler: HTTPHandler<_ContextT>, opts?: RouteOptions<_ContextT>): this;
+  delete(route: string, handler: HTTPHandler<_ContextT>, opts?: RouteOptions<_ContextT>): this;
+  patch(route: string, handler: HTTPHandler<_ContextT>, opts?: RouteOptions<_ContextT>): this;
+  head(route: string, handler: HTTPHandler<_ContextT>, opts?: RouteOptions<_ContextT>): this;
+  options(route: string, handler: HTTPHandler<_ContextT>, opts?: RouteOptions<_ContextT>): this;
+  connect(route: string, handler: HTTPHandler<_ContextT>, opts?: RouteOptions<_ContextT>): this;
+  trace(route: string, handler: HTTPHandler<_ContextT>, opts?: RouteOptions<_ContextT>): this;

Also applies to: 157-162, 181-191

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/types/h3.ts` around lines 63 - 66, RouteOptions is generic but its type
parameter isn't threaded into routing APIs, so change the signatures of
functions like on, all, and all HTTP verb helpers that accept opts?:
RouteOptions to accept opts?: RouteOptions<_ContextT> (or a matching generic
param) so route-level middleware uses Middleware<_ContextT> instead of
Middleware<H3EventContext>; update the corresponding function/method generic
declarations where you see opts?: RouteOptions to propagate the same _ContextT
generic (and adjust callers/exports) and ensure extendContext's augmented
context type is used by middleware typed as Middleware<_ContextT>.
🧹 Nitpick comments (1)
src/types/h3.ts (1)

144-152: 💤 Low value

extendContext value overload + typeof === "function" runtime check.

The implementation in src/h3.ts (line 210) treats any valOrFn whose typeof is "function" as a factory. The value overload extendContext<K, V>(key: K, val: V) therefore silently turns a callable V (e.g. a class, bound method, or stringified-handler value) into a factory invocation. This isn't reflected in the type system. Consider either splitting into two methods (extendContext for static values, extendContextFactory / extendContextWith for the function form) or adding a { value: V } | { factory: (...) => V } shape so intent is explicit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/types/h3.ts` around lines 144 - 152, The current extendContext overloads
conflate static values and factory functions causing callable values to be
treated as factories; update the API to a discriminated-union form and adjust
the impl instead of using typeof checks: change the extendContext signatures
(and call sites) to accept either { value: V } or { factory: (event:
H3Event<EventHandlerRequest, _ContextT>) => V } (e.g., extendContext<K extends
string, V>(key: K, opt: { value: V } | { factory: (event: H3Event<...>) => V }):
H3<_ContextT & Record<K, V>>), then modify the implementation (the function
currently inspecting valOrFn / valFn) to branch on the discriminant (e.g.,
'factory' in opt) and only call the factory when provided; update any callers to
pass the correct shape so callable values are preserved as static values unless
explicitly wrapped as a factory.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/h3.ts`:
- Around line 208-212: The extendContext implementation currently mutates the
instance with this.use(...) but its signature/JSDoc promise a new H3<_ContextT &
Record<K,V>> instance and also treats any function-valued valOrFn as a factory;
fix by either (A) changing extendContext to create and return a new H3 instance
that clones current routes/middleware and registers the new middleware so only
subsequently-added handlers see the new context (update JSDoc and preserve the
H3<_ContextT & Record<K,V>> return type), or (B) keep returning this but change
the TypeScript signature and JSDoc to return this and drop the widened context
type, documenting the global effect; additionally disambiguate valOrFn by using
an overload or explicit predicate (not plain typeof) so a user-supplied function
value isn’t unintentionally invoked (refer to extendContext, use, Middleware,
H3, and event.context when making these edits).

---

Duplicate comments:
In `@src/types/h3.ts`:
- Around line 128-132: The request(...) signature currently types the third
parameter as the augmented _ContextT, forcing callers to supply runtime-added
keys; change the type model to distinguish seed vs augmented contexts by
introducing two generics on H3 (e.g. H3<_SeedT, _ContextT = _SeedT>), have
extendContext widen only the _ContextT generic while keeping request(...,
context?: _SeedT) typed with the seed context, and ensure event.context remains
_ContextT (augmented) so runtime middleware keys are preserved for handlers but
not required when calling request; update the request declaration, extendContext
signature, and any H3 instantiations to follow this two-generic pattern to fix
the type mismatch.
- Around line 63-66: RouteOptions is generic but its type parameter isn't
threaded into routing APIs, so change the signatures of functions like on, all,
and all HTTP verb helpers that accept opts?: RouteOptions to accept opts?:
RouteOptions<_ContextT> (or a matching generic param) so route-level middleware
uses Middleware<_ContextT> instead of Middleware<H3EventContext>; update the
corresponding function/method generic declarations where you see opts?:
RouteOptions to propagate the same _ContextT generic (and adjust
callers/exports) and ensure extendContext's augmented context type is used by
middleware typed as Middleware<_ContextT>.

---

Nitpick comments:
In `@src/types/h3.ts`:
- Around line 144-152: The current extendContext overloads conflate static
values and factory functions causing callable values to be treated as factories;
update the API to a discriminated-union form and adjust the impl instead of
using typeof checks: change the extendContext signatures (and call sites) to
accept either { value: V } or { factory: (event: H3Event<EventHandlerRequest,
_ContextT>) => V } (e.g., extendContext<K extends string, V>(key: K, opt: {
value: V } | { factory: (event: H3Event<...>) => V }): H3<_ContextT & Record<K,
V>>), then modify the implementation (the function currently inspecting valOrFn
/ valFn) to branch on the discriminant (e.g., 'factory' in opt) and only call
the factory when provided; update any callers to pass the correct shape so
callable values are preserved as static values unless explicitly wrapped as a
factory.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7bf9e14a-9faf-415d-bbb4-a953b0d4160b

📥 Commits

Reviewing files that changed from the base of the PR and between 5ea5d68 and 44f1f3b.

📒 Files selected for processing (3)
  • src/h3.ts
  • src/types/h3.ts
  • src/types/handler.ts

Comment thread src/h3.ts
Comment on lines +208 to +212
extendContext(key: string, valOrFn: unknown): this {
return this.use(((event) => {
event.context[key] = typeof valOrFn === "function" ? valOrFn(event) : valOrFn;
}) as Middleware);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

extendContext fluent typing doesn't match runtime scoping.

The declared signature in src/types/h3.ts returns H3<_ContextT & Record<K, V>> (and the JSDoc on line 146 there says it returns "a new H3 instance"), implying that only handlers/middleware registered after the call see the extra key. The implementation, however, just calls this.use(...) and returns this, so the appended middleware runs for every route on the instance — including ones registered before extendContext. Earlier routes get the key at runtime but their event.context is typed without it, while the original H3<_ContextT> reference still being held by callers becomes unsound.

Two related concerns:

  1. The typing should either be honored by returning a new instance (or scoping the middleware to subsequently-added routes), or the JSDoc should be corrected and the return type should remain this (H3<_ContextT & Record<K, V>> cast aside) with a documented caveat.
  2. valOrFn: unknown + typeof valOrFn === "function" means a caller using the value-overload with a function value will have it invoked as a factory. Consider distinguishing by overload at the implementation level (e.g., separate methods, or an isFn flag) if that case matters.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/h3.ts` around lines 208 - 212, The extendContext implementation currently
mutates the instance with this.use(...) but its signature/JSDoc promise a new
H3<_ContextT & Record<K,V>> instance and also treats any function-valued valOrFn
as a factory; fix by either (A) changing extendContext to create and return a
new H3 instance that clones current routes/middleware and registers the new
middleware so only subsequently-added handlers see the new context (update JSDoc
and preserve the H3<_ContextT & Record<K,V>> return type), or (B) keep returning
this but change the TypeScript signature and JSDoc to return this and drop the
widened context type, documenting the global effect; additionally disambiguate
valOrFn by using an overload or explicit predicate (not plain typeof) so a
user-supplied function value isn’t unintentionally invoked (refer to
extendContext, use, Middleware, H3, and event.context when making these edits).

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.

Augment H3Context to improve event.context typing

1 participant