Skip to content
pulse logoPulseWebsockets
Explicitly connected, typed WebSocket sessions with scoped channels, presence, reconnect restoration, and heartbeat.
Version
v2.1.3
Size
4.4 KB gzip
BrowserNode ≥22
createPulsePulsePulseChannelPresenceChannelPulseOptions View all 16 exports

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);
}
FeaturePulseNative WebSocketsocket.io-client
Bundle size4.4 KB0 B~44 kB gzip
Explicit readinessManual
Session restorationProtocol-specific
Typed scoped channelsBasic
Zero runtime dependencies ripple

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/ripple
sh
npm install @vielzeug/pulse @vielzeug/ripple
sh
yarn add @vielzeug/pulse @vielzeug/ripple

Quick 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.
  • status and rooms — ripple readables for transport and confirmed membership state.

Documentation

See Also

  • Ripple — provides the reactive values exposed by Pulse.
  • Herald — receives routed Pulse events in an in-process application bus.
  • Courier — handles request/response traffic alongside a Pulse session.
  • Clockwork — models application-level authentication or session workflows.