Realtime

Ask for a collection and you are told of every create, update and delete in it as it happens. Each change is read through the watcher's own rules, so nobody is told about a row they couldn't fetch.

Server-sent events

One request, and the browser’s own EventSource:

app.ts
const events = new EventSource("/api/realtime?subscribe=messages,notices");
events.addEventListener("record", (e) => {
  const { action, collection, record } = JSON.parse(e.data);
  // action: "create", "update" or "delete"
});

Leave out subscribe for every collection the caller can see. The first event is connected, with what was subscribed. EventSource can’t send headers, so a signed-in page adds its token to the address: &token=….

A WebSocket

sluurp.socket() in the browser client does the same over one WebSocket (/api/ws), and more:

app.ts
const socket = sluurp.socket({ subscribe: ["messages"] });
socket.on("record", ({ action, record }) => show(record));

socket.join("room:4b");                         // be counted among those present
socket.emit("room:4b", "typing", { by: me });  // said, not kept
socket.send("messages", { body: "Hello" });     // a write, under the same rules as over HTTP

It reconnects by itself and joins its topics again.

What it costs

Watchers are grouped before anything is read. A rule that doesn’t name the caller gives everyone the same answer, so it is asked once for all of them: ten thousand anonymous watchers cost one query. Only a rule like author = @request.auth.id is asked per person, and people who are the same person share it.

Or let it keep itself

Most screens don’t want events; they want a list that is right. Sync keeps a filtered collection current in the browser from the same changes, and live views keep server-rendered HTML current.