88 GraphQLResolveInfo ,
99 ExecutionArgs ,
1010 DocumentNode ,
11+ ResponsePath ,
12+ FieldNode ,
1113} from 'graphql' ;
1214
1315import { Request } from 'apollo-server-env' ;
@@ -19,6 +21,8 @@ import {
1921} from 'apollo-server-core/dist/requestPipelineAPI' ;
2022export { GraphQLResponse } ;
2123
24+ import { GraphQLObjectResolver } from '@apollographql/apollo-tools' ;
25+
2226export type EndHandler = ( ...errors : Array < Error > ) => void ;
2327// A StartHandlerInvoker is a function that, given a specific GraphQLExtension,
2428// finds a specific StartHandler on that extension and calls it with appropriate
@@ -199,6 +203,15 @@ function wrapField(field: GraphQLField<any, any>): void {
199203 const fieldResolver = field . resolve ;
200204
201205 field . resolve = ( source , args , context , info ) => {
206+ // This is a bit of a hack, but since `ResponsePath` is a linked list,
207+ // a new object gets created every time a path segment is added.
208+ // So we can use that to share our `whenObjectResolved` promise across
209+ // all field resolvers for the same object.
210+ const parentPath = info . path . prev as ResponsePath & {
211+ __fields : Record < string , ReadonlyArray < FieldNode > > ;
212+ __whenObjectResolved : Promise < any > ;
213+ } ;
214+
202215 const extensionStack = context && context . _extensionStack ;
203216 const handler =
204217 ( extensionStack &&
@@ -207,12 +220,45 @@ function wrapField(field: GraphQLField<any, any>): void {
207220 /* do nothing */
208221 } ) ;
209222
210- // If no resolver has been defined for a field, use the default field resolver
211- // (which matches the behavior of graphql-js when there is no explicit resolve function defined).
223+ const resolveObject : GraphQLObjectResolver <
224+ any ,
225+ any
226+ > = ( info . parentType as any ) . resolveObject ;
227+
228+ let whenObjectResolved : Promise < any > | undefined ;
229+
230+ if ( parentPath && resolveObject ) {
231+ whenObjectResolved = parentPath . __whenObjectResolved ;
232+ if ( ! whenObjectResolved ) {
233+ whenObjectResolved = ( async ( ) => {
234+ return resolveObject ( source , context , info ) ;
235+ } ) ( ) ;
236+ parentPath . __whenObjectResolved = whenObjectResolved ;
237+ }
238+ }
239+
212240 try {
213- const result = ( fieldResolver ||
241+ // If no resolver has been defined for a field, use either the configured
242+ // field resolver or the default field resolver
243+ // (which matches the behavior of graphql-js when there is no explicit
244+ // resolve function defined).
245+ // XXX: Can't this be pulled up to the top of `wrapField` and only
246+ // assigned once? It seems `extensionStack.fieldResolver` isn't set
247+ // anywhere?
248+ const actualFieldResolver =
249+ fieldResolver ||
214250 ( extensionStack && extensionStack . fieldResolver ) ||
215- defaultFieldResolver ) ( source , args , context , info ) ;
251+ defaultFieldResolver ;
252+
253+ let result : any ;
254+ if ( whenObjectResolved ) {
255+ result = whenObjectResolved . then ( ( resolvedObject : any ) => {
256+ return actualFieldResolver ( resolvedObject , args , context , info ) ;
257+ } ) ;
258+ } else {
259+ result = actualFieldResolver ( source , args , context , info ) ;
260+ }
261+
216262 // Call the stack's handlers either immediately (if result is not a
217263 // Promise) or once the Promise is done. Then return that same
218264 // maybe-Promise value.
0 commit comments