Hibernation Plugin
The Hibernation Plugin helps you fully leverage Hibernation APIs, making it especially useful for adapters like Cloudflare Websocket Hibernation.
Setup
ts
import { experimental_HibernationPlugin as HibernationPlugin } from '@orpc/server/hibernation'
const handler = new RPCHandler(router, {
plugins: [
new HibernationPlugin(),
],
})
Event Iterator
The plugin provide HibernationEventIterator
and encodeHibernationRPCEvent
to help you return an Event Iterator that utilizes the Hibernation APIs.
Return an
HibernationEventIterator
from your handlertsimport { experimental_HibernationEventIterator as HibernationEventIterator } from '@orpc/server/hibernation' export const onMessage = os.handler(async ({ context }) => { return new HibernationEventIterator<{ message: string }>((id) => { // Save the ID. You'll need it to send events later. context.ws.serializeAttachment({ id }) }) })
Send events to clients with
encodeHibernationRPCEvent
tsimport { experimental_encodeHibernationRPCEvent as encodeHibernationRPCEvent } from '@orpc/server/hibernation' export const sendMessage = os.handler(async ({ input, context }) => { const websockets = context.getWebSockets() for (const ws of websockets) { const { id } = ws.deserializeAttachment() // yield an event to all clients ws.send(encodeHibernationRPCEvent(id, { message: input.message }, { customJsonSerializers: [ // put custom serializers here ] })) // return an event and stop event iterator ws.send(encodeHibernationRPCEvent(id, { message: input.message }, { event: 'done' })) // throw an error and stop event iterator ws.send(encodeHibernationRPCEvent(id, new ORPCError('INTERNAL_SERVER_ERROR'), { event: 'error' })) } })
Cloudflare Durable Object Chat Room Example?
This example demonstrates how to set up a chat room using Cloudflare Durable Objects and Websocket Hibernation. Everyone connected to the same Durable Object can send messages to each other.
ts
import { experimental_RPCHandler as RPCHandler } from '@orpc/server/websocket'
import {
experimental_encodeHibernationRPCEvent as encodeHibernationRPCEvent,
experimental_HibernationEventIterator as HibernationEventIterator,
experimental_HibernationPlugin as HibernationPlugin,
} from '@orpc/server/hibernation'
import { onError, os } from '@orpc/server'
import { DurableObject } from 'cloudflare:workers'
import { z } from 'zod'
const base = os.$context<{
handler: RPCHandler<any>
ws: WebSocket
getWebsockets: () => WebSocket[]
}>()
export const router = {
send: base.input(z.object({ message: z.string() })).handler(async ({ input, context }) => {
const websockets = context.getWebsockets()
for (const ws of websockets) {
const data = ws.deserializeAttachment()
if (typeof data !== 'object' || data === null) {
continue
}
const { id } = data
ws.send(encodeHibernationRPCEvent(id, input.message))
}
}),
onMessage: base.handler(async ({ context }) => {
return new HibernationEventIterator<string>((id) => {
context.ws.serializeAttachment({ id })
})
}),
}
const handler = new RPCHandler(router, {
interceptors: [
onError((error) => {
console.error(error)
}),
],
plugins: [
new HibernationPlugin(),
],
})
export class ChatRoom extends DurableObject {
async fetch(): Promise<Response> {
const { '0': client, '1': server } = new WebSocketPair()
this.ctx.acceptWebSocket(server)
return new Response(null, {
status: 101,
webSocket: client,
})
}
async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise<void> {
await handler.message(ws, message, {
context: {
handler,
ws,
getWebsockets: () => this.ctx.getWebSockets(),
},
})
}
async webSocketClose(ws: WebSocket): Promise<void> {
handler.close(ws)
}
}
ts
import { experimental_RPCLink as RPCLink } from '@orpc/client/websocket'
import { createORPCClient } from '@orpc/client'
import type { router } from '../../worker/dos/chat-room'
import type { RouterClient } from '@orpc/server'
const websocket = new WebSocket(`${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/chat-room`)
websocket.addEventListener('error', (event) => {
console.error(event)
})
const link = new RPCLink({
websocket,
})
export const chatRoomClient: RouterClient<typeof router> = createORPCClient(link)
tsx
import { useEffect, useState } from 'react'
import { chatRoomClient } from '../lib/chat-room'
export function ChatRoom() {
const [messages, setMessages] = useState<string[]>([])
useEffect(() => {
const controller = new AbortController()
void (async () => {
for await (const message of await chatRoomClient.onMessage(undefined, { signal: controller.signal })) {
setMessages(messages => [...messages, message])
}
})()
return () => {
controller.abort()
}
}, [])
const sendMessage = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
const form = new FormData(e.target as HTMLFormElement)
const message = form.get('message') as string
await chatRoomClient.send({ message })
}
return (
<div>
<h1>Chat Room</h1>
<p>Open multiple tabs to chat together</p>
<ul>
{messages.map((message, index) => (
<li key={index}>{message}</li>
))}
</ul>
<form onSubmit={sendMessage}>
<input name="message" type="text" required defaultValue="hello" />
<button type="submit">Send</button>
</form>
</div>
)
}