Skip to content

Creating a Client

The @zocket/client package provides createClient — a fully typed WebSocket client that infers its API from your app definition.

import { createClient } from "@zocket/client";
import type { app } from "./server";
const client = createClient<typeof app>({
url: "ws://localhost:3000",
});

The generic <typeof app> is the only type annotation you need. Everything else — actor names, method signatures, event payloads, state shapes — is inferred.

interface ClientOptions {
url: string;
rpcTimeout?: number; // ms — reject pending RPCs after this duration
}

If set, any RPC that doesn’t receive a response within the timeout is automatically rejected:

const client = createClient<typeof app>({
url: "ws://localhost:3000",
rpcTimeout: 5000, // 5 seconds
});

Access actors by name, then pass an instance ID:

const room = client.chat("general");
const game = client.game("match-42");

Each call returns a typed ActorHandle — see Actor Handles for the full API.

A promise that resolves when the WebSocket connection is open:

await client.$ready;
console.log("Connected!");

Gracefully close the connection and dispose all handles:

client.$close();

This:

  1. Clears all pending dispose timers
  2. Disposes all active actor handles (unsubscribes from events/state, rejects pending RPCs)
  3. Closes the WebSocket

Messages sent before the WebSocket is open are queued and flushed once the connection is established. You don’t need to await $ready before calling methods:

const client = createClient<typeof app>({ url: "ws://localhost:3000" });
// These are queued if the connection isn't open yet
const room = client.chat("general");
await room.sendMessage({ text: "hello" }); // waits for connection + response

If the WebSocket closes or errors, all pending RPCs on all handles are rejected:

try {
await room.sendMessage({ text: "hello" });
} catch (err) {
console.error(err.message); // "WebSocket closed" or "WebSocket error"
}

createClient returns a Proxy object. When you access client.chat, it returns a function. Calling that function (client.chat("room-1")) creates or retrieves a shared ActorHandleImpl for that (actorName, actorId) pair.

Handles are reference-counted — multiple consumers can share the same handle. When the ref count drops to zero, disposal is deferred by one tick to support React StrictMode’s unmount/remount cycle.