Description
The sse() helper combined with the generator pattern does not work on Deno. The SSE response returns 200 OK with Content-Type: text/event-stream, but no events are ever transmitted to the client. The EventStream tab in browser DevTools is completely empty.
The same code works correctly on Bun.
Root Cause
In adapter/utils.js, the createStreamHandler enqueues SSE data as raw strings into the ReadableStream:
// adapter/utils.js:168-169 (start callback)
if (init.value.toSSE)
controller.enqueue(init.value.toSSE()); // toSSE() returns a string
// adapter/utils.js:201-202 (pull callback)
if (chunk.toSSE)
controller.enqueue(chunk.toSSE()); // same issue
The toSSE() method (in utils.js) returns a plain string like "event: progress\ndata: {...}\n\n". This string is passed directly to controller.enqueue() without encoding.
A ReadableStream used as a Response body should contain Uint8Array chunks per the web standard. Bun is lenient and auto-encodes string chunks to bytes. Deno follows the spec strictly — the raw strings are silently not transmitted.
Reproduction
import { Elysia, sse } from 'elysia';
new Elysia()
.get('/sse', async function* () {
yield sse({ event: 'ping', data: { message: 'hello' } });
yield sse({ event: 'ping', data: { message: 'world' } });
})
.listen(3000);
- Run with
deno run --allow-net server.ts
- Open browser to
http://localhost:3000/sse or use new EventSource('/sse')
- Expected: Two
ping events received
- Actual: Response is 200 with
text/event-stream content-type, but EventStream tab is empty. No events arrive. The EventSource error event fires due to connection close.
Works correctly when running with Bun.
Suggested Fix
Encode the string to Uint8Array before enqueuing in adapter/utils.js:
const encoder = new TextEncoder();
// In start callback:
if (init.value.toSSE)
controller.enqueue(encoder.encode(init.value.toSSE()));
// In pull callback:
if (chunk.toSSE)
controller.enqueue(encoder.encode(chunk.toSSE()));
Workaround
Return a manual Response with a self-built ReadableStream instead of using the sse() + generator pattern:
.get('/sse', () => {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
// ... produce SSE data ...
const sseString = `event: ping\ndata: ${JSON.stringify({ message: 'hello' })}\n\n`;
controller.enqueue(encoder.encode(sseString));
controller.close();
},
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
})
Environment
- Elysia: 1.4.28
- Deno: 2.x
- OS: macOS (Darwin arm64)
Description
The
sse()helper combined with the generator pattern does not work on Deno. The SSE response returns 200 OK withContent-Type: text/event-stream, but no events are ever transmitted to the client. The EventStream tab in browser DevTools is completely empty.The same code works correctly on Bun.
Root Cause
In
adapter/utils.js, thecreateStreamHandlerenqueues SSE data as raw strings into theReadableStream:The
toSSE()method (inutils.js) returns a plain string like"event: progress\ndata: {...}\n\n". This string is passed directly tocontroller.enqueue()without encoding.A
ReadableStreamused as aResponsebody should containUint8Arraychunks per the web standard. Bun is lenient and auto-encodes string chunks to bytes. Deno follows the spec strictly — the raw strings are silently not transmitted.Reproduction
deno run --allow-net server.tshttp://localhost:3000/sseor usenew EventSource('/sse')pingevents receivedtext/event-streamcontent-type, but EventStream tab is empty. No events arrive. TheEventSourceerrorevent fires due to connection close.Works correctly when running with Bun.
Suggested Fix
Encode the string to
Uint8Arraybefore enqueuing inadapter/utils.js:Workaround
Return a manual
Responsewith a self-builtReadableStreaminstead of using thesse()+ generator pattern:Environment