Skip to content

Commit a047bce

Browse files
authored
Merge pull request #1535 from MarcelOlsen/fix/sse-response-validation
fix: skip response validation for generators and streams
2 parents 1084b5f + a6907c2 commit a047bce

File tree

2 files changed

+114
-2
lines changed

2 files changed

+114
-2
lines changed

src/compose.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ const composeValidationFactory = ({
267267
`c.set.status=${name}.code\n` +
268268
`${name}=${name}.response` +
269269
`}` +
270-
`if(${name} instanceof Response === false)` +
270+
`if(${name} instanceof Response === false && typeof ${name}?.next !== 'function' && !(${name} instanceof ReadableStream))` +
271271
`switch(c.set.status){`
272272

273273
for (const [status, value] of Object.entries(validator.response!)) {

test/validator/response.test.ts

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { Elysia, t } from '../../src'
1+
import { Elysia, t, sse } from '../../src'
2+
import { streamResponse } from '../../src/adapter/utils'
3+
import * as z from 'zod'
24

35
import { describe, expect, it } from 'bun:test'
46
import { post, req, upload } from '../utils'
@@ -450,4 +452,114 @@ describe('Response Validator', () => {
450452
const res = await app.handle(req('/'))
451453
expect(res.status).toBe(200)
452454
})
455+
456+
it('validate SSE response with generator', async () => {
457+
const app = new Elysia().get(
458+
'/',
459+
function* () {
460+
yield sse({ data: { name: 'Alice' } })
461+
yield sse({ data: { name: 'Bob' } })
462+
},
463+
{
464+
response: t.Object({
465+
data: t.Object({
466+
name: t.String()
467+
})
468+
})
469+
}
470+
)
471+
472+
const res = await app.handle(req('/'))
473+
expect(res.status).toBe(200)
474+
expect(res.headers.get('content-type')).toBe('text/event-stream')
475+
476+
// Verify the stream contains the expected SSE data
477+
const result = []
478+
for await (const chunk of streamResponse(res)) {
479+
result.push(chunk)
480+
}
481+
482+
expect(result.join('')).toContain('data: {"name":"Alice"}')
483+
expect(result.join('')).toContain('data: {"name":"Bob"}')
484+
})
485+
486+
it('validate async SSE response with generator', async () => {
487+
const app = new Elysia().get(
488+
'/',
489+
async function* () {
490+
yield sse({ data: { name: 'Charlie' } })
491+
await Bun.sleep(1)
492+
yield sse({ data: { name: 'Diana' } })
493+
},
494+
{
495+
response: t.Object({
496+
data: t.Object({
497+
name: t.String()
498+
})
499+
})
500+
}
501+
)
502+
503+
const res = await app.handle(req('/'))
504+
expect(res.status).toBe(200)
505+
expect(res.headers.get('content-type')).toBe('text/event-stream')
506+
})
507+
508+
it('validate streaming response with generator', async () => {
509+
const app = new Elysia().get(
510+
'/',
511+
function* () {
512+
yield { message: 'first' }
513+
yield { message: 'second' }
514+
},
515+
{
516+
response: t.Object({
517+
message: t.String()
518+
})
519+
}
520+
)
521+
522+
const res = await app.handle(req('/'))
523+
expect(res.status).toBe(200)
524+
525+
const result = []
526+
for await (const chunk of streamResponse(res)) {
527+
result.push(chunk)
528+
}
529+
530+
expect(result.join('')).toContain('"message":"first"')
531+
expect(result.join('')).toContain('"message":"second"')
532+
})
533+
534+
it('validate SSE with Zod schema (bug report scenario)', async () => {
535+
// This test reproduces the exact bug report:
536+
// https://github.com/elysiajs/elysia/issues/1490
537+
const Schema = z.object({
538+
data: z.object({
539+
name: z.string()
540+
})
541+
})
542+
543+
const app = new Elysia().get(
544+
'/',
545+
function* () {
546+
yield sse({ data: { name: 'Name' } })
547+
},
548+
{ response: Schema }
549+
)
550+
551+
const res = await app.handle(req('/'))
552+
553+
// Should not throw validation error
554+
expect(res.status).toBe(200)
555+
expect(res.headers.get('content-type')).toBe('text/event-stream')
556+
557+
// Verify the stream contains the expected SSE data
558+
const result = []
559+
for await (const chunk of streamResponse(res)) {
560+
result.push(chunk)
561+
}
562+
563+
expect(result.join('')).toContain('data: {"name":"Name"}')
564+
})
453565
})

0 commit comments

Comments
 (0)