Build
Realtime
Chat rooms, typed streams, presence and fan-out events on one connection.
One session, many lanes. connect() returns the shared session for your key or seat; each lane opens on first use and all of them ride one connection.
import { connect } from '@openpulse/sdk' // servers: reads OPENPULSE_REALM_KEYimport { connect } from '@openpulse/sdk/browser' // browsers: connect({ getToken })Typed messages#
Give every kind of message a schema. defineExtension names it and parses it, so a subscriber only gets values of that type.
import { defineExtension } from '@openpulse/sdk'export const Message = defineExtension<{ from: string; text: string }>({ schema: 'app.message@v1', codec: { parse: (v) => v as { from: string; text: string } }, // or a zod schema's parse})Chat#
const room = pulse.chat('support-42')const off = room.subscribe(Message, (msg, event) => render(msg, event.sequence))await room.publish(Message, { from: '@ana', text: 'On my way' })Delivery to readers is free: one message to a thousand people counts once. Readers that reconnect pick up from their last sequence.
Any stream#
For live data that is not a conversation (orders, prices, job progress), open a stream by name.
const orders = pulse.stream('orders') // preset api_event_streamorders.subscribe(Order, (order) => update(order))await orders.publish(Order, { id: 'o-17', status: 'packed' })Presence#
Who is here and what they are doing. Each participant sets their own state; the room keeps only the newest per participant, handles heartbeats and expiry, and tells everyone when the list changes. Presence updates cost a tenth of a message.
const here = pulse.presence<{ cursor: number; typing: boolean }>('doc-42', { id: user.did, ttlMs: 3000 })here.onChange((peers) => drawCursors(peers))await here.set({ cursor: 118, typing: true })// …await here.close()For a shared place where people and agents work together, pulse.space() adds names, what each participant is looking at and doing, and which person an agent acts for.
Events#
pulse.events(scope) is a fan-out lane for invalidations: “order o-17 changed, refetch”. Keep your data in your own database and use events to tell clients when to look.
Connection state#
pulse.onStatus(({ state, transport }) => { // state: 'live' | 'degraded' | 'auth-error' transport: how pushes arrive right now banner.show(state !== 'live')})Limits to know#
An event counts once per started 64 KiB. Each realm may post up to its plan’s fair-use rate (Hobby 1,000 events a second, Pro 10,000); past it the relay answers 429 and the SDK resends after a second. Details in usage, limits and billing.