or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

batch-operations.mderror-handling.mdgraphql-client.mdindex.mdraw-requests.mdstatic-functions.md
tile.json

error-handling.mddocs/

Error Handling

Comprehensive error handling system providing detailed context about failed requests, GraphQL errors, and network issues. The error handling system includes custom error classes, error policies, and detailed error context information.

Capabilities

Client Error Class

Custom error class that extends the standard Error with GraphQL-specific context and response information.

/**
 * Custom error class for GraphQL request failures
 * Contains both the GraphQL response and request context
 */
class ClientError extends Error {
  /** Complete GraphQL response containing errors and metadata */
  response: GraphQLResponse;
  /** Request context with query and variables */
  request: GraphQLRequestContext;
  
  /**
   * Create a new ClientError instance
   * @param response - GraphQL response containing errors
   * @param request - Request context information
   */
  constructor(response: GraphQLResponse, request: GraphQLRequestContext);
}

Usage Examples:

import { request, ClientError, gql } from "graphql-request";

const query = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      secretField  # This field might not exist or require permissions
    }
  }
`;

try {
  const data = await request("https://api.example.com/graphql", query, {
    id: "invalid-id",
  });
} catch (error) {
  if (error instanceof ClientError) {
    console.log("GraphQL errors:", error.response.errors);
    console.log("Request query:", error.request.query);
    console.log("Request variables:", error.request.variables);
    console.log("Response status:", error.response.status);
    
    // Handle specific error types
    const authErrors = error.response.errors?.filter(
      err => err.extensions?.code === "UNAUTHENTICATED"
    );
    
    if (authErrors && authErrors.length > 0) {
      console.log("Authentication required - redirecting to login");
      // Handle authentication error
    }
  } else {
    console.error("Network or other error:", error);
  }
}

GraphQL Response Structure

Standard GraphQL response structure that may contain errors alongside data.

interface GraphQLResponse<T = unknown> {
  /** Response data (may be null or partial if errors occurred) */
  data?: T;
  /** Array of GraphQL errors */
  errors?: GraphQLError[];
  /** Server-specific extensions */
  extensions?: unknown;
  /** HTTP status code */
  status: number;
  /** Additional response fields */
  [key: string]: unknown;
}

interface GraphQLError {
  /** Error message */
  message: string;
  /** Source locations where the error occurred */
  locations?: Array<{
    line: number;
    column: number;
  }>;
  /** Path to the field that caused the error */
  path?: Array<string | number>;
  /** Additional error information */
  extensions?: {
    code?: string;
    exception?: {
      stacktrace?: string[];
    };
    [key: string]: any;
  };
}

GraphQL Request Context

Context information about the request that caused the error.

interface GraphQLRequestContext<V extends Variables = Variables> {
  /** GraphQL query string or array of queries (for batch requests) */
  query: string | string[];
  /** Variables used in the request */
  variables?: V;
}

Error Policies

Error policies control how the client handles GraphQL errors in responses.

Error Policy Types

/**
 * Error handling policy for GraphQL responses
 * - 'none': Throw on any GraphQL errors (default)
 * - 'ignore': Ignore GraphQL errors, return data if available
 * - 'all': Return both data and errors (for rawRequest only)
 */
type ErrorPolicy = "none" | "ignore" | "all";

Usage Examples:

import { GraphQLClient } from "graphql-request";

// Default: throw on any errors
const strictClient = new GraphQLClient("https://api.example.com/graphql", {
  errorPolicy: "none", // default behavior
});

// Ignore errors, return partial data
const lenientClient = new GraphQLClient("https://api.example.com/graphql", {
  errorPolicy: "ignore",
});

// Return both data and errors (use with rawRequest)
const debugClient = new GraphQLClient("https://api.example.com/graphql", {
  errorPolicy: "all",
});

// Example with lenient error handling
try {
  const data = await lenientClient.request(`
    {
      user(id: "123") {
        name
        secretField  # might fail
      }
      posts {
        title  # this might still succeed
      }
    }
  `);
  
  // data will contain posts even if user.secretField failed
  console.log("Data received:", data);
} catch (error) {
  // Only throws for network errors or server errors
  console.error("Request failed completely:", error);
}

Advanced Error Handling Patterns

Error Classification and Recovery

import { request, ClientError, gql } from "graphql-request";

interface ErrorHandlingResult<T> {
  data?: T;
  handled: boolean;
  shouldRetry: boolean;
  error?: Error;
}

async function handleGraphQLRequest<T>(
  url: string,
  query: string,
  variables?: any
): Promise<ErrorHandlingResult<T>> {
  try {
    const data = await request<T>(url, query, variables);
    return { data, handled: true, shouldRetry: false };
  } catch (error) {
    if (error instanceof ClientError) {
      const { response } = error;
      
      // Handle specific GraphQL error codes
      const errors = response.errors || [];
      
      for (const gqlError of errors) {
        const errorCode = gqlError.extensions?.code;
        
        switch (errorCode) {
          case "UNAUTHENTICATED":
            console.log("Authentication required");
            // Trigger re-authentication
            return { handled: true, shouldRetry: true, error };
            
          case "RATE_LIMITED":
            console.log("Rate limited, waiting before retry");
            const retryAfter = gqlError.extensions?.retryAfter || 1000;
            await new Promise(resolve => setTimeout(resolve, retryAfter));
            return { handled: true, shouldRetry: true, error };
            
          case "VALIDATION_ERROR":
            console.log("Input validation failed:", gqlError.message);
            // Don't retry validation errors
            return { handled: true, shouldRetry: false, error };
            
          case "INTERNAL_ERROR":
            console.log("Server error, may retry");
            return { handled: true, shouldRetry: true, error };
            
          default:
            console.log("Unhandled GraphQL error:", gqlError.message);
        }
      }
      
      // Check HTTP status for network-level errors
      if (response.status >= 500) {
        console.log("Server error, may retry");
        return { handled: true, shouldRetry: true, error };
      }
      
      return { handled: false, shouldRetry: false, error };
    } else {
      // Network or parsing errors
      console.error("Network error:", error);
      return { handled: true, shouldRetry: true, error };
    }
  }
}

// Usage with retry logic
async function requestWithRetry<T>(
  url: string,
  query: string,
  variables?: any,
  maxRetries: number = 3
): Promise<T> {
  let attempts = 0;
  
  while (attempts < maxRetries) {
    const result = await handleGraphQLRequest<T>(url, query, variables);
    
    if (result.data) {
      return result.data;
    }
    
    if (!result.shouldRetry) {
      throw result.error || new Error("Request failed without retry");
    }
    
    attempts++;
    console.log(`Retry attempt ${attempts}/${maxRetries}`);
    
    // Exponential backoff
    if (attempts < maxRetries) {
      await new Promise(resolve => 
        setTimeout(resolve, Math.pow(2, attempts) * 1000)
      );
    }
  }
  
  throw new Error(`Request failed after ${maxRetries} attempts`);
}

Partial Error Handling with Raw Requests

import { rawRequest, GraphQLClient } from "graphql-request";

// Using error policy "all" to get both data and errors
const client = new GraphQLClient("https://api.example.com/graphql", {
  errorPolicy: "all",
});

async function handlePartialErrors() {
  const query = `
    {
      user(id: "123") {
        name
        email
        secretField  # might fail due to permissions
      }
      posts {
        id
        title
        content
      }
    }
  `;
  
  try {
    const response = await client.rawRequest(query);
    
    // Check for errors
    if (response.errors && response.errors.length > 0) {
      console.log("GraphQL errors occurred:");
      
      response.errors.forEach((error, index) => {
        console.log(`Error ${index + 1}:`, error.message);
        
        if (error.path) {
          console.log("  Path:", error.path.join("."));
        }
        
        if (error.extensions?.code) {
          console.log("  Code:", error.extensions.code);
        }
      });
      
      // Check which fields have data despite errors
      if (response.data) {
        console.log("Available data:", Object.keys(response.data));
        
        // Handle partial data
        if (response.data.user) {
          console.log("User data available:", response.data.user);
        }
        
        if (response.data.posts) {
          console.log("Posts data available:", response.data.posts.length, "posts");
        }
      }
    }
    
    // Return data even if some fields failed
    return response.data;
  } catch (error) {
    // This should only happen for network errors with errorPolicy: "all"
    console.error("Request completely failed:", error);
    throw error;
  }
}

Custom Error Middleware

import { GraphQLClient } from "graphql-request";

// Response middleware for centralized error handling
const errorHandlingMiddleware = async (response, request) => {
  // Only handle actual errors
  if (response instanceof Error) {
    console.error("Request failed:", {
      operationName: request.operationName,
      variables: request.variables,
      url: request.url,
      error: response.message,
    });
    
    // Log to external service
    if (process.env.NODE_ENV === "production") {
      // await logToExternalService({
      //   type: "graphql_error",
      //   operation: request.operationName,
      //   error: response.message,
      //   timestamp: new Date().toISOString(),
      // });
    }
    
    return; // Let the error propagate
  }
  
  // Handle successful responses with GraphQL errors
  if (response.errors && response.errors.length > 0) {
    const criticalErrors = response.errors.filter(
      error => error.extensions?.severity === "critical"
    );
    
    if (criticalErrors.length > 0) {
      console.error("Critical GraphQL errors detected:", criticalErrors);
      
      // Could trigger alerts, logging, etc.
    }
  }
};

// Client with error middleware
const client = new GraphQLClient("https://api.example.com/graphql", {
  responseMiddleware: errorHandlingMiddleware,
});

Error Boundaries for React Applications

import { request, ClientError } from "graphql-request";

// Error context for React applications
interface GraphQLErrorInfo {
  query: string;
  variables?: any;
  errors: any[];
  timestamp: Date;
}

class GraphQLErrorBoundary {
  private static errorLog: GraphQLErrorInfo[] = [];
  
  static async safeRequest<T>(
    url: string,
    query: string,
    variables?: any
  ): Promise<T | null> {
    try {
      return await request<T>(url, query, variables);
    } catch (error) {
      const errorInfo: GraphQLErrorInfo = {
        query,
        variables,
        errors: error instanceof ClientError ? error.response.errors || [] : [error],
        timestamp: new Date(),
      };
      
      this.errorLog.push(errorInfo);
      
      // In React, you might dispatch to an error context
      console.error("GraphQL request failed:", errorInfo);
      
      return null;
    }
  }
  
  static getErrorLog(): GraphQLErrorInfo[] {
    return [...this.errorLog];
  }
  
  static clearErrorLog(): void {
    this.errorLog = [];
  }
}

// Usage in React components
async function loadUserData(userId: string) {
  const userData = await GraphQLErrorBoundary.safeRequest(
    "https://api.example.com/graphql",
    `
      query GetUser($id: ID!) {
        user(id: $id) {
          name
          email
        }
      }
    `,
    { id: userId }
  );
  
  if (!userData) {
    // Handle null case (error occurred)
    return { user: null, hasError: true };
  }
  
  return { user: userData.user, hasError: false };
}

Supporting Types

type Variables = object;

interface JsonSerializer {
  stringify: (obj: any) => string;
  parse: (obj: string) => unknown;
}

type ResponseMiddleware = (
  response: GraphQLClientResponse<unknown> | ClientError | Error,
  request: RequestInitExtended
) => Promise<void> | void;

interface RequestInitExtended<V extends Variables = Variables> extends RequestInit {
  url: string;
  operationName?: string;
  variables?: V;
}