diff --git a/src/execution/collectFields.ts b/src/execution/collectFields.ts index 91ac792505..af263112ec 100644 --- a/src/execution/collectFields.ts +++ b/src/execution/collectFields.ts @@ -26,13 +26,17 @@ import { typeFromAST } from '../utilities/typeFromAST.js'; import { getDirectiveValues } from './values.js'; +export type FieldGroup = ReadonlyArray; + +export type GroupedFieldSet = Map; + export interface PatchFields { label: string | undefined; - fields: Map>; + groupedFieldSet: GroupedFieldSet; } export interface FieldsAndPatches { - fields: Map>; + groupedFieldSet: GroupedFieldSet; patches: Array; } @@ -52,7 +56,7 @@ export function collectFields( runtimeType: GraphQLObjectType, operation: OperationDefinitionNode, ): FieldsAndPatches { - const fields = new AccumulatorMap(); + const groupedFieldSet = new AccumulatorMap(); const patches: Array = []; collectFieldsImpl( schema, @@ -61,11 +65,11 @@ export function collectFields( operation, runtimeType, operation.selectionSet, - fields, + groupedFieldSet, patches, new Set(), ); - return { fields, patches }; + return { groupedFieldSet, patches }; } /** @@ -85,18 +89,18 @@ export function collectSubfields( variableValues: { [variable: string]: unknown }, operation: OperationDefinitionNode, returnType: GraphQLObjectType, - fieldNodes: ReadonlyArray, + fieldGroup: FieldGroup, ): FieldsAndPatches { - const subFieldNodes = new AccumulatorMap(); + const subGroupedFieldSet = new AccumulatorMap(); const visitedFragmentNames = new Set(); const subPatches: Array = []; const subFieldsAndPatches = { - fields: subFieldNodes, + groupedFieldSet: subGroupedFieldSet, patches: subPatches, }; - for (const node of fieldNodes) { + for (const node of fieldGroup) { if (node.selectionSet) { collectFieldsImpl( schema, @@ -105,7 +109,7 @@ export function collectSubfields( operation, returnType, node.selectionSet, - subFieldNodes, + subGroupedFieldSet, subPatches, visitedFragmentNames, ); @@ -122,7 +126,7 @@ function collectFieldsImpl( operation: OperationDefinitionNode, runtimeType: GraphQLObjectType, selectionSet: SelectionSetNode, - fields: AccumulatorMap, + groupedFieldSet: AccumulatorMap, patches: Array, visitedFragmentNames: Set, ): void { @@ -132,7 +136,7 @@ function collectFieldsImpl( if (!shouldIncludeNode(variableValues, selection)) { continue; } - fields.add(getFieldEntryKey(selection), selection); + groupedFieldSet.add(getFieldEntryKey(selection), selection); break; } case Kind.INLINE_FRAGMENT: { @@ -160,7 +164,7 @@ function collectFieldsImpl( ); patches.push({ label: defer.label, - fields: patchFields, + groupedFieldSet: patchFields, }); } else { collectFieldsImpl( @@ -170,7 +174,7 @@ function collectFieldsImpl( operation, runtimeType, selection.selectionSet, - fields, + groupedFieldSet, patches, visitedFragmentNames, ); @@ -216,7 +220,7 @@ function collectFieldsImpl( ); patches.push({ label: defer.label, - fields: patchFields, + groupedFieldSet: patchFields, }); } else { collectFieldsImpl( @@ -226,7 +230,7 @@ function collectFieldsImpl( operation, runtimeType, fragment.selectionSet, - fields, + groupedFieldSet, patches, visitedFragmentNames, ); diff --git a/src/execution/execute.ts b/src/execution/execute.ts index a722563289..df048480ba 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -19,7 +19,6 @@ import { locatedError } from '../error/locatedError.js'; import type { DocumentNode, - FieldNode, FragmentDefinitionNode, OperationDefinitionNode, } from '../language/ast.js'; @@ -48,6 +47,7 @@ import { GraphQLStreamDirective } from '../type/directives.js'; import type { GraphQLSchema } from '../type/schema.js'; import { assertValidSchema } from '../type/validate.js'; +import type { FieldGroup, GroupedFieldSet } from './collectFields.js'; import { collectFields, collectSubfields as _collectSubfields, @@ -72,7 +72,7 @@ const collectSubfields = memoize3( ( exeContext: ExecutionContext, returnType: GraphQLObjectType, - fieldNodes: ReadonlyArray, + fieldGroup: FieldGroup, ) => _collectSubfields( exeContext.schema, @@ -80,7 +80,7 @@ const collectSubfields = memoize3( exeContext.variableValues, exeContext.operation, returnType, - fieldNodes, + fieldGroup, ), ); @@ -536,7 +536,7 @@ function executeOperation( ); } - const { fields: rootFields, patches } = collectFields( + const { groupedFieldSet, patches } = collectFields( schema, fragments, variableValues, @@ -548,7 +548,13 @@ function executeOperation( switch (operation.operation) { case OperationTypeNode.QUERY: - result = executeFields(exeContext, rootType, rootValue, path, rootFields); + result = executeFields( + exeContext, + rootType, + rootValue, + path, + groupedFieldSet, + ); break; case OperationTypeNode.MUTATION: result = executeFieldsSerially( @@ -556,22 +562,28 @@ function executeOperation( rootType, rootValue, path, - rootFields, + groupedFieldSet, ); break; case OperationTypeNode.SUBSCRIPTION: // TODO: deprecate `subscribe` and move all logic here // Temporary solution until we finish merging execute and subscribe together - result = executeFields(exeContext, rootType, rootValue, path, rootFields); + result = executeFields( + exeContext, + rootType, + rootValue, + path, + groupedFieldSet, + ); } for (const patch of patches) { - const { label, fields: patchFields } = patch; + const { label, groupedFieldSet: patchGroupedFieldSet } = patch; executeDeferredFragment( exeContext, rootType, rootValue, - patchFields, + patchGroupedFieldSet, label, path, ); @@ -589,17 +601,17 @@ function executeFieldsSerially( parentType: GraphQLObjectType, sourceValue: unknown, path: Path | undefined, - fields: Map>, + fields: GroupedFieldSet, ): PromiseOrValue> { return promiseReduce( fields, - (results, [responseName, fieldNodes]) => { + (results, [responseName, fieldGroup]) => { const fieldPath = addPath(path, responseName, parentType.name); const result = executeField( exeContext, parentType, sourceValue, - fieldNodes, + fieldGroup, fieldPath, ); if (result === undefined) { @@ -627,20 +639,20 @@ function executeFields( parentType: GraphQLObjectType, sourceValue: unknown, path: Path | undefined, - fields: Map>, + fields: GroupedFieldSet, asyncPayloadRecord?: AsyncPayloadRecord, ): PromiseOrValue> { const results = Object.create(null); let containsPromise = false; try { - for (const [responseName, fieldNodes] of fields) { + for (const [responseName, fieldGroup] of fields) { const fieldPath = addPath(path, responseName, parentType.name); const result = executeField( exeContext, parentType, sourceValue, - fieldNodes, + fieldGroup, fieldPath, asyncPayloadRecord, ); @@ -683,12 +695,11 @@ function executeField( exeContext: ExecutionContext, parentType: GraphQLObjectType, source: unknown, - fieldNodes: ReadonlyArray, + fieldGroup: FieldGroup, path: Path, asyncPayloadRecord?: AsyncPayloadRecord, ): PromiseOrValue { - const errors = asyncPayloadRecord?.errors ?? exeContext.errors; - const fieldName = fieldNodes[0].name.value; + const fieldName = fieldGroup[0].name.value; const fieldDef = exeContext.schema.getField(parentType, fieldName); if (!fieldDef) { return; @@ -700,7 +711,7 @@ function executeField( const info = buildResolveInfo( exeContext, fieldDef, - fieldNodes, + fieldGroup, parentType, path, ); @@ -712,7 +723,7 @@ function executeField( // TODO: find a way to memoize, in case this field is within a List type. const args = getArgumentValues( fieldDef, - fieldNodes[0], + fieldGroup[0], exeContext.variableValues, ); @@ -727,7 +738,7 @@ function executeField( return completePromisedValue( exeContext, returnType, - fieldNodes, + fieldGroup, info, path, result, @@ -738,7 +749,7 @@ function executeField( const completed = completeValue( exeContext, returnType, - fieldNodes, + fieldGroup, info, path, result, @@ -749,18 +760,30 @@ function executeField( // Note: we don't rely on a `catch` method, but we do expect "thenable" // to take a second callback for the error case. return completed.then(undefined, (rawError) => { - const error = locatedError(rawError, fieldNodes, pathToArray(path)); - const handledError = handleFieldError(error, returnType, errors); + handleFieldError( + rawError, + exeContext, + returnType, + fieldGroup, + path, + asyncPayloadRecord, + ); filterSubsequentPayloads(exeContext, path, asyncPayloadRecord); - return handledError; + return null; }); } return completed; } catch (rawError) { - const error = locatedError(rawError, fieldNodes, pathToArray(path)); - const handledError = handleFieldError(error, returnType, errors); + handleFieldError( + rawError, + exeContext, + returnType, + fieldGroup, + path, + asyncPayloadRecord, + ); filterSubsequentPayloads(exeContext, path, asyncPayloadRecord); - return handledError; + return null; } } @@ -771,7 +794,7 @@ function executeField( export function buildResolveInfo( exeContext: ExecutionContext, fieldDef: GraphQLField, - fieldNodes: ReadonlyArray, + fieldGroup: FieldGroup, parentType: GraphQLObjectType, path: Path, ): GraphQLResolveInfo { @@ -779,7 +802,7 @@ export function buildResolveInfo( // information about the current execution state. return { fieldName: fieldDef.name, - fieldNodes, + fieldNodes: fieldGroup, returnType: fieldDef.type, parentType, path, @@ -792,20 +815,26 @@ export function buildResolveInfo( } function handleFieldError( - error: GraphQLError, + rawError: unknown, + exeContext: ExecutionContext, returnType: GraphQLOutputType, - errors: Array, -): null { + fieldGroup: FieldGroup, + path: Path, + asyncPayloadRecord?: AsyncPayloadRecord | undefined, +): void { + const error = locatedError(rawError, fieldGroup, pathToArray(path)); + // If the field type is non-nullable, then it is resolved without any // protection from errors, however it still properly locates the error. if (isNonNullType(returnType)) { throw error; } + const errors = asyncPayloadRecord?.errors ?? exeContext.errors; + // Otherwise, error protection is applied, logging the error and resolving // a null value for this field if one is encountered. errors.push(error); - return null; } /** @@ -832,7 +861,7 @@ function handleFieldError( function completeValue( exeContext: ExecutionContext, returnType: GraphQLOutputType, - fieldNodes: ReadonlyArray, + fieldGroup: FieldGroup, info: GraphQLResolveInfo, path: Path, result: unknown, @@ -849,7 +878,7 @@ function completeValue( const completed = completeValue( exeContext, returnType.ofType, - fieldNodes, + fieldGroup, info, path, result, @@ -873,7 +902,7 @@ function completeValue( return completeListValue( exeContext, returnType, - fieldNodes, + fieldGroup, info, path, result, @@ -893,7 +922,7 @@ function completeValue( return completeAbstractValue( exeContext, returnType, - fieldNodes, + fieldGroup, info, path, result, @@ -906,7 +935,7 @@ function completeValue( return completeObjectValue( exeContext, returnType, - fieldNodes, + fieldGroup, info, path, result, @@ -924,7 +953,7 @@ function completeValue( async function completePromisedValue( exeContext: ExecutionContext, returnType: GraphQLOutputType, - fieldNodes: ReadonlyArray, + fieldGroup: FieldGroup, info: GraphQLResolveInfo, path: Path, result: Promise, @@ -935,7 +964,7 @@ async function completePromisedValue( let completed = completeValue( exeContext, returnType, - fieldNodes, + fieldGroup, info, path, resolved, @@ -946,11 +975,16 @@ async function completePromisedValue( } return completed; } catch (rawError) { - const errors = asyncPayloadRecord?.errors ?? exeContext.errors; - const error = locatedError(rawError, fieldNodes, pathToArray(path)); - const handledError = handleFieldError(error, returnType, errors); + handleFieldError( + rawError, + exeContext, + returnType, + fieldGroup, + path, + asyncPayloadRecord, + ); filterSubsequentPayloads(exeContext, path, asyncPayloadRecord); - return handledError; + return null; } } @@ -961,7 +995,7 @@ async function completePromisedValue( */ function getStreamValues( exeContext: ExecutionContext, - fieldNodes: ReadonlyArray, + fieldGroup: FieldGroup, path: Path, ): | undefined @@ -978,7 +1012,7 @@ function getStreamValues( // safe to only check the first fieldNode for the stream directive const stream = getDirectiveValues( GraphQLStreamDirective, - fieldNodes[0], + fieldGroup[0], exeContext.variableValues, ); @@ -1018,14 +1052,13 @@ function getStreamValues( async function completeAsyncIteratorValue( exeContext: ExecutionContext, itemType: GraphQLOutputType, - fieldNodes: ReadonlyArray, + fieldGroup: FieldGroup, info: GraphQLResolveInfo, path: Path, iterator: AsyncIterator, asyncPayloadRecord?: AsyncPayloadRecord, ): Promise> { - const errors = asyncPayloadRecord?.errors ?? exeContext.errors; - const stream = getStreamValues(exeContext, fieldNodes, path); + const stream = getStreamValues(exeContext, fieldGroup, path); let containsPromise = false; const completedResults: Array = []; let index = 0; @@ -1037,11 +1070,11 @@ async function completeAsyncIteratorValue( index >= stream.initialCount ) { // eslint-disable-next-line @typescript-eslint/no-floating-promises - executeStreamIterator( + executeStreamAsyncIterator( index, iterator, exeContext, - fieldNodes, + fieldGroup, info, itemType, path, @@ -1060,8 +1093,15 @@ async function completeAsyncIteratorValue( break; } } catch (rawError) { - const error = locatedError(rawError, fieldNodes, pathToArray(itemPath)); - completedResults.push(handleFieldError(error, itemType, errors)); + handleFieldError( + rawError, + exeContext, + itemType, + fieldGroup, + itemPath, + asyncPayloadRecord, + ); + completedResults.push(null); break; } @@ -1069,10 +1109,9 @@ async function completeAsyncIteratorValue( completeListItemValue( iteration.value, completedResults, - errors, exeContext, itemType, - fieldNodes, + fieldGroup, info, itemPath, asyncPayloadRecord, @@ -1092,14 +1131,13 @@ async function completeAsyncIteratorValue( function completeListValue( exeContext: ExecutionContext, returnType: GraphQLList, - fieldNodes: ReadonlyArray, + fieldGroup: FieldGroup, info: GraphQLResolveInfo, path: Path, result: unknown, asyncPayloadRecord?: AsyncPayloadRecord, ): PromiseOrValue> { const itemType = returnType.ofType; - const errors = asyncPayloadRecord?.errors ?? exeContext.errors; if (isAsyncIterable(result)) { const iterator = result[Symbol.asyncIterator](); @@ -1107,7 +1145,7 @@ function completeListValue( return completeAsyncIteratorValue( exeContext, itemType, - fieldNodes, + fieldGroup, info, path, iterator, @@ -1121,7 +1159,7 @@ function completeListValue( ); } - const stream = getStreamValues(exeContext, fieldNodes, path); + const stream = getStreamValues(exeContext, fieldGroup, path); // This is specified as a simple map, however we're optimizing the path // where the list contains no Promises by avoiding creating another Promise. @@ -1144,7 +1182,7 @@ function completeListValue( itemPath, item, exeContext, - fieldNodes, + fieldGroup, info, itemType, stream.label, @@ -1158,10 +1196,9 @@ function completeListValue( completeListItemValue( item, completedResults, - errors, exeContext, itemType, - fieldNodes, + fieldGroup, info, itemPath, asyncPayloadRecord, @@ -1184,10 +1221,9 @@ function completeListValue( function completeListItemValue( item: unknown, completedResults: Array, - errors: Array, exeContext: ExecutionContext, itemType: GraphQLOutputType, - fieldNodes: ReadonlyArray, + fieldGroup: FieldGroup, info: GraphQLResolveInfo, itemPath: Path, asyncPayloadRecord?: AsyncPayloadRecord, @@ -1197,7 +1233,7 @@ function completeListItemValue( completePromisedValue( exeContext, itemType, - fieldNodes, + fieldGroup, info, itemPath, item, @@ -1212,7 +1248,7 @@ function completeListItemValue( const completedItem = completeValue( exeContext, itemType, - fieldNodes, + fieldGroup, info, itemPath, item, @@ -1224,14 +1260,16 @@ function completeListItemValue( // to take a second callback for the error case. completedResults.push( completedItem.then(undefined, (rawError) => { - const error = locatedError( + handleFieldError( rawError, - fieldNodes, - pathToArray(itemPath), + exeContext, + itemType, + fieldGroup, + itemPath, + asyncPayloadRecord, ); - const handledError = handleFieldError(error, itemType, errors); filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord); - return handledError; + return null; }), ); @@ -1240,10 +1278,16 @@ function completeListItemValue( completedResults.push(completedItem); } catch (rawError) { - const error = locatedError(rawError, fieldNodes, pathToArray(itemPath)); - const handledError = handleFieldError(error, itemType, errors); + handleFieldError( + rawError, + exeContext, + itemType, + fieldGroup, + itemPath, + asyncPayloadRecord, + ); filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord); - completedResults.push(handledError); + completedResults.push(null); } return false; @@ -1274,7 +1318,7 @@ function completeLeafValue( function completeAbstractValue( exeContext: ExecutionContext, returnType: GraphQLAbstractType, - fieldNodes: ReadonlyArray, + fieldGroup: FieldGroup, info: GraphQLResolveInfo, path: Path, result: unknown, @@ -1292,11 +1336,11 @@ function completeAbstractValue( resolvedRuntimeType, exeContext, returnType, - fieldNodes, + fieldGroup, info, result, ), - fieldNodes, + fieldGroup, info, path, result, @@ -1311,11 +1355,11 @@ function completeAbstractValue( runtimeType, exeContext, returnType, - fieldNodes, + fieldGroup, info, result, ), - fieldNodes, + fieldGroup, info, path, result, @@ -1327,14 +1371,14 @@ function ensureValidRuntimeType( runtimeTypeName: unknown, exeContext: ExecutionContext, returnType: GraphQLAbstractType, - fieldNodes: ReadonlyArray, + fieldGroup: FieldGroup, info: GraphQLResolveInfo, result: unknown, ): GraphQLObjectType { if (runtimeTypeName == null) { throw new GraphQLError( `Abstract type "${returnType.name}" must resolve to an Object type at runtime for field "${info.parentType.name}.${info.fieldName}". Either the "${returnType.name}" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.`, - { nodes: fieldNodes }, + { nodes: fieldGroup }, ); } @@ -1357,21 +1401,21 @@ function ensureValidRuntimeType( if (runtimeType == null) { throw new GraphQLError( `Abstract type "${returnType.name}" was resolved to a type "${runtimeTypeName}" that does not exist inside the schema.`, - { nodes: fieldNodes }, + { nodes: fieldGroup }, ); } if (!isObjectType(runtimeType)) { throw new GraphQLError( `Abstract type "${returnType.name}" was resolved to a non-object type "${runtimeTypeName}".`, - { nodes: fieldNodes }, + { nodes: fieldGroup }, ); } if (!exeContext.schema.isSubType(returnType, runtimeType)) { throw new GraphQLError( `Runtime Object type "${runtimeType.name}" is not a possible type for "${returnType.name}".`, - { nodes: fieldNodes }, + { nodes: fieldGroup }, ); } @@ -1384,7 +1428,7 @@ function ensureValidRuntimeType( function completeObjectValue( exeContext: ExecutionContext, returnType: GraphQLObjectType, - fieldNodes: ReadonlyArray, + fieldGroup: FieldGroup, info: GraphQLResolveInfo, path: Path, result: unknown, @@ -1399,12 +1443,12 @@ function completeObjectValue( if (isPromise(isTypeOf)) { return isTypeOf.then((resolvedIsTypeOf) => { if (!resolvedIsTypeOf) { - throw invalidReturnTypeError(returnType, result, fieldNodes); + throw invalidReturnTypeError(returnType, result, fieldGroup); } return collectAndExecuteSubfields( exeContext, returnType, - fieldNodes, + fieldGroup, path, result, asyncPayloadRecord, @@ -1413,14 +1457,14 @@ function completeObjectValue( } if (!isTypeOf) { - throw invalidReturnTypeError(returnType, result, fieldNodes); + throw invalidReturnTypeError(returnType, result, fieldGroup); } } return collectAndExecuteSubfields( exeContext, returnType, - fieldNodes, + fieldGroup, path, result, asyncPayloadRecord, @@ -1430,45 +1474,42 @@ function completeObjectValue( function invalidReturnTypeError( returnType: GraphQLObjectType, result: unknown, - fieldNodes: ReadonlyArray, + fieldGroup: FieldGroup, ): GraphQLError { return new GraphQLError( `Expected value of type "${returnType.name}" but got: ${inspect(result)}.`, - { nodes: fieldNodes }, + { nodes: fieldGroup }, ); } function collectAndExecuteSubfields( exeContext: ExecutionContext, returnType: GraphQLObjectType, - fieldNodes: ReadonlyArray, + fieldGroup: FieldGroup, path: Path, result: unknown, asyncPayloadRecord?: AsyncPayloadRecord, ): PromiseOrValue> { // Collect sub-fields to execute to complete this value. - const { fields: subFieldNodes, patches: subPatches } = collectSubfields( - exeContext, - returnType, - fieldNodes, - ); + const { groupedFieldSet: subGroupedFieldSet, patches: subPatches } = + collectSubfields(exeContext, returnType, fieldGroup); const subFields = executeFields( exeContext, returnType, result, path, - subFieldNodes, + subGroupedFieldSet, asyncPayloadRecord, ); for (const subPatch of subPatches) { - const { label, fields: subPatchFieldNodes } = subPatch; + const { label, groupedFieldSet: subPatchGroupedFieldSet } = subPatch; executeDeferredFragment( exeContext, returnType, result, - subPatchFieldNodes, + subPatchGroupedFieldSet, label, path, asyncPayloadRecord, @@ -1691,7 +1732,7 @@ function executeSubscription( ); } - const { fields: rootFields } = collectFields( + const { groupedFieldSet } = collectFields( schema, fragments, variableValues, @@ -1699,15 +1740,15 @@ function executeSubscription( operation, ); - const firstRootField = rootFields.entries().next().value; - const [responseName, fieldNodes] = firstRootField; - const fieldName = fieldNodes[0].name.value; + const firstRootField = groupedFieldSet.entries().next().value; + const [responseName, fieldGroup] = firstRootField; + const fieldName = fieldGroup[0].name.value; const fieldDef = schema.getField(rootType, fieldName); if (!fieldDef) { throw new GraphQLError( `The subscription field "${fieldName}" is not defined.`, - { nodes: fieldNodes }, + { nodes: fieldGroup }, ); } @@ -1715,7 +1756,7 @@ function executeSubscription( const info = buildResolveInfo( exeContext, fieldDef, - fieldNodes, + fieldGroup, rootType, path, ); @@ -1726,7 +1767,7 @@ function executeSubscription( // Build a JS object of arguments from the field.arguments AST, using the // variables scope to fulfill any variable references. - const args = getArgumentValues(fieldDef, fieldNodes[0], variableValues); + const args = getArgumentValues(fieldDef, fieldGroup[0], variableValues); // The resolve function's optional third argument is a context value that // is provided to every resolve function within an execution. It is commonly @@ -1740,13 +1781,13 @@ function executeSubscription( if (isPromise(result)) { return result.then(assertEventStream).then(undefined, (error) => { - throw locatedError(error, fieldNodes, pathToArray(path)); + throw locatedError(error, fieldGroup, pathToArray(path)); }); } return assertEventStream(result); } catch (error) { - throw locatedError(error, fieldNodes, pathToArray(path)); + throw locatedError(error, fieldGroup, pathToArray(path)); } } @@ -1770,7 +1811,7 @@ function executeDeferredFragment( exeContext: ExecutionContext, parentType: GraphQLObjectType, sourceValue: unknown, - fields: Map>, + fields: GroupedFieldSet, label?: string, path?: Path, parentContext?: AsyncPayloadRecord, @@ -1810,7 +1851,7 @@ function executeStreamField( itemPath: Path, item: PromiseOrValue, exeContext: ExecutionContext, - fieldNodes: ReadonlyArray, + fieldGroup: FieldGroup, info: GraphQLResolveInfo, itemType: GraphQLOutputType, label?: string, @@ -1826,7 +1867,7 @@ function executeStreamField( const completedItems = completePromisedValue( exeContext, itemType, - fieldNodes, + fieldGroup, info, itemPath, item, @@ -1850,19 +1891,22 @@ function executeStreamField( completedItem = completeValue( exeContext, itemType, - fieldNodes, + fieldGroup, info, itemPath, item, asyncPayloadRecord, ); } catch (rawError) { - const error = locatedError(rawError, fieldNodes, pathToArray(itemPath)); - completedItem = handleFieldError( - error, + handleFieldError( + rawError, + exeContext, itemType, - asyncPayloadRecord.errors, + fieldGroup, + itemPath, + asyncPayloadRecord, ); + completedItem = null; filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord); } } catch (error) { @@ -1875,14 +1919,16 @@ function executeStreamField( if (isPromise(completedItem)) { const completedItems = completedItem .then(undefined, (rawError) => { - const error = locatedError(rawError, fieldNodes, pathToArray(itemPath)); - const handledError = handleFieldError( - error, + handleFieldError( + rawError, + exeContext, itemType, - asyncPayloadRecord.errors, + fieldGroup, + itemPath, + asyncPayloadRecord, ); filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord); - return handledError; + return null; }) .then( (value) => [value], @@ -1901,10 +1947,10 @@ function executeStreamField( return asyncPayloadRecord; } -async function executeStreamIteratorItem( +async function executeStreamAsyncIteratorItem( iterator: AsyncIterator, exeContext: ExecutionContext, - fieldNodes: ReadonlyArray, + fieldGroup: FieldGroup, info: GraphQLResolveInfo, itemType: GraphQLOutputType, asyncPayloadRecord: StreamRecord, @@ -1919,17 +1965,23 @@ async function executeStreamIteratorItem( } item = value; } catch (rawError) { - const error = locatedError(rawError, fieldNodes, pathToArray(itemPath)); - const value = handleFieldError(error, itemType, asyncPayloadRecord.errors); + handleFieldError( + rawError, + exeContext, + itemType, + fieldGroup, + itemPath, + asyncPayloadRecord, + ); // don't continue if iterator throws - return { done: true, value }; + return { done: true, value: null }; } let completedItem; try { completedItem = completeValue( exeContext, itemType, - fieldNodes, + fieldGroup, info, itemPath, item, @@ -1938,30 +1990,38 @@ async function executeStreamIteratorItem( if (isPromise(completedItem)) { completedItem = completedItem.then(undefined, (rawError) => { - const error = locatedError(rawError, fieldNodes, pathToArray(itemPath)); - const handledError = handleFieldError( - error, + handleFieldError( + rawError, + exeContext, itemType, - asyncPayloadRecord.errors, + fieldGroup, + itemPath, + asyncPayloadRecord, ); filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord); - return handledError; + return null; }); } return { done: false, value: completedItem }; } catch (rawError) { - const error = locatedError(rawError, fieldNodes, pathToArray(itemPath)); - const value = handleFieldError(error, itemType, asyncPayloadRecord.errors); + handleFieldError( + rawError, + exeContext, + itemType, + fieldGroup, + itemPath, + asyncPayloadRecord, + ); filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord); - return { done: false, value }; + return { done: false, value: null }; } } -async function executeStreamIterator( +async function executeStreamAsyncIterator( initialIndex: number, iterator: AsyncIterator, exeContext: ExecutionContext, - fieldNodes: ReadonlyArray, + fieldGroup: FieldGroup, info: GraphQLResolveInfo, itemType: GraphQLOutputType, path: Path, @@ -1984,10 +2044,10 @@ async function executeStreamIterator( let iteration; try { // eslint-disable-next-line no-await-in-loop - iteration = await executeStreamIteratorItem( + iteration = await executeStreamAsyncIteratorItem( iterator, exeContext, - fieldNodes, + fieldGroup, info, itemType, asyncPayloadRecord, diff --git a/src/validation/rules/SingleFieldSubscriptionsRule.ts b/src/validation/rules/SingleFieldSubscriptionsRule.ts index 4a3d834124..c6cd93ab58 100644 --- a/src/validation/rules/SingleFieldSubscriptionsRule.ts +++ b/src/validation/rules/SingleFieldSubscriptionsRule.ts @@ -41,15 +41,15 @@ export function SingleFieldSubscriptionsRule( fragments[definition.name.value] = definition; } } - const { fields } = collectFields( + const { groupedFieldSet } = collectFields( schema, fragments, variableValues, subscriptionType, node, ); - if (fields.size > 1) { - const fieldSelectionLists = [...fields.values()]; + if (groupedFieldSet.size > 1) { + const fieldSelectionLists = [...groupedFieldSet.values()]; const extraFieldSelectionLists = fieldSelectionLists.slice(1); const extraFieldSelections = extraFieldSelectionLists.flat(); context.reportError( @@ -61,15 +61,15 @@ export function SingleFieldSubscriptionsRule( ), ); } - for (const fieldNodes of fields.values()) { - const fieldName = fieldNodes[0].name.value; + for (const fieldGroup of groupedFieldSet.values()) { + const fieldName = fieldGroup[0].name.value; if (fieldName.startsWith('__')) { context.reportError( new GraphQLError( operationName != null ? `Subscription "${operationName}" must not select an introspection top level field.` : 'Anonymous Subscription must not select an introspection top level field.', - { nodes: fieldNodes }, + { nodes: fieldGroup }, ), ); }