/
makeExecutableSchema.ts
130 lines (108 loc) · 3.73 KB
/
makeExecutableSchema.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import { defaultFieldResolver, GraphQLSchema, GraphQLFieldResolver } from 'graphql';
import { IExecutableSchemaDefinition, ILogger } from './Interfaces';
import { SchemaDirectiveVisitor } from './schemaVisitor';
import mergeDeep from './mergeDeep';
import {
attachDirectiveResolvers,
assertResolveFunctionsPresent,
addResolveFunctionsToSchema,
attachConnectorsToContext,
addSchemaLevelResolveFunction,
buildSchemaFromTypeDefinitions,
decorateWithLogger,
forEachField,
SchemaError
} from './generate';
export function makeExecutableSchema<TContext = any>({
typeDefs,
resolvers = {},
connectors,
logger,
allowUndefinedInResolve = true,
resolverValidationOptions = {},
directiveResolvers = null,
schemaDirectives = null,
parseOptions = {},
inheritResolversFromInterfaces = false
}: IExecutableSchemaDefinition<TContext>) {
// Validate and clean up arguments
if (typeof resolverValidationOptions !== 'object') {
throw new SchemaError('Expected `resolverValidationOptions` to be an object');
}
if (!typeDefs) {
throw new SchemaError('Must provide typeDefs');
}
if (!resolvers) {
throw new SchemaError('Must provide resolvers');
}
// We allow passing in an array of resolver maps, in which case we merge them
const resolverMap = Array.isArray(resolvers)
? resolvers.filter(resolverObj => typeof resolverObj === 'object').reduce(mergeDeep, {})
: resolvers;
// Arguments are now validated and cleaned up
let schema = buildSchemaFromTypeDefinitions(typeDefs, parseOptions);
schema = addResolveFunctionsToSchema({
schema,
resolvers: resolverMap,
resolverValidationOptions,
inheritResolversFromInterfaces
});
assertResolveFunctionsPresent(schema, resolverValidationOptions);
if (!allowUndefinedInResolve) {
addCatchUndefinedToSchema(schema);
}
if (logger) {
addErrorLoggingToSchema(schema, logger);
}
if (typeof resolvers['__schema'] === 'function') {
// TODO a bit of a hack now, better rewrite generateSchema to attach it there.
// not doing that now, because I'd have to rewrite a lot of tests.
addSchemaLevelResolveFunction(schema, resolvers['__schema'] as GraphQLFieldResolver<any, any>);
}
if (connectors) {
// connectors are optional, at least for now. That means you can just import them in the resolve
// function if you want.
attachConnectorsToContext(schema, connectors);
}
if (directiveResolvers) {
attachDirectiveResolvers(schema, directiveResolvers);
}
if (schemaDirectives) {
SchemaDirectiveVisitor.visitSchemaDirectives(schema, schemaDirectives);
}
return schema;
}
function decorateToCatchUndefined(
fn: GraphQLFieldResolver<any, any>,
hint: string
): GraphQLFieldResolver<any, any> {
if (typeof fn === 'undefined') {
fn = defaultFieldResolver;
}
return (root, args, ctx, info) => {
const result = fn(root, args, ctx, info);
if (typeof result === 'undefined') {
throw new Error(`Resolve function for "${hint}" returned undefined`);
}
return result;
};
}
export function addCatchUndefinedToSchema(schema: GraphQLSchema): void {
forEachField(schema, (field, typeName, fieldName) => {
const errorHint = `${typeName}.${fieldName}`;
field.resolve = decorateToCatchUndefined(field.resolve, errorHint);
});
}
export function addErrorLoggingToSchema(schema: GraphQLSchema, logger: ILogger): void {
if (!logger) {
throw new Error('Must provide a logger');
}
if (typeof logger.log !== 'function') {
throw new Error('Logger.log must be a function');
}
forEachField(schema, (field, typeName, fieldName) => {
const errorHint = `${typeName}.${fieldName}`;
field.resolve = decorateWithLogger(field.resolve, logger, errorHint);
});
}
export * from './generate';