Skip to content

Commit 7835729

Browse files
committed
inline child records that are ready
except for the initial result, which is left alone!
1 parent 13d73d6 commit 7835729

File tree

3 files changed

+363
-88
lines changed

3 files changed

+363
-88
lines changed

src/execution/IncrementalPublisher.ts

+214-29
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,18 @@ export interface FormattedCompletedResult {
169169
errors?: ReadonlyArray<GraphQLError>;
170170
}
171171

172+
interface IncrementalStreamTarget {
173+
errors?: Array<GraphQLError>;
174+
items: Array<unknown>;
175+
}
176+
172177
interface IncrementalAggregate {
173178
newPendingSources: Set<DeferredFragmentRecord | StreamRecord>;
174179
incrementalResults: Array<IncrementalResult>;
175180
completedResults: Array<CompletedResult>;
181+
deferParents: Map<DeferredFragmentRecord, SubsequentDataRecord>;
182+
initialStreams: Map<StreamRecord, SubsequentDataRecord>;
183+
streamTargets: Map<StreamRecord, IncrementalStreamTarget>;
176184
}
177185

178186
/**
@@ -234,14 +242,24 @@ export class IncrementalPublisher {
234242

235243
const parentDeferUsage = deferUsage.ancestors[0];
236244

237-
const parent =
238-
parentDeferUsage === undefined
239-
? (incrementalDataRecord as InitialResultRecord | StreamItemsRecord)
240-
: this._deferredFragmentRecordFromDeferUsage(
241-
parentDeferUsage,
242-
newDeferMap,
243-
);
244-
parent.children.add(deferredFragmentRecord);
245+
if (parentDeferUsage === undefined) {
246+
const parent = incrementalDataRecord as
247+
| InitialResultRecord
248+
| StreamItemsRecord;
249+
parent.children.add(deferredFragmentRecord);
250+
if (isStreamItemsRecord(incrementalDataRecord)) {
251+
incrementalDataRecord.childDefers.add(deferredFragmentRecord);
252+
}
253+
} else {
254+
const parent = this._deferredFragmentRecordFromDeferUsage(
255+
parentDeferUsage,
256+
newDeferMap,
257+
);
258+
parent.children.add(deferredFragmentRecord);
259+
if (!isInitialResultRecord(incrementalDataRecord)) {
260+
incrementalDataRecord.childDefers.add(deferredFragmentRecord);
261+
}
262+
}
245263

246264
newDeferMap.set(deferUsage, deferredFragmentRecord);
247265
}
@@ -307,9 +325,15 @@ export class IncrementalPublisher {
307325
});
308326

309327
if (isDeferredGroupedFieldSetRecord(incrementalDataRecord)) {
328+
incrementalDataRecord.childStreams.add(streamRecord);
310329
for (const parent of incrementalDataRecord.deferredFragmentRecords) {
311330
parent.children.add(streamItemsRecord);
312331
}
332+
} else if (isStreamItemsRecord(incrementalDataRecord)) {
333+
if (streamRecord !== incrementalDataRecord.streamRecord) {
334+
incrementalDataRecord.childStreams.add(streamRecord);
335+
}
336+
incrementalDataRecord.children.add(streamItemsRecord);
313337
} else {
314338
incrementalDataRecord.children.add(streamItemsRecord);
315339
}
@@ -596,15 +620,23 @@ export class IncrementalPublisher {
596620
newPendingSources: new Set<DeferredFragmentRecord | StreamRecord>(),
597621
incrementalResults: [],
598622
completedResults: [],
623+
deferParents: new Map(),
624+
initialStreams: new Map(),
625+
streamTargets: new Map(),
599626
};
600627
}
601628

602629
private _incrementalReducer(
603630
aggregate: IncrementalAggregate,
604631
completedRecords: ReadonlySet<SubsequentResultRecord>,
605632
): IncrementalAggregate {
606-
const { newPendingSources, incrementalResults, completedResults } =
607-
aggregate;
633+
const {
634+
newPendingSources,
635+
incrementalResults,
636+
completedResults,
637+
deferParents,
638+
initialStreams,
639+
} = aggregate;
608640
for (const subsequentResultRecord of completedRecords) {
609641
for (const child of subsequentResultRecord.children) {
610642
if (child.filtered) {
@@ -636,14 +668,56 @@ export class IncrementalPublisher {
636668
if (subsequentResultRecord.streamRecord.errors.length > 0) {
637669
continue;
638670
}
639-
const incrementalResult: IncrementalStreamResult = {
640-
items: subsequentResultRecord.items,
641-
path: subsequentResultRecord.streamRecord.path,
642-
};
643-
if (subsequentResultRecord.errors.length > 0) {
644-
incrementalResult.errors = subsequentResultRecord.errors;
671+
this._updateTargets(subsequentResultRecord, aggregate);
672+
const streamRecord = subsequentResultRecord.streamRecord;
673+
const initialStream = initialStreams.get(streamRecord);
674+
if (initialStream === undefined) {
675+
initialStreams.set(streamRecord, subsequentResultRecord);
676+
const streamResult: IncrementalStreamResult = {
677+
items: subsequentResultRecord.items,
678+
path: streamRecord.path,
679+
};
680+
if (subsequentResultRecord.errors.length > 0) {
681+
streamResult.errors = subsequentResultRecord.errors;
682+
}
683+
incrementalResults.push(streamResult);
684+
} else if (isStreamItemsRecord(initialStream)) {
685+
if (initialStream.streamRecord === streamRecord) {
686+
if (subsequentResultRecord.items.length > 0) {
687+
initialStream.items.push(...subsequentResultRecord.items);
688+
}
689+
this._updateTargetErrors(
690+
initialStream,
691+
subsequentResultRecord.errors,
692+
);
693+
} else {
694+
const target = this._findTargetFromStreamPath(
695+
initialStream.items,
696+
initialStream.path,
697+
streamRecord.path,
698+
) as Array<unknown>;
699+
if (subsequentResultRecord.items.length > 0) {
700+
target.push(...subsequentResultRecord.items);
701+
}
702+
this._updateTargetErrors(
703+
initialStream,
704+
subsequentResultRecord.errors,
705+
);
706+
}
707+
} else {
708+
const target = this._findTarget(
709+
initialStream.data as ObjMap<unknown>,
710+
initialStream.path,
711+
streamRecord.path,
712+
) as Array<unknown>;
713+
if (subsequentResultRecord.items.length > 0) {
714+
target.push(...subsequentResultRecord.items);
715+
}
716+
this._updateTargetErrors(
717+
initialStream,
718+
subsequentResultRecord.errors,
719+
);
645720
}
646-
incrementalResults.push(incrementalResult);
647721
}
648722
} else {
649723
newPendingSources.delete(subsequentResultRecord);
@@ -653,18 +727,50 @@ export class IncrementalPublisher {
653727
if (subsequentResultRecord.errors.length > 0) {
654728
continue;
655729
}
730+
const parent = deferParents.get(subsequentResultRecord);
656731
for (const deferredGroupedFieldSetRecord of subsequentResultRecord.deferredGroupedFieldSetRecords) {
657732
if (!deferredGroupedFieldSetRecord.sent) {
733+
this._updateTargets(deferredGroupedFieldSetRecord, aggregate);
658734
deferredGroupedFieldSetRecord.sent = true;
659-
const incrementalResult: IncrementalDeferResult = {
660-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
661-
data: deferredGroupedFieldSetRecord.data!,
662-
path: deferredGroupedFieldSetRecord.path,
663-
};
664-
if (deferredGroupedFieldSetRecord.errors.length > 0) {
665-
incrementalResult.errors = deferredGroupedFieldSetRecord.errors;
735+
if (parent === undefined) {
736+
const incrementalResult: IncrementalDeferResult = {
737+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
738+
data: deferredGroupedFieldSetRecord.data!,
739+
path: deferredGroupedFieldSetRecord.path,
740+
};
741+
if (deferredGroupedFieldSetRecord.errors.length > 0) {
742+
incrementalResult.errors = deferredGroupedFieldSetRecord.errors;
743+
}
744+
incrementalResults.push(incrementalResult);
745+
} else {
746+
const deferredFragmentTarget = isStreamItemsRecord(parent)
747+
? this._findTargetFromStreamPath(
748+
parent.items,
749+
parent.path,
750+
deferredGroupedFieldSetRecord.path,
751+
)
752+
: this._findTarget(
753+
parent.data as ObjMap<unknown>,
754+
parent.path,
755+
deferredGroupedFieldSetRecord.path,
756+
);
757+
758+
const deferredGroupedFieldSetTarget = this._findTarget(
759+
deferredFragmentTarget,
760+
subsequentResultRecord.path,
761+
deferredGroupedFieldSetRecord.path,
762+
);
763+
const data =
764+
deferredGroupedFieldSetRecord.data as ObjMap<unknown>;
765+
for (const key of Object.keys(data)) {
766+
(deferredGroupedFieldSetTarget as ObjMap<unknown>)[key] =
767+
data[key];
768+
}
769+
this._updateTargetErrors(
770+
parent,
771+
deferredGroupedFieldSetRecord.errors,
772+
);
666773
}
667-
incrementalResults.push(incrementalResult);
668774
}
669775
}
670776
}
@@ -673,6 +779,72 @@ export class IncrementalPublisher {
673779
return aggregate;
674780
}
675781

782+
private _updateTargets(
783+
subsequentDataRecord: SubsequentDataRecord,
784+
aggregate: IncrementalAggregate,
785+
): void {
786+
const { childDefers, childStreams } = subsequentDataRecord;
787+
const { deferParents, initialStreams } = aggregate;
788+
for (const childDefer of childDefers) {
789+
deferParents.set(childDefer, subsequentDataRecord);
790+
}
791+
for (const childStream of childStreams) {
792+
initialStreams.set(childStream, subsequentDataRecord);
793+
}
794+
}
795+
796+
private _findTarget(
797+
data: ObjMap<unknown> | Array<unknown>,
798+
dataPath: ReadonlyArray<string | number>,
799+
targetPath: ReadonlyArray<string | number>,
800+
): ObjMap<unknown> | Array<unknown> {
801+
let i = 0;
802+
while (i < dataPath.length) {
803+
i++;
804+
}
805+
let dataOrItems = data;
806+
while (i < targetPath.length) {
807+
const key = targetPath[i++];
808+
const value = (dataOrItems as ObjMap<unknown>)[key as string];
809+
dataOrItems = value as ObjMap<unknown>;
810+
}
811+
return dataOrItems;
812+
}
813+
814+
private _findTargetFromStreamPath(
815+
data: ObjMap<unknown> | Array<unknown>,
816+
dataPath: ReadonlyArray<string | number>,
817+
targetPath: ReadonlyArray<string | number>,
818+
): ObjMap<unknown> | Array<unknown> {
819+
const pathToStream = [...dataPath];
820+
const start = pathToStream.pop() as number;
821+
let i = 0;
822+
while (i < pathToStream.length) {
823+
i++;
824+
}
825+
const adjustedIndex = (targetPath[i++] as number) - start;
826+
let dataOrItems = (data as Array<unknown>)[adjustedIndex];
827+
while (i < targetPath.length) {
828+
const key = targetPath[i++];
829+
const value = (dataOrItems as ObjMap<unknown>)[key as string];
830+
dataOrItems = value as ObjMap<unknown>;
831+
}
832+
return dataOrItems as ObjMap<unknown> | Array<unknown>;
833+
}
834+
835+
private _updateTargetErrors(
836+
subsequentDataRecord: SubsequentDataRecord,
837+
errors: ReadonlyArray<GraphQLError>,
838+
): void {
839+
for (const error of errors) {
840+
if (subsequentDataRecord.errors === undefined) {
841+
subsequentDataRecord.errors = [error];
842+
} else {
843+
subsequentDataRecord.errors.push(error);
844+
}
845+
}
846+
}
847+
676848
private _incrementalFinalizer(
677849
aggregate: IncrementalAggregate,
678850
): SubsequentIncrementalExecutionResult {
@@ -832,6 +1004,8 @@ export class DeferredGroupedFieldSetRecord {
8321004
deferPriority: number;
8331005
streamPriority: number;
8341006
deferredFragmentRecords: ReadonlyArray<DeferredFragmentRecord>;
1007+
childDefers: Set<DeferredFragmentRecord>;
1008+
childStreams: Set<StreamRecord>;
8351009
groupedFieldSet: GroupedFieldSet;
8361010
shouldInitiateDefer: boolean;
8371011
errors: Array<GraphQLError>;
@@ -852,6 +1026,8 @@ export class DeferredGroupedFieldSetRecord {
8521026
this.deferPriority = opts.deferPriority;
8531027
this.streamPriority = opts.streamPriority;
8541028
this.deferredFragmentRecords = opts.deferredFragmentRecords;
1029+
this.childDefers = new Set();
1030+
this.childStreams = new Set();
8551031
this.groupedFieldSet = opts.groupedFieldSet;
8561032
this.shouldInitiateDefer = opts.shouldInitiateDefer;
8571033
this.errors = [];
@@ -918,6 +1094,8 @@ export class StreamItemsRecord {
9181094
streamPriority: number;
9191095
items: Array<unknown>;
9201096
children: Set<SubsequentResultRecord>;
1097+
childDefers: Set<DeferredFragmentRecord>;
1098+
childStreams: Set<StreamRecord>;
9211099
isFinalRecord?: boolean;
9221100
isCompletedAsyncIterator?: boolean;
9231101
isCompleted: boolean;
@@ -937,6 +1115,8 @@ export class StreamItemsRecord {
9371115
this.deferPriority = opts.deferPriority;
9381116
this.streamPriority = opts.streamPriority;
9391117
this.children = new Set();
1118+
this.childDefers = new Set();
1119+
this.childStreams = new Set();
9401120
this.errors = [];
9411121
this.isCompleted = false;
9421122
this.filtered = false;
@@ -954,13 +1134,18 @@ export class StreamItemsRecord {
9541134
}
9551135
}
9561136

957-
export type IncrementalDataRecord =
958-
| InitialResultRecord
959-
| DeferredGroupedFieldSetRecord
960-
| StreamItemsRecord;
1137+
export type IncrementalDataRecord = InitialResultRecord | SubsequentDataRecord;
1138+
1139+
type SubsequentDataRecord = DeferredGroupedFieldSetRecord | StreamItemsRecord;
9611140

9621141
export type SubsequentResultRecord = DeferredFragmentRecord | StreamItemsRecord;
9631142

1143+
function isInitialResultRecord(
1144+
incrementalDataRecord: unknown,
1145+
): incrementalDataRecord is InitialResultRecord {
1146+
return incrementalDataRecord instanceof InitialResultRecord;
1147+
}
1148+
9641149
function isDeferredGroupedFieldSetRecord(
9651150
incrementalDataRecord: unknown,
9661151
): incrementalDataRecord is DeferredGroupedFieldSetRecord {

src/execution/__tests__/defer-test.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -2055,12 +2055,12 @@ describe('Execute: defer directive', () => {
20552055
{
20562056
incremental: [
20572057
{
2058-
data: { name: 'slow', friends: [{}, {}, {}] },
2058+
data: {
2059+
name: 'slow',
2060+
friends: [{ name: 'Han' }, { name: 'Leia' }, { name: 'C-3PO' }],
2061+
},
20592062
path: ['hero'],
20602063
},
2061-
{ data: { name: 'Han' }, path: ['hero', 'friends', 0] },
2062-
{ data: { name: 'Leia' }, path: ['hero', 'friends', 1] },
2063-
{ data: { name: 'C-3PO' }, path: ['hero', 'friends', 2] },
20642064
],
20652065
completed: [
20662066
{ path: ['hero'] },

0 commit comments

Comments
 (0)