Custom Handlers
If you’re not using Bun, or need full control over the WebSocket lifecycle, you can use createHandlers() to get runtime-agnostic callbacks.
createHandlers
Section titled “createHandlers”import { createHandlers } from "@zocket/server";import { app } from "./app";
const handlers = createHandlers(app);This returns a HandlerCallbacks object:
interface HandlerCallbacks { onConnection(conn: Connection): void; onMessage(conn: Connection, raw: string): Promise<void>; onClose(conn: Connection): void; manager: ActorManager;}Note that onMessage is async — if you need to know when processing is done (e.g., to ack a message queue delivery), await it.
Connection Interface
Section titled “Connection Interface”Your adapter must provide objects that implement Connection:
interface Connection { send(message: string): void; id: string; // Stable identifier for lifecycle hooks}The id must be unique per connection and stable for its lifetime.
Wiring to a Custom Runtime
Section titled “Wiring to a Custom Runtime”Here’s a sketch for wiring to a generic WebSocket server:
import { createHandlers } from "@zocket/server";import { app } from "./app";
const handlers = createHandlers(app);let connId = 0;
myWebSocketServer.on("connection", (ws) => { const conn = { id: `custom_${++connId}`, send: (msg: string) => ws.send(msg), };
handlers.onConnection(conn);
ws.on("message", async (data: string) => { await handlers.onMessage(conn, data); });
ws.on("close", () => { handlers.onClose(conn); });});Message Routing
Section titled “Message Routing”The handler routes messages based on their type field:
| Message Type | Action |
|---|---|
rpc | Invoke method, send rpc:result back |
event:sub | Subscribe connection to actor events |
event:unsub | Unsubscribe from events |
state:sub | Subscribe to state + send initial snapshot |
state:unsub | Unsubscribe from state patches |
ActorManager
Section titled “ActorManager”The manager property on HandlerCallbacks gives you direct access to the actor manager. You can also import and construct one yourself:
import { ActorManager } from "@zocket/server";Inspecting State
Section titled “Inspecting State”const { manager } = handlers;
// Number of hot actor instancesconsole.log(manager.size);
// List all hot actorsconst actors = manager.list();// [{ actorName: "counter", actorId: "main" }, ...]Destroying Actors
Section titled “Destroying Actors”// Destroy a specific actor (calls onDeactivate if defined)await manager.destroy("counter", "main");
// Destroy all actors (for shutdown or redeploy)await manager.destroyAll();Lifecycle Events
Section titled “Lifecycle Events”Subscribe to actor creation and destruction:
manager.on("actorCreated", ({ actorName, actorId }) => { console.log(`Actor created: ${actorName}/${actorId}`);});
manager.on("actorDestroyed", ({ actorName, actorId }) => { console.log(`Actor destroyed: ${actorName}/${actorId}`);});The on() method returns an unsubscribe function.
State Initialization
Section titled “State Initialization”When an actor instance is first created, the manager initializes state by:
- Validating
{}against the state schema (works with schemas that have defaults) - If that fails, validating
undefined(works with top-level.default()) - If both fail, using
{}as a fallback
After initialization, the actor’s onActivate hook is called if defined.