// This Durable Object class has four counters that are incremented in different ways. // One counter is incremented via RESTful HTTP requests, and persisted in DO storage. // A second counter is incremented via RPC requests, and also persisted in DO storage. // A third counter is incremented via WebSocket messages, persisted in DO storage, and // broadcast to all connected clients whenever it changes. If you open multiple clients // connected to the WebSocket interface, you'll see the count update in real time across // all clients when any one of them increments the WebSocket counter. // The fourth counter uses Svelte 5 Runes within the Durable Object to manage // reactive state. import { DurableObject } from "cloudflare:workers"; const REST_STORAGE_KEY = "count"; const RPC_STORAGE_KEY = "rpc-count"; const WS_STORAGE_KEY = "ws-count"; const RUNES_STORAGE_KEY = "runes-count"; const WS_TAG = "live-counter"; export class CounterDurableObject extends DurableObject { // svelte 5 runes can be used within .do.svelte.ts Durable Object classes. // Here we use a $state rune to hold a "runeCount". runeCount = $state(0); constructor(ctx: DurableObjectState, env: Env) { super(ctx, env); this.ctx.storage.get(RUNES_STORAGE_KEY).then((storedRuneCount) => { if (storedRuneCount !== undefined) { this.runeCount = storedRuneCount; } }); // the $effect block here broadcasts the current value of runeCount to all // connected WebSocket clients whenever it changes. This demonstrates how // Svelte 5's reactivity model can be used within Durable Objects to manage // state and side effects in response to that state. $effect.root(() => { $effect(() => { this.broadcastRuneCount(this.runeCount); this.ctx.storage.put(RUNES_STORAGE_KEY, this.runeCount); }); }); } async fetch(request: Request) { const url = new URL(request.url); if ( request.headers.get("upgrade")?.toLowerCase() === "websocket" && url.pathname === "/ws-live" ) { return this.handleWebSocketSession(); } if (request.method === "GET" && url.pathname === "/count") { return Response.json({ count: await this.readCount(REST_STORAGE_KEY) }); } if (request.method === "POST" && url.pathname === "/increment") { const count = (await this.readCount(REST_STORAGE_KEY)) + 2; await this.ctx.storage.put(REST_STORAGE_KEY, count); return Response.json({ count }); } return new Response("Not found", { status: 404 }); } async getRpcCount() { return this.readCount(RPC_STORAGE_KEY); } async incrementRpcCount() { const count = (await this.readCount(RPC_STORAGE_KEY)) + 1; await this.ctx.storage.put(RPC_STORAGE_KEY, count); return count; } async getWsCount() { return this.readCount(WS_STORAGE_KEY); } async incrementWsCount() { const count = (await this.readCount(WS_STORAGE_KEY)) + 1; await this.ctx.storage.put(WS_STORAGE_KEY, count); this.broadcastCount(count); return count; } webSocketMessage(_ws: WebSocket, message: string | ArrayBuffer) { void this.handleSocketMessage(message); } webSocketClose() { // No cleanup required beyond the Durable Object runtime releasing the socket. } webSocketError() { // Connection errors do not need custom handling for this counter demo. } private handleWebSocketSession() { const pair = new WebSocketPair(); const [client, server] = Object.values(pair); this.ctx.acceptWebSocket(server, [WS_TAG]); void this.sendCurrentCount(server); return new Response(null, { status: 101, webSocket: client, }); } async incrementWsRuneCount() { this.runeCount += 1; return this.runeCount; } private async handleSocketMessage(message: string | ArrayBuffer) { const text = typeof message === "string" ? message : new TextDecoder().decode(message); if (text === "increment") { await this.incrementWsCount(); return; } if (text === "rune-increment") { await this.incrementWsRuneCount(); return; } if (text === "sync") { this.broadcastCount(await this.readCount(WS_STORAGE_KEY)); this.broadcastRuneCount(this.runeCount); } } private async sendCurrentCount(socket: WebSocket) { socket.send( JSON.stringify({ count: await this.readCount(WS_STORAGE_KEY), runeCount: this.runeCount, }), ); } private broadcastCount(count: number) { const payload = JSON.stringify({ count }); for (const socket of this.ctx.getWebSockets(WS_TAG)) { socket.send(payload); } } private broadcastRuneCount(runeCount: number) { const payload = JSON.stringify({ runeCount }); for (const socket of this.ctx.getWebSockets(WS_TAG)) { socket.send(payload); } } private async readCount(storageKey: string) { return (await this.ctx.storage.get(storageKey)) ?? 0; } }