Skip to content

beryl/presence

Presence - Distributed presence tracking backed by a CRDT

Wraps the pure lattice_presence/presence_state CRDT in an OTP actor that:

  • Handles track/untrack calls
  • Periodically broadcasts state via PubSub for cross-node replication
  • Receives remote state from PubSub and merges it internally
  • Invokes on_diff callback when merges produce non-empty diffs

When running beryl under beryl/supervisor, enable presence with supervisor.with_presence and obtain the handle from supervisor.presence instead of calling start directly.

let ps = pubsub.start(pubsub.default_config())
let config =
presence.default_config("node1")
|> presence.with_pubsub(ps)
|> presence.with_broadcast_interval(1500)
let assert Ok(p) = presence.start(config)
let ref = presence.track(p, "room:lobby", "user:1", "socket-1", meta)
let entries = presence.list(p, "room:lobby")

Configuration for starting presence.

Build configs with default_config and the with_* functions so beryl can add future options without exposing record fields as public API.

pub type Config

An opaque diff representing presence joins and leaves grouped by topic.

This is passed to Config.on_diff and accepted by beryl.broadcast_presence_diff.

pub type Diff

Messages the presence actor handles

pub type Message

A running Presence instance.

This handle is intentionally opaque so callers cannot forge actor subjects or depend on the runtime representation.

pub type Presence

A presence entry returned from queries and diff accessors.

This type is intentionally transparent so callers can inspect query results and construct entries for diff.

pub type PresenceEntry {
PresenceEntry(
session_id: String,
key: String,
meta: json.Json
)
}

Errors from presence operations

pub type PresenceError {
PresenceStartFailed(error.StartFailure)
}

The presence actor failed to start.

Default configuration (no PubSub).

The broadcast interval defaults to 1500 ms so that adding with_pubsub yields working two-way replication out of the box; without PubSub the interval is unused. Use with_broadcast_interval(0) to disable periodic broadcasts and control replication manually.

pub fn default_config(String) -> Config

Build a presence diff from topic-grouped joins and leaves.

Most applications receive diffs from Config.on_diff; this helper is for callers that need to construct a diff to pass to beryl.broadcast_presence_diff.

pub fn diff(
joins: List(#(String, List(PresenceEntry))),
leaves: List(#(String, List(PresenceEntry)))
) -> Diff

Get presence joins for a topic in this diff.

pub fn diff_joins(
Diff,
String
) -> List(PresenceEntry)

Get presence leaves for a topic in this diff.

pub fn diff_leaves(
Diff,
String
) -> List(PresenceEntry)

List topics touched by this diff.

pub fn diff_topics(Diff) -> List(String)

Get presences for a specific key within a topic

Panics if the presence actor is unavailable or does not reply within 5 seconds.

pub fn get_by_key(
Presence,
String,
String
) -> List(#(String, json.Json))

List all presences for a topic

Panics if the presence actor is unavailable or does not reply within 5 seconds.

pub fn list(
Presence,
String
) -> List(PresenceEntry)

Start the presence actor

pub fn start(Config) -> Result(Presence, PresenceError)

Track a presence in a topic.

session_id identifies the session (e.g. socket) that owns this presence and is the value untrack_all matches on when the session disconnects.

Returns a server-generated tracking ref: an opaque, unique handle for this specific presence. Pass it to untrack to remove exactly this entry later. The ref is not the session id — it is minted by the presence actor and is only meaningful to that actor. The ref is also merged into object metas as phx_ref for Phoenix client compatibility.

Panics if the presence actor is unavailable or does not reply within 5 seconds.

pub fn track(
Presence,
String,
String,
String,
json.Json
) -> String

Untrack a specific presence using the ref returned by track.

Removing an unknown or already-removed ref is a harmless no-op.

Panics if the presence actor is unavailable or does not reply within 5 seconds.

pub fn untrack(
Presence,
String
) -> Nil

Untrack all presences for a session (e.g., when a socket disconnects)

Panics if the presence actor is unavailable or does not reply within 5 seconds.

pub fn untrack_all(
Presence,
String
) -> Nil

Set how often presence state is broadcast for replication.

Use 0 to disable periodic broadcasts.

pub fn with_broadcast_interval(
Config,
Int
) -> Config

Set the callback invoked when local changes or remote merges produce a diff.

pub fn with_on_diff(
Config,
fn(Diff) -> Nil
) -> Config

Enable PubSub replication for presence.

pub fn with_pubsub(
Config,
pubsub.PubSub(SyncPayload)
) -> Config