/** * Reactive WebSocket composable with auto-reconnect, heartbeat, * message history, and signal-based connection state. * * @module bquery/reactive */ import { Signal } from './core'; /** @internal */ type WebSocketSendData = string | Blob | ArrayBufferLike | ArrayBufferView; /** Connection status for a WebSocket. */ export type WebSocketStatus = 'CONNECTING' | 'OPEN' | 'CLOSING' | 'CLOSED'; /** Connection status for an EventSource. */ export type EventSourceStatus = 'CONNECTING' | 'OPEN' | 'CLOSED'; /** Configuration for automatic reconnection. */ export interface WebSocketReconnectConfig { /** Maximum number of reconnection attempts. Use `0` to disable reconnection (default: Infinity / unlimited). */ maxAttempts?: number; /** Base delay in ms between reconnects (default: 1000). */ delay?: number; /** Maximum delay in ms between reconnects (default: 30000). */ maxDelay?: number; /** Multiply factor for exponential backoff (default: 2). */ factor?: number; /** Custom predicate — return `false` to prevent a reconnect attempt. */ shouldReconnect?: (event: CloseEvent, attempts: number) => boolean; } /** Reconnect configuration supported by `useEventSource()`. */ export type EventSourceReconnectConfig = Pick; /** Configuration for keep-alive heartbeats. */ export interface WebSocketHeartbeatConfig { /** Outgoing ping message (default: `'ping'`). */ message?: WebSocketSendData; /** Interval in ms between heartbeat pings (default: 30 000). */ interval?: number; /** Time in ms to wait for a pong before assuming the connection is dead (default: 10 000). */ pongTimeout?: number; /** Expected response message. If set, only messages matching this value reset the pong timer. */ responseMessage?: string; } /** Serializer/deserializer for typed messaging. */ export interface WebSocketSerializer { /** Serialize a value before sending over the wire. Default: `JSON.stringify`. */ serialize?: (data: TSend) => WebSocketSendData; /** Deserialize an incoming message. Default: `JSON.parse`. */ deserialize?: (event: MessageEvent) => TReceive; } /** Full configuration accepted by `useWebSocket()`. */ export interface UseWebSocketOptions extends WebSocketSerializer { /** Sub-protocols to request during the WebSocket handshake. */ protocols?: string | string[]; /** Open the connection immediately (default: true). */ immediate?: boolean; /** Automatically reconnect on unexpected close (default: true). Pass `false` or config. */ autoReconnect?: boolean | WebSocketReconnectConfig; /** Keep-alive heartbeat configuration. Pass `true` for defaults or a config object. */ heartbeat?: boolean | WebSocketHeartbeatConfig; /** Maximum number of messages to keep in `history` (default: 0 = disabled). */ historySize?: number; /** Called when the connection opens. */ onOpen?: (event: Event) => void; /** Called when a message is received (after deserialization). */ onMessage?: (data: TReceive, event: MessageEvent) => void; /** Called when the connection closes. */ onClose?: (event: CloseEvent) => void; /** Called when a connection error occurs. */ onError?: (event: Event) => void; /** Called after a successful reconnection. Receives the reconnection attempt count. */ onReconnect?: (attempts: number) => void; } /** Return value of `useWebSocket()`. */ export interface UseWebSocketReturn { /** Reactive connection status. */ status: { readonly value: WebSocketStatus; peek(): WebSocketStatus; }; /** Reactive last received message (deserialized). */ data: Signal; /** Last error event. */ error: Signal; /** Rolling message history (newest last). */ history: Signal; /** Computed boolean — `true` when the socket is `OPEN`. */ isConnected: { readonly value: boolean; peek(): boolean; }; /** Number of reconnection attempts since last successful open. */ reconnectAttempts: Signal; /** Round-trip latency in ms measured via heartbeat pings (requires `heartbeat` option). */ latency: Signal; /** Timestamp of the last unexpected disconnection, or 0 if never disconnected. */ lastDisconnectedAt: Signal; /** * Send a message. * * If the socket is not `OPEN`, the message is queued and sent once a * connection is (re)established, subject to the configured options. */ send: (data: TSend) => void; /** * Send raw data without serialization. * * Uses the same queuing behavior as {@link send}: data is queued when the * socket is not `OPEN` and flushed once a connection is (re)established. */ sendRaw: (data: WebSocketSendData) => void; /** Manually open / reconnect the WebSocket. */ open: () => void; /** Gracefully close the connection. */ close: (code?: number, reason?: string) => void; /** Tear down all resources (close + remove listeners + stop reconnect/heartbeat). */ dispose: () => void; } /** * Reactive WebSocket composable with auto-reconnect, heartbeat, * typed messaging, and signal-based connection state. * * @template TSend - Type of outgoing messages (serialized via `serialize`) * @template TReceive - Type of incoming messages (deserialized via `deserialize`) * @param url - WebSocket URL (`ws://` or `wss://`) or a getter returning one * @param options - Connection, reconnect, heartbeat, and serialization options * @returns Reactive WebSocket state with `send()`, `open()`, `close()`, and `dispose()` * * @example * ```ts * import { useWebSocket } from '@bquery/bquery/reactive'; * * const ws = useWebSocket<{ type: string; payload: unknown }>('wss://api.example.com/ws', { * autoReconnect: { maxAttempts: 5, delay: 2000 }, * heartbeat: true, * historySize: 50, * onMessage: (data) => console.log('Received:', data), * }); * * ws.send({ type: 'subscribe', payload: { channel: 'updates' } }); * * // Reactive state * effect(() => { * console.log('Connected:', ws.isConnected.value); * console.log('Last message:', ws.data.value); * }); * * // Cleanup * ws.dispose(); * ``` */ export declare const useWebSocket: (url: string | URL | (() => string | URL), options?: UseWebSocketOptions) => UseWebSocketReturn; /** Default channel message format used by `useWebSocketChannel()`. */ export interface ChannelMessage { /** Channel / topic name. */ channel: string; /** Message payload. */ data: T; } /** Configuration for `useWebSocketChannel()`. */ export interface UseWebSocketChannelOptions { /** * Extract the channel name from an incoming deserialized message. * Default: reads `(msg as ChannelMessage).channel`. */ getChannel?: (msg: TReceive) => string | undefined; /** * Wrap a payload + channel into the wire format before sending. * Default: `{ channel, data }`. */ wrap?: (channel: string, data: TSend) => TReceive; } /** A single channel subscription returned by `subscribe()`. */ export interface ChannelSubscription { /** Reactive last message received on this channel. */ data: Signal; /** Unsubscribe from this channel. */ unsubscribe: () => void; } /** Return value of `useWebSocketChannel()`. */ export interface UseWebSocketChannelReturn { /** Subscribe to a topic. Multiple subscriptions to the same channel share a signal. */ subscribe: (channel: string) => ChannelSubscription; /** Publish a message to a channel. */ publish: (channel: string, data: TSend) => void; /** The underlying `useWebSocket` return for direct access. */ ws: UseWebSocketReturn; } /** * Topic-based channel multiplexer over a single WebSocket connection. * * Builds on `useWebSocket()` and routes incoming messages to per-channel * reactive signals based on a configurable channel extractor. * * @template TSend - Type of outgoing message payloads * @template TReceive - Type of incoming deserialized messages * @param url - WebSocket URL * @param wsOptions - All `useWebSocket` options * @param channelOptions - Channel routing configuration * @returns Channel multiplexer with `subscribe()`, `publish()`, and the underlying `ws` * * @example * ```ts * import { useWebSocketChannel } from '@bquery/bquery/reactive'; * * const chat = useWebSocketChannel('wss://chat.example.com/ws'); * * const general = chat.subscribe('general'); * const updates = chat.subscribe('updates'); * * effect(() => console.log('General:', general.data.value)); * * chat.publish('general', { text: 'Hello!' }); * ``` */ export declare const useWebSocketChannel: (url: string | URL | (() => string | URL), wsOptions?: UseWebSocketOptions, channelOptions?: UseWebSocketChannelOptions) => UseWebSocketChannelReturn; /** Configuration for `useEventSource()`. */ export interface UseEventSourceOptions { /** Whether to open the connection immediately (default: true). */ immediate?: boolean; /** Automatically reconnect on error (default: true). Pass a reconnect config to customize delay and attempt limits. */ autoReconnect?: boolean | EventSourceReconnectConfig; /** Event names to listen for besides the default `message` event. */ events?: string[]; /** Deserializer for incoming event data. Default: `JSON.parse` with string fallback. */ deserialize?: (data: string) => TData; /** EventSource init options (e.g. `withCredentials`). */ eventSourceInit?: EventSourceInit; /** Called when the connection opens. */ onOpen?: (event: Event) => void; /** Called when a message is received. */ onMessage?: (data: TData, event: MessageEvent) => void; /** Called when an error occurs. */ onError?: (event: Event) => void; } /** Return value of `useEventSource()`. */ export interface UseEventSourceReturn { /** Current connection status (`CONNECTING`, `OPEN`, `CLOSED`). */ status: { readonly value: EventSourceStatus; peek(): EventSourceStatus; }; /** Last received data (deserialized). */ data: Signal; /** Last event name that delivered data. */ eventName: Signal; /** Last error event. */ error: Signal; /** Computed boolean — `true` when the EventSource is open. */ isConnected: { readonly value: boolean; peek(): boolean; }; /** Manually open / reconnect the EventSource. */ open: () => void; /** Close the connection. */ close: () => void; /** Tear down all resources. */ dispose: () => void; } /** * Reactive Server-Sent Events (SSE) composable. * * Wraps the native `EventSource` API with reactive signals, auto-reconnect, * and typed deserialization. * * @template TData - Type of deserialized event data * @param url - SSE endpoint URL or a getter returning one * @param options - EventSource options * @returns Reactive EventSource state with `open()`, `close()`, and `dispose()` * * @example * ```ts * import { useEventSource } from '@bquery/bquery/reactive'; * * const sse = useEventSource<{ type: string; message: string }>('/api/events', { * events: ['notification', 'update'], * onMessage: (data) => console.log('Event:', data), * }); * * effect(() => { * if (sse.data.value) { * console.log(`[${sse.eventName.value}]`, sse.data.value); * } * }); * * sse.dispose(); * ``` */ export declare const useEventSource: (url: string | URL | (() => string | URL), options?: UseEventSourceOptions) => UseEventSourceReturn; export {}; //# sourceMappingURL=websocket.d.ts.map