UNPKG

38.7 kBJavaScriptView Raw
1import { defaultFieldResolver, isScalarType, getNamedType, Kind, print, buildASTSchema, extendSchema, isSchema, isSpecifiedScalarType, isEnumType, isUnionType, isObjectType, isInterfaceType, GraphQLEnumType, GraphQLScalarType, GraphQLUnionType, GraphQLObjectType, GraphQLInterfaceType } from 'graphql';
2import { mapSchema, MapperKind, forEachField, getDirectives, parseGraphQLSDL, isDocumentNode, forEachDefaultValue, serializeInputValue, healSchema, parseInputValue, mergeDeep, SchemaDirectiveVisitor, pruneSchema } from '@graphql-tools/utils';
3
4// wraps all resolvers of query, mutation or subscription fields
5// with the provided function to simulate a root schema level resolver
6function addSchemaLevelResolver(schema, fn) {
7 // TODO test that schema is a schema, fn is a function
8 const fnToRunOnlyOnce = runAtMostOncePerRequest(fn);
9 return mapSchema(schema, {
10 [MapperKind.ROOT_FIELD]: (fieldConfig, _fieldName, typeName, schema) => {
11 // XXX this should run at most once per request to simulate a true root resolver
12 // for graphql-js this is an approximation that works with queries but not mutations
13 // XXX if the type is a subscription, a same query AST will be ran multiple times so we
14 // deactivate here the runOnce if it's a subscription. This may not be optimal though...
15 const subscription = schema.getSubscriptionType();
16 if (subscription != null && subscription.name === typeName) {
17 return {
18 ...fieldConfig,
19 resolve: wrapResolver(fieldConfig.resolve, fn),
20 };
21 }
22 return {
23 ...fieldConfig,
24 resolve: wrapResolver(fieldConfig.resolve, fnToRunOnlyOnce),
25 };
26 },
27 });
28}
29// XXX badly named function. this doesn't really wrap, it just chains resolvers...
30function wrapResolver(innerResolver, outerResolver) {
31 return (obj, args, ctx, info) => resolveMaybePromise(outerResolver(obj, args, ctx, info), root => {
32 if (innerResolver != null) {
33 return innerResolver(root, args, ctx, info);
34 }
35 return defaultFieldResolver(root, args, ctx, info);
36 });
37}
38function isPromise(maybePromise) {
39 return maybePromise && typeof maybePromise.then === 'function';
40}
41// resolvers can be synchronous or asynchronous. if all resolvers
42// in an operation return synchronously, the execution should return
43// synchronously. the maybe-sync/maybe-async nature of resolvers should be
44// preserved
45function resolveMaybePromise(maybePromise, fulfillmentCallback) {
46 if (isPromise(maybePromise)) {
47 return maybePromise.then(fulfillmentCallback);
48 }
49 return fulfillmentCallback(maybePromise);
50}
51// XXX this function only works for resolvers
52// XXX very hacky way to remember if the function
53// already ran for this request. This will only work
54// if people don't actually cache the operation.
55// if they do cache the operation, they will have to
56// manually remove the __runAtMostOnce before every request.
57function runAtMostOncePerRequest(fn) {
58 let value;
59 const randomNumber = Math.random();
60 return (root, args, ctx, info) => {
61 if (!info.operation['__runAtMostOnce']) {
62 info.operation['__runAtMostOnce'] = {};
63 }
64 if (!info.operation['__runAtMostOnce'][randomNumber]) {
65 info.operation['__runAtMostOnce'][randomNumber] = true;
66 value = fn(root, args, ctx, info);
67 }
68 return value;
69 };
70}
71
72function assertResolversPresent(schema, resolverValidationOptions = {}) {
73 const { requireResolversForArgs, requireResolversForNonScalar, requireResolversForAllFields, } = resolverValidationOptions;
74 if (requireResolversForAllFields && (requireResolversForArgs || requireResolversForNonScalar)) {
75 throw new TypeError('requireResolversForAllFields takes precedence over the more specific assertions. ' +
76 'Please configure either requireResolversForAllFields or requireResolversForArgs / ' +
77 'requireResolversForNonScalar, but not a combination of them.');
78 }
79 forEachField(schema, (field, typeName, fieldName) => {
80 // requires a resolver for *every* field.
81 if (requireResolversForAllFields) {
82 expectResolver('requireResolversForAllFields', requireResolversForAllFields, field, typeName, fieldName);
83 }
84 // requires a resolver on every field that has arguments
85 if (requireResolversForArgs && field.args.length > 0) {
86 expectResolver('requireResolversForArgs', requireResolversForArgs, field, typeName, fieldName);
87 }
88 // requires a resolver on every field that returns a non-scalar type
89 if (requireResolversForNonScalar !== 'ignore' && !isScalarType(getNamedType(field.type))) {
90 expectResolver('requireResolversForNonScalar', requireResolversForNonScalar, field, typeName, fieldName);
91 }
92 });
93}
94function expectResolver(validator, behavior, field, typeName, fieldName) {
95 if (!field.resolve) {
96 const message = `Resolver missing for "${typeName}.${fieldName}".
97To disable this validator, use:
98 resolverValidationOptions: {
99 ${validator}: 'ignore'
100 }`;
101 if (behavior === 'error') {
102 throw new Error(message);
103 }
104 if (behavior === 'warn') {
105 // eslint-disable-next-line no-console
106 console.warn(message);
107 }
108 return;
109 }
110 if (typeof field.resolve !== 'function') {
111 throw new Error(`Resolver "${typeName}.${fieldName}" must be a function`);
112 }
113}
114
115function attachDirectiveResolvers(schema, directiveResolvers) {
116 if (typeof directiveResolvers !== 'object') {
117 throw new Error(`Expected directiveResolvers to be of type object, got ${typeof directiveResolvers}`);
118 }
119 if (Array.isArray(directiveResolvers)) {
120 throw new Error('Expected directiveResolvers to be of type object, got Array');
121 }
122 return mapSchema(schema, {
123 [MapperKind.OBJECT_FIELD]: fieldConfig => {
124 const newFieldConfig = { ...fieldConfig };
125 const directives = getDirectives(schema, fieldConfig);
126 Object.keys(directives).forEach(directiveName => {
127 if (directiveResolvers[directiveName]) {
128 const resolver = directiveResolvers[directiveName];
129 const originalResolver = newFieldConfig.resolve != null ? newFieldConfig.resolve : defaultFieldResolver;
130 const directiveArgs = directives[directiveName];
131 newFieldConfig.resolve = (source, originalArgs, context, info) => {
132 return resolver(() => new Promise((resolve, reject) => {
133 const result = originalResolver(source, originalArgs, context, info);
134 if (result instanceof Error) {
135 reject(result);
136 }
137 resolve(result);
138 }), source, directiveArgs, context, info);
139 };
140 }
141 });
142 return newFieldConfig;
143 },
144 });
145}
146
147const isExtensionNode = (def) => def.kind === Kind.OBJECT_TYPE_EXTENSION ||
148 def.kind === Kind.INTERFACE_TYPE_EXTENSION ||
149 def.kind === Kind.INPUT_OBJECT_TYPE_EXTENSION ||
150 def.kind === Kind.UNION_TYPE_EXTENSION ||
151 def.kind === Kind.ENUM_TYPE_EXTENSION ||
152 def.kind === Kind.SCALAR_TYPE_EXTENSION ||
153 def.kind === Kind.SCHEMA_EXTENSION;
154function filterAndExtractExtensionDefinitions(ast) {
155 const extensionDefs = [];
156 const typesDefs = [];
157 ast.definitions.forEach(def => {
158 if (isExtensionNode(def)) {
159 extensionDefs.push(def);
160 }
161 else {
162 typesDefs.push(def);
163 }
164 });
165 return {
166 typesAst: {
167 ...ast,
168 definitions: typesDefs,
169 },
170 extensionsAst: {
171 ...ast,
172 definitions: extensionDefs,
173 },
174 };
175}
176function filterExtensionDefinitions(ast) {
177 const { typesAst } = filterAndExtractExtensionDefinitions(ast);
178 return typesAst;
179}
180function extractExtensionDefinitions(ast) {
181 const { extensionsAst } = filterAndExtractExtensionDefinitions(ast);
182 return extensionsAst;
183}
184
185function concatenateTypeDefs(typeDefinitionsAry, calledFunctionRefs = new Set()) {
186 const resolvedTypeDefinitions = new Set();
187 typeDefinitionsAry.forEach((typeDef) => {
188 if (typeof typeDef === 'function') {
189 if (!calledFunctionRefs.has(typeDef)) {
190 calledFunctionRefs.add(typeDef);
191 resolvedTypeDefinitions.add(concatenateTypeDefs(typeDef(), calledFunctionRefs));
192 }
193 }
194 else if (typeof typeDef === 'string') {
195 resolvedTypeDefinitions.add(typeDef.trim());
196 }
197 else if (typeDef.kind !== undefined) {
198 resolvedTypeDefinitions.add(print(typeDef).trim());
199 }
200 else {
201 const type = typeof typeDef;
202 throw new Error(`typeDef array must contain only strings, documents, or functions, got ${type}`);
203 }
204 });
205 return [...resolvedTypeDefinitions].join('\n');
206}
207
208function buildSchemaFromTypeDefinitions(typeDefinitions, parseOptions, noExtensionExtraction) {
209 const document = buildDocumentFromTypeDefinitions(typeDefinitions, parseOptions);
210 if (noExtensionExtraction) {
211 return buildASTSchema(document);
212 }
213 const { typesAst, extensionsAst } = filterAndExtractExtensionDefinitions(document);
214 const backcompatOptions = { commentDescriptions: true };
215 let schema = buildASTSchema(typesAst, backcompatOptions);
216 if (extensionsAst.definitions.length > 0) {
217 schema = extendSchema(schema, extensionsAst, backcompatOptions);
218 }
219 return schema;
220}
221function buildDocumentFromTypeDefinitions(typeDefinitions, parseOptions) {
222 let document;
223 if (typeof typeDefinitions === 'string') {
224 document = parseGraphQLSDL('', typeDefinitions, parseOptions).document;
225 }
226 else if (Array.isArray(typeDefinitions)) {
227 document = parseGraphQLSDL('', concatenateTypeDefs(typeDefinitions), parseOptions).document;
228 }
229 else if (isDocumentNode(typeDefinitions)) {
230 document = typeDefinitions;
231 }
232 else {
233 const type = typeof typeDefinitions;
234 throw new Error(`typeDefs must be a string, array or schema AST, got ${type}`);
235 }
236 return document;
237}
238
239function chainResolvers(resolvers) {
240 return (root, args, ctx, info) => resolvers.reduce((prev, curResolver) => {
241 if (curResolver != null) {
242 return curResolver(prev, args, ctx, info);
243 }
244 return defaultFieldResolver(prev, args, ctx, info);
245 }, root);
246}
247
248/*
249 * fn: The function to decorate with the logger
250 * logger: an object instance of type Logger
251 * hint: an optional hint to add to the error's message
252 */
253function decorateWithLogger(fn, logger, hint) {
254 const resolver = fn != null ? fn : defaultFieldResolver;
255 const logError = (e) => {
256 // TODO: clone the error properly
257 const newE = new Error();
258 newE.stack = e.stack;
259 /* istanbul ignore else: always get the hint from addErrorLoggingToSchema */
260 if (hint) {
261 newE['originalMessage'] = e.message;
262 newE.message = `Error in resolver ${hint}\n${e.message}`;
263 }
264 logger.log(newE);
265 };
266 return (root, args, ctx, info) => {
267 try {
268 const result = resolver(root, args, ctx, info);
269 // If the resolver returns a Promise log any Promise rejects.
270 if (result && typeof result.then === 'function' && typeof result.catch === 'function') {
271 result.catch((reason) => {
272 // make sure that it's an error we're logging.
273 const error = reason instanceof Error ? reason : new Error(reason);
274 logError(error);
275 // We don't want to leave an unhandled exception so pass on error.
276 return reason;
277 });
278 }
279 return result;
280 }
281 catch (e) {
282 logError(e);
283 // we want to pass on the error, just in case.
284 throw e;
285 }
286 };
287}
288
289// If we have any union or interface types throw if no there is no resolveType resolver
290function checkForResolveTypeResolver(schema, requireResolversForResolveType) {
291 mapSchema(schema, {
292 [MapperKind.ABSTRACT_TYPE]: type => {
293 if (!type.resolveType) {
294 const message = `Type "${type.name}" is missing a "__resolveType" resolver. Pass 'ignore' into ` +
295 '"resolverValidationOptions.requireResolversForResolveType" to disable this error.';
296 if (requireResolversForResolveType === 'error') {
297 throw new Error(message);
298 }
299 if (requireResolversForResolveType === 'warn') {
300 // eslint-disable-next-line no-console
301 console.warn(message);
302 }
303 }
304 return undefined;
305 },
306 });
307}
308
309function extendResolversFromInterfaces(schema, resolvers) {
310 const typeNames = Object.keys({
311 ...schema.getTypeMap(),
312 ...resolvers,
313 });
314 const extendedResolvers = {};
315 typeNames.forEach(typeName => {
316 const type = schema.getType(typeName);
317 if ('getInterfaces' in type) {
318 const allInterfaceResolvers = type
319 .getInterfaces()
320 .map(iFace => resolvers[iFace.name])
321 .filter(interfaceResolvers => interfaceResolvers != null);
322 extendedResolvers[typeName] = {};
323 allInterfaceResolvers.forEach(interfaceResolvers => {
324 Object.keys(interfaceResolvers).forEach(fieldName => {
325 if (fieldName === '__isTypeOf' || !fieldName.startsWith('__')) {
326 extendedResolvers[typeName][fieldName] = interfaceResolvers[fieldName];
327 }
328 });
329 });
330 const typeResolvers = resolvers[typeName];
331 extendedResolvers[typeName] = {
332 ...extendedResolvers[typeName],
333 ...typeResolvers,
334 };
335 }
336 else {
337 const typeResolvers = resolvers[typeName];
338 if (typeResolvers != null) {
339 extendedResolvers[typeName] = typeResolvers;
340 }
341 }
342 });
343 return extendedResolvers;
344}
345
346function addResolversToSchema(schemaOrOptions, legacyInputResolvers, legacyInputValidationOptions) {
347 const options = isSchema(schemaOrOptions)
348 ? {
349 schema: schemaOrOptions,
350 resolvers: legacyInputResolvers,
351 resolverValidationOptions: legacyInputValidationOptions,
352 }
353 : schemaOrOptions;
354 let { schema, resolvers: inputResolvers, defaultFieldResolver, resolverValidationOptions = {}, inheritResolversFromInterfaces = false, updateResolversInPlace = false, } = options;
355 const { requireResolversToMatchSchema = 'error', requireResolversForResolveType } = resolverValidationOptions;
356 const resolvers = inheritResolversFromInterfaces
357 ? extendResolversFromInterfaces(schema, inputResolvers)
358 : inputResolvers;
359 Object.keys(resolvers).forEach(typeName => {
360 const resolverValue = resolvers[typeName];
361 const resolverType = typeof resolverValue;
362 if (typeName === '__schema') {
363 if (resolverType !== 'function') {
364 throw new Error(`"${typeName}" defined in resolvers, but has invalid value "${resolverValue}". A schema resolver's value must be of type object or function.`);
365 }
366 }
367 else {
368 if (resolverType !== 'object') {
369 throw new Error(`"${typeName}" defined in resolvers, but has invalid value "${resolverValue}". The resolver's value must be of type object.`);
370 }
371 const type = schema.getType(typeName);
372 if (type == null) {
373 if (requireResolversToMatchSchema === 'ignore') {
374 return;
375 }
376 throw new Error(`"${typeName}" defined in resolvers, but not in schema`);
377 }
378 else if (isSpecifiedScalarType(type)) {
379 // allow -- without recommending -- overriding of specified scalar types
380 Object.keys(resolverValue).forEach(fieldName => {
381 if (fieldName.startsWith('__')) {
382 type[fieldName.substring(2)] = resolverValue[fieldName];
383 }
384 else {
385 type[fieldName] = resolverValue[fieldName];
386 }
387 });
388 }
389 else if (isEnumType(type)) {
390 const values = type.getValues();
391 Object.keys(resolverValue).forEach(fieldName => {
392 if (!fieldName.startsWith('__') &&
393 !values.some(value => value.name === fieldName) &&
394 requireResolversToMatchSchema &&
395 requireResolversToMatchSchema !== 'ignore') {
396 throw new Error(`${type.name}.${fieldName} was defined in resolvers, but not present within ${type.name}`);
397 }
398 });
399 }
400 else if (isUnionType(type)) {
401 Object.keys(resolverValue).forEach(fieldName => {
402 if (!fieldName.startsWith('__') &&
403 requireResolversToMatchSchema &&
404 requireResolversToMatchSchema !== 'ignore') {
405 throw new Error(`${type.name}.${fieldName} was defined in resolvers, but ${type.name} is not an object or interface type`);
406 }
407 });
408 }
409 else if (isObjectType(type) || isInterfaceType(type)) {
410 Object.keys(resolverValue).forEach(fieldName => {
411 if (!fieldName.startsWith('__')) {
412 const fields = type.getFields();
413 const field = fields[fieldName];
414 if (field == null && requireResolversToMatchSchema && requireResolversToMatchSchema !== 'ignore') {
415 throw new Error(`${typeName}.${fieldName} defined in resolvers, but not in schema`);
416 }
417 const fieldResolve = resolverValue[fieldName];
418 if (typeof fieldResolve !== 'function' && typeof fieldResolve !== 'object') {
419 throw new Error(`Resolver ${typeName}.${fieldName} must be object or function`);
420 }
421 }
422 });
423 }
424 }
425 });
426 schema = updateResolversInPlace
427 ? addResolversToExistingSchema(schema, resolvers, defaultFieldResolver)
428 : createNewSchemaWithResolvers(schema, resolvers, defaultFieldResolver);
429 if (requireResolversForResolveType || requireResolversForResolveType !== 'ignore') {
430 checkForResolveTypeResolver(schema, requireResolversForResolveType);
431 }
432 return schema;
433}
434function addResolversToExistingSchema(schema, resolvers, defaultFieldResolver) {
435 const typeMap = schema.getTypeMap();
436 Object.keys(resolvers).forEach(typeName => {
437 if (typeName !== '__schema') {
438 const type = schema.getType(typeName);
439 const resolverValue = resolvers[typeName];
440 if (isScalarType(type)) {
441 Object.keys(resolverValue).forEach(fieldName => {
442 var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
443 if (fieldName.startsWith('__')) {
444 type[fieldName.substring(2)] = resolverValue[fieldName];
445 }
446 else if (fieldName === 'astNode' && type.astNode != null) {
447 type.astNode = {
448 ...type.astNode,
449 description: (_c = (_b = (_a = resolverValue) === null || _a === void 0 ? void 0 : _a.astNode) === null || _b === void 0 ? void 0 : _b.description) !== null && _c !== void 0 ? _c : type.astNode.description,
450 directives: ((_d = type.astNode.directives) !== null && _d !== void 0 ? _d : []).concat((_g = (_f = (_e = resolverValue) === null || _e === void 0 ? void 0 : _e.astNode) === null || _f === void 0 ? void 0 : _f.directives) !== null && _g !== void 0 ? _g : []),
451 };
452 }
453 else if (fieldName === 'extensionASTNodes' && type.extensionASTNodes != null) {
454 type.extensionASTNodes = ((_h = []) !== null && _h !== void 0 ? _h : type.extensionASTNodes).concat((_k = (_j = resolverValue) === null || _j === void 0 ? void 0 : _j.extensionASTNodes) !== null && _k !== void 0 ? _k : []);
455 }
456 else if (fieldName === 'extensions' &&
457 type.extensions != null &&
458 resolverValue.extensions != null) {
459 type.extensions = Object.assign({}, type.extensions, resolverValue.extensions);
460 }
461 else {
462 type[fieldName] = resolverValue[fieldName];
463 }
464 });
465 }
466 else if (isEnumType(type)) {
467 const config = type.toConfig();
468 const enumValueConfigMap = config.values;
469 Object.keys(resolverValue).forEach(fieldName => {
470 var _a, _b, _c, _d, _e, _f, _g, _h, _j;
471 if (fieldName.startsWith('__')) {
472 config[fieldName.substring(2)] = resolverValue[fieldName];
473 }
474 else if (fieldName === 'astNode' && config.astNode != null) {
475 config.astNode = {
476 ...config.astNode,
477 description: (_c = (_b = (_a = resolverValue) === null || _a === void 0 ? void 0 : _a.astNode) === null || _b === void 0 ? void 0 : _b.description) !== null && _c !== void 0 ? _c : config.astNode.description,
478 directives: ((_d = config.astNode.directives) !== null && _d !== void 0 ? _d : []).concat((_g = (_f = (_e = resolverValue) === null || _e === void 0 ? void 0 : _e.astNode) === null || _f === void 0 ? void 0 : _f.directives) !== null && _g !== void 0 ? _g : []),
479 };
480 }
481 else if (fieldName === 'extensionASTNodes' && config.extensionASTNodes != null) {
482 config.extensionASTNodes = config.extensionASTNodes.concat((_j = (_h = resolverValue) === null || _h === void 0 ? void 0 : _h.extensionASTNodes) !== null && _j !== void 0 ? _j : []);
483 }
484 else if (fieldName === 'extensions' &&
485 type.extensions != null &&
486 resolverValue.extensions != null) {
487 type.extensions = Object.assign({}, type.extensions, resolverValue.extensions);
488 }
489 else if (enumValueConfigMap[fieldName]) {
490 enumValueConfigMap[fieldName].value = resolverValue[fieldName];
491 }
492 });
493 typeMap[typeName] = new GraphQLEnumType(config);
494 }
495 else if (isUnionType(type)) {
496 Object.keys(resolverValue).forEach(fieldName => {
497 if (fieldName.startsWith('__')) {
498 type[fieldName.substring(2)] = resolverValue[fieldName];
499 }
500 });
501 }
502 else if (isObjectType(type) || isInterfaceType(type)) {
503 Object.keys(resolverValue).forEach(fieldName => {
504 if (fieldName.startsWith('__')) {
505 // this is for isTypeOf and resolveType and all the other stuff.
506 type[fieldName.substring(2)] = resolverValue[fieldName];
507 return;
508 }
509 const fields = type.getFields();
510 const field = fields[fieldName];
511 if (field != null) {
512 const fieldResolve = resolverValue[fieldName];
513 if (typeof fieldResolve === 'function') {
514 // for convenience. Allows shorter syntax in resolver definition file
515 field.resolve = fieldResolve;
516 }
517 else {
518 setFieldProperties(field, fieldResolve);
519 }
520 }
521 });
522 }
523 }
524 });
525 // serialize all default values prior to healing fields with new scalar/enum types.
526 forEachDefaultValue(schema, serializeInputValue);
527 // schema may have new scalar/enum types that require healing
528 healSchema(schema);
529 // reparse all default values with new parsing functions.
530 forEachDefaultValue(schema, parseInputValue);
531 if (defaultFieldResolver != null) {
532 forEachField(schema, field => {
533 if (!field.resolve) {
534 field.resolve = defaultFieldResolver;
535 }
536 });
537 }
538 return schema;
539}
540function createNewSchemaWithResolvers(schema, resolvers, defaultFieldResolver) {
541 schema = mapSchema(schema, {
542 [MapperKind.SCALAR_TYPE]: type => {
543 const config = type.toConfig();
544 const resolverValue = resolvers[type.name];
545 if (!isSpecifiedScalarType(type) && resolverValue != null) {
546 Object.keys(resolverValue).forEach(fieldName => {
547 var _a, _b, _c, _d, _e, _f, _g, _h, _j;
548 if (fieldName.startsWith('__')) {
549 config[fieldName.substring(2)] = resolverValue[fieldName];
550 }
551 else if (fieldName === 'astNode' && config.astNode != null) {
552 config.astNode = {
553 ...config.astNode,
554 description: (_c = (_b = (_a = resolverValue) === null || _a === void 0 ? void 0 : _a.astNode) === null || _b === void 0 ? void 0 : _b.description) !== null && _c !== void 0 ? _c : config.astNode.description,
555 directives: ((_d = config.astNode.directives) !== null && _d !== void 0 ? _d : []).concat((_g = (_f = (_e = resolverValue) === null || _e === void 0 ? void 0 : _e.astNode) === null || _f === void 0 ? void 0 : _f.directives) !== null && _g !== void 0 ? _g : []),
556 };
557 }
558 else if (fieldName === 'extensionASTNodes' && config.extensionASTNodes != null) {
559 config.extensionASTNodes = config.extensionASTNodes.concat((_j = (_h = resolverValue) === null || _h === void 0 ? void 0 : _h.extensionASTNodes) !== null && _j !== void 0 ? _j : []);
560 }
561 else if (fieldName === 'extensions' &&
562 config.extensions != null &&
563 resolverValue.extensions != null) {
564 config.extensions = Object.assign({}, type.extensions, resolverValue.extensions);
565 }
566 else {
567 config[fieldName] = resolverValue[fieldName];
568 }
569 });
570 return new GraphQLScalarType(config);
571 }
572 },
573 [MapperKind.ENUM_TYPE]: type => {
574 const resolverValue = resolvers[type.name];
575 const config = type.toConfig();
576 const enumValueConfigMap = config.values;
577 if (resolverValue != null) {
578 Object.keys(resolverValue).forEach(fieldName => {
579 var _a, _b, _c, _d, _e, _f, _g, _h, _j;
580 if (fieldName.startsWith('__')) {
581 config[fieldName.substring(2)] = resolverValue[fieldName];
582 }
583 else if (fieldName === 'astNode' && config.astNode != null) {
584 config.astNode = {
585 ...config.astNode,
586 description: (_c = (_b = (_a = resolverValue) === null || _a === void 0 ? void 0 : _a.astNode) === null || _b === void 0 ? void 0 : _b.description) !== null && _c !== void 0 ? _c : config.astNode.description,
587 directives: ((_d = config.astNode.directives) !== null && _d !== void 0 ? _d : []).concat((_g = (_f = (_e = resolverValue) === null || _e === void 0 ? void 0 : _e.astNode) === null || _f === void 0 ? void 0 : _f.directives) !== null && _g !== void 0 ? _g : []),
588 };
589 }
590 else if (fieldName === 'extensionASTNodes' && config.extensionASTNodes != null) {
591 config.extensionASTNodes = config.extensionASTNodes.concat((_j = (_h = resolverValue) === null || _h === void 0 ? void 0 : _h.extensionASTNodes) !== null && _j !== void 0 ? _j : []);
592 }
593 else if (fieldName === 'extensions' &&
594 config.extensions != null &&
595 resolverValue.extensions != null) {
596 config.extensions = Object.assign({}, type.extensions, resolverValue.extensions);
597 }
598 else if (enumValueConfigMap[fieldName]) {
599 enumValueConfigMap[fieldName].value = resolverValue[fieldName];
600 }
601 });
602 return new GraphQLEnumType(config);
603 }
604 },
605 [MapperKind.UNION_TYPE]: type => {
606 const resolverValue = resolvers[type.name];
607 if (resolverValue != null) {
608 const config = type.toConfig();
609 Object.keys(resolverValue).forEach(fieldName => {
610 if (fieldName.startsWith('__')) {
611 config[fieldName.substring(2)] = resolverValue[fieldName];
612 }
613 });
614 return new GraphQLUnionType(config);
615 }
616 },
617 [MapperKind.OBJECT_TYPE]: type => {
618 const resolverValue = resolvers[type.name];
619 if (resolverValue != null) {
620 const config = type.toConfig();
621 Object.keys(resolverValue).forEach(fieldName => {
622 if (fieldName.startsWith('__')) {
623 config[fieldName.substring(2)] = resolverValue[fieldName];
624 }
625 });
626 return new GraphQLObjectType(config);
627 }
628 },
629 [MapperKind.INTERFACE_TYPE]: type => {
630 const resolverValue = resolvers[type.name];
631 if (resolverValue != null) {
632 const config = type.toConfig();
633 Object.keys(resolverValue).forEach(fieldName => {
634 if (fieldName.startsWith('__')) {
635 config[fieldName.substring(2)] = resolverValue[fieldName];
636 }
637 });
638 return new GraphQLInterfaceType(config);
639 }
640 },
641 [MapperKind.COMPOSITE_FIELD]: (fieldConfig, fieldName, typeName) => {
642 const resolverValue = resolvers[typeName];
643 if (resolverValue != null) {
644 const fieldResolve = resolverValue[fieldName];
645 if (fieldResolve != null) {
646 const newFieldConfig = { ...fieldConfig };
647 if (typeof fieldResolve === 'function') {
648 // for convenience. Allows shorter syntax in resolver definition file
649 newFieldConfig.resolve = fieldResolve;
650 }
651 else {
652 setFieldProperties(newFieldConfig, fieldResolve);
653 }
654 return newFieldConfig;
655 }
656 }
657 },
658 });
659 if (defaultFieldResolver != null) {
660 schema = mapSchema(schema, {
661 [MapperKind.OBJECT_FIELD]: fieldConfig => ({
662 ...fieldConfig,
663 resolve: fieldConfig.resolve != null ? fieldConfig.resolve : defaultFieldResolver,
664 }),
665 });
666 }
667 return schema;
668}
669function setFieldProperties(field, propertiesObj) {
670 Object.keys(propertiesObj).forEach(propertyName => {
671 field[propertyName] = propertiesObj[propertyName];
672 });
673}
674
675function addErrorLoggingToSchema(schema, logger) {
676 if (!logger) {
677 throw new Error('Must provide a logger');
678 }
679 if (typeof logger.log !== 'function') {
680 throw new Error('Logger.log must be a function');
681 }
682 return mapSchema(schema, {
683 [MapperKind.OBJECT_FIELD]: (fieldConfig, fieldName, typeName) => ({
684 ...fieldConfig,
685 resolve: decorateWithLogger(fieldConfig.resolve, logger, `${typeName}.${fieldName}`),
686 }),
687 });
688}
689
690function decorateToCatchUndefined(fn, hint) {
691 const resolve = fn == null ? defaultFieldResolver : fn;
692 return (root, args, ctx, info) => {
693 const result = resolve(root, args, ctx, info);
694 if (typeof result === 'undefined') {
695 throw new Error(`Resolver for "${hint}" returned undefined`);
696 }
697 return result;
698 };
699}
700function addCatchUndefinedToSchema(schema) {
701 return mapSchema(schema, {
702 [MapperKind.OBJECT_FIELD]: (fieldConfig, fieldName, typeName) => ({
703 ...fieldConfig,
704 resolve: decorateToCatchUndefined(fieldConfig.resolve, `${typeName}.${fieldName}`),
705 }),
706 });
707}
708
709/**
710 * Builds a schema from the provided type definitions and resolvers.
711 *
712 * The type definitions are written using Schema Definition Language (SDL). They
713 * can be provided as a string, a `DocumentNode`, a function, or an array of any
714 * of these. If a function is provided, it will be passed no arguments and
715 * should return an array of strings or `DocumentNode`s.
716 *
717 * Note: You can use `graphql-tag` to not only parse a string into a
718 * `DocumentNode` but also to provide additional syntax highlighting in your
719 * editor (with the appropriate editor plugin).
720 *
721 * ```js
722 * const typeDefs = gql`
723 * type Query {
724 * posts: [Post]
725 * author(id: Int!): Author
726 * }
727 * `;
728 * ```
729 *
730 * The `resolvers` object should be a map of type names to nested object, which
731 * themselves map the type's fields to their appropriate resolvers.
732 * See the [Resolvers](/docs/resolvers) section of the documentation for more details.
733 *
734 * ```js
735 * const resolvers = {
736 * Query: {
737 * posts: (obj, args, ctx, info) => getAllPosts(),
738 * author: (obj, args, ctx, info) => getAuthorById(args.id)
739 * }
740 * };
741 * ```
742 *
743 * Once you've defined both the `typeDefs` and `resolvers`, you can create your
744 * schema:
745 *
746 * ```js
747 * const schema = makeExecutableSchema({
748 * typeDefs,
749 * resolvers,
750 * })
751 * ```
752 */
753function makeExecutableSchema({ typeDefs, resolvers = {}, logger, allowUndefinedInResolve = true, resolverValidationOptions = {}, directiveResolvers, schemaDirectives, schemaTransforms: userProvidedSchemaTransforms, parseOptions = {}, inheritResolversFromInterfaces = false, pruningOptions, updateResolversInPlace = false, noExtensionExtraction = false, }) {
754 // Validate and clean up arguments
755 if (typeof resolverValidationOptions !== 'object') {
756 throw new Error('Expected `resolverValidationOptions` to be an object');
757 }
758 if (!typeDefs) {
759 throw new Error('Must provide typeDefs');
760 }
761 // Arguments are now validated and cleaned up
762 const schemaTransforms = [
763 schema => {
764 // We allow passing in an array of resolver maps, in which case we merge them
765 const resolverMap = Array.isArray(resolvers) ? resolvers.reduce(mergeDeep, {}) : resolvers;
766 const schemaWithResolvers = addResolversToSchema({
767 schema,
768 resolvers: resolverMap,
769 resolverValidationOptions,
770 inheritResolversFromInterfaces,
771 updateResolversInPlace,
772 });
773 if (Object.keys(resolverValidationOptions).length > 0) {
774 assertResolversPresent(schemaWithResolvers, resolverValidationOptions);
775 }
776 return schemaWithResolvers;
777 },
778 ];
779 if (!allowUndefinedInResolve) {
780 schemaTransforms.push(addCatchUndefinedToSchema);
781 }
782 if (logger != null) {
783 schemaTransforms.push(schema => addErrorLoggingToSchema(schema, logger));
784 }
785 if (typeof resolvers['__schema'] === 'function') {
786 // TODO a bit of a hack now, better rewrite generateSchema to attach it there.
787 // not doing that now, because I'd have to rewrite a lot of tests.
788 schemaTransforms.push(schema => addSchemaLevelResolver(schema, resolvers['__schema']));
789 }
790 if (userProvidedSchemaTransforms) {
791 schemaTransforms.push(schema => userProvidedSchemaTransforms.reduce((s, schemaTransform) => schemaTransform(s), schema));
792 }
793 // directive resolvers are implemented using SchemaDirectiveVisitor.visitSchemaDirectives
794 // schema visiting modifies the schema in place
795 if (directiveResolvers != null) {
796 schemaTransforms.push(schema => attachDirectiveResolvers(schema, directiveResolvers));
797 }
798 if (schemaDirectives != null) {
799 schemaTransforms.push(schema => {
800 SchemaDirectiveVisitor.visitSchemaDirectives(schema, schemaDirectives);
801 return schema;
802 });
803 }
804 if (pruningOptions) {
805 schemaTransforms.push(pruneSchema);
806 }
807 const schemaFromTypeDefs = buildSchemaFromTypeDefinitions(typeDefs, parseOptions, noExtensionExtraction);
808 return schemaTransforms.reduce((schema, schemaTransform) => schemaTransform(schema), schemaFromTypeDefs);
809}
810
811export { addCatchUndefinedToSchema, addErrorLoggingToSchema, addResolversToSchema, addSchemaLevelResolver, assertResolversPresent, attachDirectiveResolvers, buildDocumentFromTypeDefinitions, buildSchemaFromTypeDefinitions, chainResolvers, checkForResolveTypeResolver, concatenateTypeDefs, decorateWithLogger, extendResolversFromInterfaces, extractExtensionDefinitions, filterAndExtractExtensionDefinitions, filterExtensionDefinitions, makeExecutableSchema };
812//# sourceMappingURL=index.esm.js.map