import * as graphql from 'graphql'; import { ValidationRule } from 'graphql'; /** * Error extension codes for depth limit violations. */ declare const ERROR_CODES: Readonly<{ readonly IGNORE_RULE_ERROR: "IGNORE_RULE_ERROR"; readonly NODE_LIMIT_EXCEEDED: "NODE_LIMIT_EXCEEDED"; readonly QUERY_TOO_DEEP: "QUERY_TOO_DEEP"; }>; /** * Callback invoked after depth validation with per-operation depth results. * * The `depths` argument is always a plain object (Object.prototype) for * compatibility with common object utilities and `hasOwnProperty` access. * * @example * ```typescript * const callback: DepthCallback = (depths) => { * console.log(depths); // { "GetUser": 3, "ListPosts": 5 } * }; * ``` */ type DepthCallback = (depths: Record) => void; /** * Function signature for the depthLimit validation rule factory. */ interface DepthLimitFunction { (maxDepth: number, options?: DepthLimitOptions, callback?: DepthCallback): graphql.ValidationRule; (maxDepth: number, callback?: DepthCallback): graphql.ValidationRule; } /** * Configuration options for the depth limit validation rule. * * @example * ```typescript * const options: DepthLimitOptions = { * caseInsensitiveIgnore: true, * directiveMode: "cap", * ignore: ["metadata", /.*Connection$/], * ignoreIntrospection: "typename", * ignoreMode: "exclude", * shortCircuit: true, * useDirective: true, * }; * ``` */ interface DepthLimitOptions { /** Maximum expanded selection nodes per operation, including fragment reuse. @default 10000 */ maxNodes?: number; /** * Enable case-insensitive matching for string ignore rules. * * Does not affect `RegExp` rules — use the `i` flag on RegExp patterns * for case-insensitive regex matching. * * @default false */ caseInsensitiveIgnore?: boolean; /** * Controls how `@depth` directives interact with the global `maxDepth`. * * - `"cap"` (default): directives can only tighten below the global max * - `"override"`: the first directive replaces the global max for its subtree * * @default "cap" */ directiveMode?: DirectiveMode; /** Rule or array of rules defining which fields to skip during depth calculation. */ ignore?: IgnoreRule | IgnoreRule[]; /** * Controls which introspection fields are ignored during depth calculation. * * - `"all"`: ignore every `__`-prefixed field and skip its entire subtree * (regardless of `ignoreMode`) * - `"typename"` (default): only ignore `__typename` * - `"none"`: count all introspection fields toward depth * * **Note:** When set to `"all"`, introspection fields are always fully * skipped — including their subtree — even when `ignoreMode` is `"exclude"`. * This excludes those subtrees from depth protection; it does not disable * introspection. * * @default "typename" */ ignoreIntrospection?: IntrospectionMode; /** * Controls whether ignored fields skip their entire subtree or only the depth increment. * * - `"exclude"` (default): only the depth increment is skipped; children are still traversed * - `"skip"`: the field and its entire subtree are excluded from depth calculation * * **Warning:** Using `"exclude"` on a **recursive** field (e.g., `friends: [User]` where * `User` has a `friends` field) effectively allows unbounded depth on that edge because * the depth increment is suppressed at every level of recursion. If the ignored field * appears in a self-referential chain, the depth counter never advances along that path. * Enable `limitIgnoredRecursion` or remove the recursive field from `ignore` * to keep depth protection along that edge. `"skip"` removes all depth protection * for the subtree. The `maxNodes` budget still bounds expanded traversal. * * @default "exclude" */ ignoreMode?: IgnoreMode; /** * Guard against unbounded depth from ignored recursive fields. * * When `true` and `ignoreMode` is `"exclude"`, the engine tracks which * ignored field names have appeared on the current traversal path. If the * same ignored field name is encountered again (indicating recursion), * subsequent occurrences increment depth normally instead of being suppressed. * * This prevents the scenario where `ignoreMode: "exclude"` on a recursive * field (e.g., `friends: [User]`) allows unbounded depth because the depth * counter never advances. * * Has no effect when `ignoreMode` is `"skip"` (the subtree is already removed). * * **Note:** When no schema is available in the validation context, the * recursion guard uses field names alone (without type prefixes). This * means identically named fields on unrelated types may collide on the * same path, causing conservative over-counting. For full type-aware * tracking, ensure a schema is present. * * @default false */ limitIgnoredRecursion?: boolean; /** * Bail immediately on the first depth violation instead of traversing * the full query tree. * * By default, this is automatically determined: * - `true` when no `callback` is provided (fastest path for validation-only use) * - `false` when a `callback` is provided (full traversal needed for accurate depths) * * Set explicitly to override the automatic behavior. For example, * `shortCircuit: false` without a callback traverses the full tree and * reports exact depth in error messages rather than a lower-bound * (`"at least N"`). * * @default undefined (auto-detected from callback presence) */ shortCircuit?: boolean; /** * Read `@depth(max: Int!)` directives from field definitions. * * Requires a schema in the validation context. When no schema is available * (e.g., custom `ValidationContext` with `getSchema()` returning null), * this option silently falls back to the global `maxDepth` because * directives cannot be resolved without type information. * * @default false */ useDirective?: boolean; } /** * Controls how `@depth` directives interact with the global `maxDepth`. * * - `"cap"` — directives can only tighten the limit below the global `maxDepth` (secure default) * - `"override"` — the first directive replaces the global limit for its subtree */ type DirectiveMode = "cap" | "override"; /** * Controls how ignored fields affect depth traversal. * * - `"exclude"` — skip the depth increment but still traverse children (secure default) * - `"skip"` — skip the field and its entire subtree * * **Caveat:** `"exclude"` on recursive fields allows unbounded depth along * ignored edges because the depth counter never advances. See {@link DepthLimitOptions.ignoreMode} * for details and mitigation strategies. */ type IgnoreMode = "exclude" | "skip"; /** * Rule for ignoring fields during depth calculation. * * - `string` — exact field name match (case-sensitive by default) * - `RegExp` — pattern match against field names * - `function` — custom predicate receiving the field name * * **Safety:** `RegExp` patterns are validated at setup time for common * ReDoS (catastrophic backtracking) structures such as nested quantifiers * (e.g., `/(a+)+$/`). Patterns flagged as unsafe are rejected with a * descriptive `TypeError`. Use simple, linear-time patterns where possible. * * @example * ```typescript * const rules: IgnoreRule[] = [ * "metadata", * /^internal/, * (name) => name.endsWith("Connection"), * ]; * ``` */ type IgnoreRule = ((fieldName: string) => boolean) | RegExp | string; /** * Controls which introspection fields are ignored during depth calculation. * * - `"all"` — ignore every `__`-prefixed field and skip its entire subtree, * regardless of `ignoreMode` * - `"typename"` — only ignore `__typename` (secure default) * - `"none"` — count all introspection fields toward depth * * **Note:** Scalar introspection fields (e.g., `__typename`) never increase * depth regardless of this setting because only composite fields with nested * selections contribute to depth. This setting controls whether such fields * are _ignored_ (skipping the depth increment entirely), which matters when * they appear as nested selections within composite fields. */ type IntrospectionMode = "all" | "none" | "typename"; /** * Creates a GraphQL validation rule that limits query depth. * * Prevents DoS attacks and resource exhaustion from excessively deep queries * by enforcing a maximum depth on operations. Supports per-field overrides * via the `@depth` directive, customizable ignore rules, and an optional * callback for monitoring. * * Security considerations: * - The global `maxDepth` is a hard ceiling by default (`directiveMode: "cap"`) * - Correctly handles fragments reused at different depths * - Circular fragment references are detected per-path * - Only `__typename` is ignored by default (`ignoreIntrospection: "typename"`) * - Short-circuits on first violation when no callback is provided * * Limitations: * - Variables in `@depth` directives are not supported (falls back to global limit) * - Field names are case-sensitive by default (use `caseInsensitiveIgnore` if needed) * - `useDirective: true` requires a schema in the validation context; without one * it silently falls back to the global limit (directives cannot be resolved) * * @param maxDepth - Maximum allowed depth for queries (must be a non-negative integer) * @param options - Optional configuration for ignore rules, directives, and case sensitivity * @param callback - Optional callback invoked with per-operation depths as a plain object payload * @returns A GraphQL validation rule function * @throws {Error} If `maxDepth` is not a non-negative integer * * @example * ```typescript * import { depthLimit } from "graphql-query-depth-limit-esm"; * import { validate } from "graphql"; * * const errors = validate(schema, document, [ * depthLimit(5, { * ignore: ["friends", /.*Connection$/], * useDirective: true, * }), * ]); * * const withCallback = depthLimit(5, (depths) => { * console.log(depths); * }); * ``` */ declare function depthLimit(maxDepth: number, callback?: DepthCallback): ValidationRule; declare function depthLimit(maxDepth: number, options?: DepthLimitOptions, callback?: DepthCallback): ValidationRule; /** * GraphQL SDL type definition for the `@depth` directive. * * Include this in your schema definition to enable per-field depth * overrides when using `depthLimit` with `{ useDirective: true }`. * * @example * ```ts * import { makeExecutableSchema } from "@graphql-tools/schema"; * import { depthDirectiveTypeDefs } from "graphql-query-depth-limit-esm"; * * const schema = makeExecutableSchema({ * typeDefs: [depthDirectiveTypeDefs, yourTypeDefs], * resolvers, * }); * ``` */ declare const depthDirectiveTypeDefs = "directive @depth(max: Int!) on FIELD_DEFINITION"; /** * Detects whether a `RegExp` is potentially vulnerable to catastrophic * backtracking (ReDoS). * * Uses lightweight static analysis on the pattern source to identify * two common ReDoS structures: * * 1. **Nested quantifiers** — A quantified group containing a quantified * sub-expression (e.g., `(a+)+`, `(a*)*`, `(a+)*`, `(a{2,})+`). * 2. **Overlapping alternation under a quantifier** — A quantified group * whose alternatives can match the same input (e.g., `(a|a)+`, * `(\w|\d)+`). * * This is a heuristic — it may reject some safe patterns and miss some * unsafe ones. The goal is to catch the most common footguns without * requiring a full regex parser. * * @param regex - The `RegExp` to analyze * @returns A descriptive reason string if the pattern is potentially * unsafe, or `null` if no issues are detected * * @example * ```typescript * isUnsafeRegExp(/(a+)+$/); // "nested quantifier: group with inner quantifier followed by outer quantifier" * isUnsafeRegExp(/^internal/); // null (safe) * ``` */ declare function isUnsafeRegExp(regex: RegExp): string | null; export { ERROR_CODES, depthDirectiveTypeDefs, depthLimit, isUnsafeRegExp }; export type { DepthCallback, DepthLimitFunction, DepthLimitOptions, DirectiveMode, IgnoreMode, IgnoreRule, IntrospectionMode };