From e95ea9b77986fb4299ac12f423d2b501dcf52050 Mon Sep 17 00:00:00 2001 From: Ivan Goncharov Date: Fri, 24 Sep 2021 14:27:09 +0300 Subject: [PATCH] collectFields/collectSubfields refactor and export as part of public API (#3272) Motivated by #3246 --- src/execution/collectFields.ts | 65 +++++++++++++++++-- src/execution/execute.ts | 56 +++++++--------- src/subscription/subscribe.ts | 2 - .../rules/SingleFieldSubscriptionsRule.ts | 2 - 4 files changed, 83 insertions(+), 42 deletions(-) diff --git a/src/execution/collectFields.ts b/src/execution/collectFields.ts index 4d41b6803c..396745d554 100644 --- a/src/execution/collectFields.ts +++ b/src/execution/collectFields.ts @@ -22,8 +22,7 @@ import { typeFromAST } from '../utilities/typeFromAST'; import { getDirectiveValues } from './values'; /** - * Given a selectionSet, adds all of the fields in that selection to - * the passed in map of fields, and returns it at the end. + * Given a selectionSet, collect all of the fields and returns it at the end. * * CollectFields requires the "runtime type" of an object. For a field which * returns an Interface or Union type, the "runtime type" will be the actual @@ -37,9 +36,64 @@ export function collectFields( variableValues: { [variable: string]: unknown }, runtimeType: GraphQLObjectType, selectionSet: SelectionSetNode, +): Map> { + const fields = new Map(); + collectFieldsImpl( + schema, + fragments, + variableValues, + runtimeType, + selectionSet, + fields, + new Set(), + ); + return fields; +} + +/** + * Given an array of field nodes, collects all of the subfields of the passed + * in fields, and returns it at the end. + * + * CollectFields requires the "return type" of an object. For a field which + * returns an Interface or Union type, the "return type" will be the actual + * Object type returned by that field. + * + * @internal + */ +export function collectSubfields( + schema: GraphQLSchema, + fragments: ObjMap, + variableValues: { [variable: string]: unknown }, + returnType: GraphQLObjectType, + fieldNodes: ReadonlyArray, +): Map> { + const subFieldNodes = new Map(); + const visitedFragmentNames = new Set(); + for (const node of fieldNodes) { + if (node.selectionSet) { + collectFieldsImpl( + schema, + fragments, + variableValues, + returnType, + node.selectionSet, + subFieldNodes, + visitedFragmentNames, + ); + } + } + return subFieldNodes; +} + +function collectFieldsImpl( + schema: GraphQLSchema, + fragments: ObjMap, + variableValues: { [variable: string]: unknown }, + runtimeType: GraphQLObjectType, + selectionSet: SelectionSetNode, fields: Map>, visitedFragmentNames: Set, -): Map> { +): void { for (const selection of selectionSet.selections) { switch (selection.kind) { case Kind.FIELD: { @@ -62,7 +116,7 @@ export function collectFields( ) { continue; } - collectFields( + collectFieldsImpl( schema, fragments, variableValues, @@ -89,7 +143,7 @@ export function collectFields( ) { continue; } - collectFields( + collectFieldsImpl( schema, fragments, variableValues, @@ -102,7 +156,6 @@ export function collectFields( } } } - return fields; } /** diff --git a/src/execution/execute.ts b/src/execution/execute.ts index 1ef3adc82f..e48701c0e1 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -54,7 +54,30 @@ import { import { getOperationRootType } from '../utilities/getOperationRootType'; import { getVariableValues, getArgumentValues } from './values'; -import { collectFields } from './collectFields'; +import { + collectFields, + collectSubfields as _collectSubfields, +} from './collectFields'; + +/** + * A memoized collection of relevant subfields with regard to the return + * type. Memoizing ensures the subfields are not repeatedly calculated, which + * saves overhead when resolving lists of values. + */ +const collectSubfields = memoize3( + ( + exeContext: ExecutionContext, + returnType: GraphQLObjectType, + fieldNodes: ReadonlyArray, + ) => + _collectSubfields( + exeContext.schema, + exeContext.fragments, + exeContext.variableValues, + returnType, + fieldNodes, + ), +); /** * Terminology @@ -330,8 +353,6 @@ function executeOperation( exeContext.variableValues, type, operation.selectionSet, - new Map(), - new Set(), ); const path = undefined; @@ -921,35 +942,6 @@ function invalidReturnTypeError( ); } -/** - * A memoized collection of relevant subfields with regard to the return - * type. Memoizing ensures the subfields are not repeatedly calculated, which - * saves overhead when resolving lists of values. - */ -const collectSubfields = memoize3(_collectSubfields); -function _collectSubfields( - exeContext: ExecutionContext, - returnType: GraphQLObjectType, - fieldNodes: ReadonlyArray, -): Map> { - let subFieldNodes = new Map(); - const visitedFragmentNames = new Set(); - for (const node of fieldNodes) { - if (node.selectionSet) { - subFieldNodes = collectFields( - exeContext.schema, - exeContext.fragments, - exeContext.variableValues, - returnType, - node.selectionSet, - subFieldNodes, - visitedFragmentNames, - ); - } - } - return subFieldNodes; -} - /** * If a resolveType function is not given, then a default resolve behavior is * used which attempts two strategies: diff --git a/src/subscription/subscribe.ts b/src/subscription/subscribe.ts index 6fbca779df..be6b517e7b 100644 --- a/src/subscription/subscribe.ts +++ b/src/subscription/subscribe.ts @@ -198,8 +198,6 @@ async function executeSubscription( variableValues, type, operation.selectionSet, - new Map(), - new Set(), ); const [responseName, fieldNodes] = [...fields.entries()][0]; const fieldDef = getFieldDef(schema, type, fieldNodes[0]); diff --git a/src/validation/rules/SingleFieldSubscriptionsRule.ts b/src/validation/rules/SingleFieldSubscriptionsRule.ts index 736f0f006f..ad900aa92f 100644 --- a/src/validation/rules/SingleFieldSubscriptionsRule.ts +++ b/src/validation/rules/SingleFieldSubscriptionsRule.ts @@ -44,8 +44,6 @@ export function SingleFieldSubscriptionsRule( variableValues, subscriptionType, node.selectionSet, - new Map(), - new Set(), ); if (fields.size > 1) { const fieldSelectionLists = [...fields.values()];