@firtoz/socka
Version:
Standard Schema–first WebSocket RPC for TypeScript — Bun, Hono, Node ws, Cloudflare Workers, Durable Objects
176 lines (172 loc) • 8.13 kB
TypeScript
import { S as SockaContractBound, a as SockaWireFormat, p as SockaServerResponseFrame, n as SockaServerErrorFrame, o as SockaServerEventFrame, d as InferSockaSend, c as InferSockaPushPayload, b as InferSockaPushHandlers, m as SockaReportError } from '../socka-report-error-BALZPjd1.js';
export { B as reportSockaError } from '../socka-report-error-BALZPjd1.js';
import '@standard-schema/spec';
interface SockaWebSocketClientOptions<TContract extends SockaContractBound> {
contract: TContract;
/** Default `"json"` (text frames). Use `"msgpack"` for binary `ArrayBuffer` frames. */
wireFormat?: SockaWireFormat;
url?: string;
webSocket?: WebSocket;
/**
* When `false`, the socket is not created until {@link SockaWebSocketClient.connect}
* (or the first operation that implicitly opens, e.g. {@link SockaSession} `send`).
* Default `true`.
*/
autoConnect?: boolean;
serializeJson?: (value: unknown) => string;
deserializeJson?: (raw: string) => unknown;
onOpen?: (event: Event) => void;
onClose?: (event: CloseEvent) => void;
onError?: (event: Event) => void;
onResponse?: (frame: SockaServerResponseFrame) => void;
onServerError?: (frame: SockaServerErrorFrame) => void;
onEvent?: (frame: SockaServerEventFrame) => void;
onValidationError?: (error: Error, rawMessage: unknown) => void;
/**
* Automatic reconnect after an abnormal close. **Disabled by default** when
* {@link webSocket} is injected (tests); enabled when using {@link url}.
* Pass `false` to disable.
*/
reconnect?: false | SockaReconnectConfig;
/** Fired before a reconnect attempt is scheduled (after delay is chosen). */
onReconnecting?: (info: SockaReconnectingInfo) => void;
/** Fired after a new socket reaches `open` following a reconnect. */
onReconnected?: (info: SockaReconnectedInfo) => void;
}
/** Options for {@link SockaWebSocketClientOptions.reconnect}. */
type SockaReconnectConfig = {
/** Default `1000`. */
initialDelayMs?: number;
/** Default `30000`. */
maxDelayMs?: number;
/** 0–1 fraction of delay to randomize. Default `0.2`. */
jitter?: number;
/** Omit for infinite attempts. */
maxAttempts?: number;
/**
* When `true` (default), delay reconnect until `document` is visible again.
*/
pauseWhenHidden?: boolean;
};
type SockaReconnectingInfo = {
attempt: number;
delayMs: number;
};
type SockaReconnectedInfo = {
attempt: number;
};
/** High-level connection lifecycle for UI and telemetry. */
type SockaConnectionStatus = "idle" | "connecting" | "open" | "reconnecting" | "closed";
/**
* Browser WebSocket client driven by a socka contract. Sends client request
* frames and dispatches decoded server frames to callbacks.
*/
declare class SockaWebSocketClient<TContract extends SockaContractBound> {
private ws;
private readonly opts;
private readonly wireFormat;
private readonly serializeJson;
private readonly deserializeJson;
private readonly onResponseCb?;
private readonly onServerErrorCb?;
private readonly onEventCb?;
private readonly onValidationError?;
readonly contract: TContract;
private manualClose;
private reconnectAttempt;
private reconnectTimer;
private _status;
private readonly statusListeners;
constructor(options: SockaWebSocketClientOptions<TContract>);
private setStatus;
/** Current connection lifecycle state. */
get status(): SockaConnectionStatus;
/**
* Subscribe to {@link status} changes. The listener is called immediately with the
* current status, then on every transition. Returns an unsubscribe function.
*/
onStatusChange(listener: (status: SockaConnectionStatus) => void): () => void;
private createSocket;
private attachSocket;
private getReconnectEnabled;
private resolveReconnectConfig;
private computeReconnectDelayMs;
private maybeScheduleReconnect;
private openReplacementSocket;
private handleMessageEvent;
/**
* Creates the WebSocket (when {@link SockaWebSocketClientOptions.autoConnect}
* was `false`) and waits until the connection is open.
*/
connect(): Promise<void>;
sendRequest(id: string, rpc: string, body: Record<string, unknown>): void;
close(code?: number, reason?: string): void;
get readyState(): number;
get socket(): WebSocket;
waitForOpen(): Promise<void>;
}
type SockaSessionPushWaitOptions<TContract extends SockaContractBound, K extends keyof TContract["pushes"] & string> = {
signal?: AbortSignal;
timeoutMs?: number;
predicate?: (payload: InferSockaPushPayload<TContract, K>) => boolean;
};
type SockaSessionSubscribeApi<TContract extends SockaContractBound> = {
on<K extends keyof TContract["pushes"] & string>(name: K, handler: (payload: InferSockaPushPayload<TContract, K>) => void | Promise<void>): void;
off<K extends keyof TContract["pushes"] & string>(name: K, handler: (payload: InferSockaPushPayload<TContract, K>) => void | Promise<void>): void;
once<K extends keyof TContract["pushes"] & string>(name: K, handler: (payload: InferSockaPushPayload<TContract, K>) => void | Promise<void>): void;
waitForPush<K extends keyof TContract["pushes"] & string>(name: K, options?: SockaSessionPushWaitOptions<TContract, K>): Promise<InferSockaPushPayload<TContract, K>>;
};
type SockaSessionOptions<TContract extends SockaContractBound> = Omit<SockaWebSocketClientOptions<TContract>, "onResponse" | "onServerError" | "onEvent"> & {
onOpen?: (event: Event) => void;
onClose?: (event: CloseEvent) => void;
onError?: (event: Event) => void;
pushHandlers?: Partial<InferSockaPushHandlers<TContract>>;
/**
* Optional sink for client-side push pipeline failures (listener throws,
* push payload validation). Defaults to `console.error`; see
* `SockaReportError` in `@firtoz/socka/core`.
*/
reportError?: (event: SockaReportError) => void;
};
/**
* Browser WebSocket session: **`session.send`** for contract calls, **`session.subscribe`**
* for server pushes, **`session.client`** for low-level wire access.
*/
declare class SockaSessionBase<TContract extends SockaContractBound> {
readonly client: SockaWebSocketClient<TContract>;
readonly send: InferSockaSend<TContract>;
readonly subscribe: SockaSessionSubscribeApi<TContract>;
private readonly pending;
private idSeq;
private readonly pushListeners;
private readonly reportError?;
constructor(options: SockaSessionOptions<TContract>);
private createSubscribeApi;
private addPushListener;
private removePushListener;
private waitForPushImpl;
private buildSendMethods;
private call;
private handleResponse;
private handleServerError;
private handleEvent;
private dispatchValidatedPush;
private nextId;
rejectAllPending(reason: Error): void;
close(code?: number, reason?: string): void;
/** Opens the WebSocket when using {@link SockaWebSocketClientOptions.autoConnect} `false`. */
connect(): Promise<void>;
/** Same as {@link SockaWebSocketClient.status}. */
get status(): SockaConnectionStatus;
/** Same as {@link SockaWebSocketClient.onStatusChange}. */
onStatusChange(listener: (status: SockaConnectionStatus) => void): () => void;
}
interface SockaSessionConstructor {
new <TContract extends SockaContractBound>(options: SockaSessionOptions<TContract>): SockaSession<TContract>;
}
type SockaSession<TContract extends SockaContractBound> = SockaSessionBase<TContract>;
/**
* WebSocket session: **`session.send`** for contract calls, **`session.subscribe`** for server pushes.
*/
declare const SockaSession: SockaSessionConstructor;
export { type SockaConnectionStatus, type SockaReconnectConfig, type SockaReconnectedInfo, type SockaReconnectingInfo, SockaReportError, SockaSession, type SockaSessionConstructor, type SockaSessionOptions, type SockaSessionPushWaitOptions, type SockaSessionSubscribeApi, SockaWebSocketClient, type SockaWebSocketClientOptions };