feat: convert errors into ApolloError (#1225)

This commit is contained in:
Oliver Hoff 2021-07-19 15:45:40 +02:00 committed by GitHub
parent 08b6b0e7d9
commit 334310d5e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 61 additions and 38 deletions

View File

@ -4,5 +4,6 @@
"javascriptreact", "javascriptreact",
"vue" "vue"
], ],
"eslint.enable": true "eslint.enable": true,
"typescript.tsdk": "node_modules/typescript/lib"
} }

View File

@ -1,10 +1,11 @@
import { DocumentNode } from 'graphql' import { DocumentNode } from 'graphql'
import { MutationOptions, OperationVariables, FetchResult, TypedDocumentNode } from '@apollo/client/core' import { MutationOptions, OperationVariables, FetchResult, TypedDocumentNode, ApolloError } from '@apollo/client/core'
import { ref, onBeforeUnmount, isRef, Ref, getCurrentInstance } from 'vue-demi' import { ref, onBeforeUnmount, isRef, Ref, getCurrentInstance } from 'vue-demi'
import { useApolloClient } from './useApolloClient' import { useApolloClient } from './useApolloClient'
import { ReactiveFunction } from './util/ReactiveFunction' import { ReactiveFunction } from './util/ReactiveFunction'
import { useEventHook } from './util/useEventHook' import { useEventHook } from './util/useEventHook'
import { trackMutation } from './util/loadingTracking' import { trackMutation } from './util/loadingTracking'
import { toApolloError } from './util/toApolloError'
/** /**
* `useMutation` options for mutations that don't require `variables`. * `useMutation` options for mutations that don't require `variables`.
@ -27,12 +28,12 @@ export type MutateFunction<TResult, TVariables> = (variables?: TVariables | null
export interface UseMutationReturn<TResult, TVariables> { export interface UseMutationReturn<TResult, TVariables> {
mutate: MutateFunction<TResult, TVariables> mutate: MutateFunction<TResult, TVariables>
loading: Ref<boolean> loading: Ref<boolean>
error: Ref<Error | null> error: Ref<ApolloError | null>
called: Ref<boolean> called: Ref<boolean>
onDone: (fn: (param: FetchResult<TResult, Record<string, any>, Record<string, any>>) => void) => { onDone: (fn: (param: FetchResult<TResult, Record<string, any>, Record<string, any>>) => void) => {
off: () => void off: () => void
} }
onError: (fn: (param: Error) => void) => { onError: (fn: (param: ApolloError) => void) => {
off: () => void off: () => void
} }
} }
@ -47,11 +48,11 @@ export function useMutation<
const vm = getCurrentInstance() const vm = getCurrentInstance()
const loading = ref<boolean>(false) const loading = ref<boolean>(false)
vm && trackMutation(loading) vm && trackMutation(loading)
const error = ref<Error | null>(null) const error = ref<ApolloError | null>(null)
const called = ref<boolean>(false) const called = ref<boolean>(false)
const doneEvent = useEventHook<FetchResult<TResult, Record<string, any>, Record<string, any>>>() const doneEvent = useEventHook<FetchResult<TResult, Record<string, any>, Record<string, any>>>()
const errorEvent = useEventHook<Error>() const errorEvent = useEventHook<ApolloError>()
// Apollo Client // Apollo Client
const { resolveClient } = useApolloClient() const { resolveClient } = useApolloClient()
@ -94,11 +95,12 @@ export function useMutation<
doneEvent.trigger(result) doneEvent.trigger(result)
return result return result
} catch (e) { } catch (e) {
error.value = e const apolloError = toApolloError(e)
error.value = apolloError
loading.value = false loading.value = false
errorEvent.trigger(e) errorEvent.trigger(apolloError)
if (currentOptions.throws === 'always' || (currentOptions.throws !== 'never' && !errorEvent.getCount())) { if (currentOptions.throws === 'always' || (currentOptions.throws !== 'never' && !errorEvent.getCount())) {
throw e throw apolloError
} }
} }
return null return null

View File

@ -20,6 +20,7 @@ import {
FetchMoreOptions, FetchMoreOptions,
ObservableSubscription, ObservableSubscription,
TypedDocumentNode, TypedDocumentNode,
ApolloError,
} from '@apollo/client/core' } from '@apollo/client/core'
import { throttle, debounce } from 'throttle-debounce' import { throttle, debounce } from 'throttle-debounce'
import { useApolloClient } from './useApolloClient' import { useApolloClient } from './useApolloClient'
@ -28,6 +29,7 @@ import { paramToRef } from './util/paramToRef'
import { paramToReactive } from './util/paramToReactive' import { paramToReactive } from './util/paramToReactive'
import { useEventHook } from './util/useEventHook' import { useEventHook } from './util/useEventHook'
import { trackQuery } from './util/loadingTracking' import { trackQuery } from './util/loadingTracking'
import { toApolloError } from './util/toApolloError'
import type { CurrentInstance } from './util/types' import type { CurrentInstance } from './util/types'
@ -58,7 +60,7 @@ export interface UseQueryReturn<TResult, TVariables> {
result: Ref<TResult | undefined> result: Ref<TResult | undefined>
loading: Ref<boolean> loading: Ref<boolean>
networkStatus: Ref<number | undefined> networkStatus: Ref<number | undefined>
error: Ref<Error | null> error: Ref<ApolloError | null>
start: () => void start: () => void
stop: () => void stop: () => void
restart: () => void restart: () => void
@ -73,7 +75,7 @@ export interface UseQueryReturn<TResult, TVariables> {
onResult: (fn: (param: ApolloQueryResult<TResult>) => void) => { onResult: (fn: (param: ApolloQueryResult<TResult>) => void) => {
off: () => void off: () => void
} }
onError: (fn: (param: Error) => void) => { onError: (fn: (param: ApolloError) => void) => {
off: () => void off: () => void
} }
} }
@ -154,8 +156,8 @@ export function useQueryImpl<
*/ */
const result = ref<TResult | undefined>() const result = ref<TResult | undefined>()
const resultEvent = useEventHook<ApolloQueryResult<TResult>>() const resultEvent = useEventHook<ApolloQueryResult<TResult>>()
const error = ref<Error | null>(null) const error = ref<ApolloError | null>(null)
const errorEvent = useEventHook<Error>() const errorEvent = useEventHook<ApolloError>()
// Loading // Loading
@ -168,7 +170,7 @@ export function useQueryImpl<
// SSR // SSR
let firstResolve: (() => void) | undefined let firstResolve: (() => void) | undefined
let firstReject: ((error: Error) => void) | undefined let firstReject: ((apolloError: ApolloError) => void) | undefined
onServerPrefetch?.(() => { onServerPrefetch?.(() => {
if (!isEnabled.value || (isServer && currentOptions.value?.prefetch === false)) return if (!isEnabled.value || (isServer && currentOptions.value?.prefetch === false)) return
@ -178,8 +180,8 @@ export function useQueryImpl<
firstResolve = undefined firstResolve = undefined
firstReject = undefined firstReject = undefined
} }
firstReject = (error: Error) => { firstReject = (apolloError: ApolloError) => {
reject(error) reject(apolloError)
firstResolve = undefined firstResolve = undefined
firstReject = undefined firstReject = undefined
} }
@ -258,15 +260,10 @@ export function useQueryImpl<
processNextResult(queryResult) processNextResult(queryResult)
// Result errors // ApolloQueryResult.error may be set at the same time as we get a result
// This is set when `errorPolicy` is `all` // when `errorPolicy` is `all`
if (queryResult.errors?.length) { if (queryResult.error !== undefined) {
const e = new Error(`GraphQL error: ${queryResult.errors.map(e => e.message).join(' | ')}`) processError(queryResult.error)
Object.assign(e, {
graphQLErrors: queryResult.errors,
networkError: null,
})
processError(e)
} else { } else {
if (firstResolve) { if (firstResolve) {
firstResolve() firstResolve()
@ -282,22 +279,25 @@ export function useQueryImpl<
resultEvent.trigger(queryResult) resultEvent.trigger(queryResult)
} }
function onError (queryError: any) { function onError (queryError: unknown) {
// any error should already be an ApolloError, but we make sure
const apolloError = toApolloError(queryError)
processNextResult((query.value as ObservableQuery<TResult, TVariables>).getCurrentResult()) processNextResult((query.value as ObservableQuery<TResult, TVariables>).getCurrentResult())
processError(queryError) processError(apolloError)
if (firstReject) { if (firstReject) {
firstReject(queryError) firstReject(apolloError)
stop() stop()
} }
// The observable closes the sub if an error occurs // The observable closes the sub if an error occurs
resubscribeToQuery() resubscribeToQuery()
} }
function processError (queryError: any) { function processError (apolloError: ApolloError) {
error.value = queryError error.value = apolloError
loading.value = false loading.value = false
networkStatus.value = 8 networkStatus.value = 8
errorEvent.trigger(queryError) errorEvent.trigger(apolloError)
} }
function resubscribeToQuery () { function resubscribeToQuery () {

View File

@ -16,6 +16,7 @@ import {
Observable, Observable,
ObservableSubscription, ObservableSubscription,
TypedDocumentNode, TypedDocumentNode,
ApolloError,
} from '@apollo/client/core' } from '@apollo/client/core'
import { throttle, debounce } from 'throttle-debounce' import { throttle, debounce } from 'throttle-debounce'
import { ReactiveFunction } from './util/ReactiveFunction' import { ReactiveFunction } from './util/ReactiveFunction'
@ -26,6 +27,7 @@ import { useEventHook } from './util/useEventHook'
import { trackSubscription } from './util/loadingTracking' import { trackSubscription } from './util/loadingTracking'
import type { CurrentInstance } from './util/types' import type { CurrentInstance } from './util/types'
import { toApolloError } from './util/toApolloError'
export interface UseSubscriptionOptions < export interface UseSubscriptionOptions <
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
@ -45,7 +47,7 @@ type OptionsParameter<TResult, TVariables> = UseSubscriptionOptions<TResult, TVa
export interface UseSubscriptionReturn<TResult, TVariables> { export interface UseSubscriptionReturn<TResult, TVariables> {
result: Ref<TResult | null | undefined> result: Ref<TResult | null | undefined>
loading: Ref<boolean> loading: Ref<boolean>
error: Ref<Error | null> error: Ref<ApolloError | null>
start: () => void start: () => void
stop: () => void stop: () => void
restart: () => void restart: () => void
@ -56,7 +58,7 @@ export interface UseSubscriptionReturn<TResult, TVariables> {
onResult: (fn: (param: FetchResult<TResult, Record<string, any>, Record<string, any>>) => void) => { onResult: (fn: (param: FetchResult<TResult, Record<string, any>, Record<string, any>>) => void) => {
off: () => void off: () => void
} }
onError: (fn: (param: Error) => void) => { onError: (fn: (param: ApolloError) => void) => {
off: () => void off: () => void
} }
} }
@ -119,8 +121,8 @@ export function useSubscription <
const result = ref<TResult | null | undefined>() const result = ref<TResult | null | undefined>()
const resultEvent = useEventHook<FetchResult<TResult>>() const resultEvent = useEventHook<FetchResult<TResult>>()
const error = ref<Error | null>(null) const error = ref<ApolloError | null>(null)
const errorEvent = useEventHook<Error>() const errorEvent = useEventHook<ApolloError>()
const loading = ref(false) const loading = ref(false)
vm && trackSubscription(loading) vm && trackSubscription(loading)
@ -157,10 +159,12 @@ export function useSubscription <
resultEvent.trigger(fetchResult) resultEvent.trigger(fetchResult)
} }
function onError (fetchError: any) { function onError (fetchError: unknown) {
error.value = fetchError const apolloError = toApolloError(fetchError)
error.value = apolloError
loading.value = false loading.value = false
errorEvent.trigger(fetchError) errorEvent.trigger(apolloError)
} }
function stop () { function stop () {

View File

@ -0,0 +1,16 @@
import { ApolloError, isApolloError } from '@apollo/client'
export function toApolloError (error: unknown): ApolloError {
if (!(error instanceof Error)) {
return new ApolloError({
networkError: Object.assign(new Error(), { originalError: error }),
errorMessage: String(error),
})
}
if (isApolloError(error)) {
return error
}
return new ApolloError({ networkError: error, errorMessage: error.message })
}