diff --git a/src/execution/__tests__/defer-test.ts b/src/execution/__tests__/defer-test.ts index 9f55fbc7326..9320945eeb0 100644 --- a/src/execution/__tests__/defer-test.ts +++ b/src/execution/__tests__/defer-test.ts @@ -205,6 +205,7 @@ describe('Execute: defer directive', () => { incremental: [ { data: { + id: '1', name: 'Luke', }, path: ['hero'], @@ -409,7 +410,9 @@ describe('Execute: defer directive', () => { { incremental: [ { - data: {}, + data: { + name: 'Luke', + }, path: ['hero'], }, ], @@ -444,7 +447,9 @@ describe('Execute: defer directive', () => { { incremental: [ { - data: {}, + data: { + name: 'Luke', + }, path: ['hero'], }, ], @@ -522,7 +527,7 @@ describe('Execute: defer directive', () => { ]); }); - it('Can deduplicate leaf fields present in the initial payload', async () => { + it('Does not deduplicate leaf fields present in the initial payload', async () => { const document = parse(` query { hero { @@ -580,7 +585,9 @@ describe('Execute: defer directive', () => { }, }, anotherNestedObject: { - deeperObject: {}, + deeperObject: { + foo: 'foo', + }, }, }, path: ['hero'], @@ -591,7 +598,7 @@ describe('Execute: defer directive', () => { ]); }); - it('Can deduplicate fields with deferred fragments at multiple levels', async () => { + it('Does not deduplicate fields with deferred fragments at multiple levels', async () => { const document = parse(` query { hero { @@ -642,6 +649,7 @@ describe('Execute: defer directive', () => { incremental: [ { data: { + foo: 'foo', bar: 'bar', baz: 'baz', bak: 'bak', @@ -651,6 +659,7 @@ describe('Execute: defer directive', () => { { data: { deeperObject: { + foo: 'foo', bar: 'bar', baz: 'baz', }, @@ -661,6 +670,7 @@ describe('Execute: defer directive', () => { data: { nestedObject: { deeperObject: { + foo: 'foo', bar: 'bar', }, }, @@ -732,7 +742,7 @@ describe('Execute: defer directive', () => { ]); }); - it('can deduplicate fields with deferred fragments in different branches at multiple non-overlapping levels', async () => { + it('Can deduplicate fields with deferred fragments in different branches at multiple non-overlapping levels', async () => { const document = parse(` query { a { diff --git a/src/execution/__tests__/executor-test.ts b/src/execution/__tests__/executor-test.ts index 4199600fc11..0ec1de3ff08 100644 --- a/src/execution/__tests__/executor-test.ts +++ b/src/execution/__tests__/executor-test.ts @@ -215,7 +215,8 @@ describe('Execute: Handles basic execution tasks', () => { expect(resolvedInfo).to.have.all.keys( 'fieldName', - 'fieldNodes', + 'fieldGroup', + 'deferDepth', 'returnType', 'parentType', 'path', @@ -238,9 +239,10 @@ describe('Execute: Handles basic execution tasks', () => { operation, }); - const field = operation.selectionSet.selections[0]; + const fieldNode = operation.selectionSet.selections[0]; expect(resolvedInfo).to.deep.include({ - fieldNodes: [field], + fieldGroup: [{ fieldNode, depth: 0, deferDepth: undefined }], + deferDepth: undefined, variableValues: { var: 'abc' }, }); diff --git a/src/execution/execute.ts b/src/execution/execute.ts index 5025a622c19..38a73d1328c 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -38,7 +38,6 @@ import type { GraphQLTypeResolver, } from '../type/definition.js'; import { - getNamedType, isAbstractType, isLeafType, isListType, @@ -631,26 +630,19 @@ function executeFieldsSerially( (results, [responseName, fieldGroup]) => { const fieldPath = exeContext.addPath(path, responseName, parentType.name); - const fieldName = fieldGroup[0].fieldNode.name.value; - const fieldDef = exeContext.schema.getField(parentType, fieldName); - if (!fieldDef) { - return results; - } - - const returnType = fieldDef.type; - - if (!shouldExecute(fieldGroup, returnType)) { + if (!shouldExecute(fieldGroup)) { return results; } const result = executeField( exeContext, parentType, - fieldDef, - returnType, sourceValue, fieldGroup, fieldPath, ); + if (result === undefined) { + return results; + } if (isPromise(result)) { return result.then((resolvedResult) => { results[responseName] = resolvedResult; @@ -666,25 +658,11 @@ function executeFieldsSerially( function shouldExecute( fieldGroup: FieldGroup, - returnType: GraphQLOutputType, deferDepth?: number | undefined, ): boolean { - if (deferDepth === undefined || !isLeafType(getNamedType(returnType))) { - return fieldGroup.some( - ({ deferDepth: fieldDeferDepth }) => fieldDeferDepth === deferDepth, - ); - } - - let hasDepth = false; - for (const { deferDepth: fieldDeferDepth } of fieldGroup) { - if (fieldDeferDepth === undefined) { - return false; - } - if (fieldDeferDepth === deferDepth) { - hasDepth = true; - } - } - return hasDepth; + return fieldGroup.some( + ({ deferDepth: fieldDeferDepth }) => fieldDeferDepth === deferDepth, + ); } /** @@ -706,22 +684,10 @@ function executeFields( for (const [responseName, fieldGroup] of groupedFieldSet) { const fieldPath = exeContext.addPath(path, responseName, parentType.name); - const fieldName = fieldGroup[0].fieldNode.name.value; - const fieldDef = exeContext.schema.getField(parentType, fieldName); - if (!fieldDef) { - continue; - } - - const returnType = fieldDef.type; - - if ( - shouldExecute(fieldGroup, returnType, asyncPayloadRecord?.deferDepth) - ) { + if (shouldExecute(fieldGroup, asyncPayloadRecord?.deferDepth)) { const result = executeField( exeContext, parentType, - fieldDef, - returnType, sourceValue, fieldGroup, fieldPath, @@ -769,15 +735,19 @@ function toNodes(fieldGroup: FieldGroup): ReadonlyArray { function executeField( exeContext: ExecutionContext, parentType: GraphQLObjectType, - fieldDef: GraphQLField, - returnType: GraphQLOutputType, source: unknown, fieldGroup: FieldGroup, path: Path, asyncPayloadRecord?: AsyncPayloadRecord, ): PromiseOrValue { const errors = asyncPayloadRecord?.errors ?? exeContext.errors; + const fieldName = fieldGroup[0].fieldNode.name.value; + const fieldDef = exeContext.schema.getField(parentType, fieldName); + if (!fieldDef) { + return; + } + const returnType = fieldDef.type; const resolveFn = fieldDef.resolve ?? exeContext.fieldResolver; const info = buildResolveInfo( @@ -786,6 +756,7 @@ function executeField( fieldGroup, parentType, path, + asyncPayloadRecord, ); // Get the resolve function, regardless of if its result is normal or abrupt (error). @@ -865,12 +836,14 @@ export function buildResolveInfo( fieldGroup: FieldGroup, parentType: GraphQLObjectType, path: Path, + asyncPayloadRecord?: AsyncPayloadRecord | undefined, ): GraphQLResolveInfo { // The resolve function's optional fourth argument is a collection of // information about the current execution state. return { fieldName: fieldDef.name, - fieldNodes: toNodes(fieldGroup), + fieldGroup, + deferDepth: asyncPayloadRecord?.deferDepth, returnType: fieldDef.type, parentType, path, @@ -1222,7 +1195,6 @@ function completeListValue( // This is specified as a simple map, however we're optimizing the path // where the list contains no Promises by avoiding creating another Promise. let containsPromise = false; - const deferDepth = asyncPayloadRecord?.deferDepth; let previousAsyncPayloadRecord = asyncPayloadRecord; const completedResults: Array = []; let index = 0; @@ -1244,7 +1216,6 @@ function completeListValue( fieldGroup, info, itemType, - deferDepth, previousAsyncPayloadRecord, ); index++; @@ -1923,11 +1894,10 @@ function executeStreamField( fieldGroup: FieldGroup, info: GraphQLResolveInfo, itemType: GraphQLOutputType, - deferDepth: number | undefined, parentContext?: AsyncPayloadRecord, ): AsyncPayloadRecord { const asyncPayloadRecord = new StreamRecord({ - deferDepth, + deferDepth: parentContext?.deferDepth, path: itemPath, parentContext, exeContext, diff --git a/src/type/definition.ts b/src/type/definition.ts index 81488efb392..83208b18d33 100644 --- a/src/type/definition.ts +++ b/src/type/definition.ts @@ -890,7 +890,12 @@ export type GraphQLFieldResolver< export interface GraphQLResolveInfo { readonly fieldName: string; - readonly fieldNodes: ReadonlyArray; + readonly fieldGroup: ReadonlyArray<{ + fieldNode: FieldNode; + depth: number; + deferDepth: number | undefined; + }>; + readonly deferDepth: number | undefined; readonly returnType: GraphQLOutputType; readonly parentType: GraphQLObjectType; readonly path: Path;