Skip to content

Channels (Pub/Sub)

PulsaChannel provides ephemeral pub/sub messaging over WebSocket, independent of any database table. All subscribers to a channel name receive messages in real time.

const channel = pb.channel('room:123:typing');

Channel names are arbitrary strings. Use namespaced patterns (e.g., entity:id:event) to avoid collisions.


Register a handler for a specific event type. Supports wildcard '*' to catch all events.

channel.on('typing', (payload) => {
console.log(`${payload.user} is typing`);
});
channel.on('presence', (payload) => {
console.log(`${payload.user} is ${payload.status}`);
});
// Catch all events on this channel
channel.on('*', (payload) => {
console.log('Any event:', payload);
});

Returns this — chainable.


Connect to the channel and start receiving events. Must be called after registering handlers with .on().

const channel = pb.channel('room:123');
channel.on('message', (data) => console.log(data));
await channel.subscribe(); // Opens WebSocket if not already open

Broadcast an event to all subscribers of this channel.

await channel.send('typing', {
user: pb.auth.getCachedUser()!.email,
roomId: '123',
});
ParameterTypeDescription
eventstringEvent name (arbitrary string)
payloadanyData to broadcast

Stop listening and disconnect from the channel. Clears all registered handlers.

channel.unsubscribeChannel();

// In your component
const channel = pb.channel(`room:${roomId}:typing`);
channel.on('typing', ({ userId, isTyping }) => {
setTypingUsers((prev) =>
isTyping ? [...prev, userId] : prev.filter((id) => id !== userId)
);
});
await channel.subscribe();
// On input change
const handleInput = async () => {
await channel.send('typing', {
userId: pb.auth.getCachedUser()!.sub,
isTyping: true,
});
};
// On component unmount
channel.unsubscribeChannel();
const presence = pb.channel(`room:${roomId}:presence`);
presence.on('join', ({ user }) => addOnlineUser(user));
presence.on('leave', ({ user }) => removeOnlineUser(user));
await presence.subscribe();
// Announce join
await presence.send('join', { user: { id: pb.auth.getCachedUser()!.sub, name: 'Jane' } });
// On unmount
window.addEventListener('beforeunload', async () => {
await presence.send('leave', { user: { id: pb.auth.getCachedUser()!.sub } });
presence.unsubscribeChannel();
});
Featurepb.channel()pb.from(Model).on().listen()
SourceCustom pub/sub (NATS)PostgreSQL WAL changes
PersistenceEphemeralDurable
Use caseTyping, presence, UI eventsData sync — row CRUD
RLSNot enforcedEnforced via PostgreSQL
Payload structureArbitraryChangeMessage format