Skip to content

feat(sse): allow returning EventStream directly from handlers#1508

Merged
pi0 merged 2 commits into
mainfrom
feat/return-event-stream
Jul 22, 2026
Merged

feat(sse): allow returning EventStream directly from handlers#1508
pi0 merged 2 commits into
mainfrom
feat/return-event-stream

Conversation

@pi0x

@pi0x pi0x commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Handlers can now return eventStream instead of return eventStream.send().

EventStream extends HTTPResponse, so the existing toResponse
prepareResponseBody branch (val instanceof HTTPResponse) renders it as the
response directly — streaming the readable side with the SSE headers. No
core / response.ts changes were needed.

app.get("/sse", (event) => {
  const eventStream = createEventStream(event);
  const interval = setInterval(() => eventStream.push("Hello world"), 1000);
  eventStream.onClosed(() => clearInterval(interval));
  return eventStream; // previously: return eventStream.send()
});

Details

  • EventStream extends HTTPResponse: the transform stream is created before
    super() so its readable side becomes the response body, with status: 200
    and the SSE headers passed to super().
  • Extracted an eventStreamHeaders(event) helper shared by the direct-return
    path and setEventStreamHeaders().
  • send() is kept and fully backward-compatible (still stages headers on
    event.res and returns the readable), so return eventStream.send() keeps
    working.
  • Prepared event.res.headers still merge in as before; HEAD / null-body
    handling comes for free via the HTTPResponse path.

Tests

  • Added a regression test asserting the direct-return path streams events with
    text/event-stream and status 200 (runs in both web + node matrix).
  • Updated docs/examples (return eventStream).
  • Full suite green: 1610 passed, no type errors.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • SSE handlers can now return event streams directly for simpler streaming responses.
    • Added centralized SSE header handling to standardize response headers.
  • Bug Fixes

    • Improved event stream response initialization and streaming behavior while keeping existing push/cleanup behavior.
  • Documentation

    • Updated SSE guides and examples to show handlers returning the event stream directly.
  • Tests

    • Added an SSE test covering direct event stream returns and verification of multiple streamed messages.

EventStream now extends HTTPResponse, so handlers can `return eventStream`
instead of `return eventStream.send()`. The existing `toResponse` /
HTTPResponse branch renders it as the response (streaming the readable side
with the SSE headers), so no core/response changes are needed.

`send()` is kept for backward compatibility.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@pi0x
pi0x requested a review from pi0 as a code owner July 22, 2026 23:05
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 841ee340-7391-4c8e-8e4b-d0f691a56866

📥 Commits

Reviewing files that changed from the base of the PR and between 8c25366 and a3bd5e6.

📒 Files selected for processing (1)
  • src/utils/internal/event-stream.ts

📝 Walkthrough

Walkthrough

SSE handlers now return EventStream instances directly. EventStream extends HTTPResponse, initializes SSE response metadata, centralizes header creation, and gains integration coverage plus updated documentation examples.

Changes

SSE direct response flow

Layer / File(s) Summary
HTTPResponse-backed stream implementation
src/utils/internal/event-stream.ts
EventStream now extends HTTPResponse, initializes its readable stream with status and SSE headers, removes _handled, and centralizes header generation.
Direct-return examples and validation
examples/server-sent-events.mjs, docs/.../2.websocket.md, docs/2.utils/2.response.md, src/utils/event-stream.ts, test/sse.test.ts
Examples return eventStream directly, and an SSE test validates response headers and three streamed messages.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • h3js/h3#1450: Both changes update the createEventStream SSE examples in the source and response documentation.

Suggested reviewers: pi0

Poem

I’m a bunny with a stream to send,
No .send() call around the bend.
Headers bloom and messages hop,
Three bright carrots—plop, plop, plop!
SSE bounds through HTTP’s door,
Directly flowing evermore.

🚥 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 matches the main change: allowing EventStream to be returned directly from handlers.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/return-event-stream

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.

🧹 Nitpick comments (1)
test/sse.test.ts (1)

58-62: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert all guaranteed SSE headers.

This regression test checks status and Content-Type, but it would still pass if cache-control or x-accel-buffering regressed. Add assertions for those headers while leaving the conditional connection header runtime-dependent.

Proposed test additions
     expect(res.headers.get("Content-Type")).toBe("text/event-stream");
+    expect(res.headers.get("cache-control")).toBe(
+      "private, no-cache, no-store, no-transform, must-revalidate, max-age=0",
+    );
+    expect(res.headers.get("x-accel-buffering")).toBe("no");
🤖 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 `@test/sse.test.ts` around lines 58 - 62, Extend the SSE response assertions in
the test around the existing status and Content-Type checks to also verify the
guaranteed cache-control and x-accel-buffering headers. Do not assert the
connection header, since its value is runtime-dependent, and preserve the
existing message-count assertion.
🤖 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.

Nitpick comments:
In `@test/sse.test.ts`:
- Around line 58-62: Extend the SSE response assertions in the test around the
existing status and Content-Type checks to also verify the guaranteed
cache-control and x-accel-buffering headers. Do not assert the connection
header, since its value is runtime-dependent, and preserve the existing
message-count assertion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: de9a9bcc-6b89-459d-ab35-021e31253c8f

📥 Commits

Reviewing files that changed from the base of the PR and between 8dc8a28 and 8c25366.

📒 Files selected for processing (6)
  • docs/1.guide/901.advanced/2.websocket.md
  • docs/2.utils/2.response.md
  • examples/server-sent-events.mjs
  • src/utils/event-stream.ts
  • src/utils/internal/event-stream.ts
  • test/sse.test.ts

@pi0
pi0 merged commit c82019d into main Jul 22, 2026
5 of 7 checks passed
@pi0
pi0 deleted the feat/return-event-stream branch July 22, 2026 23:09
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