Coherent, zero-dependency, lazy, simple, GraphQL over WebSocket Protocol compliant server and client
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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" },
];