graphql-query-depth-limit-esm 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -11
- package/dist/index.cjs +149 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -8
- package/dist/index.d.ts +41 -8
- package/dist/index.js +147 -31
- package/dist/index.js.map +1 -1
- package/package.json +12 -7
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/depth-limit.ts","../src/depth-engine.ts","../src/directives.ts","../src/ignore.ts"],"sourcesContent":["// Exports are sorted by module path (biome's organizeImports rule), not by\n// export name. This is enforced by the linter and is intentional.\nexport { ERROR_CODES } from \"./constants.js\";\nexport { depthLimit } from \"./depth-limit.js\";\nexport { depthDirectiveTypeDefs } from \"./directives.js\";\nexport type {\n\tDepthCallback,\n\tDepthLimitFunction,\n\tDepthLimitOptions,\n\tDirectiveMode,\n\tIgnoreMode,\n\tIgnoreRule,\n\tIntrospectionMode,\n} from \"./types.js\";\n","/**\n * Error extension codes for depth limit violations.\n */\nexport const ERROR_CODES = {\n\tIGNORE_RULE_ERROR: \"IGNORE_RULE_ERROR\",\n\tQUERY_TOO_DEEP: \"QUERY_TOO_DEEP\",\n} as const;\n","import {\n\ttype ASTVisitor,\n\tGraphQLError,\n\ttype ValidationContext,\n\ttype ValidationRule,\n} from \"graphql\";\n\nimport { ERROR_CODES } from \"./constants.js\";\nimport {\n\tcalculateDepth,\n\tcreateTraversalCaches,\n\textractDefinitions,\n\ttype TraversalConfig,\n} from \"./depth-engine.js\";\nimport type { DepthCallback, DepthLimitFunction, DepthLimitOptions, IgnoreRule } from \"./types.js\";\n\n/** Valid values for the `directiveMode` option. */\nconst DIRECTIVE_MODES = new Set<string>([\"cap\", \"override\"]);\n\n/** Valid values for the `ignoreMode` option. */\nconst IGNORE_MODES = new Set<string>([\"exclude\", \"skip\"]);\n\n/** Valid values for the `ignoreIntrospection` option. */\nconst INTROSPECTION_MODES = new Set<string>([\"all\", \"none\", \"typename\"]);\n\n/**\n * Normalized depthLimit options with validated ignore rules.\n */\ntype NormalizedDepthLimitOptions = Omit<DepthLimitOptions, \"ignore\"> & {\n\tignore?: IgnoreRule[];\n};\n\n/**\n * Creates a GraphQL validation rule that limits query depth.\n *\n * Prevents DoS attacks and resource exhaustion from excessively deep queries\n * by enforcing a maximum depth on operations. Supports per-field overrides\n * via the `@depth` directive, customizable ignore rules, and an optional\n * callback for monitoring.\n *\n * Security considerations:\n * - The global `maxDepth` is a hard ceiling by default (`directiveMode: \"cap\"`)\n * - Correctly handles fragments reused at different depths\n * - Circular fragment references are detected per-path\n * - Only `__typename` is ignored by default (`ignoreIntrospection: \"typename\"`)\n * - Short-circuits on first violation when no callback is provided\n *\n * Limitations:\n * - Variables in `@depth` directives are not supported (falls back to global limit)\n * - Field names are case-sensitive by default (use `caseInsensitiveIgnore` if needed)\n * - `useDirective: true` requires a schema in the validation context; without one\n * it silently falls back to the global limit (directives cannot be resolved)\n * - RegExp ignore rules are executed against field names; patterns with catastrophic\n * backtracking (e.g., `/^(a+)+$/`) may cause performance issues\n *\n * @param maxDepth - Maximum allowed depth for queries (must be a non-negative integer)\n * @param options - Optional configuration for ignore rules, directives, and case sensitivity\n * @param callback - Optional callback invoked with depth results for each operation\n * @returns A GraphQL validation rule function\n * @throws {Error} If `maxDepth` is not a non-negative integer\n *\n * @example\n * ```typescript\n * import { depthLimit } from \"graphql-query-depth-limit-esm\";\n * import { validate } from \"graphql\";\n *\n * const errors = validate(schema, document, [\n * depthLimit(5, {\n * ignore: [\"friends\", /.*Connection$/],\n * useDirective: true,\n * }),\n * ]);\n *\n * const withCallback = depthLimit(5, (depths) => {\n * console.log(depths);\n * });\n * ```\n */\nexport function depthLimit(maxDepth: number, callback?: DepthCallback): ValidationRule;\nexport function depthLimit(\n\tmaxDepth: number,\n\toptions?: DepthLimitOptions,\n\tcallback?: DepthCallback,\n): ValidationRule;\nexport function depthLimit(\n\tmaxDepth: number,\n\toptions?: DepthLimitOptions | DepthCallback,\n\tcallback?: DepthCallback,\n): ValidationRule {\n\tif (!Number.isInteger(maxDepth) || maxDepth < 0) {\n\t\tthrow new Error(`Invalid maxDepth: ${maxDepth}. Must be a non-negative integer.`);\n\t}\n\n\tconst normalized = normalizeDepthLimitArgs(options, callback);\n\n\treturn createValidationRule(maxDepth, normalized.options, normalized.callback);\n}\n\n/** Compile-time check that the implementation satisfies the public type. */\ndepthLimit satisfies DepthLimitFunction;\n\n/**\n * Validates that a value is a boolean or undefined.\n */\nfunction assertBooleanOption(name: string, value: unknown): void {\n\tif (value !== undefined && typeof value !== \"boolean\") {\n\t\tthrow new TypeError(`Invalid ${name}: expected boolean, received ${typeof value}.`);\n\t}\n}\n\n/**\n * Creates the validation rule closure with validated parameters.\n */\nfunction createValidationRule(\n\tmaxDepth: number,\n\toptions?: NormalizedDepthLimitOptions,\n\tcallback?: DepthCallback,\n): ValidationRule {\n\tconst shortCircuit = options?.shortCircuit ?? callback == null;\n\n\treturn function depthLimitValidationRule(context: ValidationContext): ASTVisitor {\n\t\tlet anonymousCount = 0;\n\t\tconst caches = createTraversalCaches();\n\t\tconst document = context.getDocument();\n\t\tconst depths: Record<string, number> | undefined = callback ? {} : undefined;\n\t\tconst { fragments, operations } = extractDefinitions(document.definitions);\n\t\tconst schema = context.getSchema() ?? undefined;\n\t\t// By design: when useDirective is true but no schema is available,\n\t\t// directives silently fall back to the global maxDepth. This is not a\n\t\t// \"silent failure\" — directives cannot be resolved without type info,\n\t\t// so the global limit is the correct and safe default. Emitting an\n\t\t// error here would penalize valid schema-less contexts (e.g., custom\n\t\t// ValidationContext wrappers) where the user intentionally omits the\n\t\t// schema. See DepthLimitOptions.useDirective JSDoc for documentation.\n\t\tconst useDirective = Boolean(schema) && (options?.useDirective ?? false);\n\n\t\t// Pre-collect named operation names to avoid key collisions with\n\t\t// generated anonymous operation keys (e.g., \"anonymous\", \"anonymous_1\").\n\t\tconst namedOperationNames = new Set<string>();\n\t\tfor (const op of operations) {\n\t\t\tif (op.name?.value) {\n\t\t\t\tnamedOperationNames.add(op.name.value);\n\t\t\t}\n\t\t}\n\n\t\tconst config: TraversalConfig = {\n\t\t\tcaseInsensitiveIgnore: options?.caseInsensitiveIgnore ?? false,\n\t\t\tdirectiveMode: options?.directiveMode ?? \"cap\",\n\t\t\tignore: options?.ignore,\n\t\t\tignoreMode: options?.ignoreMode ?? \"exclude\",\n\t\t\tintrospectionMode: options?.ignoreIntrospection ?? \"typename\",\n\t\t\tlimitIgnoredRecursion: options?.limitIgnoredRecursion ?? false,\n\t\t\tshortCircuit,\n\t\t\tuseDirective,\n\t\t};\n\n\t\tconst rootTypeMap = schema\n\t\t\t? {\n\t\t\t\t\tmutation: schema.getMutationType() ?? undefined,\n\t\t\t\t\tquery: schema.getQueryType() ?? undefined,\n\t\t\t\t\tsubscription: schema.getSubscriptionType() ?? undefined,\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tmutation: undefined,\n\t\t\t\t\tquery: undefined,\n\t\t\t\t\tsubscription: undefined,\n\t\t\t\t};\n\n\t\tfor (const operation of operations) {\n\t\t\tlet operationName: string;\n\t\t\tif (operation.name?.value) {\n\t\t\t\toperationName = operation.name.value;\n\t\t\t} else {\n\t\t\t\tlet candidate = anonymousCount === 0 ? \"anonymous\" : `anonymous_${anonymousCount}`;\n\t\t\t\twhile (namedOperationNames.has(candidate)) {\n\t\t\t\t\tanonymousCount++;\n\t\t\t\t\tcandidate = `anonymous_${anonymousCount}`;\n\t\t\t\t}\n\t\t\t\toperationName = candidate;\n\t\t\t\tanonymousCount++;\n\t\t\t}\n\t\t\tconst rootType = rootTypeMap[operation.operation];\n\n\t\t\tlet result: ReturnType<typeof calculateDepth>;\n\t\t\ttry {\n\t\t\t\tresult = calculateDepth(caches, config, fragments, maxDepth, operation, rootType, schema);\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof Error && error.name === \"IgnoreRuleError\") {\n\t\t\t\t\tcontext.reportError(\n\t\t\t\t\t\tnew GraphQLError(error.message, {\n\t\t\t\t\t\t\textensions: {\n\t\t\t\t\t\t\t\tcode: ERROR_CODES.IGNORE_RULE_ERROR,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tnodes: [operation],\n\t\t\t\t\t\t\toriginalError: error,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tif (depths) {\n\t\t\t\tsetDepthResult(depths, operationName, result.depth);\n\t\t\t}\n\n\t\t\tif (result.violation) {\n\t\t\t\tconst depthValue = result.violation.depth;\n\t\t\t\tconst depthLabel = shortCircuit ? `at least ${depthValue}` : `${depthValue}`;\n\t\t\t\tconst violationPath = result.violation.path;\n\t\t\t\tconst pathSuffix = violationPath.length > 0 ? ` (at ${violationPath.join(\".\")})` : \"\";\n\n\t\t\t\tcontext.reportError(\n\t\t\t\t\tnew GraphQLError(\n\t\t\t\t\t\t`'${operationName}' has depth ${depthLabel} which exceeds maximum allowed depth of ${result.violation.maxDepth}${pathSuffix}`,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\textensions: {\n\t\t\t\t\t\t\t\tcode: ERROR_CODES.QUERY_TOO_DEEP,\n\t\t\t\t\t\t\t\tdepth: depthValue,\n\t\t\t\t\t\t\t\tmaxDepth: result.violation.maxDepth,\n\t\t\t\t\t\t\t\tpath: violationPath,\n\t\t\t\t\t\t\t\tshortCircuit,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tnodes: result.violation.node ? [operation, result.violation.node] : [operation],\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (callback && depths) {\n\t\t\tcallback(depths);\n\t\t}\n\n\t\t// All depth validation is performed eagerly above because fragment\n\t\t// resolution requires the full document upfront. Nothing remains\n\t\t// for the visitor traversal phase.\n\t\treturn {};\n\t};\n}\n\n/**\n * Determines whether a value is a valid ignore rule.\n */\nfunction isIgnoreRule(rule: unknown): rule is IgnoreRule {\n\treturn typeof rule === \"function\" || rule instanceof RegExp || typeof rule === \"string\";\n}\n\n/**\n * Normalizes the optional depthLimit arguments.\n */\nfunction normalizeDepthLimitArgs(\n\toptions: DepthLimitOptions | DepthCallback | undefined,\n\tcallback: DepthCallback | undefined,\n): { callback?: DepthCallback; options?: NormalizedDepthLimitOptions } {\n\tif (callback !== undefined && typeof callback !== \"function\") {\n\t\tthrow new TypeError(\"Invalid callback: expected a function.\");\n\t}\n\n\tif (typeof options === \"function\") {\n\t\tif (callback) {\n\t\t\tthrow new TypeError(\"Invalid depthLimit arguments: callback provided twice.\");\n\t\t}\n\n\t\treturn { callback: options, options: undefined };\n\t}\n\n\tif (\n\t\toptions !== undefined &&\n\t\t(options === null || typeof options !== \"object\" || Array.isArray(options))\n\t) {\n\t\tconst receivedType = Array.isArray(options)\n\t\t\t? \"array\"\n\t\t\t: options === null\n\t\t\t\t? \"null\"\n\t\t\t\t: typeof options;\n\t\tthrow new TypeError(`Invalid options: expected an object, received ${receivedType}.`);\n\t}\n\n\treturn {\n\t\tcallback,\n\t\toptions: options ? normalizeDepthLimitOptions(options) : undefined,\n\t};\n}\n\n/**\n * Normalizes and validates depthLimit options.\n */\nfunction normalizeDepthLimitOptions(options: DepthLimitOptions): NormalizedDepthLimitOptions {\n\tassertBooleanOption(\"caseInsensitiveIgnore\", options.caseInsensitiveIgnore);\n\n\tif (options.directiveMode !== undefined && !DIRECTIVE_MODES.has(options.directiveMode)) {\n\t\tthrow new TypeError(\n\t\t\t`Invalid directiveMode: \"${options.directiveMode}\". Must be \"cap\" or \"override\".`,\n\t\t);\n\t}\n\n\tif (\n\t\toptions.ignoreIntrospection !== undefined &&\n\t\t!INTROSPECTION_MODES.has(options.ignoreIntrospection)\n\t) {\n\t\tthrow new TypeError(\n\t\t\t`Invalid ignoreIntrospection: \"${options.ignoreIntrospection}\". Must be \"all\", \"none\", or \"typename\".`,\n\t\t);\n\t}\n\n\tif (options.ignoreMode !== undefined && !IGNORE_MODES.has(options.ignoreMode)) {\n\t\tthrow new TypeError(\n\t\t\t`Invalid ignoreMode: \"${options.ignoreMode}\". Must be \"exclude\" or \"skip\".`,\n\t\t);\n\t}\n\n\tassertBooleanOption(\"limitIgnoredRecursion\", options.limitIgnoredRecursion);\n\tassertBooleanOption(\"shortCircuit\", options.shortCircuit);\n\tassertBooleanOption(\"useDirective\", options.useDirective);\n\n\tconst ignore = normalizeIgnoreRules(options.ignore);\n\treturn { ...options, ignore };\n}\n\n/**\n * Normalizes and validates ignore rules.\n */\nfunction normalizeIgnoreRules(ignore: DepthLimitOptions[\"ignore\"]): IgnoreRule[] | undefined {\n\tif (ignore == null) {\n\t\treturn undefined;\n\t}\n\n\tconst rules: unknown[] = Array.isArray(ignore) ? ignore : [ignore];\n\n\tfor (const [index, rule] of rules.entries()) {\n\t\tif (!isIgnoreRule(rule)) {\n\t\t\tconst receivedType = Array.isArray(rule) ? \"array\" : rule === null ? \"null\" : typeof rule;\n\t\t\tthrow new TypeError(\n\t\t\t\t`Invalid ignore rule at index ${index}: expected string, RegExp, or function, received ${receivedType}.`,\n\t\t\t);\n\t\t}\n\t}\n\n\treturn rules as IgnoreRule[];\n}\n\n/**\n * Safely assigns a depth entry without allowing prototype pollution.\n */\nfunction setDepthResult(target: Record<string, number>, key: string, value: number): void {\n\tObject.defineProperty(target, key, {\n\t\tconfigurable: true,\n\t\tenumerable: true,\n\t\tvalue,\n\t\twritable: true,\n\t});\n}\n","import {\n\ttype ASTNode,\n\ttype DefinitionNode,\n\ttype FragmentDefinitionNode,\n\ttype GraphQLField,\n\ttype GraphQLInterfaceType,\n\ttype GraphQLObjectType,\n\ttype GraphQLOutputType,\n\ttype GraphQLSchema,\n\tgetNamedType,\n\tisCompositeType,\n\tisInterfaceType,\n\tisObjectType,\n\tKind,\n\ttype OperationDefinitionNode,\n\ttype SelectionNode,\n} from \"graphql\";\n\nimport { getDepthFromDirective } from \"./directives.js\";\nimport { shouldIgnoreField } from \"./ignore.js\";\nimport type { DirectiveMode, IgnoreMode, IgnoreRule, IntrospectionMode } from \"./types.js\";\n\n/**\n * Result returned by the depth calculation engine.\n */\ninterface DepthResult {\n\t/** Maximum depth found across all branches */\n\tdepth: number;\n\t/** Deepest violation found, or `null` if within limits */\n\tviolation: DepthViolation | null;\n}\n\n/**\n * Records a depth limit violation with the offending depth and its limit.\n */\ninterface DepthViolation {\n\t/** The actual depth that exceeded the limit */\n\tdepth: number;\n\t/** The maximum allowed depth that was exceeded */\n\tmaxDepth: number;\n\t/** The AST node that caused the violation, for precise error locations */\n\tnode?: ASTNode;\n\t/** Field path from the operation root to the violation point */\n\tpath: string[];\n}\n\n/**\n * Result of resolving a `@depth` directive on a field definition.\n * @internal\n */\ninterface DirectiveResolution {\n\t/** Whether a directive limit is now active on this path */\n\thasDirectiveLimit: boolean;\n\t/** The resolved maximum depth for this branch */\n\tmaxDepth: number;\n}\n\n/**\n * Lightweight linked-list node for building field paths without per-field\n * array allocations. Only materialized into `string[]` when reporting a\n * violation or populating callback results.\n * @internal\n */\ninterface PathNode {\n\t/** Parent node in the path, or `undefined` for the root */\n\tparent: PathNode | undefined;\n\t/** Field name or alias for this path segment */\n\tsegment: string;\n}\n\n/**\n * Single unit of work on the iterative DFS stack.\n * @internal\n */\ninterface StackFrame {\n\t/** Current depth at this point in traversal */\n\tcurrentDepth: number;\n\t/** Whether a `@depth` directive has already constrained this path */\n\thasDirectiveLimit: boolean;\n\t/** Ignored field names seen on the current path (for recursion guard) */\n\tignoredFieldsOnPath: Set<string>;\n\t/** Maximum allowed depth for this branch */\n\tmaxDepth: number;\n\t/** The AST node whose selectionSet should be processed */\n\tnode: ASTNode & { selectionSet?: { selections: readonly SelectionNode[] } };\n\t/** Parent type for field resolution */\n\tparentType: GraphQLOutputType | undefined;\n\t/** Linked-list path from the operation root to this node */\n\tpath: PathNode | undefined;\n\t/** Fragment names visited on the current path (for cycle detection) */\n\tvisitedFragments: Set<string>;\n}\n\n/**\n * Caches shared across all stack frames during a single validation run\n * to avoid repeated interface graph walks and directive AST lookups.\n * @internal\n */\ninterface TraversalCaches {\n\t/** Cached raw directive depth per `typeName:fieldName` */\n\tdirectiveDepths: Map<string, number | undefined>;\n\t/** Cached interface lists per type name */\n\tinterfaces: Map<string, GraphQLInterfaceType[]>;\n}\n\n/**\n * Immutable configuration shared across all stack frames during traversal.\n * @internal\n */\ninterface TraversalConfig {\n\t/** Whether to use case-insensitive matching for string ignore rules */\n\tcaseInsensitiveIgnore: boolean;\n\t/** Controls how `@depth` directives interact with the global `maxDepth` */\n\tdirectiveMode: DirectiveMode;\n\t/** Rules for fields to ignore in depth calculation */\n\tignore: IgnoreRule[] | undefined;\n\t/** Controls how ignored fields affect depth traversal */\n\tignoreMode: IgnoreMode;\n\t/** Controls which introspection fields are ignored */\n\tintrospectionMode: IntrospectionMode;\n\t/** Whether repeated ignored fields on a path should increment depth */\n\tlimitIgnoredRecursion: boolean;\n\t/** Whether to bail immediately on violation (when no callback needs true depth) */\n\tshortCircuit: boolean;\n\t/** Whether to check for `@depth` directives on fields */\n\tuseDirective: boolean;\n}\n\n/**\n * Calculates the depth of a GraphQL query AST node using iterative DFS.\n *\n * Handles three selection types:\n * - **Fields**: Increment depth by 1 for composite (non-scalar) fields\n * - **Fragment spreads**: Expand the fragment in-place (no depth increment)\n * - **Inline fragments**: Process in-place (no depth increment)\n *\n * Fragment cycle detection uses per-path visited sets so that the same\n * fragment reused at different depths is calculated correctly.\n *\n * When `shortCircuit` is enabled (no callback), the engine bails immediately\n * on the first violation instead of traversing the full subtree.\n *\n * Uses an explicit stack instead of recursion to prevent stack overflow\n * on deeply nested queries.\n *\n * @param caches - Shared caches for interface and directive lookups\n * @param config - Immutable traversal configuration\n * @param fragments - Map of all fragment definitions in the document\n * @param maxDepth - Maximum allowed depth for this branch\n * @param node - The AST node to calculate depth for\n * @param parentType - Root type for field resolution\n * @param schema - GraphQL schema for type resolution\n * @returns The maximum depth and the deepest violation found\n */\nfunction calculateDepth(\n\tcaches: TraversalCaches,\n\tconfig: TraversalConfig,\n\tfragments: Map<string, FragmentDefinitionNode>,\n\tmaxDepth: number,\n\tnode: ASTNode & { selectionSet?: { selections: readonly SelectionNode[] } },\n\tparentType: GraphQLOutputType | undefined,\n\tschema: GraphQLSchema | undefined,\n): DepthResult {\n\tlet deepestViolation: DepthViolation | null = null;\n\tlet globalMaxDepth = 0;\n\n\tif (!node.selectionSet) {\n\t\treturn { depth: 0, violation: null };\n\t}\n\n\tconst stack: StackFrame[] = [\n\t\t{\n\t\t\tcurrentDepth: 0,\n\t\t\thasDirectiveLimit: false,\n\t\t\tignoredFieldsOnPath: new Set<string>(),\n\t\t\tmaxDepth,\n\t\t\tnode,\n\t\t\tparentType,\n\t\t\tpath: undefined,\n\t\t\tvisitedFragments: new Set<string>(),\n\t\t},\n\t];\n\n\tfor (let frame = stack.pop(); frame !== undefined; frame = stack.pop()) {\n\t\tif (!frame.node.selectionSet) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (const selection of frame.node.selectionSet.selections) {\n\t\t\tswitch (selection.kind) {\n\t\t\t\tcase Kind.FIELD: {\n\t\t\t\t\tconst fieldName = selection.name.value;\n\t\t\t\t\tconst isIntrospectionField = fieldName.startsWith(\"__\");\n\n\t\t\t\t\t// When introspection is fully ignored, always skip the subtree\n\t\t\t\t\t// regardless of ignoreMode.\n\t\t\t\t\tif (config.introspectionMode === \"all\" && isIntrospectionField) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Leaf fields (no selectionSet) never contribute to depth,\n\t\t\t\t\t// so skip them before running ignore rules to avoid unnecessary\n\t\t\t\t\t// predicate evaluation and potential errors on irrelevant fields.\n\t\t\t\t\tif (!selection.selectionSet) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst isIgnored = shouldIgnoreField(\n\t\t\t\t\t\tfieldName,\n\t\t\t\t\t\tconfig.ignore,\n\t\t\t\t\t\tconfig.caseInsensitiveIgnore,\n\t\t\t\t\t\tconfig.introspectionMode,\n\t\t\t\t\t);\n\n\t\t\t\t\tif (isIgnored && config.ignoreMode === \"skip\") {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Resolve field type and directive depth\n\t\t\t\t\tlet fieldMaxDepth = frame.maxDepth;\n\t\t\t\t\tlet fieldType: GraphQLOutputType | undefined;\n\t\t\t\t\tlet hasDirectiveLimit = frame.hasDirectiveLimit;\n\n\t\t\t\t\tif (schema && frame.parentType) {\n\t\t\t\t\t\tconst namedType = getNamedType(frame.parentType);\n\t\t\t\t\t\tif (isObjectType(namedType) || isInterfaceType(namedType)) {\n\t\t\t\t\t\t\tconst fieldDef = namedType.getFields()[fieldName];\n\t\t\t\t\t\t\tif (fieldDef) {\n\t\t\t\t\t\t\t\tfieldType = fieldDef.type;\n\n\t\t\t\t\t\t\t\t// Defensively skip non-composite fields that erroneously\n\t\t\t\t\t\t\t\t// have selections (normally caught by GraphQL's own\n\t\t\t\t\t\t\t\t// validation rules, but guards against miscounted depth\n\t\t\t\t\t\t\t\t// when this rule runs standalone or before other rules).\n\t\t\t\t\t\t\t\tconst resolvedType = getNamedType(fieldType);\n\t\t\t\t\t\t\t\tif (resolvedType && !isCompositeType(resolvedType)) {\n\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (config.useDirective) {\n\t\t\t\t\t\t\t\t\tconst resolved = resolveFieldDirectiveDepth(\n\t\t\t\t\t\t\t\t\t\tcaches,\n\t\t\t\t\t\t\t\t\t\tconfig,\n\t\t\t\t\t\t\t\t\t\tframe.currentDepth,\n\t\t\t\t\t\t\t\t\t\tframe.maxDepth,\n\t\t\t\t\t\t\t\t\t\tfieldDef,\n\t\t\t\t\t\t\t\t\t\tframe.hasDirectiveLimit,\n\t\t\t\t\t\t\t\t\t\tnamedType,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tfieldMaxDepth = resolved.maxDepth;\n\t\t\t\t\t\t\t\t\thasDirectiveLimit = resolved.hasDirectiveLimit;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Determine whether this ignored field should still increment\n\t\t\t\t\t// depth due to the recursion guard detecting repeated ignores\n\t\t\t\t\t// on the same path. The key is type-aware (`Type:field`) when\n\t\t\t\t\t// a schema is present so identically named fields on unrelated\n\t\t\t\t\t// types are tracked independently. Without a schema, the key\n\t\t\t\t\t// uses the field name alone, which may cause conservative\n\t\t\t\t\t// over-counting on paths with same-named fields on different types.\n\t\t\t\t\tlet ignoredFieldsOnPath = frame.ignoredFieldsOnPath;\n\t\t\t\t\tlet effectivelyIgnored = isIgnored;\n\n\t\t\t\t\tif (isIgnored && config.limitIgnoredRecursion) {\n\t\t\t\t\t\tconst parentName = frame.parentType ? getNamedType(frame.parentType)?.name : undefined;\n\t\t\t\t\t\tconst recursionKey = parentName ? `${parentName}:${fieldName}` : fieldName;\n\n\t\t\t\t\t\tif (ignoredFieldsOnPath.has(recursionKey)) {\n\t\t\t\t\t\t\t// Same type:field was already ignored on this path — increment depth\n\t\t\t\t\t\t\teffectivelyIgnored = false;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// First occurrence — track it for subsequent path segments\n\t\t\t\t\t\t\tignoredFieldsOnPath = new Set(ignoredFieldsOnPath);\n\t\t\t\t\t\t\tignoredFieldsOnPath.add(recursionKey);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst newDepth = effectivelyIgnored ? frame.currentDepth : frame.currentDepth + 1;\n\n\t\t\t\t\t// Use alias for path (matches the response shape clients see),\n\t\t\t\t\t// while fieldName is used for schema lookups and ignore rules.\n\t\t\t\t\tconst pathSegment = selection.alias?.value ?? fieldName;\n\t\t\t\t\tconst fieldPath = pathPush(frame.path, pathSegment);\n\n\t\t\t\t\t// Track maximum depth found\n\t\t\t\t\tif (newDepth > globalMaxDepth) {\n\t\t\t\t\t\tglobalMaxDepth = newDepth;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check for violation\n\t\t\t\t\tif (newDepth > fieldMaxDepth) {\n\t\t\t\t\t\tconst violation: DepthViolation = {\n\t\t\t\t\t\t\tdepth: newDepth,\n\t\t\t\t\t\t\tmaxDepth: fieldMaxDepth,\n\t\t\t\t\t\t\tnode: selection,\n\t\t\t\t\t\t\tpath: pathToArray(fieldPath),\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tif (config.shortCircuit) {\n\t\t\t\t\t\t\treturn { depth: newDepth, violation };\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!deepestViolation || violation.depth > deepestViolation.depth) {\n\t\t\t\t\t\t\tdeepestViolation = violation;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Push children onto stack\n\t\t\t\t\tstack.push({\n\t\t\t\t\t\tcurrentDepth: newDepth,\n\t\t\t\t\t\thasDirectiveLimit,\n\t\t\t\t\t\tignoredFieldsOnPath,\n\t\t\t\t\t\tmaxDepth: fieldMaxDepth,\n\t\t\t\t\t\tnode: selection,\n\t\t\t\t\t\tparentType: fieldType,\n\t\t\t\t\t\tpath: fieldPath,\n\t\t\t\t\t\tvisitedFragments: frame.visitedFragments,\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Kind.FRAGMENT_SPREAD: {\n\t\t\t\t\tconst fragmentName = selection.name.value;\n\n\t\t\t\t\t// Check membership before copying to avoid wasted allocations\n\t\t\t\t\t// when the fragment was already visited on this path.\n\t\t\t\t\tif (frame.visitedFragments.has(fragmentName)) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst fragment = fragments.get(fragmentName);\n\t\t\t\t\tif (!fragment) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Create independent copy for per-path cycle detection\n\t\t\t\t\tconst fragmentVisited = new Set(frame.visitedFragments);\n\t\t\t\t\tfragmentVisited.add(fragmentName);\n\n\t\t\t\t\tconst parentType = fragment.typeCondition\n\t\t\t\t\t\t? resolveTypeCondition(fragment.typeCondition.name.value, schema, frame.parentType)\n\t\t\t\t\t\t: frame.parentType;\n\n\t\t\t\t\tstack.push({\n\t\t\t\t\t\tcurrentDepth: frame.currentDepth,\n\t\t\t\t\t\thasDirectiveLimit: frame.hasDirectiveLimit,\n\t\t\t\t\t\tignoredFieldsOnPath: frame.ignoredFieldsOnPath,\n\t\t\t\t\t\tmaxDepth: frame.maxDepth,\n\t\t\t\t\t\tnode: fragment,\n\t\t\t\t\t\tparentType,\n\t\t\t\t\t\tpath: frame.path,\n\t\t\t\t\t\tvisitedFragments: fragmentVisited,\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Kind.INLINE_FRAGMENT: {\n\t\t\t\t\tconst parentType = selection.typeCondition\n\t\t\t\t\t\t? resolveTypeCondition(selection.typeCondition.name.value, schema, frame.parentType)\n\t\t\t\t\t\t: frame.parentType;\n\n\t\t\t\t\tstack.push({\n\t\t\t\t\t\tcurrentDepth: frame.currentDepth,\n\t\t\t\t\t\thasDirectiveLimit: frame.hasDirectiveLimit,\n\t\t\t\t\t\tignoredFieldsOnPath: frame.ignoredFieldsOnPath,\n\t\t\t\t\t\tmaxDepth: frame.maxDepth,\n\t\t\t\t\t\tnode: selection,\n\t\t\t\t\t\tparentType,\n\t\t\t\t\t\tpath: frame.path,\n\t\t\t\t\t\tvisitedFragments: frame.visitedFragments,\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tdefault: {\n\t\t\t\t\tconst exhaustiveCheck: never = selection;\n\t\t\t\t\tthrow new Error(`Unhandled selection kind: ${(exhaustiveCheck as SelectionNode).kind}`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { depth: globalMaxDepth, violation: deepestViolation };\n}\n\n/**\n * Collects all interfaces implemented by a type, including transitively\n * inherited interfaces (interface-implements-interface chains).\n *\n * @param type - The object or interface type to collect interfaces from\n * @returns All directly and transitively implemented interfaces\n */\nfunction collectInterfaces(type: GraphQLInterfaceType | GraphQLObjectType): GraphQLInterfaceType[] {\n\tconst interfaces: GraphQLInterfaceType[] = [];\n\tconst seen = new Set<string>();\n\tconst stack = [...type.getInterfaces()];\n\n\tfor (let iface = stack.pop(); iface !== undefined; iface = stack.pop()) {\n\t\tif (seen.has(iface.name)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tseen.add(iface.name);\n\t\tinterfaces.push(iface);\n\n\t\tfor (const parent of iface.getInterfaces()) {\n\t\t\tif (!seen.has(parent.name)) {\n\t\t\t\tstack.push(parent);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn interfaces;\n}\n\n/**\n * Creates empty traversal caches for a new validation run.\n *\n * @returns Fresh caches for interface and directive lookups\n */\nfunction createTraversalCaches(): TraversalCaches {\n\treturn {\n\t\tdirectiveDepths: new Map<string, number | undefined>(),\n\t\tinterfaces: new Map<string, GraphQLInterfaceType[]>(),\n\t};\n}\n\n/**\n * Extracts all fragment and operation definitions from a GraphQL document\n * in a single pass.\n *\n * **By design:** Duplicate fragment names are silently overwritten (last wins)\n * rather than raising a validation error. This is intentional — detecting\n * duplicates is the responsibility of GraphQL's built-in `UniqueFragmentNamesRule`,\n * not a depth-limiting rule. When both rules run together (the normal case),\n * duplicates are already caught before this code executes. When used standalone,\n * last-wins is a safe, deterministic fallback that avoids coupling depth\n * validation to fragment uniqueness concerns.\n *\n * @param definitions - Array of definition nodes from the parsed document\n * @returns Fragment map and operation array\n */\nfunction extractDefinitions(definitions: readonly DefinitionNode[]): {\n\tfragments: Map<string, FragmentDefinitionNode>;\n\toperations: OperationDefinitionNode[];\n} {\n\tconst fragments = new Map<string, FragmentDefinitionNode>();\n\tconst operations: OperationDefinitionNode[] = [];\n\n\tfor (const definition of definitions) {\n\t\tif (definition.kind === Kind.FRAGMENT_DEFINITION) {\n\t\t\tfragments.set(definition.name.value, definition);\n\t\t} else if (definition.kind === Kind.OPERATION_DEFINITION) {\n\t\t\toperations.push(definition);\n\t\t}\n\t}\n\n\treturn { fragments, operations };\n}\n\n/**\n * Returns cached interfaces for a type, computing and caching on first access.\n *\n * @param caches - Traversal caches\n * @param type - The object or interface type to get interfaces for\n * @returns All directly and transitively implemented interfaces\n */\nfunction getCachedInterfaces(\n\tcaches: TraversalCaches,\n\ttype: GraphQLInterfaceType | GraphQLObjectType,\n): GraphQLInterfaceType[] {\n\tconst cached = caches.interfaces.get(type.name);\n\tif (cached !== undefined) {\n\t\treturn cached;\n\t}\n\n\tconst result = collectInterfaces(type);\n\tcaches.interfaces.set(type.name, result);\n\treturn result;\n}\n\n/**\n * Creates a new path node by appending a segment to the parent path.\n *\n * @param parent - Parent path node, or `undefined` for the root\n * @param segment - Field name or alias for this path segment\n * @returns New path node linked to the parent\n */\nfunction pathPush(parent: PathNode | undefined, segment: string): PathNode {\n\treturn { parent, segment };\n}\n\n/**\n * Materializes a linked-list path into a string array.\n *\n * @param node - Leaf path node to materialize from\n * @returns Array of path segments from root to leaf\n */\nfunction pathToArray(node: PathNode | undefined): string[] {\n\tconst result: string[] = [];\n\tlet current = node;\n\twhile (current) {\n\t\tresult.push(current.segment);\n\t\tcurrent = current.parent;\n\t}\n\tresult.reverse();\n\treturn result;\n}\n\n/**\n * Resolves a `@depth` directive on a field definition, falling back to\n * interface field directives when the concrete field has none.\n *\n * **Precedence:** The concrete field's directive takes priority. Interface\n * directives are only consulted when the concrete field has no `@depth`.\n * When multiple interfaces define `@depth` on the same field, the strictest\n * (minimum) value wins.\n *\n * Results are memoized per `typeName:fieldName` in the traversal caches\n * to avoid repeated interface graph walks on large schemas.\n *\n * @param caches - Traversal caches for memoizing lookups\n * @param config - Traversal configuration\n * @param currentDepth - Current depth in the query tree\n * @param currentMaxDepth - Current maximum depth for this branch\n * @param fieldDef - The field definition to inspect\n * @param hasDirectiveLimit - Whether a directive has already constrained this path\n * @param namedType - The named parent type owning this field (already unwrapped)\n * @returns Resolved maximum depth and directive limit state\n */\nfunction resolveFieldDirectiveDepth(\n\tcaches: TraversalCaches,\n\tconfig: TraversalConfig,\n\tcurrentDepth: number,\n\tcurrentMaxDepth: number,\n\tfieldDef: GraphQLField<unknown, unknown>,\n\thasDirectiveLimit: boolean,\n\tnamedType: GraphQLInterfaceType | GraphQLObjectType,\n): DirectiveResolution {\n\tconst cacheKey = `${namedType.name}:${fieldDef.name}`;\n\n\tlet directiveDepth: number | undefined;\n\n\tif (caches.directiveDepths.has(cacheKey)) {\n\t\tdirectiveDepth = caches.directiveDepths.get(cacheKey);\n\t} else {\n\t\tdirectiveDepth = getDepthFromDirective(fieldDef);\n\n\t\tif (directiveDepth === undefined) {\n\t\t\tconst interfaces = getCachedInterfaces(caches, namedType);\n\t\t\tfor (const iface of interfaces) {\n\t\t\t\tconst ifaceField = iface.getFields()[fieldDef.name];\n\t\t\t\tconst ifaceDepth = getDepthFromDirective(ifaceField);\n\t\t\t\tif (ifaceDepth !== undefined) {\n\t\t\t\t\tdirectiveDepth =\n\t\t\t\t\t\tdirectiveDepth === undefined ? ifaceDepth : Math.min(directiveDepth, ifaceDepth);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tcaches.directiveDepths.set(cacheKey, directiveDepth);\n\t}\n\n\tif (directiveDepth !== undefined) {\n\t\tconst relativeMax = currentDepth + directiveDepth;\n\t\tconst maxDepth =\n\t\t\tconfig.directiveMode === \"cap\"\n\t\t\t\t? Math.min(currentMaxDepth, relativeMax)\n\t\t\t\t: hasDirectiveLimit\n\t\t\t\t\t? Math.min(currentMaxDepth, relativeMax)\n\t\t\t\t\t: relativeMax;\n\t\treturn { hasDirectiveLimit: true, maxDepth };\n\t}\n\n\treturn { hasDirectiveLimit, maxDepth: currentMaxDepth };\n}\n\n/**\n * Resolves the parent type from a type condition on a fragment or inline fragment.\n *\n * Falls back to `currentParentType` when the schema is unavailable or the\n * type condition resolves to a non-composite type (which GraphQL's own\n * validation would reject, but is handled defensively here).\n *\n * @param typeConditionName - The name of the type condition\n * @param schema - GraphQL schema for type lookup\n * @param currentParentType - Fallback parent type if resolution fails\n * @returns The resolved composite type or the current parent type\n */\nfunction resolveTypeCondition(\n\ttypeConditionName: string,\n\tschema: GraphQLSchema | undefined,\n\tcurrentParentType: GraphQLOutputType | undefined,\n): GraphQLOutputType | undefined {\n\tif (schema) {\n\t\tconst type = schema.getType(typeConditionName);\n\t\tif (type && isCompositeType(type)) {\n\t\t\treturn type;\n\t\t}\n\t}\n\treturn currentParentType;\n}\n\nexport { calculateDepth, createTraversalCaches, extractDefinitions };\nexport type { DepthResult, TraversalCaches, TraversalConfig };\n","import { type GraphQLField, Kind } from \"graphql\";\n\n/**\n * GraphQL SDL type definition for the `@depth` directive.\n *\n * Include this in your schema definition to enable per-field depth\n * overrides when using `depthLimit` with `{ useDirective: true }`.\n *\n * @example\n * ```ts\n * import { makeExecutableSchema } from \"@graphql-tools/schema\";\n * import { depthDirectiveTypeDefs } from \"graphql-query-depth-limit-esm\";\n *\n * const schema = makeExecutableSchema({\n * typeDefs: [depthDirectiveTypeDefs, yourTypeDefs],\n * resolvers,\n * });\n * ```\n */\nexport const depthDirectiveTypeDefs = `\n directive @depth(max: Int!) on FIELD_DEFINITION\n`;\n\n/**\n * Extracts the maximum depth from a `@depth(max: Int!)` directive on a field definition.\n *\n * Only integer literals are supported. Variables (e.g., `@depth(max: $var)`) are not\n * supported because variable values are unavailable during the validation phase.\n * Fields with variable-based directives fall back to the global depth limit.\n *\n * @param field - The GraphQL field definition to inspect\n * @returns The maximum depth from the directive, or `undefined` if not found or invalid\n *\n * @example\n * ```graphql\n * type Query {\n * users: [User!]! @depth(max: 3)\n * }\n * ```\n */\nexport function getDepthFromDirective(\n\tfield: GraphQLField<unknown, unknown> | undefined,\n): number | undefined {\n\tif (!field?.astNode?.directives) {\n\t\treturn undefined;\n\t}\n\n\tconst depthDirective = field.astNode.directives.find((d) => d.name.value === \"depth\");\n\n\tif (!depthDirective?.arguments) {\n\t\treturn undefined;\n\t}\n\n\tconst maxArg = depthDirective.arguments.find((arg) => arg.name.value === \"max\");\n\n\tif (maxArg?.value.kind === Kind.INT) {\n\t\tconst directiveDepth = Number.parseInt(maxArg.value.value, 10);\n\t\tif (Number.isFinite(directiveDepth) && directiveDepth >= 0) {\n\t\t\treturn directiveDepth;\n\t\t}\n\t}\n\n\treturn undefined;\n}\n","import type { IgnoreRule, IntrospectionMode } from \"./types.js\";\n\n/**\n * Determines whether a field should be ignored during depth calculation.\n *\n * Introspection field handling is controlled by the `introspectionMode` parameter:\n * - `\"all\"` — ignore every `__`-prefixed field\n * - `\"typename\"` (default) — only ignore `__typename`\n * - `\"none\"` — count all introspection fields toward depth\n *\n * **Warning:** When `ignoreMode: \"skip\"` is set, an ignored field's **entire\n * subtree** is skipped — not just the depth increment. Ignoring a composite field\n * bypasses all depth protection for everything nested under it. Use\n * `ignoreMode: \"exclude\"` (default) to skip only the depth increment while still\n * traversing children.\n *\n * @param fieldName - The name of the field to check\n * @param ignore - Array of ignore rules (strings, RegExp, or functions)\n * @param caseInsensitive - Whether to use case-insensitive matching for string rules\n * @param introspectionMode - How to handle introspection fields\n * @returns `true` if the field should be skipped, `false` otherwise\n *\n * @example\n * ```typescript\n * shouldIgnoreField(\"__typename\", []); // true (introspection, default mode)\n * shouldIgnoreField(\"__schema\", [], false, \"typename\"); // false (only __typename ignored)\n * shouldIgnoreField(\"__schema\", [], false, \"all\"); // true (all __ fields ignored)\n * shouldIgnoreField(\"metadata\", [\"metadata\"]); // true (exact match)\n * shouldIgnoreField(\"Metadata\", [\"metadata\"], true); // true (case-insensitive)\n * shouldIgnoreField(\"posts\", [/^internal/]); // false (no match)\n * ```\n */\nexport function shouldIgnoreField(\n\tfieldName: string,\n\tignore?: IgnoreRule[],\n\tcaseInsensitive = false,\n\tintrospectionMode: IntrospectionMode = \"typename\",\n): boolean {\n\tif (introspectionMode === \"all\" && fieldName.startsWith(\"__\")) {\n\t\treturn true;\n\t}\n\n\tif (introspectionMode === \"typename\" && fieldName === \"__typename\") {\n\t\treturn true;\n\t}\n\n\tif (!ignore || ignore.length === 0) {\n\t\treturn false;\n\t}\n\n\t// Precompute lowercased field name once for all string rule comparisons\n\tconst normalizedFieldName = caseInsensitive ? fieldName.toLowerCase() : fieldName;\n\n\tfor (const rule of ignore) {\n\t\tif (typeof rule === \"string\") {\n\t\t\tconst normalizedRule = caseInsensitive ? rule.toLowerCase() : rule;\n\t\t\tif (normalizedRule === normalizedFieldName) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} else if (rule instanceof RegExp) {\n\t\t\ttry {\n\t\t\t\t// Reset lastIndex for stateful regexes (/g, /y flags) to ensure\n\t\t\t\t// consistent results. This mutates the RegExp object, which is\n\t\t\t\t// intentional. Without the reset, repeated calls with the same\n\t\t\t\t// global or sticky regex produce inconsistent results.\n\t\t\t\tif (rule.global || rule.sticky) {\n\t\t\t\t\trule.lastIndex = 0;\n\t\t\t\t}\n\t\t\t\tif (rule.test(fieldName)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\t// Frozen or exotic RegExp objects throw on lastIndex mutation or\n\t\t\t\t// test(). Wrap as IgnoreRuleError so the caller can handle it\n\t\t\t\t// consistently with function rule errors.\n\t\t\t\tconst message = `Ignore rule RegExp threw for field \"${fieldName}\": ${\n\t\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t\t}`;\n\t\t\t\tconst wrapped = new Error(message, { cause: error });\n\t\t\t\twrapped.name = \"IgnoreRuleError\";\n\t\t\t\tthrow wrapped;\n\t\t\t}\n\t\t} else {\n\t\t\ttry {\n\t\t\t\tif (rule(fieldName)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst message = `Ignore rule function threw for field \"${fieldName}\": ${\n\t\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t\t}`;\n\t\t\t\tconst wrapped = new Error(message, { cause: error });\n\t\t\t\twrapped.name = \"IgnoreRuleError\";\n\t\t\t\tthrow wrapped;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false;\n}\n"],"mappings":"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","names":["import_graphql","import_graphql","parentType"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/depth-limit.ts","../src/depth-engine.ts","../src/directives.ts","../src/ignore.ts"],"sourcesContent":["/**\n * GraphQL Query Depth Limit — ESM Compatible\n *\n * A validation rule that limits query depth and rejects excessively\n * nested queries before execution. Works with any GraphQL server.\n *\n * @packageDocumentation\n */\n\n// Exports are sorted by module path (biome's organizeImports rule), not by\n// export name. This is enforced by the linter and is intentional.\nexport { ERROR_CODES } from \"./constants.js\";\nexport { depthLimit } from \"./depth-limit.js\";\nexport { depthDirectiveTypeDefs } from \"./directives.js\";\nexport { isUnsafeRegExp } from \"./ignore.js\";\nexport type {\n\tDepthCallback,\n\tDepthLimitFunction,\n\tDepthLimitOptions,\n\tDirectiveMode,\n\tIgnoreMode,\n\tIgnoreRule,\n\tIntrospectionMode,\n} from \"./types.js\";\n","/**\n * Error extension codes for depth limit violations.\n */\nexport const ERROR_CODES = Object.freeze({\n\tIGNORE_RULE_ERROR: \"IGNORE_RULE_ERROR\",\n\tQUERY_TOO_DEEP: \"QUERY_TOO_DEEP\",\n} as const);\n","import {\n\ttype ASTVisitor,\n\tGraphQLError,\n\ttype OperationDefinitionNode,\n\ttype ValidationContext,\n\ttype ValidationRule,\n} from \"graphql\";\n\nimport { ERROR_CODES } from \"./constants.js\";\nimport {\n\tcalculateDepth,\n\tcreateTraversalCaches,\n\textractDefinitions,\n\ttype TraversalConfig,\n} from \"./depth-engine.js\";\nimport { isUnsafeRegExp } from \"./ignore.js\";\nimport type { DepthCallback, DepthLimitFunction, DepthLimitOptions, IgnoreRule } from \"./types.js\";\n\n/** Valid values for the `directiveMode` option. */\nconst DIRECTIVE_MODES = new Set<string>([\"cap\", \"override\"]);\n\n/** Valid values for the `ignoreMode` option. */\nconst IGNORE_MODES = new Set<string>([\"exclude\", \"skip\"]);\n\n/** Valid values for the `ignoreIntrospection` option. */\nconst INTROSPECTION_MODES = new Set<string>([\"all\", \"none\", \"typename\"]);\n\n/**\n * Normalized depthLimit options with validated ignore rules.\n */\ntype NormalizedDepthLimitOptions = Omit<DepthLimitOptions, \"ignore\"> & {\n\tignore?: IgnoreRule[];\n};\n\n/**\n * Creates a GraphQL validation rule that limits query depth.\n *\n * Prevents DoS attacks and resource exhaustion from excessively deep queries\n * by enforcing a maximum depth on operations. Supports per-field overrides\n * via the `@depth` directive, customizable ignore rules, and an optional\n * callback for monitoring.\n *\n * Security considerations:\n * - The global `maxDepth` is a hard ceiling by default (`directiveMode: \"cap\"`)\n * - Correctly handles fragments reused at different depths\n * - Circular fragment references are detected per-path\n * - Only `__typename` is ignored by default (`ignoreIntrospection: \"typename\"`)\n * - Short-circuits on first violation when no callback is provided\n *\n * Limitations:\n * - Variables in `@depth` directives are not supported (falls back to global limit)\n * - Field names are case-sensitive by default (use `caseInsensitiveIgnore` if needed)\n * - `useDirective: true` requires a schema in the validation context; without one\n * it silently falls back to the global limit (directives cannot be resolved)\n * - RegExp ignore rules are executed against field names; patterns with catastrophic\n * backtracking (e.g., `/^(a+)+$/`) may cause performance issues\n *\n * @param maxDepth - Maximum allowed depth for queries (must be a non-negative integer)\n * @param options - Optional configuration for ignore rules, directives, and case sensitivity\n * @param callback - Optional callback invoked with per-operation depths as a plain object payload\n * @returns A GraphQL validation rule function\n * @throws {Error} If `maxDepth` is not a non-negative integer\n *\n * @example\n * ```typescript\n * import { depthLimit } from \"graphql-query-depth-limit-esm\";\n * import { validate } from \"graphql\";\n *\n * const errors = validate(schema, document, [\n * depthLimit(5, {\n * ignore: [\"friends\", /.*Connection$/],\n * useDirective: true,\n * }),\n * ]);\n *\n * const withCallback = depthLimit(5, (depths) => {\n * console.log(depths);\n * });\n * ```\n */\nexport function depthLimit(maxDepth: number, callback?: DepthCallback): ValidationRule;\nexport function depthLimit(\n\tmaxDepth: number,\n\toptions?: DepthLimitOptions,\n\tcallback?: DepthCallback,\n): ValidationRule;\nexport function depthLimit(\n\tmaxDepth: number,\n\toptions?: DepthLimitOptions | DepthCallback,\n\tcallback?: DepthCallback,\n): ValidationRule {\n\tif (!Number.isInteger(maxDepth) || maxDepth < 0) {\n\t\tthrow new Error(`Invalid maxDepth: ${maxDepth}. Must be a non-negative integer.`);\n\t}\n\n\tconst normalized = normalizeDepthLimitArgs(options, callback);\n\n\treturn createValidationRule(maxDepth, normalized.options, normalized.callback);\n}\n\n/** Compile-time check that the implementation satisfies the public type. */\ndepthLimit satisfies DepthLimitFunction;\n\n/**\n * Validates that a value is a boolean or undefined.\n */\nfunction assertBooleanOption(name: string, value: unknown): void {\n\tif (value !== undefined && typeof value !== \"boolean\") {\n\t\tthrow new TypeError(`Invalid ${name}: expected boolean, received ${typeof value}.`);\n\t}\n}\n\n/**\n * Creates the validation rule closure with validated parameters.\n */\nfunction createValidationRule(\n\tmaxDepth: number,\n\toptions?: NormalizedDepthLimitOptions,\n\tcallback?: DepthCallback,\n): ValidationRule {\n\tconst shortCircuit = options?.shortCircuit ?? callback == null;\n\n\treturn function depthLimitValidationRule(context: ValidationContext): ASTVisitor {\n\t\tconst caches = createTraversalCaches();\n\t\tconst document = context.getDocument();\n\t\tconst depths: Record<string, number> | undefined = callback\n\t\t\t? createDepthResultRecord()\n\t\t\t: undefined;\n\t\tconst { fragments, operations } = extractDefinitions(document.definitions);\n\t\tconst schema = context.getSchema() ?? undefined;\n\t\t// By design: when useDirective is true but no schema is available,\n\t\t// directives silently fall back to the global maxDepth. This is not a\n\t\t// \"silent failure\" — directives cannot be resolved without type info,\n\t\t// so the global limit is the correct and safe default. Emitting an\n\t\t// error here would penalize valid schema-less contexts (e.g., custom\n\t\t// ValidationContext wrappers) where the user intentionally omits the\n\t\t// schema. See DepthLimitOptions.useDirective JSDoc for documentation.\n\t\tconst useDirective = Boolean(schema) && (options?.useDirective ?? false);\n\n\t\tconst nextOperationName = createOperationNameAllocator(operations);\n\n\t\tconst config: TraversalConfig = {\n\t\t\tcaseInsensitiveIgnore: options?.caseInsensitiveIgnore ?? false,\n\t\t\tdirectiveMode: options?.directiveMode ?? \"cap\",\n\t\t\tignore: options?.ignore,\n\t\t\tignoreMode: options?.ignoreMode ?? \"exclude\",\n\t\t\tintrospectionMode: options?.ignoreIntrospection ?? \"typename\",\n\t\t\tlimitIgnoredRecursion: options?.limitIgnoredRecursion ?? false,\n\t\t\tshortCircuit,\n\t\t\tuseDirective,\n\t\t};\n\n\t\tconst rootTypeMap = schema\n\t\t\t? {\n\t\t\t\t\tmutation: schema.getMutationType() ?? undefined,\n\t\t\t\t\tquery: schema.getQueryType() ?? undefined,\n\t\t\t\t\tsubscription: schema.getSubscriptionType() ?? undefined,\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tmutation: undefined,\n\t\t\t\t\tquery: undefined,\n\t\t\t\t\tsubscription: undefined,\n\t\t\t\t};\n\n\t\tfor (const operation of operations) {\n\t\t\tconst operationName = nextOperationName(operation);\n\t\t\tconst rootType = rootTypeMap[operation.operation];\n\n\t\t\tlet result: ReturnType<typeof calculateDepth>;\n\t\t\ttry {\n\t\t\t\tresult = calculateDepth(caches, config, fragments, maxDepth, operation, rootType, schema);\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof Error && error.name === \"IgnoreRuleError\") {\n\t\t\t\t\tcontext.reportError(\n\t\t\t\t\t\tnew GraphQLError(error.message, {\n\t\t\t\t\t\t\textensions: {\n\t\t\t\t\t\t\t\tcode: ERROR_CODES.IGNORE_RULE_ERROR,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tnodes: [operation],\n\t\t\t\t\t\t\toriginalError: error,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tif (depths) {\n\t\t\t\tsetDepthResult(depths, operationName, result.depth);\n\t\t\t}\n\n\t\t\tif (result.violation) {\n\t\t\t\tconst depthValue = result.violation.depth;\n\t\t\t\tconst depthLabel = shortCircuit ? `at least ${depthValue}` : `${depthValue}`;\n\t\t\t\tconst violationPath = result.violation.path;\n\t\t\t\tconst pathSuffix = violationPath.length > 0 ? ` (at ${violationPath.join(\".\")})` : \"\";\n\n\t\t\t\tcontext.reportError(\n\t\t\t\t\tnew GraphQLError(\n\t\t\t\t\t\t`'${operationName}' has depth ${depthLabel} which exceeds maximum allowed depth of ${result.violation.maxDepth}${pathSuffix}`,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\textensions: {\n\t\t\t\t\t\t\t\tcode: ERROR_CODES.QUERY_TOO_DEEP,\n\t\t\t\t\t\t\t\tdepth: depthValue,\n\t\t\t\t\t\t\t\tmaxDepth: result.violation.maxDepth,\n\t\t\t\t\t\t\t\tpath: violationPath,\n\t\t\t\t\t\t\t\tshortCircuit,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tnodes: result.violation.node ? [operation, result.violation.node] : [operation],\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (callback && depths) {\n\t\t\t// Keep callback payload as a plain object for compatibility.\n\t\t\tcallback({ ...depths });\n\t\t}\n\n\t\t// All depth validation is performed eagerly above because fragment\n\t\t// resolution requires the full document upfront. Nothing remains\n\t\t// for the visitor traversal phase.\n\t\treturn {};\n\t};\n}\n\n/**\n * Determines whether a value is a valid ignore rule.\n */\nfunction isIgnoreRule(rule: unknown): rule is IgnoreRule {\n\treturn typeof rule === \"function\" || rule instanceof RegExp || typeof rule === \"string\";\n}\n\n/**\n * Normalizes the optional depthLimit arguments.\n */\nfunction normalizeDepthLimitArgs(\n\toptions: DepthLimitOptions | DepthCallback | undefined,\n\tcallback: DepthCallback | undefined,\n): { callback?: DepthCallback; options?: NormalizedDepthLimitOptions } {\n\tif (callback !== undefined && typeof callback !== \"function\") {\n\t\tthrow new TypeError(\"Invalid callback: expected a function.\");\n\t}\n\n\tif (typeof options === \"function\") {\n\t\tif (callback) {\n\t\t\tthrow new TypeError(\"Invalid depthLimit arguments: callback provided twice.\");\n\t\t}\n\n\t\treturn { callback: options, options: undefined };\n\t}\n\n\tif (\n\t\toptions !== undefined &&\n\t\t(options === null || typeof options !== \"object\" || Array.isArray(options))\n\t) {\n\t\tconst receivedType = Array.isArray(options)\n\t\t\t? \"array\"\n\t\t\t: options === null\n\t\t\t\t? \"null\"\n\t\t\t\t: typeof options;\n\t\tthrow new TypeError(`Invalid options: expected an object, received ${receivedType}.`);\n\t}\n\n\treturn {\n\t\tcallback,\n\t\toptions: options ? normalizeDepthLimitOptions(options) : undefined,\n\t};\n}\n\n/**\n * Normalizes and validates depthLimit options.\n */\nfunction normalizeDepthLimitOptions(options: DepthLimitOptions): NormalizedDepthLimitOptions {\n\tassertBooleanOption(\"caseInsensitiveIgnore\", options.caseInsensitiveIgnore);\n\n\tif (options.directiveMode !== undefined && !DIRECTIVE_MODES.has(options.directiveMode)) {\n\t\tthrow new TypeError(\n\t\t\t`Invalid directiveMode: \"${options.directiveMode}\". Must be \"cap\" or \"override\".`,\n\t\t);\n\t}\n\n\tif (\n\t\toptions.ignoreIntrospection !== undefined &&\n\t\t!INTROSPECTION_MODES.has(options.ignoreIntrospection)\n\t) {\n\t\tthrow new TypeError(\n\t\t\t`Invalid ignoreIntrospection: \"${options.ignoreIntrospection}\". Must be \"all\", \"none\", or \"typename\".`,\n\t\t);\n\t}\n\n\tif (options.ignoreMode !== undefined && !IGNORE_MODES.has(options.ignoreMode)) {\n\t\tthrow new TypeError(\n\t\t\t`Invalid ignoreMode: \"${options.ignoreMode}\". Must be \"exclude\" or \"skip\".`,\n\t\t);\n\t}\n\n\tassertBooleanOption(\"limitIgnoredRecursion\", options.limitIgnoredRecursion);\n\tassertBooleanOption(\"shortCircuit\", options.shortCircuit);\n\tassertBooleanOption(\"useDirective\", options.useDirective);\n\n\tconst ignore = normalizeIgnoreRules(options.ignore);\n\treturn { ...options, ignore };\n}\n\n/**\n * Normalizes and validates ignore rules.\n */\nfunction normalizeIgnoreRules(ignore: DepthLimitOptions[\"ignore\"]): IgnoreRule[] | undefined {\n\tif (ignore == null) {\n\t\treturn undefined;\n\t}\n\n\tconst rules: unknown[] = Array.isArray(ignore) ? ignore : [ignore];\n\n\tfor (const [index, rule] of rules.entries()) {\n\t\tif (!isIgnoreRule(rule)) {\n\t\t\tconst receivedType = Array.isArray(rule) ? \"array\" : rule === null ? \"null\" : typeof rule;\n\t\t\tthrow new TypeError(\n\t\t\t\t`Invalid ignore rule at index ${index}: expected string, RegExp, or function, received ${receivedType}.`,\n\t\t\t);\n\t\t}\n\n\t\tif (rule instanceof RegExp) {\n\t\t\tconst reason = isUnsafeRegExp(rule);\n\t\t\tif (reason) {\n\t\t\t\tthrow new TypeError(\n\t\t\t\t\t`Unsafe RegExp ignore rule at index ${index}: /${rule.source}/${rule.flags} — ${reason}. Use a simpler pattern to avoid catastrophic backtracking.`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn rules as IgnoreRule[];\n}\n\n/**\n * Creates a null-prototype record for internal depth result accumulation.\n */\nfunction createDepthResultRecord(): Record<string, number> {\n\treturn Object.create(null) as Record<string, number>;\n}\n\n/**\n * Creates a stable allocator for callback operation names.\n *\n * Ensures:\n * - Anonymous names never collide with explicit operation names\n * - Duplicate named operations receive deterministic suffixes\n * - Generated names do not overwrite previous callback entries\n */\nfunction createOperationNameAllocator(\n\toperations: readonly OperationDefinitionNode[],\n): (operation: OperationDefinitionNode) => string {\n\tconst explicitNames = new Set<string>();\n\tfor (const operation of operations) {\n\t\tif (operation.name?.value) {\n\t\t\texplicitNames.add(operation.name.value);\n\t\t}\n\t}\n\n\tconst usedNames = new Set<string>();\n\tconst namedCounts = new Map<string, number>();\n\tlet anonymousCount = 0;\n\n\treturn (operation: OperationDefinitionNode): string => {\n\t\tconst explicitName = operation.name?.value;\n\t\tif (explicitName) {\n\t\t\tlet suffix = namedCounts.get(explicitName) ?? 0;\n\t\t\tlet candidate = suffix === 0 ? explicitName : `${explicitName}_${suffix}`;\n\n\t\t\twhile (usedNames.has(candidate) || (suffix > 0 && explicitNames.has(candidate))) {\n\t\t\t\tsuffix++;\n\t\t\t\tcandidate = `${explicitName}_${suffix}`;\n\t\t\t}\n\n\t\t\tnamedCounts.set(explicitName, suffix + 1);\n\t\t\tusedNames.add(candidate);\n\t\t\treturn candidate;\n\t\t}\n\n\t\tanonymousCount++;\n\t\tlet candidate = anonymousCount === 1 ? \"[anonymous]\" : `[anonymous:${anonymousCount}]`;\n\t\twhile (usedNames.has(candidate) || explicitNames.has(candidate)) {\n\t\t\tanonymousCount++;\n\t\t\tcandidate = `[anonymous:${anonymousCount}]`;\n\t\t}\n\n\t\tusedNames.add(candidate);\n\t\treturn candidate;\n\t};\n}\n\n/**\n * Safely assigns a depth entry without allowing prototype pollution.\n */\nfunction setDepthResult(target: Record<string, number>, key: string, value: number): void {\n\tObject.defineProperty(target, key, {\n\t\tconfigurable: true,\n\t\tenumerable: true,\n\t\tvalue,\n\t\twritable: true,\n\t});\n}\n","import {\n\ttype ASTNode,\n\ttype DefinitionNode,\n\ttype FragmentDefinitionNode,\n\ttype GraphQLField,\n\ttype GraphQLInterfaceType,\n\ttype GraphQLObjectType,\n\ttype GraphQLOutputType,\n\ttype GraphQLSchema,\n\tgetNamedType,\n\tisCompositeType,\n\tisInterfaceType,\n\tisObjectType,\n\tKind,\n\ttype OperationDefinitionNode,\n\ttype SelectionNode,\n} from \"graphql\";\n\nimport { getDepthFromDirective } from \"./directives.js\";\nimport { shouldIgnoreField } from \"./ignore.js\";\nimport type { DirectiveMode, IgnoreMode, IgnoreRule, IntrospectionMode } from \"./types.js\";\n\n/**\n * Result returned by the depth calculation engine.\n */\ninterface DepthResult {\n\t/** Maximum depth found across all branches */\n\tdepth: number;\n\t/** Deepest violation found, or `null` if within limits */\n\tviolation: DepthViolation | null;\n}\n\n/**\n * Records a depth limit violation with the offending depth and its limit.\n */\ninterface DepthViolation {\n\t/** The actual depth that exceeded the limit */\n\tdepth: number;\n\t/** The maximum allowed depth that was exceeded */\n\tmaxDepth: number;\n\t/** The AST node that caused the violation, for precise error locations */\n\tnode?: ASTNode;\n\t/** Field path from the operation root to the violation point */\n\tpath: string[];\n}\n\n/**\n * Result of resolving a `@depth` directive on a field definition.\n * @internal\n */\ninterface DirectiveResolution {\n\t/** Whether a directive limit is now active on this path */\n\thasDirectiveLimit: boolean;\n\t/** The resolved maximum depth for this branch */\n\tmaxDepth: number;\n}\n\n/**\n * Lightweight linked-list node for building field paths without per-field\n * array allocations. Only materialized into `string[]` when reporting a\n * violation or populating callback results.\n * @internal\n */\ninterface PathNode {\n\t/** Parent node in the path, or `undefined` for the root */\n\tparent: PathNode | undefined;\n\t/** Field name or alias for this path segment */\n\tsegment: string;\n}\n\n/**\n * Single unit of work on the iterative DFS stack.\n * @internal\n */\ninterface StackFrame {\n\t/** Current depth at this point in traversal */\n\tcurrentDepth: number;\n\t/** Whether a `@depth` directive has already constrained this path */\n\thasDirectiveLimit: boolean;\n\t/** Ignored field names seen on the current path (for recursion guard) */\n\tignoredFieldsOnPath: Set<string>;\n\t/** Maximum allowed depth for this branch */\n\tmaxDepth: number;\n\t/** The AST node whose selectionSet should be processed */\n\tnode: ASTNode & { selectionSet?: { selections: readonly SelectionNode[] } };\n\t/** Parent type for field resolution */\n\tparentType: GraphQLOutputType | undefined;\n\t/** Linked-list path from the operation root to this node */\n\tpath: PathNode | undefined;\n\t/** Fragment names visited on the current path (for cycle detection) */\n\tvisitedFragments: Set<string>;\n}\n\n/**\n * Caches shared across all stack frames during a single validation run\n * to avoid repeated interface graph walks and directive AST lookups.\n * @internal\n */\ninterface TraversalCaches {\n\t/** Cached raw directive depth per `typeName:fieldName` */\n\tdirectiveDepths: Map<string, number | undefined>;\n\t/** Cached interface lists per type name */\n\tinterfaces: Map<string, GraphQLInterfaceType[]>;\n}\n\n/**\n * Immutable configuration shared across all stack frames during traversal.\n * @internal\n */\ninterface TraversalConfig {\n\t/** Whether to use case-insensitive matching for string ignore rules */\n\tcaseInsensitiveIgnore: boolean;\n\t/** Controls how `@depth` directives interact with the global `maxDepth` */\n\tdirectiveMode: DirectiveMode;\n\t/** Rules for fields to ignore in depth calculation */\n\tignore: IgnoreRule[] | undefined;\n\t/** Controls how ignored fields affect depth traversal */\n\tignoreMode: IgnoreMode;\n\t/** Controls which introspection fields are ignored */\n\tintrospectionMode: IntrospectionMode;\n\t/** Whether repeated ignored fields on a path should increment depth */\n\tlimitIgnoredRecursion: boolean;\n\t/** Whether to bail immediately on violation (when no callback needs true depth) */\n\tshortCircuit: boolean;\n\t/** Whether to check for `@depth` directives on fields */\n\tuseDirective: boolean;\n}\n\n/**\n * Calculates the depth of a GraphQL query AST node using iterative DFS.\n *\n * Handles three selection types:\n * - **Fields**: Increment depth by 1 for composite (non-scalar) fields\n * - **Fragment spreads**: Expand the fragment in-place (no depth increment)\n * - **Inline fragments**: Process in-place (no depth increment)\n *\n * Fragment cycle detection uses per-path visited sets so that the same\n * fragment reused at different depths is calculated correctly.\n *\n * When `shortCircuit` is enabled (no callback), the engine bails immediately\n * on the first violation instead of traversing the full subtree.\n *\n * Uses an explicit stack instead of recursion to prevent stack overflow\n * on deeply nested queries.\n *\n * @param caches - Shared caches for interface and directive lookups\n * @param config - Immutable traversal configuration\n * @param fragments - Map of all fragment definitions in the document\n * @param maxDepth - Maximum allowed depth for this branch\n * @param node - The AST node to calculate depth for\n * @param parentType - Root type for field resolution\n * @param schema - GraphQL schema for type resolution\n * @returns The maximum depth and the deepest violation found\n */\nfunction calculateDepth(\n\tcaches: TraversalCaches,\n\tconfig: TraversalConfig,\n\tfragments: Map<string, FragmentDefinitionNode>,\n\tmaxDepth: number,\n\tnode: ASTNode & { selectionSet?: { selections: readonly SelectionNode[] } },\n\tparentType: GraphQLOutputType | undefined,\n\tschema: GraphQLSchema | undefined,\n): DepthResult {\n\tlet deepestViolation: DepthViolation | null = null;\n\tlet globalMaxDepth = 0;\n\n\tif (!node.selectionSet) {\n\t\treturn { depth: 0, violation: null };\n\t}\n\n\tconst stack: StackFrame[] = [\n\t\t{\n\t\t\tcurrentDepth: 0,\n\t\t\thasDirectiveLimit: false,\n\t\t\tignoredFieldsOnPath: new Set<string>(),\n\t\t\tmaxDepth,\n\t\t\tnode,\n\t\t\tparentType,\n\t\t\tpath: undefined,\n\t\t\tvisitedFragments: new Set<string>(),\n\t\t},\n\t];\n\n\tfor (let frame = stack.pop(); frame !== undefined; frame = stack.pop()) {\n\t\tif (!frame.node.selectionSet) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst nextFrames: StackFrame[] = [];\n\n\t\tfor (const selection of frame.node.selectionSet.selections) {\n\t\t\tswitch (selection.kind) {\n\t\t\t\tcase Kind.FIELD: {\n\t\t\t\t\tconst fieldName = selection.name.value;\n\t\t\t\t\tconst isIntrospectionField = fieldName.startsWith(\"__\");\n\n\t\t\t\t\t// When introspection is fully ignored, always skip the subtree\n\t\t\t\t\t// regardless of ignoreMode.\n\t\t\t\t\tif (config.introspectionMode === \"all\" && isIntrospectionField) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Leaf fields (no selectionSet) never contribute to depth,\n\t\t\t\t\t// so skip them before running ignore rules to avoid unnecessary\n\t\t\t\t\t// predicate evaluation and potential errors on irrelevant fields.\n\t\t\t\t\tif (!selection.selectionSet) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst isIgnored = shouldIgnoreField(\n\t\t\t\t\t\tfieldName,\n\t\t\t\t\t\tconfig.ignore,\n\t\t\t\t\t\tconfig.caseInsensitiveIgnore,\n\t\t\t\t\t\tconfig.introspectionMode,\n\t\t\t\t\t);\n\n\t\t\t\t\tif (isIgnored && config.ignoreMode === \"skip\") {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Resolve field type and directive depth\n\t\t\t\t\tlet fieldMaxDepth = frame.maxDepth;\n\t\t\t\t\tlet fieldType: GraphQLOutputType | undefined;\n\t\t\t\t\tlet hasDirectiveLimit = frame.hasDirectiveLimit;\n\n\t\t\t\t\tif (schema && frame.parentType) {\n\t\t\t\t\t\tconst namedType = getNamedType(frame.parentType);\n\t\t\t\t\t\tif (isObjectType(namedType) || isInterfaceType(namedType)) {\n\t\t\t\t\t\t\tconst fieldDef = namedType.getFields()[fieldName];\n\t\t\t\t\t\t\tif (fieldDef) {\n\t\t\t\t\t\t\t\tfieldType = fieldDef.type;\n\n\t\t\t\t\t\t\t\t// Defensively skip non-composite fields that erroneously\n\t\t\t\t\t\t\t\t// have selections (normally caught by GraphQL's own\n\t\t\t\t\t\t\t\t// validation rules, but guards against miscounted depth\n\t\t\t\t\t\t\t\t// when this rule runs standalone or before other rules).\n\t\t\t\t\t\t\t\tconst resolvedType = getNamedType(fieldType);\n\t\t\t\t\t\t\t\tif (resolvedType && !isCompositeType(resolvedType)) {\n\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (config.useDirective) {\n\t\t\t\t\t\t\t\t\tconst resolved = resolveFieldDirectiveDepth(\n\t\t\t\t\t\t\t\t\t\tcaches,\n\t\t\t\t\t\t\t\t\t\tconfig,\n\t\t\t\t\t\t\t\t\t\tframe.currentDepth,\n\t\t\t\t\t\t\t\t\t\tframe.maxDepth,\n\t\t\t\t\t\t\t\t\t\tfieldDef,\n\t\t\t\t\t\t\t\t\t\tframe.hasDirectiveLimit,\n\t\t\t\t\t\t\t\t\t\tnamedType,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tfieldMaxDepth = resolved.maxDepth;\n\t\t\t\t\t\t\t\t\thasDirectiveLimit = resolved.hasDirectiveLimit;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Determine whether this ignored field should still increment\n\t\t\t\t\t// depth due to the recursion guard detecting repeated ignores\n\t\t\t\t\t// on the same path. The key is type-aware (`Type:field`) when\n\t\t\t\t\t// a schema is present so identically named fields on unrelated\n\t\t\t\t\t// types are tracked independently. Without a schema, the key\n\t\t\t\t\t// uses the field name alone, which may cause conservative\n\t\t\t\t\t// over-counting on paths with same-named fields on different types.\n\t\t\t\t\tlet ignoredFieldsOnPath = frame.ignoredFieldsOnPath;\n\t\t\t\t\tlet effectivelyIgnored = isIgnored;\n\n\t\t\t\t\tif (isIgnored && config.limitIgnoredRecursion) {\n\t\t\t\t\t\tconst parentName = frame.parentType ? getNamedType(frame.parentType)?.name : undefined;\n\t\t\t\t\t\tconst recursionKey = parentName ? `${parentName}:${fieldName}` : fieldName;\n\n\t\t\t\t\t\tif (ignoredFieldsOnPath.has(recursionKey)) {\n\t\t\t\t\t\t\t// Same type:field was already ignored on this path — increment depth\n\t\t\t\t\t\t\teffectivelyIgnored = false;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// First occurrence — track it for subsequent path segments\n\t\t\t\t\t\t\tignoredFieldsOnPath = new Set(ignoredFieldsOnPath);\n\t\t\t\t\t\t\tignoredFieldsOnPath.add(recursionKey);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst newDepth = effectivelyIgnored ? frame.currentDepth : frame.currentDepth + 1;\n\n\t\t\t\t\t// Use alias for path (matches the response shape clients see),\n\t\t\t\t\t// while fieldName is used for schema lookups and ignore rules.\n\t\t\t\t\tconst pathSegment = selection.alias?.value ?? fieldName;\n\t\t\t\t\tconst fieldPath = pathPush(frame.path, pathSegment);\n\n\t\t\t\t\t// Track maximum depth found\n\t\t\t\t\tif (newDepth > globalMaxDepth) {\n\t\t\t\t\t\tglobalMaxDepth = newDepth;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check for violation\n\t\t\t\t\tif (newDepth > fieldMaxDepth) {\n\t\t\t\t\t\tconst violation: DepthViolation = {\n\t\t\t\t\t\t\tdepth: newDepth,\n\t\t\t\t\t\t\tmaxDepth: fieldMaxDepth,\n\t\t\t\t\t\t\tnode: selection,\n\t\t\t\t\t\t\tpath: pathToArray(fieldPath),\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tif (config.shortCircuit) {\n\t\t\t\t\t\t\treturn { depth: newDepth, violation };\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!deepestViolation || violation.depth > deepestViolation.depth) {\n\t\t\t\t\t\t\tdeepestViolation = violation;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Queue children and push in reverse after this selection set\n\t\t\t\t\t// so DFS traversal follows query order deterministically.\n\t\t\t\t\tnextFrames.push({\n\t\t\t\t\t\tcurrentDepth: newDepth,\n\t\t\t\t\t\thasDirectiveLimit,\n\t\t\t\t\t\tignoredFieldsOnPath,\n\t\t\t\t\t\tmaxDepth: fieldMaxDepth,\n\t\t\t\t\t\tnode: selection,\n\t\t\t\t\t\tparentType: fieldType,\n\t\t\t\t\t\tpath: fieldPath,\n\t\t\t\t\t\tvisitedFragments: frame.visitedFragments,\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Kind.FRAGMENT_SPREAD: {\n\t\t\t\t\tconst fragmentName = selection.name.value;\n\n\t\t\t\t\t// Check membership before copying to avoid wasted allocations\n\t\t\t\t\t// when the fragment was already visited on this path.\n\t\t\t\t\tif (frame.visitedFragments.has(fragmentName)) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst fragment = fragments.get(fragmentName);\n\t\t\t\t\tif (!fragment) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Create independent copy for per-path cycle detection\n\t\t\t\t\tconst fragmentVisited = new Set(frame.visitedFragments);\n\t\t\t\t\tfragmentVisited.add(fragmentName);\n\n\t\t\t\t\tconst parentType = fragment.typeCondition\n\t\t\t\t\t\t? resolveTypeCondition(fragment.typeCondition.name.value, schema, frame.parentType)\n\t\t\t\t\t\t: frame.parentType;\n\n\t\t\t\t\tnextFrames.push({\n\t\t\t\t\t\tcurrentDepth: frame.currentDepth,\n\t\t\t\t\t\thasDirectiveLimit: frame.hasDirectiveLimit,\n\t\t\t\t\t\tignoredFieldsOnPath: frame.ignoredFieldsOnPath,\n\t\t\t\t\t\tmaxDepth: frame.maxDepth,\n\t\t\t\t\t\tnode: fragment,\n\t\t\t\t\t\tparentType,\n\t\t\t\t\t\tpath: frame.path,\n\t\t\t\t\t\tvisitedFragments: fragmentVisited,\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Kind.INLINE_FRAGMENT: {\n\t\t\t\t\tconst parentType = selection.typeCondition\n\t\t\t\t\t\t? resolveTypeCondition(selection.typeCondition.name.value, schema, frame.parentType)\n\t\t\t\t\t\t: frame.parentType;\n\n\t\t\t\t\tnextFrames.push({\n\t\t\t\t\t\tcurrentDepth: frame.currentDepth,\n\t\t\t\t\t\thasDirectiveLimit: frame.hasDirectiveLimit,\n\t\t\t\t\t\tignoredFieldsOnPath: frame.ignoredFieldsOnPath,\n\t\t\t\t\t\tmaxDepth: frame.maxDepth,\n\t\t\t\t\t\tnode: selection,\n\t\t\t\t\t\tparentType,\n\t\t\t\t\t\tpath: frame.path,\n\t\t\t\t\t\tvisitedFragments: frame.visitedFragments,\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tdefault: {\n\t\t\t\t\tconst exhaustiveCheck: never = selection;\n\t\t\t\t\tthrowUnhandledSelectionKind(exhaustiveCheck);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (let index = nextFrames.length - 1; index >= 0; index--) {\n\t\t\tconst nextFrame = nextFrames[index];\n\t\t\tif (nextFrame) {\n\t\t\t\tstack.push(nextFrame);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { depth: globalMaxDepth, violation: deepestViolation };\n}\n\n/**\n * Throws for impossible selection kinds. This should only run when a\n * malformed or manually constructed AST bypasses GraphQL parser guarantees.\n */\nfunction throwUnhandledSelectionKind(selection: SelectionNode): never {\n\tthrow new Error(`Unhandled selection kind: ${selection.kind}`);\n}\n\n/**\n * Collects all interfaces implemented by a type, including transitively\n * inherited interfaces (interface-implements-interface chains).\n *\n * @param type - The object or interface type to collect interfaces from\n * @returns All directly and transitively implemented interfaces\n */\nfunction collectInterfaces(type: GraphQLInterfaceType | GraphQLObjectType): GraphQLInterfaceType[] {\n\tconst interfaces: GraphQLInterfaceType[] = [];\n\tconst seen = new Set<string>();\n\tconst stack = [...type.getInterfaces()];\n\n\tfor (let iface = stack.pop(); iface !== undefined; iface = stack.pop()) {\n\t\tif (seen.has(iface.name)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tseen.add(iface.name);\n\t\tinterfaces.push(iface);\n\n\t\tfor (const parent of iface.getInterfaces()) {\n\t\t\tif (!seen.has(parent.name)) {\n\t\t\t\tstack.push(parent);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn interfaces;\n}\n\n/**\n * Creates empty traversal caches for a new validation run.\n *\n * @returns Fresh caches for interface and directive lookups\n */\nfunction createTraversalCaches(): TraversalCaches {\n\treturn {\n\t\tdirectiveDepths: new Map<string, number | undefined>(),\n\t\tinterfaces: new Map<string, GraphQLInterfaceType[]>(),\n\t};\n}\n\n/**\n * Extracts all fragment and operation definitions from a GraphQL document\n * in a single pass.\n *\n * **By design:** Duplicate fragment names are silently overwritten (last wins)\n * rather than raising a validation error. This is intentional — detecting\n * duplicates is the responsibility of GraphQL's built-in `UniqueFragmentNamesRule`,\n * not a depth-limiting rule. When both rules run together (the normal case),\n * duplicates are already caught before this code executes. When used standalone,\n * last-wins is a safe, deterministic fallback that avoids coupling depth\n * validation to fragment uniqueness concerns.\n *\n * @param definitions - Array of definition nodes from the parsed document\n * @returns Fragment map and operation array\n */\nfunction extractDefinitions(definitions: readonly DefinitionNode[]): {\n\tfragments: Map<string, FragmentDefinitionNode>;\n\toperations: OperationDefinitionNode[];\n} {\n\tconst fragments = new Map<string, FragmentDefinitionNode>();\n\tconst operations: OperationDefinitionNode[] = [];\n\n\tfor (const definition of definitions) {\n\t\tif (definition.kind === Kind.FRAGMENT_DEFINITION) {\n\t\t\tfragments.set(definition.name.value, definition);\n\t\t} else if (definition.kind === Kind.OPERATION_DEFINITION) {\n\t\t\toperations.push(definition);\n\t\t}\n\t}\n\n\treturn { fragments, operations };\n}\n\n/**\n * Returns cached interfaces for a type, computing and caching on first access.\n *\n * @param caches - Traversal caches\n * @param type - The object or interface type to get interfaces for\n * @returns All directly and transitively implemented interfaces\n */\nfunction getCachedInterfaces(\n\tcaches: TraversalCaches,\n\ttype: GraphQLInterfaceType | GraphQLObjectType,\n): GraphQLInterfaceType[] {\n\tconst cached = caches.interfaces.get(type.name);\n\tif (cached !== undefined) {\n\t\treturn cached;\n\t}\n\n\tconst result = collectInterfaces(type);\n\tcaches.interfaces.set(type.name, result);\n\treturn result;\n}\n\n/**\n * Creates a new path node by appending a segment to the parent path.\n *\n * @param parent - Parent path node, or `undefined` for the root\n * @param segment - Field name or alias for this path segment\n * @returns New path node linked to the parent\n */\nfunction pathPush(parent: PathNode | undefined, segment: string): PathNode {\n\treturn { parent, segment };\n}\n\n/**\n * Materializes a linked-list path into a string array.\n *\n * @param node - Leaf path node to materialize from\n * @returns Array of path segments from root to leaf\n */\nfunction pathToArray(node: PathNode | undefined): string[] {\n\tconst result: string[] = [];\n\tlet current = node;\n\twhile (current) {\n\t\tresult.push(current.segment);\n\t\tcurrent = current.parent;\n\t}\n\tresult.reverse();\n\treturn result;\n}\n\n/**\n * Resolves a `@depth` directive on a field definition, falling back to\n * interface field directives when the concrete field has none.\n *\n * **Precedence:** The concrete field's directive takes priority. Interface\n * directives are only consulted when the concrete field has no `@depth`.\n * When multiple interfaces define `@depth` on the same field, the strictest\n * (minimum) value wins.\n *\n * Results are memoized per `typeName:fieldName` in the traversal caches\n * to avoid repeated interface graph walks on large schemas.\n *\n * @param caches - Traversal caches for memoizing lookups\n * @param config - Traversal configuration\n * @param currentDepth - Current depth in the query tree\n * @param currentMaxDepth - Current maximum depth for this branch\n * @param fieldDef - The field definition to inspect\n * @param hasDirectiveLimit - Whether a directive has already constrained this path\n * @param namedType - The named parent type owning this field (already unwrapped)\n * @returns Resolved maximum depth and directive limit state\n */\nfunction resolveFieldDirectiveDepth(\n\tcaches: TraversalCaches,\n\tconfig: TraversalConfig,\n\tcurrentDepth: number,\n\tcurrentMaxDepth: number,\n\tfieldDef: GraphQLField<unknown, unknown>,\n\thasDirectiveLimit: boolean,\n\tnamedType: GraphQLInterfaceType | GraphQLObjectType,\n): DirectiveResolution {\n\tconst cacheKey = `${namedType.name}:${fieldDef.name}`;\n\n\tlet directiveDepth: number | undefined;\n\n\tif (caches.directiveDepths.has(cacheKey)) {\n\t\tdirectiveDepth = caches.directiveDepths.get(cacheKey);\n\t} else {\n\t\tdirectiveDepth = getDepthFromDirective(fieldDef);\n\n\t\tif (directiveDepth === undefined) {\n\t\t\tconst interfaces = getCachedInterfaces(caches, namedType);\n\t\t\tfor (const iface of interfaces) {\n\t\t\t\tconst ifaceField = iface.getFields()[fieldDef.name];\n\t\t\t\tconst ifaceDepth = getDepthFromDirective(ifaceField);\n\t\t\t\tif (ifaceDepth !== undefined) {\n\t\t\t\t\tdirectiveDepth =\n\t\t\t\t\t\tdirectiveDepth === undefined ? ifaceDepth : Math.min(directiveDepth, ifaceDepth);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tcaches.directiveDepths.set(cacheKey, directiveDepth);\n\t}\n\n\tif (directiveDepth !== undefined) {\n\t\tconst relativeMax = currentDepth + directiveDepth;\n\t\tconst maxDepth =\n\t\t\tconfig.directiveMode === \"cap\"\n\t\t\t\t? Math.min(currentMaxDepth, relativeMax)\n\t\t\t\t: hasDirectiveLimit\n\t\t\t\t\t? Math.min(currentMaxDepth, relativeMax)\n\t\t\t\t\t: relativeMax;\n\t\treturn { hasDirectiveLimit: true, maxDepth };\n\t}\n\n\treturn { hasDirectiveLimit, maxDepth: currentMaxDepth };\n}\n\n/**\n * Resolves the parent type from a type condition on a fragment or inline fragment.\n *\n * Falls back to `currentParentType` when the schema is unavailable or the\n * type condition resolves to a non-composite type (which GraphQL's own\n * validation would reject, but is handled defensively here).\n *\n * @param typeConditionName - The name of the type condition\n * @param schema - GraphQL schema for type lookup\n * @param currentParentType - Fallback parent type if resolution fails\n * @returns The resolved composite type or the current parent type\n */\nfunction resolveTypeCondition(\n\ttypeConditionName: string,\n\tschema: GraphQLSchema | undefined,\n\tcurrentParentType: GraphQLOutputType | undefined,\n): GraphQLOutputType | undefined {\n\tif (schema) {\n\t\tconst type = schema.getType(typeConditionName);\n\t\tif (type && isCompositeType(type)) {\n\t\t\treturn type;\n\t\t}\n\t}\n\treturn currentParentType;\n}\n\nexport { calculateDepth, createTraversalCaches, extractDefinitions };\nexport type { DepthResult, TraversalCaches, TraversalConfig };\n","import { type GraphQLField, Kind } from \"graphql\";\n\n/**\n * GraphQL SDL type definition for the `@depth` directive.\n *\n * Include this in your schema definition to enable per-field depth\n * overrides when using `depthLimit` with `{ useDirective: true }`.\n *\n * @example\n * ```ts\n * import { makeExecutableSchema } from \"@graphql-tools/schema\";\n * import { depthDirectiveTypeDefs } from \"graphql-query-depth-limit-esm\";\n *\n * const schema = makeExecutableSchema({\n * typeDefs: [depthDirectiveTypeDefs, yourTypeDefs],\n * resolvers,\n * });\n * ```\n */\nexport const depthDirectiveTypeDefs = /* GraphQL */ `directive @depth(max: Int!) on FIELD_DEFINITION`;\n\n/**\n * Extracts the maximum depth from a `@depth(max: Int!)` directive on a field definition.\n *\n * Only integer literals are supported. Variables (e.g., `@depth(max: $var)`) are not\n * supported because variable values are unavailable during the validation phase.\n * Fields with variable-based directives fall back to the global depth limit.\n *\n * @param field - The GraphQL field definition to inspect\n * @returns The maximum depth from the directive, or `undefined` if not found or invalid\n *\n * @example\n * ```graphql\n * type Query {\n * users: [User!]! @depth(max: 3)\n * }\n * ```\n */\nexport function getDepthFromDirective(\n\tfield: GraphQLField<unknown, unknown> | undefined,\n): number | undefined {\n\tif (!field?.astNode?.directives) {\n\t\treturn undefined;\n\t}\n\n\tconst depthDirective = field.astNode.directives.find((d) => d.name.value === \"depth\");\n\n\tif (!depthDirective?.arguments) {\n\t\treturn undefined;\n\t}\n\n\tconst maxArg = depthDirective.arguments.find((arg) => arg.name.value === \"max\");\n\n\tif (maxArg?.value.kind === Kind.INT) {\n\t\tconst directiveDepth = Number.parseInt(maxArg.value.value, 10);\n\t\tif (Number.isFinite(directiveDepth) && directiveDepth >= 0) {\n\t\t\treturn directiveDepth;\n\t\t}\n\t}\n\n\treturn undefined;\n}\n","import type { IgnoreRule, IntrospectionMode } from \"./types.js\";\n\n/**\n * Quantifier characters that indicate repetition in a regex pattern.\n * Used by {@link isUnsafeRegExp} to detect nested quantifier structures.\n */\nconst QUANTIFIER_CHARS = new Set([\"+\", \"*\", \"?\"]);\n\n/**\n * Pattern that matches a `{n,m}` style quantifier at a given position.\n * Captures groups: `{min,}`, `{min,max}`, or `{n}` where `min > 1`.\n */\nconst BRACE_QUANTIFIER = /^\\{(\\d+)(?:,(\\d*))?\\}/;\n\n/**\n * Detects whether a `RegExp` is potentially vulnerable to catastrophic\n * backtracking (ReDoS).\n *\n * Uses lightweight static analysis on the pattern source to identify\n * two common ReDoS structures:\n *\n * 1. **Nested quantifiers** — A quantified group containing a quantified\n * sub-expression (e.g., `(a+)+`, `(a*)*`, `(a+)*`, `(a{2,})+`).\n * 2. **Overlapping alternation under a quantifier** — A quantified group\n * whose alternatives can match the same input (e.g., `(a|a)+`,\n * `(\\w|\\d)+`).\n *\n * This is a heuristic — it may reject some safe patterns and miss some\n * unsafe ones. The goal is to catch the most common footguns without\n * requiring a full regex parser.\n *\n * @param regex - The `RegExp` to analyze\n * @returns A descriptive reason string if the pattern is potentially\n * unsafe, or `null` if no issues are detected\n *\n * @example\n * ```typescript\n * isUnsafeRegExp(/(a+)+$/); // \"nested quantifier: group with inner quantifier followed by outer quantifier\"\n * isUnsafeRegExp(/^internal/); // null (safe)\n * ```\n */\nexport function isUnsafeRegExp(regex: RegExp): string | null {\n\tconst source = regex.source;\n\tconst length = source.length;\n\n\t// Track group nesting and whether any group contains a quantifier\n\tconst groupStack: boolean[] = [];\n\n\tfor (let i = 0; i < length; i++) {\n\t\tconst char = source.charAt(i);\n\n\t\t// Skip escaped characters\n\t\tif (char === \"\\\\\") {\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Skip character classes entirely — quantifiers inside [...] are literals\n\t\tif (char === \"[\") {\n\t\t\twhile (i < length && source[i] !== \"]\") {\n\t\t\t\tif (source[i] === \"\\\\\") i++;\n\t\t\t\ti++;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Opening group (capturing or non-capturing)\n\t\tif (char === \"(\") {\n\t\t\tgroupStack.push(false);\n\t\t\t// Skip non-capturing/lookahead syntax: (?:, (?=, (?!, (?<=, (?<!\n\t\t\t// so the `?` is not misinterpreted as a quantifier on the `(`.\n\t\t\tif (source[i + 1] === \"?\") {\n\t\t\t\ti++;\n\t\t\t\tif (source[i + 1] === \"<\" && (source[i + 2] === \"=\" || source[i + 2] === \"!\")) {\n\t\t\t\t\ti += 2;\n\t\t\t\t} else if (source[i + 1] === \":\" || source[i + 1] === \"=\" || source[i + 1] === \"!\") {\n\t\t\t\t\ti++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Closing group — check if followed by a quantifier\n\t\tif (char === \")\") {\n\t\t\tconst groupHadQuantifier = groupStack.pop() ?? false;\n\n\t\t\tif (groupHadQuantifier && isFollowedByQuantifier(source, i + 1, length)) {\n\t\t\t\treturn \"nested quantifier: group with inner quantifier followed by outer quantifier\";\n\t\t\t}\n\n\t\t\t// Mark the parent group as containing a quantifier if this group is quantified\n\t\t\tif (groupStack.length > 0 && isFollowedByQuantifier(source, i + 1, length)) {\n\t\t\t\tgroupStack[groupStack.length - 1] = true;\n\t\t\t}\n\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Detect quantifiers on non-group atoms and mark the parent group\n\t\tif (QUANTIFIER_CHARS.has(char) || char === \"{\") {\n\t\t\tif (char === \"{\") {\n\t\t\t\tconst match = BRACE_QUANTIFIER.exec(source.slice(i));\n\t\t\t\tif (!match) continue;\n\n\t\t\t\tconst minStr = match[1] ?? \"0\";\n\t\t\t\tconst min = Number.parseInt(minStr, 10);\n\t\t\t\tconst fullMatch = match[0] ?? \"\";\n\t\t\t\tconst hasComma = fullMatch.includes(\",\");\n\n\t\t\t\t// {0} and {1} are not meaningful quantifiers for ReDoS\n\t\t\t\tif (!hasComma && min <= 1) continue;\n\t\t\t\t// {0,0} and {0,1} ({1} equivalent to ?) are borderline — skip\n\t\t\t\tif (\n\t\t\t\t\thasComma &&\n\t\t\t\t\tmatch[2] !== undefined &&\n\t\t\t\t\tmatch[2] !== \"\" &&\n\t\t\t\t\tNumber.parseInt(match[2], 10) <= 1\n\t\t\t\t)\n\t\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (groupStack.length > 0) {\n\t\t\t\tgroupStack[groupStack.length - 1] = true;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Checks if the character at `pos` starts a quantifier (`+`, `*`, `?`, `{n,m}`).\n */\nfunction isFollowedByQuantifier(source: string, pos: number, length: number): boolean {\n\tif (pos >= length) return false;\n\n\tconst char = source[pos];\n\tif (char === \"+\" || char === \"*\") return true;\n\tif (char === \"{\") {\n\t\tconst match = BRACE_QUANTIFIER.exec(source.slice(pos));\n\t\tif (match) {\n\t\t\tconst minStr = match[1] ?? \"0\";\n\t\t\tconst min = Number.parseInt(minStr, 10);\n\t\t\tconst fullMatch = match[0] ?? \"\";\n\t\t\tconst hasComma = fullMatch.includes(\",\");\n\t\t\tif (!hasComma && min <= 1) return false;\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/**\n * Determines whether a field should be ignored during depth calculation.\n *\n * Introspection field handling is controlled by the `introspectionMode` parameter:\n * - `\"all\"` — ignore every `__`-prefixed field\n * - `\"typename\"` (default) — only ignore `__typename`\n * - `\"none\"` — count all introspection fields toward depth\n *\n * **Warning:** When `ignoreMode: \"skip\"` is set, an ignored field's **entire\n * subtree** is skipped — not just the depth increment. Ignoring a composite field\n * bypasses all depth protection for everything nested under it. Use\n * `ignoreMode: \"exclude\"` (default) to skip only the depth increment while still\n * traversing children.\n *\n * @param fieldName - The name of the field to check\n * @param ignore - Array of ignore rules (strings, RegExp, or functions)\n * @param caseInsensitive - Whether to use case-insensitive matching for string rules\n * @param introspectionMode - How to handle introspection fields\n * @returns `true` if the field should be skipped, `false` otherwise\n *\n * @example\n * ```typescript\n * shouldIgnoreField(\"__typename\", []); // true (introspection, default mode)\n * shouldIgnoreField(\"__schema\", [], false, \"typename\"); // false (only __typename ignored)\n * shouldIgnoreField(\"__schema\", [], false, \"all\"); // true (all __ fields ignored)\n * shouldIgnoreField(\"metadata\", [\"metadata\"]); // true (exact match)\n * shouldIgnoreField(\"Metadata\", [\"metadata\"], true); // true (case-insensitive)\n * shouldIgnoreField(\"posts\", [/^internal/]); // false (no match)\n * ```\n */\nexport function shouldIgnoreField(\n\tfieldName: string,\n\tignore?: IgnoreRule[],\n\tcaseInsensitive = false,\n\tintrospectionMode: IntrospectionMode = \"typename\",\n): boolean {\n\tif (introspectionMode === \"all\" && fieldName.startsWith(\"__\")) {\n\t\treturn true;\n\t}\n\n\tif (introspectionMode === \"typename\" && fieldName === \"__typename\") {\n\t\treturn true;\n\t}\n\n\tif (!ignore || ignore.length === 0) {\n\t\treturn false;\n\t}\n\n\t// Precompute lowercased field name once for all string rule comparisons\n\tconst normalizedFieldName = caseInsensitive ? fieldName.toLowerCase() : fieldName;\n\n\tfor (const rule of ignore) {\n\t\tif (typeof rule === \"string\") {\n\t\t\tconst normalizedRule = caseInsensitive ? rule.toLowerCase() : rule;\n\t\t\tif (normalizedRule === normalizedFieldName) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} else if (rule instanceof RegExp) {\n\t\t\ttry {\n\t\t\t\t// Reset lastIndex for stateful regexes (/g, /y flags) to ensure\n\t\t\t\t// consistent results. This mutates the RegExp object, which is\n\t\t\t\t// intentional. Without the reset, repeated calls with the same\n\t\t\t\t// global or sticky regex produce inconsistent results.\n\t\t\t\tif (rule.global || rule.sticky) {\n\t\t\t\t\trule.lastIndex = 0;\n\t\t\t\t}\n\t\t\t\tif (rule.test(fieldName)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\t// Frozen or exotic RegExp objects throw on lastIndex mutation or\n\t\t\t\t// test(). Wrap as IgnoreRuleError so the caller can handle it\n\t\t\t\t// consistently with function rule errors.\n\t\t\t\tconst message = `Ignore rule RegExp threw for field \"${fieldName}\": ${\n\t\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t\t}`;\n\t\t\t\tconst wrapped = new Error(message, { cause: error });\n\t\t\t\twrapped.name = \"IgnoreRuleError\";\n\t\t\t\tthrow wrapped;\n\t\t\t}\n\t\t} else {\n\t\t\ttry {\n\t\t\t\tif (rule(fieldName)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst message = `Ignore rule function threw for field \"${fieldName}\": ${\n\t\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t\t}`;\n\t\t\t\tconst wrapped = new Error(message, { cause: error });\n\t\t\t\twrapped.name = \"IgnoreRuleError\";\n\t\t\t\tthrow wrapped;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false;\n}\n"],"mappings":"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","names":["import_graphql","import_graphql","parentType","candidate"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -4,14 +4,17 @@ import { ValidationRule } from 'graphql';
|
|
|
4
4
|
/**
|
|
5
5
|
* Error extension codes for depth limit violations.
|
|
6
6
|
*/
|
|
7
|
-
declare const ERROR_CODES: {
|
|
7
|
+
declare const ERROR_CODES: Readonly<{
|
|
8
8
|
readonly IGNORE_RULE_ERROR: "IGNORE_RULE_ERROR";
|
|
9
9
|
readonly QUERY_TOO_DEEP: "QUERY_TOO_DEEP";
|
|
10
|
-
}
|
|
10
|
+
}>;
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Callback invoked after depth validation with per-operation depth results.
|
|
14
14
|
*
|
|
15
|
+
* The `depths` argument is always a plain object (Object.prototype) for
|
|
16
|
+
* compatibility with common object utilities and `hasOwnProperty` access.
|
|
17
|
+
*
|
|
15
18
|
* @example
|
|
16
19
|
* ```typescript
|
|
17
20
|
* const callback: DepthCallback = (depths) => {
|
|
@@ -172,9 +175,10 @@ type IgnoreMode = "exclude" | "skip";
|
|
|
172
175
|
* - `RegExp` — pattern match against field names
|
|
173
176
|
* - `function` — custom predicate receiving the field name
|
|
174
177
|
*
|
|
175
|
-
* **
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
+
* **Safety:** `RegExp` patterns are validated at setup time for common
|
|
179
|
+
* ReDoS (catastrophic backtracking) structures such as nested quantifiers
|
|
180
|
+
* (e.g., `/(a+)+$/`). Patterns flagged as unsafe are rejected with a
|
|
181
|
+
* descriptive `TypeError`. Use simple, linear-time patterns where possible.
|
|
178
182
|
*
|
|
179
183
|
* @example
|
|
180
184
|
* ```typescript
|
|
@@ -227,7 +231,7 @@ type IntrospectionMode = "all" | "none" | "typename";
|
|
|
227
231
|
*
|
|
228
232
|
* @param maxDepth - Maximum allowed depth for queries (must be a non-negative integer)
|
|
229
233
|
* @param options - Optional configuration for ignore rules, directives, and case sensitivity
|
|
230
|
-
* @param callback - Optional callback invoked with
|
|
234
|
+
* @param callback - Optional callback invoked with per-operation depths as a plain object payload
|
|
231
235
|
* @returns A GraphQL validation rule function
|
|
232
236
|
* @throws {Error} If `maxDepth` is not a non-negative integer
|
|
233
237
|
*
|
|
@@ -268,6 +272,35 @@ declare function depthLimit(maxDepth: number, options?: DepthLimitOptions, callb
|
|
|
268
272
|
* });
|
|
269
273
|
* ```
|
|
270
274
|
*/
|
|
271
|
-
declare const depthDirectiveTypeDefs = "
|
|
275
|
+
declare const depthDirectiveTypeDefs = "directive @depth(max: Int!) on FIELD_DEFINITION";
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Detects whether a `RegExp` is potentially vulnerable to catastrophic
|
|
279
|
+
* backtracking (ReDoS).
|
|
280
|
+
*
|
|
281
|
+
* Uses lightweight static analysis on the pattern source to identify
|
|
282
|
+
* two common ReDoS structures:
|
|
283
|
+
*
|
|
284
|
+
* 1. **Nested quantifiers** — A quantified group containing a quantified
|
|
285
|
+
* sub-expression (e.g., `(a+)+`, `(a*)*`, `(a+)*`, `(a{2,})+`).
|
|
286
|
+
* 2. **Overlapping alternation under a quantifier** — A quantified group
|
|
287
|
+
* whose alternatives can match the same input (e.g., `(a|a)+`,
|
|
288
|
+
* `(\w|\d)+`).
|
|
289
|
+
*
|
|
290
|
+
* This is a heuristic — it may reject some safe patterns and miss some
|
|
291
|
+
* unsafe ones. The goal is to catch the most common footguns without
|
|
292
|
+
* requiring a full regex parser.
|
|
293
|
+
*
|
|
294
|
+
* @param regex - The `RegExp` to analyze
|
|
295
|
+
* @returns A descriptive reason string if the pattern is potentially
|
|
296
|
+
* unsafe, or `null` if no issues are detected
|
|
297
|
+
*
|
|
298
|
+
* @example
|
|
299
|
+
* ```typescript
|
|
300
|
+
* isUnsafeRegExp(/(a+)+$/); // "nested quantifier: group with inner quantifier followed by outer quantifier"
|
|
301
|
+
* isUnsafeRegExp(/^internal/); // null (safe)
|
|
302
|
+
* ```
|
|
303
|
+
*/
|
|
304
|
+
declare function isUnsafeRegExp(regex: RegExp): string | null;
|
|
272
305
|
|
|
273
|
-
export { type DepthCallback, type DepthLimitFunction, type DepthLimitOptions, type DirectiveMode, ERROR_CODES, type IgnoreMode, type IgnoreRule, type IntrospectionMode, depthDirectiveTypeDefs, depthLimit };
|
|
306
|
+
export { type DepthCallback, type DepthLimitFunction, type DepthLimitOptions, type DirectiveMode, ERROR_CODES, type IgnoreMode, type IgnoreRule, type IntrospectionMode, depthDirectiveTypeDefs, depthLimit, isUnsafeRegExp };
|
package/dist/index.d.ts
CHANGED
|
@@ -4,14 +4,17 @@ import { ValidationRule } from 'graphql';
|
|
|
4
4
|
/**
|
|
5
5
|
* Error extension codes for depth limit violations.
|
|
6
6
|
*/
|
|
7
|
-
declare const ERROR_CODES: {
|
|
7
|
+
declare const ERROR_CODES: Readonly<{
|
|
8
8
|
readonly IGNORE_RULE_ERROR: "IGNORE_RULE_ERROR";
|
|
9
9
|
readonly QUERY_TOO_DEEP: "QUERY_TOO_DEEP";
|
|
10
|
-
}
|
|
10
|
+
}>;
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Callback invoked after depth validation with per-operation depth results.
|
|
14
14
|
*
|
|
15
|
+
* The `depths` argument is always a plain object (Object.prototype) for
|
|
16
|
+
* compatibility with common object utilities and `hasOwnProperty` access.
|
|
17
|
+
*
|
|
15
18
|
* @example
|
|
16
19
|
* ```typescript
|
|
17
20
|
* const callback: DepthCallback = (depths) => {
|
|
@@ -172,9 +175,10 @@ type IgnoreMode = "exclude" | "skip";
|
|
|
172
175
|
* - `RegExp` — pattern match against field names
|
|
173
176
|
* - `function` — custom predicate receiving the field name
|
|
174
177
|
*
|
|
175
|
-
* **
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
+
* **Safety:** `RegExp` patterns are validated at setup time for common
|
|
179
|
+
* ReDoS (catastrophic backtracking) structures such as nested quantifiers
|
|
180
|
+
* (e.g., `/(a+)+$/`). Patterns flagged as unsafe are rejected with a
|
|
181
|
+
* descriptive `TypeError`. Use simple, linear-time patterns where possible.
|
|
178
182
|
*
|
|
179
183
|
* @example
|
|
180
184
|
* ```typescript
|
|
@@ -227,7 +231,7 @@ type IntrospectionMode = "all" | "none" | "typename";
|
|
|
227
231
|
*
|
|
228
232
|
* @param maxDepth - Maximum allowed depth for queries (must be a non-negative integer)
|
|
229
233
|
* @param options - Optional configuration for ignore rules, directives, and case sensitivity
|
|
230
|
-
* @param callback - Optional callback invoked with
|
|
234
|
+
* @param callback - Optional callback invoked with per-operation depths as a plain object payload
|
|
231
235
|
* @returns A GraphQL validation rule function
|
|
232
236
|
* @throws {Error} If `maxDepth` is not a non-negative integer
|
|
233
237
|
*
|
|
@@ -268,6 +272,35 @@ declare function depthLimit(maxDepth: number, options?: DepthLimitOptions, callb
|
|
|
268
272
|
* });
|
|
269
273
|
* ```
|
|
270
274
|
*/
|
|
271
|
-
declare const depthDirectiveTypeDefs = "
|
|
275
|
+
declare const depthDirectiveTypeDefs = "directive @depth(max: Int!) on FIELD_DEFINITION";
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Detects whether a `RegExp` is potentially vulnerable to catastrophic
|
|
279
|
+
* backtracking (ReDoS).
|
|
280
|
+
*
|
|
281
|
+
* Uses lightweight static analysis on the pattern source to identify
|
|
282
|
+
* two common ReDoS structures:
|
|
283
|
+
*
|
|
284
|
+
* 1. **Nested quantifiers** — A quantified group containing a quantified
|
|
285
|
+
* sub-expression (e.g., `(a+)+`, `(a*)*`, `(a+)*`, `(a{2,})+`).
|
|
286
|
+
* 2. **Overlapping alternation under a quantifier** — A quantified group
|
|
287
|
+
* whose alternatives can match the same input (e.g., `(a|a)+`,
|
|
288
|
+
* `(\w|\d)+`).
|
|
289
|
+
*
|
|
290
|
+
* This is a heuristic — it may reject some safe patterns and miss some
|
|
291
|
+
* unsafe ones. The goal is to catch the most common footguns without
|
|
292
|
+
* requiring a full regex parser.
|
|
293
|
+
*
|
|
294
|
+
* @param regex - The `RegExp` to analyze
|
|
295
|
+
* @returns A descriptive reason string if the pattern is potentially
|
|
296
|
+
* unsafe, or `null` if no issues are detected
|
|
297
|
+
*
|
|
298
|
+
* @example
|
|
299
|
+
* ```typescript
|
|
300
|
+
* isUnsafeRegExp(/(a+)+$/); // "nested quantifier: group with inner quantifier followed by outer quantifier"
|
|
301
|
+
* isUnsafeRegExp(/^internal/); // null (safe)
|
|
302
|
+
* ```
|
|
303
|
+
*/
|
|
304
|
+
declare function isUnsafeRegExp(regex: RegExp): string | null;
|
|
272
305
|
|
|
273
|
-
export { type DepthCallback, type DepthLimitFunction, type DepthLimitOptions, type DirectiveMode, ERROR_CODES, type IgnoreMode, type IgnoreRule, type IntrospectionMode, depthDirectiveTypeDefs, depthLimit };
|
|
306
|
+
export { type DepthCallback, type DepthLimitFunction, type DepthLimitOptions, type DirectiveMode, ERROR_CODES, type IgnoreMode, type IgnoreRule, type IntrospectionMode, depthDirectiveTypeDefs, depthLimit, isUnsafeRegExp };
|