Skip to content

Commit

Permalink
simplify graph by allowing mutation
Browse files Browse the repository at this point in the history
  • Loading branch information
yaacovCR committed Jun 19, 2024
1 parent c75866f commit 970f6d6
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 124 deletions.
173 changes: 54 additions & 119 deletions src/execution/IncrementalGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,16 @@ import type {
StreamRecord,
SubsequentResultRecord,
} from './types.js';
import { isDeferredGroupedFieldSetRecord } from './types.js';

interface DeferredFragmentNode {
deferredFragmentRecord: DeferredFragmentRecord;
deferredGroupedFieldSetRecords: Set<DeferredGroupedFieldSetRecord>;
reconcilableResults: Set<ReconcilableDeferredGroupedFieldSetResult>;
children: Set<SubsequentResultNode>;
}

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<SubsequentResultNode>;
private _deferredFragmentNodes: Map<
DeferredFragmentRecord,
DeferredFragmentNode
>;
private _rootNodes: Set<SubsequentResultRecord>;

private _completedQueue: Array<IncrementalDataRecordResult>;
private _nextQueue: Array<
Expand All @@ -49,15 +33,14 @@ export class IncrementalGraph {

constructor() {
this._rootNodes = new Set();
this._deferredFragmentNodes = new Map();
this._completedQueue = [];
this._nextQueue = [];
}

getNewPending(
incrementalDataRecords: ReadonlyArray<IncrementalDataRecord>,
): ReadonlyArray<SubsequentResultRecord> {
const initialResultChildren = new Set<SubsequentResultNode>();
const initialResultChildren = new Set<SubsequentResultRecord>();
this._addIncrementalDataRecords(
incrementalDataRecords,
undefined,
Expand All @@ -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;
Expand Down Expand Up @@ -131,64 +113,48 @@ export class IncrementalGraph {
reconcilableResults: ReadonlyArray<ReconcilableDeferredGroupedFieldSetResult>;
}
| 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._removePending(deferredFragmentNode);
this._removePending(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 newPending = this._promoteNonEmptyToRoot(
deferredFragmentNode.children,
deferredFragmentRecord.children,
);
return { newPending, reconcilableResults };
}

removeDeferredFragment(
deferredFragmentRecord: DeferredFragmentRecord,
): boolean {
const deferredFragmentNode = this._deferredFragmentNodes.get(
deferredFragmentRecord,
);
if (deferredFragmentNode === undefined) {
if (!this._rootNodes.has(deferredFragmentRecord)) {
return false;
}
this._removePending(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._removePending(deferredFragmentRecord);
return true;
}

removeStream(streamRecord: StreamRecord): void {
this._removePending(streamRecord);
}

private _removePending(subsequentResultNode: SubsequentResultNode): void {
this._rootNodes.delete(subsequentResultNode);
private _removePending(subsequentResultRecord: SubsequentResultRecord): void {
this._rootNodes.delete(subsequentResultRecord);
if (this._rootNodes.size === 0) {
for (const resolve of this._nextQueue) {
resolve({ value: undefined, done: true });
Expand All @@ -199,16 +165,16 @@ export class IncrementalGraph {
private _addIncrementalDataRecords(
incrementalDataRecords: ReadonlyArray<IncrementalDataRecord>,
parents: ReadonlyArray<DeferredFragmentRecord> | undefined,
initialResultChildren?: Set<SubsequentResultNode> | undefined,
initialResultChildren?: Set<SubsequentResultRecord> | 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,
);
}
Expand All @@ -220,97 +186,66 @@ 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(
newPendingNodes: Set<SubsequentResultNode>,
maybeEmptyNewPending: Set<SubsequentResultRecord>,
): ReadonlyArray<SubsequentResultRecord> {
const newPendingResults: Array<SubsequentResultRecord> = [];
for (const node of newPendingNodes) {
if (isDeferredFragmentNode(node)) {
if (node.deferredGroupedFieldSetRecords.size > 0) {
for (const deferredGroupedFieldSetRecord of node.deferredGroupedFieldSetRecords) {
const newPending: Array<SubsequentResultRecord> = [];
for (const subsequentResultRecord of maybeEmptyNewPending) {
if (isDeferredFragmentRecord(subsequentResultRecord)) {
if (subsequentResultRecord.deferredGroupedFieldSetRecords.size > 0) {
for (const deferredGroupedFieldSetRecord of subsequentResultRecord.deferredGroupedFieldSetRecords) {
if (!this._hasPendingFragment(deferredGroupedFieldSetRecord)) {
this._onDeferredGroupedFieldSet(deferredGroupedFieldSetRecord);
}
}
this._rootNodes.add(node);
newPendingResults.push(node.deferredFragmentRecord);
this._rootNodes.add(subsequentResultRecord);
newPending.push(subsequentResultRecord);
continue;
}
this._deferredFragmentNodes.delete(node.deferredFragmentRecord);
for (const child of node.children) {
newPendingNodes.add(child);
for (const child of subsequentResultRecord.children) {
maybeEmptyNewPending.add(child);
}
} else {
this._rootNodes.add(node);
newPendingResults.push(node);
this._rootNodes.add(subsequentResultRecord);
newPending.push(subsequentResultRecord);

// eslint-disable-next-line @typescript-eslint/no-floating-promises
this._onStreamItems(node);
this._onStreamItems(subsequentResultRecord);
}
}
return newPendingResults;
return newPending;
}

private _hasPendingFragment(
deferredGroupedFieldSetRecord: DeferredGroupedFieldSetRecord,
): boolean {
return this._fragmentsToNodes(
deferredGroupedFieldSetRecord.deferredFragmentRecords,
).some((node) => this._rootNodes.has(node));
}

private _fragmentsToNodes(
deferredFragmentRecords: ReadonlyArray<DeferredFragmentRecord>,
): Array<DeferredFragmentNode> {
return deferredFragmentRecords
.map((deferredFragmentRecord) =>
this._deferredFragmentNodes.get(deferredFragmentRecord),
)
.filter<DeferredFragmentNode>(isDeferredFragmentNode);
return deferredGroupedFieldSetRecord.deferredFragmentRecords.some(
(deferredFragmentRecord) => this._rootNodes.has(deferredFragmentRecord),
);
}

private _addDeferredFragmentNode(
private _addDeferredFragment(
deferredFragmentRecord: DeferredFragmentRecord,
initialResultChildren: Set<SubsequentResultNode> | undefined,
): DeferredFragmentNode {
let deferredFragmentNode = this._deferredFragmentNodes.get(
deferredFragmentRecord,
);
if (deferredFragmentNode !== undefined) {
return deferredFragmentNode;
subsequentResultRecords: Set<SubsequentResultRecord> | 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;
invariant(subsequentResultRecords !== undefined);
subsequentResultRecords.add(deferredFragmentRecord);
return;
}
const parentNode = this._addDeferredFragmentNode(
parent,
initialResultChildren,
);
parentNode.children.add(deferredFragmentNode);
return deferredFragmentNode;
parent.children.add(deferredFragmentRecord);
this._addDeferredFragment(parent, subsequentResultRecords);
}

private _onDeferredGroupedFieldSet(
Expand Down
8 changes: 4 additions & 4 deletions src/execution/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ import { buildIncrementalResponse } from './IncrementalPublisher.js';
import { mapAsyncIterable } from './mapAsyncIterable.js';
import type {
CancellableStreamRecord,
DeferredFragmentRecord,
DeferredGroupedFieldSetRecord,
DeferredGroupedFieldSetResult,
ExecutionResult,
Expand All @@ -73,6 +72,7 @@ import type {
StreamItemResult,
StreamRecord,
} from './types.js';
import { DeferredFragmentRecord } from './types.js';
import {
getArgumentValues,
getDirectiveValues,
Expand Down Expand Up @@ -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);
Expand Down
25 changes: 24 additions & 1 deletion src/execution/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<DeferredGroupedFieldSetRecord>;
reconcilableResults: Set<ReconcilableDeferredGroupedFieldSetResult>;
children: Set<SubsequentResultRecord>;

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 {
Expand Down

0 comments on commit 970f6d6

Please sign in to comment.