API Overview
StarfishClient Methods
Connection
| Method | Returns | Description |
|---|---|---|
connect(project?) | Promise<void> | Connect to the server, optionally declaring the Project |
disconnect() | Promise<void> | Disconnect and clean up |
connectionState$ | Observable<ConnectionState> | Connection state changes |
clientId | string | null | Your assigned client ID |
project | string | null | The effective Project the server bound you to |
Sessions
| Method | Returns | Description |
|---|---|---|
join(session, options?) | Promise<JoinResult> | Join a session by name |
joinProject(project, options?) | Promise<JoinResult> | Join a Project by policy: connect, enter the named-or-default policy, and land in the matched session in one call |
leave() | Promise<void> | Leave the current session |
clients$ | Observable<ClientInfo[]> | All clients in the session |
peers$ | Observable<ClientInfo[]> | All clients except yourself |
Topics
| Method | Returns | Description |
|---|---|---|
topics.subscribe(topic, callback?) | Promise<StarfishFrame> | Subscribe to a topic |
topics.unsubscribe(topic) | Promise<void> | Unsubscribe from a topic |
topics.publish(topic, payload, options?) | void | Publish to a topic |
topics.topic$(topic) | EventStream<StarfishFrame> | Stream of messages for a topic |
Messaging
| Method | Returns | Description |
|---|---|---|
messaging.send(to, payload, options?) | void | Send to specific client(s) |
messaging.broadcast(payload, options?) | void | Send to all clients in the session |
messaging.messages$ | EventStream<StarfishFrame> | All incoming direct messages |
messaging.messagesFrom$(peerId) | EventStream<StarfishFrame> | Messages from a specific peer |
Presence
| Method | Returns | Description |
|---|---|---|
presence.set(payload) | void | Set your presence data |
presence$ | Observable<Map<string, any>> | All clients' presence data |
State
| Method | Returns | Description |
|---|---|---|
state.set(options) | Promise<StateResult> | Write state with an operation |
state.get({ key, scope }) | Promise<StateResult> | Read state |
state.changes$ | EventStream<StateResult> | All state change events |
state.keyChanges$(key) | EventStream<StateResult> | Changes for a specific key |
WebRTC
| Method | Returns | Description |
|---|---|---|
connectRTC(peerId, channels?) | Promise<void> | Open RTC connection to a peer |
disconnectRTC(peerId) | void | Close RTC connection |
rtcPeers$ | Observable<RTCPeerInfo[]> | null | RTC peer connection states |
getPeerConnection(peerId) | RTCPeerConnection | null | Escape hatch to the raw peer connection |
Media
Share and receive live WebRTC MediaStreams (camera, mic, screen, canvas) over the peer-to-peer mesh, with the same broadcast/direct/topic addressing as state. Browser-only (TypeScript SDK). client.getMedia() returns null when RTC is not enabled. See the Share Realtime Media recipe and the Realtime Media reference for the full API.
| Method | Returns | Description |
|---|---|---|
media.share(stream, opts?) | Publication | Share a stream (session / { to } / { topic }) |
media.subscribe(topic) | Promise<void> | Declare interest in a media topic |
media.unsubscribe(topic) | Promise<void> | Stop receiving a media topic |
media.streamAdded$ | EventStream<RemoteStream> | Remote stream added (.subscribe(cb) returns Unsubscribe) |
media.streamEnded$ | EventStream<RemoteStream> | Remote stream removed (.subscribe(cb) returns Unsubscribe) |
media.remote$ | Observable<RemoteStream[]> | Reactive list of inbound remote streams |
Events
| Method | Returns | Description |
|---|---|---|
events$(filter?) | EventStream<StarfishFrame> | Filtered event stream (filter by resource, method, topic, from) |
onFrame(callback) | Unsubscribe | Listen to all frames |
Clock
| Method | Returns | Description |
|---|---|---|
clock.sync(samples?) | Promise<number> | Sync with server clock |
clock.now() | number | Current server-adjusted time |
clock.offset | number | Clock offset in ms |
clock.runAt(serverTime, callback) | Unsubscribe | Schedule callback at server time; call the returned handle to cancel |
Pools
Server-managed matchmaking: enter a pool and the server groups you with other waiting clients into a shared session. TypeScript exposes these through client.pool; Python exposes them directly on the client. See the Pool reference for the full API.
| TypeScript | Python | Returns | Description |
|---|---|---|---|
pool.enter(name, options) | pool_enter(options) | Promise<PoolEnterResult> / PoolEnteredResult | Enter a pool — ad-hoc form (client supplies config) |
pool.enter({ policy }) | pool_enter(PoolPolicyEnterOptions(...)) | Promise<PoolEnterResult> / PoolEnteredResult | Enter by declared policy (config is server-owned). See Matchmaking as a Policy |
pool.leave(name) | pool_leave(pool) | void / None | Leave a pool |
pool.claim(name, targetId) | pool_claim(pool, target) | void / None | Claim a specific member (claim/mutual modes) |
pool.accept(name, fromId) | pool_accept(pool, from_) | void / None | Accept a proposal (propose mode) |
pool.reject(name, fromId) | pool_reject(pool, from_) | void / None | Reject a proposal (propose mode) |
pool.assign(name, groups) | pool_assign(pool, groups) | Promise<StarfishFrame> / StarfishFrame | Assign groups (delegated mode, matchmaker role) |
pool.matched$ | pool_matched | EventStream<PoolMatchedEvent> / EventStream[PoolMatchResult] | Emitted when the server matches you into a session |
pool.members$ | pool_members(pool) | Observable<PoolMember[]> | Current members of the pool |
pool.proposal$ | — | EventStream<...> | Incoming pairing proposals (TypeScript only) |
pool.claimRejected$ | — | EventStream<...> | Emitted when a claim you made is rejected (TypeScript only) |
Pool mode is one of "auto" (server pairs by group size), "claim", "mutual", "propose", or "delegated". In the ad-hoc form you supply mode/groupSize and pass create: true to open the pool if it does not exist yet — this works in implicit and non-lockdown Projects. In the policy form you name a declared policy and the server owns mode/groupSize/filter. Matchmaking precedes session membership: you don't join a session before entering — on matched you join the server-minted session.
Language-Specific API Differences
The SDKs share the same concepts but adapt to each language's conventions:
| Concept | TypeScript | Python | Swift |
|---|---|---|---|
| Async | Promise / async/await | asyncio coroutines | async/await with structured concurrency |
| Reactive streams | Observable<T> / EventStream<T> | Observable[T] / EventStream[T] | AsyncStream<T> |
| Subscribe callback | .subscribe(cb) | .subscribe(cb) | for await ... in stream or .subscribe(cb) |
| Unsubscribe | Call returned function | Call returned function | Call returned closure |
| Errors | StarfishError thrown | StarfishError raised | StarfishError thrown |
| Topic stream | topics.topic$(name) | topic_stream(name) | topics.messages(forTopic:) |
| State key stream | state.keyChanges$(name) | state.key_stream(name) | state.changes(forKey:) |
| Presence observable | presence$ | presence | presence.presenceUpdates |
| Connection observable | connectionState$ | connection_state | connectionState |
Observable vs EventStream
The SDK uses two reactive primitives:
Observable<T> holds a current value and emits when it changes. Use .value to read the current state synchronously.
// Read current value
const state = client.connectionState$.value;
// React to changes
const unsub = client.connectionState$.subscribe((state) => {
console.log(state);
});
// Stop listening
unsub();state = client.connection_state.value
unsub = client.connection_state.subscribe(
lambda state: print(state)
)
unsub()// As AsyncStream
for await state in client.connectionState {
print(state)
}
// Or callback-based
let unsub = client.connectionState.subscribe { state in
print(state)
}
unsub()EventStream<T> emits discrete events with no "current value." Subscribe to receive events as they occur.
const unsub = client.state.changes$.subscribe((result) => {
console.log("State changed:", result.key, result.value);
});unsub = client.state.changed.subscribe(
lambda result: print("State changed:", result.key, result.value)
)for await result in client.state.changes {
print("State changed:", result.key, result.value)
}Key Types
StarfishFrame
The protocol message unit — an envelope with header and payload:
interface StarfishFrame {
header: StarfishHeader;
payload?: Record<string, unknown>;
}
interface StarfishHeader {
v?: 2;
id: string;
resource: string;
method: string;
kind: "request" | "response" | "event";
ts?: number;
session?: string;
from?: string;
to?: string | string[];
topic?: string;
replyTo?: string;
delivery?: DeliveryOptions;
priority?: "low" | "normal" | "high" | "critical";
ttl?: number;
meta?: Record<string, unknown>;
}See Core Concepts for details on kind semantics and header.meta extensibility.
ClientInfo
interface ClientInfo {
id: string;
name?: string;
role?: string;
meta?: Record<string, unknown>;
}StateResult
interface StateResult {
key: string;
scope: "self" | "session";
value: unknown;
version: number;
}StarfishError
class StarfishError extends Error {
code: string; // e.g. "NO_SESSION", "NOT_CONNECTED"
message: string;
resource?: string; // the resource that produced the error
retry?: boolean; // whether the client should retry
details?: unknown;
}Pool Types
// Ad-hoc form: the client supplies the pool config.
interface PoolEnterOptions {
groupSize: number;
mode?: "auto" | "claim" | "mutual" | "propose" | "delegated"; // default "auto"
role?: "member" | "matchmaker"; // default "member"
meta?: Record<string, unknown>; // member metadata
filter?: Record<string, string>;
create?: boolean;
}
// Policy form: the client references a declared policy; the Project owns the config.
interface PoolPolicyEnterOptions {
policy?: string; // omit to use the Project's default policy
role?: "member" | "matchmaker";
meta?: Record<string, unknown>;
}
interface PoolMember {
id: string;
meta?: Record<string, unknown>;
}
interface PoolMatchedEvent {
pool: string;
session: string;
peers: PoolMember[];
}In Python these correspond to the PoolEnterOptions, PoolPolicyEnterOptions, PoolMember, and PoolMatchResult dataclasses. See the Pool reference for the Python field definitions.
See Troubleshooting for a list of all error codes.