@vielzeug/pulse 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +37 -42
  2. package/dist/_connection.cjs +2 -0
  3. package/dist/_connection.cjs.map +1 -0
  4. package/dist/_connection.d.ts +9 -0
  5. package/dist/_connection.d.ts.map +1 -0
  6. package/dist/_connection.js +107 -0
  7. package/dist/_connection.js.map +1 -0
  8. package/dist/channel.cjs +1 -1
  9. package/dist/channel.cjs.map +1 -1
  10. package/dist/channel.js +32 -35
  11. package/dist/channel.js.map +1 -1
  12. package/dist/heartbeat.cjs +1 -1
  13. package/dist/heartbeat.cjs.map +1 -1
  14. package/dist/heartbeat.d.ts.map +1 -1
  15. package/dist/heartbeat.js +1 -2
  16. package/dist/heartbeat.js.map +1 -1
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/presence.cjs +1 -1
  20. package/dist/presence.cjs.map +1 -1
  21. package/dist/presence.d.ts.map +1 -1
  22. package/dist/presence.js +34 -35
  23. package/dist/presence.js.map +1 -1
  24. package/dist/pulse.cjs +1 -1
  25. package/dist/pulse.cjs.map +1 -1
  26. package/dist/pulse.d.ts +4 -13
  27. package/dist/pulse.d.ts.map +1 -1
  28. package/dist/pulse.iife.js +1 -1
  29. package/dist/pulse.iife.js.map +1 -1
  30. package/dist/pulse.js +1 -1
  31. package/dist/pulse.js.map +1 -1
  32. package/dist/types.d.ts +40 -69
  33. package/dist/types.d.ts.map +1 -1
  34. package/package.json +2 -2
  35. package/dist/_dev.cjs +0 -2
  36. package/dist/_dev.cjs.map +0 -1
  37. package/dist/_dev.d.ts +0 -2
  38. package/dist/_dev.d.ts.map +0 -1
  39. package/dist/_dev.js +0 -6
  40. package/dist/_dev.js.map +0 -1
  41. package/dist/reconnect.cjs +0 -2
  42. package/dist/reconnect.cjs.map +0 -1
  43. package/dist/reconnect.d.ts +0 -13
  44. package/dist/reconnect.d.ts.map +0 -1
  45. package/dist/reconnect.js +0 -32
  46. package/dist/reconnect.js.map +0 -1
package/dist/pulse.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"pulse.js","names":[],"sources":["../src/errors.ts","../src/_dev.ts","../src/_utils.ts","../src/_wait.ts","../src/channel.ts","../src/protocol.ts","../src/heartbeat.ts","../src/presence.ts","../src/reconnect.ts","../src/pulse.ts"],"sourcesContent":["/**\n * Base class for all pulse errors.\n * Use `instanceof PulseError` to catch any pulse-originated error in one branch.\n */\nexport class PulseError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is PulseError {\n return err instanceof PulseError;\n }\n}\n\n/**\n * Thrown when a WebSocket connection cannot be established or is lost\n * without triggering auto-reconnect (budget exhausted, or reconnect disabled).\n */\nexport class PulseConnectionError extends PulseError {\n readonly url: string;\n\n constructor(message: string, url: string, opts?: ErrorOptions) {\n super(message, opts);\n this.url = url;\n }\n}\n\n/**\n * Thrown when `wait()` or `join()`/`leave()` times out before receiving a\n * server response, or when an AbortSignal fires before the event arrives.\n */\nexport class PulseTimeoutError extends PulseError {\n readonly event: string;\n\n constructor(event: string, opts?: ErrorOptions) {\n super(`Timed out waiting for \"${event}\"`, opts);\n this.event = event;\n }\n}\n\n/**\n * Thrown when `wait()` is aborted via an AbortSignal before the event fires.\n */\nexport class PulseAbortError extends PulseError {\n constructor(opts?: ErrorOptions) {\n super('Aborted', opts);\n }\n}\n\n/**\n * Thrown when a method is called on a disposed Pulse instance or channel.\n */\nexport class PulseDisposedError extends PulseError {\n constructor(target = 'Pulse', opts?: ErrorOptions) {\n super(`${target} instance is disposed`, opts);\n }\n}\n\n/**\n * Thrown when the server sends a frame that cannot be parsed or violates\n * the wire protocol schema.\n */\nexport class PulseProtocolError extends PulseError {\n readonly raw: unknown;\n\n constructor(message: string, raw?: unknown, opts?: ErrorOptions) {\n super(message, opts);\n this.raw = raw;\n }\n}\n","const isDev = !(globalThis as { __PULSE_PROD__?: boolean }).__PULSE_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/pulse] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n","/**\n * Returns a signal that aborts as soon as any of the provided signals abort.\n * With a single argument, returns it directly (no allocation).\n * Cleanup listeners are registered to prevent leaks.\n *\n * @internal\n */\nexport function combineSignals(first: AbortSignal, ...rest: AbortSignal[]): AbortSignal {\n if (rest.length === 0) return first;\n\n return rest.reduce(mergeTwo, first);\n}\n\nfunction mergeTwo(a: AbortSignal, b: AbortSignal): AbortSignal {\n if (a.aborted) return a;\n\n if (b.aborted) return b;\n\n const ctrl = new AbortController();\n const onA = (): void => ctrl.abort(a.reason);\n const onB = (): void => ctrl.abort(b.reason);\n\n a.addEventListener('abort', onA, { once: true });\n b.addEventListener('abort', onB, { once: true });\n ctrl.signal.addEventListener(\n 'abort',\n () => {\n a.removeEventListener('abort', onA);\n b.removeEventListener('abort', onB);\n },\n { once: true },\n );\n\n return ctrl.signal;\n}\n\n/**\n * Create a child `AbortController` that aborts as soon as `parent` does.\n * Handles the case where `parent` is already aborted at call time — a plain\n * `addEventListener('abort', ...)` would miss that, since the event already fired.\n *\n * @internal\n */\nexport function deriveAbortController(parent: AbortSignal): AbortController {\n const ctrl = new AbortController();\n\n if (parent.aborted) {\n ctrl.abort(parent.reason);\n } else {\n parent.addEventListener('abort', () => ctrl.abort(parent.reason), { once: true });\n }\n\n return ctrl;\n}\n\n/**\n * Sleep for `ms` milliseconds, aborting early when the signal fires.\n * Resolves (does not reject) on abort — callers check `signal.aborted`.\n *\n * @internal\n */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal?.aborted) {\n resolve();\n\n return;\n }\n\n const id = setTimeout(resolve, ms);\n\n signal?.addEventListener(\n 'abort',\n () => {\n clearTimeout(id);\n resolve();\n },\n { once: true },\n );\n });\n}\n\n/**\n * Full-jitter exponential backoff in ms, capped at `maxMs`.\n * `attempt` is zero-based.\n *\n * @internal\n */\nexport function defaultReconnectDelay(attempt: number, maxMs = 30_000): number {\n const base = Math.min(1000 * Math.pow(2, attempt), maxMs);\n\n return Math.random() * base;\n}\n","import type { Unsubscribe } from './types';\n\nimport { combineSignals } from './_utils';\nimport { PulseAbortError, PulseTimeoutError } from './errors';\n\n/**\n * Shared implementation for `pulse.wait()` and `channel.wait()`.\n * Resolves on the next event dispatched via `subscribe`, or rejects when\n * the combined signal aborts or the optional timeout elapses.\n *\n * @internal\n */\nexport function createWaitPromise<T>(\n event: string,\n baseSignal: AbortSignal,\n opts: { signal?: AbortSignal; timeout?: number } | undefined,\n subscribe: (event: string, handler: (payload: unknown) => void) => Unsubscribe,\n): Promise<T> {\n return new Promise((resolve, reject) => {\n const signals: AbortSignal[] = [baseSignal];\n\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n if (opts?.signal) signals.push(opts.signal);\n\n if (opts?.timeout !== undefined) {\n const timeoutCtrl = new AbortController();\n\n timeoutId = setTimeout(() => {\n timeoutCtrl.abort(new PulseTimeoutError(event));\n }, opts.timeout);\n signals.push(timeoutCtrl.signal);\n }\n\n const combined = signals.length === 1 ? signals[0]! : combineSignals(signals[0]!, ...signals.slice(1));\n\n if (combined.aborted) {\n clearTimeout(timeoutId);\n reject(combined.reason instanceof PulseTimeoutError ? combined.reason : new PulseAbortError());\n\n return;\n }\n\n let unsub: Unsubscribe = () => {};\n\n const onAbort = (): void => {\n clearTimeout(timeoutId);\n unsub();\n reject(combined.reason instanceof PulseTimeoutError ? combined.reason : new PulseAbortError());\n };\n\n combined.addEventListener('abort', onAbort, { once: true });\n\n unsub = subscribe(event, (payload) => {\n clearTimeout(timeoutId);\n combined.removeEventListener('abort', onAbort);\n resolve(payload as T);\n });\n });\n}\n","import type { EventKey, MessageMap, PulseChannel, Unsubscribe } from './types';\n\nimport { warn } from './_dev';\nimport { deriveAbortController } from './_utils';\nimport { createWaitPromise } from './_wait';\n\ntype SendFn = (channel: string, event: string, payload: unknown) => void;\ntype SubscribeFn = (channel: string, event: string, handler: (payload: unknown) => void) => () => void;\n\n/**\n * Create a typed channel object that scopes messaging to a named namespace.\n * @internal\n */\nexport function createChannel<TServer extends MessageMap, TClient extends MessageMap>(\n name: string,\n send: SendFn,\n subscribe: SubscribeFn,\n disposalSignal: AbortSignal,\n onDispose?: () => void,\n): PulseChannel<TServer, TClient> {\n const ctrl = deriveAbortController(disposalSignal);\n const tracked = new Set<() => void>();\n\n let disposed = ctrl.signal.aborted;\n\n ctrl.signal.addEventListener(\n 'abort',\n () => {\n disposed = true;\n },\n { once: true },\n );\n\n const channel: PulseChannel<TServer, TClient> = {\n get disposalSignal() {\n return ctrl.signal;\n },\n\n dispose() {\n if (disposed) return;\n\n disposed = true;\n ctrl.abort();\n\n for (const unsub of tracked) unsub();\n\n tracked.clear();\n onDispose?.();\n },\n\n get disposed() {\n return disposed;\n },\n\n get name() {\n return name;\n },\n\n on<K extends EventKey<TServer>>(event: K, handler: (payload: TServer[K]) => void): Unsubscribe {\n if (disposed) {\n warn(`on('${String(event)}') called on a disposed channel '${name}' — listener ignored`);\n\n return () => {};\n }\n\n const unsub = subscribe(name, event, handler as (payload: unknown) => void);\n\n tracked.add(unsub);\n\n return () => {\n unsub();\n tracked.delete(unsub);\n };\n },\n\n once<K extends EventKey<TServer>>(event: K, handler: (payload: TServer[K]) => void): Unsubscribe {\n if (disposed) {\n warn(`once('${String(event)}') called on a disposed channel '${name}' — listener ignored`);\n\n return () => {};\n }\n\n let unsub: Unsubscribe = () => {};\n\n const wrapped = (payload: unknown): void => {\n tracked.delete(unsub);\n unsub();\n (handler as (payload: unknown) => void)(payload);\n };\n\n unsub = subscribe(name, event, wrapped);\n tracked.add(unsub);\n\n return () => {\n tracked.delete(unsub);\n unsub();\n };\n },\n\n send<K extends EventKey<TClient>>(event: K, payload: TClient[K]): void {\n if (disposed) {\n warn(`send('${String(event)}') called on a disposed channel '${name}' — message dropped`);\n\n return;\n }\n\n send(name, event, payload);\n },\n\n [Symbol.dispose]() {\n this.dispose();\n },\n\n wait<K extends EventKey<TServer>>(\n event: K,\n opts?: { signal?: AbortSignal; timeout?: number },\n ): Promise<TServer[K]> {\n return createWaitPromise<TServer[K]>(event as string, ctrl.signal, opts, (ev, handler) =>\n subscribe(name, ev, handler),\n );\n },\n };\n\n return channel;\n}\n","import { PulseProtocolError } from './errors';\n\n// ─── Outgoing frames (client → server) ────────────────────────────────────────\n\nexport type OutMessageFrame = { channel?: string; event: string; payload: unknown; type: 'message' };\nexport type OutSubscribeFrame = { channel: string; type: 'subscribe' };\nexport type OutUnsubscribeFrame = { channel: string; type: 'unsubscribe' };\nexport type OutJoinFrame = { room: string; type: 'join' };\nexport type OutLeaveFrame = { room: string; type: 'leave' };\nexport type OutPresenceFrame = { room: string; state: unknown; type: 'presence' };\nexport type OutPingFrame = { ts: number; type: 'ping' };\n\nexport type OutFrame =\n | OutJoinFrame\n | OutLeaveFrame\n | OutMessageFrame\n | OutPingFrame\n | OutPresenceFrame\n | OutSubscribeFrame\n | OutUnsubscribeFrame;\n\n// ─── Incoming frames (server → client) ────────────────────────────────────────\n\nexport type InMessageFrame = { channel?: string; event: string; payload: unknown; type: 'message' };\nexport type InSubscribedFrame = { channel: string; type: 'subscribed' };\nexport type InUnsubscribedFrame = { channel: string; type: 'unsubscribed' };\nexport type InJoinedFrame = { room: string; type: 'joined' };\nexport type InLeftFrame = { room: string; type: 'left' };\nexport type InPresenceStateFrame = { members: Record<string, unknown>; room: string; type: 'presence_state' };\nexport type InPresenceJoinFrame = { id: string; room: string; state: unknown; type: 'presence_join' };\nexport type InPresenceLeaveFrame = { id: string; room: string; type: 'presence_leave' };\nexport type InPongFrame = { ts: number; type: 'pong' };\nexport type InErrorFrame = { code: string; message: string; type: 'error' };\n\nexport type InFrame =\n | InErrorFrame\n | InJoinedFrame\n | InLeftFrame\n | InMessageFrame\n | InPongFrame\n | InPresenceJoinFrame\n | InPresenceLeaveFrame\n | InPresenceStateFrame\n | InSubscribedFrame\n | InUnsubscribedFrame;\n\ntype ParsedFrame = { type: string } & Record<string, unknown>;\nexport type DecodedInFrame = { frame: InFrame; kind: 'known' } | { kind: 'unknown'; type: string };\n\n// ─── Codec ────────────────────────────────────────────────────────────────────\n\n/**\n * Encode an outgoing frame to a JSON string.\n * @internal\n */\nexport function encode(frame: OutFrame): string {\n return JSON.stringify(frame);\n}\n\n/**\n * Parse a raw MessageEvent data string and ensure it has a string `type` field.\n * Throws `PulseProtocolError` if the data is not valid JSON or missing the `type` field.\n * @internal\n */\nexport function decode(raw: unknown): ParsedFrame {\n if (typeof raw !== 'string') {\n throw new PulseProtocolError('Expected string message data', raw);\n }\n\n let parsed: unknown;\n\n try {\n parsed = JSON.parse(raw);\n } catch (cause) {\n throw new PulseProtocolError('JSON parse error', raw);\n }\n\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n !('type' in parsed) ||\n typeof (parsed as { type: unknown }).type !== 'string'\n ) {\n throw new PulseProtocolError('Missing or invalid \"type\" field', parsed);\n }\n\n return parsed as ParsedFrame;\n}\n\nfunction isObjectRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction getStringProp(frame: unknown, key: string): string | null {\n if (!isObjectRecord(frame)) return null;\n\n const value = frame[key];\n\n return typeof value === 'string' ? value : null;\n}\n\nexport function isErrorFrame(frame: unknown): frame is InErrorFrame {\n return getStringProp(frame, 'code') !== null && getStringProp(frame, 'message') !== null;\n}\n\nexport function isJoinedFrame(frame: unknown): frame is InJoinedFrame {\n return getStringProp(frame, 'room') !== null;\n}\n\nexport function isLeftFrame(frame: unknown): frame is InLeftFrame {\n return getStringProp(frame, 'room') !== null;\n}\n\nexport function isMessageFrame(frame: unknown): frame is InMessageFrame {\n if (!isObjectRecord(frame) || getStringProp(frame, 'event') === null || !('payload' in frame)) return false;\n\n return frame.channel === undefined || typeof frame.channel === 'string';\n}\n\nexport function isPongFrame(frame: unknown): frame is InPongFrame {\n return isObjectRecord(frame) && typeof frame.ts === 'number';\n}\n\nexport function isPresenceJoinFrame(frame: unknown): frame is InPresenceJoinFrame {\n return (\n isObjectRecord(frame) &&\n getStringProp(frame, 'id') !== null &&\n getStringProp(frame, 'room') !== null &&\n 'state' in frame\n );\n}\n\nexport function isPresenceLeaveFrame(frame: unknown): frame is InPresenceLeaveFrame {\n return getStringProp(frame, 'id') !== null && getStringProp(frame, 'room') !== null;\n}\n\nexport function isPresenceStateFrame(frame: unknown): frame is InPresenceStateFrame {\n return isObjectRecord(frame) && getStringProp(frame, 'room') !== null && isObjectRecord(frame.members);\n}\n\nfunction isSubscribedFrame(frame: unknown): frame is InSubscribedFrame {\n return getStringProp(frame, 'channel') !== null;\n}\n\nfunction isUnsubscribedFrame(frame: unknown): frame is InUnsubscribedFrame {\n return getStringProp(frame, 'channel') !== null;\n}\n\n/**\n * Parse and validate known incoming frame shapes.\n * Unknown frame `type` values are passed through so callers can warn/drop them.\n * @internal\n */\nexport function decodeValidated(raw: unknown): DecodedInFrame {\n const frame = decode(raw) as ParsedFrame;\n\n switch (frame.type) {\n case 'error':\n if (isErrorFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'joined':\n if (isJoinedFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'left':\n if (isLeftFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'message':\n if (isMessageFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'pong':\n if (isPongFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'presence_join':\n if (isPresenceJoinFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'presence_leave':\n if (isPresenceLeaveFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'presence_state':\n if (isPresenceStateFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'subscribed':\n if (isSubscribedFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'unsubscribed':\n if (isUnsubscribedFrame(frame)) return { frame, kind: 'known' };\n\n break;\n default:\n return { kind: 'unknown', type: frame.type };\n }\n\n throw new PulseProtocolError(`Malformed \"${frame.type}\" frame`, frame);\n}\n","import type { HeartbeatOptions } from './types';\n\nimport { warn } from './_dev';\nimport { encode } from './protocol';\n\nconst DEFAULT_INTERVAL = 30_000;\nconst DEFAULT_TIMEOUT = 5_000;\n\nexport type HeartbeatHandle = {\n /** Called when a `pong` frame is received. Resets the timeout timer. */\n onPong(): void;\n /** Start the heartbeat loop. */\n start(): void;\n /** Stop the heartbeat loop and cancel any pending timers. */\n stop(): void;\n};\n\n/**\n * Create a heartbeat manager that sends periodic pings and expects pong replies.\n * Calls `onDead` when a pong is not received within the timeout window.\n * @internal\n */\nexport function createHeartbeat(\n opts: boolean | HeartbeatOptions | undefined,\n send: (frame: string) => void,\n onDead: () => void,\n): HeartbeatHandle {\n if (!opts) {\n return { onPong() {}, start() {}, stop() {} };\n }\n\n const options: HeartbeatOptions = opts === true ? {} : opts;\n const interval = options.interval ?? DEFAULT_INTERVAL;\n const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n\n let pingTimer: ReturnType<typeof setTimeout> | undefined;\n let pongTimer: ReturnType<typeof setTimeout> | undefined;\n let running = false;\n\n function schedulePing(): void {\n pingTimer = setTimeout(() => {\n const ts = Date.now();\n\n send(encode({ ts, type: 'ping' }));\n\n pongTimer = setTimeout(() => {\n warn(`Heartbeat timed out after ${timeout}ms — treating connection as dead`);\n onDead();\n }, timeout);\n }, interval);\n }\n\n return {\n onPong() {\n clearTimeout(pongTimer);\n pongTimer = undefined;\n\n if (running) schedulePing();\n },\n\n start() {\n running = true;\n clearTimeout(pingTimer);\n clearTimeout(pongTimer);\n pongTimer = undefined;\n schedulePing();\n },\n\n stop() {\n running = false;\n clearTimeout(pingTimer);\n clearTimeout(pongTimer);\n pingTimer = undefined;\n pongTimer = undefined;\n },\n };\n}\n","import { type Signal, signal } from '@vielzeug/ripple';\n\nimport type { PresenceChannel, Unsubscribe } from './types';\n\nimport { warn } from './_dev';\nimport { deriveAbortController } from './_utils';\nimport { type InPresenceJoinFrame, type InPresenceLeaveFrame, type InPresenceStateFrame, encode } from './protocol';\n\ntype RawSendFn = (frame: string) => void;\n\nexport type PresenceEvents = {\n onJoin(handler: (frame: InPresenceJoinFrame) => void): Unsubscribe;\n onLeave(handler: (frame: InPresenceLeaveFrame) => void): Unsubscribe;\n onState(handler: (frame: InPresenceStateFrame) => void): Unsubscribe;\n};\n\n/**\n * Create a presence channel that reactively tracks room member states.\n * @internal\n */\nexport function createPresence<T>(\n room: string,\n rawSend: RawSendFn,\n events: PresenceEvents,\n disposalSignal: AbortSignal,\n onDispose?: () => void,\n): PresenceChannel<T> {\n const ctrl = deriveAbortController(disposalSignal);\n\n let disposed = ctrl.signal.aborted;\n\n ctrl.signal.addEventListener(\n 'abort',\n () => {\n disposed = true;\n },\n { once: true },\n );\n\n const members: Signal<Map<string, T>> = signal(new Map<string, T>());\n const joinHandlers = new Set<(memberId: string, state: T) => void>();\n const leaveHandlers = new Set<(memberId: string) => void>();\n\n const unsubs: Array<() => void> = [\n events.onState((frame) => {\n if (frame.room !== room) return;\n\n const next = new Map<string, T>();\n\n for (const [id, state] of Object.entries(frame.members)) {\n next.set(id, state as T);\n }\n\n members.value = next;\n }),\n events.onJoin((frame) => {\n if (frame.room !== room) return;\n\n const next = new Map(members.value);\n\n next.set(frame.id, frame.state as T);\n members.value = next;\n\n for (const handler of joinHandlers) {\n handler(frame.id, frame.state as T);\n }\n }),\n events.onLeave((frame) => {\n if (frame.room !== room) return;\n\n const next = new Map(members.value);\n\n next.delete(frame.id);\n members.value = next;\n\n for (const handler of leaveHandlers) {\n handler(frame.id);\n }\n }),\n ];\n\n const presence: PresenceChannel<T> = {\n get disposalSignal() {\n return ctrl.signal;\n },\n\n dispose() {\n if (disposed) return;\n\n disposed = true;\n rawSend(encode({ room, type: 'leave' }));\n ctrl.abort();\n\n for (const unsub of unsubs) unsub();\n\n unsubs.length = 0;\n joinHandlers.clear();\n leaveHandlers.clear();\n onDispose?.();\n },\n\n get disposed() {\n return disposed;\n },\n\n onJoin(handler: (memberId: string, state: T) => void): Unsubscribe {\n if (disposed) {\n warn(`onJoin() called on a disposed presence channel '${room}' — listener ignored`);\n\n return () => {};\n }\n\n joinHandlers.add(handler);\n\n return () => joinHandlers.delete(handler);\n },\n\n onLeave(handler: (memberId: string) => void): Unsubscribe {\n if (disposed) {\n warn(`onLeave() called on a disposed presence channel '${room}' — listener ignored`);\n\n return () => {};\n }\n\n leaveHandlers.add(handler);\n\n return () => leaveHandlers.delete(handler);\n },\n\n get room() {\n return room;\n },\n\n get state() {\n return members;\n },\n\n [Symbol.dispose]() {\n this.dispose();\n },\n\n update(state: T): void {\n if (disposed) return;\n\n rawSend(encode({ room, state, type: 'presence' }));\n },\n };\n\n return presence;\n}\n","import type { ReconnectOptions } from './types';\n\nimport { defaultReconnectDelay, sleep } from './_utils';\n\nconst DEFAULT_MAX_ATTEMPTS = 5;\n\nexport type ReconnectHandle = {\n /**\n * Attempt a reconnect. Waits for the computed delay then calls `connect`.\n * Returns outcome details including success and, when available, the failure reason.\n */\n attempt(\n connect: () => Promise<void>,\n signal: AbortSignal,\n onReconnect?: (attempt: number) => void,\n ): Promise<{ error?: unknown; ok: boolean }>;\n /** Cancel any pending reconnect attempt and reset the counter. */\n reset(): void;\n};\n\n/**\n * Create a stateful reconnect manager.\n * @internal\n */\nexport function createReconnect(opts: boolean | ReconnectOptions | undefined): ReconnectHandle {\n if (!opts) {\n return {\n attempt: async () => ({ ok: false }),\n reset() {},\n };\n }\n\n const options: ReconnectOptions = opts === true ? {} : opts;\n const maxAttempts = options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;\n const delayFn: (attempt: number) => number =\n typeof options.delay === 'function'\n ? options.delay\n : typeof options.delay === 'number'\n ? () => options.delay as number\n : defaultReconnectDelay;\n\n let attempts = 0;\n\n return {\n async attempt(\n connect: () => Promise<void>,\n signal: AbortSignal,\n onReconnect?: (attempt: number) => void,\n ): Promise<{ error?: unknown; ok: boolean }> {\n if (attempts >= maxAttempts) return { ok: false };\n\n const delay = delayFn(attempts);\n\n attempts++;\n onReconnect?.(attempts);\n\n await sleep(delay, signal);\n\n if (signal.aborted) return { ok: false };\n\n try {\n await connect();\n\n attempts = 0;\n\n return { ok: true };\n } catch (error) {\n return { error, ok: false };\n }\n },\n\n reset() {\n attempts = 0;\n },\n };\n}\n","import { signal } from '@vielzeug/ripple';\n\nimport type {\n EventKey,\n MessageMap,\n Middleware,\n PresenceChannel,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseStatus,\n Unsubscribe,\n} from './types';\n\nimport { warn } from './_dev';\nimport { combineSignals } from './_utils';\nimport { createWaitPromise } from './_wait';\nimport { createChannel } from './channel';\nimport { PulseAbortError, PulseConnectionError, PulseDisposedError, PulseTimeoutError } from './errors';\nimport { createHeartbeat } from './heartbeat';\nimport { createPresence } from './presence';\nimport {\n type DecodedInFrame,\n type InPresenceJoinFrame,\n type InPresenceLeaveFrame,\n type InPresenceStateFrame,\n decodeValidated,\n encode,\n} from './protocol';\nimport { createReconnect } from './reconnect';\n\n// ─── Internal routing map ─────────────────────────────────────────────────────\n\ntype Handler = (payload: unknown) => void;\n\n/**\n * A two-level listener map: channel (null = root) → event → Set<handler>.\n * @internal\n */\nclass ListenerMap {\n private readonly map = new Map<string | null, Map<string, Set<Handler>>>();\n\n add(channel: string | null, event: string, handler: Handler): () => void {\n let events = this.map.get(channel);\n\n if (!events) {\n events = new Map();\n this.map.set(channel, events);\n }\n\n let handlers = events.get(event);\n\n if (!handlers) {\n handlers = new Set();\n events.set(event, handlers);\n }\n\n handlers.add(handler);\n\n return () => {\n handlers.delete(handler);\n\n if (handlers.size === 0) events.delete(event);\n\n if (events.size === 0) this.map.delete(channel);\n };\n }\n\n dispatch(channel: string | null, event: string, payload: unknown): void {\n this.map\n .get(channel)\n ?.get(event)\n ?.forEach((h) => h(payload));\n }\n\n clear(): void {\n this.map.clear();\n }\n}\n\n// ─── createPulse ──────────────────────────────────────────────────────────────\n\n/**\n * Create a managed WebSocket connection with typed messaging, channels,\n * rooms, and presence.\n *\n * @example\n * ```ts\n * const pulse = createPulse<ServerEvents, ClientEvents>('wss://api.example.com/ws', {\n * reconnect: true,\n * heartbeat: true,\n * });\n * pulse.on('message', (data) => console.log(data));\n * ```\n */\nexport function createPulse<TServer extends MessageMap = MessageMap, TClient extends MessageMap = MessageMap>(\n url: string,\n opts: PulseOptions = {},\n): Pulse<TServer, TClient> {\n // ── State ──────────────────────────────────────────────────────────────────\n\n const disposalCtrl = new AbortController();\n const status = signal<PulseStatus>('connecting');\n const rooms = signal<ReadonlySet<string>>(new Set());\n const listeners = new ListenerMap();\n const middleware: readonly Middleware[] = opts.middleware ?? [];\n\n const pendingJoins = new Map<string, Set<() => void>>();\n const pendingLeaves = new Map<string, Set<() => void>>();\n\n let ws: WebSocket | null = null;\n let disposed = false;\n let intentionalClose = false;\n\n const pendingOpens: Array<{ reject: (e: Error) => void; resolve: () => void }> = [];\n\n // Memoized channel cache — same name returns the same object\n const channelCache = new Map<string, PulseChannel<MessageMap, MessageMap>>();\n // Memoized presence cache — same room returns the same object\n const presenceCache = new Map<string, PresenceChannel<unknown>>();\n\n // Message buffer (null = disabled)\n const bufferCfg = opts.buffer;\n const buffer: string[] | null = bufferCfg ? [] : null;\n const bufferMaxSize: number = typeof bufferCfg === 'object' && bufferCfg !== null ? (bufferCfg.maxSize ?? 50) : 50;\n\n // ── Reconnect / Heartbeat ──────────────────────────────────────────────────\n\n const reconnect = createReconnect(opts.reconnect);\n\n const heartbeat = createHeartbeat(\n opts.heartbeat,\n (frame) => {\n if (ws?.readyState === WebSocket.OPEN) ws.send(frame);\n },\n () => {\n warn('Heartbeat dead — closing socket for reconnect');\n ws?.close(4000, 'heartbeat timeout');\n },\n );\n\n // ── Raw send helper ────────────────────────────────────────────────────────\n\n function rawSend(frame: string): void {\n if (ws?.readyState === WebSocket.OPEN) {\n ws.send(frame);\n } else if (buffer !== null) {\n if (buffer.length >= bufferMaxSize) buffer.shift();\n\n buffer.push(frame);\n } else {\n warn(`send() called while connection is ${status.value} — message dropped`);\n }\n }\n\n // ── Middleware pipeline ────────────────────────────────────────────────────\n\n function runMiddleware(event: string, payload: unknown, final: () => void): void {\n let idx = 0;\n\n function next(): void {\n if (idx >= middleware.length) {\n final();\n\n return;\n }\n\n const mw = middleware[idx++]!;\n\n mw(event, payload, next);\n }\n\n next();\n }\n\n // ── WebSocket lifecycle ────────────────────────────────────────────────────\n\n function openSocket(): void {\n if (disposed) return;\n\n ws = new WebSocket(url, opts.protocols);\n\n ws.onopen = (): void => {\n intentionalClose = false;\n status.value = 'open';\n reconnect.reset();\n heartbeat.start();\n opts.onOpen?.();\n\n const pending = pendingOpens.splice(0);\n\n for (const p of pending) p.resolve();\n\n if (buffer && buffer.length > 0) {\n const queued = buffer.splice(0);\n\n for (const f of queued) ws!.send(f);\n }\n\n for (const name of channelCache.keys()) {\n ws!.send(encode({ channel: name, type: 'subscribe' }));\n }\n };\n\n ws.onmessage = (ev: MessageEvent): void => {\n opts.onMessage?.(ev);\n\n let frame: DecodedInFrame;\n\n try {\n frame = decodeValidated(ev.data);\n } catch (err) {\n warn(`Protocol error: ${String(err)}`);\n\n return;\n }\n\n try {\n handleFrame(frame);\n } catch (err) {\n warn(`Frame handling error: ${String(err)}`);\n }\n };\n\n ws.onerror = (): void => {\n if (opts.onError) {\n opts.onError(new PulseConnectionError('WebSocket error', url));\n } else {\n warn('WebSocket error — pass onError to observe it (see onClose for recovery logic)');\n }\n\n const pending = pendingOpens.splice(0);\n\n for (const p of pending) p.reject(new PulseConnectionError('WebSocket error', url));\n };\n\n ws.onclose = (ev: CloseEvent): void => {\n heartbeat.stop();\n opts.onClose?.(ev.code, ev.reason);\n\n const pending = pendingOpens.splice(0);\n\n for (const p of pending) p.reject(new PulseConnectionError('Connection closed before open', url));\n\n if (intentionalClose || disposed) {\n status.value = 'closed';\n\n return;\n }\n\n handleUnexpectedClose();\n };\n }\n\n async function handleUnexpectedClose(): Promise<void> {\n status.value = 'reconnecting';\n\n const result = await reconnect.attempt(\n () =>\n new Promise<void>((resolve, reject) => {\n pendingOpens.push({ reject, resolve });\n openSocket();\n }),\n disposalCtrl.signal,\n opts.onReconnect,\n );\n\n if (!result.ok && !disposed) {\n if (result.error) {\n warn(`Reconnect attempt failed: ${String(result.error)}`);\n }\n\n warn('Reconnect budget exhausted — connection permanently closed');\n status.value = 'closed';\n }\n }\n\n // ── Frame routing ──────────────────────────────────────────────────────────\n\n function handleFrame(decoded: DecodedInFrame): void {\n if (decoded.kind === 'unknown') {\n warn(`Received frame with an unrecognized type: ${decoded.type}`);\n\n return;\n }\n\n const frame = decoded.frame;\n\n switch (frame.type) {\n case 'error':\n warn(`Server error [${frame.code}]: ${frame.message}`);\n\n break;\n\n case 'joined': {\n const next = new Set(rooms.value);\n\n next.add(frame.room);\n rooms.value = next;\n\n const joinCbs = pendingJoins.get(frame.room);\n\n if (joinCbs) {\n pendingJoins.delete(frame.room);\n\n for (const cb of joinCbs) cb();\n }\n\n break;\n }\n\n case 'left': {\n const next = new Set(rooms.value);\n\n next.delete(frame.room);\n rooms.value = next;\n\n const leaveCbs = pendingLeaves.get(frame.room);\n\n if (leaveCbs) {\n pendingLeaves.delete(frame.room);\n\n for (const cb of leaveCbs) cb();\n }\n\n break;\n }\n\n case 'message':\n listeners.dispatch(frame.channel ?? null, frame.event, frame.payload);\n\n break;\n\n case 'pong':\n heartbeat.onPong();\n\n break;\n\n case 'presence_join':\n listeners.dispatch(null, frame.type, frame);\n\n break;\n\n case 'presence_leave':\n listeners.dispatch(null, frame.type, frame);\n\n break;\n\n case 'presence_state':\n listeners.dispatch(null, frame.type, frame);\n\n break;\n\n case 'subscribed':\n case 'unsubscribed':\n break;\n }\n }\n\n // ── Shared room confirmation helper ───────────────────────────────────────\n\n function awaitRoomConfirmation(\n pending: Map<string, Set<() => void>>,\n room: string,\n frameType: 'join' | 'leave',\n roomOpts?: { signal?: AbortSignal; timeout?: number },\n ): Promise<void> {\n if (disposed) return Promise.reject(new PulseDisposedError());\n\n return new Promise((resolve, reject) => {\n const extraSignals: AbortSignal[] = [];\n\n if (roomOpts?.signal) extraSignals.push(roomOpts.signal);\n\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n if (roomOpts?.timeout !== undefined) {\n const tc = new AbortController();\n\n timeoutId = setTimeout(() => tc.abort(new PulseTimeoutError(frameType)), roomOpts.timeout);\n extraSignals.push(tc.signal);\n }\n\n const sig =\n extraSignals.length === 0 ? disposalCtrl.signal : combineSignals(disposalCtrl.signal, ...extraSignals);\n\n if (sig.aborted) {\n clearTimeout(timeoutId);\n reject(sig.reason instanceof PulseTimeoutError ? sig.reason : new PulseAbortError());\n\n return;\n }\n\n let onAbort: () => void = () => {};\n\n const onConfirm = (): void => {\n clearTimeout(timeoutId);\n sig.removeEventListener('abort', onAbort);\n resolve();\n };\n\n onAbort = (): void => {\n clearTimeout(timeoutId);\n\n const set = pending.get(room);\n\n set?.delete(onConfirm);\n\n if (set?.size === 0) pending.delete(room);\n\n reject(sig.reason instanceof PulseTimeoutError ? sig.reason : new PulseAbortError());\n };\n\n let pendingSet = pending.get(room);\n const shouldSendFrame = !pendingSet;\n\n if (!pendingSet) {\n pendingSet = new Set();\n pending.set(room, pendingSet);\n }\n\n pendingSet.add(onConfirm);\n sig.addEventListener('abort', onAbort, { once: true });\n\n const sendFrame = (): void => rawSend(encode({ room, type: frameType }));\n\n if (!shouldSendFrame) return;\n\n if (ws?.readyState === WebSocket.OPEN) {\n sendFrame();\n } else {\n pulse\n .connect()\n .then(sendFrame)\n .catch((err: unknown) => {\n clearTimeout(timeoutId);\n sig.removeEventListener('abort', onAbort);\n\n const set = pending.get(room);\n\n set?.delete(onConfirm);\n\n if (set?.size === 0) pending.delete(room);\n\n reject(err);\n });\n }\n });\n }\n\n // ── Public API ─────────────────────────────────────────────────────────────\n\n const pulse: Pulse<TServer, TClient> = {\n channel<TChServer extends MessageMap = TServer, TChClient extends MessageMap = TClient>(\n name: string,\n ): PulseChannel<TChServer, TChClient> {\n const cached = channelCache.get(name);\n\n if (cached) return cached as PulseChannel<TChServer, TChClient>;\n\n if (disposed) {\n warn(`channel('${name}') called on a disposed Pulse — returning an already-disposed channel`);\n }\n\n const ch = createChannel<TChServer, TChClient>(\n name,\n (chan, event, payload) => {\n runMiddleware(event, payload, () => {\n rawSend(encode({ channel: chan, event, payload, type: 'message' }));\n });\n },\n (chan, event, handler) => listeners.add(chan, event, handler),\n disposalCtrl.signal,\n () => {\n channelCache.delete(name);\n rawSend(encode({ channel: name, type: 'unsubscribe' }));\n },\n );\n\n if (!disposed) channelCache.set(name, ch as PulseChannel<MessageMap, MessageMap>);\n\n if (ws?.readyState === WebSocket.OPEN) {\n ws.send(encode({ channel: name, type: 'subscribe' }));\n }\n\n return ch;\n },\n\n connect(): Promise<void> {\n if (disposed) return Promise.reject(new PulseDisposedError());\n\n if (ws?.readyState === WebSocket.OPEN) return Promise.resolve();\n\n return new Promise((resolve, reject) => {\n pendingOpens.push({ reject, resolve });\n\n if (!ws || ws.readyState > WebSocket.OPEN) openSocket();\n });\n },\n\n disconnect(code = 1000, reason = ''): void {\n intentionalClose = true;\n ws?.close(code, reason);\n },\n\n get disposalSignal() {\n return disposalCtrl.signal;\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n intentionalClose = true;\n heartbeat.stop();\n ws?.close(1000, 'disposed');\n ws = null;\n\n const pending = pendingOpens.splice(0);\n\n for (const p of pending) p.reject(new PulseDisposedError());\n\n channelCache.clear();\n presenceCache.clear();\n pendingJoins.clear();\n pendingLeaves.clear();\n listeners.clear();\n disposalCtrl.abort();\n },\n\n get disposed() {\n return disposed;\n },\n\n join(room: string, roomOpts?: { signal?: AbortSignal; timeout?: number }): Promise<void> {\n return awaitRoomConfirmation(pendingJoins, room, 'join', roomOpts);\n },\n\n leave(room: string, roomOpts?: { signal?: AbortSignal; timeout?: number }): Promise<void> {\n return awaitRoomConfirmation(pendingLeaves, room, 'leave', roomOpts);\n },\n\n on<K extends EventKey<TServer>>(event: K, handler: (payload: TServer[K]) => void): Unsubscribe {\n if (disposed) {\n warn(`on('${String(event)}') called on a disposed Pulse — listener ignored`);\n\n return () => {};\n }\n\n return listeners.add(null, event, handler as Handler);\n },\n\n once<K extends EventKey<TServer>>(event: K, handler: (payload: TServer[K]) => void): Unsubscribe {\n if (disposed) {\n warn(`once('${String(event)}') called on a disposed Pulse — listener ignored`);\n\n return () => {};\n }\n\n let unsub: Unsubscribe = () => {};\n\n const wrapped: Handler = (payload) => {\n unsub();\n (handler as Handler)(payload);\n };\n\n unsub = listeners.add(null, event, wrapped);\n\n return unsub;\n },\n\n presence<T>(room: string): PresenceChannel<T> {\n const cached = presenceCache.get(room);\n\n if (cached) return cached as PresenceChannel<T>;\n\n const ch = createPresence<T>(\n room,\n rawSend,\n {\n onJoin: (handler) => listeners.add(null, 'presence_join', (p) => handler(p as InPresenceJoinFrame)),\n onLeave: (handler) => listeners.add(null, 'presence_leave', (p) => handler(p as InPresenceLeaveFrame)),\n onState: (handler) => listeners.add(null, 'presence_state', (p) => handler(p as InPresenceStateFrame)),\n },\n disposalCtrl.signal,\n () => {\n presenceCache.delete(room);\n },\n );\n\n if (!disposed) {\n presenceCache.set(room, ch as PresenceChannel<unknown>);\n }\n\n if (disposed) {\n warn(`presence('${room}') called on a disposed Pulse — returning an already-disposed presence channel`);\n } else {\n pulse.join(room).catch((err: unknown) => {\n if (err instanceof PulseAbortError || err instanceof PulseDisposedError) return;\n\n warn(`presence() join failed for room '${room}': ${String(err)}`);\n });\n }\n\n return ch;\n },\n\n get rooms() {\n return rooms;\n },\n\n send<K extends EventKey<TClient>>(event: K, payload: TClient[K]): void {\n if (disposed) {\n warn(`send('${String(event)}') called on a disposed Pulse — message dropped`);\n\n return;\n }\n\n runMiddleware(event, payload, () => {\n rawSend(encode({ event, payload, type: 'message' }));\n });\n },\n\n get status() {\n return status;\n },\n\n [Symbol.dispose]() {\n this.dispose();\n },\n\n wait<K extends EventKey<TServer>>(\n event: K,\n waitOpts?: { signal?: AbortSignal; timeout?: number },\n ): Promise<TServer[K]> {\n return createWaitPromise<TServer[K]>(event as string, disposalCtrl.signal, waitOpts, (ev, handler) =>\n listeners.add(null, ev, handler),\n );\n },\n };\n\n if (!opts.lazy) openSocket();\n\n return pulse;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pulse.js","names":[],"sources":["../src/errors.ts","../src/_utils.ts","../src/_connection.ts","../src/_wait.ts","../src/channel.ts","../src/protocol.ts","../src/heartbeat.ts","../src/presence.ts","../src/pulse.ts"],"sourcesContent":["/**\n * Base class for all pulse errors.\n * Use `instanceof PulseError` to catch any pulse-originated error in one branch.\n */\nexport class PulseError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is PulseError {\n return err instanceof PulseError;\n }\n}\n\n/**\n * Thrown when a WebSocket connection cannot be established or is lost\n * without triggering auto-reconnect (budget exhausted, or reconnect disabled).\n */\nexport class PulseConnectionError extends PulseError {\n readonly url: string;\n\n constructor(message: string, url: string, opts?: ErrorOptions) {\n super(message, opts);\n this.url = url;\n }\n}\n\n/**\n * Thrown when `wait()` or `join()`/`leave()` times out before receiving a\n * server response, or when an AbortSignal fires before the event arrives.\n */\nexport class PulseTimeoutError extends PulseError {\n readonly event: string;\n\n constructor(event: string, opts?: ErrorOptions) {\n super(`Timed out waiting for \"${event}\"`, opts);\n this.event = event;\n }\n}\n\n/**\n * Thrown when `wait()` is aborted via an AbortSignal before the event fires.\n */\nexport class PulseAbortError extends PulseError {\n constructor(opts?: ErrorOptions) {\n super('Aborted', opts);\n }\n}\n\n/**\n * Thrown when a method is called on a disposed Pulse instance or channel.\n */\nexport class PulseDisposedError extends PulseError {\n constructor(target = 'Pulse', opts?: ErrorOptions) {\n super(`${target} instance is disposed`, opts);\n }\n}\n\n/**\n * Thrown when the server sends a frame that cannot be parsed or violates\n * the wire protocol schema.\n */\nexport class PulseProtocolError extends PulseError {\n readonly raw: unknown;\n\n constructor(message: string, raw?: unknown, opts?: ErrorOptions) {\n super(message, opts);\n this.raw = raw;\n }\n}\n","/**\n * Returns a signal that aborts as soon as any of the provided signals abort.\n * With a single argument, returns it directly (no allocation).\n * Cleanup listeners are registered to prevent leaks.\n *\n * @internal\n */\nexport function combineSignals(first: AbortSignal, ...rest: AbortSignal[]): AbortSignal {\n if (rest.length === 0) return first;\n\n return rest.reduce(mergeTwo, first);\n}\n\nfunction mergeTwo(a: AbortSignal, b: AbortSignal): AbortSignal {\n if (a.aborted) return a;\n\n if (b.aborted) return b;\n\n const ctrl = new AbortController();\n const onA = (): void => ctrl.abort(a.reason);\n const onB = (): void => ctrl.abort(b.reason);\n\n a.addEventListener('abort', onA, { once: true });\n b.addEventListener('abort', onB, { once: true });\n ctrl.signal.addEventListener(\n 'abort',\n () => {\n a.removeEventListener('abort', onA);\n b.removeEventListener('abort', onB);\n },\n { once: true },\n );\n\n return ctrl.signal;\n}\n\n/**\n * Create a child `AbortController` that aborts as soon as `parent` does.\n * Handles the case where `parent` is already aborted at call time — a plain\n * `addEventListener('abort', ...)` would miss that, since the event already fired.\n *\n * @internal\n */\nexport function deriveAbortController(parent: AbortSignal): AbortController {\n const ctrl = new AbortController();\n\n if (parent.aborted) {\n ctrl.abort(parent.reason);\n } else {\n parent.addEventListener('abort', () => ctrl.abort(parent.reason), { once: true });\n }\n\n return ctrl;\n}\n\n/**\n * Sleep for `ms` milliseconds, aborting early when the signal fires.\n * Resolves (does not reject) on abort — callers check `signal.aborted`.\n *\n * @internal\n */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal?.aborted) {\n resolve();\n\n return;\n }\n\n const id = setTimeout(resolve, ms);\n\n signal?.addEventListener(\n 'abort',\n () => {\n clearTimeout(id);\n resolve();\n },\n { once: true },\n );\n });\n}\n\n/**\n * Full-jitter exponential backoff in ms, capped at `maxMs`.\n * `attempt` is zero-based.\n *\n * @internal\n */\nexport function defaultReconnectDelay(attempt: number, maxMs = 30_000): number {\n const base = Math.min(1000 * Math.pow(2, attempt), maxMs);\n\n return Math.random() * base;\n}\n","import type { PulseStatus, ReconnectOptions } from './types';\n\nimport { defaultReconnectDelay, sleep } from './_utils';\nimport { PulseConnectionError, PulseDisposedError } from './errors';\n\ntype ConnectionCallbacks = {\n onClose(): void;\n onError(error: PulseConnectionError): void;\n onMessage(event: MessageEvent): void;\n onOpen(): void;\n onStatus(status: PulseStatus): void;\n};\n\nexport type ConnectionController = {\n connect(): Promise<void>;\n disconnect(code?: number, reason?: string): void;\n dispose(): void;\n forceReconnect(code: number, reason: string): void;\n readonly open: boolean;\n send(frame: string): void;\n};\n\ntype Opening = {\n promise: Promise<void>;\n reject(error: PulseConnectionError): void;\n resolve(): void;\n socket: WebSocket;\n};\n\nconst DEFAULT_MAX_ATTEMPTS = 5;\n\n/** Owns the one active socket and its retry loop. @internal */\nexport function createConnection(\n url: string,\n protocols: string | string[] | undefined,\n reconnect: boolean | ReconnectOptions | undefined,\n callbacks: ConnectionCallbacks,\n): ConnectionController {\n const options: ReconnectOptions | undefined = reconnect === true ? {} : reconnect || undefined;\n const maxAttempts = options ? (options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS) : 0;\n const configuredDelay = options?.delay;\n const delay: (attempt: number) => number =\n typeof configuredDelay === 'function'\n ? configuredDelay\n : typeof configuredDelay === 'number'\n ? () => configuredDelay\n : defaultReconnectDelay;\n\n let disposed = false;\n let desired = false;\n let retrying = false;\n let retryCtrl: AbortController | undefined;\n let socket: WebSocket | undefined;\n let opening: Opening | undefined;\n const waiters = new Set<{ reject(error: Error): void; resolve(): void }>();\n\n function setStatus(status: PulseStatus): void {\n callbacks.onStatus(status);\n }\n\n function rejectWaiters(error: Error): void {\n for (const waiter of waiters) waiter.reject(error);\n waiters.clear();\n }\n\n function resolveWaiters(): void {\n for (const waiter of waiters) waiter.resolve();\n waiters.clear();\n }\n\n function rejectOpening(socketToReject: WebSocket, error: PulseConnectionError): void {\n if (opening?.socket !== socketToReject) return;\n\n opening.reject(error);\n opening = undefined;\n }\n\n function openSocket(): Promise<void> {\n if (socket?.readyState === WebSocket.OPEN) return Promise.resolve();\n\n if (opening) return opening.promise;\n\n const nextSocket = new WebSocket(url, protocols);\n\n socket = nextSocket;\n\n let resolveOpening: () => void;\n let rejectOpeningPromise: (error: PulseConnectionError) => void;\n const promise = new Promise<void>((resolve, reject) => {\n resolveOpening = resolve;\n rejectOpeningPromise = reject;\n });\n\n opening = { promise, reject: rejectOpeningPromise!, resolve: resolveOpening!, socket: nextSocket };\n\n nextSocket.onopen = (): void => {\n if (socket !== nextSocket || !desired) {\n nextSocket.close(1000, 'stale connection');\n\n return;\n }\n\n if (opening?.socket === nextSocket) {\n opening.resolve();\n opening = undefined;\n }\n\n setStatus('open');\n callbacks.onOpen();\n resolveWaiters();\n };\n\n nextSocket.onmessage = (event: MessageEvent): void => {\n if (socket === nextSocket) callbacks.onMessage(event);\n };\n\n nextSocket.onerror = (): void => {\n if (socket !== nextSocket) return;\n\n const error = new PulseConnectionError('WebSocket error', url);\n\n callbacks.onError(error);\n rejectOpening(nextSocket, error);\n rejectWaiters(error);\n };\n\n nextSocket.onclose = (): void => {\n if (socket !== nextSocket) return;\n\n socket = undefined;\n callbacks.onClose();\n\n const error = new PulseConnectionError('Connection closed', url);\n\n rejectOpening(nextSocket, error);\n\n if (!desired || disposed) {\n setStatus('closed');\n\n return;\n }\n\n if (!retrying) void reconnectLoop();\n };\n\n return promise;\n }\n\n async function reconnectLoop(): Promise<void> {\n if (retrying || disposed || !desired) return;\n\n retrying = true;\n\n const controller = new AbortController();\n\n retryCtrl = controller;\n setStatus('reconnecting');\n\n let lastError: PulseConnectionError | undefined;\n\n for (let attempt = 0; attempt < maxAttempts && desired && !disposed; attempt++) {\n await sleep(delay(attempt), controller.signal);\n\n if (controller.signal.aborted || !desired || disposed) break;\n\n try {\n await openSocket();\n retrying = false;\n\n return;\n } catch (error) {\n lastError = error instanceof PulseConnectionError ? error : new PulseConnectionError('Reconnect failed', url);\n }\n }\n\n if (!controller.signal.aborted && desired && !disposed) {\n desired = false;\n setStatus('closed');\n callbacks.onError(lastError ?? new PulseConnectionError('Reconnect budget exhausted', url));\n rejectWaiters(lastError ?? new PulseConnectionError('Reconnect budget exhausted', url));\n }\n\n if (retryCtrl === controller) retryCtrl = undefined;\n\n retrying = false;\n }\n\n function cancelRetry(): void {\n retryCtrl?.abort();\n retryCtrl = undefined;\n retrying = false;\n }\n\n return {\n connect(): Promise<void> {\n if (disposed) return Promise.reject(new PulseDisposedError());\n\n desired = true;\n\n if (socket?.readyState === WebSocket.OPEN) return Promise.resolve();\n\n const connected = new Promise<void>((resolve, reject) => {\n waiters.add({ reject, resolve });\n });\n\n if (!opening && !retrying) {\n setStatus('connecting');\n void openSocket().catch(() => {});\n }\n\n return connected;\n },\n\n disconnect(code = 1000, reason = ''): void {\n desired = false;\n cancelRetry();\n rejectWaiters(new PulseConnectionError('Connection closed', url));\n setStatus('closed');\n socket?.close(code, reason);\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n desired = false;\n cancelRetry();\n rejectWaiters(new PulseDisposedError());\n socket?.close(1000, 'disposed');\n socket = undefined;\n opening = undefined;\n setStatus('closed');\n },\n\n forceReconnect(code: number, reason: string): void {\n if (desired && socket?.readyState === WebSocket.OPEN) socket.close(code, reason);\n },\n\n get open() {\n return socket?.readyState === WebSocket.OPEN;\n },\n\n send(frame: string): void {\n if (socket?.readyState !== WebSocket.OPEN) {\n throw new PulseConnectionError('Connection is not open', url);\n }\n\n socket.send(frame);\n },\n };\n}\n","import type { Unsubscribe } from './types';\n\nimport { combineSignals } from './_utils';\nimport { PulseAbortError, PulseTimeoutError } from './errors';\n\n/**\n * Shared implementation for `pulse.wait()` and `channel.wait()`.\n * Resolves on the next event dispatched via `subscribe`, or rejects when\n * the combined signal aborts or the optional timeout elapses.\n *\n * @internal\n */\nexport function createWaitPromise<T>(\n event: string,\n baseSignal: AbortSignal,\n opts: { signal?: AbortSignal; timeout?: number } | undefined,\n subscribe: (event: string, handler: (payload: unknown) => void) => Unsubscribe,\n): Promise<T> {\n return new Promise((resolve, reject) => {\n const signals: AbortSignal[] = [baseSignal];\n\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n if (opts?.signal) signals.push(opts.signal);\n\n if (opts?.timeout !== undefined) {\n const timeoutCtrl = new AbortController();\n\n timeoutId = setTimeout(() => {\n timeoutCtrl.abort(new PulseTimeoutError(event));\n }, opts.timeout);\n signals.push(timeoutCtrl.signal);\n }\n\n const combined = signals.length === 1 ? signals[0]! : combineSignals(signals[0]!, ...signals.slice(1));\n\n if (combined.aborted) {\n clearTimeout(timeoutId);\n reject(combined.reason instanceof PulseTimeoutError ? combined.reason : new PulseAbortError());\n\n return;\n }\n\n let unsub: Unsubscribe = () => {};\n\n const onAbort = (): void => {\n clearTimeout(timeoutId);\n unsub();\n reject(combined.reason instanceof PulseTimeoutError ? combined.reason : new PulseAbortError());\n };\n\n combined.addEventListener('abort', onAbort, { once: true });\n\n unsub = subscribe(event, (payload) => {\n clearTimeout(timeoutId);\n combined.removeEventListener('abort', onAbort);\n resolve(payload as T);\n });\n });\n}\n","import type { EventKey, MessageMap, PulseChannel, Unsubscribe } from './types';\n\nimport { deriveAbortController } from './_utils';\nimport { createWaitPromise } from './_wait';\nimport { PulseDisposedError } from './errors';\n\ntype SendFn = (channel: string, event: string, payload: unknown) => void;\ntype SubscribeFn = (channel: string, event: string, handler: (payload: unknown) => void) => () => void;\n\n/**\n * Create a typed channel object that scopes messaging to a named namespace.\n * @internal\n */\nexport function createChannel<TServer extends MessageMap, TClient extends MessageMap>(\n name: string,\n send: SendFn,\n subscribe: SubscribeFn,\n disposalSignal: AbortSignal,\n onDispose?: () => void,\n): PulseChannel<TServer, TClient> {\n const ctrl = deriveAbortController(disposalSignal);\n const tracked = new Set<() => void>();\n\n let disposed = ctrl.signal.aborted;\n\n ctrl.signal.addEventListener(\n 'abort',\n () => {\n disposed = true;\n },\n { once: true },\n );\n\n const channel: PulseChannel<TServer, TClient> = {\n get disposalSignal() {\n return ctrl.signal;\n },\n\n dispose() {\n if (disposed) return;\n\n disposed = true;\n ctrl.abort();\n\n for (const unsub of tracked) unsub();\n\n tracked.clear();\n onDispose?.();\n },\n\n get disposed() {\n return disposed;\n },\n\n get name() {\n return name;\n },\n\n on<K extends EventKey<TServer>>(event: K, handler: (payload: TServer[K]) => void): Unsubscribe {\n if (disposed) {\n throw new PulseDisposedError(`Channel \"${name}\"`);\n }\n\n const unsub = subscribe(name, event, handler as (payload: unknown) => void);\n\n tracked.add(unsub);\n\n return () => {\n unsub();\n tracked.delete(unsub);\n };\n },\n\n once<K extends EventKey<TServer>>(event: K, handler: (payload: TServer[K]) => void): Unsubscribe {\n if (disposed) {\n throw new PulseDisposedError(`Channel \"${name}\"`);\n }\n\n let unsub: Unsubscribe = () => {};\n\n const wrapped = (payload: unknown): void => {\n tracked.delete(unsub);\n unsub();\n (handler as (payload: unknown) => void)(payload);\n };\n\n unsub = subscribe(name, event, wrapped);\n tracked.add(unsub);\n\n return () => {\n tracked.delete(unsub);\n unsub();\n };\n },\n\n send<K extends EventKey<TClient>>(event: K, payload: TClient[K]): void {\n if (disposed) {\n throw new PulseDisposedError(`Channel \"${name}\"`);\n }\n\n send(name, event, payload);\n },\n\n [Symbol.dispose]() {\n this.dispose();\n },\n\n wait<K extends EventKey<TServer>>(\n event: K,\n opts?: { signal?: AbortSignal; timeout?: number },\n ): Promise<TServer[K]> {\n return createWaitPromise<TServer[K]>(event as string, ctrl.signal, opts, (ev, handler) =>\n subscribe(name, ev, handler),\n );\n },\n };\n\n return channel;\n}\n","import { PulseProtocolError } from './errors';\n\n// ─── Outgoing frames (client → server) ────────────────────────────────────────\n\nexport type OutMessageFrame = { channel?: string; event: string; payload: unknown; type: 'message' };\nexport type OutSubscribeFrame = { channel: string; type: 'subscribe' };\nexport type OutUnsubscribeFrame = { channel: string; type: 'unsubscribe' };\nexport type OutJoinFrame = { room: string; type: 'join' };\nexport type OutLeaveFrame = { room: string; type: 'leave' };\nexport type OutPresenceFrame = { room: string; state: unknown; type: 'presence' };\nexport type OutPingFrame = { ts: number; type: 'ping' };\n\nexport type OutFrame =\n | OutJoinFrame\n | OutLeaveFrame\n | OutMessageFrame\n | OutPingFrame\n | OutPresenceFrame\n | OutSubscribeFrame\n | OutUnsubscribeFrame;\n\n// ─── Incoming frames (server → client) ────────────────────────────────────────\n\nexport type InMessageFrame = { channel?: string; event: string; payload: unknown; type: 'message' };\nexport type InSubscribedFrame = { channel: string; type: 'subscribed' };\nexport type InUnsubscribedFrame = { channel: string; type: 'unsubscribed' };\nexport type InJoinedFrame = { room: string; type: 'joined' };\nexport type InLeftFrame = { room: string; type: 'left' };\nexport type InPresenceStateFrame = { members: Record<string, unknown>; room: string; type: 'presence_state' };\nexport type InPresenceJoinFrame = { id: string; room: string; state: unknown; type: 'presence_join' };\nexport type InPresenceLeaveFrame = { id: string; room: string; type: 'presence_leave' };\nexport type InPongFrame = { ts: number; type: 'pong' };\nexport type InErrorFrame = { code: string; message: string; type: 'error' };\n\nexport type InFrame =\n | InErrorFrame\n | InJoinedFrame\n | InLeftFrame\n | InMessageFrame\n | InPongFrame\n | InPresenceJoinFrame\n | InPresenceLeaveFrame\n | InPresenceStateFrame\n | InSubscribedFrame\n | InUnsubscribedFrame;\n\ntype ParsedFrame = { type: string } & Record<string, unknown>;\nexport type DecodedInFrame = { frame: InFrame; kind: 'known' } | { kind: 'unknown'; type: string };\n\n// ─── Codec ────────────────────────────────────────────────────────────────────\n\n/**\n * Encode an outgoing frame to a JSON string.\n * @internal\n */\nexport function encode(frame: OutFrame): string {\n return JSON.stringify(frame);\n}\n\n/**\n * Parse a raw MessageEvent data string and ensure it has a string `type` field.\n * Throws `PulseProtocolError` if the data is not valid JSON or missing the `type` field.\n * @internal\n */\nexport function decode(raw: unknown): ParsedFrame {\n if (typeof raw !== 'string') {\n throw new PulseProtocolError('Expected string message data', raw);\n }\n\n let parsed: unknown;\n\n try {\n parsed = JSON.parse(raw);\n } catch (cause) {\n throw new PulseProtocolError('JSON parse error', raw);\n }\n\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n !('type' in parsed) ||\n typeof (parsed as { type: unknown }).type !== 'string'\n ) {\n throw new PulseProtocolError('Missing or invalid \"type\" field', parsed);\n }\n\n return parsed as ParsedFrame;\n}\n\nfunction isObjectRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction getStringProp(frame: unknown, key: string): string | null {\n if (!isObjectRecord(frame)) return null;\n\n const value = frame[key];\n\n return typeof value === 'string' ? value : null;\n}\n\nexport function isErrorFrame(frame: unknown): frame is InErrorFrame {\n return getStringProp(frame, 'code') !== null && getStringProp(frame, 'message') !== null;\n}\n\nexport function isJoinedFrame(frame: unknown): frame is InJoinedFrame {\n return getStringProp(frame, 'room') !== null;\n}\n\nexport function isLeftFrame(frame: unknown): frame is InLeftFrame {\n return getStringProp(frame, 'room') !== null;\n}\n\nexport function isMessageFrame(frame: unknown): frame is InMessageFrame {\n if (!isObjectRecord(frame) || getStringProp(frame, 'event') === null || !('payload' in frame)) return false;\n\n return frame.channel === undefined || typeof frame.channel === 'string';\n}\n\nexport function isPongFrame(frame: unknown): frame is InPongFrame {\n return isObjectRecord(frame) && typeof frame.ts === 'number';\n}\n\nexport function isPresenceJoinFrame(frame: unknown): frame is InPresenceJoinFrame {\n return (\n isObjectRecord(frame) &&\n getStringProp(frame, 'id') !== null &&\n getStringProp(frame, 'room') !== null &&\n 'state' in frame\n );\n}\n\nexport function isPresenceLeaveFrame(frame: unknown): frame is InPresenceLeaveFrame {\n return getStringProp(frame, 'id') !== null && getStringProp(frame, 'room') !== null;\n}\n\nexport function isPresenceStateFrame(frame: unknown): frame is InPresenceStateFrame {\n return isObjectRecord(frame) && getStringProp(frame, 'room') !== null && isObjectRecord(frame.members);\n}\n\nfunction isSubscribedFrame(frame: unknown): frame is InSubscribedFrame {\n return getStringProp(frame, 'channel') !== null;\n}\n\nfunction isUnsubscribedFrame(frame: unknown): frame is InUnsubscribedFrame {\n return getStringProp(frame, 'channel') !== null;\n}\n\n/**\n * Parse and validate known incoming frame shapes.\n * Unknown frame `type` values are passed through so callers can warn/drop them.\n * @internal\n */\nexport function decodeValidated(raw: unknown): DecodedInFrame {\n const frame = decode(raw) as ParsedFrame;\n\n switch (frame.type) {\n case 'error':\n if (isErrorFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'joined':\n if (isJoinedFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'left':\n if (isLeftFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'message':\n if (isMessageFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'pong':\n if (isPongFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'presence_join':\n if (isPresenceJoinFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'presence_leave':\n if (isPresenceLeaveFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'presence_state':\n if (isPresenceStateFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'subscribed':\n if (isSubscribedFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'unsubscribed':\n if (isUnsubscribedFrame(frame)) return { frame, kind: 'known' };\n\n break;\n default:\n return { kind: 'unknown', type: frame.type };\n }\n\n throw new PulseProtocolError(`Malformed \"${frame.type}\" frame`, frame);\n}\n","import type { HeartbeatOptions } from './types';\n\nimport { encode } from './protocol';\n\nconst DEFAULT_INTERVAL = 30_000;\nconst DEFAULT_TIMEOUT = 5_000;\n\nexport type HeartbeatHandle = {\n /** Called when a `pong` frame is received. Resets the timeout timer. */\n onPong(): void;\n /** Start the heartbeat loop. */\n start(): void;\n /** Stop the heartbeat loop and cancel any pending timers. */\n stop(): void;\n};\n\n/**\n * Create a heartbeat manager that sends periodic pings and expects pong replies.\n * Calls `onDead` when a pong is not received within the timeout window.\n * @internal\n */\nexport function createHeartbeat(\n opts: boolean | HeartbeatOptions | undefined,\n send: (frame: string) => void,\n onDead: () => void,\n): HeartbeatHandle {\n if (!opts) {\n return { onPong() {}, start() {}, stop() {} };\n }\n\n const options: HeartbeatOptions = opts === true ? {} : opts;\n const interval = options.interval ?? DEFAULT_INTERVAL;\n const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n\n let pingTimer: ReturnType<typeof setTimeout> | undefined;\n let pongTimer: ReturnType<typeof setTimeout> | undefined;\n let running = false;\n\n function schedulePing(): void {\n pingTimer = setTimeout(() => {\n const ts = Date.now();\n\n send(encode({ ts, type: 'ping' }));\n\n pongTimer = setTimeout(() => {\n onDead();\n }, timeout);\n }, interval);\n }\n\n return {\n onPong() {\n clearTimeout(pongTimer);\n pongTimer = undefined;\n\n if (running) schedulePing();\n },\n\n start() {\n running = true;\n clearTimeout(pingTimer);\n clearTimeout(pongTimer);\n pongTimer = undefined;\n schedulePing();\n },\n\n stop() {\n running = false;\n clearTimeout(pingTimer);\n clearTimeout(pongTimer);\n pingTimer = undefined;\n pongTimer = undefined;\n },\n };\n}\n","import { type Signal, signal } from '@vielzeug/ripple';\n\nimport type { PresenceChannel, Unsubscribe } from './types';\n\nimport { deriveAbortController } from './_utils';\nimport { PulseDisposedError } from './errors';\nimport { type InPresenceJoinFrame, type InPresenceLeaveFrame, type InPresenceStateFrame } from './protocol';\n\ntype UpdateFn<T> = (room: string, state: T) => void;\n\nexport type PresenceEvents = {\n onJoin(handler: (frame: InPresenceJoinFrame) => void): Unsubscribe;\n onLeave(handler: (frame: InPresenceLeaveFrame) => void): Unsubscribe;\n onState(handler: (frame: InPresenceStateFrame) => void): Unsubscribe;\n};\n\n/**\n * Create a presence channel that reactively tracks room member states.\n * @internal\n */\nexport function createPresence<T>(\n room: string,\n update: UpdateFn<T>,\n events: PresenceEvents,\n disposalSignal: AbortSignal,\n registerReset?: (reset: () => void) => void,\n onDispose?: () => void,\n): PresenceChannel<T> {\n const ctrl = deriveAbortController(disposalSignal);\n\n let disposed = ctrl.signal.aborted;\n\n ctrl.signal.addEventListener(\n 'abort',\n () => {\n disposed = true;\n },\n { once: true },\n );\n\n const members: Signal<Map<string, T>> = signal(new Map<string, T>());\n\n registerReset?.(() => {\n members.value = new Map<string, T>();\n });\n\n const joinHandlers = new Set<(memberId: string, state: T) => void>();\n const leaveHandlers = new Set<(memberId: string) => void>();\n\n const unsubs: Array<() => void> = [\n events.onState((frame) => {\n if (frame.room !== room) return;\n\n const next = new Map<string, T>();\n\n for (const [id, state] of Object.entries(frame.members)) {\n next.set(id, state as T);\n }\n\n members.value = next;\n }),\n events.onJoin((frame) => {\n if (frame.room !== room) return;\n\n const next = new Map(members.value);\n\n next.set(frame.id, frame.state as T);\n members.value = next;\n\n for (const handler of joinHandlers) {\n handler(frame.id, frame.state as T);\n }\n }),\n events.onLeave((frame) => {\n if (frame.room !== room) return;\n\n const next = new Map(members.value);\n\n next.delete(frame.id);\n members.value = next;\n\n for (const handler of leaveHandlers) {\n handler(frame.id);\n }\n }),\n ];\n\n const presence: PresenceChannel<T> = {\n get disposalSignal() {\n return ctrl.signal;\n },\n\n dispose() {\n if (disposed) return;\n\n disposed = true;\n ctrl.abort();\n\n for (const unsub of unsubs) unsub();\n\n unsubs.length = 0;\n joinHandlers.clear();\n leaveHandlers.clear();\n onDispose?.();\n },\n\n get disposed() {\n return disposed;\n },\n\n onJoin(handler: (memberId: string, state: T) => void): Unsubscribe {\n if (disposed) {\n throw new PulseDisposedError(`Presence \"${room}\"`);\n }\n\n joinHandlers.add(handler);\n\n return () => joinHandlers.delete(handler);\n },\n\n onLeave(handler: (memberId: string) => void): Unsubscribe {\n if (disposed) {\n throw new PulseDisposedError(`Presence \"${room}\"`);\n }\n\n leaveHandlers.add(handler);\n\n return () => leaveHandlers.delete(handler);\n },\n\n get room() {\n return room;\n },\n\n get state() {\n return members;\n },\n\n [Symbol.dispose]() {\n this.dispose();\n },\n\n update(state: T): void {\n if (disposed) throw new PulseDisposedError(`Presence \"${room}\"`);\n\n update(room, state);\n },\n };\n\n return presence;\n}\n","import { signal } from '@vielzeug/ripple';\n\nimport type {\n ChannelDefinitions,\n EventKey,\n MessageMap,\n PresenceChannel,\n PresenceDefinitions,\n Pulse,\n PulseChannel,\n PulseOptions,\n PulseStatus,\n Unsubscribe,\n} from './types';\n\nimport { createConnection } from './_connection';\nimport { createWaitPromise } from './_wait';\nimport { createChannel } from './channel';\nimport {\n PulseAbortError,\n PulseConnectionError,\n PulseDisposedError,\n PulseError,\n PulseProtocolError,\n PulseTimeoutError,\n} from './errors';\nimport { createHeartbeat, type HeartbeatHandle } from './heartbeat';\nimport { createPresence } from './presence';\nimport {\n type DecodedInFrame,\n type InPresenceJoinFrame,\n type InPresenceLeaveFrame,\n type InPresenceStateFrame,\n decodeValidated,\n encode,\n} from './protocol';\n\ntype Handler = (payload: unknown) => void;\ntype RoomOperation = 'join' | 'leave';\ntype RoomWaiter = { reject(error: PulseError): void; resolve(): void };\ntype PendingRoom = { callbacks: Set<RoomWaiter>; next?: PendingRoom; operation: RoomOperation };\n\nclass ListenerMap {\n private readonly map = new Map<string | null, Map<string, Set<Handler>>>();\n\n add(channel: string | null, event: string, handler: Handler): Unsubscribe {\n let events = this.map.get(channel);\n\n if (!events) {\n events = new Map();\n this.map.set(channel, events);\n }\n\n let handlers = events.get(event);\n\n if (!handlers) {\n handlers = new Set();\n events.set(event, handlers);\n }\n\n handlers.add(handler);\n\n return () => {\n handlers.delete(handler);\n\n if (handlers.size === 0) events.delete(event);\n\n if (events.size === 0) this.map.delete(channel);\n };\n }\n\n clear(): void {\n this.map.clear();\n }\n\n dispatch(channel: string | null, event: string, payload: unknown): void {\n for (const handler of this.map.get(channel)?.get(event) ?? []) handler(payload);\n }\n}\n\n/**\n * Create a typed, explicitly connected WebSocket client with scoped channels,\n * rooms, presence, reconnect, and heartbeat support.\n */\nexport function createPulse<\n TServer extends MessageMap = MessageMap,\n TClient extends MessageMap = MessageMap,\n TChannels extends ChannelDefinitions = ChannelDefinitions,\n TPresence extends PresenceDefinitions = PresenceDefinitions,\n>(url: string, options: PulseOptions = {}): Pulse<TServer, TClient, TChannels, TPresence> {\n const disposalCtrl = new AbortController();\n const status = signal<PulseStatus>('closed');\n const rooms = signal<ReadonlySet<string>>(new Set());\n const listeners = new ListenerMap();\n const channelReferences = new Map<string, number>();\n const presenceReferences = new Map<string, number>();\n const manualRooms = new Set<string>();\n const localPresence = new Map<string, unknown>();\n const presenceResetters = new Map<string, Set<() => void>>();\n const pendingRooms = new Map<string, PendingRoom>();\n\n let disposed = false;\n\n function report(error: PulseError): void {\n options.onError?.(error);\n }\n\n function sendInternal(frame: string): void {\n connection.send(frame);\n }\n\n function desiredRoom(room: string): boolean {\n return manualRooms.has(room) || (presenceReferences.get(room) ?? 0) > 0;\n }\n\n function setRoom(room: string, joined: boolean): void {\n const next = new Set(rooms.value);\n\n if (joined) next.add(room);\n else next.delete(room);\n\n rooms.value = next;\n }\n\n function resetRemoteSession(): void {\n rooms.value = new Set();\n\n for (const resetters of presenceResetters.values()) {\n for (const reset of resetters) reset();\n }\n }\n\n function rejectPendingRooms(error: PulseError, reportFailure = true): void {\n let rejected = false;\n\n for (const [room, firstPending] of pendingRooms) {\n let pending: PendingRoom | undefined = firstPending;\n\n while (pending) {\n if (pending.operation === 'join') manualRooms.delete(room);\n\n for (const waiter of pending.callbacks) {\n waiter.reject(error);\n rejected = true;\n }\n\n pending = pending.next;\n }\n }\n\n pendingRooms.clear();\n\n if (rejected && reportFailure) report(error);\n }\n\n function handleTransportClose(): void {\n heartbeat.stop();\n resetRemoteSession();\n rejectPendingRooms(new PulseConnectionError('Connection closed before room confirmation', url));\n }\n\n function restoreSession(): void {\n for (const channel of channelReferences.keys()) {\n sendInternal(encode({ channel, type: 'subscribe' }));\n }\n\n for (const room of new Set([...manualRooms, ...presenceReferences.keys()])) {\n if (desiredRoom(room)) sendInternal(encode({ room, type: 'join' }));\n }\n\n for (const [room, state] of localPresence) {\n if (desiredRoom(room)) sendInternal(encode({ room, state, type: 'presence' }));\n }\n }\n\n const connection = createConnection(url, options.protocols, options.reconnect, {\n onClose() {\n handleTransportClose();\n },\n onError: report,\n onMessage(event) {\n let decoded: DecodedInFrame;\n\n try {\n decoded = decodeValidated(event.data);\n } catch (error) {\n report(\n error instanceof PulseProtocolError ? error : new PulseProtocolError('Failed to decode frame', event.data),\n );\n\n return;\n }\n\n if (decoded.kind === 'unknown') {\n report(new PulseProtocolError(`Unknown frame type \"${decoded.type}\"`, event.data));\n\n return;\n }\n\n try {\n handleFrame(decoded);\n } catch (error) {\n report(new PulseProtocolError('Frame handler threw', decoded.frame, { cause: error }));\n }\n },\n onOpen() {\n restoreSession();\n heartbeat.start();\n },\n onStatus(nextStatus) {\n status.value = nextStatus;\n },\n });\n\n const heartbeat: HeartbeatHandle = createHeartbeat(\n options.heartbeat,\n (frame) => {\n if (connection.open) sendInternal(frame);\n },\n () => connection.forceReconnect(4000, 'heartbeat timeout'),\n );\n\n function resolvePendingRoom(room: string, operation: RoomOperation): void {\n const pending = pendingRooms.get(room);\n\n if (!pending || pending.operation !== operation) return;\n\n for (const waiter of pending.callbacks) waiter.resolve();\n\n const nextPending = pending.next;\n\n if (!nextPending) {\n pendingRooms.delete(room);\n\n const followUpOperation =\n operation === 'join' && !desiredRoom(room)\n ? 'leave'\n : operation === 'leave' && desiredRoom(room)\n ? 'join'\n : undefined;\n\n if (followUpOperation) {\n const followUpPending: PendingRoom = { callbacks: new Set<RoomWaiter>(), operation: followUpOperation };\n\n pendingRooms.set(room, followUpPending);\n\n try {\n sendInternal(encode({ room, type: followUpOperation }));\n } catch (error) {\n pendingRooms.delete(room);\n\n if (followUpOperation === 'join') manualRooms.delete(room);\n\n report(error instanceof PulseError ? error : new PulseConnectionError('Failed to send room request', url));\n }\n }\n\n return;\n }\n\n pendingRooms.set(room, nextPending);\n\n try {\n sendInternal(encode({ room, type: nextPending.operation }));\n } catch (error) {\n pendingRooms.delete(room);\n\n if (nextPending.operation === 'join') manualRooms.delete(room);\n\n for (const waiter of nextPending.callbacks) {\n waiter.reject(\n error instanceof PulseError ? error : new PulseConnectionError('Failed to send room request', url),\n );\n }\n }\n }\n\n function handleFrame(decoded: Extract<DecodedInFrame, { kind: 'known' }>): void {\n const frame = decoded.frame;\n\n switch (frame.type) {\n case 'error':\n report(new PulseProtocolError(`Server error [${frame.code}]: ${frame.message}`, frame));\n\n break;\n case 'joined':\n if (desiredRoom(frame.room)) setRoom(frame.room, true);\n\n resolvePendingRoom(frame.room, 'join');\n\n break;\n case 'left':\n setRoom(frame.room, false);\n resolvePendingRoom(frame.room, 'leave');\n\n break;\n case 'message':\n listeners.dispatch(frame.channel ?? null, frame.event, frame.payload);\n\n break;\n case 'pong':\n heartbeat.onPong();\n\n break;\n case 'presence_join':\n case 'presence_leave':\n case 'presence_state':\n listeners.dispatch(null, frame.type, frame);\n\n break;\n case 'subscribed':\n case 'unsubscribed':\n break;\n }\n }\n\n function awaitRoom(\n room: string,\n operation: RoomOperation,\n opts?: { signal?: AbortSignal; timeout?: number },\n ): Promise<void> {\n if (disposed) {\n if (operation === 'join') manualRooms.delete(room);\n\n return Promise.reject(new PulseDisposedError());\n }\n\n if (!connection.open) {\n if (operation === 'join') manualRooms.delete(room);\n\n return Promise.reject(new PulseConnectionError('Connection is not open', url));\n }\n\n const existing = pendingRooms.get(room);\n\n if (operation === 'join' && rooms.value.has(room) && !existing) return Promise.resolve();\n\n if (operation === 'leave' && !rooms.value.has(room) && !existing) return Promise.resolve();\n\n let pending = existing;\n\n if (pending) {\n while (pending.next) pending = pending.next;\n\n if (pending.operation !== operation) {\n const nextPending: PendingRoom = { callbacks: new Set<RoomWaiter>(), operation };\n\n pending.next = nextPending;\n pending = nextPending;\n }\n } else {\n pending = { callbacks: new Set<RoomWaiter>(), operation };\n }\n\n return new Promise((resolve, reject) => {\n const timeoutCtrl = opts?.timeout === undefined ? undefined : new AbortController();\n const signal =\n opts?.signal && timeoutCtrl\n ? AbortSignal.any([opts.signal, timeoutCtrl.signal])\n : (opts?.signal ?? timeoutCtrl?.signal);\n const timeoutId = opts?.timeout === undefined ? undefined : setTimeout(() => timeoutCtrl?.abort(), opts.timeout);\n const waiter: RoomWaiter = {\n reject(error) {\n clearTimeout(timeoutId);\n signal?.removeEventListener('abort', rejectRoom);\n reject(error);\n },\n resolve() {\n clearTimeout(timeoutId);\n signal?.removeEventListener('abort', rejectRoom);\n resolve();\n },\n };\n const rejectRoom = (): void => {\n pending.callbacks.delete(waiter);\n\n if (pending.callbacks.size === 0 && operation === 'join') manualRooms.delete(room);\n\n waiter.reject(timeoutCtrl?.signal.aborted ? new PulseTimeoutError(operation) : new PulseAbortError());\n };\n\n if (signal?.aborted) {\n rejectRoom();\n\n return;\n }\n\n pending.callbacks.add(waiter);\n signal?.addEventListener('abort', rejectRoom, { once: true });\n\n if (existing) return;\n\n pendingRooms.set(room, pending);\n\n try {\n sendInternal(encode({ room, type: operation }));\n } catch (error) {\n pending.callbacks.delete(waiter);\n\n if (pending.callbacks.size === 0) pendingRooms.delete(room);\n\n waiter.reject(\n error instanceof PulseError ? error : new PulseConnectionError('Failed to send room request', url),\n );\n }\n });\n }\n\n function acquireChannel(name: string): void {\n const references = channelReferences.get(name) ?? 0;\n\n channelReferences.set(name, references + 1);\n\n if (references === 0 && connection.open) sendInternal(encode({ channel: name, type: 'subscribe' }));\n }\n\n function releaseChannel(name: string): void {\n const references = channelReferences.get(name);\n\n if (!references) return;\n\n if (references > 1) {\n channelReferences.set(name, references - 1);\n\n return;\n }\n\n channelReferences.delete(name);\n\n if (connection.open) sendInternal(encode({ channel: name, type: 'unsubscribe' }));\n }\n\n function acquirePresence(room: string, reset: () => void): void {\n const references = presenceReferences.get(room) ?? 0;\n const resetters = presenceResetters.get(room) ?? new Set<() => void>();\n\n resetters.add(reset);\n presenceResetters.set(room, resetters);\n presenceReferences.set(room, references + 1);\n\n if (references === 0 && connection.open) sendInternal(encode({ room, type: 'join' }));\n }\n\n function releasePresence(room: string, reset: () => void): void {\n presenceResetters.get(room)?.delete(reset);\n\n if (presenceResetters.get(room)?.size === 0) presenceResetters.delete(room);\n\n const references = presenceReferences.get(room);\n\n if (!references) return;\n\n if (references > 1) {\n presenceReferences.set(room, references - 1);\n\n return;\n }\n\n presenceReferences.delete(room);\n localPresence.delete(room);\n\n if (!manualRooms.has(room) && connection.open) sendInternal(encode({ room, type: 'leave' }));\n }\n\n const pulse: Pulse<TServer, TClient, TChannels, TPresence> = {\n channel<K extends keyof TChannels & string>(name: K): PulseChannel<TChannels[K]['server'], TChannels[K]['client']> {\n if (disposed) throw new PulseDisposedError();\n\n acquireChannel(name);\n\n return createChannel<TChannels[K]['server'], TChannels[K]['client']>(\n name,\n (channel, event, payload) => {\n const transformed = options.transform\n ? options.transform({ channel, event, payload })\n : { channel, event, payload };\n\n if (transformed === null) return;\n\n sendInternal(encode({ ...transformed, type: 'message' }));\n },\n (channel, event, handler) => listeners.add(channel, event, handler),\n disposalCtrl.signal,\n () => releaseChannel(name),\n );\n },\n\n connect(): Promise<void> {\n if (disposed) return Promise.reject(new PulseDisposedError());\n\n return connection.connect();\n },\n\n disconnect(code = 1000, reason = ''): void {\n if (disposed) return;\n\n handleTransportClose();\n connection.disconnect(code, reason);\n },\n\n get disposalSignal() {\n return disposalCtrl.signal;\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n heartbeat.stop();\n resetRemoteSession();\n rejectPendingRooms(new PulseDisposedError(), false);\n connection.dispose();\n channelReferences.clear();\n presenceReferences.clear();\n manualRooms.clear();\n localPresence.clear();\n presenceResetters.clear();\n pendingRooms.clear();\n listeners.clear();\n disposalCtrl.abort();\n },\n\n get disposed() {\n return disposed;\n },\n\n join(room: string, opts?: { signal?: AbortSignal; timeout?: number }): Promise<void> {\n manualRooms.add(room);\n\n return awaitRoom(room, 'join', opts);\n },\n\n leave(room: string, opts?: { signal?: AbortSignal; timeout?: number }): Promise<void> {\n manualRooms.delete(room);\n\n if (desiredRoom(room)) return Promise.resolve();\n\n return awaitRoom(room, 'leave', opts);\n },\n\n on<K extends EventKey<TServer>>(event: K, handler: (payload: TServer[K]) => void): Unsubscribe {\n if (disposed) throw new PulseDisposedError();\n\n return listeners.add(null, event, handler as Handler);\n },\n\n once<K extends EventKey<TServer>>(event: K, handler: (payload: TServer[K]) => void): Unsubscribe {\n if (disposed) throw new PulseDisposedError();\n\n let unsubscribe: Unsubscribe = () => {};\n\n unsubscribe = listeners.add(null, event, (payload) => {\n unsubscribe();\n (handler as Handler)(payload);\n });\n\n return unsubscribe;\n },\n\n presence<K extends keyof TPresence & string>(room: K): PresenceChannel<TPresence[K]> {\n if (disposed) throw new PulseDisposedError();\n\n let reset: (() => void) | undefined;\n\n const presence = createPresence<TPresence[K]>(\n room,\n (presenceRoom, state) => {\n sendInternal(encode({ room: presenceRoom, state, type: 'presence' }));\n localPresence.set(presenceRoom, state);\n },\n {\n onJoin: (handler) => listeners.add(null, 'presence_join', (value) => handler(value as InPresenceJoinFrame)),\n onLeave: (handler) =>\n listeners.add(null, 'presence_leave', (value) => handler(value as InPresenceLeaveFrame)),\n onState: (handler) =>\n listeners.add(null, 'presence_state', (value) => handler(value as InPresenceStateFrame)),\n },\n disposalCtrl.signal,\n (createdReset) => {\n reset = createdReset;\n },\n () => release(),\n );\n\n const resetPresence = reset;\n\n if (!resetPresence) throw new Error('Presence reset callback was not initialized');\n\n const release = () => releasePresence(room, resetPresence);\n\n acquirePresence(room, resetPresence);\n\n return presence;\n },\n\n get rooms() {\n return rooms;\n },\n\n send<K extends EventKey<TClient>>(event: K, payload: TClient[K]): void {\n if (disposed) throw new PulseDisposedError();\n\n const transformed = options.transform ? options.transform({ event, payload }) : { event, payload };\n\n if (transformed === null) return;\n\n sendInternal(encode({ ...transformed, type: 'message' }));\n },\n\n get status() {\n return status;\n },\n\n [Symbol.dispose]() {\n this.dispose();\n },\n\n wait<K extends EventKey<TServer>>(\n event: K,\n opts?: { signal?: AbortSignal; timeout?: number },\n ): Promise<TServer[K]> {\n return createWaitPromise<TServer[K]>(event, disposalCtrl.signal, opts, (waitEvent, handler) =>\n listeners.add(null, waitEvent, handler),\n );\n },\n };\n\n return pulse;\n}\n"],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -1,6 +1,4 @@
1
1
  import type { Readable } from '@vielzeug/ripple';
2
- /** A read-only view of a Map — callers cannot mutate the entries. */
3
- export type ReadonlyMap<K, V> = Omit<Map<K, V>, 'clear' | 'delete' | 'set'>;
4
2
  /** A map of event name → payload type. Use as the generic parameter for Pulse. */
5
3
  export type MessageMap = Record<string, unknown>;
6
4
  /** Extract valid event keys from a MessageMap. */
@@ -26,59 +24,31 @@ export type HeartbeatOptions = {
26
24
  /** How long to wait for a pong before treating the connection as dead. Default: 5_000. */
27
25
  timeout?: number;
28
26
  };
29
- /**
30
- * Intercepts outgoing messages. Call `next()` to allow the message to be sent;
31
- * omit to suppress it.
32
- */
33
- export type Middleware = (event: string, payload: unknown, next: () => void) => void;
34
- export type BufferOptions = {
35
- /**
36
- * Maximum number of outgoing frames to buffer while disconnected.
37
- * Oldest frames are evicted when the buffer is full. Default: 50.
38
- */
39
- maxSize?: number;
27
+ /** An outgoing application message before it is serialized. Return `null` to drop it. */
28
+ export type OutgoingMessage = {
29
+ channel?: string;
30
+ event: string;
31
+ payload: unknown;
32
+ };
33
+ /** Transform or filter outgoing application messages. Internal protocol frames bypass this hook. */
34
+ export type OutgoingTransform = (message: Readonly<OutgoingMessage>) => OutgoingMessage | null;
35
+ /** Server and client event maps for one named channel. */
36
+ export type ChannelDefinition = {
37
+ client: MessageMap;
38
+ server: MessageMap;
40
39
  };
40
+ /** Named channel schemas supplied when creating Pulse. */
41
+ export type ChannelDefinitions = Record<string, ChannelDefinition>;
42
+ /** Named presence-state schemas supplied when creating Pulse. */
43
+ export type PresenceDefinitions = Record<string, unknown>;
41
44
  export type PulseOptions = {
42
- /**
43
- * Buffer outgoing messages while the connection is not open, then flush on
44
- * reconnect. `true` uses defaults (`maxSize: 50`). `false` disables (default).
45
- * Dropped messages are discarded with a dev warning when buffering is off.
46
- */
47
- buffer?: boolean | BufferOptions;
48
45
  /**
49
46
  * Heartbeat ping/pong keep-alive.
50
47
  * `true` uses defaults. `false` disables. Default: `false`.
51
48
  */
52
49
  heartbeat?: boolean | HeartbeatOptions;
53
- /**
54
- * Defer the initial WebSocket connection until `connect()` is called explicitly.
55
- * Default: `false` (connects immediately on creation).
56
- */
57
- lazy?: boolean;
58
- /**
59
- * Middleware functions run on every outgoing `send()`, before the message is
60
- * written to the socket. Call `next()` to proceed or omit to suppress.
61
- */
62
- middleware?: readonly Middleware[];
63
- /** Called when the connection is closed by either side. */
64
- onClose?: (code: number, reason: string) => void;
65
- /**
66
- * Called on a WebSocket error event. Note: WebSocket errors almost always
67
- * precede a close event — use `onClose` for recovery logic.
68
- */
69
- onError?: (error: Error) => void;
70
- /**
71
- * Called with every raw `MessageEvent` before any parsing occurs.
72
- * Useful for low-level debugging.
73
- */
74
- onMessage?: (event: MessageEvent) => void;
75
- /** Called when the connection is established (or re-established). */
76
- onOpen?: () => void;
77
- /**
78
- * Called at the start of each reconnect attempt.
79
- * `attempt` is 1-based (1 = first retry).
80
- */
81
- onReconnect?: (attempt: number) => void;
50
+ /** Receives typed transport and protocol failures. */
51
+ onError?: (error: import('./errors').PulseError) => void;
82
52
  /** Sub-protocols to pass to the WebSocket constructor. */
83
53
  protocols?: string | string[];
84
54
  /**
@@ -86,14 +56,16 @@ export type PulseOptions = {
86
56
  * `true` uses defaults. `false` disables. Default: `false`.
87
57
  */
88
58
  reconnect?: boolean | ReconnectOptions;
59
+ /** Transform or filter application messages before serialization. */
60
+ transform?: OutgoingTransform;
89
61
  };
90
62
  /**
91
63
  * An isolated message namespace multiplexed over the shared WebSocket connection.
92
- * Obtain one via `pulse.channel(name)`. Multiple calls with the same name return
93
- * the **same** channel object dispose it once to fully remove the subscription.
64
+ * Obtain one via `pulse.channel(name)`. Each call returns an independent scope;
65
+ * the server subscription remains active until every scope is disposed.
94
66
  *
95
67
  * @example
96
- * const notif = pulse.channel<{ alert: string }>('notifications');
68
+ * const notif = pulse.channel('notifications');
97
69
  * notif.on('alert', (msg) => console.log(msg));
98
70
  * notif.send('alert', 'Hello!');
99
71
  */
@@ -102,7 +74,7 @@ export type PulseChannel<TServer extends MessageMap = MessageMap, TClient extend
102
74
  [Symbol.dispose](): void;
103
75
  /** `AbortSignal` aborted when `dispose()` is called. */
104
76
  readonly disposalSignal: AbortSignal;
105
- /** Permanently unsubscribes all listeners and prevents future sends. */
77
+ /** Releases this scope's listeners and subscription reference. */
106
78
  dispose(): void;
107
79
  /** Whether this channel has been disposed. */
108
80
  readonly disposed: boolean;
@@ -117,7 +89,7 @@ export type PulseChannel<TServer extends MessageMap = MessageMap, TClient extend
117
89
  once<K extends EventKey<TServer>>(event: K, handler: (payload: TServer[K]) => void): Unsubscribe;
118
90
  /**
119
91
  * Send a typed message to the server, scoped to this channel.
120
- * No-op if the pulse connection is not open and buffering is disabled.
92
+ * Throws `PulseConnectionError` unless the connection is open.
121
93
  */
122
94
  send<K extends EventKey<TClient>>(event: K, payload: TClient[K]): void;
123
95
  /**
@@ -134,7 +106,7 @@ export type PulseChannel<TServer extends MessageMap = MessageMap, TClient extend
134
106
  * Obtain one via `pulse.presence(room)`.
135
107
  *
136
108
  * @example
137
- * const lobby = pulse.presence<{ name: string; status: string }>('lobby');
109
+ * const lobby = pulse.presence('lobby');
138
110
  * effect(() => console.log('Online:', [...lobby.state.value.keys()]));
139
111
  * lobby.update({ name: 'Alice', status: 'online' });
140
112
  */
@@ -143,7 +115,7 @@ export type PresenceChannel<T = unknown> = {
143
115
  [Symbol.dispose](): void;
144
116
  /** `AbortSignal` aborted when `dispose()` is called. */
145
117
  readonly disposalSignal: AbortSignal;
146
- /** Permanently stops tracking. Sends a leave frame to the server. */
118
+ /** Releases this scope's listeners and room reference. */
147
119
  dispose(): void;
148
120
  /** Whether this presence channel has been disposed. */
149
121
  readonly disposed: boolean;
@@ -163,22 +135,21 @@ export type PresenceChannel<T = unknown> = {
163
135
  readonly state: Readable<ReadonlyMap<string, T>>;
164
136
  /**
165
137
  * Broadcast this client's presence state to all room members.
166
- * Calling this also implicitly joins the room if not already joined.
138
+ * Throws `PulseConnectionError` unless the connection is open.
167
139
  */
168
140
  update(state: T): void;
169
141
  };
170
- export type Pulse<TServer extends MessageMap = MessageMap, TClient extends MessageMap = MessageMap> = {
142
+ export type Pulse<TServer extends MessageMap = MessageMap, TClient extends MessageMap = MessageMap, TChannels extends ChannelDefinitions = ChannelDefinitions, TPresence extends PresenceDefinitions = PresenceDefinitions> = {
171
143
  /** Delegates to `dispose()`. Enables `using` declarations. */
172
144
  [Symbol.dispose](): void;
173
145
  /**
174
- * Return an isolated message namespace over the shared connection.
175
- * Multiple calls with the same name return the **same** channel object —
176
- * dispose it once to remove the subscription.
146
+ * Create an isolated message namespace over the shared connection.
147
+ * Each call returns an independently disposable scope. The server subscription
148
+ * remains active until every scope for this name is disposed.
177
149
  */
178
- channel<TChServer extends MessageMap = TServer, TChClient extends MessageMap = TClient>(name: string): PulseChannel<TChServer, TChClient>;
150
+ channel<K extends keyof TChannels & string>(name: K): PulseChannel<TChannels[K]['server'], TChannels[K]['client']>;
179
151
  /**
180
- * Explicitly open the connection. Resolves when `'open'` fires.
181
- * Required when `lazy: true`; otherwise called automatically on creation.
152
+ * Explicitly open the connection. Resolves after session restoration completes.
182
153
  * Rejects if the connection closes before opening.
183
154
  */
184
155
  connect(): Promise<void>;
@@ -218,16 +189,16 @@ export type Pulse<TServer extends MessageMap = MessageMap, TClient extends Messa
218
189
  /** Subscribe once — auto-removes after first invocation. */
219
190
  once<K extends EventKey<TServer>>(event: K, handler: (payload: TServer[K]) => void): Unsubscribe;
220
191
  /**
221
- * Return a presence channel that tracks all members' state in a room.
222
- * Multiple calls with the same room return the **same** presence object.
223
- * Calling `presence()` implicitly joins the room.
192
+ * Create a presence scope that tracks all members' state in a room.
193
+ * Each call returns an independently disposable scope. The room remains joined
194
+ * until every scope for this name is disposed.
224
195
  */
225
- presence<T>(room: string): PresenceChannel<T>;
196
+ presence<K extends keyof TPresence & string>(room: K): PresenceChannel<TPresence[K]>;
226
197
  /** Reactive set of rooms the client is currently a member of. */
227
198
  readonly rooms: Readable<ReadonlySet<string>>;
228
199
  /**
229
- * Send a typed event to the server. If buffering is enabled, the message is
230
- * queued when the connection is not open and flushed on reconnect.
200
+ * Send a typed event to the server.
201
+ * Throws `PulseConnectionError` unless the connection is open.
231
202
  */
232
203
  send<K extends EventKey<TClient>>(event: K, payload: TClient[K]): void;
233
204
  /** Reactive connection status. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vielzeug/pulse",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "Typed WebSocket client with channel multiplexing, presence tracking, auto-reconnect, and heartbeat",
5
5
  "type": "module",
6
6
  "files": [
@@ -8,7 +8,7 @@
8
8
  ],
9
9
  "main": "./dist/index.cjs",
10
10
  "module": "./dist/index.js",
11
- "types": "dist/index.d.ts",
11
+ "types": "./dist/index.d.ts",
12
12
  "exports": {
13
13
  ".": {
14
14
  "types": "./dist/index.d.ts",
package/dist/_dev.cjs DELETED
@@ -1,2 +0,0 @@
1
- function e(e){}exports.warn=e;
2
- //# sourceMappingURL=_dev.cjs.map
package/dist/_dev.cjs.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"_dev.cjs","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __PULSE_PROD__?: boolean }).__PULSE_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/pulse] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n"],"mappings":"AAGA,SAAgB,EAAK,EAAmB,CAExC"}
package/dist/_dev.d.ts DELETED
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=_dev.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"_dev.d.ts","sourceRoot":"","sources":["../src/_dev.ts"],"names":[],"mappings":""}
package/dist/_dev.js DELETED
@@ -1,6 +0,0 @@
1
- //#region src/_dev.ts
2
- function e(e) {}
3
- //#endregion
4
- export { e as warn };
5
-
6
- //# sourceMappingURL=_dev.js.map
package/dist/_dev.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"_dev.js","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __PULSE_PROD__?: boolean }).__PULSE_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/pulse] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n"],"mappings":";AAGA,SAAgB,EAAK,GAAmB,CAExC"}
@@ -1,2 +0,0 @@
1
- const e=require("./_utils.cjs");var t=5;function n(n){if(!n)return{attempt:async()=>({ok:!1}),reset(){}};let r=n===!0?{}:n,i=r.maxAttempts??t,a=typeof r.delay==`function`?r.delay:typeof r.delay==`number`?()=>r.delay:e.defaultReconnectDelay,o=0;return{async attempt(t,n,r){if(o>=i)return{ok:!1};let s=a(o);if(o++,r?.(o),await e.sleep(s,n),n.aborted)return{ok:!1};try{return await t(),o=0,{ok:!0}}catch(e){return{error:e,ok:!1}}},reset(){o=0}}}exports.createReconnect=n;
2
- //# sourceMappingURL=reconnect.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"reconnect.cjs","names":[],"sources":["../src/reconnect.ts"],"sourcesContent":["import type { ReconnectOptions } from './types';\n\nimport { defaultReconnectDelay, sleep } from './_utils';\n\nconst DEFAULT_MAX_ATTEMPTS = 5;\n\nexport type ReconnectHandle = {\n /**\n * Attempt a reconnect. Waits for the computed delay then calls `connect`.\n * Returns outcome details including success and, when available, the failure reason.\n */\n attempt(\n connect: () => Promise<void>,\n signal: AbortSignal,\n onReconnect?: (attempt: number) => void,\n ): Promise<{ error?: unknown; ok: boolean }>;\n /** Cancel any pending reconnect attempt and reset the counter. */\n reset(): void;\n};\n\n/**\n * Create a stateful reconnect manager.\n * @internal\n */\nexport function createReconnect(opts: boolean | ReconnectOptions | undefined): ReconnectHandle {\n if (!opts) {\n return {\n attempt: async () => ({ ok: false }),\n reset() {},\n };\n }\n\n const options: ReconnectOptions = opts === true ? {} : opts;\n const maxAttempts = options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;\n const delayFn: (attempt: number) => number =\n typeof options.delay === 'function'\n ? options.delay\n : typeof options.delay === 'number'\n ? () => options.delay as number\n : defaultReconnectDelay;\n\n let attempts = 0;\n\n return {\n async attempt(\n connect: () => Promise<void>,\n signal: AbortSignal,\n onReconnect?: (attempt: number) => void,\n ): Promise<{ error?: unknown; ok: boolean }> {\n if (attempts >= maxAttempts) return { ok: false };\n\n const delay = delayFn(attempts);\n\n attempts++;\n onReconnect?.(attempts);\n\n await sleep(delay, signal);\n\n if (signal.aborted) return { ok: false };\n\n try {\n await connect();\n\n attempts = 0;\n\n return { ok: true };\n } catch (error) {\n return { error, ok: false };\n }\n },\n\n reset() {\n attempts = 0;\n },\n };\n}\n"],"mappings":"gCAIA,IAAM,EAAuB,EAoB7B,SAAgB,EAAgB,EAA+D,CAC7F,GAAI,CAAC,EACH,MAAO,CACL,QAAS,UAAa,CAAE,GAAI,EAAM,GAClC,OAAQ,CAAC,CACX,EAGF,IAAM,EAA4B,IAAS,GAAO,CAAC,EAAI,EACjD,EAAc,EAAQ,aAAe,EACrC,EACJ,OAAO,EAAQ,OAAU,WACrB,EAAQ,MACR,OAAO,EAAQ,OAAU,aACjB,EAAQ,MACd,EAAA,sBAEJ,EAAW,EAEf,MAAO,CACL,MAAM,QACJ,EACA,EACA,EAC2C,CAC3C,GAAI,GAAY,EAAa,MAAO,CAAE,GAAI,EAAM,EAEhD,IAAM,EAAQ,EAAQ,CAAQ,EAO9B,GALA,IACA,IAAc,CAAQ,EAEtB,MAAM,EAAA,MAAM,EAAO,CAAM,EAErB,EAAO,QAAS,MAAO,CAAE,GAAI,EAAM,EAEvC,GAAI,CAKF,OAJA,MAAM,EAAQ,EAEd,EAAW,EAEJ,CAAE,GAAI,EAAK,CACpB,OAAS,EAAO,CACd,MAAO,CAAE,QAAO,GAAI,EAAM,CAC5B,CACF,EAEA,OAAQ,CACN,EAAW,CACb,CACF,CACF"}
@@ -1,13 +0,0 @@
1
- export type ReconnectHandle = {
2
- /**
3
- * Attempt a reconnect. Waits for the computed delay then calls `connect`.
4
- * Returns outcome details including success and, when available, the failure reason.
5
- */
6
- attempt(connect: () => Promise<void>, signal: AbortSignal, onReconnect?: (attempt: number) => void): Promise<{
7
- error?: unknown;
8
- ok: boolean;
9
- }>;
10
- /** Cancel any pending reconnect attempt and reset the counter. */
11
- reset(): void;
12
- };
13
- //# sourceMappingURL=reconnect.d.ts.map