Skip to content
This repository has been archived by the owner on Apr 15, 2020. It is now read-only.

Commit

Permalink
feat(delegation): selectionSet option
Browse files Browse the repository at this point in the history
Specifying the selection Set option can indicate that there are no existing arguments.

Use selectionSets instead of fragments for type merging.
  • Loading branch information
yaacovCR committed Feb 2, 2020
1 parent 299a21f commit 338fdd0
Show file tree
Hide file tree
Showing 12 changed files with 256 additions and 115 deletions.
24 changes: 16 additions & 8 deletions src/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
GraphQLObjectType,
InlineFragmentNode,
GraphQLOutputType,
SelectionSetNode,
} from 'graphql';

import { TypeMap } from 'graphql/type/schema';
Expand Down Expand Up @@ -101,8 +102,7 @@ export type SubschemaConfig = {
};

export type MergedTypeConfig = {
fragment?: string;
parsedFragment?: InlineFragmentNode,
selectionSet?: string;
merge: MergedTypeResolver;
};

Expand All @@ -111,7 +111,7 @@ export type MergedTypeResolver = (
context: Record<string, any>,
info: IGraphQLToolsResolveInfo,
subschema: GraphQLSchema | SubschemaConfig,
fieldNodes: Array<FieldNode>,
selectionSet: SelectionSetNode,
) => any;

export type GraphQLSchemaWithTransforms = GraphQLSchema & { transforms?: Array<Transform> };
Expand All @@ -132,8 +132,9 @@ export interface IDelegateToSchemaOptions<TContext = { [key: string]: any }> {
operation?: Operation;
fieldName?: string;
returnType?: GraphQLOutputType;
fieldNodes?: ReadonlyArray<FieldNode>;
args?: { [key: string]: any };
selectionSet?: SelectionSetNode;
fieldNodes?: ReadonlyArray<FieldNode>;
context?: TContext;
info: IGraphQLToolsResolveInfo;
rootValue?: Record<string, any>;
Expand All @@ -147,8 +148,9 @@ export interface ICreateRequestFromInfo {
schema: GraphQLSchema | SubschemaConfig;
operation: Operation;
fieldName: string;
additionalArgs: Record<string, any>;
fieldNodes: ReadonlyArray<FieldNode>;
args?: Record<string, any>;
selectionSet?: SelectionSetNode;
fieldNodes?: ReadonlyArray<FieldNode>;
}

export type IDelegateRequestOptions = {
Expand All @@ -174,22 +176,28 @@ export type MergeInfo = {
field: string;
fragment: string;
}>;
replacementSelectionSets: ReplacementSelectionSetMapping,
replacementFragments: ReplacementFragmentMapping,
mergedTypes: Record<string, MergedTypeInfo>,
delegateToSchema<TContext>(options: IDelegateToSchemaOptions<TContext>): any;
};

export type ReplacementSelectionSetMapping = {
[typeName: string]: { [fieldName: string]: SelectionSetNode };
};

export type ReplacementFragmentMapping = {
[typeName: string]: { [fieldName: string]: InlineFragmentNode };
};

export type MergedTypeInfo = {
subschemas: Array<SubschemaConfig>,
fragment?: InlineFragmentNode,
selectionSet?: SelectionSetNode,
uniqueFields: Record<string, SubschemaConfig>,
nonUniqueFields: Record<string, Array<SubschemaConfig>>,
typeMaps: Map<SubschemaConfig, TypeMap>,
containsFragment: Map<SubschemaConfig, Map<InlineFragmentNode, boolean>>,
selectionSets: Map<SubschemaConfig, SelectionSetNode>,
containsSelectionSet: Map<SubschemaConfig, Map<SelectionSetNode, boolean>>,
};

export type IFieldResolver<TSource, TContext, TArgs = Record<string, any>> = (
Expand Down
56 changes: 31 additions & 25 deletions src/stitching/createRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
GraphQLNonNull,
TypeNode,
GraphQLType,
SelectionSetNode,
} from 'graphql';

import {
Expand Down Expand Up @@ -48,8 +49,9 @@ export function createRequestFromInfo({
schema,
operation = getDelegatingOperation(info.parentType, info.schema),
fieldName = info.fieldName,
additionalArgs,
fieldNodes = info.fieldNodes,
args,
selectionSet,
fieldNodes,
}: ICreateRequestFromInfo): Request {
return createRequest(
info.schema,
Expand All @@ -59,8 +61,9 @@ export function createRequestFromInfo({
schema,
operation,
fieldName,
additionalArgs,
fieldNodes,
args,
selectionSet,
selectionSet ? undefined : (fieldNodes ? fieldNodes : info.fieldNodes),
);
}

Expand All @@ -72,24 +75,28 @@ export function createRequest(
targetSchemaOrSchemaConfig: GraphQLSchema | SubschemaConfig,
targetOperation: Operation,
targetField: string,
additionalArgs: Record<string, any>,
args: Record<string, any>,
selectionSet: SelectionSetNode,
fieldNodes: ReadonlyArray<FieldNode>,
): Request {
let selections: Array<SelectionNode> = [];
const originalSelections: ReadonlyArray<SelectionNode> = fieldNodes;
originalSelections.forEach((field: FieldNode) => {
const fieldSelections = field.selectionSet
? field.selectionSet.selections
: [];
selections = selections.concat(fieldSelections);
});
let argumentNodes: ReadonlyArray<ArgumentNode>;

let selectionSet = undefined;
if (selections.length > 0) {
selectionSet = {
if (!selectionSet && fieldNodes) {
const selections: Array<SelectionNode> = fieldNodes.reduce(
(acc, fieldNode) => fieldNode.selectionSet ?
acc.concat(fieldNode.selectionSet.selections) :
acc,
[],
);

selectionSet = selections.length ? {
kind: Kind.SELECTION_SET,
selections: selections,
};
} : undefined;

argumentNodes = fieldNodes[0].arguments;
} else {
argumentNodes = [];
}

let variables = {};
Expand All @@ -99,8 +106,7 @@ export function createRequest(
variables[varName] = serializeInputValue(varType, variableValues[varName]);
}

let args = fieldNodes[0].arguments;
if (additionalArgs) {
if (args) {
const {
arguments: updatedArguments,
variableDefinitions: updatedVariableDefinitions,
Expand All @@ -109,20 +115,20 @@ export function createRequest(
targetSchemaOrSchemaConfig,
targetOperation,
targetField,
args,
argumentNodes,
variableDefinitions,
variables,
additionalArgs,
args,
);
args = updatedArguments;
argumentNodes = updatedArguments;
variableDefinitions = updatedVariableDefinitions;
variables = updatedVariableValues;
}

const fieldNode: FieldNode = {
const rootfieldNode: FieldNode = {
kind: Kind.FIELD,
alias: null,
arguments: args,
arguments: argumentNodes,
selectionSet,
name: {
kind: Kind.NAME,
Expand All @@ -136,7 +142,7 @@ export function createRequest(
variableDefinitions,
selectionSet: {
kind: Kind.SELECTION_SET,
selections: [fieldNode],
selections: [rootfieldNode],
},
};

Expand Down
44 changes: 29 additions & 15 deletions src/stitching/delegateToSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ import {
import {
ExpandAbstractTypes,
FilterToSchema,
AddReplacementSelectionSets,
AddReplacementFragments,
AddMergedTypeFragments,
AddMergedTypeSelectionSets,
AddTypenameToAbstract,
CheckResultAndHandleErrors,
applyRequestTransforms,
applyResultTransforms,
Transform,
} from '../transforms';

import {
Expand Down Expand Up @@ -54,15 +56,17 @@ export default function delegateToSchema(
fieldName = info.fieldName,
returnType = info.returnType,
args,
fieldNodes = info.fieldNodes,
selectionSet,
fieldNodes,
} = options;

const request = createRequestFromInfo({
info,
schema: subschemaOrSubschemaConfig,
operation,
fieldName,
additionalArgs: args,
args,
selectionSet,
fieldNodes,
});

Expand All @@ -82,7 +86,6 @@ export function delegateRequest({
info,
operation = getDelegatingOperation(info.parentType, info.schema),
fieldName = info.fieldName,
fieldNodes = info.fieldNodes,
returnType = info.returnType,
context,
transforms = [],
Expand All @@ -102,25 +105,35 @@ export function delegateRequest({
rootValue = rootValue || info.rootValue;
}

transforms = [
let delegationTransforms: Array<Transform> = [
new CheckResultAndHandleErrors(info, fieldName, subschema, context, returnType, skipTypeMerging),
...transforms,
new ExpandAbstractTypes(info.schema, targetSchema),
];

if (info.mergeInfo) {
transforms.push(
delegationTransforms.push(
new AddReplacementSelectionSets(info.schema, info.mergeInfo.replacementSelectionSets),
new AddMergedTypeSelectionSets(info.schema, info.mergeInfo.mergedTypes),
);
}

delegationTransforms = delegationTransforms.concat(transforms);

delegationTransforms.push(
new ExpandAbstractTypes(info.schema, targetSchema),
);

if (info.mergeInfo) {
delegationTransforms.push(
new AddReplacementFragments(targetSchema, info.mergeInfo.replacementFragments),
new AddMergedTypeFragments(targetSchema, info.mergeInfo.mergedTypes),
);
}

transforms.push(
delegationTransforms.push(
new FilterToSchema(targetSchema),
new AddTypenameToAbstract(targetSchema),
);

request = applyRequestTransforms(request, transforms);
request = applyRequestTransforms(request, delegationTransforms);

if (!skipValidation) {
const errors = validate(targetSchema, request.document);
Expand All @@ -140,9 +153,10 @@ export function delegateRequest({
});

if (executionResult instanceof Promise) {
return executionResult.then((originalResult: any) => applyResultTransforms(originalResult, transforms));
return executionResult.then((originalResult: any) =>
applyResultTransforms(originalResult, delegationTransforms));
} else {
return applyResultTransforms(executionResult, transforms);
return applyResultTransforms(executionResult, delegationTransforms);
}

} else if (operation === 'subscription') {
Expand All @@ -157,15 +171,15 @@ export function delegateRequest({
if (isAsyncIterable(subscriptionResult)) {
// "subscribe" to the subscription result and map the result through the transforms
return mapAsyncIterator<ExecutionResult, any>(subscriptionResult, result => {
const transformedResult = applyResultTransforms(result, transforms);
const transformedResult = applyResultTransforms(result, delegationTransforms);
// wrap with fieldName to return for an additional round of resolutioon
// with payload as rootValue
return {
[info.fieldName]: transformedResult,
};
});
} else {
return applyResultTransforms(subscriptionResult, transforms);
return applyResultTransforms(subscriptionResult, delegationTransforms);
}
});

Expand Down
22 changes: 10 additions & 12 deletions src/stitching/mergeFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,17 @@ function buildDelegationPlan(
proxiableSubschemas: Array<SubschemaConfig>,
nonProxiableSubschemas: Array<SubschemaConfig>,
} {
// 1. use fragment and source subschemas to calculate if possible to delegate to given subschema
// TODO: change logic so that fragment can be spread across multiple subschemas?
// 1. calculate if possible to delegate to given subschema
// TODO: change logic so that required selection set can be spread across multiple subschemas?

const proxiableSubschemas: Array<SubschemaConfig> = [];
const nonProxiableSubschemas: Array<SubschemaConfig> = [];

targetSubschemas.forEach(t => {
if (sourceSubschemas.some(s =>
mergedTypeInfo.containsFragment.get(s).get(t.mergedTypeConfigs[typeName].parsedFragment)
if (sourceSubschemas.some(s => {
const selectionSet = mergedTypeInfo.selectionSets.get(t);
return mergedTypeInfo.containsSelectionSet.get(s).get(selectionSet);
}
)) {
proxiableSubschemas.push(t);
} else {
Expand Down Expand Up @@ -118,19 +120,15 @@ export function mergeFields(

const maybePromises: Promise<any> | any = [];
delegationMap.forEach((selections: Array<SelectionNode>, s: SubschemaConfig) => {
const newFieldNodes = [{
...info.fieldNodes[0],
selectionSet: {
kind: Kind.SELECTION_SET,
selections,
}
}];
const maybePromise = s.mergedTypeConfigs[typeName].merge(
object,
context,
info,
s,
newFieldNodes,
{
kind: Kind.SELECTION_SET,
selections,
},
);
maybePromises.push(maybePromise);
});
Expand Down

0 comments on commit 338fdd0

Please sign in to comment.