Reference
API reference
The essentials of every public class in @afterrealism/dendri-client and
@afterrealism/dendri-y. Full option types are in
Configuration; TypeScript declarations ship with the packages.
Package entry points
| Import path | Exports |
|---|---|
@afterrealism/dendri-client | Dendri, MsgPackDendri, Room, createDendriStore, DendriServerAPI, errors, enums, types. |
…/store | Store-only bundle: createDendriStore and store types. |
…/msgpack | MsgPack wire serializer. |
…/react | useDendriStore hook (peer dep: react ≥ 18). |
…/vue | useDendriStore composable (peer dep: vue ≥ 3.3). |
…/svelte | toSvelteStore (no dependency). |
@afterrealism/dendri-y | DendriYjsProvider, DendriRoomLike. |
class Dendri
A peer: one identity plus a signaling connection. Extends a typed event emitter.
new Dendri(options: DendriOptions)
new Dendri(id: string, options?: DendriOptions)
Properties
| Property | Type | Description |
|---|---|---|
id | string | null | The peer's brokering ID (assigned by the server unless you passed one). |
open | boolean | true once connected to the signaling server. |
connectionState | ConnectionState | Current state machine value (see lifecycle). |
disconnected | boolean | Whether the signaling connection is currently down. |
destroyed | boolean | Whether destroy() has been called. |
Methods
| Method | Returns | Description |
|---|---|---|
connect(peerId, options?) | DataConnection | Open a data connection to a remote peer. Options: label, metadata, serialization, reliable. |
call(peerId, stream, options?) | MediaConnection | Start an audio/video call with a local MediaStream. |
connectHybrid(peerId, options?) | HybridConnection | Data connection with automatic WebRTC ⇄ relay fallback and upgrade retries. |
joinRoom(name) / leaveRoom(name) | void | Low-level room membership (the Room class manages this for you). |
getConnection(peerId, connectionId) | DataConnection | MediaConnection | null | Look up an existing connection. |
disconnect() | void | Close the signaling connection but keep existing P2P connections alive. |
reconnect() | void | Re-establish signaling with the previous ID. |
destroy() | void | Close everything: all connections plus signaling. Terminal. |
diagnostics() | object | Snapshot of connection internals for debugging. |
Events
| Event | Payload | Fired when |
|---|---|---|
open | (id: string) | Connected to the signaling server. |
connection | (conn: DataConnection) | A remote peer opened a data connection to you. |
call | (call: MediaConnection) | A remote peer is calling you. |
close | — | The peer was destroyed. |
disconnected | (currentId: string) | Signaling connection dropped (auto-reconnect kicks in). |
reconnected | — | Signaling successfully re-established; room state is re-sent. |
error | (err: DendriError) | Fatal or connection-level errors. |
roomPeers | (room: string, peers: string[]) | The server sent the current peer list for a room. |
presenceUpdate | (peerId, room, data) | A peer updated its presence. |
connectionStateChanged | (state, previous) | The connection state machine transitioned. |
MsgPackDendri is a drop-in subclass that serializes data connections with MsgPack
instead of JSON/BinaryPack — smaller payloads for object-heavy traffic.
DataConnection & MediaConnection
| Member | On | Description |
|---|---|---|
send(data) | Data | Send any serializable payload (binary supported; large messages are chunked). |
answer(stream?) | Media | Accept an incoming call, optionally attaching your own stream. |
close() | Both | Tear down the connection. |
open | Both | true once usable. |
peer | Both | The remote peer's ID. |
metadata / label | Data | Values set by the initiator at connect() time. |
localStream / remoteStream | Media | The attached media streams. |
on("open" | "data" | "close" | "error") | Data | Lifecycle + payload events. |
on("stream" | "close" | "error") | Media | stream delivers the remote MediaStream. |
class Room
Star-topology room lifecycle: host claiming, peer discovery, reconnection, and deterministic host migration.
import { Dendri, Room } from "@afterrealism/dendri-client";
const room = new Room("room-id", { migrationTimeout: 10_000 });
room.join(Dendri, { url: "wss://signal.example.com" });
Members
| Member | Description |
|---|---|
isHost / hostId / peerId | Topology info for this peer. |
peers / peerCount | Known remote peers (excluding self). |
broadcast(data, { topic? }) | Send to all peers; falls back to the server relay when P2P isn't available. |
broadcastBinary(bytes, { topic }) | Broadcast a Uint8Array; receivers get bytes back intact on every path. |
broadcastWithAck(data, timeoutMs?) | Resolves when every connected peer has ACKed (at-least-once delivery). |
subscribe(topic, handler) | Receive messages on a topic. Returns an unsubscribe function. |
onData(handler) | Receive all data regardless of topic. |
registerRpcMethod(name, handler) | Expose a method other peers can call. Returns an unregister function. |
performRpc(method, payload, { timeout?, peerId? }) | Call a remote method; targets one peer or broadcasts to all handlers. |
setPresence(data) / getPresence(peerId) / getOthers() | Publish and read presence state. |
leave() | Leave the room and clean up all connections and managers. |
Events
| Event | Payload | Fired when |
|---|---|---|
joined | (peerId, isHost) | This peer joined the room. |
peerJoined / peerLeft | (peerId) | A remote peer joined or left. |
data | (peerId, data) | Data received from any peer. |
hostChanged | (newHostId) | Host migration completed. |
error | (err: Error) | Room-level failures. |
createDendriStore()
A framework-agnostic reactive wrapper around a Room. See
Framework guides for usage; the surface:
interface DendriStore {
// Reactive state (also available as an immutable snapshot)
readonly connectionState: ConnectionState;
readonly isHost: boolean;
readonly myPeerId: string | null;
readonly hostId: string | null;
readonly peers: readonly string[];
readonly peerCount: number;
readonly presences: ReadonlyMap<string, unknown>;
// Actions
join(roomId: string): void;
leave(): void;
broadcast(data: unknown, options?: { topic?: string }): void;
broadcastBinary(bytes: Uint8Array, options: { topic: string }): void;
broadcastWithAck(data: unknown, timeout?: number): Promise<void>;
setPresence(data: Record<string, unknown>): void;
// Event registration — each returns an unsubscribe function
onPeerJoin(handler: (peerId: string) => void): () => void;
onPeerLeave(handler: (peerId: string) => void): () => void;
onData(handler: (data: unknown, peerId: string) => void): () => void;
onPresenceUpdate(handler: (peerId: string, data: unknown) => void): () => void;
onHostChanged(handler: (hostId: string) => void): () => void;
// Framework integration
subscribe(listener: () => void): () => void; // state changes
subscribe(topic: string, handler: (data, peerId) => void): () => void; // topic messages
getSnapshot(): DendriStoreSnapshot;
destroy(): void;
}
Framework adapters
| Import | Signature | Notes |
|---|---|---|
…/react | useDendriStore(store): DendriStoreSnapshot | Built on useSyncExternalStore; concurrent-safe. |
…/vue | useDendriStore(store): Readonly<ShallowRef<DendriStoreSnapshot>> | Auto-unsubscribes on unmount. |
…/svelte | toSvelteStore(store): SvelteReadable<DendriStoreSnapshot> | Standard store contract; works with $ syntax in Svelte 4 and 5. |
class DendriServerAPI
Typed client for the server's REST endpoints:
| Method | Returns |
|---|---|
getHealth() | Liveness payload from /health. |
listPeers(room?) | Peer IDs (server must run with --allow_discovery). |
generatePeerId() | A fresh server-generated peer ID. |
getTurnCredentials() | Ephemeral { iceServers } (server must run with --turn_secret). |
getAnalytics() | Analytics dataset descriptor. |
class DendriYjsProvider
new DendriYjsProvider(options: {
room: DendriRoomLike; // a DendriStore satisfies this
doc: Y.Doc;
awareness?: Awareness;
})
provider.destroy(): void; // detach listeners, clean up awareness state
DendriRoomLike is the minimal surface the provider needs
(myPeerId, broadcastBinary, subscribe,
onPeerJoin, onPeerLeave) — any object implementing it works, which is
how the provider stays framework- and transport-agnostic. Full guide:
Collaborative editing.
Errors
Connection-level failures surface as DendriError, which carries a stable
type (e.g. ID_TAKEN, NETWORK, SERVER_ERROR),
a retryable flag, and optional details. Always attach an
error listener to peers and rooms — unhandled peer errors are fatal to the peer.