Client

API reference for @waits/lively-client — the low-level TypeScript client for connecting to a Lively server, managing rooms, presence, cursors, storage, and real-time events.

LivelyClient

The main entry point. Create one client per app, then join rooms through it.

Constructor:

client.ts
import { LivelyClient } from "@waits/lively-client";
const client = new LivelyClient({
serverUrl: "ws://localhost:1999",
reconnect: true, // optional, default true
maxRetries: 20, // optional, default 20
});

joinRoom(roomId, options) — connects to a room and returns a Room instance:

const room = client.joinRoom("my-room", {
userId: "user-123",
displayName: "Alice",
cursorThrottleMs: 50, // optional
initialStorage: { count: 0 }, // optional
inactivityTime: 60_000, // optional, ms until "away"
offlineInactivityTime: 300_000, // optional, ms until "offline"
});

Other methods:

client.leaveRoom("my-room"); // disconnect and remove
client.getRoom("my-room"); // Room | undefined
client.getRooms(); // Room[]

Room

Returned by client.joinRoom(). Holds all real-time state for a single room — presence, cursors, storage, events, and history.

Presence

room.getPresence(); // PresenceUser[]
room.getOthers(); // PresenceUser[] (excludes self)
room.getSelf(); // PresenceUser | null
room.getOthersOnLocation("page-1"); // filter by location
// Update your presence data
room.updatePresence({
location: "page-1",
metadata: { role: "editor" },
onlineStatus: "online",
});

Cursors

// Send cursor position (auto-throttled)
room.updateCursor(x, y);
room.updateCursor(x, y, viewportPos, viewportScale);
// Read all cursors
const cursors = room.getCursors(); // Map<string, CursorData>

Storage

// Get the CRDT storage root (async — waits for server sync)
const { root} = await room.getStorage();
// Read and mutate via LiveObject / LiveMap / LiveList
root.get("count"); // read
root.set("count", 42); // write (auto-synced)
// Synchronous getter (null if not loaded yet)
room.getCurrentRoot(); // LiveObject | null
// Subscribe to CRDT changes
room.subscribe(root, () => console.log("changed!"));
room.subscribe(root, () => console.log("deep!"), { isDeep: true });
// Callback when storage root is replaced after reconnect
room.onStorageReset((newRoot) => { ... });

Events

// Subscribe to room events
const unsub = room.subscribe("presence", (users) => { ... });
room.subscribe("cursors", (cursors) => { ... });
room.subscribe("status", (status) => { ... });
room.subscribe("message", (msg) => { ... });
room.subscribe("error", (err) => { ... });
// Send arbitrary messages
room.send({ type: "chat", text: "hello" });
// Batch multiple operations into a single message
room.batch(() => {
root.set("a", 1);
root.set("b", 2);
});
unsub(); // unsubscribe

Follow Mode

room.followUser("user-456"); // start following a user
room.stopFollowing(); // stop following
room.getFollowing(); // string | null
room.getFollowers(); // string[] (users following you)

Live State

Lightweight key-value state synced via last-write-wins (not CRDT). Good for ephemeral UI state like viewport position.

room.setLiveState("theme", "dark");
room.getLiveState("theme"); // "dark"
room.getAllLiveStates(); // Map<string, { value, timestamp, userId }>
const unsub = room.subscribeLiveState("theme", () => { ... });

History (Undo / Redo)

room.undo(); // undo last storage mutation
room.redo(); // redo
room.getHistory(); // HistoryManager | null

History works with room.batch() — batched mutations are undone/redone as a single unit.

Connection

room.connect(); // open WebSocket (called automatically by joinRoom)
room.disconnect(); // close WebSocket
room.getStatus(); // ConnectionStatus

ConnectionManager

Handles WebSocket lifecycle internally. You rarely use this directly — Room wraps it. Supports auto-reconnect with exponential backoff, heartbeats, and connection timeouts.

import { ConnectionManager } from "@waits/lively-client";
const conn = new ConnectionManager({
url: "ws://localhost:1999/rooms/my-room",
reconnect: true,
maxRetries: 20,
connectionTimeoutMs: 10_000,
});
conn.on("status", (s) => console.log(s));
conn.connect();

ActivityTracker

Detects idle and active users by listening for DOM events (mousemove, keydown, pointer, visibility). Transitions through online away offline. Used internally by Room.

import { ActivityTracker } from "@waits/lively-client";
const tracker = new ActivityTracker({
inactivityTime: 60_000, // 1 min → away
offlineInactivityTime: 300_000, // 5 min → offline
});
tracker.start((status) => console.log(status));
tracker.getStatus(); // "online" | "away" | "offline"
tracker.stop();

Utilities

Re-exported for convenience.

import { EventEmitter, throttle } from "@waits/lively-client";
// CRDT types re-exported from @waits/lively-storage
import { LiveObject, LiveMap, LiveList, StorageDocument } from "@waits/lively-client";

Types

Configuration:

  • ClientConfig{ serverUrl, reconnect?, maxRetries?, WebSocket? }
  • JoinRoomOptions{ userId, displayName, cursorThrottleMs?, initialStorage?, inactivityTime?, offlineInactivityTime?, token? }
  • RoomConfigfull room config (used internally)
  • ConnectionConfig{ url, reconnect?, maxRetries?, baseDelay?, maxDelay?, heartbeatIntervalMs?, connectionTimeoutMs? }
  • ActivityTrackerConfig{ inactivityTime?, offlineInactivityTime?, pollInterval? }

Runtime:

  • ConnectionStatus"connected" | "connecting" | "reconnecting" | "disconnected"
  • OnlineStatus"online" | "away" | "offline"
  • PresenceUser{ userId, displayName, onlineStatus, location?, metadata?, joinedAt }
  • CursorData{ userId, x, y, viewportPos?, viewportScale? }

Next Steps