The shared core for the highly customizable and versatile GraphQL client
—
Comprehensive error handling system for GraphQL and network errors, providing unified error representation and detailed error information for debugging and user feedback.
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" }
}],
});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)
);
})
);
};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);
}
});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);
}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
}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;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