diff --git a/src/execution/execute.ts b/src/execution/execute.ts index 99582b828d..2b4025401c 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -281,52 +281,26 @@ function executeOperation( rootType, operation, ); - let groupedFieldSet = collectedFields.groupedFieldSet; - const newDeferUsages = collectedFields.newDeferUsages; - let graphqlWrappedResult: PromiseOrValue< - GraphQLWrappedResult> - >; - if (newDeferUsages.length === 0) { - graphqlWrappedResult = executeRootGroupedFieldSet( - exeContext, - operation.operation, - rootType, - rootValue, - groupedFieldSet, - undefined, - ); - } else { - const executionPlan = buildExecutionPlan(groupedFieldSet); - groupedFieldSet = executionPlan.groupedFieldSet; - const newGroupedFieldSets = executionPlan.newGroupedFieldSets; - const newDeferMap = addNewDeferredFragments(newDeferUsages, new Map()); - graphqlWrappedResult = executeRootGroupedFieldSet( - exeContext, - operation.operation, - rootType, - rootValue, - groupedFieldSet, - newDeferMap, - ); - - if (newGroupedFieldSets.size > 0) { - const newPendingExecutionGroups = collectExecutionGroups( - exeContext, - rootType, - rootValue, - undefined, - undefined, - newGroupedFieldSets, - newDeferMap, - ); + const { groupedFieldSet, newDeferUsages } = collectedFields; + const graphqlWrappedResult = + newDeferUsages.length === 0 + ? executeRootGroupedFieldSet( + exeContext, + operation.operation, + rootType, + rootValue, + groupedFieldSet, + undefined, + ) + : executeExecutionPlan( + exeContext, + rootType, + rootValue, + newDeferUsages, + buildExecutionPlan(groupedFieldSet), + ); - graphqlWrappedResult = withNewExecutionGroups( - graphqlWrappedResult, - newPendingExecutionGroups, - ); - } - } if (isPromise(graphqlWrappedResult)) { return graphqlWrappedResult.then( (resolved) => buildDataResponse(exeContext, resolved[0], resolved[1]), @@ -346,6 +320,49 @@ function executeOperation( } } +function executeExecutionPlan( + exeContext: ExecutionContext, + returnType: GraphQLObjectType, + sourceValue: unknown, + newDeferUsages: ReadonlyArray, + executionPlan: ExecutionPlan, + path?: Path | undefined, + incrementalContext?: IncrementalContext | undefined, + deferMap?: ReadonlyMap | undefined, +): PromiseOrValue>> { + const newDeferMap = getNewDeferMap(newDeferUsages, deferMap, path); + + const { groupedFieldSet, newGroupedFieldSets } = executionPlan; + + const graphqlWrappedResult = executeFields( + exeContext, + returnType, + sourceValue, + path, + groupedFieldSet, + incrementalContext, + newDeferMap, + ); + + if (newGroupedFieldSets.size > 0) { + const newPendingExecutionGroups = collectExecutionGroups( + exeContext, + returnType, + sourceValue, + path, + incrementalContext?.deferUsageSet, + newGroupedFieldSets, + newDeferMap, + ); + + return withNewExecutionGroups( + graphqlWrappedResult, + newPendingExecutionGroups, + ); + } + return graphqlWrappedResult; +} + function withNewExecutionGroups( result: PromiseOrValue>>, newPendingExecutionGroups: ReadonlyArray, @@ -1663,21 +1680,14 @@ function invalidReturnTypeError( * * Note: As defer directives may be used with operations returning lists, * a DeferUsage object may correspond to many DeferredFragmentRecords. - * - * DeferredFragmentRecord creation includes the following steps: - * 1. The new DeferredFragmentRecord is instantiated at the given path. - * 2. The parent result record is calculated from the given incremental data - * record. - * 3. The IncrementalPublisher is notified that a new DeferredFragmentRecord - * with the calculated parent has been added; the record will be released only - * after the parent has completed. - * */ -function addNewDeferredFragments( +function getNewDeferMap( newDeferUsages: ReadonlyArray, - newDeferMap: Map, + deferMap?: ReadonlyMap | undefined, path?: Path | undefined, ): ReadonlyMap { + const newDeferMap = new Map(deferMap); + // For each new deferUsage object: for (const newDeferUsage of newDeferUsages) { const parentDeferUsage = newDeferUsage.parentDeferUsage; @@ -1724,56 +1734,30 @@ function collectAndExecuteSubfields( returnType, fieldGroup, ); - let groupedFieldSet = collectedSubfields.groupedFieldSet; - const newDeferUsages = collectedSubfields.newDeferUsages; - if (deferMap === undefined && newDeferUsages.length === 0) { - return executeFields( - exeContext, - returnType, - result, - path, - groupedFieldSet, - incrementalContext, - undefined, - ); - } - const subExecutionPlan = buildSubExecutionPlan( - groupedFieldSet, - incrementalContext?.deferUsageSet, - ); - - groupedFieldSet = subExecutionPlan.groupedFieldSet; - const newGroupedFieldSets = subExecutionPlan.newGroupedFieldSets; - const newDeferMap = addNewDeferredFragments( - newDeferUsages, - new Map(deferMap), - path, - ); - - const subFields = executeFields( - exeContext, - returnType, - result, - path, - groupedFieldSet, - incrementalContext, - newDeferMap, - ); - - if (newGroupedFieldSets.size > 0) { - const newPendingExecutionGroups = collectExecutionGroups( - exeContext, - returnType, - result, - path, - incrementalContext?.deferUsageSet, - newGroupedFieldSets, - newDeferMap, - ); - - return withNewExecutionGroups(subFields, newPendingExecutionGroups); - } - return subFields; + const { groupedFieldSet, newDeferUsages } = collectedSubfields; + return deferMap === undefined && newDeferUsages.length === 0 + ? executeFields( + exeContext, + returnType, + result, + path, + groupedFieldSet, + incrementalContext, + undefined, + ) + : executeExecutionPlan( + exeContext, + returnType, + result, + newDeferUsages, + buildSubExecutionPlan( + groupedFieldSet, + incrementalContext?.deferUsageSet, + ), + path, + incrementalContext, + deferMap, + ); } function buildSubExecutionPlan(