The JavaScript SDK is in pre-release and not on npm yet. Install it from source.
FolkwaysDocs

Subscribing

Receive a topic's events in order, choose where to start, handle gaps, and follow many topics at once.

const room = client.topic('chat/lobby');

const sub = room.subscribe((msg) => {
  console.log(msg.name, msg.publisher, msg.data);
}, { from: { last: 50 } });

await sub.ready; // the node confirmed the subscription

topic.subscribe(handler?, options?) returns a Subscription. The handler runs for every event, in order, one at a time: if it returns a promise, the next event waits for it.

room.subscribe(async (msg) => {
  await saveToDatabase(msg); // the next event is delivered after this finishes
});

Where to start

from decides what you receive first on stream topics:

fromYou receive
'latest' (default)Only new events
'oldest'Everything the topic still keeps, then new events
{ last: 50 }The last 50 events, then new ones
{ after: cursor }Everything after a cursor you saved (a Cursor or an "epoch:seq" string), then new ones

from only applies the first time. After a reconnect, the SDK resumes right after the last event your handler finished, so nothing is missed or repeated. With a persistent store that also holds across page reloads.

On kv and signal topics, you first receive the current values (a snapshot), then changes.

Options

OptionDefaultMeaning
from'latest'Where to start when there is no saved position (see above)
onGap'from-oldest'What to do when the position you asked for is no longer kept: 'from-oldest', 'from-latest' or 'fail'
typesallOnly these delivery types, e.g. ['stream']
eventsallOnly these event names, e.g. ['message']

The subscription

MemberMeaning
readyA promise that resolves when the node confirms the subscription, and rejects if it refuses (e.g. Forbidden)
status'pending', 'active', 'resyncing' (reconnecting), 'failed', 'ended' or 'closed'
cursorThe last event this subscription finished handling
unsubscribe()Stops it

It also emits events; on() returns a function that removes the listener:

sub.on('error', (err) => console.error(err.message));            // failed, or your handler threw
sub.on('gap', (gap) => backfill(gap.requested, gap.oldest));     // events were missed, see below
sub.on('ended', (err) => console.warn('ended:', err.codeName));  // e.g. permissions changed
sub.on('snapshot', (current) => console.log(current.length, 'current values'));
sub.on('status', (status) => console.log(status));

You can also read events with for await, which ends when the subscription does:

for await (const msg of client.topic('jobs/new').subscribe()) {
  await handle(msg);
}

Gaps

A topic keeps a limited history (5 MiB by default, and 24 hours at most). If a client was away longer, the events right after its position may be gone. The subscription then emits gap:

sub.on('gap', ({ reason, requested, oldest }) => {
  // reason: 'expired' (history was trimmed) or 'epoch-lost' (the node restarted)
  // Events between `requested` and `oldest` are missing: fetch them from your own database if you need them.
});

With onGap: 'from-oldest' (the default) the subscription carries on from the oldest event still kept; 'from-latest' skips to new events; 'fail' ends it.

Subscribing twice

Several subscribe() calls on the same topic share one subscription on the network, so subscribing in several components is cheap. The first call's options apply. The network subscription ends when the last one unsubscribes.

Many topics at once

Pass an array to client.topic() to handle a set of topics together, such as every chat in an inbox:

const inbox = client.topic(['chat/a', 'chat/b', 'chat/c']);

const sub = inbox.subscribe(notify, { types: ['stream'] });
const { ok, failed } = await sub.ready; // settles when every topic has answered

inbox.add('chat/d');    // joins the live subscription
inbox.remove('chat/a'); // leaves it

Events arrive in order within each topic, but not across topics. Pass strict: true to make ready reject if any topic fails.

Listening to everything

client.on() sees every event the client already receives, from any subscription, with an optional filter. It never subscribes by itself.

const off = client.on({ topic: 'chat/*', event: 'message', self: false }, (msg) => {
  showNotification(msg.topic, msg.publisher);
});
FilterMatches
topicA topic name or pattern (*, trailing >)
delivery'stream', 'kv' or 'signal'
eventAn event name or a list of names
publisherAn identity or a list of identities
selffalse skips events your own identity published

On this page