Summary#
Realtime is one construct: a topic. Everything else — rooms, threads, direct messages, typing indicators, read receipts — is ordinary app modelling on top of it.
[Principal] entity User {
[MaxLength(200)] string Email;
security { allow read when IsAuthenticated; }
}
class Tick { public int Count; }
topic Heartbeat() {
Candidates = _ => true; // who may subscribe. REQUIRED — silence is a compile error
Carries = Tick; // what each message holds
}
void Beat(int n) {
Heartbeat.Publish(new Tick { Count = n });
}Description#
What a topic is for#
A page that must reflect something happening elsewhere has three options, and only one of them scales:
| approach | what it costs |
|---|---|
| poll | every page re-asks on a timer, whether or not anything changed |
| signal, then re-read | one message tells the page something changed and it re-runs its whole query |
| a topic | the message carries the value, so the page receives exactly what changed |
The middle option is what a plain live query does, and it is correct but expensive: a signal carrying no value can only be answered by re-asking the whole question. Measured, a room of ten people a hundred messages deep moves about a thousand rows to deliver one "ok" — every one of which was already on every screen. The same message on a value-carrying topic moves ten.
Security is declared, once#
A topic's Candidates rule decides who may subscribe, and the platform enforces it at the moment of joining. There
is nothing to remember at the call site and nothing an app can bypass, which is the same split entity security
already has: the app owns the policy, the platform owns the enforcement.
Because the address is made of typed parameters rather than a string name, a topic's name is never its security
boundary — guessing RoomFeed gets you nothing without a rule that admits you at that address.
What is not part of it#
A topic is transport. The vocabulary of a chat product — channels, threads, DMs, reactions, read receipts, moderation — is app modelling, and the platform deliberately ships none of it: those are entities and rules an app declares, and they are better for being the app's own.
Examples#
See topic for compiled examples: a room feed with a membership rule, an explicitly public topic, a broadcast topic with a separate publish rule, a webhook publishing with no row to carry it, and presence.
See also#
- topic — the construct, in full
- Listen — the subscribe half: what a page receives
- Here, Announce — who is here, and what they are doing
- security { } — the
allowrules aCandidatespredicate reads - component — where a subscription is consumed