Skip to content

Fix stitching from and to interfaces #1443

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/delegate/src/createRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import {
DocumentNode,
} from 'graphql';

import { Request, ICreateRequest, serializeInputValue, updateArgument } from '@graphql-tools/utils';
import { ICreateRequestFromInfo } from './types';
import { Request, serializeInputValue, updateArgument } from '@graphql-tools/utils';
import { ICreateRequestFromInfo, ICreateRequest } from './types';

export function getDelegatingOperation(parentType: GraphQLObjectType, schema: GraphQLSchema): OperationTypeNode {
if (parentType === schema.getMutationType()) {
Expand Down
14 changes: 12 additions & 2 deletions packages/delegate/src/delegateToSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from '@graphql-tools/utils';

import ExpandAbstractTypes from './transforms/ExpandAbstractTypes';
import WrapConcreteTypes from './transforms/WrapConcreteTypes';
import FilterToSchema from './transforms/FilterToSchema';
import AddReplacementSelectionSets from './transforms/AddReplacementSelectionSets';
import AddReplacementFragments from './transforms/AddReplacementFragments';
Expand Down Expand Up @@ -76,6 +77,7 @@ function buildDelegationTransforms(
args: Record<string, any>,
returnType: GraphQLOutputType,
transforms: Array<Transform>,
transformedSchema: GraphQLSchema,
skipTypeMerging: boolean
): Array<Transform> {
let delegationTransforms: Array<Transform> = [
Expand All @@ -89,9 +91,15 @@ function buildDelegationTransforms(
);
}

delegationTransforms = delegationTransforms.concat(transforms);
const transformedTargetSchema =
info.mergeInfo == null
? transformedSchema ?? targetSchema
: transformedSchema ?? info.mergeInfo.transformedSchemas.get(subschemaOrSubschemaConfig) ?? targetSchema;

delegationTransforms.push(new WrapConcreteTypes(returnType, transformedTargetSchema));
delegationTransforms.push(new ExpandAbstractTypes(info.schema, transformedTargetSchema));

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

if (info.mergeInfo != null) {
delegationTransforms.push(new AddReplacementFragments(targetSchema, info.mergeInfo.replacementFragments));
Expand All @@ -117,6 +125,7 @@ export function delegateRequest({
returnType = info.returnType,
context,
transforms = [],
transformedSchema,
skipValidation,
skipTypeMerging,
}: IDelegateRequestOptions) {
Expand Down Expand Up @@ -147,6 +156,7 @@ export function delegateRequest({
args,
returnType,
requestTransforms.reverse(),
transformedSchema,
skipTypeMerging
);

Expand Down
95 changes: 95 additions & 0 deletions packages/delegate/src/transforms/WrapConcreteTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import {
DocumentNode,
GraphQLSchema,
Kind,
getNamedType,
GraphQLOutputType,
isAbstractType,
TypeInfo,
visit,
visitWithTypeInfo,
isObjectType,
FieldNode,
} from 'graphql';

import { Transform, Request } from '@graphql-tools/utils';

// For motivation, see https://github.com/ardatan/graphql-tools/issues/751

export default class WrapConcreteTypes implements Transform {
private readonly returnType: GraphQLOutputType;
private readonly targetSchema: GraphQLSchema;

constructor(returnType: GraphQLOutputType, targetSchema: GraphQLSchema) {
this.returnType = returnType;
this.targetSchema = targetSchema;
}

public transformRequest(originalRequest: Request): Request {
const document = wrapConcreteTypes(this.returnType, this.targetSchema, originalRequest.document);
return {
...originalRequest,
document,
};
}
}

function wrapConcreteTypes(
returnType: GraphQLOutputType,
targetSchema: GraphQLSchema,
document: DocumentNode
): DocumentNode {
const namedType = getNamedType(returnType);

if (!isObjectType(namedType)) {
return document;
}

const queryRootType = targetSchema.getQueryType();
const mutationRootType = targetSchema.getMutationType();
const subscriptionRootType = targetSchema.getSubscriptionType();

const typeInfo = new TypeInfo(targetSchema);
const newDocument = visit(
document,
visitWithTypeInfo(typeInfo, {
[Kind.FIELD](node: FieldNode) {
const maybeType = typeInfo.getParentType();
if (maybeType == null) {
return false;
}

const parentType = getNamedType(maybeType);
if (parentType !== queryRootType && parentType !== mutationRootType && parentType !== subscriptionRootType) {
return false;
}

if (!isAbstractType(getNamedType(typeInfo.getType()))) {
return false;
}

return {
...node,
selectionSet: {
kind: Kind.SELECTION_SET,
selections: [
{
kind: Kind.INLINE_FRAGMENT,
typeCondition: {
kind: Kind.NAMED_TYPE,
name: {
kind: Kind.NAME,
value: namedType.name,
},
},
selectionSet: node.selectionSet,
},
],
},
};
},
})
);

return newDocument;
}
32 changes: 26 additions & 6 deletions packages/delegate/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import {
GraphQLResolveInfo,
GraphQLFieldResolver,
InlineFragmentNode,
FragmentDefinitionNode,
GraphQLObjectType,
VariableDefinitionNode,
} from 'graphql';
import { Operation, Transform, Request, TypeMap, ExecutionResult } from '@graphql-tools/utils';

Expand All @@ -22,6 +25,7 @@ export interface IDelegateToSchemaOptions<TContext = Record<string, any>, TArgs
info: GraphQLResolveInfo;
rootValue?: Record<string, any>;
transforms?: Array<Transform>;
transformedSchema?: GraphQLSchema;
skipValidation?: boolean;
skipTypeMerging?: boolean;
}
Expand All @@ -38,6 +42,19 @@ export interface ICreateRequestFromInfo {
fieldNodes?: ReadonlyArray<FieldNode>;
}

export interface ICreateRequest {
sourceSchema?: GraphQLSchema;
sourceParentType?: GraphQLObjectType;
sourceFieldName?: string;
fragments?: Record<string, FragmentDefinitionNode>;
variableDefinitions?: ReadonlyArray<VariableDefinitionNode>;
variableValues?: Record<string, any>;
targetOperation: Operation;
targetFieldName: string;
selectionSet?: SelectionSetNode;
fieldNodes?: ReadonlyArray<FieldNode>;
}

export interface MergedTypeInfo {
subschemas: Array<SubschemaConfig>;
selectionSet?: SelectionSetNode;
Expand Down Expand Up @@ -70,12 +87,15 @@ export type Subscriber = <TReturn = Record<string, any>, TArgs = Record<string,
params: ExecutionParams<TArgs, TContext>
) => Promise<AsyncIterator<ExecutionResult<TReturn>> | ExecutionResult<TReturn>>;

export type CreateProxyingResolverFn = (
schema: GraphQLSchema | SubschemaConfig,
transforms: Array<Transform>,
operation: Operation,
fieldName: string
) => GraphQLFieldResolver<any, any>;
export interface ICreateProxyingResolverOptions {
schema: GraphQLSchema | SubschemaConfig;
transforms?: Array<Transform>;
transformedSchema?: GraphQLSchema;
operation?: Operation;
fieldName?: string;
}

export type CreateProxyingResolverFn = (options: ICreateProxyingResolverOptions) => GraphQLFieldResolver<any, any>;

export interface SubschemaConfig {
schema: GraphQLSchema;
Expand Down
2 changes: 2 additions & 0 deletions packages/stitch/src/mergeInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ import { delegateToSchema, isSubschemaConfig, SubschemaConfig } from '@graphql-t
import { MergeTypeCandidate, MergedTypeInfo, MergeInfo, MergeTypeFilter } from './types';

export function createMergeInfo(
transformedSchemas: Map<GraphQLSchema | SubschemaConfig, GraphQLSchema>,
typeCandidates: Record<string, Array<MergeTypeCandidate>>,
mergeTypes?: boolean | Array<string> | MergeTypeFilter
): MergeInfo {
return {
transformedSchemas,
fragments: [],
replacementSelectionSets: undefined,
replacementFragments: undefined,
Expand Down
24 changes: 13 additions & 11 deletions packages/stitch/src/stitchSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,6 @@ export function stitchSchemas({
throw new Error('Expected `resolverValidationOptions` to be an object');
}

const typeCandidates: Record<string, Array<MergeTypeCandidate>> = Object.create(null);
const extensions: Array<DocumentNode> = [];
const directives: Array<GraphQLDirective> = [];
const schemaDefs = Object.create(null);
const operationTypeNames = {
query: 'Query',
mutation: 'Mutation',
subscription: 'Subscription',
};

let schemaLikeObjects: Array<GraphQLSchema | SubschemaConfig | DocumentNode | GraphQLNamedType> = [...subschemas];
if (typeDefs) {
schemaLikeObjects.push(buildDocumentFromTypeDefinitions(typeDefs, parseOptions));
Expand All @@ -78,8 +68,20 @@ export function stitchSchemas({
}
});

const transformedSchemas: Map<GraphQLSchema | SubschemaConfig, GraphQLSchema> = new Map();
const typeCandidates: Record<string, Array<MergeTypeCandidate>> = Object.create(null);
const extensions: Array<DocumentNode> = [];
const directives: Array<GraphQLDirective> = [];
const schemaDefs = Object.create(null);
const operationTypeNames = {
query: 'Query',
mutation: 'Mutation',
subscription: 'Subscription',
};

buildTypeCandidates({
schemaLikeObjects,
transformedSchemas,
typeCandidates,
extensions,
directives,
Expand All @@ -90,7 +92,7 @@ export function stitchSchemas({

let mergeInfo: MergeInfo;

mergeInfo = createMergeInfo(typeCandidates, mergeTypes);
mergeInfo = createMergeInfo(transformedSchemas, typeCandidates, mergeTypes);

const typeMap = buildTypeMap({
typeCandidates,
Expand Down
4 changes: 4 additions & 0 deletions packages/stitch/src/typeCandidates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ function isDocumentNode(schemaLikeObject: any): schemaLikeObject is DocumentNode

export function buildTypeCandidates({
schemaLikeObjects,
transformedSchemas,
typeCandidates,
extensions,
directives,
Expand All @@ -51,6 +52,7 @@ export function buildTypeCandidates({
mergeDirectives,
}: {
schemaLikeObjects: Array<GraphQLSchema | SubschemaConfig | DocumentNode | GraphQLNamedType>;
transformedSchemas: Map<GraphQLSchema | SubschemaConfig, GraphQLSchema>;
typeCandidates: Record<string, Array<MergeTypeCandidate>>;
extensions: Array<DocumentNode>;
directives: Array<GraphQLDirective>;
Expand Down Expand Up @@ -80,6 +82,8 @@ export function buildTypeCandidates({
if (isSchema(schemaLikeObject) || isSubschemaConfig(schemaLikeObject)) {
const schema = wrapSchema(schemaLikeObject);

transformedSchemas.set(schemaLikeObject, schema);

const operationTypes = {
query: schema.getQueryType(),
mutation: schema.getMutationType(),
Expand Down
1 change: 1 addition & 0 deletions packages/stitch/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface MergedTypeInfo {
}

export interface MergeInfo {
transformedSchemas: Map<GraphQLSchema | SubschemaConfig, GraphQLSchema>;
fragments: Array<{
field: string;
fragment: string;
Expand Down
Loading