@cungminh2710/graphql-depth-limit
Version:
Limit the complexity of your GraphQL queries based on depth.
117 lines (114 loc) • 3.61 kB
JavaScript
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 };