Skip to content
Effect 4.0 is here. One ecosystem. Zero dependencies.

GraphQLClientError

Serializable GraphQL client errors with an operation name and a Schema.TaggedError reason.

11 exports Since v4.0.0 Source

Errors

The response was not a GraphQL response, or data did not match the result Schema.

Details

data: null without errors also produces a DecodeError, since ResponseError requires a non-empty errors array.

Signature

declare class DecodeError extends {
readonly _tag: "DecodeError";
readonly description: string;
} & YieldableError<this> {
constructor(...args: [props: {
readonly _tag?: "DecodeError";
readonly description: string;
}, options?: MakeOptions]);
isRetryable: boolean;
message: string;
}

The variables Schema rejected the input, so nothing was sent.

Signature

declare class EncodeError extends {
readonly _tag: "EncodeError";
readonly description: string;
} & YieldableError<this> {
constructor(...args: [props: {
readonly _tag?: "EncodeError";
readonly description: string;
}, options?: MakeOptions]);
isRetryable: boolean;
message: string;
}

The error every GraphQLClient method fails with. It names the operation and carries one of the five reasons; cause is the reason itself.

Signature

declare class GraphQLClientError extends {
readonly _tag: "GraphQLClientError";
readonly operation: string;
readonly reason: ResponseError | TransportError | EncodeError | DecodeError | PaginationError;
} & YieldableError<this> {
constructor(...args: [props: {
readonly _tag?: "GraphQLClientError";
readonly operation: string;
readonly reason: ResponseError | TransportError | EncodeError | DecodeError | PaginationError;
}, options?: MakeOptions]);
readonly "~effect/graphql/GraphQLClientError": "~effect/graphql/GraphQLClientError";
readonly cause: ResponseError | TransportError | EncodeError | DecodeError | PaginationError;
headers: Headers | undefined;
isRetryable: boolean;
message: string;
retryAfter: Duration | undefined;
status: number | undefined;
}

Example Retrying only what is retryable

import { Duration, Schema } from "effect"
import { GraphQLClientError } from "effect/graphql"
const error = new GraphQLClientError.GraphQLClientError({
operation: "Viewer",
reason: new GraphQLClientError.TransportError({
description: "503 Service Unavailable",
status: 503,
retryAfter: Duration.seconds(30)
})
})
error.isRetryable // => true
error.retryAfter // => Duration.seconds(30)
// Errors round-trip through their Schema
const encoded = Schema.encodeSync(GraphQLClientError.GraphQLClientError)(error)
Schema.decodeUnknownSync(GraphQLClientError.GraphQLClientError)(encoded).reason._tag // => "TransportError"

Cursor paging could not continue. Only GraphQLClient.pages and GraphQLClient.items raise it, when a connection becomes null on a page after the first, or when hasNextPage is true but endCursor is null or did not advance.

Details

cursor is the last cursor that was sent, null on the first page.

Signature

declare class PaginationError extends {
readonly _tag: "PaginationError";
readonly cursor: string | null;
readonly description: string;
} & YieldableError<this> {
constructor(...args: [props: {
readonly _tag?: "PaginationError";
readonly cursor: string | null;
readonly description: string;
}, options?: MakeOptions]);
isRetryable: boolean;
message: string;
}

The server answered with a non-empty errors array.

Details

Queries and mutations fail even when partial data is present, since a field error's null cannot be distinguished from a genuine null. Use { partial: true } to receive decoded { data, errors } instead.

On this error, data is raw. GraphQLError.path[0] identifies the response key (or alias). Non-2xx HTTP responses with a GraphQL body also use this reason rather than TransportError.

status and headers are the HTTP status and response headers, as the transport reported them and middleware left them. Headers use Headers.Headers at runtime and encode as a plain string record. Decoding restores the normalized header collection. Both are unset over graphql-ws, and status is unset for graphql-sse events. retryAfter parses the Retry-After response header as whole seconds or an HTTP date, measured from Date.now() when the error is constructed. isRetryable is true for HTTP status 429 or 500โ€“599, and for any response with a valid Retry-After, whatever its status. It is false otherwise, including when there is no status.

For queries and mutations, callers opt into retries with Effect.retry, and should wait for retryAfter when it is set. With GraphQLClient.pages, retry each page's call, not the stream: Stream.retry starts again from the first page. Subscriptions retry a retryable ResponseError on their own, by resubscribing. graphql-sse events carry the stream response's headers, so an event with errors is retried when that response had a valid Retry-After, and ends the stream otherwise.

Signature

declare class ResponseError extends {
readonly _tag: "ResponseError";
readonly data?: Json;
readonly errors: readonly [{
[key: string]: Json;
readonly extensions?: {
[key: string]: Json;
};
readonly locations?: readonly Array<{
readonly column: number;
readonly line: number;
}>;
readonly message: string;
readonly path?: readonly Array<string | number>;
}, {
[key: string]: Json;
readonly extensions?: {
[key: string]: Json;
};
readonly locations?: readonly Array<{
readonly column: number;
readonly line: number;
}>;
readonly message: string;
readonly path?: readonly Array<string | number>;
}];
readonly extensions?: {
[key: string]: Json;
};
readonly headers?: Headers;
readonly status?: number;
} & YieldableError<this> {
constructor(...args: [props: {
readonly _tag?: "ResponseError";
readonly data?: unknown;
readonly errors: readonly [{
[key: string]: unknown;
readonly extensions?: {
[key: string]: unknown;
};
readonly locations?: readonly Array<{
readonly column: number;
readonly line: number;
}>;
readonly message: string;
readonly path?: readonly Array<string | number>;
}, {
[key: string]: unknown;
readonly extensions?: {
[key: string]: unknown;
};
readonly locations?: readonly Array<{
readonly column: number;
readonly line: number;
}>;
readonly message: string;
readonly path?: readonly Array<string | number>;
}];
readonly extensions?: {
[key: string]: unknown;
};
readonly headers?: Headers;
readonly status?: number;
}, options?: MakeOptions]);
isRetryable: boolean;
message: string;
retryAfter: Duration | undefined;
}

Example Inspecting the errors of a failed call

import { GraphQLClientError } from "effect/graphql"
const reason = new GraphQLClientError.ResponseError({
errors: [{ message: "Not found", path: ["repository"] }],
data: { repository: null },
status: 200
})
reason.isRetryable // => false
reason.status // => 200
reason.errors.map((error) => error.path?.[0]) // => ["repository"]

Example Retrying a call on retryable errors

import { Effect } from "effect"
import { GraphQLClientError } from "effect/graphql"
const call = Effect.fail(new GraphQLClientError.GraphQLClientError({
operation: "Viewer",
reason: new GraphQLClientError.ResponseError({
errors: [{ message: "Query timed out" }],
status: 502
})
}))
const retried = Effect.retry(call, {
while: (error) => error.reason.isRetryable,
times: 3
})

The client could not get a GraphQL response from the server: a network failure, an HTTP response that is not a GraphQL response, or a closed WebSocket connection.

Details

  • status and headers are only set for an HTTP response that is not a GraphQL response. Headers use Headers.Headers at runtime and encode as a plain string record; decoding restores the header collection.
  • retryAfter comes from the Retry-After header (whole seconds or an HTTP date) and is encoded as milliseconds. When it is set, the error is retryable at any status, unless the close code is fatal.
  • closeCode is set when a WebSocket connection closed. See isFatalCloseCode for the codes that are not retryable.
  • fatal: true marks a failure that retrying cannot fix, such as an operation on a graphql-ws transport whose scope has closed.
  • cause holds the original error, such as the HttpClientError. Once encoded it shrinks to { name, message }, the standard Schema.Defect behaviour.

Signature

declare class TransportError extends {
readonly _tag: "TransportError";
readonly cause?: unknown;
readonly closeCode?: number;
readonly description: string;
readonly fatal?: boolean;
readonly headers?: Headers;
readonly retryAfter?: Duration;
readonly status?: number;
} & YieldableError<this> {
constructor(...args: [props: {
readonly _tag?: "TransportError";
readonly cause?: unknown;
readonly closeCode?: number;
readonly description: string;
readonly fatal?: boolean;
readonly headers?: Headers;
readonly retryAfter?: Duration;
readonly status?: number;
}, options?: MakeOptions]);
isRetryable: boolean;
message: string;
static fromHttpClientError(error: HttpClientError, options?: {
readonly now?: number;
}): TransportError;
}

Example Retryability of transport failures

import { GraphQLClientError } from "effect/graphql"
new GraphQLClientError.TransportError({ description: "ECONNREFUSED" }).isRetryable // => true
new GraphQLClientError.TransportError({ description: "503", status: 503 }).isRetryable // => true
new GraphQLClientError.TransportError({ description: "404", status: 404 }).isRetryable // => false
new GraphQLClientError.TransportError({ description: "closed", closeCode: 4401 }).isRetryable // => false
new GraphQLClientError.TransportError({ description: "shut down", fatal: true }).isRetryable // => false

Guards

Tests whether a value is a GraphQLClientError.

Signature

declare function isGraphQLClientError(u: unknown): u is GraphQLClientError

Models

Since v4.0.0

The decoded type of GraphQLError.

Signature

type GraphQLError = typeof GraphQLError.Type
Since v4.0.0

The union of every reason a GraphQLClientError can carry.

Signature

type Reason = ResponseError | TransportError | EncodeError | DecodeError | PaginationError

Predicates

Tests whether a WebSocket close code is fatal, meaning a TransportError carrying it is not retryable.

Details

Fatal codes are every standard 1xxx code except 1000, 1001, 1005, 1006, 1012, 1013 and 1014, plus the graphql-ws codes 4400 (bad request), 4401 (unauthorized), 4403 (forbidden), 4406 (subprotocol not acceptable), 4409 (subscriber already exists), 4429 (too many initialisation requests) and 4500 (internal server error). Any other code is retryable.

4403 is fatal because reconnecting with the same credentials is refused again.

Signature

declare function isFatalCloseCode(code: number): boolean

Example Checking close codes

import { GraphQLClientError } from "effect/graphql"
GraphQLClientError.isFatalCloseCode(1006) // => false
GraphQLClientError.isFatalCloseCode(1011) // => true
GraphQLClientError.isFatalCloseCode(4401) // => true

Schemas

One entry of a GraphQL response's errors array, as described by the GraphQL specification.

Details

path[0] is the response key the error belongs to, which is the alias when the document used one, so callers can match an error to the field it hit. Unknown top-level fields are preserved as JSON values, including GitHub's type field.

Signature

declare const GraphQLError: StructWithRest<Struct<{
readonly extensions: optional<$Record<String, Codec<Json, Json, never, never>>>;
readonly locations: optional<$Array<Struct<{
readonly column: Number;
readonly line: Number;
}>>>;
readonly message: String;
readonly path: optional<$Array<Union<readonly [String, Number]>>>;
}>, readonly [$Record<String, Codec<Json, Json, never, never>>]>

Example Finding which alias an error belongs to

import { Schema } from "effect"
import { GraphQLClientError } from "effect/graphql"
const error = Schema.decodeUnknownSync(GraphQLClientError.GraphQLError)({
message: "Could not resolve to a Repository",
path: ["missing", "issues"],
type: "NOT_FOUND"
})
error.path?.[0] // => "missing"