orpc-durable-iterator-integration
Extends Event Iterator with durable event streams, automatic reconnections, and event recovery.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Extends Event Iterator with durable event streams, automatic reconnections, and event recovery.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
Quick reference for Better Notify configuration, patterns, and common gotchas
Interactive setup wizard for adding Better Notify to a TypeScript/JavaScript project
Context and API guidance for Better Notify — end-to-end typed notification infrastructure for Node.js
Seamlessly use AI SDK inside your oRPC projects without any extra overhead.
Use oRPC inside an Astro project.
Functions to encode and decode base64url strings (URL-safe variant of base64).
| name | oRPC Durable Iterator Integration |
| description | Extends Event Iterator with durable event streams, automatic reconnections, and event recovery. |
| license | MIT |
| metadata | {"author":"Ali Torki","homepage":"https://github.com/ali-master","version":"1.0.0"} |
Durable Iterator extends Event Iterator by offloading streaming to a separate service that provides durable event streams, automatic reconnections, and event recovery.
Currently supports only Cloudflare Durable Objects.
npm install @orpc/experimental-durable-iterator@latest
import { DurableIteratorObject } from '@orpc/experimental-durable-iterator/durable-object'
export class ChatRoom extends DurableIteratorObject<{ message: string }> {
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env, {
signingKey: 'secret-key',
interceptors: [onError(e => console.error(e))],
onSubscribed: (websocket, lastEventId) => {
console.log(`WebSocket Ready id=${websocket['~orpc'].deserializeId()}`)
}
})
}
someMethod() {
this.publishEvent({ message: 'Hello, world!' })
}
}
import { upgradeDurableIteratorRequest } from '@orpc/experimental-durable-iterator/durable-object'
export default {
async fetch(request, env) {
const url = new URL(request.url)
if (url.pathname === '/chat-room') {
return upgradeDurableIteratorRequest(request, {
signingKey: 'secret-key',
namespace: env.CHAT_ROOM,
})
}
return new Response('Not Found', { status: 404 })
},
}
export { ChatRoom }
this.publishEvent({ message: 'Hello, world!' }, {
tags: ['tag1', 'tag2'],
targets: ws => ws['~orpc'].deserializeTokenPayload().att.role === 'admin',
exclude: [senderWs],
})
Enable by configuring resumeRetentionSeconds:
super(ctx, env, {
signingKey: 'secret-key',
resumeRetentionSeconds: 60 * 2, // 2 minutes
})
import { DurableIterator } from '@orpc/experimental-durable-iterator'
export const router = {
onMessage: base.handler(({ context }) => {
return new DurableIterator<ChatRoom>('some-room', {
tags: ['tag1', 'tag2'],
signingKey: 'secret-key',
})
}),
sendMessage: base
.input(z.object({ message: z.string() }))
.handler(async ({ context, input }) => {
const id = context.env.CHAT_ROOM.idFromName('some-room')
const stub = context.env.CHAT_ROOM.get(id)
await stub.publishEvent(input)
}),
}
Enable the handler plugin:
import { DurableIteratorHandlerPlugin } from '@orpc/experimental-durable-iterator'
const handler = new RPCHandler(router, {
plugins: [
new CORSPlugin({ exposeHeaders: ['x-orpc-durable-iterator'] }),
new DurableIteratorHandlerPlugin(),
],
})
import { DurableIteratorLinkPlugin } from '@orpc/experimental-durable-iterator/client'
const link = new RPCLink({
url: 'http://localhost:3000/rpc',
plugins: [
new DurableIteratorLinkPlugin({
url: 'ws://localhost:3000/chat-room',
interceptors: [onError(e => console.error(e))],
}),
],
})
const iterator = await client.onMessage()
for await (const { message } of iterator) {
console.log('Received message:', message)
}
await client.sendMessage({ message: 'Hello, world!' })
new DurableIteratorLinkPlugin({
url: 'ws://localhost:3000/chat-room',
refreshTokenBeforeExpireInSeconds: 10 * 60, // 10 minutes
})
Define methods that accept a DurableIteratorWebsocket instance:
import { DurableIteratorWebsocket } from '@orpc/experimental-durable-iterator/durable-object'
export class ChatRoom extends DurableIteratorObject<{ message: string }> {
singleClient(ws: DurableIteratorWebsocket) {
return base
.input(z.object({ message: z.string() }))
.handler(({ input, context }) => {
this.publishEvent(input, { exclude: [ws] })
})
.callable()
}
routerClient(ws: DurableIteratorWebsocket) {
return {
ping: base.handler(() => 'pong').callable(),
echo: base.input(z.object({ text: z.string() }))
.handler(({ input }) => `Echo: ${input.text}`)
.callable(),
}
}
}
Server side:
export const onMessage = base.handler(({ context }) => {
return new DurableIterator<ChatRoom>('some-room', {
signingKey: 'secret-key',
att: { userId: 'user-123' },
}).rpc('singleClient', 'routerClient')
})
Client side:
const iterator = await client.onMessage()
await iterator.singleClient({ message: 'Hello, world!' })
const response = await iterator.routerClient.ping()
const echo = await iterator.routerClient.echo({ text: 'Hello' })
import type { ContractRouterClient } from '@orpc/contract'
import { oc, type } from '@orpc/contract'
import type { ClientDurableIterator } from '@orpc/experimental-durable-iterator/client'
import type { DurableIteratorObject } from '@orpc/experimental-durable-iterator'
export const publishMessageContract = oc.input(z.object({ message: z.string() }))
export interface ChatRoom extends DurableIteratorObject<{ message: string }> {
publishMessage(...args: any[]): ContractRouterClient<typeof publishMessageContract>
}
export const contract = {
onMessage: oc.output(type<ClientDurableIterator<ChatRoom, 'publishMessage'>>()),
}