Skip to content
pulse logoPulseWebsockets
Explicitly connected, typed WebSocket sessions with scoped channels, ref-counted rooms with reactive presence, reconnect restoration, and heartbeat.
Version
v3.0.0
Size
4.6 KB gzip
Dependencies
Zero dependencies
BrowserNode ≥22
createPulsePulsePulseChannelRoomScopeRoomScopeBase View all 24 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<{ server: { 'chat:message': { text: string } }; client: { 'chat:send': { text: string } } }>(
  '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.6 KB0 B~44 kB gzip
Explicit readinessManual
Session restorationProtocol-specific
Typed scoped channelsBasic
Typed rooms with presence
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
sh
npm install @vielzeug/pulse
sh
yarn add @vielzeug/pulse

Quick Start

Define the protocol schema at construction time, create scopes, then connect before sending.

ts
import { createPulse } from '@vielzeug/pulse';

type Schema = {
  server: { 'chat:message': { text: string } };
  client: { 'chat:send': { text: string } };
  channels: {
    chat: {
      client: { send: { text: string } };
      server: { message: { text: string } };
    };
  };
  rooms: {
    lobby: { presence: { name: string } };
  };
};

const pulse = createPulse<Schema>('wss://api.example.com/ws', {
  reconnect: true,
});
pulse.tap((event) => {
  if (event.type === 'error') console.error(event.error);
  if (event.type === 'status-change') console.log('status:', event.status);
});
const chat = pulse.channel('chat');
const lobby = pulse.room('lobby');

try {
  await pulse.connect();
  chat.send('send', { text: 'Hello!' });
  await lobby.joined;
  lobby.updatePresence({ 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.
  • room() — named, schema-bound ref-counted room scopes with optional reactive presence. The first scope sends join; the last disposal sends leave.
  • reconnect — ordered restoration of channel subscriptions, room memberships, and local presence state.
  • transform — one synchronous transform or filter for application messages.
  • tap() — subscribe to lifecycle events (status changes, errors, disposal) via a typed PulseEvent stream.
  • heartbeat — ping/pong liveness detection that uses the same reconnect controller.
  • status and rooms — framework-neutral external stores for transport and confirmed membership state.

Documentation

See Also

  • Ripple — bridges Pulse external stores into computed values and effects.
  • 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.