Shared types, constants, and utilities used by both client and server implementations, providing the complete GraphQL over WebSocket Protocol message types and processing functions.
Core protocol identifiers and version information.
/** The WebSocket sub-protocol used for GraphQL over WebSocket Protocol */
const GRAPHQL_TRANSPORT_WS_PROTOCOL = "graphql-transport-ws";
/** Deprecated subprotocol used by subscriptions-transport-ws (private) */
const DEPRECATED_GRAPHQL_WS_PROTOCOL = "graphql-ws";Usage Examples:
import { GRAPHQL_TRANSPORT_WS_PROTOCOL } from "graphql-ws";
// WebSocket client connection
const ws = new WebSocket("ws://localhost:4000/graphql", GRAPHQL_TRANSPORT_WS_PROTOCOL);
// Server subprotocol handling
const server = new WebSocketServer({
handleProtocols: (protocols) => {
return protocols.includes(GRAPHQL_TRANSPORT_WS_PROTOCOL)
? GRAPHQL_TRANSPORT_WS_PROTOCOL
: false;
},
});Standard close codes for different error and termination conditions as defined by the GraphQL over WebSocket Protocol.
enum CloseCode {
/** Internal server error */
InternalServerError = 4500,
/** Internal client error */
InternalClientError = 4005,
/** Bad request - malformed message or invalid payload */
BadRequest = 4400,
/** Bad response from server */
BadResponse = 4004,
/** Unauthorized - tried subscribing before connect ack */
Unauthorized = 4401,
/** Forbidden - insufficient permissions */
Forbidden = 4403,
/** Subprotocol not acceptable */
SubprotocolNotAcceptable = 4406,
/** Connection initialization timeout */
ConnectionInitialisationTimeout = 4408,
/** Connection acknowledgment timeout */
ConnectionAcknowledgementTimeout = 4504,
/** Subscriber already exists - subscriber distinction is very important */
SubscriberAlreadyExists = 4409,
/** Too many initialization requests */
TooManyInitialisationRequests = 4429,
}Usage Examples:
import { CloseCode } from "graphql-ws";
// Client error handling
client.on("closed", (event) => {
switch (event.code) {
case CloseCode.Unauthorized:
console.log("Authentication failed");
redirectToLogin();
break;
case CloseCode.Forbidden:
console.log("Insufficient permissions");
showPermissionError();
break;
case CloseCode.BadRequest:
console.log("Malformed request");
break;
default:
console.log("Connection closed:", event.code, event.reason);
}
});
// Server closing connection
server.close(CloseCode.Unauthorized, "Invalid token");Protocol message types for the GraphQL over WebSocket communication.
enum MessageType {
/** Client -> Server: Initialize connection */
ConnectionInit = "connection_init",
/** Server -> Client: Acknowledge connection */
ConnectionAck = "connection_ack",
/** Bidirectional: Ping for keep-alive */
Ping = "ping",
/** Bidirectional: Pong response to ping */
Pong = "pong",
/** Client -> Server: Start subscription/query/mutation */
Subscribe = "subscribe",
/** Server -> Client: Send result data */
Next = "next",
/** Server -> Client: Send error */
Error = "error",
/** Server -> Client: Complete operation */
Complete = "complete",
}Fundamental types used throughout the GraphQL over WebSocket implementation.
/** Globally unique ID for identifying subscriptions */
type ID = string;
/** Function for transforming values during JSON parsing */
type JSONMessageReviver = (this: any, key: string, value: any) => any;
/** Function for customizing JSON string production */
type JSONMessageReplacer = (this: any, key: string, value: any) => any;Interface for resources that need cleanup.
interface Disposable {
/** Dispose of the resource and perform cleanup */
dispose(): void;
}Usage Examples:
import { createClient } from "graphql-ws";
const client = createClient({ url: "ws://localhost:4000/graphql" });
// Client implements Disposable
process.on("SIGTERM", () => {
client.dispose(); // Clean up resources
});Stream representation interface for handling values over time.
interface Sink<T = unknown> {
/** Called for each emitted value */
next(value: T): void;
/** Called when an error occurs */
error(error: unknown): void;
/** Called when the stream completes */
complete(): void;
}Usage Examples:
const sink: Sink<ExecutionResult> = {
next: (result) => {
console.log("Received result:", result.data);
},
error: (err) => {
console.error("Stream error:", err);
},
complete: () => {
console.log("Stream completed");
},
};
client.subscribe({ query: "subscription { messages }" }, sink);Protocol message interfaces for client-server communication.
interface ConnectionInitMessage {
readonly type: "connection_init";
readonly payload?: Record<string, unknown>;
}
interface ConnectionAckMessage {
readonly type: "connection_ack";
readonly payload?: Record<string, unknown>;
}
interface PingMessage {
readonly type: "ping";
readonly payload?: Record<string, unknown>;
}
interface PongMessage {
readonly type: "pong";
readonly payload?: Record<string, unknown>;
}
interface SubscribeMessage {
readonly type: "subscribe";
readonly id: ID;
readonly payload: SubscribePayload;
}
interface NextMessage {
readonly type: "next";
readonly id: ID;
readonly payload: FormattedExecutionResult;
}
interface ErrorMessage {
readonly type: "error";
readonly id: ID;
readonly payload: readonly GraphQLFormattedError[];
}
interface CompleteMessage {
readonly type: "complete";
readonly id: ID;
}
type Message<T extends MessageType = MessageType> = T extends "connection_init"
? ConnectionInitMessage
: T extends "connection_ack"
? ConnectionAckMessage
: T extends "ping"
? PingMessage
: T extends "pong"
? PongMessage
: T extends "subscribe"
? SubscribeMessage
: T extends "next"
? NextMessage
: T extends "error"
? ErrorMessage
: T extends "complete"
? CompleteMessage
: never;Types for GraphQL operation payloads and results.
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>;
}
interface ExecutionResult<Data = Record<string, unknown>, Extensions = unknown> {
/** Operation result data */
data?: Data | null;
/** GraphQL errors that occurred during execution */
errors?: ReadonlyArray<GraphQLError>;
/** Indicates if there are more results coming */
hasNext?: boolean;
/** Extensions from execution */
extensions?: Extensions;
}
interface FormattedExecutionResult<Data = Record<string, unknown>, Extensions = unknown> {
/** Operation result data */
data?: Data | null;
/** Formatted GraphQL errors */
errors?: ReadonlyArray<GraphQLFormattedError>;
/** Indicates if there are more results coming */
hasNext?: boolean;
/** Extensions from execution */
extensions?: Extensions;
}Functions for validating, parsing, and stringifying protocol messages.
/**
* Validates a value as a protocol-compliant message
* @param val - Value to validate
* @returns Validated message
* @throws Error if validation fails
*/
function validateMessage(val: unknown): Message;
/**
* Parses raw WebSocket message data into protocol message
* @param data - Raw message data
* @param reviver - Optional JSON reviver function
* @returns Parsed protocol message
* @throws Error if parsing fails
*/
function parseMessage(data: unknown, reviver?: JSONMessageReviver): Message;
/**
* Stringifies protocol message for sending over WebSocket
* @param msg - Protocol message to stringify
* @param replacer - Optional JSON replacer function
* @returns Stringified message
*/
function stringifyMessage<T extends MessageType>(
msg: Message<T>,
replacer?: JSONMessageReplacer
): string;Usage Examples:
import { parseMessage, stringifyMessage, validateMessage } from "graphql-ws";
// Parse incoming WebSocket message
websocket.onmessage = (event) => {
try {
const message = parseMessage(event.data);
console.log("Received:", message.type);
// Validate message structure
validateMessage(message);
// Handle message...
} catch (error) {
console.error("Invalid message:", error);
}
};
// Send outgoing message
const subscribeMsg = {
type: "subscribe" as const,
id: "sub_1",
payload: {
query: "subscription { messages }",
},
};
websocket.send(stringifyMessage(subscribeMsg));The GraphQL over WebSocket Protocol defines a specific message flow:
connection_init with optional connection parametersconnection_acksubscribe with GraphQL operationnext messages with results (for subscriptions/queries)error messages if errors occurcomplete message when operation finishesping messagespong messagesComplete Example:
// Message flow example
const messages = [
// 1. Connection initialization
{ type: "connection_init", payload: { authToken: "..." } },
{ type: "connection_ack" },
// 2. Start subscription
{
type: "subscribe",
id: "sub_1",
payload: { query: "subscription { messageAdded { id content } }" }
},
// 3. Receive results
{
type: "next",
id: "sub_1",
payload: { data: { messageAdded: { id: "1", content: "Hello" } } }
},
{
type: "next",
id: "sub_1",
payload: { data: { messageAdded: { id: "2", content: "World" } } }
},
// 4. Keep-alive
{ type: "ping" },
{ type: "pong" },
// 5. Complete subscription
{ type: "complete", id: "sub_1" },
];