{"version":3,"sources":["../src/non-null.ts"],"sourcesContent":["import {\n  type ExecutionResult,\n  type FieldNode,\n  type GraphQLError,\n  type GraphQLType,\n  isListType,\n  isNonNullType,\n  isObjectType,\n  isAbstractType\n} from \"graphql\";\nimport merge from \"lodash.merge\";\nimport { collectFields, collectSubfields, resolveFieldDef } from \"./ast.js\";\nimport { getOperationRootType } from \"./compat.js\";\nimport { type CompilationContext } from \"./execution.js\";\n\ninterface QueryMetadata {\n  isNullable: boolean;\n  children: { [key: string]: QueryMetadata };\n}\n\nexport type NullTrimmer = (data: any, errors: GraphQLError[]) => any;\n\n/**\n *\n * @param {CompilationContext} compilationContext\n * @returns {(data: any, errors: GraphQLError[]) => {data: any; errors: GraphQLError[]}}\n */\nexport function createNullTrimmer(\n  compilationContext: CompilationContext\n): NullTrimmer {\n  return trimData(parseQueryNullables(compilationContext));\n}\n\n/**\n * Trims a data response according to the field errors in non null fields.\n *\n * Errors are filtered to ensure a single field error per field.\n *\n * @param {QueryMetadata} nullable Description of the query and their nullability\n * @returns {(data: any, errors: GraphQLError[]) => {data: any; errors: GraphQLError[]}}\n * the trimmed data and a filtered list of errors.\n */\nfunction trimData(nullable: QueryMetadata): NullTrimmer {\n  return (data: any, errors: GraphQLError[]): ExecutionResult => {\n    const finalErrors = [];\n    const processedErrors = new Set<string>();\n    for (const error of errors) {\n      if (!error.path) {\n        // should never happen, it is a bug if it does\n        throw new Error(\"no path available for tree trimming\");\n      }\n      if (processedErrors.has(error.path.join(\".\"))) {\n        // there can be multiple field errors in some scenario\n        // there is no need to continue processing and it should not be part of the final response\n        continue;\n      }\n      const ancestors = findNullableAncestor(nullable, error.path);\n      // The top level field is always nullable\n      // http://facebook.github.io/graphql/June2018/#sec-Errors-and-Non-Nullability\n      //\n      // There is no mention if the following errors need to be present in the response.\n      // For now we assume this is not needed.\n      if (ancestors.length === 0) {\n        data = null;\n        finalErrors.push(error);\n        break;\n      }\n      removeBranch(data, ancestors);\n      processedErrors.add(error.path.join(\".\"));\n      finalErrors.push(error);\n    }\n    return { data, errors: finalErrors };\n  };\n}\n\n/**\n * Removes a branch out of the response data by mutating the original object.\n *\n * @param tree response data\n * @param {Array<number | string>} branch array with the path that should be trimmed\n */\nfunction removeBranch(tree: any, branch: Array<number | string>): void {\n  for (let i = 0; i < branch.length - 1; ++i) {\n    // if ancestor has already been removed, there's nothing to do\n    if (tree[branch[i]] === null) {\n      return;\n    }\n    tree = tree[branch[i]];\n  }\n  const toNull = branch[branch.length - 1];\n  tree[toNull] = null;\n}\n\n/**\n * Name of the child used in array to contain the description.\n *\n * Only used for list to contain the child description.\n */\nconst ARRAY_CHILD_NAME = \"index\";\n\n/**\n *\n * @param {QueryMetadata} nullable Description of the query and their nullability\n * @param {ReadonlyArray<string | number>} paths path of the error location\n * @returns {Array<string | number>} path of the branch to be made null\n */\nfunction findNullableAncestor(\n  nullable: QueryMetadata,\n  paths: ReadonlyArray<string | number>\n): Array<string | number> {\n  let lastNullable = 0;\n  for (let i = 0; i < paths.length; ++i) {\n    const path = paths[i];\n    const child =\n      nullable.children[typeof path === \"string\" ? path : ARRAY_CHILD_NAME];\n    if (!child) {\n      // Stopping the search since we reached a leaf node,\n      // the loop should be on its final iteration\n      break;\n    }\n    if (child.isNullable) {\n      lastNullable = i + 1;\n    }\n    nullable = child;\n  }\n  return paths.slice(0, lastNullable);\n}\n\n/**\n * Produce a description of the query regarding its nullability.\n *\n * Leaf nodes are not present in this representation since they are not\n * interesting for removing branches of the response tree.\n *\n * The structure is recursive like the query.\n * @param {CompilationContext} compilationContext Execution content\n * @returns {QueryMetadata} description of the query\n */\nfunction parseQueryNullables(\n  compilationContext: CompilationContext\n): QueryMetadata {\n  const type = getOperationRootType(\n    compilationContext.schema,\n    compilationContext.operation\n  );\n  const fields = collectFields(\n    compilationContext,\n    type,\n    compilationContext.operation.selectionSet,\n    Object.create(null),\n    Object.create(null)\n  );\n  const properties = Object.create(null);\n  for (const responseName of Object.keys(fields)) {\n    const fieldType = resolveFieldDef(\n      compilationContext,\n      type,\n      fields[responseName]\n    );\n    if (!fieldType) {\n      // if field does not exist, it should be ignored for compatibility concerns.\n      // Usually, validation would stop it before getting here but this could be an old query\n      continue;\n    }\n    const property = transformNode(\n      compilationContext,\n      fields[responseName],\n      fieldType.type\n    );\n    if (property != null) {\n      properties[responseName] = property;\n    }\n  }\n  return {\n    isNullable: true,\n    children: properties\n  };\n}\n\n/**\n * Processes a single node to produce a description of itself and its children.\n *\n * Leaf nodes are ignore and removed from the description\n * @param {CompilationContext} compilationContext\n * @param {FieldNode[]} fieldNodes list of fields\n * @param {GraphQLType} type Current type being processed.\n * @returns {QueryMetadata | null} null if node is a leaf, otherwise a description of the node and its children.\n */\nfunction transformNode(\n  compilationContext: CompilationContext,\n  fieldNodes: FieldNode[],\n  type: GraphQLType\n): QueryMetadata | null {\n  if (isNonNullType(type)) {\n    const nullable = transformNode(compilationContext, fieldNodes, type.ofType);\n    if (nullable != null) {\n      nullable.isNullable = false;\n      return nullable;\n    }\n    return null;\n  }\n  if (isObjectType(type)) {\n    const subfields = collectSubfields(compilationContext, type, fieldNodes);\n    const properties = Object.create(null);\n    for (const responseName of Object.keys(subfields)) {\n      const fieldType = resolveFieldDef(\n        compilationContext,\n        type,\n        subfields[responseName]\n      );\n      if (!fieldType) {\n        // if field does not exist, it should be ignored for compatibility concerns.\n        // Usually, validation would stop it before getting here but this could be an old query\n        continue;\n      }\n      const property = transformNode(\n        compilationContext,\n        subfields[responseName],\n        fieldType.type\n      );\n      if (property != null) {\n        properties[responseName] = property;\n      }\n    }\n    return {\n      isNullable: true,\n      children: properties\n    };\n  }\n  if (isListType(type)) {\n    const child = transformNode(compilationContext, fieldNodes, type.ofType);\n    if (child != null) {\n      return {\n        isNullable: true,\n        children: { [ARRAY_CHILD_NAME]: child }\n      };\n    }\n\n    return {\n      isNullable: true,\n      children: {}\n    };\n  }\n  if (isAbstractType(type)) {\n    return compilationContext.schema.getPossibleTypes(type).reduce(\n      (res, t) => {\n        const property = transformNode(compilationContext, fieldNodes, t);\n        if (property != null) {\n          // We do a deep merge because children can have subset of properties\n          // TODO: Possible bug: two object with different nullability on objects.\n          res.children = merge(res.children, property.children);\n        }\n        return res;\n      },\n      {\n        isNullable: true,\n        children: {}\n      }\n    );\n  }\n\n  // Scalars and enum are ignored since they are leaf values\n  return null;\n}\n"],"mappings":"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","names":[]}