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 74f2af7 commit ca687b4
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 125 deletions.
184 changes: 60 additions & 124 deletions src/execution/IncrementalGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,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 _pending: Set<SubsequentResultNode>;
private _deferredFragmentNodes: Map<
DeferredFragmentRecord,
DeferredFragmentNode
>;
private _pending: Set<SubsequentResultRecord>;

private _completedQueue: Array<IncrementalDataRecordResult>;
private _nextQueue: Array<
Expand All @@ -50,33 +34,31 @@ export class IncrementalGraph {

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

getNewPending(
incrementalDataRecords: ReadonlyArray<IncrementalDataRecord>,
): ReadonlyArray<SubsequentResultRecord> {
const newPending = new Set<SubsequentResultNode>();
const newPending = new Set<SubsequentResultRecord>();
this._addIncrementalDataRecords(
incrementalDataRecords,
undefined,
newPending,
);
return this._pendingNodesToResults(newPending);
return this._getNewPending(newPending);
}

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 @@ -132,64 +114,46 @@ 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._pending.has(deferredFragmentRecord)) {
return undefined;
}
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._pendingNodesToResults(
deferredFragmentNode.children,
);
const newPending = this._getNewPending(deferredFragmentRecord.children);
return { newPending, reconcilableResults };
}

removeDeferredFragment(
deferredFragmentRecord: DeferredFragmentRecord,
): boolean {
const deferredFragmentNode = this._deferredFragmentNodes.get(
deferredFragmentRecord,
);
if (deferredFragmentNode === undefined) {
if (!this._pending.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._pending.delete(subsequentResultNode);
private _removePending(subsequentResultRecord: SubsequentResultRecord): void {
this._pending.delete(subsequentResultRecord);
if (this._pending.size === 0) {
for (const resolve of this._nextQueue) {
resolve({ value: undefined, done: true });
Expand All @@ -200,116 +164,88 @@ export class IncrementalGraph {
private _addIncrementalDataRecords(
incrementalDataRecords: ReadonlyArray<IncrementalDataRecord>,
parents: ReadonlyArray<DeferredFragmentRecord> | undefined,
newPending?: Set<SubsequentResultNode> | undefined,
subsequentResultRecords?: 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,
newPending,
subsequentResultRecords,
);
deferredFragmentNode.deferredGroupedFieldSetRecords.add(
deferredFragmentRecord.deferredGroupedFieldSetRecords.add(
incrementalDataRecord,
);
}
if (this._hasPendingFragment(incrementalDataRecord)) {
this._onDeferredGroupedFieldSet(incrementalDataRecord);
}
} else if (parents === undefined) {
invariant(newPending !== undefined);
newPending.add(incrementalDataRecord);
invariant(subsequentResultRecords !== undefined);
subsequentResultRecords.add(incrementalDataRecord);
} else {
for (const parent of parents) {
const deferredFragmentNode = this._addDeferredFragmentNode(
parent,
newPending,
);
deferredFragmentNode.children.add(incrementalDataRecord);
this._addDeferredFragment(parent, subsequentResultRecords);
parent.children.add(incrementalDataRecord);
}
}
}
}

private _pendingNodesToResults(
newPendingNodes: Set<SubsequentResultNode>,
private _getNewPending(
subsequentResultRecords: Set<SubsequentResultRecord>,
): ReadonlyArray<SubsequentResultRecord> {
const newPendingResults: Array<SubsequentResultRecord> = [];
for (const node of newPendingNodes) {
if (isDeferredFragmentNode(node)) {
if (node.deferredGroupedFieldSetRecords.size > 0) {
node.deferredFragmentRecord.setAsPending();
for (const deferredGroupedFieldSetRecord of node.deferredGroupedFieldSetRecords) {
const newPending: Array<SubsequentResultRecord> = [];
for (const subsequentResultRecord of subsequentResultRecords) {
if (isDeferredFragmentRecord(subsequentResultRecord)) {
if (subsequentResultRecord.deferredGroupedFieldSetRecords.size > 0) {
subsequentResultRecord.setAsPending();
for (const deferredGroupedFieldSetRecord of subsequentResultRecord.deferredGroupedFieldSetRecords) {
if (!this._hasPendingFragment(deferredGroupedFieldSetRecord)) {
this._onDeferredGroupedFieldSet(deferredGroupedFieldSetRecord);
}
}
this._pending.add(node);
newPendingResults.push(node.deferredFragmentRecord);
this._pending.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) {
subsequentResultRecords.add(child);
}
} else {
this._pending.add(node);
newPendingResults.push(node);
this._pending.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._pending.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._pending.has(deferredFragmentRecord),
);
}

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

private _onDeferredGroupedFieldSet(
Expand Down
14 changes: 13 additions & 1 deletion src/execution/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,11 @@ export class DeferredFragmentRecord {
path: Path | undefined;
label: string | undefined;
parent: DeferredFragmentRecord | undefined;
fns: Array<() => void>;
deferredGroupedFieldSetRecords: Set<DeferredGroupedFieldSetRecord>;
reconcilableResults: Set<ReconcilableDeferredGroupedFieldSetResult>;
children: Set<SubsequentResultRecord>;
pending: boolean;
fns: Array<() => void>;

constructor(
path: Path | undefined,
Expand All @@ -230,6 +233,9 @@ export class DeferredFragmentRecord {
this.path = path;
this.label = label;
this.parent = parent;
this.deferredGroupedFieldSetRecords = new Set();
this.reconcilableResults = new Set();
this.children = new Set();
this.pending = false;
this.fns = [];
}
Expand All @@ -246,6 +252,12 @@ export class DeferredFragmentRecord {
}
}

export function isDeferredFragmentRecord(
subsequentResultRecord: SubsequentResultRecord,
): subsequentResultRecord is DeferredFragmentRecord {
return subsequentResultRecord instanceof DeferredFragmentRecord;
}

export interface StreamItemResult {
item?: unknown;
incrementalDataRecords?: ReadonlyArray<IncrementalDataRecord> | undefined;
Expand Down

0 comments on commit ca687b4

Please sign in to comment.