Skip to content

refactor(sse): promote EventStream to public API, deprecate createEventStream#1509

Merged
pi0 merged 4 commits into
mainfrom
refactor/event-stream-public-api
Jul 22, 2026
Merged

refactor(sse): promote EventStream to public API, deprecate createEventStream#1509
pi0 merged 4 commits into
mainfrom
refactor/event-stream-public-api

Conversation

@pi0x

@pi0x pi0x commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Promotes the EventStream class to a first-class public export (import { EventStream } from "h3").
  • Moves the implementation out of src/utils/internal/event-stream.ts into the public src/utils/event-stream.ts. The low-level, purely-internal helpers (string formatters + header helpers) remain in internal/.
  • Soft-deprecates createEventStream and relocates it to src/_deprecated.ts as a thin compatibility wrapper around new EventStream(event).
  • Updates docs (docs/1.guide/901.advanced/2.websocket.md), the runnable example, and MIGRATION.md to prefer constructing the class directly.

Motivation

createEventStream was marked @experimental and only ever wrapped new EventStream(event). Now that the class is stable it's cleaner to expose it directly; the wrapper stays for compatibility so this is a non-breaking change.

Test plan

  • pnpm lint (oxlint + oxfmt) ✅
  • npx tsc --noEmit
  • pnpm vitest run test/sse.test.ts test/unit/sse.test.ts — 41 passing ✅

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added the EventStream class for creating and managing Server-Sent Event streams.
    • Added isEventStream to identify event stream responses.
    • EventStream supports pausing/resuming, buffering, pushing messages/comments, close callbacks, and automatic cleanup.
  • Documentation

    • Updated SSE guides and examples to use new EventStream(event).
    • Updated “Event Stream” API docs and migration notes.
  • Deprecations

    • createEventStream remains as a deprecated compatibility helper.
  • Tests

    • Updated SSE and export-list snapshots to reflect the new API.

…ntStream

Make the `EventStream` class a first-class public export and move its
implementation out of `utils/internal/` into `utils/event-stream.ts`. The
pure formatters and header helpers stay internal.

`createEventStream` becomes a soft-deprecated compatibility wrapper and moves
to `_deprecated.ts`. Docs, examples, and the migration guide now prefer
`new EventStream(event)`.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@pi0x
pi0x requested a review from pi0 as a code owner July 22, 2026 23:16
@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: db71a32f-fb86-4a09-8a58-019e72de4ff3

📥 Commits

Reviewing files that changed from the base of the PR and between 4e26d0f and 5847e38.

📒 Files selected for processing (2)
  • docs/1.guide/901.advanced/2.websocket.md
  • test/unit/package.test.ts

📝 Walkthrough

Walkthrough

Changes

EventStream API migration

Layer / File(s) Summary
EventStream lifecycle implementation
src/utils/event-stream.ts, src/utils/internal/event-stream.ts
EventStream now implements SSE construction, publishing, buffering, lifecycle handling, callbacks, legacy sending, and runtime detection.
Public exports and compatibility wiring
src/index.ts, src/_deprecated.ts, test/unit/sse.test.ts, test/unit/package.test.ts
The main export exposes EventStream, while deprecated createEventStream wraps the constructor and tests use the public implementation and updated export list.
Usage examples and migration guidance
docs/..., examples/server-sent-events.mjs, MIGRATION.md
Documentation, examples, and migration guidance use direct EventStream construction and document isEventStream.

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

Possibly related PRs

  • h3js/h3#1450: Updates related SSE documentation and createEventStream examples.
  • h3js/h3#1488: Adds related onDispose SSE cleanup wiring.
  • h3js/h3#1508: Implements related direct EventStream returns and SSE behavior.

Suggested reviewers: pi0

Poem

I’m a bunny with a stream to send,
New EventStream hops around the bend.
Old helpers rest, compatibility stays,
Messages and comments flow through sunny days.
SSE carrots bloom in code so bright! 🐇

🚥 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 accurately summarizes the main change: EventStream becomes public while createEventStream is deprecated.
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 refactor/event-stream-public-api

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.

@pi0
pi0 merged commit dd19737 into main Jul 22, 2026
7 of 9 checks passed
@pi0
pi0 deleted the refactor/event-stream-public-api branch July 22, 2026 23:22

@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: 2

🧹 Nitpick comments (2)
src/utils/event-stream.ts (2)

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

Move _noop to the internal helper section.

Internal helpers belong at the end of the file or under src/utils/internal/. As per coding guidelines, “Place internal helpers at the end of files or in src/utils/internal/.”

🤖 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/event-stream.ts` at line 12, Move the _noop helper from its current
top-level position into the file’s internal helper section at the end of the
file, preserving its existing implementation and usages.

Source: Coding guidelines


59-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Use # private fields for stream state.

These fields are implementation-private but use _ names. Convert them to # fields (and update references) to follow the source convention and enforce runtime privacy. As per coding guidelines, “Use k prefixes for symbol constants, ~ prefixes for private/non-enumerable properties, and # for truly private class fields.”

🤖 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/event-stream.ts` around lines 59 - 69, Convert the
implementation-private fields in the event-stream class—_event,
_transformStream, _writer, _encoder, _closeCallbacks, _writerIsClosed, _paused,
_unsentData, and _disposed—to # private fields, and update every reference
within the class accordingly. Preserve their existing types, initializers, and
behavior.

Source: Coding guidelines

🤖 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/index.ts`:
- Around line 162-166: Update the public export block in src/index.ts to
re-export the documented isEventStream guard alongside EventStream and its
related types, so consumers can import it from the package root. Keep the
existing event-stream exports unchanged.

In `@src/utils/event-stream.ts`:
- Around line 195-203: Update flush() to detach and clear _unsentData before
awaiting _writer.write: capture the current payload in a local snapshot, reset
the pending buffer immediately, then encode and write only that snapshot.
Preserve newly pushed data for a later flush and ensure concurrent flush() calls
cannot write the same payload twice.

---

Nitpick comments:
In `@src/utils/event-stream.ts`:
- Line 12: Move the _noop helper from its current top-level position into the
file’s internal helper section at the end of the file, preserving its existing
implementation and usages.
- Around line 59-69: Convert the implementation-private fields in the
event-stream class—_event, _transformStream, _writer, _encoder, _closeCallbacks,
_writerIsClosed, _paused, _unsentData, and _disposed—to # private fields, and
update every reference within the class accordingly. Preserve their existing
types, initializers, and behavior.
🪄 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: d58fab8b-3130-4a68-8373-2a8c937f27b8

📥 Commits

Reviewing files that changed from the base of the PR and between c82019d and 4e26d0f.

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

Comment thread src/index.ts
Comment on lines 162 to 166
export {
type EventStreamMessage,
type EventStreamOptions,
createEventStream,
EventStream,
} from "./utils/event-stream.ts";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Export the documented isEventStream guard.

docs/2.utils/2.response.md now documents isEventStream(input), but this public export list omits it. import { isEventStream } from "h3" will fail. Re-export it here, or remove it from the public docs.

 export {
   type EventStreamMessage,
   type EventStreamOptions,
   EventStream,
+  isEventStream,
 } from "./utils/event-stream.ts";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export {
type EventStreamMessage,
type EventStreamOptions,
createEventStream,
EventStream,
} from "./utils/event-stream.ts";
export {
type EventStreamMessage,
type EventStreamOptions,
EventStream,
isEventStream,
} from "./utils/event-stream.ts";
🤖 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/index.ts` around lines 162 - 166, Update the public export block in
src/index.ts to re-export the documented isEventStream guard alongside
EventStream and its related types, so consumers can import it from the package
root. Keep the existing event-stream exports unchanged.

Comment thread src/utils/event-stream.ts
Comment on lines +195 to +203
async flush(): Promise<void> {
if (this._isClosed) {
return;
}
if (this._unsentData?.length) {
await this._writer.write(this._encoder.encode(this._unsentData)).catch(() => {
this._writerIsClosed = true;
});
this._unsentData = undefined;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Detach the pending buffer before awaiting the write.

A concurrent pause() followed by push() can append to _unsentData while Line 200 awaits; Line 203 then clears that newly appended event. Concurrent flush() calls can likewise send the same payload twice. Snapshot and clear the buffer before awaiting.

Proposed fix
 async flush(): Promise<void> {
   if (this._isClosed) {
     return;
   }
-  if (this._unsentData?.length) {
-    await this._writer.write(this._encoder.encode(this._unsentData)).catch(() => {
+  const unsentData = this._unsentData;
+  this._unsentData = undefined;
+  if (unsentData?.length) {
+    await this._writer.write(this._encoder.encode(unsentData)).catch(() => {
       this._writerIsClosed = true;
     });
-    this._unsentData = undefined;
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async flush(): Promise<void> {
if (this._isClosed) {
return;
}
if (this._unsentData?.length) {
await this._writer.write(this._encoder.encode(this._unsentData)).catch(() => {
this._writerIsClosed = true;
});
this._unsentData = undefined;
async flush(): Promise<void> {
if (this._isClosed) {
return;
}
const unsentData = this._unsentData;
this._unsentData = undefined;
if (unsentData?.length) {
await this._writer.write(this._encoder.encode(unsentData)).catch(() => {
this._writerIsClosed = true;
});
}
🤖 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/event-stream.ts` around lines 195 - 203, Update flush() to detach
and clear _unsentData before awaiting _writer.write: capture the current payload
in a local snapshot, reset the pending buffer immediately, then encode and write
only that snapshot. Preserve newly pushed data for a later flush and ensure
concurrent flush() calls cannot write the same payload twice.

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