Last active
December 29, 2020 23:33
-
-
Save nksaraf/2b694e3697ccfd9eb7185c5c161b9717 to your computer and use it in GitHub Desktop.
fetchGraphQL.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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