Durable Objects deep dive

Durable Objects WebSocket hibernation without a chat database

WebSocket hibernation can keep a room reachable while its Durable Object sleeps. It does not preserve ordinary JavaScript memory, so Babili separates durable room state, live connection identity, and disposable message history.

Three kinds of state, three different homes

  • Durable room state: expiry policy, creator capability, room status, and one-time invite records live in Durable Object storage.
  • Live connection state: session ID, creator role, identity key, and connection time live in each WebSocket's serialized attachment.
  • Conversation history: encrypted message envelopes remain in connected browsers and are not written to Durable Object storage.

This split is the core design choice. Hibernation can discard the object's process-local memory, but the object can reload durable metadata and enumerate accepted sockets when it wakes.

Make socket attachments the membership source of truth

The room accepts a socket with acceptWebSocket, then writes a small attachment with serializeAttachment. After a wake-up, routing code calls getWebSockets and deserializeAttachment instead of trusting an in-memory participant map.

this.ctx.acceptWebSocket(server);
server.serializeAttachment({
  sessionId: "",
  creator: false,
  identityKey: "",
  connectedAt: 0
});

The empty session ID marks an accepted socket that has not completed its authenticated join. Once the creator capability or one-time invite is validated, the attachment is replaced with the joined session record.

Rebuild presence by enumerating live sockets

Presence, targeted relay, participant removal, and room capacity all derive from the current socket set. Duplicate session IDs are collapsed before the room announces its membership count.

Durable storage still records public room metadata, but it is not treated as the authority for who is connected at this instant. That avoids restoring a stale participant map after hibernation.

Let clients supply encrypted history

A joining browser sends a sync_request through the relay. Connected peers answer with at most the latest 200 encrypted message envelopes. The Durable Object routes that payload but never commits it to storage.

This deliberately gives up server-backed recovery. If every browser that held an item disconnects, a later participant cannot retrieve it. A malicious peer can also omit or reorder sync data. Protocol v3 authenticates each synced event to an admitted ephemeral session, but peer-supplied sync still cannot prove completeness.

What hibernation does—and does not—buy

  • The object can sleep while accepted WebSockets remain attached to the room.
  • Connection identity survives through serialized attachments, not class fields.
  • Room policy survives because it is stored and reloaded during initialization.
  • Message history stays disposable because connected clients, not storage, hold it.
  • Cloudflare still observes normal connection metadata and relayed payload sizes.

This is not a general recipe for durable chat. It is a tradeoff for a room where losing history is preferable to turning the relay into an archive.

Inspect the implementation

The complete TypeScript implementation, room protocol, tests, threat model, and Cloudflare deployment path are available under AGPL-3.0.

Read the Durable Object source.