Version v2.1.3 Size 4.4 KB gzip
Why Pulse?
Native WebSocket leaves connection ownership, event routing, reconnect restoration, and cleanup to each application. Pulse provides those boundaries while making readiness explicit: applications connect before sending, and disconnected messages never disappear silently.
ts
// Before
const socket = new WebSocket('wss://api.example.com/ws');
socket.addEventListener('message', (event) => route(JSON.parse(event.data)));
socket.addEventListener('close', () => setTimeout(() => reconnect(), 1_000));
// After
const pulse = createPulse<ServerEvents, ClientEvents>('wss://api.example.com/ws', { reconnect: true });
try {
await pulse.connect();
pulse.on('chat:message', (message) => console.log(message.text));
pulse.send('chat:send', { text: 'Hello!' });
} catch (error) {
console.error('Pulse connection failed:', error);
}| Feature | Pulse | Native WebSocket | socket.io-client |
|---|---|---|---|
| Bundle size | 4.4 KB | 0 B | ~44 kB gzip |
| Explicit readiness | Manual | ||
| Session restoration | Protocol-specific | ||
| Typed scoped channels | Basic | ||
| Zero runtime dependencies |
Use Pulse when you need a typed WebSocket session whose reconnect and cleanup behavior must be deterministic.
Consider native WebSocket when a single untyped connection does not need retry, routing, or session restoration.
Installation
sh
pnpm add @vielzeug/pulse @vielzeug/ripplesh
npm install @vielzeug/pulse @vielzeug/ripplesh
yarn add @vielzeug/pulse @vielzeug/rippleQuick Start
Define the protocol at construction time, create scopes, then connect before sending.
ts
import { createPulse } from '@vielzeug/pulse';
type ServerEvents = { 'chat:message': { text: string } };
type ClientEvents = { 'chat:send': { text: string } };
type Channels = {
chat: {
client: { send: { text: string } };
server: { message: { text: string } };
};
};
type Presence = { lobby: { name: string } };
const pulse = createPulse<ServerEvents, ClientEvents, Channels, Presence>('wss://api.example.com/ws', {
reconnect: true,
onError: (error) => console.error(error),
});
const chat = pulse.channel('chat');
const lobby = pulse.presence('lobby');
try {
await pulse.connect();
chat.send('send', { text: 'Hello!' });
lobby.update({ name: 'Ada' });
} catch (error) {
console.error('Pulse connection failed:', error);
}
pulse.dispose();Features
connect()— explicit readiness; application messages throw while disconnected.channel()— named, schema-bound scopes with independent disposal and reference-counted server subscriptions.presence()— named, schema-bound reactive presence scopes with reference-counted room membership.reconnect— ordered restoration of channel subscriptions, rooms, and local presence state.transform— one synchronous transform or filter for application messages.onError— typed connection and protocol errors.heartbeat— ping/pong liveness detection that uses the same reconnect controller.statusandrooms— ripple readables for transport and confirmed membership state.