Event Iterator (SSE)
oRPC provides built‑in support for streaming responses, real‑time updates, and server-sent events (SSE) without any extra configuration. This functionality is ideal for applications that require live updates—such as AI chat responses, live sports scores, or stock market data.
Overview
The event iterator is defined by an asynchronous generator function. In the example below, the handler continuously yields a new event every second:
const example = os
.handler(async function* ({ input, lastEventId }) {
while (true) {
yield { message: 'Hello, world!' }
await new Promise(resolve => setTimeout(resolve, 1000))
}
})
Learn how to consume the event iterator on the client here
Validate Event Iterator
oRPC includes a built‑in eventIterator
helper that works with any Standard Schema library to validate events.
import { eventIterator } from '@orpc/server'
const example = os
.output(eventIterator(z.object({ message: z.string() })))
.handler(async function* ({ input, lastEventId }) {
while (true) {
yield { message: 'Hello, world!' }
await new Promise(resolve => setTimeout(resolve, 1000))
}
})
Last Event ID & Event Metadata
Using the withEventMeta
helper, you can attach additional event meta (such as an event ID or a retry interval) to each event. On reconnect, oRPC passes the last event ID back to the handler so you can resume the stream appropriately.
import { withEventMeta } from '@orpc/server'
const example = os
.handler(async function* ({ input, lastEventId }) {
if (lastEventId) {
// Resume streaming from lastEventId
}
else {
while (true) {
yield withEventMeta({ message: 'Hello, world!' }, { id: 'some-id', retry: 10_000 })
await new Promise(resolve => setTimeout(resolve, 1000))
}
}
})
Stop Event Iterator
To signal the end of the stream, simply use a return
statement. When the handler returns, oRPC marks the stream as successfully completed and does not attempt to reconnect.
const example = os
.handler(async function* ({ input, lastEventId }) {
while (true) {
if (done) {
return
}
yield { message: 'Hello, world!' }
await new Promise(resolve => setTimeout(resolve, 1000))
}
})
Cleanup Side-Effects
If the client closes the connection or an unexpected error occurs, you can use a finally
block to clean up any side effects (for example, closing database connections or stopping background tasks):
const example = os
.handler(async function* ({ input, lastEventId }) {
try {
while (true) {
yield { message: 'Hello, world!' }
await new Promise(resolve => setTimeout(resolve, 1000))
}
}
finally {
console.log('Cleanup logic here')
}
})