Concepts
Topics
Topic names, Main Topics and sub-topics, fan-out to super topics, subtree subscriptions, and how to design your topics.
A topic is where data lives in a broker: an event is published on a topic, a state is stored under a key in a topic. Topics are hierarchical, and that hierarchy decides who receives what.
Names#
A topic is 1 to 8 levels joined by :, at most 128 characters. Each level is one or more
of a-z A-Z 0-9 _ -.
| Topic | Valid | Why |
|---|---|---|
chat | ✅ | one level |
chat:lobby | ✅ | two levels |
orders:eu-west:1042 | ✅ | three levels |
chat::lobby | ❌ | empty level |
chat/lobby | ❌ | / is not allowed |
a:b:c:d:e:f:g:h:i | ❌ | nine levels |
Topics are created by using them: there is nothing to declare before publishing or setting a state.
Main Topics and sub-topics#
The first level is the Main Topic. Everything below it is a sub-topic of the Main Topic's family:
chat ← Main Topic
├── chat:lobby ← sub-topic
├── chat:support
│ └── chat:support:42 ← sub-topic, three levels deep
└── chat:randomOnly Main Topics are managed: claims grant permissions on Main Topics, and retention and size
configs are set on Main Topics. Both apply to the whole family. You never configure chat:lobby;
it inherits everything from chat.
Fan-out: publish once, reach every level above#
A publish (Event Broker) or a change (State Broker) on a topic is delivered to the subscribers of that topic and of every super topic:
publish on chat:support:42
reaches sub chat:support:42
sub chat:support
sub chatSo a subscription receives its topic's whole subtree: sub chat:support receives events on
chat:support, chat:support:42, chat:support:42:x, and so on. Each message carries the exact
topic it was published on, so a subscriber can tell them apart.
Two rules keep this predictable:
- Once per connection. A connection subscribed to several levels (
chatandchat:support) receives each event or change once, not once per subscription. - Siblings never mix.
sub chat:supportdoes not receivechat:lobby, andsub chatdoes not receivechatter(a different Main Topic).
Reads follow the same rule: listing the history of chat:support (Event Broker) or the states of
chat:support (State Broker) covers its subtree, exactly what a subscriber would have received.
Shared budgets#
Retention and size limits apply to a Main Topic's family as a whole. If the Event Broker topic
chat keeps at most 50 000 events, that is 50 000 events across all rooms, not per room. Size Main
Topics for all their sub-topics together.
Designing topics#
One Main Topic per kind of data, one sub-topic per instance. chat:{roomId},
orders:{orderId}, board:{boardId}. Permissions and retention are decided per kind; instances
come and go freely.
Subscribe at the level you want to watch. A user in a room subscribes to chat:{roomId}. A
moderation dashboard subscribes to chat and sees every room, with no extra plumbing.
Split data with different lifetimes into different Main Topics. Retention is per Main Topic, so
put durable data and ephemeral data apart: board:{id} for items that must survive a week, and
cursors:{id} with a 60-second TTL for pointer positions.
Split data with different audiences into different Main Topics. Permissions are per Main Topic,
too. If browsers may read orders but only your backend may write it, a single claim on orders
says so. If some data must not reach some users at all, read
Security model first: a subscriber of a Main Topic
receives its whole family.
Remember the namespaces are separate. The Event Broker's chat and the State Broker's chat
are unrelated; using the same name for related data in both is a good convention, not a link.
Helpers in the client#
@thuum/client exports the rules, so you can check a topic before a round trip:
import { isTopic, isMainTopic, mainTopic, superTopics, isInSubtree } from '@thuum/client';
isTopic('chat:support:42'); // true
isMainTopic('chat:support'); // false
mainTopic('chat:support:42'); // 'chat'
superTopics('chat:support:42'); // ['chat:support:42', 'chat:support', 'chat']
isInSubtree('chat:support:42', 'chat'); // true
isInSubtree('chatter', 'chat'); // falseWhen a socket subscribes to a broad topic but a view shows one instance, filter by exact topic or
with isInSubtree:
socket.onMessage((event) => {
if (event.topic !== `chat:${roomId}`) return;
show(event);
});