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

Commit

Permalink
feat(mergeSchemas): allow transform specification
Browse files Browse the repository at this point in the history
Allow specification of transforms directly within mergeSchemas.
Allows for removal of an additional round of delegationg when merging transformed schemas.
Requires schema wrapping prior to registering a type candidate for merge.
  • Loading branch information
yaacovCR committed Oct 25, 2019
1 parent 57f154a commit 5efafbe
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 111 deletions.
9 changes: 8 additions & 1 deletion src/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,14 @@ export type SchemaExecutionConfig = {
dispatcher?: Dispatcher;
};

export type SubSchemaConfig = {
transforms?: Array<Transform>;
} & SchemaExecutionConfig;

export type GraphQLSchemaWithTransforms = GraphQLSchema & { transforms?: Array<Transform> };

export type SchemaLikeObject =
SchemaExecutionConfig |
SubSchemaConfig |
GraphQLSchema |
string |
DocumentNode |
Expand All @@ -95,6 +99,9 @@ export function isSchemaExecutionConfig(value: SchemaLikeObject): value is Schem
return !!(value as SchemaExecutionConfig).schema;
}

export function isSubSchemaConfig(value: SchemaLikeObject): value is SubSchemaConfig {
return !!(value as SubSchemaConfig).schema;
}
export interface IDelegateToSchemaOptions<TContext = { [key: string]: any }> {
schema: GraphQLSchema | SchemaExecutionConfig;
link?: ApolloLink;
Expand Down
92 changes: 16 additions & 76 deletions src/stitching/mergeSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,11 @@ import {
} from 'graphql';
import {
IDelegateToSchemaOptions,
IFieldResolver,
MergeInfo,
OnTypeConflict,
IResolversParameter,
SchemaExecutionConfig,
isSchemaExecutionConfig,
isSubSchemaConfig,
SchemaLikeObject,
GraphQLSchemaWithTransforms,
IResolvers,
} from '../Interfaces';
import {
Expand All @@ -34,22 +31,20 @@ import {
Transform,
ExpandAbstractTypes,
ReplaceFieldWithFragment,
wrapSchema,
} from '../transforms';
import {
SchemaDirectiveVisitor,
cloneDirective,
cloneType,
healSchema,
healTypes,
forEachField,
mergeDeep,
} from '../utils';
import { makeMergedType } from './makeMergedType';

type MergeTypeCandidate = {
schema?: GraphQLSchema;
executionConfig?: SchemaExecutionConfig;
type: GraphQLNamedType;
schema?: GraphQLSchema;
};

type CandidateSelector = (
Expand Down Expand Up @@ -82,14 +77,12 @@ export default function mergeSchemas({
}> = [];

schemas.forEach(schemaLikeObject => {
if (schemaLikeObject instanceof GraphQLSchema || isSchemaExecutionConfig(schemaLikeObject)) {
let schema: GraphQLSchemaWithTransforms;
let executionConfig: SchemaExecutionConfig;
if (isSchemaExecutionConfig(schemaLikeObject)) {
executionConfig = schemaLikeObject;
schema = schemaLikeObject.schema;
if (schemaLikeObject instanceof GraphQLSchema || isSubSchemaConfig(schemaLikeObject)) {
let schema: GraphQLSchema;
if (isSubSchemaConfig(schemaLikeObject)) {
schema = wrapSchema(schemaLikeObject, schemaLikeObject.transforms || []);
} else {
schema = schemaLikeObject;
schema = wrapSchema(schemaLikeObject, []);
}

allSchemas.push(schema);
Expand All @@ -104,7 +97,6 @@ export default function mergeSchemas({
if (operationTypes[typeName]) {
addTypeCandidate(typeCandidates, typeName, {
schema,
executionConfig,
type: operationTypes[typeName],
});
}
Expand All @@ -129,7 +121,6 @@ export default function mergeSchemas({
) {
addTypeCandidate(typeCandidates, type.name, {
schema,
executionConfig,
type,
});
}
Expand Down Expand Up @@ -339,27 +330,6 @@ function guessSchemaByRootField(
);
}

function createDelegatingResolver({
schema,
operation,
fieldName,
}: {
schema: GraphQLSchema | SchemaExecutionConfig,
operation: 'query' | 'mutation' | 'subscription',
fieldName: string,
}): IFieldResolver<any, any> {
return (root, args, context, info) => {
return delegateToSchema({
schema,
operation,
fieldName,
args,
context,
info,
});
};
}

function addTypeCandidate(
typeCandidates: { [name: string]: Array<MergeTypeCandidate> },
name: string,
Expand Down Expand Up @@ -395,13 +365,6 @@ function onTypeConflictToCandidateSelector(onTypeConflict: OnTypeConflict): Cand
});
}


function rootTypeNameToOperation(
name: 'Query' | 'Mutation' | 'Subscription'
): 'query' | 'mutation' | 'subscription' {
return name.toLowerCase() as 'query' | 'mutation' | 'subscription';
}

function operationToRootType(
operation: 'query' | 'mutation' | 'subscription',
schema: GraphQLSchema,
Expand All @@ -424,37 +387,14 @@ function mergeTypeCandidates(
candidateSelector = cands => cands[cands.length - 1];
}
if (name === 'Query' || name === 'Mutation' || name === 'Subscription') {
return mergeRootTypeCandidates(name, candidates);
return new GraphQLObjectType({
name,
fields: candidates.reduce((acc, candidate) => ({
...acc,
...(candidate.type as GraphQLObjectType).toConfig().fields,
}), {}),
});
} else {
const candidate = candidateSelector(candidates);
const type = cloneType(candidate.type);
makeMergedType(type);
return type;
return candidateSelector(candidates).type;
}
}

function mergeRootTypeCandidates(
name: 'Query' | 'Mutation' | 'Subscription',
candidates: Array<MergeTypeCandidate>
): GraphQLNamedType {
let operation = rootTypeNameToOperation(name);
let fields = {};
const resolverKey = operation === 'subscription' ? 'subscribe' : 'resolve';
candidates.forEach(candidate => {
const { type: candidateType, schema, executionConfig } = candidate;
const candidateFields = (candidateType as GraphQLObjectType).toConfig().fields;
Object.keys(candidateFields).forEach(fieldName => {
candidateFields[fieldName][resolverKey] =
schema ? createDelegatingResolver({
schema: executionConfig ? executionConfig : schema,
operation,
fieldName,
}) : null;
});
fields = { ...fields, ...candidateFields };
});
return new GraphQLObjectType({
name,
fields,
});
}
78 changes: 44 additions & 34 deletions src/test/testAlternateMergeSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
TransformObjectFields,
ExtendSchema,
WrapType,
FilterRootFields,
} from '../transforms';
import {
propertySchema,
Expand Down Expand Up @@ -127,39 +128,48 @@ describe('merge schemas through transforms', () => {
bookingSchemaExecConfig = await remoteBookingSchema;

// namespace and strip schemas
const transformedPropertySchema = filterSchema({
schema: transformSchema(propertySchema, [
new RenameTypes((name: string) => `Properties_${name}`),
new RenameRootFields((operation: string, name: string) => `Properties_${name}`),
]),
rootFieldFilter: (operation: string, rootField: string) =>
'Query.Properties_properties' === `${operation}.${rootField}`,
});
const transformedBookingSchema = filterSchema({
schema: transformSchema(bookingSchemaExecConfig, [
new RenameTypes((name: string) => `Bookings_${name}`),
new RenameRootFields((operation: string, name: string) => `Bookings_${name}`),
]),
rootFieldFilter: (operation: string, rootField: string) =>
'Query.Bookings_bookings' === `${operation}.${rootField}`
});
const transformedSubscriptionSchema = filterSchema({
schema: transformSchema(subscriptionSchema, [
new RenameTypes((name: string) => `Subscriptions_${name}`),
new RenameRootFields(
(operation: string, name: string) => `Subscriptions_${name}`),
]),
rootFieldFilter: (operation: string, rootField: string) =>
const propertySchemaTransforms = [
new FilterRootFields(
(operation: string, rootField: string) =>
'Query.properties' === `${operation}.${rootField}`
),
new RenameTypes((name: string) => `Properties_${name}`),
new RenameRootFields((operation: string, name: string) => `Properties_${name}`),
];
const bookingSchemaTransforms = [
new FilterRootFields(
(operation: string, rootField: string) =>
'Query.bookings' === `${operation}.${rootField}`
),
new RenameTypes((name: string) => `Bookings_${name}`),
new RenameRootFields((operation: string, name: string) => `Bookings_${name}`),
];
const subScriptionSchemaTransforms = [
new FilterRootFields(
(operation: string, rootField: string) =>
// must include a Query type otherwise graphql will error
'Query.Subscriptions_notifications' === `${operation}.${rootField}` ||
'Subscription.Subscriptions_notifications' === `${operation}.${rootField}`,
});
'Query.notifications' === `${operation}.${rootField}` ||
'Subscription.notifications' === `${operation}.${rootField}`
),
new RenameTypes((name: string) => `Subscriptions_${name}`),
new RenameRootFields(
(operation: string, name: string) => `Subscriptions_${name}`),
];

mergedSchema = mergeSchemas({
schemas: [
transformedPropertySchema,
transformedBookingSchema,
transformedSubscriptionSchema,
{
schema: propertySchema,
transforms: propertySchemaTransforms,
},
{
...bookingSchemaExecConfig,
transforms: bookingSchemaTransforms,
},
{
schema: subscriptionSchema,
transforms: subScriptionSchemaTransforms,
},
linkSchema,
],
resolvers: {
Expand All @@ -174,7 +184,7 @@ describe('merge schemas through transforms', () => {
args,
context,
info,
transforms: transformedPropertySchema.transforms,
transforms: propertySchemaTransforms,
});
} else if (args.id.startsWith('b')) {
return delegateToSchema({
Expand All @@ -184,7 +194,7 @@ describe('merge schemas through transforms', () => {
args,
context,
info,
transforms: transformedBookingSchema.transforms,
transforms: bookingSchemaTransforms,
});
} else if (args.id.startsWith('c')) {
return delegateToSchema({
Expand All @@ -194,7 +204,7 @@ describe('merge schemas through transforms', () => {
args,
context,
info,
transforms: transformedBookingSchema.transforms,
transforms: bookingSchemaTransforms,
});
} else {
throw new Error('invalid id');
Expand All @@ -215,7 +225,7 @@ describe('merge schemas through transforms', () => {
},
context,
info,
transforms: transformedBookingSchema.transforms,
transforms: bookingSchemaTransforms,
});
},
},
Expand All @@ -233,7 +243,7 @@ describe('merge schemas through transforms', () => {
},
context,
info,
transforms: transformedPropertySchema.transforms,
transforms: propertySchemaTransforms,
});
},
},
Expand Down

0 comments on commit 5efafbe

Please sign in to comment.