Skip to content

Live

Live is Anyknown’s realtime/collaborative Capability — multiple Visitors editing, watching, or reacting to the same state in a shared room, conflict-resolved with a CRDT (Yjs) underneath.

Live is split today: the client path works, the server path does not. The browser can open a WebSocket to a room and send/receive ops right now. sdk.live.room() — the server-side handle a handler would use to apply ops, read a snapshot, or push events from your own backend code — throws NotAvailableInV1Error unconditionally in a deployed App. Read this page for what you can build against now, and treat the server half as not-yet-shipped.

liveConnect (exported as connect from @anyknown/app-client) opens a WebSocket to your App’s live gateway at /_anyknown/live/<room> and gives you an event-driven connection object. This is real, running infrastructure — app-runtime routes the WebSocket to a per-room Durable Object (ROOM_DO) that holds the CRDT document, ack’s ops, and fans out to every connected client.

import { liveConnect } from "@anyknown/app-client"
const room = liveConnect("my-room-id", {
lastSeenVersion: 0,
onUnauthenticated: () => {
/* redirect to sign-in, etc. */
},
})
room.on("op", (envelope) => {
// apply envelope.op to local state
})
room.on("presence", ({ clientId, state }) => {
// another client's awareness state changed
})
room.on("error", ({ code, message, retryAfter }) => {
// e.g. code === "RATE_LIMITED"
})
room.send({
opId: crypto.randomUUID(),
clientId: myClientId,
parentVersion: currentVersion,
op: myChange,
})
room.setPresence({ cursor: { x, y } })
// later
room.disconnect()
function liveConnect(roomId: string, opts?: ClientLiveOpts): ClientLiveConnection
type ClientLiveOpts = {
lastSeenVersion?: number
onUnauthenticated?: () => void
}
type ClientLiveConnection = {
send(input: {
opId: string
clientId: string
parentVersion: number
op: unknown
ts?: number
}): void
setPresence(state: unknown): void
disconnect(): void
on<K extends "op" | "ack" | "presence" | "error" | "close" | "replay-done">(
event: K,
handler: (data: ClientLiveEventMap[K]) => void,
): () => void // returns an unsubscribe function
}

The connection reconnects automatically with exponential backoff (capped, up to 12 attempts) on a dropped socket, and replays missed ops from lastSeenVersion on reconnect — you do not need to write your own reconnect or replay logic. A "close" event fires only when you call disconnect() yourself, or after retries are exhausted; an "UNAUTHORIZED" error stops reconnection entirely and calls onUnauthenticated.

Events you can listen for:

EventFires when
opA remote op was applied to the room and reached this client
ackAn op this client sent was accepted and assigned a version
presenceAnother client’s awareness state changed
errorThe gateway rejected something (rate limit, disabled room, etc.)
closeThe connection was deliberately closed or exhausted its retries
replay-doneMissed-op replay from lastSeenVersion finished on (re)connect

The server-side capability type exists in the SDK surface — sdk.live.room(roomId) resolves to a RoomHandle with applyOp, snapshot, subscribe, and awareness methods, mirroring the client shape. None of it is callable in a deployed App today. The binding it depends on (ROOM_DO) is not wired into the App Worker build, so every call to sdk.live.room() throws immediately:

NotAvailableInV1Error: ROOM_DO binding not configured; sdk.live requires features.live=true in plan

This is not a runtime fluke or a misconfiguration you can fix from your App — it is the current, intentional state of the server half of this Capability. Wiring it up needs a cloud-api binding change and a tenant-auth design that has not shipped. Do not write handler code that calls sdk.live.room() and expects it to work; there is no App-level setting that unblocks it.

Build realtime features entirely from the browser side with liveConnect. Multiple Visitors connecting to the same roomId share state through the CRDT-backed room; your handler code does not need to (and currently cannot) participate server-side. This covers collaborative cursors, live presence, shared editing surfaces, and similar client-to-client patterns — anything that does not require your own backend to originate or validate ops.

  • sdk.live.room() throws today — do not build against it. The client liveConnect path is the only working surface. Treat the server-side room API as a documented future contract, not something you can call.

  • The client path is real infrastructure, not a mock. /_anyknown/live/<room> routes to a live ROOM_DO Durable Object with CRDT conflict resolution, ack’d ops, presence/awareness, and automatic reconnect-with-replay.

  • No client SDK ergonomics beyond liveConnect yet. There is no framework-specific wrapper (e.g. a React hook) shipped today — you own the on/send event wiring in your component.

  • A room can reject ops. Watch for error events with code: "RATE_LIMITED", "ROOM_DISABLED", or a storage-cap condition, and handle them rather than assuming every send succeeds.