UNPKG

graphql-parse-resolve-info

Version:

Parse GraphQLResolveInfo (the 4th argument of resolve) into a simple tree

249 lines 10.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getAlias = exports.simplify = exports.parse = exports.simplifyParsedResolveInfoFragmentWithType = exports.parseResolveInfo = exports.getAliasFromResolveInfo = void 0; const assert = require("assert"); const graphql_1 = require("graphql"); const values_1 = require("graphql/execution/values"); const debugFactory = require("debug"); const debug = debugFactory("graphql-parse-resolve-info"); const DEBUG_ENABLED = debug.enabled; function getArgVal(resolveInfo, argument) { if (argument.kind === "Variable") { return resolveInfo.variableValues[argument.name.value]; } else if (argument.kind === "BooleanValue") { return argument.value; } } function argNameIsIf(arg) { return arg && arg.name ? arg.name.value === "if" : false; } function skipField(resolveInfo, { directives = [] }) { let skip = false; directives.forEach(directive => { const directiveName = directive.name.value; if (Array.isArray(directive.arguments)) { const ifArgumentAst = directive.arguments.find(argNameIsIf); if (ifArgumentAst) { const argumentValueAst = ifArgumentAst.value; if (directiveName === "skip") { skip = skip || getArgVal(resolveInfo, argumentValueAst); } else if (directiveName === "include") { skip = skip || !getArgVal(resolveInfo, argumentValueAst); } } } }); return skip; } // Originally based on https://github.com/tjmehta/graphql-parse-fields function getAliasFromResolveInfo(resolveInfo) { const asts = // @ts-ignore Property 'fieldASTs' does not exist on type 'GraphQLResolveInfo'. resolveInfo.fieldNodes || resolveInfo.fieldASTs; for (let i = 0, l = asts.length; i < l; i++) { const val = asts[i]; if (val.kind === "Field") { const alias = val.alias ? val.alias.value : val.name && val.name.value; if (alias) { return alias; } } } throw new Error("Could not determine alias?!"); } exports.getAliasFromResolveInfo = getAliasFromResolveInfo; function parseResolveInfo(resolveInfo, options = {}) { const fieldNodes = // @ts-ignore Property 'fieldASTs' does not exist on type 'GraphQLResolveInfo'. resolveInfo.fieldNodes || resolveInfo.fieldASTs; const { parentType } = resolveInfo; if (!fieldNodes) { throw new Error("No fieldNodes provided!"); } if (options.keepRoot == null) { options.keepRoot = false; } if (options.deep == null) { options.deep = true; } const tree = fieldTreeFromAST(fieldNodes, resolveInfo, undefined, options, parentType); if (!options.keepRoot) { const typeKey = firstKey(tree); if (!typeKey) { return null; } const fields = tree[typeKey]; const fieldKey = firstKey(fields); if (!fieldKey) { return null; } return fields[fieldKey]; } return tree; } exports.parseResolveInfo = parseResolveInfo; function getFieldFromAST(ast, parentType) { if (ast.kind === "Field") { const fieldNode = ast; const fieldName = fieldNode.name.value; if (!(parentType instanceof graphql_1.GraphQLUnionType)) { const type = parentType; return type.getFields()[fieldName]; } else { // XXX: TODO: Handle GraphQLUnionType } } return undefined; } let iNum = 1; function fieldTreeFromAST(inASTs, resolveInfo, initTree = {}, options = {}, parentType, depth = "") { const instance = iNum++; if (DEBUG_ENABLED) debug("%s[%d] Entering fieldTreeFromAST with parent type '%s'", depth, instance, parentType); const { variableValues } = resolveInfo; const fragments = resolveInfo.fragments || {}; const asts = Array.isArray(inASTs) ? inASTs : [inASTs]; if (!initTree[parentType.name]) { initTree[parentType.name] = {}; } const outerDepth = depth; return asts.reduce((tree, selectionVal, idx) => { const depth = DEBUG_ENABLED ? `${outerDepth} ` : null; if (DEBUG_ENABLED) debug("%s[%d] Processing AST %d of %d; kind = %s", depth, instance, idx + 1, asts.length, selectionVal.kind); if (skipField(resolveInfo, selectionVal)) { if (DEBUG_ENABLED) debug("%s[%d] IGNORING due to directive", depth, instance); } else if (selectionVal.kind === "Field") { const val = selectionVal; const name = val.name.value; const isReserved = name[0] === "_" && name[1] === "_" && name !== "__id"; if (isReserved) { if (DEBUG_ENABLED) debug("%s[%d] IGNORING because field '%s' is reserved", depth, instance, name); } else { const alias = val.alias && val.alias.value ? val.alias.value : name; if (DEBUG_ENABLED) debug("%s[%d] Field '%s' (alias = '%s')", depth, instance, name, alias); const field = getFieldFromAST(val, parentType); if (field == null) { return tree; } const fieldGqlTypeOrUndefined = (0, graphql_1.getNamedType)(field.type); if (!fieldGqlTypeOrUndefined) { return tree; } const fieldGqlType = fieldGqlTypeOrUndefined; const args = (0, values_1.getArgumentValues)(field, val, variableValues) || {}; if (parentType.name && !tree[parentType.name][alias]) { const newTreeRoot = { name, alias, args, fieldsByTypeName: (0, graphql_1.isCompositeType)(fieldGqlType) ? { [fieldGqlType.name]: {}, } : {}, }; tree[parentType.name][alias] = newTreeRoot; } const selectionSet = val.selectionSet; if (selectionSet != null && options.deep && (0, graphql_1.isCompositeType)(fieldGqlType)) { const newParentType = fieldGqlType; if (DEBUG_ENABLED) debug("%s[%d] Recursing into subfields", depth, instance); fieldTreeFromAST(selectionSet.selections, resolveInfo, tree[parentType.name][alias].fieldsByTypeName, options, newParentType, `${depth} `); } else { // No fields to add if (DEBUG_ENABLED) debug("%s[%d] Exiting (no fields to add)", depth, instance); } } } else if (selectionVal.kind === "FragmentSpread" && options.deep) { const val = selectionVal; const name = val.name && val.name.value; if (DEBUG_ENABLED) debug("%s[%d] Fragment spread '%s'", depth, instance, name); const fragment = fragments[name]; assert(fragment, 'unknown fragment "' + name + '"'); let fragmentType = parentType; if (fragment.typeCondition) { fragmentType = getType(resolveInfo, fragment.typeCondition); } if (fragmentType && (0, graphql_1.isCompositeType)(fragmentType)) { const newParentType = fragmentType; fieldTreeFromAST(fragment.selectionSet.selections, resolveInfo, tree, options, newParentType, `${depth} `); } } else if (selectionVal.kind === "InlineFragment" && options.deep) { const val = selectionVal; const fragment = val; let fragmentType = parentType; if (fragment.typeCondition) { fragmentType = getType(resolveInfo, fragment.typeCondition); } if (DEBUG_ENABLED) debug("%s[%d] Inline fragment (parent = '%s', type = '%s')", depth, instance, parentType, fragmentType); if (fragmentType && (0, graphql_1.isCompositeType)(fragmentType)) { const newParentType = fragmentType; fieldTreeFromAST(fragment.selectionSet.selections, resolveInfo, tree, options, newParentType, `${depth} `); } } else { if (DEBUG_ENABLED) debug("%s[%d] IGNORING because kind '%s' not understood", depth, instance, selectionVal.kind); } // Ref: https://github.com/graphile/postgraphile/pull/342/files#diff-d6702ec9fed755c88b9d70b430fda4d8R148 return tree; }, initTree); } const hasOwnProperty = Object.prototype.hasOwnProperty; function firstKey(obj) { for (const key in obj) { if (hasOwnProperty.call(obj, key)) { return key; } } } function getType(resolveInfo, typeCondition) { const { schema } = resolveInfo; const { kind, name } = typeCondition; if (kind === "NamedType") { const typeName = name.value; return schema.getType(typeName); } } function simplifyParsedResolveInfoFragmentWithType(parsedResolveInfoFragment, type) { const { fieldsByTypeName } = parsedResolveInfoFragment; const fields = {}; const strippedType = (0, graphql_1.getNamedType)(type); if ((0, graphql_1.isCompositeType)(strippedType)) { Object.assign(fields, fieldsByTypeName[strippedType.name]); if (strippedType instanceof graphql_1.GraphQLObjectType) { const objectType = strippedType; // GraphQL ensures that the subfields cannot clash, so it's safe to simply overwrite them for (const anInterface of objectType.getInterfaces()) { Object.assign(fields, fieldsByTypeName[anInterface.name]); } } } return { ...parsedResolveInfoFragment, fields, }; } exports.simplifyParsedResolveInfoFragmentWithType = simplifyParsedResolveInfoFragmentWithType; exports.parse = parseResolveInfo; exports.simplify = simplifyParsedResolveInfoFragmentWithType; exports.getAlias = getAliasFromResolveInfo; //# sourceMappingURL=index.js.map