defineWebSocket, session, and request options.
WebSocket
Declare a socket, call the operation with a client, send and receive typed messages, then close.
defineWebSocket()
function defineWebSocket<
const P extends string,
TInput extends AnyStruct | undefined = undefined,
TIncoming extends AnyStruct = AnyStruct,
TOutgoing extends AnyStruct | undefined = undefined,
>(
definition: Omit<WebSocketDefinitionBase<TIncoming, TOutgoing>, "path"> & {
input?: TInput;
path: P;
} & BuilderField<NoInfer<TInput>, WebSocketRequestShape<NoInfer<P>>>,
): WebSocketEndpoint<
EndpointArgs<TInput>,
WebSocketIncomingData<TIncoming>,
WebSocketOutgoingData<TOutgoing>
>;- definition —
path, oneincomingstruct, and the required positive-integermaxIncomingQueueSize; optionaloutgoingandinputstructs,operation, staticprotocols, and a non-negativemaxOutgoingQueueSize. A declaredinputalways requires an explicitbuilder.inputonly defines the parameters; the builder alone decides where each value goes. Only definitions withoutinputmay omitbuilder. Thesedefinitionmembers are documented underWebSocketDefinition.builderis required exactly wheninputis declared. - Returns an operation. The first argument is a
Client. Further arguments are the input only. Calling it opens the session.
import { defineWebSocket, struct } from "@defjs/core";
const chat = defineWebSocket({
maxIncomingQueueSize: 100,
path: "/chat",
protocols: ["chat.v1"],
incoming: struct.object({
text: struct.string(),
type: struct.literal("message"),
}),
outgoing: struct.object({
text: struct.string(),
type: struct.literal("message"),
}),
});The call returns [null, session, connection] or [error, undefined, connection?]. Pass per-call options by deriving a client from RequestOptions. WebSocket reads signal, beforeConnect, heartbeat, protocols, and reconnect, and ignores HTTP progress fields.
beforeConnect, heartbeat, protocols, and reconnect each replace the client value for that call as a whole. Protocol precedence is the derived client, then withWebSocketProtocols, then the definition's protocols. heartbeat.timeoutMs is the acknowledgement wait, not a request deadline.
WebSocketSession
interface WebSocketSession<
TIncoming = unknown,
TOutgoing = never,
> extends AsyncDisposable {
readonly bufferedAmount: number;
readonly connection: WebSocketConnectionInfo;
readonly closed: Promise<WebSocketCloseInfo>;
readonly receive: AsyncIterable<TIncoming>;
readonly state: WebSocketState;
close(code?: number, reason?: string): void;
[Symbol.asyncDispose](): PromiseLike<void>;
onRuntimeError(listener: (error: unknown) => void): () => void;
onStateChange(listener: (state: WebSocketState) => void): () => void;
send(message: TOutgoing): void;
}Prefer await using ownedSession = session for owned scope cleanup. Manual session.close() and await session.closed remain available.
async function consume(
session: WebSocketSession<unknown, never>,
): Promise<void> {
await using ownedSession = session;
for await (const message of ownedSession.receive) console.log(message);
}closed reports the logical terminal state. The async disposer requests native close and waits for Defjs-owned teardown, but bounds a missing close event at one second and may reject with a DOMException named TimeoutError. It cannot prove that the physical TCP connection closed.
WebSocketState
'idle' | 'connecting' | 'open' | 'closing' | 'closed' | 'reconnecting' | 'aborted' | 'error'
WebSocketConnectionInfo
generation (increments on reconnect), and, once known, url, protocol and extensions.
WebSocketCloseInfo
type WebSocketCloseInfo =
| { kind: "closed"; code?: number; reason?: string; wasClean?: boolean }
| {
kind: "aborted";
cause?: unknown;
code?: number;
reason?: string;
wasClean?: boolean;
}
| {
kind: "error";
cause: unknown;
code?: number;
reason?: string;
wasClean?: boolean;
};kind is closed, aborted, or error. cause is absent on closed, optional on aborted, and required on error. code, reason, and wasClean may appear on every variant. A manual session.close() fills code and reason on { kind: "closed" } when the close event carries none.
WebSocketExecuteOptions
type WebSocketExecuteOptions<
TIncoming = unknown,
TOutgoing = unknown,
> = UseWebSocketConfig<TIncoming, TOutgoing>;WebSocketExecuteOptions is UseWebSocketConfig: beforeConnect, heartbeat, protocols, reconnect, and signal. WebSocketHeartbeatConfig: intervalMs, optional message, isAck, timeoutMs. There is no timeout or abort field.
signal covers the whole session, including after it opens. A deadline is AbortSignal.timeout(ms) and classifies as NET_TIMEOUT. A manual abort is NET_ABORTED. There is no startup-only deadline and no client-level default deadline. signal is the call's signal; combine signals with AbortSignal.any in application code. heartbeat is replaced whole; its timeoutMs waits for an acknowledgement and is not a request deadline.
When reconnect is set, attempts defaults to 3 per outage; 0 disables. Successful open resets the budget and backoff exponent, so each new outage starts at retry 1. Omitting attempts does not give unlimited retries within one outage. See Client for the defaults and validation rules.
Messages
incoming is one struct. Text, ArrayBuffer, typed-array, and Blob frames are read as UTF-8, JSON-parsed, then checked against that struct. A frame that fails JSON parse or the struct is reported to session.onRuntimeError and is not queued. There is no client invalid-frame option.
outgoing is one struct. session.send accepts StructInput of that struct, encodes aliases, then JSON.stringify. Values JSON cannot faithfully represent after encoding throw TypeError before sending or entering the reconnect queue, including non-finite numbers, functions, symbols, Map, Set, streams, and class instances without a JSON representation. Nested values use the same guard as HTTP JSON bodies. Heartbeat uses that guard too; a heartbeat serialization failure ends the session. Several message kinds are a struct.discriminatedUnion.
session.send({ type: "message", text: "Hello" });See WebSocket guide and Open a WebSocket session.
WebSocketDefinition
path, incoming, required maxIncomingQueueSize, optional outgoing / input / operation / protocols / maxOutgoingQueueSize. The builder may return path and query only. A declared input always requires an explicit builder. input only defines the parameters; the builder alone decides where each value goes. Only definitions without input may omit builder.
WebSocketEndpoint
Returned by defineWebSocket. The first argument is a client. Further arguments are the input only.
WebSocketRequestShape
path and query only. headers and body are never.
UseWebSocketConfig
beforeConnect, heartbeat, protocols, reconnect, and signal. WebSocketExecuteOptions is this slice. There is no abort field.
SocketAwaitResult
[null, session, connection] or [error, undefined, connection?].
ManualSocketCloseReason
Reason recorded when the session is closed via session.close().
WebSocketHeartbeatConfig
intervalMs, optional message, isAck, timeoutMs.
WebSocketIncomingData
Decoded incoming frame: Infer of the incoming struct.
WebSocketOutgoingData
Value accepted by session.send: StructInput of the outgoing struct, or never when none is declared.
SocketLifecycleOutcome
Outcome of one connect attempt (closeInfo, connection, cause, opened), used by reconnect logic. It is not the final result of the session; see WebSocketCloseInfo for that.