From cac0d43d05f78818afe41c49cad83bbc4379ec4f Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Wed, 17 Oct 2018 20:26:16 +0200 Subject: [PATCH] Proposal to change the API Currently the GraphQLDataSource package kind of follows RESTDataSource, but also uses ApolloLink infrastructure. It'd be great to have this basically like using ApolloClient, e.g. class MyApi extends GraphQLDataSource { constructor() { this.link = ApolloLink.from([ onError, createHttpLink({ fetch, uri: "https://swapi/graphql" }) ]) super(); } fetchStarships(type) { this.query(gql`...`, ...); } } This would allow custom error handling, usage of batching, retries, etc. This commit also adds some missing types and raises several questions. --- src/GraphQLDataSource.ts | 116 +++++++++++++-------------------------- 1 file changed, 38 insertions(+), 78 deletions(-) diff --git a/src/GraphQLDataSource.ts b/src/GraphQLDataSource.ts index 09fa268..29c5ec2 100644 --- a/src/GraphQLDataSource.ts +++ b/src/GraphQLDataSource.ts @@ -1,107 +1,67 @@ -import { DataSourceConfig } from 'apollo-datasource'; +import { DataSource, DataSourceConfig } from 'apollo-datasource'; import { ApolloLink, execute, GraphQLRequest, makePromise } from 'apollo-link'; import { setContext } from 'apollo-link-context'; -import { onError } from 'apollo-link-error'; -import { createHttpLink } from 'apollo-link-http'; import { ApolloError, AuthenticationError, ForbiddenError } from 'apollo-server-errors'; -import to from 'await-to-js'; import { DocumentNode } from 'graphql'; -import fetch from 'isomorphic-fetch'; -export class GraphQLDataSource { - public baseURL!: string; +type ValueOrPromise = T | Promise; +type RequestOptions = Record; + +export class GraphQLDataSource extends DataSource { public context!: TContext; + public link!: ApolloLink; - public initialize(config: DataSourceConfig): void { + initialize(config: DataSourceConfig): void { this.context = config.context; + this.link = ApolloLink.from([ + this.onRequestLink(), + this.link + ]); } public async mutation(mutation: DocumentNode, options: GraphQLRequest) { // GraphQL request requires the DocumentNode property to be named query - return this.executeSingleOperation({ ...options, query: mutation }); + return this.execute({ ...options, query: mutation }); } public async query(query: DocumentNode, options: GraphQLRequest) { - return this.executeSingleOperation({ ...options, query }); - } - - protected willSendRequest?(request: any): any; - - private composeLinks(): ApolloLink { - const uri = this.resolveUri(); - - return ApolloLink.from([ - this.onErrorLink(), - this.onRequestLink(), - createHttpLink({ fetch, uri }), - ]); - } - - private didEncounterError(error: any) { - const status = error.statusCode ? error.statusCode : null; - const message = error.bodyText ? error.bodyText : null; - - let apolloError: ApolloError; - - switch (status) { - case 401: - apolloError = new AuthenticationError(message); - break; - case 403: - apolloError = new ForbiddenError(message); - break; - default: - apolloError = new ApolloError(message); - } - - throw apolloError; + return this.execute({ ...options, query }); } - private async executeSingleOperation(operation: GraphQLRequest) { - const link = this.composeLinks(); - - const [error, response] = await to(makePromise(execute(link, operation))); - - if (error) { - this.didEncounterError(error); - } - - return response; - } - - private resolveUri(): string { - const baseURL = this.baseURL; + protected willSendRequest?(request: RequestOptions): ValueOrPromise; + + private async execute(operation: GraphQLRequest) { + try { + return await makePromise(execute(this.link, operation)); + } catch (error) { + const status = error.statusCode ? error.statusCode : null; + const message = error.bodyText ? error.bodyText : null; + + let apolloError: ApolloError; + + switch (status) { + case 401: + apolloError = new AuthenticationError(message); + break; + case 403: + apolloError = new ForbiddenError(message); + break; + default: + apolloError = new ApolloError(message); + } - if (!baseURL) { - throw new ApolloError('Cannot make request to GraphQL API, missing baseURL'); + throw apolloError; } - - return baseURL; } private onRequestLink() { - return setContext((_, request) => { + // QUESTION: Is that first argument needed? Code for apollo mentions it's actually `request, prevContext` + return setContext(async (_, request: RequestOptions) => { if (this.willSendRequest) { - this.willSendRequest(request); + await this.willSendRequest(request); } return request; }); } - - private onErrorLink() { - return onError(({ graphQLErrors, networkError }) => { - if (graphQLErrors) { - graphQLErrors.map(graphqlError => - console.error( - `[GraphQL error]: ${graphqlError}`, - ), - ); - } - - if (networkError) { - console.log(`[Network Error]: ${networkError}`); - } - }); - } }