Typed events
Type each event's data with an event map, and share topic definitions between your apps with defineTopic.
Event maps
Give client.topic() a map from event names to data types. TypeScript then checks what you publish and narrows
msg.data by msg.name:
type ChatEvents = {
message: { text: string };
edited: { uid: string; text: string };
};
const room = client.topic<ChatEvents>('chat/lobby');
await room.publish('message', { text: 'hi' }); // ✓
// room.publish('message', { body: 'hi' }); // ✗ type error
room.subscribe((msg) => {
if (msg.name === 'message') addMessage(msg.data.text); // data: { text: string }
if (msg.name === 'edited') editMessage(msg.data.uid, msg.data.text);
});Types are not sent to the node; they only help your code.
Shared topic definitions
defineTopic describes a topic once: its name pattern, its events and each event's delivery type. Put it in a
shared file and use it from your web app, mobile app and backend, so event names and types can't drift apart.
import { defineTopic } from '@folkways/realtime';
type RideEvents = {
location: { lat: number; lng: number };
status: { state: 'waiting' | 'on-the-way' | 'arrived' };
};
export const rideLocation = defineTopic<RideEvents>()('rides/{ride}/location', {
events: {
location: { delivery: 'kv', ttlMs: 30_000 },
status: 'kv',
},
});Open it with the topic's parameters. publish() then fills in each event's delivery type (and kv lifetime), so you
can't forget { delivery: 'kv' }:
const ride = rideLocation.open(client, { ride: '42' }); // topic rides/42/location
await ride.publish('location', { lat: 59.43, lng: 24.75 }); // sent as kv, expires after 30 s
// ride.publish('pickup', …) // ✗ not in the definition: refused locally| Member | Meaning |
|---|---|
pattern | The pattern as written, e.g. rides/{ride}/location |
topic(params) | The concrete name, e.g. rideLocation.topic({ ride: '42' }) → 'rides/42/location' |
open(client, params) | The typed topic handle, with each event's delivery filled in |
events | Each event's delivery type and options |
Each {param} must be one topic level: a value containing / throws InvalidTopic.
Event options in a definition:
| Option | Meaning |
|---|---|
delivery | 'stream', 'kv' or 'signal'; a plain string is short for { delivery } |
ttlMs | kv only: default lifetime of values |
Each event's delivery type must match the topic's rule in the dashboard, which today allows one type per rule.
Definitions also accept keyIsPublisher, access and presence, for rules and features that are coming soon;
leave them out for now.