GraphQL over WebSocket client implementation providing real-time GraphQL subscriptions with comprehensive connection management, event handling, and retry capabilities.
Creates a disposable GraphQL over WebSocket client with extensive configuration options.
/**
* Creates a disposable GraphQL over WebSocket client
* @param options - Client configuration options
* @returns Client instance with subscription and event capabilities
*/
function createClient<P = Record<string, unknown>>(
options: ClientOptions<P>
): Client;
interface ClientOptions<P = Record<string, unknown>> {
/** WebSocket URL or function returning URL */
url: string | (() => Promise<string> | string);
/** Connection parameters or function returning them */
connectionParams?: P | (() => Promise<P> | P);
/** Whether to establish connection lazily (default: true) */
lazy?: boolean;
/** Error handler for non-lazy connections */
onNonLazyError?: (errorOrCloseEvent: unknown) => void;
/** Timeout for lazy connection closure in milliseconds */
lazyCloseTimeout?: number;
/** Keep-alive ping interval in milliseconds */
keepAlive?: number;
/** Connection acknowledgment wait timeout in milliseconds */
connectionAckWaitTimeout?: number;
/** Disable automatic pong responses */
disablePong?: boolean;
/** Number of retry attempts for failed connections */
retryAttempts?: number;
/** Retry delay strategy function */
retryWait?: (retries: number) => Promise<void>;
/** Function to determine if retry should be attempted */
shouldRetry?: (errOrCloseEvent: unknown) => boolean;
/** Event listeners for connection lifecycle */
on?: Partial<{
[E in Event]: EventListener<E>;
}>;
/** Custom WebSocket implementation */
webSocketImpl?: unknown;
/** Custom subscription ID generator */
generateID?: (payload: SubscribePayload) => ID;
/** JSON message reviver for parsing */
jsonMessageReviver?: JSONMessageReviver;
/** JSON message replacer for stringifying */
jsonMessageReplacer?: JSONMessageReplacer;
}Usage Examples:
import { createClient } from "graphql-ws";
// Basic client
const client = createClient({
url: "ws://localhost:4000/graphql",
});
// Client with authentication
const authClient = createClient({
url: "ws://localhost:4000/graphql",
connectionParams: {
authToken: "your-auth-token",
},
});
// Client with retry configuration
const resilientClient = createClient({
url: "ws://localhost:4000/graphql",
retryAttempts: 5,
retryWait: async (retries) => {
await new Promise(resolve => setTimeout(resolve, Math.pow(2, retries) * 1000));
},
shouldRetry: (err) => {
// Retry on connection errors but not on auth failures
return err instanceof CloseEvent && err.code !== CloseCode.Unauthorized;
},
});Main client interface providing subscription management and event handling.
interface Client extends Disposable {
/** Register event listener and return unsubscribe function */
on<E extends Event>(event: E, listener: EventListener<E>): () => void;
/** Subscribe to GraphQL operation and return unsubscribe function */
subscribe<Data = Record<string, unknown>, Extensions = unknown>(
payload: SubscribePayload,
sink: Sink<FormattedExecutionResult<Data, Extensions>>
): () => void;
/** Subscribe using async iterator pattern */
iterate<Data = Record<string, unknown>, Extensions = unknown>(
payload: SubscribePayload
): AsyncIterableIterator<FormattedExecutionResult<Data, Extensions>>;
/** Terminate connection immediately */
terminate(): void;
/** Dispose client and clean up resources */
dispose(): void;
}Usage Examples:
// Subscription with callback pattern
const unsubscribe = client.subscribe(
{
query: `
subscription MessageAdded($channel: String!) {
messageAdded(channel: $channel) {
id
content
user
timestamp
}
}
`,
variables: { channel: "general" },
},
{
next: (result) => {
console.log("New message:", result.data?.messageAdded);
},
error: (err) => {
console.error("Subscription error:", err);
},
complete: () => {
console.log("Subscription completed");
},
}
);
// Subscription with async iterator
for await (const result of client.iterate({
query: "subscription { notifications { id message } }",
})) {
console.log("Notification:", result.data?.notifications);
}Comprehensive event system for monitoring connection lifecycle and protocol messages.
type Event =
| "connecting"
| "opened"
| "connected"
| "ping"
| "pong"
| "message"
| "closed"
| "error";
type EventListener<E extends Event> = E extends "connecting"
? (isRetry: boolean) => void
: E extends "opened"
? (socket: unknown) => void
: E extends "connected"
? (socket: unknown, payload?: Record<string, unknown>, wasRetry?: boolean) => void
: E extends "ping"
? (received: boolean, payload: PingMessage['payload']) => void
: E extends "pong"
? (received: boolean, payload: PongMessage['payload']) => void
: E extends "message"
? (message: Message) => void
: E extends "closed"
? (event: CloseEvent) => void
: E extends "error"
? (error: unknown) => void
: never;Usage Examples:
// Monitor connection state
client.on("connecting", () => {
console.log("Connecting to server...");
});
client.on("connected", (socket, payload) => {
console.log("Connected successfully", payload);
});
client.on("closed", (event) => {
console.log(`Connection closed: ${event.code} ${event.reason}`);
});
client.on("error", (error) => {
console.error("Client error:", error);
});
// Monitor protocol messages
client.on("ping", (pingMessage) => {
console.log("Received ping");
});
client.on("message", (message) => {
console.log("Protocol message:", message.type);
});Structure for GraphQL operation requests sent over the WebSocket connection.
interface SubscribePayload {
/** GraphQL operation string (query, mutation, or subscription) */
query: string;
/** Operation name for multi-operation documents */
operationName?: string;
/** Variables for the GraphQL operation */
variables?: Record<string, unknown>;
/** Additional extensions */
extensions?: Record<string, unknown>;
}Observer pattern interface for handling subscription results.
interface Sink<T = unknown> {
/** Called for each result value */
next(value: T): void;
/** Called when an error occurs */
error(error: unknown): void;
/** Called when the subscription completes */
complete(): void;
}Special close event class for tracking client-initiated terminations.
class TerminatedCloseEvent extends Error implements CloseEvent {
readonly name = "TerminatedCloseEvent";
readonly message = "4499: Terminated";
readonly code = 4499;
readonly reason = "Terminated";
readonly wasClean = false;
}Usage Examples:
// Handle termination in event listener
client.on("closed", (event) => {
if (event instanceof TerminatedCloseEvent) {
console.log("Client was terminated manually");
} else {
console.log("Connection closed by server or network");
}
});
// Terminate when needed
client.terminate();const client = createClient({
url: "ws://localhost:4000/graphql",
retryAttempts: 3,
shouldRetry: (errOrCloseEvent) => {
// Don't retry on authentication errors
if (errOrCloseEvent instanceof CloseEvent) {
return errOrCloseEvent.code !== CloseCode.Unauthorized;
}
return true;
},
});const client = createClient({
url: "ws://localhost:4000/graphql",
connectionParams: async () => {
const token = await getAuthToken();
return { authorization: `Bearer ${token}` };
},
});import WebSocket from "ws";
const client = createClient({
url: "ws://localhost:4000/graphql",
webSocketImpl: WebSocket,
});