Connection state
Store per-connection data in ws.state so it survives Durable Object hibernation.
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.
Declare state tokens
Section titled “Declare state tokens”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.
What to store
Section titled “What to store”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.state | Do not store |
|---|---|
| User id, room role, small flags | Class instance fields on controllers |
Keys pointing to DurableState.storage | Large blobs (attachment budget is 16 KB) |
| Channel names already in attachments | Non-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.
Controllers
Section titled “Controllers”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 state vs WebSocket state
Section titled “HTTP state vs WebSocket state”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.
Related
Section titled “Related”- HTTP request state: per-request
ctx.state - Connection state tokens in routes:
stateroute option - Durable Objects: hibernation and attachments