UNPKG

@cungminh2710/graphql-depth-limit

Version:

Limit the complexity of your GraphQL queries based on depth.

117 lines (114 loc) 3.61 kB
import { Kind, GraphQLError } from 'graphql'; // src/index.ts function arrify(value) { if (value === null || value === void 0) { return []; } if (Array.isArray(value)) { return value; } if (typeof value === "string") { return [value]; } if (typeof value[Symbol.iterator] === "function") { return [...value]; } return [value]; } var depthLimit = (maxDepth, options, callback) => (validationContext) => { try { const { definitions } = validationContext.getDocument(); const fragments = getFragments(definitions); const queries = getQueriesAndMutations(definitions); const queryDepths = /* @__PURE__ */ new Map(); queries.forEach((node, name) => { const depth = determineDepth(node, fragments, 0, maxDepth, validationContext, name, options); queryDepths.set(name, depth); }); callback && callback(queryDepths); return validationContext; } catch (err) { console.error(err); throw err; } }; function getFragments(definitions) { let map = /* @__PURE__ */ new Map(); for (let definition of definitions) { if (definition.kind === Kind.FRAGMENT_DEFINITION) { map.set(definition.name.value, definition); } } return map; } function getQueriesAndMutations(definitions) { let map = /* @__PURE__ */ new Map(); for (let definition of definitions) { if (definition.kind === Kind.OPERATION_DEFINITION) { map.set(definition.name ? definition.name.value : "", definition); } } return map; } function determineDepth(node, fragments, depthSoFar, maxDepth, context, operationName, options) { if (depthSoFar > maxDepth) { context.reportError(new GraphQLError(`'${operationName}' exceeds maximum operation depth of ${maxDepth}`, [node])); return -1; } switch (node.kind) { case Kind.FIELD: const shouldIgnore = /^__/.test(node.name.value) || seeIfIgnored(node, options == null ? void 0 : options.ignore); if (shouldIgnore || !node.selectionSet) { return 0; } return 1 + Math.max( ...node.selectionSet.selections.map( (selection) => determineDepth(selection, fragments, depthSoFar + 1, maxDepth, context, operationName, options) ) ); case Kind.FRAGMENT_SPREAD: const fragment = fragments.get(node.name.value); if (!fragment) { context.reportError(new GraphQLError(`'Fragment ${node.name.value} not found`, [node])); return -1; } return determineDepth(fragment, fragments, depthSoFar, maxDepth, context, operationName, options); case Kind.INLINE_FRAGMENT: case Kind.FRAGMENT_DEFINITION: case Kind.OPERATION_DEFINITION: return Math.max( ...node.selectionSet.selections.map( (selection) => determineDepth(selection, fragments, depthSoFar, maxDepth, context, operationName, options) ) ); default: throw new Error("uh oh! depth crawler cannot handle: " + node.kind); } } function seeIfIgnored(node, ignore) { if (!ignore) return false; for (let rule of arrify(ignore)) { if (node.kind === Kind.FIELD) { const fieldName = node.name.value; switch (rule.constructor) { case Function: if (rule(fieldName)) { return true; } break; case String: case RegExp: if (fieldName.match(rule)) { return true; } break; default: throw new Error(`Invalid ignore option: ${rule}`); } } } return false; } var src_default = depthLimit; export { src_default as default };