Skip to content

Connection state

Store per-connection data in ws.state so it survives Durable Object hibernation.

AI generated, pending review Updated 10 days ago · Flare 0.3

WebSocket handlers need data that outlives a single callback. On a Durable Object with hibernation, the runtime may evict the instance while sockets stay open, so closure locals and controller instance fields do not survive the next wake.

Put per-connection data in ws.state (or this.socket.state on controllers). The framework serializes declared state to the socket attachment on hibernating backings.

Use flareState and list tokens in the route’s state option (or static state on a controller), the same tokens HTTP routes use:

import { FlareHost, flareState } from "@flare-ts/core";
import { node } from "@flare-ts/core/node";
const Presence = flareState<{ joinedAt: number }>("Presence");
const host = new FlareHost(node);
host.ws.route("/chat/:room", { state: [Presence] })
.open((ws) => {
ws.state.set(Presence, { joinedAt: Date.now() });
})
.message((ws) => {
const presence = ws.state.get(Presence);
ws.send(String(presence?.joinedAt ?? 0));
});
const app = host.build();
app.run();

The route’s state array seeds the connection store; reads are typed by the imported flareState token.

ws.state accepts primitives, arrays, and plain objects. Values are deep-frozen on write so they stay JSON-serializable for Durable Object attachments.

Store in ws.stateDo not store
User id, room role, small flagsClass instance fields on controllers
Keys pointing to DurableState.storageLarge blobs (attachment budget is 16 KB)
Channel names already in attachmentsNon-serializable objects

When state exceeds the attachment budget, the runtime write raises a flare-branded error pointing you toward DurableState.storage and keeping only a key in ws.state.

Hibernation bookkeeping without this contract is yours to write: decide what survives a wake, re-serialize it into the socket attachment on every change, reconstruct it on the next event, and guard each read against attachments some other accept wrote. Miss one write and a connection wakes up with amnesia; overshoot and the 16 KB platform limit surfaces wherever the write happened to land. ws.state is that bookkeeping as a contract: dirty writes re-serialize automatically, reads are shape-guarded, and the budget overrun is one framework error that names the escape hatch.

import { WebSocketControllerBase, flareState } from "@flare-ts/core";
const Presence = flareState<{ joinedAt: number }>("Presence");
class ChatController extends WebSocketControllerBase {
public static override state = [Presence];
open() {
this.socket.state.set(Presence, { joinedAt: Date.now() });
}
}

HTTP request state (ctx.state) lasts for one request. WebSocket ws.state lasts for the connection (across many messages and, on Durable Object hibernation, across wakes). They use the same flareState tokens but different scopes.

Worker-to-instance HTTP calls can cross HTTP state through framework headers. That path is separate from WebSocket attachments. See State crossing.