UNPKG

graphql

Version:

A Query Language and Runtime which can target any service.

506 lines (446 loc) 17.8 kB
/* @flow */ /** * Copyright (c) 2015, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ 'use strict'; /** * Terminology * * "Definitions" are the generic name for top-level statements in the document. * Examples of this include: * 1) Operations (such as a query) * 2) Fragments * * "Operations" are a generic name for requests in the document. * Examples of this include: * 1) query, * 2) mutation * * "Selections" are the statements that can appear legally and at * single level of the query. These include: * 1) field references e.g "a" * 2) fragment "spreads" e.g. "...c" * 3) inline fragment "spreads" e.g. "...on Type { a }" */ /** * Data that must be available at all points during query execution. * * Namely, schema of the type system that is currently executing, * and the fragments defined in the query document */ /** * The result of exeution. `data` is the result of executing the * query, `errors` is null if no errors occurred, and is a * non-empty array if an error occurred. */ var _Object$defineProperty = require('babel-runtime/core-js/object/define-property')['default']; var _Promise = require('babel-runtime/core-js/promise')['default']; var _Object$keys = require('babel-runtime/core-js/object/keys')['default']; var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default']; _Object$defineProperty(exports, '__esModule', { value: true }); exports.execute = execute; var _error = require('../error'); var _utilsInvariant = require('../utils/invariant'); var _utilsInvariant2 = _interopRequireDefault(_utilsInvariant); var _utilsTypeFromAST = require('../utils/typeFromAST'); var _utilsTypeFromAST2 = _interopRequireDefault(_utilsTypeFromAST); var _utilsIsNullish = require('../utils/isNullish'); var _utilsIsNullish2 = _interopRequireDefault(_utilsIsNullish); var _language = require('../language'); var _values = require('./values'); var _typeDefinition = require('../type/definition'); var _typeIntrospection = require('../type/introspection'); var _typeDirectives = require('../type/directives'); /** * Implements the "Evaluating requests" section of the spec. */ function execute(schema, root, ast, operationName, args) { (0, _utilsInvariant2['default'])(schema, 'Must provide schema'); var errors = []; return new _Promise(function (resolve) { var exeContext = buildExecutionContext(schema, root, ast, operationName, args, errors); resolve(executeOperation(exeContext, root, exeContext.operation)); })['catch'](function (error) { errors.push(error); return null; }).then(function (data) { if (!errors.length) { return { data: data }; } return { data: data, errors: errors.map(_error.formatError) }; }); } /** * Constructs a ExecutionContext object from the arguments passed to * execute, which we will pass throughout the other execution methods. */ function buildExecutionContext(schema, root, ast, operationName, args, errors) { var operations = {}; var fragments = {}; ast.definitions.forEach(function (statement) { switch (statement.kind) { case _language.Kind.OPERATION_DEFINITION: operations[statement.name ? statement.name.value : ''] = statement; break; case _language.Kind.FRAGMENT_DEFINITION: fragments[statement.name.value] = statement; break; } }); if (!operationName && _Object$keys(operations).length !== 1) { throw new _error.GraphQLError('Must provide operation name if query contains multiple operations'); } var opName = operationName || _Object$keys(operations)[0]; var operation = operations[opName]; if (!operation) { throw new _error.GraphQLError('Unknown operation name: ' + opName); } var variables = (0, _values.getVariableValues)(schema, operation.variableDefinitions || [], args || {}); var exeContext = { schema: schema, fragments: fragments, root: root, operation: operation, variables: variables, errors: errors }; return exeContext; } /** * Implements the "Evaluating operations" section of the spec. */ function executeOperation(exeContext, root, operation) { var type = getOperationRootType(exeContext.schema, operation); var fields = collectFields(exeContext, type, operation.selectionSet, {}, {}); if (operation.operation === 'mutation') { return executeFieldsSerially(exeContext, type, root, fields); } return executeFields(exeContext, type, root, fields); } /** * Extracts the root type of the operation from the schema. */ function getOperationRootType(schema, operation) { switch (operation.operation) { case 'query': return schema.getQueryType(); case 'mutation': var mutationType = schema.getMutationType(); if (!mutationType) { throw new _error.GraphQLError('Schema is not configured for mutations', [operation]); } return mutationType; default: throw new _error.GraphQLError('Can only execute queries and mutations', [operation]); } } /** * Implements the "Evaluating selection sets" section of the spec * for "write" mode. */ function executeFieldsSerially(exeContext, parentType, source, fields) { return _Object$keys(fields).reduce(function (prevPromise, responseName) { return prevPromise.then(function (results) { var fieldASTs = fields[responseName]; var result = resolveField(exeContext, parentType, source, fieldASTs); if (result === undefined) { return results; } if (isThenable(result)) { return result.then(function (resolvedResult) { results[responseName] = resolvedResult; return results; }); } else { results[responseName] = result; } return results; }); }, _Promise.resolve({})); } /** * Implements the "Evaluating selection sets" section of the spec * for "read" mode. */ function executeFields(exeContext, parentType, source, fields) { var containsPromise = false; var finalResults = _Object$keys(fields).reduce(function (results, responseName) { var fieldASTs = fields[responseName]; var result = resolveField(exeContext, parentType, source, fieldASTs); if (result === undefined) { return results; } results[responseName] = result; if (isThenable(result)) { containsPromise = true; } return results; }, {}); // If there are no promises, we can just return the object if (!containsPromise) { return finalResults; } // Otherwise, results is a map from field name to the result // of resolving that field, which is possibly a promise. Return // a promise that will return this same map, but with any // promises replaced with the values they resolved to. return promiseForObject(finalResults); } /** * Given a selectionSet, adds all of the fields in that selection to * the passed in map of fields, and returns it at the end. */ function collectFields(exeContext, type, selectionSet, fields, visitedFragmentNames) { for (var i = 0; i < selectionSet.selections.length; i++) { var selection = selectionSet.selections[i]; switch (selection.kind) { case _language.Kind.FIELD: if (!shouldIncludeNode(exeContext, selection.directives)) { continue; } var name = getFieldEntryKey(selection); if (!fields[name]) { fields[name] = []; } fields[name].push(selection); break; case _language.Kind.INLINE_FRAGMENT: if (!shouldIncludeNode(exeContext, selection.directives) || !doesFragmentConditionMatch(exeContext, selection, type)) { continue; } collectFields(exeContext, type, selection.selectionSet, fields, visitedFragmentNames); break; case _language.Kind.FRAGMENT_SPREAD: var fragName = selection.name.value; if (visitedFragmentNames[fragName] || !shouldIncludeNode(exeContext, selection.directives)) { continue; } visitedFragmentNames[fragName] = true; var fragment = exeContext.fragments[fragName]; if (!fragment || !shouldIncludeNode(exeContext, fragment.directives) || !doesFragmentConditionMatch(exeContext, fragment, type)) { continue; } collectFields(exeContext, type, fragment.selectionSet, fields, visitedFragmentNames); break; } } return fields; } /** * Determines if a field should be included based on @if and @unless directives. */ function shouldIncludeNode(exeContext, directives) { var ifDirective = (0, _values.getDirectiveValue)(_typeDirectives.GraphQLIfDirective, directives, exeContext.variables); if (ifDirective !== undefined) { return ifDirective; } var unlessDirective = (0, _values.getDirectiveValue)(_typeDirectives.GraphQLUnlessDirective, directives, exeContext.variables); if (unlessDirective !== undefined) { return !unlessDirective; } return true; } /** * Determines if a fragment is applicable to the given type. */ function doesFragmentConditionMatch(exeContext, fragment, type) { var conditionalType = (0, _utilsTypeFromAST2['default'])(exeContext.schema, fragment.typeCondition); if (conditionalType === type) { return true; } if (conditionalType instanceof _typeDefinition.GraphQLInterfaceType || conditionalType instanceof _typeDefinition.GraphQLUnionType) { return conditionalType.isPossibleType(type); } return false; } /** * A wrapper around Promise.all that operates on an object rather than an * iterable. * * Effectively, this method transforms a `Map<string, Promise<T>>` into * a `Promise<Map<string, T>>`, in the same way that `Promise.all` transforms * a `Array<Promise<T>>` into a `Promise<Array<T>>`. * * This is akin to bluebird's `Promise.props`, but implemented only using * `Promise.all` so it will work with any implementation of ES6 promises. */ function promiseForObject(object) { var keys = _Object$keys(object); var valuesAndPromises = keys.map(function (name) { return object[name]; }); return _Promise.all(valuesAndPromises).then(function (values) { return values.reduce(function (resolvedObject, value, i) { resolvedObject[keys[i]] = value; return resolvedObject; }, {}); }); } /** * Implements the logic to compute the key of a given field’s entry */ function getFieldEntryKey(node) { return node.alias ? node.alias.value : node.name.value; } /** * A wrapper function for resolving the field, that catches the error * and adds it to the context's global if the error is not rethrowable. */ function resolveField(exeContext, parentType, source, fieldASTs) { var fieldDef = getFieldDef(exeContext.schema, parentType, fieldASTs[0]); if (!fieldDef) { return; } // If the field type is non-nullable, then it is resolved without any // protection from errors. if (fieldDef.type instanceof _typeDefinition.GraphQLNonNull) { return resolveFieldOrError(exeContext, parentType, source, fieldASTs, fieldDef); } // Otherwise, error protection is applied, logging the error and resolving // a null value for this field if one is encountered. try { var result = resolveFieldOrError(exeContext, parentType, source, fieldASTs, fieldDef); if (isThenable(result)) { return result.then(undefined, function (error) { exeContext.errors.push(error); return _Promise.resolve(null); }); } return result; } catch (error) { exeContext.errors.push(error); return null; } } /** * Resolves the field on the given source object. In particular, this * figures out the object that the field returns using the resolve function, * then calls completeField to corece scalars or execute the sub * selection set for objects. */ function resolveFieldOrError(exeContext, parentType, source, fieldASTs, fieldDef) { var fieldAST = fieldASTs[0]; var fieldType = fieldDef.type; var resolveFn = fieldDef.resolve || defaultResolveFn; // Build a JS object of arguments from the field.arguments AST, using the // variables scope to fulfill any variable references. // TODO: find a way to memoize, in case this field is within a Array type. var args = (0, _values.getArgumentValues)(fieldDef.args, fieldAST.arguments, exeContext.variables); try { var result = resolveFn(source, args, exeContext.root, // TODO: provide all fieldASTs, not just the first field fieldAST, fieldType, parentType, exeContext.schema); } catch (error) { throw new _error.GraphQLError(error.message, [fieldAST], error.stack); } if (isThenable(result)) { return result.then(function (resolvedResult) { return completeField(exeContext, fieldType, fieldASTs, resolvedResult); }, function (error) { return _Promise.reject(new _error.GraphQLError(error.message, [fieldAST], error.stack)); }); } return completeField(exeContext, fieldType, fieldASTs, result); } /** * Implements the instructions for completeValue as defined in the * "Field entries" section of the spec. * * If the field type is Non-Null, then this recursively completes the value * for the inner type. It throws a field error if that completion returns null, * as per the "Nullability" section of the spec. * * If the field type is a List, then this recursively completes the value * for the inner type on each item in the list. * * If the field type is a Scalar or Enum, ensures the completed value is a legal * value of the type by calling the `coerce` method of GraphQL type definition. * * Otherwise, the field type expects a sub-selection set, and will complete the * value by evaluating all sub-selections. */ function completeField(exeContext, fieldType, fieldASTs, result) { // If field type is NonNull, complete for inner type, and throw field error // if result is null. if (fieldType instanceof _typeDefinition.GraphQLNonNull) { var completed = completeField(exeContext, fieldType.ofType, fieldASTs, result); if (completed === null) { throw new _error.GraphQLError('Cannot return null for non-nullable type.', fieldASTs); } return completed; } // If result is null-like, return null. if ((0, _utilsIsNullish2['default'])(result)) { return null; } // If field type is List, complete each item in the list with the inner type if (fieldType instanceof _typeDefinition.GraphQLList) { var itemType = fieldType.ofType; (0, _utilsInvariant2['default'])(Array.isArray(result), 'User Error: expected iterable, but did not find one.'); return result.map(function (item) { return completeField(exeContext, itemType, fieldASTs, item); }); } // If field type is Scalar or Enum, coerce to a valid value, returning null // if coercion is not possible. if (fieldType instanceof _typeDefinition.GraphQLScalarType || fieldType instanceof _typeDefinition.GraphQLEnumType) { (0, _utilsInvariant2['default'])(fieldType.coerce, 'Missing coerce method on type'); var coercedResult = fieldType.coerce(result); return (0, _utilsIsNullish2['default'])(coercedResult) ? null : coercedResult; } // Field type must be Object, Interface or Union and expect sub-selections. var objectType = fieldType instanceof _typeDefinition.GraphQLObjectType ? fieldType : fieldType instanceof _typeDefinition.GraphQLInterfaceType || fieldType instanceof _typeDefinition.GraphQLUnionType ? fieldType.resolveType(result) : null; if (!objectType) { return null; } // Collect sub-fields to execute to complete this value. var subFieldASTs = {}; var visitedFragmentNames = {}; for (var i = 0; i < fieldASTs.length; i++) { var selectionSet = fieldASTs[i].selectionSet; if (selectionSet) { subFieldASTs = collectFields(exeContext, objectType, selectionSet, subFieldASTs, visitedFragmentNames); } } return executeFields(exeContext, objectType, result, subFieldASTs); } /** * If a resolve function is not given, then a default resolve behavior is used * which takes the property of the source object of the same name as the field * and returns it as the result, or if it's a function, returns the result * of calling that function. */ function defaultResolveFn(source, args, root, fieldAST) { var property = source[fieldAST.name.value]; return typeof property === 'function' ? property.call(source) : property; } /** * Checks to see if this object acts like a Promise, i.e. has a "then" * function. */ function isThenable(value) { return value && typeof value === 'object' && typeof value.then === 'function'; } /** * This method looks up the field on the given type defintion. * It has special casing for the two introspection fields, __schema * and __typename. __typename is special becuase it can always be * queried as a field, even in situations where no other fields * are allowed, like on a Union. __schema could get automatically * added to the query type, but that would require mutating type * definitions, which would cause issues. */ function getFieldDef(schema, parentType, fieldAST) { var name = fieldAST.name.value; if (name === _typeIntrospection.SchemaMetaFieldDef.name && schema.getQueryType() === parentType) { return _typeIntrospection.SchemaMetaFieldDef; } else if (name === _typeIntrospection.TypeMetaFieldDef.name && schema.getQueryType() === parentType) { return _typeIntrospection.TypeMetaFieldDef; } else if (name === _typeIntrospection.TypeNameMetaFieldDef.name) { return _typeIntrospection.TypeNameMetaFieldDef; } return parentType.getFields()[name]; }