PartyServer for Cloudflare Durable Objects
PartyServer is a TypeScript library for building real-time applications on Cloudflare Durable Objects, inspired by PartyKit. It extends Durable Objects with features like room-based routing, lifecycle hooks (`onConnect`, `onMessage`, `onClose`), a unified API for managing hibernated and non-hibernated Durable Objects, and easy broadcasting. Key differentiators from PartyKit include decoupling the URL from the server name, omitting built-in bindings for other Cloudflare services (encouraging `wrangler`'s native support), and requiring manual Durable Object bindings and migrations in `wrangler.jsonc`. The current stable version is 0.4.1, with frequent patch and minor releases, indicating active development. It is primarily designed for the Cloudflare Workers environment and leverages WebSockets for real-time communication.
Common errors
-
Error: Durable Object binding "MyServer" not found. You must configure Durable Object bindings in wrangler.toml/wrangler.jsonc.
cause The Durable Object binding for `MyServer` (or your equivalent class) is missing or incorrectly configured in `wrangler.jsonc`.fixAdd or correct the `durable_objects.bindings` entry in `wrangler.jsonc`, ensuring `name` matches the `env` property used in `fetch` and `class_name` matches your exported Durable Object class name. -
TypeError: 'this.broadcast is not a function' or 'this.room is undefined'
cause Your `Server` class likely doesn't extend `partyserver.Server`, or `this` context is lost in a callback.fixEnsure your Durable Object class explicitly `extends Server` from `partyserver`. Verify that `this` context is preserved in methods or use arrow functions for callbacks if necessary.
Warnings
- breaking The method signature for `Agent#connect` in related package `partysync` (part of the broader PartyKit ecosystem) was renamed to `Agent#connectTo` to avoid collision with new TCP socket binding declarations in `@cloudflare/workers-types`.
- breaking PartyServer changed its internal mechanism for passing room names and properties to Durable Objects from HTTP headers to RPC. This prevents sensitive data from appearing in logs but may break compatibility with older client implementations or custom routing logic that relied on header inspection.
- gotcha Unlike PartyKit, PartyServer does not automatically infer Durable Object bindings or migrations. You must manually define `durable_objects.bindings` and `migrations` in your `wrangler.jsonc` file for each Durable Object class.
- gotcha Type errors can occur under newer `@cloudflare/workers-types` versions due to changes in how `fetch` and `ArrayBufferView` are handled. PartyServer aims to be compatible, but older versions might conflict.
Install
-
npm install partyserver -
yarn add partyserver -
pnpm add partyserver
Imports
- Server
import Server from 'partyserver';
import { Server } from 'partyserver'; - routePartykitRequest
const { routePartykitRequest } = require('partyserver');import { routePartykitRequest } from 'partyserver'; - PartySocket
import { PartySocket } from 'partyserver';import { PartySocket } from 'partysocket';
Quickstart
import { routePartykitRequest, Server } from "partyserver";
import { PartySocket } from "partysocket";
interface Env {
MyServer: DurableObjectNamespace;
}
// Define your Server logic
export class MyServer extends Server {
onConnect(connection: WebSocket, context: any) {
console.log(`Connected ${connection.id} to server ${this.name}`);
}
onMessage(connection: WebSocket, message: string | ArrayBuffer) {
console.log(`Message from ${connection.id}:`, message);
// Broadcast the message to all other connections in the room
this.broadcast(message, [connection.id]);
}
onClose(connection: WebSocket, code: number, reason: string, wasClean: boolean) {
console.log(`Connection ${connection.id} closed. Clean: ${wasClean}`);
}
}
// Worker entry point to route requests to Durable Objects
export default {
async fetch(request: Request, env: Env): Promise<Response> {
return (
(await routePartykitRequest(request, env)) ||
new Response("Not Found", { status: 404 })
);
}
} satisfies ExportedHandler<Env>;
// === Minimal wrangler.jsonc configuration ===
/*
{
"name": "my-partyserver-app",
"main": "index.ts",
"durable_objects": {
"bindings": [
{
"name": "MyServer",
"class_name": "MyServer"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["MyServer"]
}
]
}
*/
// === Client-side connection example (requires 'partysocket') ===
/*
const socket = new PartySocket({
host: "https://my-partyserver-app.threepointone.workers.dev",
party: "my-server", // 'my-server' corresponds to the kebab-cased Durable Object class name 'MyServer'
room: "my-room",
onOpen: () => console.log("Client connected!"),
onMessage: (event) => console.log("Client received message:", event.data),
onClose: () => console.log("Client disconnected"),
onError: (event) => console.error("Client error:", event)
});
// To send a message from the client
// socket.send("Hello from client!");
*/