From 26672e49903294c708ef3b60d27efe392d9a8dd3 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Thu, 6 Jul 2023 15:11:48 +0300 Subject: [PATCH] inline child records that are ready except for the initial result, which is left alone! --- src/execution/IncrementalPublisher.ts | 268 ++++++++++++++++++++++--- src/execution/__tests__/defer-test.ts | 8 +- src/execution/__tests__/stream-test.ts | 259 ++++++++++++++++++------ src/execution/execute.ts | 1 + 4 files changed, 450 insertions(+), 86 deletions(-) diff --git a/src/execution/IncrementalPublisher.ts b/src/execution/IncrementalPublisher.ts index cc3c40b207..113784752b 100644 --- a/src/execution/IncrementalPublisher.ts +++ b/src/execution/IncrementalPublisher.ts @@ -162,10 +162,18 @@ export interface FormattedCompletedResult { errors?: ReadonlyArray; } +interface IncrementalStreamTarget { + errors?: Array; + items: Array; +} + interface IncrementalAggregate { newPendingSources: Set; incrementalResults: Array; completedResults: Array; + deferParents: Map; + initialStreams: Map; + streamTargets: Map; } /** @@ -199,12 +207,17 @@ export class IncrementalPublisher { reportNewDeferFragmentRecord( deferredFragmentRecord: DeferredFragmentRecord, + parentIncrementalDataRecord: + | InitialResultRecord + | DeferredGroupedFieldSetRecord + | StreamItemsRecord, parentIncrementalResultRecord: | InitialResultRecord | DeferredFragmentRecord | StreamItemsRecord, ): void { parentIncrementalResultRecord.children.add(deferredFragmentRecord); + parentIncrementalDataRecord.childDefers.add(deferredFragmentRecord); } reportNewDeferredGroupedFieldSetRecord( @@ -225,9 +238,21 @@ export class IncrementalPublisher { if (isDeferredGroupedFieldSetRecord(parentIncrementalDataRecord)) { for (const parent of parentIncrementalDataRecord.deferredFragmentRecords) { parent.children.add(streamItemsRecord); + parentIncrementalDataRecord.childStreams.add( + streamItemsRecord.streamRecord, + ); + } + } else if (isStreamItemsRecord(parentIncrementalDataRecord)) { + const streamRecord = streamItemsRecord.streamRecord; + if (streamRecord !== parentIncrementalDataRecord.streamRecord) { + parentIncrementalDataRecord.childStreams.add(streamRecord); } + parentIncrementalDataRecord.children.add(streamItemsRecord); } else { parentIncrementalDataRecord.children.add(streamItemsRecord); + parentIncrementalDataRecord.childStreams.add( + streamItemsRecord.streamRecord, + ); } } @@ -235,7 +260,14 @@ export class IncrementalPublisher { deferredGroupedFieldSetRecord: DeferredGroupedFieldSetRecord, data: ObjMap, ): void { - deferredGroupedFieldSetRecord.data = data; + deferredGroupedFieldSetRecord.result = { + data, + path: deferredGroupedFieldSetRecord.path, + }; + const errors = deferredGroupedFieldSetRecord.errors; + if (errors.length > 0) { + deferredGroupedFieldSetRecord.result.errors = errors; + } for (const deferredFragmentRecord of deferredGroupedFieldSetRecord.deferredFragmentRecords) { deferredFragmentRecord._pending.delete(deferredGroupedFieldSetRecord); if (deferredFragmentRecord._pending.size === 0) { @@ -264,7 +296,14 @@ export class IncrementalPublisher { streamItemsRecord: StreamItemsRecord, items: Array, ) { - streamItemsRecord.items = items; + streamItemsRecord.result = { + items, + path: streamItemsRecord.streamRecord.path, + }; + const errors = streamItemsRecord.errors; + if (errors.length > 0) { + streamItemsRecord.result.errors = errors; + } streamItemsRecord.isCompleted = true; this._release(streamItemsRecord); } @@ -513,6 +552,9 @@ export class IncrementalPublisher { newPendingSources: new Set(), incrementalResults: [], completedResults: [], + deferParents: new Map(), + initialStreams: new Map(), + streamTargets: new Map(), }; } @@ -520,8 +562,13 @@ export class IncrementalPublisher { aggregate: IncrementalAggregate, completedRecords: ReadonlySet, ): IncrementalAggregate { - const { newPendingSources, incrementalResults, completedResults } = - aggregate; + const { + newPendingSources, + incrementalResults, + completedResults, + deferParents, + initialStreams, + } = aggregate; for (const subsequentResultRecord of completedRecords) { for (const child of subsequentResultRecord.children) { if (child.filtered) { @@ -549,14 +596,59 @@ export class IncrementalPublisher { if (subsequentResultRecord.streamRecord.errors.length > 0) { continue; } - const incrementalResult: IncrementalStreamResult = { - items: subsequentResultRecord.items, - path: subsequentResultRecord.streamRecord.path, - }; - if (subsequentResultRecord.errors.length > 0) { - incrementalResult.errors = subsequentResultRecord.errors; + this._updateTargets(subsequentResultRecord, aggregate); + const streamRecord = subsequentResultRecord.streamRecord; + const initialStream = initialStreams.get(streamRecord); + if (initialStream === undefined) { + initialStreams.set(streamRecord, subsequentResultRecord); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + incrementalResults.push(subsequentResultRecord.result!); + } else if (isStreamItemsRecord(initialStream)) { + if (initialStream.streamRecord === streamRecord) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const items = subsequentResultRecord.result!.items; + if (items.length > 0) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + initialStream.result!.items.push(...items); + } + this._updateTargetErrors( + initialStream, + subsequentResultRecord.errors, + ); + } else { + const target = this._findTargetFromStreamPath( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + initialStream.result!.items, + initialStream.path, + streamRecord.path, + ) as Array; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const items = subsequentResultRecord.result!.items; + if (items.length > 0) { + target.push(...items); + } + this._updateTargetErrors( + initialStream, + subsequentResultRecord.errors, + ); + } + } else { + const target = this._findTarget( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + initialStream.result!.data, + initialStream.path, + streamRecord.path, + ) as Array; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const items = subsequentResultRecord.result!.items; + if (items.length > 0) { + target.push(...items); + } + this._updateTargetErrors( + initialStream, + subsequentResultRecord.errors, + ); } - incrementalResults.push(incrementalResult); } else { newPendingSources.delete(subsequentResultRecord); completedResults.push( @@ -565,18 +657,52 @@ export class IncrementalPublisher { if (subsequentResultRecord.errors.length > 0) { continue; } + const parent = deferParents.get(subsequentResultRecord); for (const deferredGroupedFieldSetRecord of subsequentResultRecord.deferredGroupedFieldSetRecords) { if (!deferredGroupedFieldSetRecord.sent) { + this._updateTargets(deferredGroupedFieldSetRecord, aggregate); deferredGroupedFieldSetRecord.sent = true; - const incrementalResult: IncrementalDeferResult = { + if (parent === undefined) { + const incrementalResult: IncrementalDeferResult = { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + data: deferredGroupedFieldSetRecord.result!.data, + path: deferredGroupedFieldSetRecord.path, + }; + if (deferredGroupedFieldSetRecord.errors.length > 0) { + incrementalResult.errors = deferredGroupedFieldSetRecord.errors; + } + incrementalResults.push(incrementalResult); + } else { + const deferredFragmentTarget = isStreamItemsRecord(parent) + ? this._findTargetFromStreamPath( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + parent.result!.items, + parent.path, + deferredGroupedFieldSetRecord.path, + ) + : this._findTarget( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + parent.result!.data, + parent.path, + deferredGroupedFieldSetRecord.path, + ); + + const deferredGroupedFieldSetTarget = this._findTarget( + deferredFragmentTarget, + subsequentResultRecord.path, + deferredGroupedFieldSetRecord.path, + ); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - data: deferredGroupedFieldSetRecord.data!, - path: deferredGroupedFieldSetRecord.path, - }; - if (deferredGroupedFieldSetRecord.errors.length > 0) { - incrementalResult.errors = deferredGroupedFieldSetRecord.errors; + const data = deferredGroupedFieldSetRecord.result!.data; + for (const key of Object.keys(data)) { + (deferredGroupedFieldSetTarget as ObjMap)[key] = + data[key]; + } + this._updateTargetErrors( + parent, + deferredGroupedFieldSetRecord.errors, + ); } - incrementalResults.push(incrementalResult); } } } @@ -585,6 +711,74 @@ export class IncrementalPublisher { return aggregate; } + private _updateTargets( + subsequentDataRecord: SubsequentDataRecord, + aggregate: IncrementalAggregate, + ): void { + const { childDefers, childStreams } = subsequentDataRecord; + const { deferParents, initialStreams } = aggregate; + for (const childDefer of childDefers) { + deferParents.set(childDefer, subsequentDataRecord); + } + for (const childStream of childStreams) { + initialStreams.set(childStream, subsequentDataRecord); + } + } + + private _findTarget( + data: ObjMap | Array, + dataPath: ReadonlyArray, + targetPath: ReadonlyArray, + ): ObjMap | Array { + let i = 0; + while (i < dataPath.length) { + i++; + } + let dataOrItems = data; + while (i < targetPath.length) { + const key = targetPath[i++]; + const value = (dataOrItems as ObjMap)[key as string]; + dataOrItems = value as ObjMap; + } + return dataOrItems; + } + + private _findTargetFromStreamPath( + data: ObjMap | Array, + dataPath: ReadonlyArray, + targetPath: ReadonlyArray, + ): ObjMap | Array { + const pathToStream = [...dataPath]; + const start = pathToStream.pop() as number; + let i = 0; + while (i < pathToStream.length) { + i++; + } + const adjustedIndex = (targetPath[i++] as number) - start; + let dataOrItems = (data as Array)[adjustedIndex]; + while (i < targetPath.length) { + const key = targetPath[i++]; + const value = (dataOrItems as ObjMap)[key as string]; + dataOrItems = value as ObjMap; + } + return dataOrItems as ObjMap | Array; + } + + private _updateTargetErrors( + subsequentDataRecord: SubsequentDataRecord, + errors: ReadonlyArray, + ): void { + for (const error of errors) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const result = subsequentDataRecord.result!; + if (result.errors === undefined) { + result.errors = [error]; + } else { + result.errors.push(error); + } + } + } + private _incrementalFinalizer( aggregate: IncrementalAggregate, ): SubsequentIncrementalExecutionResult { @@ -722,12 +916,16 @@ export class InitialResultRecord { errors: Array; children: Set; priority: number; + childDefers: Set; + childStreams: Set; deferPriority: number; published: true; constructor() { this.errors = []; this.children = new Set(); this.priority = 0; + this.childDefers = new Set(); + this.childStreams = new Set(); this.deferPriority = 0; this.published = true; } @@ -739,10 +937,19 @@ export class DeferredGroupedFieldSetRecord { priority: number; deferPriority: number; deferredFragmentRecords: ReadonlyArray; + childDefers: Set; + childStreams: Set; groupedFieldSet: GroupedFieldSet; shouldInitiateDefer: boolean; errors: Array; - data: ObjMap | undefined; + result: + | { + errors?: Array; + data: ObjMap; + path: ReadonlyArray; + } + | undefined; + published: true | Promise; publish: () => void; sent: boolean; @@ -759,6 +966,8 @@ export class DeferredGroupedFieldSetRecord { this.priority = opts.priority; this.deferPriority = opts.deferPriority; this.deferredFragmentRecords = opts.deferredFragmentRecords; + this.childDefers = new Set(); + this.childStreams = new Set(); this.groupedFieldSet = opts.groupedFieldSet; this.shouldInitiateDefer = opts.shouldInitiateDefer; this.errors = []; @@ -819,12 +1028,21 @@ export class StreamRecord { /** @internal */ export class StreamItemsRecord { errors: Array; + result: + | { + errors?: Array; + items: Array; + path: ReadonlyArray; + } + | undefined; + streamRecord: StreamRecord; path: ReadonlyArray; priority: number; deferPriority: number; - items: Array; children: Set; + childDefers: Set; + childStreams: Set; isFinalRecord?: boolean; isCompletedAsyncIterator?: boolean; isCompleted: boolean; @@ -843,10 +1061,11 @@ export class StreamItemsRecord { this.priority = opts.priority; this.deferPriority = 0; this.children = new Set(); + this.childDefers = new Set(); + this.childStreams = new Set(); this.errors = []; this.isCompleted = false; this.filtered = false; - this.items = []; // promiseWithResolvers uses void only as a generic type parameter // see: https://typescript-eslint.io/rules/no-invalid-void-type/ // eslint-disable-next-line @typescript-eslint/no-invalid-void-type @@ -860,9 +1079,8 @@ export class StreamItemsRecord { } } -export type IncrementalDataRecord = - | InitialResultRecord - | DeferredGroupedFieldSetRecord - | StreamItemsRecord; +export type IncrementalDataRecord = InitialResultRecord | SubsequentDataRecord; + +type SubsequentDataRecord = DeferredGroupedFieldSetRecord | StreamItemsRecord; type SubsequentResultRecord = DeferredFragmentRecord | StreamItemsRecord; diff --git a/src/execution/__tests__/defer-test.ts b/src/execution/__tests__/defer-test.ts index 5ef4b866b8..ce19fca3a9 100644 --- a/src/execution/__tests__/defer-test.ts +++ b/src/execution/__tests__/defer-test.ts @@ -2087,12 +2087,12 @@ describe('Execute: defer directive', () => { { incremental: [ { - data: { name: 'slow', friends: [{}, {}, {}] }, + data: { + name: 'slow', + friends: [{ name: 'Han' }, { name: 'Leia' }, { name: 'C-3PO' }], + }, path: ['hero'], }, - { data: { name: 'Han' }, path: ['hero', 'friends', 0] }, - { data: { name: 'Leia' }, path: ['hero', 'friends', 1] }, - { data: { name: 'C-3PO' }, path: ['hero', 'friends', 2] }, ], completed: [ { path: ['hero'] }, diff --git a/src/execution/__tests__/stream-test.ts b/src/execution/__tests__/stream-test.ts index 1dd97f7263..32f7e310ca 100644 --- a/src/execution/__tests__/stream-test.ts +++ b/src/execution/__tests__/stream-test.ts @@ -11,6 +11,7 @@ import type { DocumentNode } from '../../language/ast.js'; import { parse } from '../../language/parser.js'; import { + GraphQLEnumType, GraphQLList, GraphQLNonNull, GraphQLObjectType, @@ -24,19 +25,35 @@ import type { SubsequentIncrementalExecutionResult, } from '../IncrementalPublisher.js'; +const episodeEnum = new GraphQLEnumType({ + name: 'Episode', + values: { + NEW_HOPE: { + value: 4, + }, + EMPIRE: { + value: 5, + }, + JEDI: { + value: 6, + }, + }, +}); + const friendType = new GraphQLObjectType({ fields: { id: { type: GraphQLID }, name: { type: GraphQLString }, nonNullName: { type: new GraphQLNonNull(GraphQLString) }, + appearsIn: { type: new GraphQLList(episodeEnum) }, }, name: 'Friend', }); const friends = [ - { name: 'Luke', id: 1 }, - { name: 'Han', id: 2 }, - { name: 'Leia', id: 3 }, + { name: 'Luke', id: 1, appearsIn: [4, 5, 6] }, + { name: 'Han', id: 2, appearsIn: [4, 5, 6] }, + { name: 'Leia', id: 3, appearsIn: [4, 5, 6] }, ]; const query = new GraphQLObjectType({ @@ -146,10 +163,7 @@ describe('Execute: stream directive', () => { hasNext: true, }, { - incremental: [ - { items: ['banana'], path: ['scalarList'] }, - { items: ['coconut'], path: ['scalarList'] }, - ], + incremental: [{ items: ['banana', 'coconut'], path: ['scalarList'] }], completed: [{ path: ['scalarList'] }], hasNext: false, }, @@ -170,9 +184,7 @@ describe('Execute: stream directive', () => { }, { incremental: [ - { items: ['apple'], path: ['scalarList'] }, - { items: ['banana'], path: ['scalarList'] }, - { items: ['coconut'], path: ['scalarList'] }, + { items: ['apple', 'banana', 'coconut'], path: ['scalarList'] }, ], completed: [{ path: ['scalarList'] }], hasNext: false, @@ -220,11 +232,7 @@ describe('Execute: stream directive', () => { { incremental: [ { - items: ['banana'], - path: ['scalarList'], - }, - { - items: ['coconut'], + items: ['banana', 'coconut'], path: ['scalarList'], }, ], @@ -284,15 +292,127 @@ describe('Execute: stream directive', () => { { incremental: [ { - items: [['banana', 'banana', 'banana']], + items: [ + ['banana', 'banana', 'banana'], + ['coconut', 'coconut', 'coconut'], + ], path: ['scalarListList'], }, + ], + completed: [{ path: ['scalarListList'] }], + hasNext: false, + }, + ]); + }); + it('Can nest stream directives', async () => { + const document = parse(` + query { + friendList @stream(initialCount: 1) { + name + appearsIn @stream(initialCount: 1) + } + } + `); + const result = await complete(document, { + friendList: () => friends.map((f) => Promise.resolve(f)), + }); + expectJSON(result).toDeepEqual([ + { + data: { + friendList: [ + { + name: 'Luke', + appearsIn: ['NEW_HOPE'], + }, + ], + }, + pending: [ + { path: ['friendList'] }, + { path: ['friendList', 0, 'appearsIn'] }, + ], + hasNext: true, + }, + { + incremental: [ { - items: [['coconut', 'coconut', 'coconut']], - path: ['scalarListList'], + items: [ + { + name: 'Han', + appearsIn: ['NEW_HOPE', 'EMPIRE', 'JEDI'], + }, + { + name: 'Leia', + appearsIn: ['NEW_HOPE', 'EMPIRE', 'JEDI'], + }, + ], + path: ['friendList'], + }, + { + items: ['EMPIRE', 'JEDI'], + path: ['friendList', 0, 'appearsIn'], }, ], - completed: [{ path: ['scalarListList'] }], + completed: [ + { path: ['friendList'] }, + { path: ['friendList', 0, 'appearsIn'] }, + { path: ['friendList', 1, 'appearsIn'] }, + { path: ['friendList', 2, 'appearsIn'] }, + ], + hasNext: false, + }, + ]); + }); + it('Can nest stream directives under defer', async () => { + const document = parse(` + query { + friendList @stream(initialCount: 1) { + ... @defer { + name + appearsIn @stream(initialCount: 1) + } + } + } + `); + const result = await complete(document, { + friendList: () => friends.map((f) => Promise.resolve(f)), + }); + expectJSON(result).toDeepEqual([ + { + data: { + friendList: [{}], + }, + pending: [{ path: ['friendList'] }, { path: ['friendList', 0] }], + hasNext: true, + }, + { + incremental: [ + { + items: [ + { + name: 'Han', + appearsIn: ['NEW_HOPE', 'EMPIRE', 'JEDI'], + }, + { + name: 'Leia', + appearsIn: ['NEW_HOPE', 'EMPIRE', 'JEDI'], + }, + ], + path: ['friendList'], + }, + { + data: { name: 'Luke', appearsIn: ['NEW_HOPE', 'EMPIRE', 'JEDI'] }, + path: ['friendList', 0], + }, + ], + completed: [ + { path: ['friendList', 0] }, + { path: ['friendList'] }, + { path: ['friendList', 1] }, + { path: ['friendList', 2] }, + { path: ['friendList', 0, 'appearsIn'] }, + { path: ['friendList', 1, 'appearsIn'] }, + { path: ['friendList', 2, 'appearsIn'] }, + ], hasNext: false, }, ]); @@ -366,15 +486,11 @@ describe('Execute: stream directive', () => { { incremental: [ { - items: [{ name: 'Luke', id: '1' }], - path: ['friendList'], - }, - { - items: [{ name: 'Han', id: '2' }], - path: ['friendList'], - }, - { - items: [{ name: 'Leia', id: '3' }], + items: [ + { name: 'Luke', id: '1' }, + { name: 'Han', id: '2' }, + { name: 'Leia', id: '3' }, + ], path: ['friendList'], }, ], @@ -481,7 +597,7 @@ describe('Execute: stream directive', () => { it('Handles rejections in a field that returns a list of promises after initialCount is reached', async () => { const document = parse(` query { - friendList @stream(initialCount: 1) { + friendList @stream { name id } @@ -499,7 +615,7 @@ describe('Execute: stream directive', () => { expectJSON(result).toDeepEqual([ { data: { - friendList: [{ name: 'Luke', id: '1' }], + friendList: [], }, pending: [{ path: ['friendList'] }], hasNext: true, @@ -507,7 +623,7 @@ describe('Execute: stream directive', () => { { incremental: [ { - items: [null], + items: [{ name: 'Luke', id: '1' }, null, { name: 'Leia', id: '3' }], path: ['friendList'], errors: [ { @@ -517,9 +633,55 @@ describe('Execute: stream directive', () => { }, ], }, + ], + completed: [{ path: ['friendList'] }], + hasNext: false, + }, + ]); + }); + it('Handles multiple rejections in a field that returns a list of promises after initialCount is reached', async () => { + const document = parse(` + query { + friendList @stream(initialCount: 1) { + name + id + } + } + `); + const result = await complete(document, { + friendList: () => + friends.map((f, i) => { + if (i >= 1) { + return Promise.reject(new Error('bad')); + } + return Promise.resolve(f); + }), + }); + expectJSON(result).toDeepEqual([ + { + data: { + friendList: [{ name: 'Luke', id: '1' }], + }, + pending: [{ path: ['friendList'] }], + hasNext: true, + }, + { + incremental: [ { - items: [{ name: 'Leia', id: '3' }], + items: [null, null], path: ['friendList'], + errors: [ + { + message: 'bad', + locations: [{ line: 3, column: 9 }], + path: ['friendList', 1], + }, + { + message: 'bad', + locations: [{ line: 3, column: 9 }], + path: ['friendList', 2], + }, + ], }, ], completed: [{ path: ['friendList'] }], @@ -921,7 +1083,7 @@ describe('Execute: stream directive', () => { { incremental: [ { - items: [null], + items: [null, { nonNullName: 'Han' }], path: ['friendList'], errors: [ { @@ -931,10 +1093,6 @@ describe('Execute: stream directive', () => { }, ], }, - { - items: [{ nonNullName: 'Han' }], - path: ['friendList'], - }, ], completed: [{ path: ['friendList'] }], hasNext: false, @@ -967,7 +1125,7 @@ describe('Execute: stream directive', () => { { incremental: [ { - items: [null], + items: [null, { nonNullName: 'Han' }], path: ['friendList'], errors: [ { @@ -977,10 +1135,6 @@ describe('Execute: stream directive', () => { }, ], }, - { - items: [{ nonNullName: 'Han' }], - path: ['friendList'], - }, ], completed: [{ path: ['friendList'] }], hasNext: false, @@ -1097,7 +1251,7 @@ describe('Execute: stream directive', () => { { incremental: [ { - items: [null], + items: [null, { nonNullName: 'Han' }], path: ['friendList'], errors: [ { @@ -1107,10 +1261,6 @@ describe('Execute: stream directive', () => { }, ], }, - { - items: [{ nonNullName: 'Han' }], - path: ['friendList'], - }, ], hasNext: true, }, @@ -1737,11 +1887,10 @@ describe('Execute: stream directive', () => { { incremental: [ { - items: [{ id: '1', name: 'Luke' }], - path: ['nestedObject', 'nestedFriendList'], - }, - { - items: [{ id: '2', name: 'Han' }], + items: [ + { id: '1', name: 'Luke' }, + { id: '2', name: 'Han' }, + ], path: ['nestedObject', 'nestedFriendList'], }, ], @@ -1803,13 +1952,9 @@ describe('Execute: stream directive', () => { pending: [{ path: ['nestedObject', 'nestedFriendList'] }], incremental: [ { - data: { scalarField: 'slow', nestedFriendList: [] }, + data: { scalarField: 'slow', nestedFriendList: [{ name: 'Luke' }] }, path: ['nestedObject'], }, - { - items: [{ name: 'Luke' }], - path: ['nestedObject', 'nestedFriendList'], - }, ], completed: [{ path: ['nestedObject'] }], hasNext: true, diff --git a/src/execution/execute.ts b/src/execution/execute.ts index 1683aaffae..9395727a2a 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -1475,6 +1475,7 @@ function addNewDeferredFragments( incrementalPublisher.reportNewDeferFragmentRecord( deferredFragmentRecord, + incrementalDataRecord, parent, );