Skip to content

Commit

Permalink
refactor(stitchingInfo): shift more calculation to build time (#3199)
Browse files Browse the repository at this point in the history
-- run collectFields on selectionSet hints at build time
-- use a fieldNode cache at build time so that at run time we can collect unique fieldNodes simply by using a Set
  • Loading branch information
yaacovCR committed Jul 13, 2021
1 parent f14f572 commit bcdbba3
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 131 deletions.
4 changes: 2 additions & 2 deletions packages/delegate/src/delegationBindings.ts
Expand Up @@ -21,8 +21,8 @@ export function defaultDelegationBinding<TContext>(
delegationTransforms = delegationTransforms.concat([
new ExpandAbstractTypes(),
new AddSelectionSets(
stitchingInfo.selectionSetsByType,
stitchingInfo.selectionSetsByField,
stitchingInfo.fieldNodesByType,
stitchingInfo.fieldNodesByField,
stitchingInfo.dynamicSelectionSetsByField
),
new WrapConcreteTypes(),
Expand Down
42 changes: 26 additions & 16 deletions packages/delegate/src/getFieldsNotInSubschema.ts
Expand Up @@ -31,23 +31,34 @@ function collectSubFields(info: GraphQLResolveInfo, typeName: string): Record<st

// TODO: Verify whether it is safe that extensions always exists.
const stitchingInfo: Maybe<StitchingInfo> = info.schema.extensions?.['stitchingInfo'];
const selectionSetsByField = stitchingInfo?.selectionSetsByField;
const fieldNodesByField = stitchingInfo?.fieldNodesByField;

for (const responseName in subFieldNodes) {
const fieldName = subFieldNodes[responseName][0].name.value;
const fieldSelectionSet = selectionSetsByField?.[typeName]?.[fieldName];
if (fieldSelectionSet != null) {
subFieldNodes = collectFields(
partialExecutionContext,
type,
fieldSelectionSet,
subFieldNodes,
visitedFragmentNames
);
const subFieldNodesByFieldName = Object.create(null);
for (const responseKey in subFieldNodes) {
const fieldName = subFieldNodes[responseKey][0].name.value;
const additionalFieldNodes = fieldNodesByField?.[typeName]?.[fieldName];
if (additionalFieldNodes) {
for (const additionalFieldNode of additionalFieldNodes) {
const additionalFieldName = additionalFieldNode.name.value;
if (subFieldNodesByFieldName[additionalFieldName] == null) {
subFieldNodesByFieldName[additionalFieldName] = [additionalFieldNode];
} else {
subFieldNodesByFieldName[additionalFieldName].push(additionalFieldNode);
}
}
}
}

for (const responseKey in subFieldNodes) {
const fieldName = subFieldNodes[responseKey][0].name.value;
if (subFieldNodesByFieldName[fieldName] == null) {
subFieldNodesByFieldName[fieldName] = subFieldNodes[responseKey];
} else {
subFieldNodesByFieldName[fieldName].concat(subFieldNodes[responseKey]);
}
}

return subFieldNodes;
return subFieldNodesByFieldName;
}

export const getFieldsNotInSubschema = memoizeInfoAnd2Objects(function (
Expand All @@ -65,10 +76,9 @@ export const getFieldsNotInSubschema = memoizeInfoAnd2Objects(function (
const subFieldNodes = collectSubFields(info, typeName);

let fieldsNotInSchema: Array<FieldNode> = [];
for (const responseName in subFieldNodes) {
const fieldName = subFieldNodes[responseName][0].name.value;
for (const fieldName in subFieldNodes) {
if (!(fieldName in fields)) {
fieldsNotInSchema = fieldsNotInSchema.concat(subFieldNodes[responseName]);
fieldsNotInSchema = fieldsNotInSchema.concat(subFieldNodes[fieldName]);
}
}

Expand Down
43 changes: 21 additions & 22 deletions packages/delegate/src/transforms/AddSelectionSets.ts
@@ -1,22 +1,21 @@
import { SelectionSetNode, TypeInfo, Kind, FieldNode, SelectionNode, print } from 'graphql';
import { SelectionSetNode, TypeInfo, Kind, FieldNode, SelectionNode } from 'graphql';

import { Maybe, ExecutionRequest } from '@graphql-tools/utils';

import { Transform, DelegationContext } from '../types';
import { memoize2 } from '../memoize';

import VisitSelectionSets from './VisitSelectionSets';

export default class AddSelectionSets implements Transform {
private readonly transformer: VisitSelectionSets;

constructor(
selectionSetsByType: Record<string, SelectionSetNode>,
selectionSetsByField: Record<string, Record<string, SelectionSetNode>>,
fieldNodesByType: Record<string, Array<FieldNode>>,
fieldNodesByField: Record<string, Record<string, Array<FieldNode>>>,
dynamicSelectionSetsByField: Record<string, Record<string, Array<(node: FieldNode) => SelectionSetNode>>>
) {
this.transformer = new VisitSelectionSets((node, typeInfo) =>
visitSelectionSet(node, typeInfo, selectionSetsByType, selectionSetsByField, dynamicSelectionSetsByField)
visitSelectionSet(node, typeInfo, fieldNodesByType, fieldNodesByField, dynamicSelectionSetsByField)
);
}

Expand All @@ -32,30 +31,30 @@ export default class AddSelectionSets implements Transform {
function visitSelectionSet(
node: SelectionSetNode,
typeInfo: TypeInfo,
selectionSetsByType: Record<string, SelectionSetNode>,
selectionSetsByField: Record<string, Record<string, SelectionSetNode>>,
fieldNodesByType: Record<string, Array<FieldNode>>,
fieldNodesByField: Record<string, Record<string, Array<FieldNode>>>,
dynamicSelectionSetsByField: Record<string, Record<string, Array<(node: FieldNode) => SelectionSetNode>>>
): Maybe<SelectionSetNode> {
const parentType = typeInfo.getParentType();

const newSelections: Map<string, SelectionNode> = new Map();
const newSelections: Set<SelectionNode> = new Set();

if (parentType != null) {
const parentTypeName = parentType.name;
addSelectionsToMap(newSelections, node);
addSelectionsToSet(newSelections, node.selections);

if (parentTypeName in selectionSetsByType) {
const selectionSet = selectionSetsByType[parentTypeName];
addSelectionsToMap(newSelections, selectionSet);
const fieldNodes = fieldNodesByType[parentTypeName];
if (fieldNodes) {
addSelectionsToSet(newSelections, fieldNodes);
}

if (parentTypeName in selectionSetsByField) {
if (parentTypeName in fieldNodesByField) {
for (const selection of node.selections) {
if (selection.kind === Kind.FIELD) {
const name = selection.name.value;
const selectionSet = selectionSetsByField[parentTypeName][name];
if (selectionSet != null) {
addSelectionsToMap(newSelections, selectionSet);
const fieldName = selection.name.value;
const fieldNodes = fieldNodesByField[parentTypeName][fieldName];
if (fieldNodes != null) {
addSelectionsToSet(newSelections, fieldNodes);
}
}
}
Expand All @@ -70,7 +69,7 @@ function visitSelectionSet(
for (const selectionSetFn of dynamicSelectionSets) {
const selectionSet = selectionSetFn(selection);
if (selectionSet != null) {
addSelectionsToMap(newSelections, selectionSet);
addSelectionsToSet(newSelections, selectionSet.selections);
}
}
}
Expand All @@ -85,8 +84,8 @@ function visitSelectionSet(
}
}

const addSelectionsToMap = memoize2(function (map: Map<string, SelectionNode>, selectionSet: SelectionSetNode): void {
for (const selection of selectionSet.selections) {
map.set(print(selection), selection);
function addSelectionsToSet(set: Set<SelectionNode>, selections: ReadonlyArray<SelectionNode>): void {
for (const selection of selections) {
set.add(selection);
}
});
}
4 changes: 2 additions & 2 deletions packages/delegate/src/types.ts
Expand Up @@ -193,8 +193,8 @@ export type MergedTypeResolver<TContext = Record<string, any>> = (

export interface StitchingInfo<TContext = Record<string, any>> {
subschemaMap: Map<GraphQLSchema | SubschemaConfig<any, any, any, TContext>, Subschema<any, any, any, TContext>>;
selectionSetsByType: Record<string, SelectionSetNode>;
selectionSetsByField: Record<string, Record<string, SelectionSetNode>>;
fieldNodesByType: Record<string, Array<FieldNode>>;
fieldNodesByField: Record<string, Record<string, Array<FieldNode>>>;
dynamicSelectionSetsByField: Record<string, Record<string, Array<(node: FieldNode) => SelectionSetNode>>>;
mergedTypes: Record<string, MergedTypeInfo<TContext>>;
}
Expand Down

0 comments on commit bcdbba3

Please sign in to comment.