CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-graphql-ws

Coherent, zero-dependency, lazy, simple, GraphQL over WebSocket Protocol compliant server and client

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

client.mddocs/

Client API

GraphQL over WebSocket client implementation providing real-time GraphQL subscriptions with comprehensive connection management, event handling, and retry capabilities.

Capabilities

Client Creation

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;
  },
});

Client Interface

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);
}

Event System

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);
});

Subscription Payload

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>;
}

Result Sink

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;
}

Termination Handling

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();

Advanced Usage Patterns

Conditional Reconnection

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;
  },
});

Dynamic Connection Parameters

const client = createClient({
  url: "ws://localhost:4000/graphql",
  connectionParams: async () => {
    const token = await getAuthToken();
    return { authorization: `Bearer ${token}` };
  },
});

Custom WebSocket Implementation

import WebSocket from "ws";

const client = createClient({
  url: "ws://localhost:4000/graphql",
  webSocketImpl: WebSocket,
});

docs

client.md

common.md

index.md

server-adapters.md

server.md

tile.json