diff --git a/packages/graphql-client/src/GraphQLErrorList.ts b/packages/graphql-client/src/GraphQLErrorList.ts new file mode 100644 index 000000000..603ba7f12 --- /dev/null +++ b/packages/graphql-client/src/GraphQLErrorList.ts @@ -0,0 +1,22 @@ +import { GraphQLError } from 'graphql'; + +export class GraphQLErrorList extends Error { + public errors: readonly GraphQLError[]; + + constructor(errors: readonly GraphQLError[], message?: string) { + super(); + this.errors = errors; + this.stack = new Error().stack; + + const br = '\r\n'; + const summary = `${errors.length} error${errors.length > 1 ? 's' : ''}${ + message ? ' ' + message : ':' + }`; + const errList = errors.map(err => `\t- ${err.message}`).join(br); + this.message = `GraphQLErrorList - ${summary}${br}${errList}`; + } + + toString() { + return this.errors.map(err => err.toString()).join('\r\n'); + } +} diff --git a/packages/graphql-client/src/graphql-client.ts b/packages/graphql-client/src/graphql-client.ts index 051e38fac..cad956860 100644 --- a/packages/graphql-client/src/graphql-client.ts +++ b/packages/graphql-client/src/graphql-client.ts @@ -1,5 +1,6 @@ import { AccountsClient, TransportInterface } from '@accounts/client'; import { CreateUser, ImpersonationResult, LoginResult, User } from '@accounts/types'; +import { print } from 'graphql'; import gql from 'graphql-tag'; import { authenticateWithServiceMutation } from './graphql/authenticate-with-service.mutation'; import { changePasswordMutation } from './graphql/change-password.mutation'; @@ -16,6 +17,7 @@ import { sendVerificationEmailMutation } from './graphql/send-verification-email import { twoFactorSetMutation } from './graphql/two-factor-set.mutation'; import { twoFactorUnsetMutation } from './graphql/two-factor-unset.mutation'; import { verifyEmailMutation } from './graphql/verify-email.mutation'; +import { GraphQLErrorList } from './GraphQLErrorList'; export interface AuthenticateParams { [key: string]: string | object; @@ -174,7 +176,7 @@ export default class GraphQLClient implements TransportInterface { ? await this.client.getTokens() : await this.client.refreshSession(); - const { data } = await this.options.graphQLClient.mutate({ + const { data, errors } = await this.options.graphQLClient.mutate({ mutation, variables, context: { @@ -183,13 +185,18 @@ export default class GraphQLClient implements TransportInterface { }, }, }); + + if (errors) { + throw new GraphQLErrorList(errors, `in mutation: \r\n ${print(mutation)}`); + } + return data[resultField]; } private async query(query: any, resultField: any, variables: any = {}) { const tokens = await this.client.refreshSession(); - const { data } = await this.options.graphQLClient.query({ + const { data, errors } = await this.options.graphQLClient.query({ query, variables, fetchPolicy: 'network-only', @@ -199,6 +206,11 @@ export default class GraphQLClient implements TransportInterface { }, }, }); + + if (errors) { + throw new GraphQLErrorList(errors, `in query: \r\n ${print(query)}`); + } + return data[resultField]; } } diff --git a/packages/graphql-client/src/index.ts b/packages/graphql-client/src/index.ts index 933ac76a9..d448156ea 100644 --- a/packages/graphql-client/src/index.ts +++ b/packages/graphql-client/src/index.ts @@ -1,3 +1,3 @@ export * from './graphql-client'; -export { default } from './graphql-client'; -export { default as AccountsGraphQLClient } from './graphql-client'; +export { default as AccountsGraphQLClient, default } from './graphql-client'; +export * from './GraphQLErrorList'; diff --git a/website/docs/transports/graphql.md b/website/docs/transports/graphql.md index d637bf026..b4c147cd0 100644 --- a/website/docs/transports/graphql.md +++ b/website/docs/transports/graphql.md @@ -284,16 +284,72 @@ yarn add @accounts/graphql-client ```js import { ApolloClient } from 'apollo-client'; +import { AccountsClient } from '@accounts/client'; +import { AccountsClientPassword } from '@accounts/client-password'; import { AccountsGraphQLClient } from '@accounts/graphql-client'; +// Create Apollo client const apolloClient = new ApolloClient({ // apollo options }); -const accountsGraphQL = new GraphQLClient({ +// Create your transport +const accountsGraphQL = new AccountsGraphQLClient({ graphQLClient: apolloClient, - // other options + //other options like 'userFieldsFragment' }); + +// Create the AccountsClient +const accountsClient = new AccountsClient( + { + // accountsClient Options + }, + accountsGraphQL +); + +// Create service client +const passwordClient = new AccountsClientPassword(accountsClient); +``` + +### Error Handling + +The AccountsGraphQLClient will throw errors when the graphql query/mutation returns them. Because there could be multiple `GraphQLError`s, these errors will be wrapped into a `GraphQLErrorList` object. + +```js +import { GraphQLErrorList } from '@accounts/graphql-client'; + +async function registerUser() { + try { + passwordClient.createUser({ email: 'foo@foobar.com', password: 'foo' }); + } catch (e) { + if (e instanceof GraphQLErrorList) { + // the message will format the errors in a list + console.log(error.message); + /* example: + GraphQLErrorList - 1 errors in mutation: + mutation impersonate($accessToken: String!, $username: String!) { + impersonate(accessToken: $accessToken, username: $username) { + authorized + tokens { + refreshToken + accessToken + } + user { + id + email + username + } + } + } + + - Cannot query field "email" on type "User". Did you mean "emails"? + */ + + // Alternatively, the list of errors is accessible on the "errors" property + error.errors.forEach(graphQLError => console.log(graphQLError)); + } + } +} ``` ## Using with Apollo Link