diff --git a/src/execution/buildFieldPlan.ts b/src/execution/buildFieldPlan.ts index 48035e7886..7f9f6bc98e 100644 --- a/src/execution/buildFieldPlan.ts +++ b/src/execution/buildFieldPlan.ts @@ -3,16 +3,12 @@ import { isSameSet } from '../jsutils/isSameSet.js'; import type { DeferUsage, FieldDetails } from './collectFields.js'; -export const NON_DEFERRED_TARGET_SET: TargetSet = new Set([undefined]); - -export type Target = DeferUsage | undefined; -export type TargetSet = ReadonlySet; export type DeferUsageSet = ReadonlySet; export interface FieldGroup { fields: ReadonlyArray; - targets?: TargetSet | undefined; - knownTargets?: TargetSet | undefined; + deferUsages?: DeferUsageSet | undefined; + knownDeferUsages?: DeferUsageSet | undefined; } export type GroupedFieldSet = Map; @@ -24,19 +20,23 @@ export interface NewGroupedFieldSetDetails { export function buildFieldPlan( fields: Map>, - parentTargets = NON_DEFERRED_TARGET_SET, - knownTargets = NON_DEFERRED_TARGET_SET, + parentDeferUsages: DeferUsageSet = new Set(), + knownDeferUsages: DeferUsageSet = new Set(), ): { groupedFieldSet: GroupedFieldSet; newGroupedFieldSetDetailsMap: Map; newDeferUsages: ReadonlyArray; } { const newDeferUsages: Set = new Set(); - const newKnownTargets = new Set(knownTargets); + const newKnownDeferUsages = new Set(knownDeferUsages); const groupedFieldSet = new Map< string, - { fields: Array; targets: TargetSet; knownTargets: TargetSet } + { + fields: Array; + deferUsages: DeferUsageSet; + knownDeferUsages: DeferUsageSet; + } >(); const newGroupedFieldSetDetailsMap = new Map< @@ -46,8 +46,8 @@ export function buildFieldPlan( string, { fields: Array; - targets: TargetSet; - knownTargets: TargetSet; + deferUsages: DeferUsageSet; + knownDeferUsages: DeferUsageSet; } >; shouldInitiateDefer: boolean; @@ -56,41 +56,50 @@ export function buildFieldPlan( const map = new Map< string, - { targetSet: TargetSet; fieldDetailsList: ReadonlyArray } + { + deferUsageSet: DeferUsageSet; + fieldDetailsList: ReadonlyArray; + } >(); + for (const [responseKey, fieldDetailsList] of fields) { - const targetSet = new Set(); + const deferUsageSet = new Set(); + let inOriginalResult = false; for (const fieldDetails of fieldDetailsList) { - const target = fieldDetails.deferUsage; - targetSet.add(target); - if (!knownTargets.has(target)) { - // all targets that are not known must be defined - newDeferUsages.add(target as DeferUsage); + const deferUsage = fieldDetails.deferUsage; + if (deferUsage === undefined) { + inOriginalResult = true; + continue; } - newKnownTargets.add(target); - } - map.set(responseKey, { targetSet, fieldDetailsList }); - } - - for (const [responseKey, { targetSet, fieldDetailsList }] of map) { - const maskingTargetList: Array = []; - for (const target of targetSet) { - if ( - target === undefined || - getAncestors(target).every((ancestor) => !targetSet.has(ancestor)) - ) { - maskingTargetList.push(target); + deferUsageSet.add(deferUsage); + if (!knownDeferUsages.has(deferUsage)) { + newDeferUsages.add(deferUsage); + newKnownDeferUsages.add(deferUsage); } } + if (inOriginalResult) { + deferUsageSet.clear(); + } else { + deferUsageSet.forEach((deferUsage) => { + const ancestors = getAncestors(deferUsage); + for (const ancestor of ancestors) { + if (deferUsageSet.has(ancestor)) { + deferUsageSet.delete(deferUsage); + } + } + }); + } + map.set(responseKey, { deferUsageSet, fieldDetailsList }); + } - const maskingTargets: TargetSet = new Set(maskingTargetList); - if (isSameSet(maskingTargets, parentTargets)) { + for (const [responseKey, { deferUsageSet, fieldDetailsList }] of map) { + if (isSameSet(deferUsageSet, parentDeferUsages)) { let fieldGroup = groupedFieldSet.get(responseKey); if (fieldGroup === undefined) { fieldGroup = { fields: [], - targets: maskingTargets, - knownTargets: newKnownTargets, + deferUsages: deferUsageSet, + knownDeferUsages: newKnownDeferUsages, }; groupedFieldSet.set(responseKey, fieldGroup); } @@ -100,7 +109,7 @@ export function buildFieldPlan( let newGroupedFieldSetDetails = getBySet( newGroupedFieldSetDetailsMap, - maskingTargets, + deferUsageSet, ); let newGroupedFieldSet; if (newGroupedFieldSetDetails === undefined) { @@ -108,20 +117,19 @@ export function buildFieldPlan( string, { fields: Array; - targets: TargetSet; - knownTargets: TargetSet; + deferUsages: DeferUsageSet; + knownDeferUsages: DeferUsageSet; } >(); newGroupedFieldSetDetails = { groupedFieldSet: newGroupedFieldSet, - shouldInitiateDefer: maskingTargetList.some( - (deferUsage) => !parentTargets.has(deferUsage), + shouldInitiateDefer: Array.from(deferUsageSet).some( + (deferUsage) => !parentDeferUsages.has(deferUsage), ), }; newGroupedFieldSetDetailsMap.set( - // all new grouped field sets must not contain the initial result as a target - maskingTargets as DeferUsageSet, + deferUsageSet, newGroupedFieldSetDetails, ); } else { @@ -131,8 +139,8 @@ export function buildFieldPlan( if (fieldGroup === undefined) { fieldGroup = { fields: [], - targets: maskingTargets, - knownTargets: newKnownTargets, + deferUsages: deferUsageSet, + knownDeferUsages: newKnownDeferUsages, }; newGroupedFieldSet.set(responseKey, fieldGroup); } @@ -146,14 +154,12 @@ export function buildFieldPlan( }; } -function getAncestors( - deferUsage: DeferUsage, -): ReadonlyArray { +function getAncestors(deferUsage: DeferUsage): ReadonlyArray { + const ancestors: Array = []; let parentDeferUsage: DeferUsage | undefined = deferUsage.parentDeferUsage; - const ancestors: Array = [parentDeferUsage]; while (parentDeferUsage !== undefined) { - parentDeferUsage = parentDeferUsage.parentDeferUsage; ancestors.unshift(parentDeferUsage); + parentDeferUsage = parentDeferUsage.parentDeferUsage; } return ancestors; } diff --git a/src/execution/execute.ts b/src/execution/execute.ts index 6442f7f0d0..baf6b41ea1 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -101,8 +101,8 @@ const buildSubFieldPlan = memoize3( ); return buildFieldPlan( subFields, - fieldGroup.targets, - fieldGroup.knownTargets, + fieldGroup.deferUsages, + fieldGroup.knownDeferUsages, ); }, ); @@ -1464,17 +1464,15 @@ function addNewDeferredFragments( // For each new deferUsage object: for (const newDeferUsage of newDeferUsages) { - const parentTarget = newDeferUsage.parentDeferUsage; + const parentDeferUsage = newDeferUsage.parentDeferUsage; - // If the parent target is defined, the parent target is a DeferUsage object and - // the parent result record is the DeferredFragmentRecord corresponding to that DeferUsage. - // If the parent target is not defined, the parent result record is either: + // If the parent defer usage is not defined, the parent result record is either: // - the InitialResultRecord, or // - a StreamItemsRecord, as `@defer` may be nested under `@stream`. const parent = - parentTarget === undefined + parentDeferUsage === undefined ? (incrementalDataRecord as InitialResultRecord | StreamItemsRecord) - : deferredFragmentRecordFromDeferUsage(parentTarget, newDeferMap); + : deferredFragmentRecordFromDeferUsage(parentDeferUsage, newDeferMap); // Instantiate the new record. const deferredFragmentRecord = new DeferredFragmentRecord({