Skip to content

Commit

Permalink
collectFields/collectSubfields refactor and export as part of public …
Browse files Browse the repository at this point in the history
…API (#3272)

Motivated by #3246
  • Loading branch information
IvanGoncharov committed Sep 24, 2021
1 parent a05dcfc commit e95ea9b
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 42 deletions.
65 changes: 59 additions & 6 deletions src/execution/collectFields.ts
Expand Up @@ -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
Expand All @@ -37,9 +36,64 @@ export function collectFields(
variableValues: { [variable: string]: unknown },
runtimeType: GraphQLObjectType,
selectionSet: SelectionSetNode,
): Map<string, ReadonlyArray<FieldNode>> {
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<FragmentDefinitionNode>,
variableValues: { [variable: string]: unknown },
returnType: GraphQLObjectType,
fieldNodes: ReadonlyArray<FieldNode>,
): Map<string, ReadonlyArray<FieldNode>> {
const subFieldNodes = new Map();
const visitedFragmentNames = new Set<string>();
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<FragmentDefinitionNode>,
variableValues: { [variable: string]: unknown },
runtimeType: GraphQLObjectType,
selectionSet: SelectionSetNode,
fields: Map<string, Array<FieldNode>>,
visitedFragmentNames: Set<string>,
): Map<string, ReadonlyArray<FieldNode>> {
): void {
for (const selection of selectionSet.selections) {
switch (selection.kind) {
case Kind.FIELD: {
Expand All @@ -62,7 +116,7 @@ export function collectFields(
) {
continue;
}
collectFields(
collectFieldsImpl(
schema,
fragments,
variableValues,
Expand All @@ -89,7 +143,7 @@ export function collectFields(
) {
continue;
}
collectFields(
collectFieldsImpl(
schema,
fragments,
variableValues,
Expand All @@ -102,7 +156,6 @@ export function collectFields(
}
}
}
return fields;
}

/**
Expand Down
56 changes: 24 additions & 32 deletions src/execution/execute.ts
Expand Up @@ -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<FieldNode>,
) =>
_collectSubfields(
exeContext.schema,
exeContext.fragments,
exeContext.variableValues,
returnType,
fieldNodes,
),
);

/**
* Terminology
Expand Down Expand Up @@ -330,8 +353,6 @@ function executeOperation(
exeContext.variableValues,
type,
operation.selectionSet,
new Map(),
new Set(),
);

const path = undefined;
Expand Down Expand Up @@ -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<FieldNode>,
): Map<string, ReadonlyArray<FieldNode>> {
let subFieldNodes = new Map();
const visitedFragmentNames = new Set<string>();
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:
Expand Down
2 changes: 0 additions & 2 deletions src/subscription/subscribe.ts
Expand Up @@ -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]);
Expand Down
2 changes: 0 additions & 2 deletions src/validation/rules/SingleFieldSubscriptionsRule.ts
Expand Up @@ -44,8 +44,6 @@ export function SingleFieldSubscriptionsRule(
variableValues,
subscriptionType,
node.selectionSet,
new Map(),
new Set(),
);
if (fields.size > 1) {
const fieldSelectionLists = [...fields.values()];
Expand Down

0 comments on commit e95ea9b

Please sign in to comment.