elysiajs / elysia

Ergonomic Framework for Humans
https://elysiajs.com
MIT License
9.74k stars 207 forks source link

Async generator handler should follow `text/event-stream` protocol #742

Open remorses opened 1 month ago

remorses commented 1 month ago

What version of Elysia.JS is running?

"elysia": "^1.1.3"

What platform is your computer?

Darwin 23.5.0 arm64 arm

What steps can reproduce the bug?

import { treaty } from '@elysiajs/eden'

import { Elysia } from 'elysia'

const app = new Elysia()
    .get('/', async function* generator() {
        await sleep(100)
        yield JSON.stringify({ body: 'Hello Elysia' })
        yield JSON.stringify({ body: 'Hello Elysia' })
        yield JSON.stringify({ body: 'Hello Elysia' })
        yield JSON.stringify({ body: 'Hello Elysia' })
        yield JSON.stringify({ body: 'Hello Elysia' })
    })
    .listen(3000, async () => {
        const app = treaty<App>('localhost:3000')

        {
            const { data: gen, error } = await app.index.get({})
            if (error) throw error

            for await (const res of gen) {
                console.log(res)
            }
        }
    })

export type App = typeof app

console.log(
    `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
)

function sleep(ms: number) {
    return new Promise(resolve => setTimeout(resolve, ms))
}

The output of this code will be:

{"body":"Hello Elysia"}{"body":"Hello Elysia"}{"body":"Hello Elysia"}
{
  body: "Hello Elysia",
}
{
  body: "Hello Elysia",
}

There are several problems with this code:

  1. In Eden some items will be objects, sometimes strings. This is because Elysia cannot distinguish what separate each yieled item. This is because the server does not separate yielded items with new lines
  2. The type inference of Eden is wrong because when you yield a string it will be converted to an object. The server should call JSON.stringify on all yielded items if the handler is an async generator. (also this is what the text/event-stream spec says)
  3. Elysia just calls .toString() on the yielded items, this will cause problems where when you yield objects you will get [object Object] #741

https://github.com/elysiajs/elysia/blob/8173d905944b8b305f8989985abe7760af681de9/src/handler.ts#L156

What is the expected behavior?

Elysia should return a text/event-stream compatible output if the handler is an async generator:

  1. Separate each yielded object with a new line
  2. JSON.stringify each item

What do you see instead?

Elysia concatenates the outputs inline calling .toString() on each of them

Additional information

No response