Controller classes
WebSocketControllerBase subclasses with static deps, contract, and lifecycle methods.
Class-based WebSocket endpoints extend WebSocketControllerBase, declare static deps, static state, and optional static contract, then register with host.ws.controller. One instance is built per connection (per wake on a hibernating Durable Object).
Minimal controller
Section titled “Minimal controller”import { FlareHost, WebSocketControllerBase, socketContract,} from "@flare-ts/core";import { schema, str } from "@flare-ts/lib/schema";import { node } from "@flare-ts/core/node";
const Msg = schema({ text: str });
const Chat = socketContract({ chat: { incoming: Msg, outgoing: Msg, params: { room: str } },});
class ChatController extends WebSocketControllerBase<typeof Chat.chat> { public static override deps = []; public static override state = []; public static override contract = Chat.chat;
message(message: { text: string }) { this.socket.send({ text: `echo: ${message.text}` }); }}
const host = new FlareHost(node);host.ws.controller("/chat/:room", ChatController);
const app = host.build();app.run();What the base class provides
Section titled “What the base class provides”| Member | Role |
|---|---|
this.socket | Send, close, subscribe, and read this.socket.state |
this.input | Connect-time params and query (stable for the connection) |
this.inject(Token) | Resolve registered services from static deps |
this.config | Resolved config sections from static config |
Override open, message, close, and error as needed. The message argument is typed from static contract’s incoming schema when declared.
Route options on controllers
Section titled “Route options on controllers”Pass route options as the middle argument for state tokens or a channel selector beyond what the class declares. A controller’s DI is static deps read with this.inject, so inject is not accepted here:
host.ws.controller( "/chat/:room", { contract: Chat.chat, state: [PresenceState] }, ChatController,);When the class declares a static contract, options passed here must carry the same contract entry; options without it describe a contract-less route and no longer match the class. When the descriptor is fixed and you need no extra options, use the two-argument form and let static contract carry it.
Pre-handshake upgrade hook
Section titled “Pre-handshake upgrade hook”host.ws.controller() returns a handle carrying the same .upgrade() registrar as inline routes. The hook runs in request scope, before the connection and the controller instance exist, so it lives at the registration site rather than on the class:
host.ws.controller("/chat/:room", ChatController) .upgrade((upgrade) => { if (upgrade.header("x-ticket") === undefined) return new FlareResponse(401); });The hook declares its own dependencies with the options form, .upgrade({ inject, provides }, handler); the class’s static deps stay connection-time. Values the hook writes to scope.state are readable as this.socket.state from open on. The overview covers the full return contract.
Hibernation note
Section titled “Hibernation note”On a Durable Object with hibernation enabled, controller instance fields are discarded on wake. Store per-connection data in this.socket.state, not on this. See Connection state.
Related
Section titled “Related”- Inline handlers: function-form routes
- Named inject map:
static depsvs inlineinject - HTTP controller classes: parallel HTTP pattern