test: check event streams against EventSource on Node, Deno and Bun - #137
Conversation
A server built with standard-server now streams events to a real EventSource, which must receive the message, close and error events. Node and Deno use their built-in EventSource; the tests are skipped where it is missing (Bun today). Vitest workers start with --experimental-eventsource, since Node 20-26 only expose the EventSource global behind that flag.
@standard-server/aws-lambda
@standard-server/core
@standard-server/fastify
@standard-server/fetch
@standard-server/node
@standard-server/peer
@standard-server/shared
commit: |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
ℹ️ No critical issues — one coverage observation and one documentation nit.
Reviewed changes
- Node, Deno and Bun EventSource e2e tests —
tests/event-source.test.ts(root),tests/bun/tests/event-source.test.ts, andtests/deno/tests/event-source.test.tsstand up a server viasendStandardResponse/toFetchResponse, stream an async generator, and assert that a realEventSourceobserves themessage,close, anderrorevents with their exact JSON payloads. vitest.config.ts— addstest.execArgv: ['--experimental-eventsource'], the flag Node 20–26 requires to expose the built-inEventSourceglobal; the Node test self-skips if the global is absent.
ℹ️ The typical generator-completion path is both untested and EventSource-incompatible
The tests only assert a generator that returns a value (return 'bye'), because a generator that completes with no value encodes event: close with no data: line — EventSource discards a data-less event and reconnects, so the client never sees close and the test would hang. Finishing without a value is the normal way an event stream ends, so the one path most user handlers take is exactly the case these tests cannot assert. The PR body documents this, but the test files do not.
Technical details
# Default completion emits a data-less close that EventSource ignores
## Affected sites
- `tests/event-source.test.ts:43-46`, `tests/bun/tests/event-source.test.ts:42-45`, `tests/deno/tests/event-source.test.ts:39-42` — generators return a value to force a data-bearing `close`
- `packages/fetch/src/event-stream.ts:189` — `close` data is `stringifyJSON(result.value)`; `undefined` yields no `data:` line
- `packages/core/src/event-stream/encoder.ts:50-56` / `:77-81` — `data === undefined` emits no `data:` line, and `event: close` is still emitted
## Required outcome
- No behavior change is required for this PR. At minimum, the test files should carry the same caveat the PR body does, so the value-returning generators are not "simplified" into a hang.
## Open questions for the human
- Is a data-less `close` arriving at EventSource as a reconnect (never a `close`) an accepted protocol limitation, or should the sender synthesize `data` for a value-less close? If accepted, stating it in the event-stream contract would help consumers.ℹ️ Nitpicks
receive()resolves on the firstclose/errorevent but never rejects; if a regression prevents either, the test hangs until the default test timeout. A small timeout-rejection would turn an ambiguous timeout into a clear failure.describe.skipIf(typeof EventSource === 'undefined')means the Node/Bun tests can silently stop running if the global disappears (e.g. a future Node drops the flag, or Bun restructures it). That trade-off is deliberate per the PR body, but a silent skip is easy to miss.
DeepSeek Flash (default — pick a model for stronger reviews) | 𝕏

Event streams from standard-server are now tested against a real EventSource client. A server built with standard-server streams events, and EventSource must receive the
message,closeanderrorevents with the expected data. This catches encoder or adapter changes that would break browser-style SSE consumers.Coverage
sendStandardResponsewith Node's built-in EventSource; both tests pass on Node 20, 24 and 26toFetchResponsewith Deno's built-in EventSource; both tests passtoFetchResponse, skipped for now: bun-types declaresEventSource, but Bun 1.4.2 has no runtime implementation. The tests start running on their own once Bun ships oneNotes for reviewers
--experimental-eventsource, because Node 20–26 only expose the EventSource global behind that flag. The Node tests are skipped instead of failing if it is ever missingundefinedsends acloseevent without data, which EventSource silently drops, so the client reconnects instead of seeingclose. Both tests return a value; this is existing behavior, not changed here