All files graphql-async.ts

88.68% Statements 47/53
73.08% Branches 19/26
100% Functions 5/5
87.76% Lines 43/49
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 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203                                                                                                        40x   20x   20x 20x   20x     32x                     20x                       64x   64x     107x   2x     12x 93x   93x   93x 93x 93x           93x         12x 8x     4x   4x         12x     12x           12x       64x   64x       64x               93x   93x 93x               93x     93x 66x         27x         27x 8x       19x       10x     15x         15x 2x       13x        
import {
  DocumentNode,
  SelectionSetNode,
  FieldNode,
  FragmentDefinitionNode,
  InlineFragmentNode,
} from 'graphql';
 
import {
  getMainDefinition,
  getFragmentDefinitions,
  createFragmentMap,
  shouldInclude,
  getDirectiveInfoFromField,
  isField,
  isInlineFragment,
  resultKeyNameFromField,
  argumentsObjectFromField,
} from 'apollo-utilities';
 
import {
  merge,
  Resolver,
  VariableMap,
  ExecContext,
  ExecInfo,
  ExecOptions,
} from './graphql';
 
/* Based on graphql function from graphql-js:
 *
 * graphql(
 *   schema: GraphQLSchema,
 *   requestString: string,
 *   rootValue?: ?any,
 *   contextValue?: ?any,
 *   variableValues?: ?{[key: string]: any},
 *   operationName?: ?string
 * ): Promise<GraphQLResult>
 *
 * The default export as of graphql-anywhere is sync as of 4.0,
 * but below is an exported alternative that is async.
 * In the 5.0 version, this will be the only export again
 * and it will be async
 * 
 */
export function graphql(
  resolver: Resolver,
  document: DocumentNode,
  rootValue?: any,
  contextValue?: any,
  variableValues?: VariableMap,
  EexecOptions: ExecOptions = {},
): Promise<null | Object> {
  const mainDefinition = getMainDefinition(document);
 
  const fragments = getFragmentDefinitions(document);
  const fragmentMap = createFragmentMap(fragments);
 
  const resultMapper = execOptions.resultMapper;
 
  // Default matcher always matches all fragments
  const fragmentMatcher = execOptions.fragmentMatcher || (() => true);
 
  const execContext: ExecContext = {
    fragmentMap,
    contextValue,
    variableValues,
    resultMapper,
    resolver,
    fragmentMatcher,
  };
 
  return executeSelectionSet(
    mainDefinition.selectionSet,
    rootValue,
    execContext,
  );
}
 
async function executeSelectionSet(
  selectionSet: SelectionSetNode,
  rootValue: any,
  execContext: ExecContext,
) {
  const { fragmentMap, contextValue, variableValues: variables } = execContext;
 
  const result = {};
 
  const execute = async selection => {
    if (!shouldInclude(selection, variables)) {
      // Skip this entirely
      return;
    }
 
    if (isField(selection)) {
      const fieldResult = await executeField(selection, rootValue, execContext);
 
      const resultFieldKey = resultKeyNameFromField(selection);
 
      Eif (fieldResult !== undefined) {
        Eif (result[resultFieldKey] === undefined) {
          result[resultFieldKey] = fieldResult;
        } else {
          merge(result[resultFieldKey], fieldResult);
        }
      }
 
      return;
    }
 
    let fragment: InlineFragmentNode | FragmentDefinitionNode;
 
    if (isInlineFragment(selection)) {
      fragment = selection;
    } else {
      // This is a named fragment
      fragment = fragmentMap[selection.name.value];
 
      Iif (!fragment) {
        throw new Error(`No fragment named ${selection.name.value}`);
      }
    }
 
    const typeCondition = fragment.typeCondition.name.value;
 
    if (execContext.fragmentMatcher(rootValue, typeCondition, contextValue)) {
      const fragmentResult = await executeSelectionSet(
        fragment.selectionSet,
        rootValue,
        execContext,
      );
 
      merge(result, fragmentResult);
    }
  };
 
  await Promise.all(selectionSet.selections.map(execute));
 
  Iif (execContext.resultMapper) {
    return execContext.resultMapper(result, rootValue);
  }
 
  return result;
}
 
async function executeField(
  field: FieldNode,
  rootValue: any,
  execContext: ExecContext,
): Promise<null | Object> {
  const { variableValues: variables, contextValue, resolver } = execContext;
 
  const fieldName = field.name.value;
  const args = argumentsObjectFromField(field, variables);
 
  const info: ExecInfo = {
    isLeaf: !field.selectionSet,
    resultKey: resultKeyNameFromField(field),
    directives: getDirectiveInfoFromField(field, variables),
  };
 
  const result = await resolver(fieldName, rootValue, args, contextValue, info);
 
  // Handle all scalar types here
  if (!field.selectionSet) {
    return result;
  }
 
  // From here down, the field has a selection set, which means it's trying to
  // query a GraphQLObjectType
  Iif (result == null) {
    // Basically any field in a GraphQL response can be null, or missing
    return result;
  }
 
  if (Array.isArray(result)) {
    return executeSubSelectedArray(field, result, execContext);
  }
 
  // Returned value is an object, and the query has a sub-selection. Recurse.
  return executeSelectionSet(field.selectionSet, result, execContext);
}
 
function executeSubSelectedArray(field, result, execContext) {
  return Promise.all(
    result.map(item => {
      // null value in array
      Iif (item === null) {
        return null;
      }
 
      // This is a nested array, recurse
      if (Array.isArray(item)) {
        return executeSubSelectedArray(field, item, execContext);
      }
 
      // This is an object, run the selection set on it
      return executeSelectionSet(field.selectionSet, item, execContext);
    }),
  );
}