diff --git a/README.md b/README.md index 71c2b543..b976819b 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,9 @@ The `graphqlHTTP` function accepts the following options: * **`customValidateFn`**: An optional function which will be used to validate instead of default `validate` from `graphql-js`. + * **`customExecuteFn`**: An optional function which will be used to execute + instead of default `execute` from `graphql-js`. + * **`customFormatErrorFn`**: An optional function which will be used to format any errors produced by fulfilling a GraphQL operation. If no function is provided, GraphQL's default spec-compliant [`formatError`][] function will be used. diff --git a/src/__tests__/http-test.js b/src/__tests__/http-test.js index 2aac974d..797f5cd6 100644 --- a/src/__tests__/http-test.js +++ b/src/__tests__/http-test.js @@ -29,6 +29,7 @@ import { GraphQLError, BREAK, validate, + execute, } from 'graphql'; import graphqlHTTP from '../'; @@ -2000,6 +2001,37 @@ describe('test harness', () => { }); }); + describe('Custom execute', () => { + it('allow to replace default execute.', async () => { + const app = server(); + + let seenExecuteArgs; + + get( + app, + urlString(), + graphqlHTTP(() => { + return { + schema: TestSchema, + async customExecuteFn(args) { + seenExecuteArgs = args; + const result: any = await Promise.resolve(execute(args)); + result.data.test2 = 'Modification'; + return result; + }, + }; + }), + ); + + const response = await request(app).get(urlString({ query: '{test}' })); + + expect(response.text).to.equal( + '{"data":{"test":"Hello World","test2":"Modification"}}', + ); + expect(seenExecuteArgs).to.not.equal(null); + }); + }); + describe('Custom result extensions', () => { it('allows for adding extensions', async () => { const app = server(); diff --git a/src/index.js b/src/index.js index 71ad83e0..fbfa0e41 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,7 @@ import { getOperationAST, specifiedRules, } from 'graphql'; +import type { ExecutionArgs, ExecutionResult } from 'graphql'; import httpError from 'http-errors'; import url from 'url'; @@ -51,6 +52,7 @@ export type Options = ) => OptionsResult) | OptionsResult; export type OptionsResult = OptionsData | Promise; + export type OptionsData = { /** * A GraphQL schema from graphql-js. @@ -88,6 +90,12 @@ export type OptionsData = { rules: $ReadOnlyArray, ) => $ReadOnlyArray, + /** + * An optional function which will be used to execute instead of default `execute` + * from `graphql-js`. + */ + customExecuteFn?: ?(args: ExecutionArgs) => Promise, + /** * An optional function which will be used to format any errors produced by * fulfilling a GraphQL operation. If no function is provided, GraphQL's @@ -176,6 +184,7 @@ function graphqlHTTP(options: Options): Middleware { let pretty; let formatErrorFn = formatError; let validateFn = validate; + let executeFn = execute; let extensionsFn; let showGraphiQL; let query; @@ -302,15 +311,15 @@ function graphqlHTTP(options: Options): Middleware { } // Perform the execution, reporting any errors creating the context. try { - return execute( + return executeFn({ schema, - documentAST, + document: documentAST, rootValue, - context, - variables, + contextValue: context, + variableValues: variables, operationName, fieldResolver, - ); + }); } catch (contextError) { // Return 400: Bad Request if any execution context errors exist. response.statusCode = 400; @@ -407,6 +416,7 @@ function graphqlHTTP(options: Options): Middleware { } validateFn = optionsData.customValidateFn || validateFn; + executeFn = optionsData.customExecuteFn || executeFn; formatErrorFn = optionsData.customFormatErrorFn || optionsData.formatError ||