Actor Handles
An Actor Handle is the client-side proxy for a single actor instance. It provides typed methods, event subscriptions, state subscriptions, and lifecycle management.
Getting a Handle
Section titled “Getting a Handle”const room = client.chat("room-1");This returns a typed ActorHandle<typeof ChatRoom> — all methods, events, and state types are inferred.
Calling Methods
Section titled “Calling Methods”Methods are accessed directly on the handle. They return promises:
// Method with inputawait room.sendMessage({ text: "hello" });
// Method without inputconst count = await counter.increment();
// Method with return valueconst { playerId, color } = await game.join({ name: "Alice" });Event Subscriptions
Section titled “Event Subscriptions”Use .on() to listen for typed events:
const unsubscribe = room.on("newMessage", (payload) => { // payload is typed: { text: string; from: string } console.log(`${payload.from}: ${payload.text}`);});
// Later:unsubscribe();Event subscriptions are lazy — the client only sends an event:sub message to the server when the first listener is added. When all listeners are removed, it sends event:unsub.
State Subscriptions
Section titled “State Subscriptions”The .state object provides subscribe() and getSnapshot():
// Subscribe to state changesconst unsub = room.state.subscribe((state) => { console.log("Current messages:", state.messages);});
// Read current state synchronouslyconst current = room.state.getSnapshot();Like events, state subscriptions are lazy. The first subscribe() sends state:sub to the server, which triggers an immediate state:snapshot response. Subsequent mutations on the server are received as state:patch messages.
How State Updates Work
Section titled “How State Updates Work”- Server mutates state via Immer → generates JSON patches
- Server sends
state:patchto all state subscribers - Client’s
StateStoreapplies patches to local state copy - All registered listeners are notified
Disposing Handles
Section titled “Disposing Handles”Call $dispose() when you’re done with a handle:
room.$dispose();This decrements the reference count. When the ref count hits zero (after a one-tick delay), the handle:
- Unsubscribes from events on the server
- Unsubscribes from state on the server
- Rejects all pending RPCs with
"ActorHandle disposed" - Clears all listeners
Reference Counting
Section titled “Reference Counting”Multiple consumers can share the same handle. Each call to client.chat("room-1") increments the ref count. Each $dispose() decrements it. The handle is only truly disposed when the count reaches zero.
The one-tick delay on final disposal ensures React StrictMode’s temporary unmount/remount cycle doesn’t kill shared handles.
Handle Properties
Section titled “Handle Properties”| Property | Type | Description |
|---|---|---|
$actorName | string | The actor name (e.g. "chat") |
$actorId | string | The instance ID (e.g. "room-1") |
$dispose() | () => void | Decrement ref count |
Error Handling
Section titled “Error Handling”If the WebSocket disconnects, all pending RPCs on the handle are rejected:
try { await room.sendMessage({ text: "hello" });} catch (err) { console.error(err.message); // "WebSocket closed"}If an RPC timeout is configured on the client, individual RPCs will reject after the timeout:
// err.message: 'RPC "sendMessage" timed out after 5000ms'