UNPKG

15 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.attachConnectorsToContext = exports.addSchemaLevelResolveFunction = exports.buildSchemaFromTypeDefinitions = exports.addTracingToResolvers = exports.assertResolveFunctionsPresent = exports.addCatchUndefinedToSchema = exports.addResolveFunctionsToSchema = exports.addErrorLoggingToSchema = exports.forEachField = exports.SchemaError = exports.makeExecutableSchema = exports.generateSchema = undefined;
7
8var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; // Generates a schema for graphql-js given a shorthand schema
9
10// TODO: document each function clearly in the code: what arguments it accepts
11// and what it outputs.
12
13var _language = require('graphql/language');
14
15var _lodash = require('lodash');
16
17var _utilities = require('graphql/utilities');
18
19var _type = require('graphql/type');
20
21var _tracing = require('./tracing');
22
23// @schemaDefinition: A GraphQL type schema in shorthand
24// @resolvers: Definitions for resolvers to be merged with schema
25function SchemaError(message) {
26 Error.captureStackTrace(this, this.constructor);
27 this.message = message;
28}
29SchemaError.prototype = new Error();
30
31function generateSchema() {
32 console.error('generateSchema is deprecated, use makeExecutableSchema instead');
33 return _generateSchema.apply(undefined, arguments);
34}
35
36// type definitions can be a string or an array of strings.
37function _generateSchema(typeDefinitions, resolveFunctions, logger) {
38 var allowUndefinedInResolve = arguments.length <= 3 || arguments[3] === undefined ? true : arguments[3];
39
40 if (!typeDefinitions) {
41 throw new SchemaError('Must provide typeDefinitions');
42 }
43 if (!resolveFunctions) {
44 throw new SchemaError('Must provide resolveFunctions');
45 }
46
47 // TODO: check that typeDefinitions is either string or array of strings
48
49 var schema = buildSchemaFromTypeDefinitions(typeDefinitions);
50
51 addResolveFunctionsToSchema(schema, resolveFunctions);
52
53 assertResolveFunctionsPresent(schema);
54
55 if (!allowUndefinedInResolve) {
56 addCatchUndefinedToSchema(schema);
57 }
58
59 if (logger) {
60 addErrorLoggingToSchema(schema, logger);
61 }
62
63 return schema;
64}
65
66function makeExecutableSchema(_ref) {
67 var typeDefs = _ref.typeDefs;
68 var resolvers = _ref.resolvers;
69 var connectors = _ref.connectors;
70 var logger = _ref.logger;
71 var _ref$allowUndefinedIn = _ref.allowUndefinedInResolve;
72 var allowUndefinedInResolve = _ref$allowUndefinedIn === undefined ? false : _ref$allowUndefinedIn;
73
74 var jsSchema = _generateSchema(typeDefs, resolvers, logger, allowUndefinedInResolve);
75 if (typeof resolvers.__schema === 'function') {
76 // TODO a bit of a hack now, better rewrite generateSchema to attach it there.
77 // not doing that now, because I'd have to rewrite a lot of tests.
78 addSchemaLevelResolveFunction(jsSchema, resolvers.__schema);
79 }
80 if (connectors) {
81 // connectors are optional, at least for now. That means you can just import them in the resolve
82 // function if you want.
83 attachConnectorsToContext(jsSchema, connectors);
84 }
85 return jsSchema;
86}
87
88function concatenateTypeDefs(typeDefinitionsAry) {
89 var functionsCalled = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
90
91 var resolvedTypeDefinitions = [];
92 typeDefinitionsAry.forEach(function (typeDef) {
93 if (typeof typeDef === 'function') {
94 if (!(typeDef in functionsCalled)) {
95 // eslint-disable-next-line no-param-reassign
96 functionsCalled[typeDef] = 1;
97 resolvedTypeDefinitions = resolvedTypeDefinitions.concat(concatenateTypeDefs(typeDef(), functionsCalled));
98 }
99 } else {
100 if (typeof typeDef === 'string') {
101 resolvedTypeDefinitions.push(typeDef.trim());
102 } else {
103 var type = typeof typeDef === 'undefined' ? 'undefined' : _typeof(typeDef);
104 throw new SchemaError('typeDef array must contain only strings and functions, got ' + type);
105 }
106 }
107 });
108 return (0, _lodash.uniq)(resolvedTypeDefinitions.map(function (x) {
109 return x.trim();
110 })).join('\n');
111}
112
113function buildSchemaFromTypeDefinitions(typeDefinitions) {
114 // TODO: accept only array here, otherwise interfaces get confusing.
115 var myDefinitions = typeDefinitions;
116 if (typeof myDefinitions !== 'string') {
117 if (!Array.isArray(myDefinitions)) {
118 // TODO improve error message and say what type was actually found
119 throw new SchemaError('`typeDefinitions` must be a string or array');
120 }
121 myDefinitions = concatenateTypeDefs(myDefinitions);
122 }
123 return (0, _utilities.buildASTSchema)((0, _language.parse)(myDefinitions));
124}
125
126function forEachField(schema, fn) {
127 var typeMap = schema.getTypeMap();
128 Object.keys(typeMap).forEach(function (typeName) {
129 var type = typeMap[typeName];
130
131 if (!(0, _type.getNamedType)(type).name.startsWith('__') && type instanceof _type.GraphQLObjectType) {
132 (function () {
133 var fields = type.getFields();
134 Object.keys(fields).forEach(function (fieldName) {
135 var field = fields[fieldName];
136 fn(field, typeName, fieldName);
137 });
138 })();
139 }
140 });
141}
142
143// takes a GraphQL-JS schema and an object of connectors, then attaches
144// the connectors to the context by wrapping each query or mutation resolve
145// function with a function that attaches connectors if they don't exist.
146// attaches connectors only once to make sure they are singletons
147function attachConnectorsToContext(schema, connectors) {
148 if (!schema || !(schema instanceof _type.GraphQLSchema)) {
149 throw new Error('schema must be an instance of GraphQLSchema. ' + 'This error could be caused by installing more than one version of GraphQL-JS');
150 }
151
152 if ((typeof connectors === 'undefined' ? 'undefined' : _typeof(connectors)) !== 'object') {
153 var connectorType = typeof connectors === 'undefined' ? 'undefined' : _typeof(connectors);
154 throw new Error('Expected connectors to be of type object, got ' + connectorType);
155 }
156 if (Object.keys(connectors).length === 0) {
157 throw new Error('Expected connectors to not be an empty object');
158 }
159 if (Array.isArray(connectors)) {
160 throw new Error('Expected connectors to be of type object, got Array');
161 }
162 if (schema._apolloConnectorsAttached) {
163 throw new Error('Connectors already attached to context, cannot attach more than once');
164 }
165 // eslint-disable-next-line no-param-reassign
166 schema._apolloConnectorsAttached = true;
167 var attachconnectorFn = function attachconnectorFn(root, args, ctx) {
168 if ((typeof ctx === 'undefined' ? 'undefined' : _typeof(ctx)) !== 'object') {
169 // if in any way possible, we should throw an error when the attachconnectors
170 // function is called, not when a query is executed.
171 var contextType = typeof ctx === 'undefined' ? 'undefined' : _typeof(ctx);
172 throw new Error('Cannot attach connector because context is not an object: ' + contextType);
173 }
174 if (typeof ctx.connectors === 'undefined') {
175 // eslint-disable-next-line no-param-reassign
176 ctx.connectors = {};
177 }
178 Object.keys(connectors).forEach(function (connectorName) {
179 // eslint-disable-next-line no-param-reassign
180 ctx.connectors[connectorName] = new connectors[connectorName](ctx);
181 });
182 return root;
183 };
184 addSchemaLevelResolveFunction(schema, attachconnectorFn);
185}
186
187// wraps all resolve functions of query, mutation or subscription fields
188// with the provided function to simulate a root schema level resolve funciton
189function addSchemaLevelResolveFunction(schema, fn) {
190 // TODO test that schema is a schema, fn is a function
191 var rootTypes = [schema.getQueryType(), schema.getMutationType(), schema.getSubscriptionType()].filter(function (x) {
192 return !!x;
193 });
194 var rootResolveFn = runAtMostOnce(fn);
195 rootTypes.forEach(function (type) {
196 var fields = type.getFields();
197 Object.keys(fields).forEach(function (fieldName) {
198 fields[fieldName].resolve = wrapResolver(fields[fieldName].resolve, rootResolveFn);
199 });
200 });
201}
202
203function addResolveFunctionsToSchema(schema, resolveFunctions) {
204 Object.keys(resolveFunctions).forEach(function (typeName) {
205 var type = schema.getType(typeName);
206 if (!type && typeName !== '__schema') {
207 throw new SchemaError('"' + typeName + '" defined in resolvers, but not in schema');
208 }
209
210 Object.keys(resolveFunctions[typeName]).forEach(function (fieldName) {
211 if (fieldName.startsWith('__')) {
212 // this is for isTypeOf and resolveType and all the other stuff.
213 // TODO require resolveType for unions and interfaces.
214 type[fieldName.substring(2)] = resolveFunctions[typeName][fieldName];
215 return;
216 }
217
218 if (!type.getFields()[fieldName]) {
219 throw new SchemaError(typeName + '.' + fieldName + ' defined in resolvers, but not in schema');
220 }
221 var field = type.getFields()[fieldName];
222 var fieldResolve = resolveFunctions[typeName][fieldName];
223 if (typeof fieldResolve === 'function') {
224 // for convenience. Allows shorter syntax in resolver definition file
225 setFieldProperties(field, { resolve: fieldResolve });
226 } else {
227 if ((typeof fieldResolve === 'undefined' ? 'undefined' : _typeof(fieldResolve)) !== 'object') {
228 throw new SchemaError('Resolver ' + typeName + '.' + fieldName + ' must be object or function');
229 }
230 setFieldProperties(field, fieldResolve);
231 }
232 });
233 });
234}
235
236function setFieldProperties(field, propertiesObj) {
237 Object.keys(propertiesObj).forEach(function (propertyName) {
238 // eslint-disable-next-line no-param-reassign
239 field[propertyName] = propertiesObj[propertyName];
240 });
241}
242
243function assertResolveFunctionsPresent(schema) {
244 forEachField(schema, function (field, typeName, fieldName) {
245 // requires a resolve function on every field that has arguments
246 if (field.args.length > 0) {
247 expectResolveFunction(field, typeName, fieldName);
248 }
249
250 // requires a resolve function on every field that returns a non-scalar type
251 if (!((0, _type.getNamedType)(field.type) instanceof _type.GraphQLScalarType)) {
252 expectResolveFunction(field, typeName, fieldName);
253 }
254 });
255}
256
257function expectResolveFunction(field, typeName, fieldName) {
258 if (!field.resolve) {
259 throw new SchemaError('Resolve function missing for "' + typeName + '.' + fieldName + '"');
260 }
261 if (typeof field.resolve !== 'function') {
262 throw new SchemaError('Resolver "' + typeName + '.' + fieldName + '" must be a function');
263 }
264}
265
266function addErrorLoggingToSchema(schema, logger) {
267 if (!logger) {
268 throw new Error('Must provide a logger');
269 }
270 if (typeof logger.log !== 'function') {
271 throw new Error('Logger.log must be a function');
272 }
273 forEachField(schema, function (field, typeName, fieldName) {
274 var errorHint = typeName + '.' + fieldName;
275 // eslint-disable-next-line no-param-reassign
276 field.resolve = decorateWithLogger(field.resolve, logger, errorHint);
277 });
278}
279
280function wrapResolver(innerResolver, outerResolver) {
281 return function (obj, args, ctx, info) {
282 var root = outerResolver(obj, args, ctx, info);
283 if (innerResolver) {
284 return innerResolver(root, args, ctx, info);
285 }
286 return defaultResolveFn(root, args, ctx, info);
287 };
288}
289/*
290 * fn: The function to decorate with the logger
291 * logger: an object instance of type Logger
292 * hint: an optional hint to add to the error's message
293 */
294function decorateWithLogger(fn, logger) {
295 var hint = arguments.length <= 2 || arguments[2] === undefined ? '' : arguments[2];
296
297 if (typeof fn === 'undefined') {
298 // eslint-disable-next-line no-param-reassign
299 fn = defaultResolveFn;
300 }
301 return function () {
302 try {
303 return fn.apply(undefined, arguments);
304 } catch (e) {
305 // TODO: clone the error properly
306 var newE = new Error();
307 newE.stack = e.stack;
308 if (hint) {
309 newE.originalMessage = e.message;
310 newE.message = 'Error in resolver ' + hint + '\n' + e.message;
311 }
312 logger.log(newE);
313 // we want to pass on the error, just in case.
314 throw e;
315 }
316 };
317}
318
319function addCatchUndefinedToSchema(schema) {
320 forEachField(schema, function (field, typeName, fieldName) {
321 var errorHint = typeName + '.' + fieldName;
322 // eslint-disable-next-line no-param-reassign
323 field.resolve = decorateToCatchUndefined(field.resolve, errorHint);
324 });
325}
326
327function addTracingToResolvers(schema, tracer) {
328 forEachField(schema, function (field, typeName, fieldName) {
329 var functionName = typeName + '.' + fieldName;
330 // eslint-disable-next-line no-param-reassign
331 field.resolve = (0, _tracing.decorateWithTracer)(field.resolve, tracer, { functionType: 'resolve', functionName: functionName });
332 });
333}
334
335function decorateToCatchUndefined(fn, hint) {
336 if (typeof fn === 'undefined') {
337 // eslint-disable-next-line no-param-reassign
338 fn = defaultResolveFn;
339 }
340 return function () {
341 var result = fn.apply(undefined, arguments);
342 if (typeof result === 'undefined') {
343 throw new Error('Resolve function for "' + hint + '" returned undefined');
344 }
345 return result;
346 };
347}
348
349function runAtMostOnce(fn) {
350 var count = 0;
351 var value = void 0;
352 return function () {
353 if (count === 0) {
354 value = fn.apply(undefined, arguments);
355 count += 1;
356 }
357 return value;
358 };
359}
360
361/**
362 * XXX taken from graphql-js: src/execution/execute.js, because that function
363 * is not exported
364 *
365 * If a resolve function is not given, then a default resolve behavior is used
366 * which takes the property of the source object of the same name as the field
367 * and returns it as the result, or if it's a function, returns the result
368 * of calling that function.
369 */
370function defaultResolveFn(source, args, context, _ref2) {
371 var fieldName = _ref2.fieldName;
372
373 // ensure source is a value for which property access is acceptable.
374 if ((typeof source === 'undefined' ? 'undefined' : _typeof(source)) === 'object' || typeof source === 'function') {
375 var property = source[fieldName];
376 return typeof property === 'function' ? source[fieldName]() : property;
377 }
378}
379
380exports.generateSchema = generateSchema;
381exports. // TODO deprecated, remove for v 0.4.x
382makeExecutableSchema = makeExecutableSchema;
383exports.SchemaError = SchemaError;
384exports.forEachField = forEachField;
385exports.addErrorLoggingToSchema = addErrorLoggingToSchema;
386exports.addResolveFunctionsToSchema = addResolveFunctionsToSchema;
387exports.addCatchUndefinedToSchema = addCatchUndefinedToSchema;
388exports.assertResolveFunctionsPresent = assertResolveFunctionsPresent;
389exports.addTracingToResolvers = addTracingToResolvers;
390exports.buildSchemaFromTypeDefinitions = buildSchemaFromTypeDefinitions;
391exports.addSchemaLevelResolveFunction = addSchemaLevelResolveFunction;
392exports.attachConnectorsToContext = attachConnectorsToContext;
\No newline at end of file