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

Commit

Permalink
feat(mergeTypes): initial version
Browse files Browse the repository at this point in the history
  • Loading branch information
yaacovCR committed Nov 8, 2019
1 parent 1a77582 commit 8e9dd52
Show file tree
Hide file tree
Showing 9 changed files with 269 additions and 68 deletions.
19 changes: 17 additions & 2 deletions src/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,22 @@ export type SubschemaConfig = {
fetcher?: Fetcher;
dispatcher?: Dispatcher;
transforms?: Array<Transform>;
mergedTypeConfigs?: Record<string, MergedTypeConfig>;
};

export type MergedTypeConfig = {
fragment?: string;
mergedTypeResolver: MergedTypeResolver;
};

export type MergedTypeResolver = (
subschema: GraphQLSchema | SubschemaConfig,
parent: any,
args: Record<string, any>,
context: Record<string, any>,
info: IGraphQLToolsResolveInfo,
) => any;

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

export type SchemaLikeObject =
Expand Down Expand Up @@ -128,6 +142,7 @@ export type MergeInfo = {
field: string;
fragment: string;
}>;
mergedTypes: Record<string, Array<SubschemaConfig>>,
delegateToSchema<TContext>(options: IDelegateToSchemaOptions<TContext>): any;
};

Expand Down Expand Up @@ -240,10 +255,10 @@ export type OnTypeConflict = (
right: GraphQLNamedType,
info?: {
left: {
schema?: GraphQLSchema;
schema?: GraphQLSchema | SubschemaConfig;
};
right: {
schema?: GraphQLSchema;
schema?: GraphQLSchema | SubschemaConfig;
};
},
) => GraphQLNamedType;
Expand Down
17 changes: 12 additions & 5 deletions src/stitching/checkResultAndHandleErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,24 @@ import {
ExecutionResult,
GraphQLError,
GraphQLType,
GraphQLSchema,
} from 'graphql';
import { getResponseKeyFromInfo } from './getResponseKeyFromInfo';
import {
relocatedError,
combineErrors,
createMergedResult
} from './errors';
import {
SubschemaConfig,
IGraphQLToolsResolveInfo,
} from '../Interfaces';

export function checkResultAndHandleErrors(
result: ExecutionResult,
info: GraphQLResolveInfo,
responseKey?: string
responseKey?: string,
subschema?: GraphQLSchema | SubschemaConfig,
): any {
if (!responseKey) {
responseKey = getResponseKeyFromInfo(info);
Expand All @@ -29,20 +35,21 @@ export function checkResultAndHandleErrors(
return (result.errors) ? handleErrors(info, result.errors) : null;
}

return handleResult(info, result.data[responseKey], result.errors || []);
return handleResult(info, result.data[responseKey], result.errors || [], [subschema]);
}

export function handleResult(
info: GraphQLResolveInfo,
info: IGraphQLToolsResolveInfo,
result: any,
errors: ReadonlyArray<GraphQLError>
errors: ReadonlyArray<GraphQLError>,
subschemas: Array<GraphQLSchema | SubschemaConfig>,
): any {
const nullableType = getNullableType(info.returnType);

if (isLeafType(nullableType)) {
return nullableType.parseValue(result);
} else if (isCompositeType(nullableType)) {
return createMergedResult(result, errors);
return createMergedResult(result, errors, subschemas);
} else if (isListType(nullableType)) {
return createMergedResult(result, errors).map(
(r: any) => parseOutputValue(getNullableType(nullableType.ofType), r)
Expand Down
39 changes: 32 additions & 7 deletions src/stitching/defaultMergedResolver.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { GraphQLFieldResolver, defaultFieldResolver } from 'graphql';
import { getErrorsFromParent, MERGED_NULL_SYMBOL } from './errors';
import { defaultFieldResolver, getNamedType, ExecutionResult } from 'graphql';
import { getErrorsFromParent, getSubschemasFromParent, MERGED_NULL_SYMBOL } from './errors';
import { handleResult, handleErrors } from './checkResultAndHandleErrors';
import { getResponseKeyFromInfo } from './getResponseKeyFromInfo';
import { IGraphQLToolsResolveInfo } from '../Interfaces';

// Resolver that knows how to:
// a) handle aliases for proxied schemas
// b) handle errors from proxied schemas
// c) handle external to internal enum coversion
const defaultMergedResolver: GraphQLFieldResolver<any, any> = (parent, args, context, info) => {
export default async function defaultMergedResolver(
parent: Record<string, any>,
args: Record<string, any>,
context: Record<string, any>,
info: IGraphQLToolsResolveInfo,
) {
if (!parent) {
return null;
}
Expand All @@ -17,7 +23,7 @@ const defaultMergedResolver: GraphQLFieldResolver<any, any> = (parent, args, con

// check to see if parent is not a proxied result, i.e. if parent resolver was manually overwritten
// See https://github.com/apollographql/graphql-tools/issues/967
if (!Array.isArray(errors)) {
if (!errors) {
return defaultFieldResolver(parent, args, context, info);
}

Expand All @@ -27,7 +33,26 @@ const defaultMergedResolver: GraphQLFieldResolver<any, any> = (parent, args, con
return (errors.length) ? handleErrors(info, errors) : null;
}

return handleResult(info, result, errors);
};
const parentSubschemas = getSubschemasFromParent(parent);
const mergedResult = handleResult(info, result, errors, parentSubschemas);
if (info.mergeInfo) {
const typeName = getNamedType(info.returnType).name;
const initialSubschemas = info.mergeInfo.mergedTypes[typeName];
if (initialSubschemas) {
const remainingSubschemas = info.mergeInfo.mergedTypes[typeName].filter(
subschema => !parentSubschemas.includes(subschema)
);
if (remainingSubschemas.length) {
const additionalResults = await Promise.all(remainingSubschemas.map(subschema => {
const mergedTypeResolver = subschema.mergedTypeConfigs[typeName].mergedTypeResolver;
return mergedTypeResolver(subschema, parent, args, context, info);
}));
additionalResults.forEach((additionalResult: ExecutionResult) => {
Object.assign(result, additionalResult);
});
}
}
}

export default defaultMergedResolver;
return mergedResult;
}
10 changes: 5 additions & 5 deletions src/stitching/delegateToSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export default function delegateToSchema(
}

async function delegateToSchemaImplementation({
schema: schemaOrSubschemaConfig,
schema: subschema,
rootValue,
info,
operation = info.operation.operation,
Expand All @@ -71,13 +71,13 @@ async function delegateToSchemaImplementation({
let targetSchema: GraphQLSchema;
let subSchemaConfig: SubschemaConfig;

if (isSubschemaConfig(schemaOrSubschemaConfig)) {
subSchemaConfig = schemaOrSubschemaConfig;
if (isSubschemaConfig(subschema)) {
subSchemaConfig = subschema;
targetSchema = subSchemaConfig.schema;
rootValue = rootValue || subSchemaConfig.rootValue || info.rootValue;
transforms = transforms.concat((subSchemaConfig.transforms || []).slice().reverse());
} else {
targetSchema = schemaOrSubschemaConfig;
targetSchema = subschema;
rootValue = rootValue || info.rootValue;
}

Expand All @@ -98,7 +98,7 @@ async function delegateToSchemaImplementation({
};

transforms = [
new CheckResultAndHandleErrors(info, fieldName),
new CheckResultAndHandleErrors(info, fieldName, subschema),
...transforms,
new ExpandAbstractTypes(info.schema, targetSchema),
];
Expand Down
24 changes: 19 additions & 5 deletions src/stitching/errors.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import {
GraphQLError,
ASTNode
ASTNode,
GraphQLSchema
} from 'graphql';
import { SubschemaConfig } from '../Interfaces';

export let MERGED_NULL_SYMBOL: any;
export let SUBSCHEMAS_SYMBOL: any;
export let ERROR_SYMBOL: any;
if (
(typeof global !== 'undefined' && 'Symbol' in global) ||
(typeof window !== 'undefined' && 'Symbol' in window)
) {
MERGED_NULL_SYMBOL = Symbol('mergedNull');
ERROR_SYMBOL = Symbol('subSchemaErrors');
SUBSCHEMAS_SYMBOL = Symbol('subschemas');
ERROR_SYMBOL = Symbol('subschemaErrors');
} else {
MERGED_NULL_SYMBOL = '@@__mergedNull';
ERROR_SYMBOL = '@@__subSchemaErrors';
SUBSCHEMAS_SYMBOL = Symbol('subschemas');
ERROR_SYMBOL = '@@__subschemaErrors';
}

export function relocatedError(
Expand Down Expand Up @@ -43,7 +48,11 @@ export function relocatedError(
);
}

export function createMergedResult(object: any, childrenErrors: ReadonlyArray<GraphQLError> = []): any {
export function createMergedResult(
object: any,
childrenErrors: ReadonlyArray<GraphQLError> = [],
subschemas: Array<GraphQLSchema | SubschemaConfig> = [],
): any {
if (object == null) {
object = {
[MERGED_NULL_SYMBOL]: true,
Expand Down Expand Up @@ -71,7 +80,7 @@ export function createMergedResult(object: any, childrenErrors: ReadonlyArray<Gr
byIndex[index] = current;
});

object = object.map((item, index) => createMergedResult(item, byIndex[index]));
object = object.map((item, index) => createMergedResult(item, byIndex[index]), subschemas);

return object;
}
Expand All @@ -84,6 +93,7 @@ export function createMergedResult(object: any, childrenErrors: ReadonlyArray<Gr
);
return newError;
});
object[SUBSCHEMAS_SYMBOL] = subschemas;

return object;
}
Expand All @@ -92,6 +102,10 @@ export function isParentProxiedResult(parent: any) {
return parent && parent[ERROR_SYMBOL];
}

export function getSubschemasFromParent(object: any): Array<GraphQLSchema | SubschemaConfig> {
return object && object[SUBSCHEMAS_SYMBOL];
}

export function getErrorsFromParent(
object: any,
fieldName: string
Expand Down
67 changes: 41 additions & 26 deletions src/stitching/mergeSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import {
type MergeTypeCandidate = {
type: GraphQLNamedType;
schema?: GraphQLSchema;
subschema?: GraphQLSchema | SubschemaConfig;
};

type CandidateSelector = (
Expand All @@ -57,6 +58,7 @@ export default function mergeSchemas({
types = [],
typeDefs,
schemas: schemaLikeObjects = [],
mergeTypes = [],
onTypeConflict,
resolvers,
schemaDirectives,
Expand All @@ -67,6 +69,7 @@ export default function mergeSchemas({
types?: Array<GraphQLNamedType>;
typeDefs?: string | DocumentNode;
schemas?: Array<SchemaLikeObject>;
mergeTypes?: Array<string>;
onTypeConflict?: OnTypeConflict;
resolvers?: IResolversParameter;
schemaDirectives?: { [name: string]: typeof SchemaDirectiveVisitor };
Expand Down Expand Up @@ -108,6 +111,7 @@ export default function mergeSchemas({
addTypeCandidate(typeCandidates, typeName, {
schema,
type: operationTypes[typeName],
subschema: schemaLikeObject,
});
}
});
Expand All @@ -132,6 +136,7 @@ export default function mergeSchemas({
addTypeCandidate(typeCandidates, type.name, {
schema,
type,
subschema: schemaLikeObject,
});
}
});
Expand Down Expand Up @@ -169,7 +174,18 @@ export default function mergeSchemas({
}
});

const mergeInfo = createMergeInfo(allSchemas, fragments);
const mergedTypes = {};

mergeTypes.forEach(typeName => {
if (typeCandidates[typeName]) {
mergedTypes[typeName] =
typeCandidates[typeName].map(typeCandidate => typeCandidate.subschema);
} else {
throw new Error(`Cannot merge type '${typeName}', type not found.`);
}
});

const mergeInfo = createMergeInfo(allSchemas, fragments, mergedTypes);

if (!resolvers) {
resolvers = {};
Expand All @@ -191,11 +207,19 @@ export default function mergeSchemas({
}

Object.keys(typeCandidates).forEach(typeName => {
typeMap[typeName] = mergeTypeCandidates(
typeName,
typeCandidates[typeName],
onTypeConflict ? onTypeConflictToCandidateSelector(onTypeConflict) : undefined
);
if (
typeName === 'Query' ||
typeName === 'Mutation' ||
typeName === 'Subscription' ||
mergeTypes.includes(typeName)
) {
typeMap[typeName] = mergeFields(typeName, typeCandidates[typeName]);
} else {
const candidateSelector = onTypeConflict ?
onTypeConflictToCandidateSelector(onTypeConflict) :
(cands: Array<MergeTypeCandidate>) => cands[cands.length - 1];
typeMap[typeName] = candidateSelector(typeCandidates[typeName]).type;
}
});

healTypes(typeMap, directives, { skipPruning: true });
Expand Down Expand Up @@ -279,6 +303,7 @@ function createMergeInfo(
field: string;
fragment: string;
}>,
mergedTypes: Record<string, Array<SubschemaConfig>>,
): MergeInfo {
return {
delegate(
Expand Down Expand Up @@ -317,7 +342,8 @@ function createMergeInfo(
transforms: options.transforms
});
},
fragments
fragments,
mergedTypes,
};
}

Expand Down Expand Up @@ -388,23 +414,12 @@ function operationToRootType(
}
}

function mergeTypeCandidates(
name: string,
candidates: Array<MergeTypeCandidate>,
candidateSelector?: CandidateSelector
): GraphQLNamedType {
if (!candidateSelector) {
candidateSelector = cands => cands[cands.length - 1];
}
if (name === 'Query' || name === 'Mutation' || name === 'Subscription') {
return new GraphQLObjectType({
name,
fields: candidates.reduce((acc, candidate) => ({
...acc,
...(candidate.type as GraphQLObjectType).toConfig().fields,
}), {}),
});
} else {
return candidateSelector(candidates).type;
}
function mergeFields(typeName: string, candidates: Array<MergeTypeCandidate>): GraphQLNamedType {
return new GraphQLObjectType({
name: typeName,
fields: candidates.reduce((acc, candidate) => ({
...acc,
...(candidate.type as GraphQLObjectType).toConfig().fields,
}), {}),
});
}
Loading

0 comments on commit 8e9dd52

Please sign in to comment.