Skip to content
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

Merge lresolver objects in schema execution #612

Merged
merged 26 commits into from
Feb 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6587cc0
Allow passing an array of resolver functions to makeExecutable schema
Jan 11, 2018
454ff4b
Implement passing an array of resolvers sets to mergeSchemas
Jan 11, 2018
0aff303
Introduce unit or array composite type
Jan 11, 2018
9c13517
Fix @types dep
Jan 11, 2018
210a0fa
Merge branch 'master' into mergeObjectsInSchemaExecution
Jan 22, 2018
cb37c45
Merge branch 'master' into mergeObjectsInMergeSchemas
freiksenet Jan 23, 2018
cd71bbc
Merge branch 'master' into mergeObjectsInSchemaExecution
freiksenet Jan 23, 2018
9041bc2
Fix resolvers to accept and move forward args with zero or false valu…
renatobenks Jan 23, 2018
57152af
Also recreate astNode for fields (#580)
BassT Jan 23, 2018
66ddf7e
Address comments, remove lodash
Jan 23, 2018
e2391ff
Merge branch 'master' into mergeObjectsInSchemaExecution
Jan 23, 2018
de31c04
Fix @types dep
Jan 11, 2018
a6728b9
Fix resolvers to accept and move forward args with zero or false valu…
renatobenks Jan 23, 2018
008935b
Also recreate astNode for fields (#580)
BassT Jan 23, 2018
7977b06
Address comments, remove lodash
Jan 23, 2018
e8631fd
Use mergeDeep instead of lodash.merge
Jan 23, 2018
4ffe3a7
Merge branch 'master' into mergeObjectsInMergeSchemas
Jan 23, 2018
0c937ae
Fix build
Jan 23, 2018
b4cc076
Merge branch 'master' into mergeObjectsInSchemaExecution
Jan 24, 2018
48e8e49
Merge branch 'master' into mergeObjectsInSchemaExecution
freiksenet Feb 5, 2018
43f1297
Merge branch 'master' into mergeObjectsInMergeSchemas
freiksenet Feb 5, 2018
8704115
Merge branch 'master' into mergeObjectsInSchemaExecution
freiksenet Feb 5, 2018
9edfc6b
Changelog and separate utility
freiksenet Feb 5, 2018
1b78e09
Merge branch 'master' into mergeObjectsInMergeSchemas
freiksenet Feb 5, 2018
b7b60a1
Merge branch 'mergeObjectsInMergeSchemas' of git://github.com/mfix22/…
freiksenet Feb 5, 2018
a5a063e
Update CHANGELOG
freiksenet Feb 5, 2018
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Change log

### vNEXT
* `makeExecutableSchema` and `mergeSchema` now accept an array of `IResolver` [PR #612](https://github.com/apollographql/graphql-tools/pull/612) [PR #576](https://github.com/apollographql/graphql-tools/pull/576) [PR #577](https://github.com/apollographql/graphql-tools/pull/577)
* Fix `delegateToSchema.ts` to remove duplicate new variable definitions when delegating to schemas [PR #607](https://github.com/apollographql/graphql-tools/pull/607)
* Fix duplicate subscriptions for schema stitching [PR #609](https://github.com/apollographql/graphql-tools/pull/609)

Expand Down
3 changes: 2 additions & 1 deletion src/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {

/* TODO: Add documentation */

export type UnitOrList<Type> = Type | Array<Type>;
export interface IResolverValidationOptions {
requireResolversForArgs?: boolean;
requireResolversForNonScalar?: boolean;
Expand Down Expand Up @@ -71,7 +72,7 @@ export type IConnectors = { [key: string]: IConnector };

export interface IExecutableSchemaDefinition {
typeDefs: ITypeDefinitions;
resolvers?: IResolvers;
resolvers?: IResolvers | Array<IResolvers>;
connectors?: IConnectors;
logger?: ILogger;
allowUndefinedInResolve?: boolean;
Expand Down
21 changes: 21 additions & 0 deletions src/mergeDeep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export default function mergeDeep(target: any, source: any): any {
let output = Object.assign({}, target);
if (isObject(target) && isObject(source)) {
Object.keys(source).forEach(key => {
if (isObject(source[key])) {
if (!(key in target)) {
Object.assign(output, { [key]: source[key] });
} else {
output[key] = mergeDeep(target[key], source[key]);
}
} else {
Object.assign(output, { [key]: source[key] });
}
});
}
return output;
}

function isObject(item: any): Boolean {
return item && typeof item === 'object' && !Array.isArray(item);
}
26 changes: 17 additions & 9 deletions src/schemaGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@ import {
IConnectorCls,
IResolverValidationOptions,
IDirectiveResolvers,
UnitOrList,
} from './Interfaces';

import { deprecated } from 'deprecated-decorator';
import mergeDeep from './mergeDeep';

// @schemaDefinition: A GraphQL type schema in shorthand
// @resolvers: Definitions for resolvers to be merged with schema
Expand All @@ -59,7 +61,7 @@ class SchemaError extends Error {
// type definitions can be a string or an array of strings.
function _generateSchema(
typeDefinitions: ITypeDefinitions,
resolveFunctions: IResolvers,
resolveFunctions: UnitOrList<IResolvers>,
logger: ILogger,
// TODO: rename to allowUndefinedInResolve to be consistent
allowUndefinedInResolve: boolean,
Expand All @@ -78,15 +80,17 @@ function _generateSchema(
throw new SchemaError('Must provide resolvers');
}

const resolvers = Array.isArray(resolveFunctions)
? resolveFunctions
.filter(resolverObj => typeof resolverObj === 'object')
.reduce(mergeDeep, {})
: resolveFunctions;

// TODO: check that typeDefinitions is either string or array of strings

const schema = buildSchemaFromTypeDefinitions(typeDefinitions);

addResolveFunctionsToSchema(
schema,
resolveFunctions,
resolverValidationOptions,
);
addResolveFunctionsToSchema(schema, resolvers, resolverValidationOptions);

assertResolveFunctionsPresent(schema, resolverValidationOptions);

Expand Down Expand Up @@ -206,7 +210,10 @@ function buildSchemaFromTypeDefinitions(
const backcompatOptions = { commentDescriptions: true };

// TODO fix types https://github.com/apollographql/graphql-tools/issues/542
let schema: GraphQLSchema = (buildASTSchema as any)(astDocument, backcompatOptions);
let schema: GraphQLSchema = (buildASTSchema as any)(
astDocument,
backcompatOptions,
);

const extensionsAst = extractExtensionDefinitions(astDocument);
if (extensionsAst.definitions.length > 0) {
Expand All @@ -217,7 +224,6 @@ function buildSchemaFromTypeDefinitions(
return schema;
}


// This was changed in graphql@0.12
// See https://github.com/apollographql/graphql-tools/pull/541
// TODO fix types https://github.com/apollographql/graphql-tools/issues/542
Expand All @@ -226,7 +232,9 @@ const newExtensionDefinitionKind = 'ObjectTypeExtension';

export function extractExtensionDefinitions(ast: DocumentNode) {
const extensionDefs = ast.definitions.filter(
(def: DefinitionNode) => def.kind === oldTypeExtensionDefinitionKind || (def.kind as any) === newExtensionDefinitionKind,
(def: DefinitionNode) =>
def.kind === oldTypeExtensionDefinitionKind ||
(def.kind as any) === newExtensionDefinitionKind,
);

return Object.assign({}, ast, {
Expand Down
39 changes: 15 additions & 24 deletions src/stitching/mergeSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@ import {
parse,
} from 'graphql';
import TypeRegistry from './TypeRegistry';
import { IResolvers, MergeInfo, IFieldResolver } from '../Interfaces';
import {
IResolvers,
MergeInfo,
IFieldResolver,
UnitOrList,
} from '../Interfaces';
import isEmptyObject from '../isEmptyObject';
import mergeDeep from '../mergeDeep';
import {
extractExtensionDefinitions,
addResolveFunctionsToSchema,
Expand All @@ -42,7 +48,7 @@ export default function mergeSchemas({
left: GraphQLNamedType,
right: GraphQLNamedType,
) => GraphQLNamedType;
resolvers?: IResolvers | ((mergeInfo: MergeInfo) => IResolvers);
resolvers?: UnitOrList<IResolvers | ((mergeInfo: MergeInfo) => IResolvers)>;
}): GraphQLSchema {
if (!onTypeConflict) {
onTypeConflict = defaultOnTypeConflict;
Expand Down Expand Up @@ -177,6 +183,13 @@ export default function mergeSchemas({
if (resolvers) {
if (typeof resolvers === 'function') {
passedResolvers = resolvers(mergeInfo);
} else if (Array.isArray(resolvers)) {
passedResolvers = resolvers
.map(
resolver =>
typeof resolver === 'function' ? resolver(mergeInfo) : resolver,
)
.reduce(mergeDeep, {});
} else {
passedResolvers = { ...resolvers };
}
Expand Down Expand Up @@ -299,28 +312,6 @@ function createDelegatingResolver(
};
}

function isObject(item: any): Boolean {
return item && typeof item === 'object' && !Array.isArray(item);
}

function mergeDeep(target: any, source: any): any {
let output = Object.assign({}, target);
if (isObject(target) && isObject(source)) {
Object.keys(source).forEach(key => {
if (isObject(source[key])) {
if (!(key in target)) {
Object.assign(output, { [key]: source[key] });
} else {
output[key] = mergeDeep(target[key], source[key]);
}
} else {
Object.assign(output, { [key]: source[key] });
}
});
}
return output;
}

type FieldIteratorFn = (
fieldDef: GraphQLField<any, any>,
typeName: string,
Expand Down
196 changes: 196 additions & 0 deletions src/test/testMergeSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
} from './testingSchemas';
import { forAwaitEach } from 'iterall';
import { makeExecutableSchema } from '../schemaGenerator';
import { IResolvers } from '../Interfaces';

const testCombinations = [
{
Expand Down Expand Up @@ -924,6 +925,201 @@ bookingById(id: "b1") {
const Booking = mergedSchema.getType('Booking') as GraphQLObjectType;
expect(Booking.isTypeOf).to.equal(undefined);
});

it('should merge resolvers when passed an array of resolver objects', async () => {
const Scalars = () => ({
TestScalar: new GraphQLScalarType({
name: 'TestScalar',
description: undefined,
serialize: value => value,
parseValue: value => value,
parseLiteral: () => null,
})
});
const Enums = () => ({
NumericEnum: {
TEST: 1
},
Color: {
RED: '#EA3232',
}
});
const PropertyResolvers: IResolvers = {
Property: {
bookings: {
fragment: 'fragment PropertyFragment on Property { id }',
resolve(parent, args, context, info) {
return info.mergeInfo.delegate(
'query',
'bookingsByPropertyId',
{
propertyId: parent.id,
limit: args.limit ? args.limit : null,
},
context,
info,
);
}
}
}
};
const LinkResolvers: (info: any) => IResolvers = (info) => ({
Booking: {
property: {
fragment: 'fragment BookingFragment on Booking { propertyId }',
resolve(parent, args, context) {
return info.mergeInfo.delegate(
'query',
'propertyById',
{
id: parent.propertyId,
},
context,
info
);
}
}
}
});
const Query1 = () => ({
Query: {
color() {
return '#EA3232';
},
numericEnum() {
return 1;
}
}
});
const Query2: (info: any) => IResolvers = () => ({
Query: {
delegateInterfaceTest(parent, args, context, info) {
return info.mergeInfo.delegate(
'query',
'interfaceTest',
{
kind: 'ONE',
},
context,
info,
);
},
delegateArgumentTest(parent, args, context, info) {
return info.mergeInfo.delegate(
'query',
'propertyById',
{
id: 'p1',
},
context,
info,
);
},
linkTest() {
return {
test: 'test',
};
},
node: {
// fragment doesn't work
fragment: 'fragment NodeFragment on Node { id }',
resolve(parent, args, context, info) {
if (args.id.startsWith('p')) {
return info.mergeInfo.delegate(
'query',
'propertyById',
args,
context,
info,
);
} else if (args.id.startsWith('b')) {
return info.mergeInfo.delegate(
'query',
'bookingById',
args,
context,
info,
);
} else if (args.id.startsWith('c')) {
return info.mergeInfo.delegate(
'query',
'customerById',
args,
context,
info,
);
} else {
throw new Error('invalid id');
}
}
}
}
});

const AsyncQuery: (info: any) => IResolvers = (info) => ({
Query: {
async nodes(parent, args, context) {
const bookings = await info.mergeInfo.delegate(
'query',
'bookings',
{},
context,
info,
);
const properties = await info.mergeInfo.delegate(
'query',
'properties',
{},
context,
info,
);
return [...bookings, ...properties];
}
}
});
const schema = mergeSchemas({
schemas: [
propertySchema,
bookingSchema,
productSchema,
scalarTest,
enumTest,
linkSchema,
loneExtend,
localSubscriptionSchema,
],
resolvers: [
Scalars,
Enums,
PropertyResolvers,
LinkResolvers,
Query1,
Query2,
AsyncQuery
]
});

const mergedResult = await graphql(
schema,
`
query {
dateTimeTest
test1: jsonTest(input: { foo: "bar" })
test2: jsonTest(input: 5)
test3: jsonTest(input: "6")
}
`,
);
const expected = {
data: {
dateTimeTest: '1987-09-25T12:00:00',
test1: { foo: 'bar' },
test2: 5,
test3: '6'
}
};
expect(mergedResult).to.deep.equal(expected);
});
});

describe('fragments', () => {
Expand Down
Loading