Skip to content

Commit

Permalink
allow selectionSet hints for stitching to be functions (#1766)
Browse files Browse the repository at this point in the history
that take the specified gateway field as a parameter and produce a required selection set, allowing passing of arguments from the gateway field to the required target schema field.

See #1709
  • Loading branch information
yaacovCR committed Jul 13, 2020
1 parent 81e668d commit 63644bd
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 21 deletions.
3 changes: 2 additions & 1 deletion packages/delegate/src/delegationBindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ export function defaultDelegationBinding(delegationContext: DelegationContext):
delegationTransforms = delegationTransforms.concat([
new AddSelectionSets(
info.schema,
returnType,
stitchingInfo.selectionSetsByType,
stitchingInfo.selectionSetsByField,
returnType
stitchingInfo.dynamicSelectionSetsByField
),
new WrapConcreteTypes(returnType, transformedSchema),
new ExpandAbstractTypes(info.schema, transformedSchema),
Expand Down
27 changes: 23 additions & 4 deletions packages/delegate/src/transforms/AddSelectionSets.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GraphQLSchema, SelectionSetNode, TypeInfo, GraphQLOutputType, Kind } from 'graphql';
import { GraphQLSchema, SelectionSetNode, TypeInfo, GraphQLOutputType, Kind, FieldNode } from 'graphql';

import { Transform, Request } from '@graphql-tools/utils';
import VisitSelectionSets from './VisitSelectionSets';
Expand All @@ -8,12 +8,13 @@ export default class AddSelectionSetsByField implements Transform {

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

Expand All @@ -26,7 +27,8 @@ function visitSelectionSet(
node: SelectionSetNode,
typeInfo: TypeInfo,
selectionSetsByType: Record<string, SelectionSetNode>,
selectionSetsByField: Record<string, Record<string, SelectionSetNode>>
selectionSetsByField: Record<string, Record<string, SelectionSetNode>>,
dynamicSelectionSetsByField: Record<string, Record<string, Array<(node: FieldNode) => SelectionSetNode>>>
): SelectionSetNode {
const parentType = typeInfo.getParentType();
if (parentType != null) {
Expand All @@ -52,6 +54,23 @@ function visitSelectionSet(
});
}

if (parentTypeName in dynamicSelectionSetsByField) {
node.selections.forEach(selection => {
if (selection.kind === Kind.FIELD) {
const name = selection.name.value;
const dynamicSelectionSets = dynamicSelectionSetsByField[parentTypeName][name];
if (dynamicSelectionSets != null) {
dynamicSelectionSets.forEach(selectionSetFn => {
const selectionSet = selectionSetFn(selection);
if (selectionSet != null) {
selections = selections.concat(selectionSet.selections);
}
});
}
}
});
}

if (selections !== node.selections) {
return {
...node,
Expand Down
3 changes: 2 additions & 1 deletion packages/delegate/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ export type MergedTypeResolver = (
export interface StitchingInfo {
transformedSchemas: Map<GraphQLSchema | SubschemaConfig, GraphQLSchema>;
fragmentsByField: Record<string, Record<string, InlineFragmentNode>>;
selectionSetsByField: Record<string, Record<string, SelectionSetNode>>;
selectionSetsByType: Record<string, SelectionSetNode>;
selectionSetsByField: Record<string, Record<string, SelectionSetNode>>;
dynamicSelectionSetsByField: Record<string, Record<string, Array<(node: FieldNode) => SelectionSetNode>>>;
mergedTypes: Record<string, MergedTypeInfo>;
}
41 changes: 28 additions & 13 deletions packages/stitch/src/stitchingInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ export function createStitchingInfo(
return {
transformedSchemas,
fragmentsByField: undefined,
selectionSetsByField: undefined,
selectionSetsByType,
selectionSetsByField: undefined,
dynamicSelectionSetsByField: undefined,
mergedTypes,
};
}
Expand Down Expand Up @@ -194,6 +195,7 @@ function createMergedTypes(

export function completeStitchingInfo(stitchingInfo: StitchingInfo, resolvers: IResolvers): StitchingInfo {
const selectionSetsByField = Object.create(null);
const dynamicSelectionSetsByField = Object.create(null);
const rawFragments: Array<{ field: string; fragment: string }> = [];

Object.keys(resolvers).forEach(typeName => {
Expand All @@ -204,20 +206,32 @@ export function completeStitchingInfo(stitchingInfo: StitchingInfo, resolvers: I
Object.keys(type).forEach(fieldName => {
const field = type[fieldName] as IFieldResolverOptions;
if (field.selectionSet) {
const selectionSet = parseSelectionSet(field.selectionSet);
if (!(typeName in selectionSetsByField)) {
selectionSetsByField[typeName] = Object.create(null);
}
if (typeof field.selectionSet === 'function') {
if (!(typeName in selectionSetsByField)) {
dynamicSelectionSetsByField[typeName] = Object.create(null);
}

if (!(fieldName in selectionSetsByField[typeName])) {
selectionSetsByField[typeName][fieldName] = {
kind: Kind.SELECTION_SET,
selections: [],
};
if (!(fieldName in selectionSetsByField[typeName])) {
dynamicSelectionSetsByField[typeName][fieldName] = [];
}

dynamicSelectionSetsByField[typeName][fieldName].push(field.selectionSet);
} else {
const selectionSet = parseSelectionSet(field.selectionSet);
if (!(typeName in selectionSetsByField)) {
selectionSetsByField[typeName] = Object.create(null);
}

if (!(fieldName in selectionSetsByField[typeName])) {
selectionSetsByField[typeName][fieldName] = {
kind: Kind.SELECTION_SET,
selections: [],
};
}
selectionSetsByField[typeName][fieldName].selections = selectionSetsByField[typeName][
fieldName
].selections.concat(selectionSet.selections);
}
selectionSetsByField[typeName][fieldName].selections = selectionSetsByField[typeName][
fieldName
].selections.concat(selectionSet.selections);
}
if (field.fragment) {
rawFragments.push({
Expand Down Expand Up @@ -254,6 +268,7 @@ export function completeStitchingInfo(stitchingInfo: StitchingInfo, resolvers: I
});

stitchingInfo.selectionSetsByField = selectionSetsByField;
stitchingInfo.dynamicSelectionSetsByField = dynamicSelectionSetsByField;
stitchingInfo.fragmentsByField = fragmentsByField;

return stitchingInfo;
Expand Down
6 changes: 4 additions & 2 deletions packages/stitch/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
DocumentNode,
SelectionNode,
InlineFragmentNode,
FieldNode,
} from 'graphql';
import { ITypeDefinitions, TypeMap } from '@graphql-tools/utils';
import { SubschemaConfig } from '@graphql-tools/delegate';
Expand Down Expand Up @@ -32,8 +33,9 @@ export interface MergedTypeInfo {
export interface StitchingInfo {
transformedSchemas: Map<GraphQLSchema | SubschemaConfig, GraphQLSchema>;
fragmentsByField: Record<string, Record<string, InlineFragmentNode>>;
selectionSetsByField: Record<string, Record<string, SelectionSetNode>>;
selectionSetsByType: Record<string, SelectionSetNode>;
selectionSetsByField: Record<string, Record<string, SelectionSetNode>>;
dynamicSelectionSetsByField: Record<string, Record<string, Array<(node: FieldNode) => SelectionSetNode>>>;
mergedTypes: Record<string, MergedTypeInfo>;
}

Expand Down Expand Up @@ -65,6 +67,6 @@ export type OnTypeConflict = (
declare module '@graphql-tools/utils' {
interface IFieldResolverOptions<TSource = any, TContext = any, TArgs = any> {
fragment?: string;
selectionSet?: string;
selectionSet?: string | ((node: FieldNode) => SelectionSetNode);
}
}

0 comments on commit 63644bd

Please sign in to comment.