diff --git a/src/execution/IncrementalGraph.ts b/src/execution/IncrementalGraph.ts index 76f1a32351..7469a36fa5 100644 --- a/src/execution/IncrementalGraph.ts +++ b/src/execution/IncrementalGraph.ts @@ -15,32 +15,16 @@ import type { StreamRecord, SubsequentResultRecord, } from './types.js'; -import { isDeferredGroupedFieldSetRecord } from './types.js'; - -interface DeferredFragmentNode { - deferredFragmentRecord: DeferredFragmentRecord; - deferredGroupedFieldSetRecords: Set; - reconcilableResults: Set; - children: Set; -} - -function isDeferredFragmentNode( - node: SubsequentResultNode | undefined, -): node is DeferredFragmentNode { - return node !== undefined && 'deferredFragmentRecord' in node; -} - -type SubsequentResultNode = DeferredFragmentNode | StreamRecord; +import { + isDeferredFragmentRecord, + isDeferredGroupedFieldSetRecord, +} from './types.js'; /** * @internal */ export class IncrementalGraph { - private _rootNodes: Set; - private _deferredFragmentNodes: Map< - DeferredFragmentRecord, - DeferredFragmentNode - >; + private _rootNodes: Set; private _completedQueue: Array; private _nextQueue: Array< @@ -49,7 +33,6 @@ export class IncrementalGraph { constructor() { this._rootNodes = new Set(); - this._deferredFragmentNodes = new Map(); this._completedQueue = []; this._nextQueue = []; } @@ -57,7 +40,7 @@ export class IncrementalGraph { getNewRootNodes( incrementalDataRecords: ReadonlyArray, ): ReadonlyArray { - const initialResultChildren = new Set(); + const initialResultChildren = new Set(); this._addIncrementalDataRecords( incrementalDataRecords, undefined, @@ -69,13 +52,12 @@ export class IncrementalGraph { addCompletedReconcilableDeferredGroupedFieldSet( reconcilableResult: ReconcilableDeferredGroupedFieldSetResult, ): void { - for (const deferredFragmentNode of this._fragmentsToNodes( - reconcilableResult.deferredGroupedFieldSetRecord.deferredFragmentRecords, - )) { - deferredFragmentNode.deferredGroupedFieldSetRecords.delete( + for (const deferredFragmentRecord of reconcilableResult + .deferredGroupedFieldSetRecord.deferredFragmentRecords) { + deferredFragmentRecord.deferredGroupedFieldSetRecords.delete( reconcilableResult.deferredGroupedFieldSetRecord, ); - deferredFragmentNode.reconcilableResults.add(reconcilableResult); + deferredFragmentRecord.reconcilableResults.add(reconcilableResult); } const incrementalDataRecords = reconcilableResult.incrementalDataRecords; @@ -131,33 +113,28 @@ export class IncrementalGraph { reconcilableResults: ReadonlyArray; } | undefined { - const deferredFragmentNode = this._deferredFragmentNodes.get( - deferredFragmentRecord, - ); // TODO: add test case? /* c8 ignore next 3 */ - if (deferredFragmentNode === undefined) { + if (!this._rootNodes.has(deferredFragmentRecord)) { return; } - if (deferredFragmentNode.deferredGroupedFieldSetRecords.size > 0) { + if (deferredFragmentRecord.deferredGroupedFieldSetRecords.size > 0) { return; } const reconcilableResults = Array.from( - deferredFragmentNode.reconcilableResults, + deferredFragmentRecord.reconcilableResults, ); - this._removeRootNode(deferredFragmentNode); + this._removeRootNode(deferredFragmentRecord); for (const reconcilableResult of reconcilableResults) { - for (const otherDeferredFragmentNode of this._fragmentsToNodes( - reconcilableResult.deferredGroupedFieldSetRecord - .deferredFragmentRecords, - )) { - otherDeferredFragmentNode.reconcilableResults.delete( + for (const otherDeferredFragmentRecord of reconcilableResult + .deferredGroupedFieldSetRecord.deferredFragmentRecords) { + otherDeferredFragmentRecord.reconcilableResults.delete( reconcilableResult, ); } } const newRootNodes = this._promoteNonEmptyToRoot( - deferredFragmentNode.children, + deferredFragmentRecord.children, ); return { newRootNodes, reconcilableResults }; } @@ -165,21 +142,10 @@ export class IncrementalGraph { removeDeferredFragment( deferredFragmentRecord: DeferredFragmentRecord, ): boolean { - const deferredFragmentNode = this._deferredFragmentNodes.get( - deferredFragmentRecord, - ); - if (deferredFragmentNode === undefined) { + if (!this._rootNodes.has(deferredFragmentRecord)) { return false; } - this._removeRootNode(deferredFragmentNode); - this._deferredFragmentNodes.delete(deferredFragmentRecord); - // TODO: add test case for an erroring deferred fragment with child defers - /* c8 ignore next 5 */ - for (const child of deferredFragmentNode.children) { - if (isDeferredFragmentNode(child)) { - this.removeDeferredFragment(child.deferredFragmentRecord); - } - } + this._removeRootNode(deferredFragmentRecord); return true; } @@ -187,8 +153,10 @@ export class IncrementalGraph { this._removeRootNode(streamRecord); } - private _removeRootNode(subsequentResultNode: SubsequentResultNode): void { - this._rootNodes.delete(subsequentResultNode); + private _removeRootNode( + subsequentResultRecord: SubsequentResultRecord, + ): void { + this._rootNodes.delete(subsequentResultRecord); if (this._rootNodes.size === 0) { for (const resolve of this._nextQueue) { resolve({ value: undefined, done: true }); @@ -199,16 +167,16 @@ export class IncrementalGraph { private _addIncrementalDataRecords( incrementalDataRecords: ReadonlyArray, parents: ReadonlyArray | undefined, - initialResultChildren?: Set | undefined, + initialResultChildren?: Set | undefined, ): void { for (const incrementalDataRecord of incrementalDataRecords) { if (isDeferredGroupedFieldSetRecord(incrementalDataRecord)) { for (const deferredFragmentRecord of incrementalDataRecord.deferredFragmentRecords) { - const deferredFragmentNode = this._addDeferredFragmentNode( + this._addDeferredFragment( deferredFragmentRecord, initialResultChildren, ); - deferredFragmentNode.deferredGroupedFieldSetRecords.add( + deferredFragmentRecord.deferredGroupedFieldSetRecords.add( incrementalDataRecord, ); } @@ -220,22 +188,19 @@ export class IncrementalGraph { initialResultChildren.add(incrementalDataRecord); } else { for (const parent of parents) { - const deferredFragmentNode = this._addDeferredFragmentNode( - parent, - initialResultChildren, - ); - deferredFragmentNode.children.add(incrementalDataRecord); + this._addDeferredFragment(parent, initialResultChildren); + parent.children.add(incrementalDataRecord); } } } } private _promoteNonEmptyToRoot( - maybeEmptyNewRootNodes: Set, + maybeEmptyNewRootNodes: Set, ): ReadonlyArray { const newRootNodes: Array = []; for (const node of maybeEmptyNewRootNodes) { - if (isDeferredFragmentNode(node)) { + if (isDeferredFragmentRecord(node)) { if (node.deferredGroupedFieldSetRecords.size > 0) { for (const deferredGroupedFieldSetRecord of node.deferredGroupedFieldSetRecords) { if (!this._completesRootNode(deferredGroupedFieldSetRecord)) { @@ -243,10 +208,9 @@ export class IncrementalGraph { } } this._rootNodes.add(node); - newRootNodes.push(node.deferredFragmentRecord); + newRootNodes.push(node); continue; } - this._deferredFragmentNodes.delete(node.deferredFragmentRecord); for (const child of node.children) { maybeEmptyNewRootNodes.add(child); } @@ -264,53 +228,26 @@ export class IncrementalGraph { private _completesRootNode( deferredGroupedFieldSetRecord: DeferredGroupedFieldSetRecord, ): boolean { - return this._fragmentsToNodes( - deferredGroupedFieldSetRecord.deferredFragmentRecords, - ).some((node) => this._rootNodes.has(node)); - } - - private _fragmentsToNodes( - deferredFragmentRecords: ReadonlyArray, - ): Array { - return deferredFragmentRecords - .map((deferredFragmentRecord) => - this._deferredFragmentNodes.get(deferredFragmentRecord), - ) - .filter(isDeferredFragmentNode); + return deferredGroupedFieldSetRecord.deferredFragmentRecords.some( + (deferredFragmentRecord) => this._rootNodes.has(deferredFragmentRecord), + ); } - private _addDeferredFragmentNode( + private _addDeferredFragment( deferredFragmentRecord: DeferredFragmentRecord, - initialResultChildren: Set | undefined, - ): DeferredFragmentNode { - let deferredFragmentNode = this._deferredFragmentNodes.get( - deferredFragmentRecord, - ); - if (deferredFragmentNode !== undefined) { - return deferredFragmentNode; + initialResultChildren: Set | undefined, + ): void { + if (this._rootNodes.has(deferredFragmentRecord)) { + return; } - deferredFragmentNode = { - deferredFragmentRecord, - deferredGroupedFieldSetRecords: new Set(), - reconcilableResults: new Set(), - children: new Set(), - }; - this._deferredFragmentNodes.set( - deferredFragmentRecord, - deferredFragmentNode, - ); const parent = deferredFragmentRecord.parent; if (parent === undefined) { invariant(initialResultChildren !== undefined); - initialResultChildren.add(deferredFragmentNode); - return deferredFragmentNode; + initialResultChildren.add(deferredFragmentRecord); + return; } - const parentNode = this._addDeferredFragmentNode( - parent, - initialResultChildren, - ); - parentNode.children.add(deferredFragmentNode); - return deferredFragmentNode; + parent.children.add(deferredFragmentRecord); + this._addDeferredFragment(parent, initialResultChildren); } private _onDeferredGroupedFieldSet( diff --git a/src/execution/execute.ts b/src/execution/execute.ts index d01b6ee768..f32036f645 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -63,7 +63,6 @@ import { buildIncrementalResponse } from './IncrementalPublisher.js'; import { mapAsyncIterable } from './mapAsyncIterable.js'; import type { CancellableStreamRecord, - DeferredFragmentRecord, DeferredGroupedFieldSetRecord, DeferredGroupedFieldSetResult, ExecutionResult, @@ -73,6 +72,7 @@ import type { StreamItemResult, StreamRecord, } from './types.js'; +import { DeferredFragmentRecord } from './types.js'; import { getArgumentValues, getDirectiveValues, @@ -1676,11 +1676,11 @@ function addNewDeferredFragments( : deferredFragmentRecordFromDeferUsage(parentDeferUsage, newDeferMap); // Instantiate the new record. - const deferredFragmentRecord: DeferredFragmentRecord = { + const deferredFragmentRecord = new DeferredFragmentRecord( path, - label: newDeferUsage.label, + newDeferUsage.label, parent, - }; + ); // Update the map. newDeferMap.set(newDeferUsage, deferredFragmentRecord); diff --git a/src/execution/types.ts b/src/execution/types.ts index 50f9a083f8..c88ae9986e 100644 --- a/src/execution/types.ts +++ b/src/execution/types.ts @@ -214,11 +214,34 @@ export interface DeferredGroupedFieldSetRecord { export type SubsequentResultRecord = DeferredFragmentRecord | StreamRecord; -export interface DeferredFragmentRecord { +/** @internal */ +export class DeferredFragmentRecord { path: Path | undefined; label: string | undefined; id?: string | undefined; parent: DeferredFragmentRecord | undefined; + deferredGroupedFieldSetRecords: Set; + reconcilableResults: Set; + children: Set; + + constructor( + path: Path | undefined, + label: string | undefined, + parent: DeferredFragmentRecord | undefined, + ) { + this.path = path; + this.label = label; + this.parent = parent; + this.deferredGroupedFieldSetRecords = new Set(); + this.reconcilableResults = new Set(); + this.children = new Set(); + } +} + +export function isDeferredFragmentRecord( + subsequentResultRecord: SubsequentResultRecord, +): subsequentResultRecord is DeferredFragmentRecord { + return subsequentResultRecord instanceof DeferredFragmentRecord; } export interface StreamItemResult {