Usage without schema
Usage with schema
API
| Method | Description |
|---|---|
heartbeat(data: T, ttlMs: number) | Send presence data, expires after ttlMs |
leave() | Remove own entry immediately |
online() | Returns PresenceEntry<T>[] — only live (non-expired) entries |
onChange(fn) | Subscribe — returns unsubscribe function |
stream() | Returns an Effect Stream<PresenceEntry<T>[]> that emits on every change |
PresenceEntry
TTL behavior
Entries expire whennow > expiresAtMs. Call heartbeat() periodically to keep the entry alive. online() always filters out expired entries before returning.
The server runs a background GC task every few seconds that removes expired entries and broadcasts tombstone deltas to all connected clients — so peers see removals in near-real-time without waiting for the next heartbeat.
Server restart behavior
Presence is ephemeral by design — entries are stored in sled but the TTL clock is wall-time based. When the server restarts:- All presence entries that were already expired remain expired.
- Entries that were still live will appear missing for connected clients until clients re-heartbeat.
- Clients receive a fresh empty state on reconnect, then repopulate as they send heartbeats.
heartbeat() immediately after the WebSocket connects (or reconnects):
There is no server-side “grace period” after restart. If your use case requires durable presence (e.g., last-seen timestamps), use a
LwwRegister instead — it persists across restarts.