Skip to content

API Overview

StarfishClient Methods

Connection

MethodReturnsDescription
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
clientIdstring | nullYour assigned client ID
projectstring | nullThe effective Project the server bound you to

Sessions

MethodReturnsDescription
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

MethodReturnsDescription
topics.subscribe(topic, callback?)Promise<StarfishFrame>Subscribe to a topic
topics.unsubscribe(topic)Promise<void>Unsubscribe from a topic
topics.publish(topic, payload, options?)voidPublish to a topic
topics.topic$(topic)EventStream<StarfishFrame>Stream of messages for a topic

Messaging

MethodReturnsDescription
messaging.send(to, payload, options?)voidSend to specific client(s)
messaging.broadcast(payload, options?)voidSend 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

MethodReturnsDescription
presence.set(payload)voidSet your presence data
presence$Observable<Map<string, any>>All clients' presence data

State

MethodReturnsDescription
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

MethodReturnsDescription
connectRTC(peerId, channels?)Promise<void>Open RTC connection to a peer
disconnectRTC(peerId)voidClose RTC connection
rtcPeers$Observable<RTCPeerInfo[]> | nullRTC peer connection states
getPeerConnection(peerId)RTCPeerConnection | nullEscape 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.

MethodReturnsDescription
media.share(stream, opts?)PublicationShare 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

MethodReturnsDescription
events$(filter?)EventStream<StarfishFrame>Filtered event stream (filter by resource, method, topic, from)
onFrame(callback)UnsubscribeListen to all frames

Clock

MethodReturnsDescription
clock.sync(samples?)Promise<number>Sync with server clock
clock.now()numberCurrent server-adjusted time
clock.offsetnumberClock offset in ms
clock.runAt(serverTime, callback)UnsubscribeSchedule 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.

TypeScriptPythonReturnsDescription
pool.enter(name, options)pool_enter(options)Promise<PoolEnterResult> / PoolEnteredResultEnter a pool — ad-hoc form (client supplies config)
pool.enter({ policy })pool_enter(PoolPolicyEnterOptions(...))Promise<PoolEnterResult> / PoolEnteredResultEnter by declared policy (config is server-owned). See Matchmaking as a Policy
pool.leave(name)pool_leave(pool)void / NoneLeave a pool
pool.claim(name, targetId)pool_claim(pool, target)void / NoneClaim a specific member (claim/mutual modes)
pool.accept(name, fromId)pool_accept(pool, from_)void / NoneAccept a proposal (propose mode)
pool.reject(name, fromId)pool_reject(pool, from_)void / NoneReject a proposal (propose mode)
pool.assign(name, groups)pool_assign(pool, groups)Promise<StarfishFrame> / StarfishFrameAssign groups (delegated mode, matchmaker role)
pool.matched$pool_matchedEventStream<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:

ConceptTypeScriptPythonSwift
AsyncPromise / async/awaitasyncio coroutinesasync/await with structured concurrency
Reactive streamsObservable<T> / EventStream<T>Observable[T] / EventStream[T]AsyncStream<T>
Subscribe callback.subscribe(cb).subscribe(cb)for await ... in stream or .subscribe(cb)
UnsubscribeCall returned functionCall returned functionCall returned closure
ErrorsStarfishError thrownStarfishError raisedStarfishError thrown
Topic streamtopics.topic$(name)topic_stream(name)topics.messages(forTopic:)
State key streamstate.keyChanges$(name)state.key_stream(name)state.changes(forKey:)
Presence observablepresence$presencepresence.presenceUpdates
Connection observableconnectionState$connection_stateconnectionState

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.

ts
// Read current value
const state = client.connectionState$.value;

// React to changes
const unsub = client.connectionState$.subscribe((state) => {
  console.log(state);
});

// Stop listening
unsub();
python
state = client.connection_state.value

unsub = client.connection_state.subscribe(
    lambda state: print(state)
)

unsub()
swift
// 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.

ts
const unsub = client.state.changes$.subscribe((result) => {
  console.log("State changed:", result.key, result.value);
});
python
unsub = client.state.changed.subscribe(
    lambda result: print("State changed:", result.key, result.value)
)
swift
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:

ts
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

ts
interface ClientInfo {
  id: string;
  name?: string;
  role?: string;
  meta?: Record<string, unknown>;
}

StateResult

ts
interface StateResult {
  key: string;
  scope: "self" | "session";
  value: unknown;
  version: number;
}

StarfishError

ts
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

ts
// 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.