diff --git a/.changeset/breezy-feet-greet.md b/.changeset/breezy-feet-greet.md new file mode 100644 index 0000000000..33d0e2a918 --- /dev/null +++ b/.changeset/breezy-feet-greet.md @@ -0,0 +1,5 @@ +--- +'@envelop/core': minor +--- + +Handle incremental execution errors in useErrorHandler diff --git a/packages/core/package.json b/packages/core/package.json index 48f20d56f3..c1e21e1295 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -60,6 +60,7 @@ "tslib": "^2.5.0" }, "devDependencies": { + "@graphql-tools/executor": "^1.1.0", "@graphql-tools/schema": "10.0.2", "@graphql-tools/utils": "10.0.11", "@repeaterjs/repeater": "3.0.5", diff --git a/packages/core/src/plugins/use-error-handler.ts b/packages/core/src/plugins/use-error-handler.ts index bf00805bf9..20b1332064 100644 --- a/packages/core/src/plugins/use-error-handler.ts +++ b/packages/core/src/plugins/use-error-handler.ts @@ -1,4 +1,10 @@ -import { DefaultContext, ExecutionResult, Plugin, TypedExecutionArgs } from '@envelop/types'; +import { + DefaultContext, + ExecutionResult, + IncrementalExecutionResult, + Plugin, + TypedExecutionArgs, +} from '@envelop/types'; import { handleStreamOrSingleExecutionResult } from '../utils.js'; import { isGraphQLError, SerializableGraphQLErrorLike } from './use-masked-errors.js'; @@ -13,15 +19,24 @@ export type ErrorHandler = ({ }) => void; type ErrorHandlerCallback = { - result: ExecutionResult; + result: ExecutionResult | IncrementalExecutionResult; args: TypedExecutionArgs; }; const makeHandleResult = >(errorHandler: ErrorHandler) => ({ result, args }: ErrorHandlerCallback) => { - if (result.errors?.length) { - errorHandler({ errors: result.errors, context: args, phase: 'execution' }); + const errors = result.errors ? [...result.errors] : []; + if ('incremental' in result && result.incremental) { + for (const increment of result.incremental) { + if (increment.errors) { + errors.push(...increment.errors); + } + } + } + + if (errors.length) { + errorHandler({ errors, context: args, phase: 'execution' }); } }; diff --git a/packages/core/test/plugins/use-error-handler.spec.ts b/packages/core/test/plugins/use-error-handler.spec.ts index 99cb5be014..a1d70895c2 100644 --- a/packages/core/test/plugins/use-error-handler.spec.ts +++ b/packages/core/test/plugins/use-error-handler.spec.ts @@ -1,10 +1,12 @@ -import { useExtendContext } from '@envelop/core'; +import * as GraphQLJS from 'graphql'; +import { useEngine, useExtendContext } from '@envelop/core'; import { assertStreamExecutionValue, collectAsyncIteratorValues, createTestkit, } from '@envelop/testing'; import { Plugin } from '@envelop/types'; +import { normalizedExecutor } from '@graphql-tools/executor'; import { makeExecutableSchema } from '@graphql-tools/schema'; import { createGraphQLError } from '@graphql-tools/utils'; import { Repeater } from '@repeaterjs/repeater'; @@ -139,4 +141,37 @@ describe('useErrorHandler', () => { }), ); }); + + it('should invoke error handler when error happens during incremental execution', async () => { + const schema = makeExecutableSchema({ + typeDefs: /* GraphQL */ ` + directive @defer on FRAGMENT_SPREAD | INLINE_FRAGMENT + + type Query { + foo: String + } + `, + resolvers: { + Query: { + foo: () => { + throw new Error('kaboom'); + }, + }, + }, + }); + + const mockHandler = jest.fn(); + const testInstance = createTestkit( + [ + useEngine({ ...GraphQLJS, execute: normalizedExecutor, subscribe: normalizedExecutor }), + useErrorHandler(mockHandler), + ], + schema, + ); + const result = await testInstance.execute(`query { ... @defer { foo } }`); + assertStreamExecutionValue(result); + await collectAsyncIteratorValues(result); + + expect(mockHandler).toHaveBeenCalledWith(expect.objectContaining({ phase: 'execution' })); + }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9949beaba0..7e83b8c49e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -644,6 +644,9 @@ importers: specifier: ^2.5.0 version: 2.5.0 devDependencies: + '@graphql-tools/executor': + specifier: ^1.1.0 + version: 1.1.0(graphql@16.6.0) '@graphql-tools/schema': specifier: 10.0.2 version: 10.0.2(graphql@16.6.0) @@ -4786,7 +4789,7 @@ packages: '@graphql-typed-document-node/core': 3.2.0(graphql@16.6.0) '@repeaterjs/repeater': 3.0.5 graphql: 16.6.0 - tslib: 2.5.0 + tslib: 2.6.2 value-or-promise: 1.0.12 dev: true