Skip to main content

Realtime

Weblisk provides signal-driven wrappers for real-time communication protocols.

WebSocket

Reactive WebSocket with auto-reconnect and heartbeat:

js
import { socket } from 'weblisk/net/ws.js';

const ws = socket('wss://api.example.com/ws', {
  reconnect: true,   // Auto-reconnect on disconnect
  maxRetries: 10,    // Max reconnection attempts
  heartbeat: 30000,  // Heartbeat ping every 30s
});

// Reactive message signal
effect(() => {
  const msgs = ws.messages();
  if (msgs.length) {
    console.log('Latest:', msgs[msgs.length - 1]);
  }
});

// Connection state
effect(() => {
  console.log('WS state:', ws.state()); // 'connecting', 'open', 'closed'
});

// Send data
ws.send({ type: 'subscribe', channel: 'updates' });

// Close
ws.close();

Server-Sent Events (SSE)

Signal-driven SSE connection:

js
import { stream, latest } from 'weblisk/net/sse.js';

const sse = stream('/api/events', {
  channels: ['notifications', 'updates'],
});

// All events
effect(() => {
  console.log('Events:', sse.events());
});

// Just the latest event
const lastEvent = latest(sse.events);
effect(() => {
  const event = lastEvent();
  if (event) showNotification(event.data);
});

// Connection state
effect(() => {
  console.log('SSE:', sse.status()); // 'connecting', 'open', 'closed'
});

sse.close();

WebTransport

HTTP/3 bidirectional transport with WebSocket fallback:

js
import { transport } from 'weblisk/net/transport.js';

const conn = transport('https://api.example.com/transport', {
  wsUrl: 'wss://api.example.com/ws', // Fallback URL
  reconnect: true,
  heartbeat: 15000,
});

// Connection state
effect(() => console.log('State:', conn.state()));

// Send data
conn.send({ type: 'message', text: 'hello' });

// Listen for messages
conn.on('message', (data) => {
  console.log('Received:', data);
});

// Named channels
const chat = conn.channel('chat');
chat.send({ text: 'Hi!' });
chat.on('message', (msg) => console.log(msg));

conn.close();

The transport module automatically selects WebTransport when available, falling back to WebSocket in all browsers.

CRDT Primitives Pro

Conflict-free replicated data types for offline-first collaboration. Resolve concurrent writes automatically without a central server — sync state between peers, tabs, or devices.

LWW Register

Last-Writer-Wins Register — resolves concurrent writes by timestamp. Ties broken by node ID.

js
import { lwwRegister } from 'weblisk/pro/crdt.js';

const title = lwwRegister('Untitled', 'node-a');

// Local write
title.set('Meeting Notes');

// Merge a remote state received over WebSocket
title.merge(remoteState);

// Export state for syncing to other nodes
ws.send(title.state());

Counters

Grow-Only (G-Counter) and Positive-Negative (PN-Counter) for distributed counts:

js
import { gCounter, pnCounter } from 'weblisk/pro/crdt.js';

// Grow-only counter (likes, page views)
const likes = gCounter('node-a');
likes.increment();
likes.merge(remoteLikes);
console.log(likes.value()); // Total across all nodes

// PN counter (inventory, votes — supports decrement)
const stock = pnCounter('node-a');
stock.increment(10);
stock.decrement(3);

LWW Map

A key-value map where each key is an independent LWW Register — ideal for collaborative document fields:

js
import { lwwMap } from 'weblisk/pro/crdt.js';

const doc = lwwMap('node-a');
doc.set('title', 'Sprint Retro');
doc.set('status', 'in-progress');

// Merge remote changes
doc.merge(remoteState);
console.log(doc.get('title'));   // Latest value
console.log(doc.entries());   // All key-value pairs

CRDT Types

TypeUse CaseOperations
lwwRegisterSingle value (name, status)get, set, merge, state
gCounterGrow-only counts (likes, views)increment, value, merge, state
pnCounterCounts with decrement (stock, votes)increment, decrement, value, merge, state
lwwMapKey-value document fieldsget, set, delete, entries, merge, state