Lab
Publish / subscribe
// A message on a topic: what happened, and to which draft.
type Message = { topic: string; draft: string };
// A subscription: the topics it wants, and its own inbox of unread copies.
type Subscription = { id: number; topics: string[]; inbox: Message[] };
// The broker: publishers and subscribers know it, never each other.
class Broker {
nextId = 1;
subscriptions: Subscription[] = [];
subscribe(topics: string[]): number {
const id = this.nextId++;
this.subscriptions.push({ id, topics, inbox: [] });
return id;
}
// Leaves a copy in every subscription that wants this topic.
publish(topic: string, draft: string): number {
let copies = 0;
for (const subscription of this.subscriptions) {
if (subscription.topics.includes(topic)) {
subscription.inbox.push({ topic, draft });
copies++;
}
}
return copies;
}
// Hands one subscriber its unread copies, oldest first, and empties its inbox.
drain(id: number): Message[] {
const subscription = this.subscriptions.find((s) => s.id === id);
if (!subscription) return [];
const messages = subscription.inbox;
subscription.inbox = [];
return messages;
}
}
// The publisher: it knows the broker and the topic names, nothing else.
class Editor {
broker: Broker;
constructor(broker: Broker) {
this.broker = broker;
}
save(draft: string) {
this.broker.publish('draft.saved', draft);
}
remove(draft: string) {
this.broker.publish('draft.deleted', draft);
}
}
const broker = new Broker();
const editor = new Editor(broker);
// Two subscribers. Each knows the broker and its topics, not the editor.
const activity = broker.subscribe(['draft.saved']);
const search = broker.subscribe(['draft.saved', 'draft.deleted']);
// The writer saves the pricing page; the activity panel reads at once.
editor.save('pricing');
const feed = broker.drain(activity);
// Then deletes the page, and restores it from the trash.
editor.remove('pricing');
editor.save('pricing');
// The search index catches up later, in one batch.
const indexed = new Set<string>();
for (const message of broker.drain(search)) {
if (message.topic === 'draft.saved') indexed.add(message.draft);
else indexed.delete(message.draft);
}
const found = indexed.has('pricing');
const later = broker.drain(activity);
console.log(`activity: read ${feed.length}, then ${later.length}`);
console.log(`search index has pricing: ${found}`);
TODO
Visual to come