Skip to content

types: expose .crossws on defineWebSocketHandler return type#1435

Merged
pi0 merged 7 commits into
h3js:mainfrom
momomuchu:fix/websocket-handler-crossws-type-1258
Jul 5, 2026
Merged

types: expose .crossws on defineWebSocketHandler return type#1435
pi0 merged 7 commits into
h3js:mainfrom
momomuchu:fix/websocket-handler-crossws-type-1258

Conversation

@momomuchu

@momomuchu momomuchu commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

defineWebSocketHandler() attaches crossws hooks to the returned Response at runtime, but the declared return type resolved to unknown, so reading res.crossws needed a cast (#1258).

This adds a scoped WebSocketResponse type and typed overloads (hooks-only and http-fallback) so crossws is visible. Type-only change, runtime behaviour is unchanged. Kept scoped rather than introducing a global response type, per the maintainer note on #1306.

Tests: a runtime check that res.crossws equals the hooks without a cast, plus expectTypeOf assertions. Full suite passes, tsc and lint clean.

Closes #1258

Summary by CodeRabbit

  • New Features
    • Exposed a typed WebSocketResponse from the package entrypoint.
    • Enhanced defineWebSocketHandler typing so WebSocket responses include crossws in a type-safe way.
  • Bug Fixes
    • When using async hook factories, crossws is now attached only after hooks resolve, ensuring correct values on 426 Upgrade Required.
    • Kept accurate return-type inference when combining WebSocket handling with an HTTP fallback.
  • Tests
    • Added type-level and runtime coverage for crossws presence and async hook behavior.
  • Documentation
    • Removed outdated defineWebSocketHandler behavioral details from the docs.

@momomuchu
momomuchu requested a review from pi0 as a code owner July 4, 2026 13:05
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a WebSocketResponse type with crossws, updates defineWebSocketHandler typing and async response construction, re-exports the new type, removes stale docs, and adds type and runtime tests.

Changes

WebSocket Handler Typing

Layer / File(s) Summary
WebSocketResponse type and defineWebSocketHandler overloads
src/utils/ws.ts, src/index.ts
Adds exported WebSocketResponse, updates defineWebSocketHandler overloads and async hooks handling, and re-exports the new type from the package entrypoint.
Type tests, runtime tests, and docs cleanup
test/unit/types.test-d.ts, test/ws.test.ts, docs/2.utils/9.more.md
Adds type assertions for direct, fallback, and async handler returns, adds runtime checks for crossws, and removes descriptive WebSocket docs content.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • h3js/h3#1242: Both PRs change src/utils/ws.ts’s defineWebSocketHandler shape to support a hooks factory callback and async resolution.
  • h3js/h3#1425: Both PRs adjust defineWebSocketHandler typing around the optional http fallback path.

Suggested labels: enhancement

Suggested reviewers: pi0

Poem

A rabbit hopped with types so bright,
and crossws now lands just right.
The handler waits, then springs to view,
with fallback paths that type-check too. 🐇

🚥 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 clearly states the main change: exposing .crossws on defineWebSocketHandler's return type.
Linked Issues check ✅ Passed The PR exposes crossws in the TypeScript return type and adds tests, matching the bugfix goal in #1258.
Out of Scope Changes check ✅ Passed No clear unrelated code changes stand out; the type updates, overloads, and tests all support the websocket typing fix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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

🤖 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/utils/ws.ts`:
- Around line 13-21: The WebSocket response type and `defineWebSocketHandler()`
integration currently assume `crossws` is a synchronous Partial<WebSocketHooks>,
but async hook factories can return a Promise and end up attaching the wrong
runtime shape. Update the `WebSocketResponse`/`crossws` flow so the hooks
produced by `defineWebSocketHandler()` are awaited before assignment, or
otherwise restrict the accepted hook factory overloads to sync-only behavior;
make sure the change is reflected where `crossws` is attached and consumed by
adapters.
🪄 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: 99f5b9bf-f76e-4619-9bb1-a4bacb6bcc7c

📥 Commits

Reviewing files that changed from the base of the PR and between 8786f23 and 77e0dc8.

📒 Files selected for processing (11)
  • src/adapters.ts
  • src/event.ts
  • src/index.ts
  • src/utils/ws.ts
  • test/app.test.ts
  • test/bench/bundle.test.ts
  • test/event.test.ts
  • test/unit/event.test.ts
  • test/unit/request.test.ts
  • test/unit/types.test-d.ts
  • test/ws.test.ts

Comment thread src/utils/ws.ts
@pi0 pi0 changed the title fix(ws): expose crossws on defineWebSocketHandler return type types: expose .crossws on defineWebSocketHandler return type Jul 4, 2026

@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

🧹 Nitpick comments (1)
src/utils/ws.ts (1)

96-115: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated response-construction logic.

The Object.assign(new Response("WebSocket upgrade is required.", { status: 426 }), { crossws: ... }) pattern is duplicated between the async-resolution branch and the sync fallback, differing only in the crossws value used.

♻️ Suggested consolidation
+    const toUpgradeResponse = (resolvedCrossws: Partial<WebSocketHooks>) =>
+      Object.assign(
+        new Response("WebSocket upgrade is required.", { status: 426 }),
+        { crossws: resolvedCrossws },
+      );
+
     if (crossws instanceof Promise) {
-      return crossws.then((resolvedCrossws) =>
-        Object.assign(
-          new Response("WebSocket upgrade is required.", {
-            status: 426,
-          }),
-          {
-            crossws: resolvedCrossws,
-          },
-        ),
-      );
+      return crossws.then(toUpgradeResponse);
     }

-    return Object.assign(
-      new Response("WebSocket upgrade is required.", {
-        status: 426,
-      }),
-      {
-        crossws,
-      },
-    );
+    return toUpgradeResponse(crossws);
🤖 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/utils/ws.ts` around lines 96 - 115, The response-construction logic in
the websocket upgrade handler is duplicated between the `crossws.then(...)`
branch and the sync fallback. Refactor the repeated `Object.assign(new
Response("WebSocket upgrade is required.", { status: 426 }), { crossws: ... })`
pattern into a shared helper or single construction path in `src/utils/ws.ts`,
and have both branches reuse it by passing the resolved `crossws` value.
🤖 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/utils/ws.ts`:
- Around line 20-24: The WebSocket response typing in
WebSocketResponse/EventHandler currently only models a synchronous Response,
which forces casts for async hooks factories. Update the types around
defineWebSocketHandler so async hooks factories are represented as
Promise<WebSocketResponse> (or a union that preserves awaited responses), and
ensure crossws remains available after awaiting the response without requiring
casts.

---

Nitpick comments:
In `@src/utils/ws.ts`:
- Around line 96-115: The response-construction logic in the websocket upgrade
handler is duplicated between the `crossws.then(...)` branch and the sync
fallback. Refactor the repeated `Object.assign(new Response("WebSocket upgrade
is required.", { status: 426 }), { crossws: ... })` pattern into a shared helper
or single construction path in `src/utils/ws.ts`, and have both branches reuse
it by passing the resolved `crossws` value.
🪄 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: d06649bb-cf16-4e49-bc9c-32f93151d016

📥 Commits

Reviewing files that changed from the base of the PR and between d427804 and 892bfe0.

📒 Files selected for processing (2)
  • src/utils/ws.ts
  • test/ws.test.ts

Comment thread src/utils/ws.ts
@momomuchu

Copy link
Copy Markdown
Contributor Author

The async-factory await concern was fixed in 892bfe0 (the factory result is awaited before crossws is attached, covered by the awaits-an-async-hooks-factory test). The follow-up review's return-type point is also addressed now: defineWebSocketHandler's factory overload returns WebSocketResponse | Promise, with a type test that fails on the sync-only type. Resolving as addressed.

momomuchu and others added 7 commits July 5, 2026 17:20
`defineWebSocketHandler()` attaches `crossws` hooks to the 426 Response at
runtime via `Object.assign`, but its return type was a plain `EventHandler`
whose response resolved to `unknown`. Consumers (and the crossws adapter
pattern of reading `res.crossws`) had to cast with `as any` to reach the
field, and `app.fetch`-style callers saw no `crossws` at all.

Add a scoped `WebSocketResponse = Response & { crossws?: Partial<WebSocketHooks> }`
type and typed overloads so the returned handler's response exposes `crossws`
without a cast. The http-fallback overload preserves the fallback handler's
return type in the union. This is a type-only change; runtime behavior is
unchanged.

Fixes the type mismatch from h3js#1258 with a scoped augmentation rather than a
global response type, per maintainer guidance on h3js#1306.

Closes h3js#1258
defineWebSocketHandler() accepted an async hooks(event) factory but never
awaited it, so res.crossws ended up as an unresolved Promise instead of the
hooks object. Await it before attaching, and keep the sync path untouched.
defineWebSocketHandler only declared a sync WebSocketResponse return type,
so an async hooks factory still forced a cast to await the response and
read crossws. Split the overloads by hooks shape so a factory (sync or
async) returns WebSocketResponse | Promise<WebSocketResponse>, while plain
object hooks keep the sync-only type. Also extract the duplicated 426
response construction into one toUpgradeResponse helper.
The async factory test awaited res and checked crossws on the awaited
value, but Awaited<T> is a no-op on non-Promise types and crossws is
optional on WebSocketResponse, so this passed even with a sync-only
overload. Assert on res itself against the union type instead.
@pi0x
pi0x force-pushed the fix/websocket-handler-crossws-type-1258 branch from 0353f07 to 3731217 Compare July 5, 2026 17:21
@pi0
pi0 merged commit 1ff4999 into h3js:main Jul 5, 2026
9 checks passed
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.

Type mismatch occurred when using crossws

2 participants