CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-urql--core

The shared core for the highly customizable and versatile GraphQL client

Pending
Overview
Eval results
Files

errors.mddocs/

Error Handling

Comprehensive error handling system for GraphQL and network errors, providing unified error representation and detailed error information for debugging and user feedback.

Capabilities

CombinedError Class

Unified error class that combines GraphQL errors and network errors into a single error instance.

/**
 * Combined error class for GraphQL and network errors
 */
class CombinedError extends Error {
  /** Network-level error (fetch failures, timeouts, etc.) */
  networkError?: Error;
  /** Array of GraphQL execution errors */
  graphQLErrors: GraphQLError[];
  /** HTTP response that caused the error */
  response?: Response;

  /**
   * Create a new CombinedError
   * @param params - Error parameters
   */
  constructor(params: {
    networkError?: Error;
    graphQLErrors?: readonly GraphQLError[];
    response?: Response;
  });

  /** Error message combining network and GraphQL errors */
  message: string;
  /** Error name */
  name: string;
}

Usage Examples:

import { CombinedError } from "@urql/core";

// Handle errors from query results
const result = await client.query(GetUserQuery, { id: "123" }).toPromise();

if (result.error) {
  const error = result.error;
  
  // Check for network errors
  if (error.networkError) {
    console.error("Network error:", error.networkError.message);
    
    // Handle specific network errors
    if (error.networkError.message.includes('Failed to fetch')) {
      showOfflineMessage();
    }
  }
  
  // Check for GraphQL errors
  if (error.graphQLErrors.length > 0) {
    error.graphQLErrors.forEach(gqlError => {
      console.error("GraphQL error:", gqlError.message);
      
      // Handle specific GraphQL error types
      if (gqlError.extensions?.code === 'UNAUTHENTICATED') {
        redirectToLogin();
      }
    });
  }
  
  // Access HTTP response for status codes
  if (error.response) {
    console.log("Response status:", error.response.status);
    console.log("Response headers:", error.response.headers);
  }
}

// Create custom combined errors
const customError = new CombinedError({
  networkError: new Error("Connection timeout"),
  graphQLErrors: [{
    message: "User not found",
    locations: [{ line: 2, column: 3 }],
    path: ["user"],
    extensions: { code: "USER_NOT_FOUND" }
  }],
});

Error Result Creation

Create error operation results for custom exchanges and error handling.

/**
 * Create an error OperationResult from an error
 * @param operation - The operation that failed
 * @param error - Error instance (Error or CombinedError)
 * @param response - Optional ExecutionResult with partial data
 * @returns OperationResult with error information
 */
function makeErrorResult<Data = any, Variables extends AnyVariables = AnyVariables>(
  operation: Operation<Data, Variables>,
  error: Error | CombinedError,
  response?: ExecutionResult
): OperationResult<Data, Variables>;

Usage Examples:

import { makeErrorResult, CombinedError } from "@urql/core";

// In a custom exchange
const customExchange: Exchange = ({ forward }) => ops$ => {
  return pipe(
    ops$,
    mergeMap(operation => {
      try {
        return forward(fromValue(operation));
      } catch (error) {
        // Create error result for exceptions
        return fromValue(makeErrorResult(
          operation,
          new CombinedError({
            networkError: error as Error
          })
        ));
      }
    })
  );
};

// Handle timeout errors
const timeoutExchange: Exchange = ({ forward }) => ops$ => {
  return pipe(
    ops$,
    mergeMap(operation => {
      const timeout$ = pipe(
        timer(30000), // 30 second timeout
        map(() => makeErrorResult(
          operation,
          new CombinedError({
            networkError: new Error("Request timeout")
          })
        ))
      );
      
      return pipe(
        race([forward(fromValue(operation)), timeout$]),
        take(1)
      );
    })
  );
};

GraphQL Error Types

Standard GraphQL error interface and extensions.

interface GraphQLError {
  /** Error message */
  message: string;
  /** Source locations where error occurred */
  locations?: readonly GraphQLErrorLocation[];
  /** Path to the field that caused the error */
  path?: readonly (string | number)[];
  /** Additional error information */
  extensions?: GraphQLErrorExtensions;
}

interface GraphQLErrorLocation {
  /** Line number in GraphQL document */
  line: number;
  /** Column number in GraphQL document */
  column: number;
}

interface GraphQLErrorExtensions {
  /** Error code for programmatic handling */
  code?: string;
  /** Additional error metadata */
  [key: string]: any;
}

type ErrorLike = Partial<GraphQLError> | Error;

Usage Examples:

// Handle different error types
result.error?.graphQLErrors.forEach(error => {
  switch (error.extensions?.code) {
    case 'UNAUTHENTICATED':
      // Redirect to login
      window.location.href = '/login';
      break;
      
    case 'FORBIDDEN':
      // Show access denied message
      showError("You don't have permission to perform this action");
      break;
      
    case 'VALIDATION_ERROR':
      // Show field validation errors
      if (error.extensions?.field) {
        showFieldError(error.extensions.field, error.message);
      }
      break;
      
    default:
      // Generic error handling
      showError(error.message);
  }
});

Error Context and Metadata

Access error context and debugging information.

interface OperationResult<Data = any, Variables extends AnyVariables = AnyVariables> {
  /** The operation that produced this result */
  operation: Operation<Data, Variables>;
  /** Result data (may be partial if errors occurred) */
  data?: Data;
  /** Combined error information */
  error?: CombinedError;
  /** Additional response metadata */
  extensions?: Record<string, any>;
  /** Whether result is stale and will be updated */
  stale: boolean;
  /** Whether more results will follow */
  hasNext: boolean;
}

Usage Examples:

// Comprehensive error logging
const result = await client.query(GetUserQuery, { id: "123" }).toPromise();

if (result.error) {
  // Log full error context
  console.group(`Error in ${result.operation.kind} operation`);
  console.log("Operation:", result.operation.query);
  console.log("Variables:", result.operation.variables);
  console.log("Error:", result.error.message);
  
  if (result.error.networkError) {
    console.log("Network Error:", result.error.networkError);
  }
  
  if (result.error.graphQLErrors.length > 0) {
    console.log("GraphQL Errors:", result.error.graphQLErrors);
  }
  
  if (result.error.response) {
    console.log("Response Status:", result.error.response.status);
    console.log("Response Headers:", [...result.error.response.headers.entries()]);
  }
  
  console.groupEnd();
}

// Handle partial data with errors
if (result.data && result.error) {
  // Some fields succeeded, others failed
  console.log("Partial data received:", result.data);
  console.log("Errors for specific fields:", result.error.graphQLErrors);
  
  // Use partial data but show error indicators
  displayPartialData(result.data, result.error.graphQLErrors);
}

Error Handling Patterns

Common patterns for handling errors in different scenarios.

// Retry pattern with exponential backoff
async function executeWithRetry<T>(
  operation: () => Promise<OperationResult<T>>,
  maxRetries = 3
): Promise<OperationResult<T>> {
  let lastError: CombinedError | undefined;
  
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const result = await operation();
      
      if (!result.error) {
        return result;
      }
      
      // Don't retry GraphQL errors (they won't change)
      if (result.error.graphQLErrors.length > 0 && !result.error.networkError) {
        return result;
      }
      
      lastError = result.error;
      
      if (attempt < maxRetries) {
        // Exponential backoff
        await new Promise(resolve => 
          setTimeout(resolve, Math.pow(2, attempt) * 1000)
        );
      }
    } catch (error) {
      lastError = error as CombinedError;
    }
  }
  
  throw lastError;
}

// Usage
try {
  const result = await executeWithRetry(() => 
    client.query(GetUserQuery, { id: "123" }).toPromise()
  );
  // Handle successful result
} catch (error) {
  // Handle final error after retries
}

Types

Error Types

class CombinedError extends Error {
  networkError?: Error;
  graphQLErrors: GraphQLError[];
  response?: Response;
  
  constructor(params: {
    networkError?: Error;
    graphQLErrors?: readonly GraphQLError[];
    response?: Response;
  });
}

interface GraphQLError {
  message: string;
  locations?: readonly GraphQLErrorLocation[];
  path?: readonly (string | number)[];
  extensions?: GraphQLErrorExtensions;
}

interface GraphQLErrorLocation {
  line: number;
  column: number;
}

interface GraphQLErrorExtensions {
  code?: string;
  [key: string]: any;
}

type ErrorLike = Partial<GraphQLError> | Error;

Response Types

interface ExecutionResult {
  data?: null | Record<string, any>;
  errors?: ErrorLike[] | readonly ErrorLike[];
  extensions?: Record<string, any>;
  hasNext?: boolean;
  incremental?: IncrementalPayload[];
  pending?: readonly PendingIncrementalResult[];
}

Install with Tessl CLI

npx tessl i tessl/npm-urql--core

docs

client.md

errors.md

exchanges.md

index.md

internal.md

operations.md

utilities.md

tile.json