Skip to content

Instantly share code, notes, and snippets.

@nksaraf
Last active December 29, 2020 23:33
Show Gist options
  • Save nksaraf/2b694e3697ccfd9eb7185c5c161b9717 to your computer and use it in GitHub Desktop.
Save nksaraf/2b694e3697ccfd9eb7185c5c161b9717 to your computer and use it in GitHub Desktop.
fetchGraphQL.ts
/**
* Shamelessly copied from the amazing `urql` client
* https://github.com/FormidableLabs/urql/blob/main/packages/core/src/utils/error.ts
*/
import { GraphQLError } from "graphql/error/GraphQLError";
const generateErrorMessage = (
networkErr?: Error,
graphQlErrs?: GraphQLError[]
) => {
let error = "";
if (networkErr !== undefined) {
return (error = `[Network] ${networkErr.message}`);
}
if (graphQlErrs !== undefined) {
graphQlErrs.forEach((err) => {
error += `[GraphQL] ${err.message}\n`;
});
}
return error.trim();
};
const rehydrateGraphQlError = (error: any): GraphQLError => {
if (typeof error === "string") {
return new GraphQLError(error);
} else if (typeof error === "object" && error.message) {
return new GraphQLError(
error.message,
error.nodes,
error.source,
error.positions,
error.path,
error,
error.extensions || {}
);
} else {
return error as any;
}
};
/** An error which can consist of GraphQL errors and Network errors. */
export class CombinedError extends Error {
public name: string;
public message: string;
public graphQLErrors: GraphQLError[];
public networkError?: Error;
public response?: any;
constructor({
networkError,
graphQLErrors,
response,
}: {
networkError?: Error;
graphQLErrors?: (string | Partial<GraphQLError> | Error)[];
response?: any;
}) {
const normalizedGraphQLErrors = (graphQLErrors || []).map(
rehydrateGraphQlError
);
const message = generateErrorMessage(networkError, normalizedGraphQLErrors);
super(message);
this.name = "CombinedError";
this.message = message;
this.graphQLErrors = normalizedGraphQLErrors;
this.networkError = networkError;
this.response = response;
}
toString() {
return this.message;
}
}
import fetch from "isomorphic-unfetch";
import { CombinedError } from "./error";
export type BaseVariables = { [key: string]: any };
export async function resolveFetchOptions<TVariables>(
fetchOptions: FetchOptions<TVariables>,
fetchOperation: Partial<FetchOperation<TVariables>>
) {
return typeof fetchOptions === "function"
? await fetchOptions(fetchOperation as FetchOperation<TVariables>)
: fetchOptions;
}
export interface RawFetchResponse<TQuery extends Query> {
data: Response<TQuery> | null;
errors?: any[];
extensions: any;
}
export const makeResult = <TQuery extends Query>(
result: Partial<RawFetchResponse<TQuery>>,
response?: any
): FetchResult<TQuery> => ({
data: result.data,
combinedError: Array.isArray(result.errors)
? new CombinedError({
graphQLErrors: result.errors,
response,
})
: undefined,
errors: (result.errors as any) ?? null,
extensions:
(typeof result.extensions === "object" && result.extensions) || undefined,
});
export const makeNetworkErrorResult = <TQuery extends Query>(
error: Error,
response?: any
): FetchResult<TQuery> => ({
data: undefined,
combinedError: new CombinedError({
networkError: error,
response,
}),
errors: [error],
extensions: undefined,
});
export async function fetchGraphQL<TQuery extends Query>({
endpoint,
query: rawQuery,
fetchOptions = () => ({}),
variables = {},
operationName = undefined,
operationKind = "query",
}: FetchOperation<Variables<TQuery>>): Promise<FetchResult<TQuery>> {
if (!rawQuery) {
throw new Error("Query not found");
}
const query: string = rawQuery as any;
const operation = {
endpoint,
query,
variables,
operationName,
operationKind,
};
const { headers = {}, ...options } = await resolveFetchOptions(
fetchOptions,
operation
);
const body = JSON.stringify({
query,
variables,
operationName,
});
try {
const response = await fetch(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json", ...headers },
body,
...options,
});
const contentTypeHeader = response.headers.get("Content-Type");
console.log(response);
if (!contentTypeHeader?.startsWith("application/json")) {
const result = await response.text();
return makeNetworkErrorResult(
new Error(`Expected JSON response, received "${result}"`),
response
);
} else if (!response.ok) {
const result = await response.json();
return makeResult(result, response);
}
const result: {
data: Response<TQuery> | null;
errors?: any[];
} = await response.json();
return makeResult(result, response);
} catch (e) {
return makeNetworkErrorResult(e);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment