graphql-query-depth-limit-esm 0.1.0 → 2.0.0
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/LICENSE +1 -1
- package/README.md +369 -348
- package/dist/index.cjs +557 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +273 -0
- package/dist/index.d.ts +269 -7
- package/dist/index.js +536 -9
- package/dist/index.js.map +1 -0
- package/package.json +74 -65
- package/dist/depthLimit.d.ts +0 -41
- package/dist/depthLimit.d.ts.map +0 -1
- package/dist/depthLimit.js +0 -273
- package/dist/index.d.ts.map +0 -1
- package/dist/types.d.ts +0 -25
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -1
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import * as graphql from 'graphql';
|
|
2
|
+
import { ValidationRule } from 'graphql';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Error extension codes for depth limit violations.
|
|
6
|
+
*/
|
|
7
|
+
declare const ERROR_CODES: {
|
|
8
|
+
readonly IGNORE_RULE_ERROR: "IGNORE_RULE_ERROR";
|
|
9
|
+
readonly QUERY_TOO_DEEP: "QUERY_TOO_DEEP";
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Callback invoked after depth validation with per-operation depth results.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const callback: DepthCallback = (depths) => {
|
|
18
|
+
* console.log(depths); // { "GetUser": 3, "ListPosts": 5 }
|
|
19
|
+
* };
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
type DepthCallback = (depths: Record<string, number>) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Function signature for the depthLimit validation rule factory.
|
|
25
|
+
*/
|
|
26
|
+
interface DepthLimitFunction {
|
|
27
|
+
(maxDepth: number, options?: DepthLimitOptions, callback?: DepthCallback): graphql.ValidationRule;
|
|
28
|
+
(maxDepth: number, callback?: DepthCallback): graphql.ValidationRule;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Configuration options for the depth limit validation rule.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* const options: DepthLimitOptions = {
|
|
36
|
+
* caseInsensitiveIgnore: true,
|
|
37
|
+
* directiveMode: "cap",
|
|
38
|
+
* ignore: ["metadata", /.*Connection$/],
|
|
39
|
+
* ignoreIntrospection: "typename",
|
|
40
|
+
* ignoreMode: "exclude",
|
|
41
|
+
* shortCircuit: true,
|
|
42
|
+
* useDirective: true,
|
|
43
|
+
* };
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
interface DepthLimitOptions {
|
|
47
|
+
/**
|
|
48
|
+
* Enable case-insensitive matching for string ignore rules.
|
|
49
|
+
*
|
|
50
|
+
* Does not affect `RegExp` rules — use the `i` flag on RegExp patterns
|
|
51
|
+
* for case-insensitive regex matching.
|
|
52
|
+
*
|
|
53
|
+
* @default false
|
|
54
|
+
*/
|
|
55
|
+
caseInsensitiveIgnore?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Controls how `@depth` directives interact with the global `maxDepth`.
|
|
58
|
+
*
|
|
59
|
+
* - `"cap"` (default): directives can only tighten below the global max
|
|
60
|
+
* - `"override"`: the first directive replaces the global max for its subtree
|
|
61
|
+
*
|
|
62
|
+
* @default "cap"
|
|
63
|
+
*/
|
|
64
|
+
directiveMode?: DirectiveMode;
|
|
65
|
+
/** Rule or array of rules defining which fields to skip during depth calculation. */
|
|
66
|
+
ignore?: IgnoreRule | IgnoreRule[];
|
|
67
|
+
/**
|
|
68
|
+
* Controls which introspection fields are ignored during depth calculation.
|
|
69
|
+
*
|
|
70
|
+
* - `"all"`: ignore every `__`-prefixed field and skip its entire subtree
|
|
71
|
+
* (regardless of `ignoreMode`)
|
|
72
|
+
* - `"typename"` (default): only ignore `__typename`
|
|
73
|
+
* - `"none"`: count all introspection fields toward depth
|
|
74
|
+
*
|
|
75
|
+
* **Note:** When set to `"all"`, introspection fields are always fully
|
|
76
|
+
* skipped — including their subtree — even when `ignoreMode` is `"exclude"`.
|
|
77
|
+
* This is intentional: `"all"` is a security hardening mode that completely
|
|
78
|
+
* eliminates introspection fields from depth calculation.
|
|
79
|
+
*
|
|
80
|
+
* @default "typename"
|
|
81
|
+
*/
|
|
82
|
+
ignoreIntrospection?: IntrospectionMode;
|
|
83
|
+
/**
|
|
84
|
+
* Controls whether ignored fields skip their entire subtree or only the depth increment.
|
|
85
|
+
*
|
|
86
|
+
* - `"exclude"` (default): only the depth increment is skipped; children are still traversed
|
|
87
|
+
* - `"skip"`: the field and its entire subtree are excluded from depth calculation
|
|
88
|
+
*
|
|
89
|
+
* **Warning:** Using `"exclude"` on a **recursive** field (e.g., `friends: [User]` where
|
|
90
|
+
* `User` has a `friends` field) effectively allows unbounded depth on that edge because
|
|
91
|
+
* the depth increment is suppressed at every level of recursion. If the ignored field
|
|
92
|
+
* appears in a self-referential chain, the depth counter never advances along that path.
|
|
93
|
+
* Use `"skip"` instead if unbounded traversal is not intended, or combine `"exclude"`
|
|
94
|
+
* with a `@depth` directive on the field to cap the subtree independently.
|
|
95
|
+
*
|
|
96
|
+
* @default "exclude"
|
|
97
|
+
*/
|
|
98
|
+
ignoreMode?: IgnoreMode;
|
|
99
|
+
/**
|
|
100
|
+
* Guard against unbounded depth from ignored recursive fields.
|
|
101
|
+
*
|
|
102
|
+
* When `true` and `ignoreMode` is `"exclude"`, the engine tracks which
|
|
103
|
+
* ignored field names have appeared on the current traversal path. If the
|
|
104
|
+
* same ignored field name is encountered again (indicating recursion),
|
|
105
|
+
* subsequent occurrences increment depth normally instead of being suppressed.
|
|
106
|
+
*
|
|
107
|
+
* This prevents the scenario where `ignoreMode: "exclude"` on a recursive
|
|
108
|
+
* field (e.g., `friends: [User]`) allows unbounded depth because the depth
|
|
109
|
+
* counter never advances.
|
|
110
|
+
*
|
|
111
|
+
* Has no effect when `ignoreMode` is `"skip"` (the subtree is already removed).
|
|
112
|
+
*
|
|
113
|
+
* **Note:** When no schema is available in the validation context, the
|
|
114
|
+
* recursion guard uses field names alone (without type prefixes). This
|
|
115
|
+
* means identically named fields on unrelated types may collide on the
|
|
116
|
+
* same path, causing conservative over-counting. For full type-aware
|
|
117
|
+
* tracking, ensure a schema is present.
|
|
118
|
+
*
|
|
119
|
+
* @default false
|
|
120
|
+
*/
|
|
121
|
+
limitIgnoredRecursion?: boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Bail immediately on the first depth violation instead of traversing
|
|
124
|
+
* the full query tree.
|
|
125
|
+
*
|
|
126
|
+
* By default, this is automatically determined:
|
|
127
|
+
* - `true` when no `callback` is provided (fastest path for validation-only use)
|
|
128
|
+
* - `false` when a `callback` is provided (full traversal needed for accurate depths)
|
|
129
|
+
*
|
|
130
|
+
* Set explicitly to override the automatic behavior. For example,
|
|
131
|
+
* `shortCircuit: false` without a callback traverses the full tree and
|
|
132
|
+
* reports exact depth in error messages rather than a lower-bound
|
|
133
|
+
* (`"at least N"`).
|
|
134
|
+
*
|
|
135
|
+
* @default undefined (auto-detected from callback presence)
|
|
136
|
+
*/
|
|
137
|
+
shortCircuit?: boolean;
|
|
138
|
+
/**
|
|
139
|
+
* Read `@depth(max: Int!)` directives from field definitions.
|
|
140
|
+
*
|
|
141
|
+
* Requires a schema in the validation context. When no schema is available
|
|
142
|
+
* (e.g., custom `ValidationContext` with `getSchema()` returning null),
|
|
143
|
+
* this option silently falls back to the global `maxDepth` because
|
|
144
|
+
* directives cannot be resolved without type information.
|
|
145
|
+
*
|
|
146
|
+
* @default false
|
|
147
|
+
*/
|
|
148
|
+
useDirective?: boolean;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Controls how `@depth` directives interact with the global `maxDepth`.
|
|
152
|
+
*
|
|
153
|
+
* - `"cap"` — directives can only tighten the limit below the global `maxDepth` (secure default)
|
|
154
|
+
* - `"override"` — the first directive replaces the global limit for its subtree
|
|
155
|
+
*/
|
|
156
|
+
type DirectiveMode = "cap" | "override";
|
|
157
|
+
/**
|
|
158
|
+
* Controls how ignored fields affect depth traversal.
|
|
159
|
+
*
|
|
160
|
+
* - `"exclude"` — skip the depth increment but still traverse children (secure default)
|
|
161
|
+
* - `"skip"` — skip the field and its entire subtree
|
|
162
|
+
*
|
|
163
|
+
* **Caveat:** `"exclude"` on recursive fields allows unbounded depth along
|
|
164
|
+
* ignored edges because the depth counter never advances. See {@link DepthLimitOptions.ignoreMode}
|
|
165
|
+
* for details and mitigation strategies.
|
|
166
|
+
*/
|
|
167
|
+
type IgnoreMode = "exclude" | "skip";
|
|
168
|
+
/**
|
|
169
|
+
* Rule for ignoring fields during depth calculation.
|
|
170
|
+
*
|
|
171
|
+
* - `string` — exact field name match (case-sensitive by default)
|
|
172
|
+
* - `RegExp` — pattern match against field names
|
|
173
|
+
* - `function` — custom predicate receiving the field name
|
|
174
|
+
*
|
|
175
|
+
* **Note:** User-provided `RegExp` patterns are executed against field names.
|
|
176
|
+
* Patterns with catastrophic backtracking (e.g., `/^(a+)+$/`) may cause
|
|
177
|
+
* performance issues. Use simple, linear-time patterns where possible.
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```typescript
|
|
181
|
+
* const rules: IgnoreRule[] = [
|
|
182
|
+
* "metadata",
|
|
183
|
+
* /^internal/,
|
|
184
|
+
* (name) => name.endsWith("Connection"),
|
|
185
|
+
* ];
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
type IgnoreRule = ((fieldName: string) => boolean) | RegExp | string;
|
|
189
|
+
/**
|
|
190
|
+
* Controls which introspection fields are ignored during depth calculation.
|
|
191
|
+
*
|
|
192
|
+
* - `"all"` — ignore every `__`-prefixed field and skip its entire subtree,
|
|
193
|
+
* regardless of `ignoreMode`
|
|
194
|
+
* - `"typename"` — only ignore `__typename` (secure default)
|
|
195
|
+
* - `"none"` — count all introspection fields toward depth
|
|
196
|
+
*
|
|
197
|
+
* **Note:** Scalar introspection fields (e.g., `__typename`) never increase
|
|
198
|
+
* depth regardless of this setting because only composite fields with nested
|
|
199
|
+
* selections contribute to depth. This setting controls whether such fields
|
|
200
|
+
* are _ignored_ (skipping the depth increment entirely), which matters when
|
|
201
|
+
* they appear as nested selections within composite fields.
|
|
202
|
+
*/
|
|
203
|
+
type IntrospectionMode = "all" | "none" | "typename";
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Creates a GraphQL validation rule that limits query depth.
|
|
207
|
+
*
|
|
208
|
+
* Prevents DoS attacks and resource exhaustion from excessively deep queries
|
|
209
|
+
* by enforcing a maximum depth on operations. Supports per-field overrides
|
|
210
|
+
* via the `@depth` directive, customizable ignore rules, and an optional
|
|
211
|
+
* callback for monitoring.
|
|
212
|
+
*
|
|
213
|
+
* Security considerations:
|
|
214
|
+
* - The global `maxDepth` is a hard ceiling by default (`directiveMode: "cap"`)
|
|
215
|
+
* - Correctly handles fragments reused at different depths
|
|
216
|
+
* - Circular fragment references are detected per-path
|
|
217
|
+
* - Only `__typename` is ignored by default (`ignoreIntrospection: "typename"`)
|
|
218
|
+
* - Short-circuits on first violation when no callback is provided
|
|
219
|
+
*
|
|
220
|
+
* Limitations:
|
|
221
|
+
* - Variables in `@depth` directives are not supported (falls back to global limit)
|
|
222
|
+
* - Field names are case-sensitive by default (use `caseInsensitiveIgnore` if needed)
|
|
223
|
+
* - `useDirective: true` requires a schema in the validation context; without one
|
|
224
|
+
* it silently falls back to the global limit (directives cannot be resolved)
|
|
225
|
+
* - RegExp ignore rules are executed against field names; patterns with catastrophic
|
|
226
|
+
* backtracking (e.g., `/^(a+)+$/`) may cause performance issues
|
|
227
|
+
*
|
|
228
|
+
* @param maxDepth - Maximum allowed depth for queries (must be a non-negative integer)
|
|
229
|
+
* @param options - Optional configuration for ignore rules, directives, and case sensitivity
|
|
230
|
+
* @param callback - Optional callback invoked with depth results for each operation
|
|
231
|
+
* @returns A GraphQL validation rule function
|
|
232
|
+
* @throws {Error} If `maxDepth` is not a non-negative integer
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* ```typescript
|
|
236
|
+
* import { depthLimit } from "graphql-query-depth-limit-esm";
|
|
237
|
+
* import { validate } from "graphql";
|
|
238
|
+
*
|
|
239
|
+
* const errors = validate(schema, document, [
|
|
240
|
+
* depthLimit(5, {
|
|
241
|
+
* ignore: ["friends", /.*Connection$/],
|
|
242
|
+
* useDirective: true,
|
|
243
|
+
* }),
|
|
244
|
+
* ]);
|
|
245
|
+
*
|
|
246
|
+
* const withCallback = depthLimit(5, (depths) => {
|
|
247
|
+
* console.log(depths);
|
|
248
|
+
* });
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
declare function depthLimit(maxDepth: number, callback?: DepthCallback): ValidationRule;
|
|
252
|
+
declare function depthLimit(maxDepth: number, options?: DepthLimitOptions, callback?: DepthCallback): ValidationRule;
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* GraphQL SDL type definition for the `@depth` directive.
|
|
256
|
+
*
|
|
257
|
+
* Include this in your schema definition to enable per-field depth
|
|
258
|
+
* overrides when using `depthLimit` with `{ useDirective: true }`.
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```ts
|
|
262
|
+
* import { makeExecutableSchema } from "@graphql-tools/schema";
|
|
263
|
+
* import { depthDirectiveTypeDefs } from "graphql-query-depth-limit-esm";
|
|
264
|
+
*
|
|
265
|
+
* const schema = makeExecutableSchema({
|
|
266
|
+
* typeDefs: [depthDirectiveTypeDefs, yourTypeDefs],
|
|
267
|
+
* resolvers,
|
|
268
|
+
* });
|
|
269
|
+
* ```
|
|
270
|
+
*/
|
|
271
|
+
declare const depthDirectiveTypeDefs = "\n directive @depth(max: Int!) on FIELD_DEFINITION\n";
|
|
272
|
+
|
|
273
|
+
export { type DepthCallback, type DepthLimitFunction, type DepthLimitOptions, type DirectiveMode, ERROR_CODES, type IgnoreMode, type IgnoreRule, type IntrospectionMode, depthDirectiveTypeDefs, depthLimit };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,273 @@
|
|
|
1
|
+
import * as graphql from 'graphql';
|
|
2
|
+
import { ValidationRule } from 'graphql';
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
|
-
*
|
|
5
|
+
* Error extension codes for depth limit violations.
|
|
6
|
+
*/
|
|
7
|
+
declare const ERROR_CODES: {
|
|
8
|
+
readonly IGNORE_RULE_ERROR: "IGNORE_RULE_ERROR";
|
|
9
|
+
readonly QUERY_TOO_DEEP: "QUERY_TOO_DEEP";
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Callback invoked after depth validation with per-operation depth results.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const callback: DepthCallback = (depths) => {
|
|
18
|
+
* console.log(depths); // { "GetUser": 3, "ListPosts": 5 }
|
|
19
|
+
* };
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
type DepthCallback = (depths: Record<string, number>) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Function signature for the depthLimit validation rule factory.
|
|
25
|
+
*/
|
|
26
|
+
interface DepthLimitFunction {
|
|
27
|
+
(maxDepth: number, options?: DepthLimitOptions, callback?: DepthCallback): graphql.ValidationRule;
|
|
28
|
+
(maxDepth: number, callback?: DepthCallback): graphql.ValidationRule;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Configuration options for the depth limit validation rule.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* const options: DepthLimitOptions = {
|
|
36
|
+
* caseInsensitiveIgnore: true,
|
|
37
|
+
* directiveMode: "cap",
|
|
38
|
+
* ignore: ["metadata", /.*Connection$/],
|
|
39
|
+
* ignoreIntrospection: "typename",
|
|
40
|
+
* ignoreMode: "exclude",
|
|
41
|
+
* shortCircuit: true,
|
|
42
|
+
* useDirective: true,
|
|
43
|
+
* };
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
interface DepthLimitOptions {
|
|
47
|
+
/**
|
|
48
|
+
* Enable case-insensitive matching for string ignore rules.
|
|
49
|
+
*
|
|
50
|
+
* Does not affect `RegExp` rules — use the `i` flag on RegExp patterns
|
|
51
|
+
* for case-insensitive regex matching.
|
|
52
|
+
*
|
|
53
|
+
* @default false
|
|
54
|
+
*/
|
|
55
|
+
caseInsensitiveIgnore?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Controls how `@depth` directives interact with the global `maxDepth`.
|
|
58
|
+
*
|
|
59
|
+
* - `"cap"` (default): directives can only tighten below the global max
|
|
60
|
+
* - `"override"`: the first directive replaces the global max for its subtree
|
|
61
|
+
*
|
|
62
|
+
* @default "cap"
|
|
63
|
+
*/
|
|
64
|
+
directiveMode?: DirectiveMode;
|
|
65
|
+
/** Rule or array of rules defining which fields to skip during depth calculation. */
|
|
66
|
+
ignore?: IgnoreRule | IgnoreRule[];
|
|
67
|
+
/**
|
|
68
|
+
* Controls which introspection fields are ignored during depth calculation.
|
|
69
|
+
*
|
|
70
|
+
* - `"all"`: ignore every `__`-prefixed field and skip its entire subtree
|
|
71
|
+
* (regardless of `ignoreMode`)
|
|
72
|
+
* - `"typename"` (default): only ignore `__typename`
|
|
73
|
+
* - `"none"`: count all introspection fields toward depth
|
|
74
|
+
*
|
|
75
|
+
* **Note:** When set to `"all"`, introspection fields are always fully
|
|
76
|
+
* skipped — including their subtree — even when `ignoreMode` is `"exclude"`.
|
|
77
|
+
* This is intentional: `"all"` is a security hardening mode that completely
|
|
78
|
+
* eliminates introspection fields from depth calculation.
|
|
79
|
+
*
|
|
80
|
+
* @default "typename"
|
|
81
|
+
*/
|
|
82
|
+
ignoreIntrospection?: IntrospectionMode;
|
|
83
|
+
/**
|
|
84
|
+
* Controls whether ignored fields skip their entire subtree or only the depth increment.
|
|
85
|
+
*
|
|
86
|
+
* - `"exclude"` (default): only the depth increment is skipped; children are still traversed
|
|
87
|
+
* - `"skip"`: the field and its entire subtree are excluded from depth calculation
|
|
88
|
+
*
|
|
89
|
+
* **Warning:** Using `"exclude"` on a **recursive** field (e.g., `friends: [User]` where
|
|
90
|
+
* `User` has a `friends` field) effectively allows unbounded depth on that edge because
|
|
91
|
+
* the depth increment is suppressed at every level of recursion. If the ignored field
|
|
92
|
+
* appears in a self-referential chain, the depth counter never advances along that path.
|
|
93
|
+
* Use `"skip"` instead if unbounded traversal is not intended, or combine `"exclude"`
|
|
94
|
+
* with a `@depth` directive on the field to cap the subtree independently.
|
|
95
|
+
*
|
|
96
|
+
* @default "exclude"
|
|
97
|
+
*/
|
|
98
|
+
ignoreMode?: IgnoreMode;
|
|
99
|
+
/**
|
|
100
|
+
* Guard against unbounded depth from ignored recursive fields.
|
|
101
|
+
*
|
|
102
|
+
* When `true` and `ignoreMode` is `"exclude"`, the engine tracks which
|
|
103
|
+
* ignored field names have appeared on the current traversal path. If the
|
|
104
|
+
* same ignored field name is encountered again (indicating recursion),
|
|
105
|
+
* subsequent occurrences increment depth normally instead of being suppressed.
|
|
106
|
+
*
|
|
107
|
+
* This prevents the scenario where `ignoreMode: "exclude"` on a recursive
|
|
108
|
+
* field (e.g., `friends: [User]`) allows unbounded depth because the depth
|
|
109
|
+
* counter never advances.
|
|
110
|
+
*
|
|
111
|
+
* Has no effect when `ignoreMode` is `"skip"` (the subtree is already removed).
|
|
112
|
+
*
|
|
113
|
+
* **Note:** When no schema is available in the validation context, the
|
|
114
|
+
* recursion guard uses field names alone (without type prefixes). This
|
|
115
|
+
* means identically named fields on unrelated types may collide on the
|
|
116
|
+
* same path, causing conservative over-counting. For full type-aware
|
|
117
|
+
* tracking, ensure a schema is present.
|
|
118
|
+
*
|
|
119
|
+
* @default false
|
|
120
|
+
*/
|
|
121
|
+
limitIgnoredRecursion?: boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Bail immediately on the first depth violation instead of traversing
|
|
124
|
+
* the full query tree.
|
|
125
|
+
*
|
|
126
|
+
* By default, this is automatically determined:
|
|
127
|
+
* - `true` when no `callback` is provided (fastest path for validation-only use)
|
|
128
|
+
* - `false` when a `callback` is provided (full traversal needed for accurate depths)
|
|
129
|
+
*
|
|
130
|
+
* Set explicitly to override the automatic behavior. For example,
|
|
131
|
+
* `shortCircuit: false` without a callback traverses the full tree and
|
|
132
|
+
* reports exact depth in error messages rather than a lower-bound
|
|
133
|
+
* (`"at least N"`).
|
|
134
|
+
*
|
|
135
|
+
* @default undefined (auto-detected from callback presence)
|
|
136
|
+
*/
|
|
137
|
+
shortCircuit?: boolean;
|
|
138
|
+
/**
|
|
139
|
+
* Read `@depth(max: Int!)` directives from field definitions.
|
|
140
|
+
*
|
|
141
|
+
* Requires a schema in the validation context. When no schema is available
|
|
142
|
+
* (e.g., custom `ValidationContext` with `getSchema()` returning null),
|
|
143
|
+
* this option silently falls back to the global `maxDepth` because
|
|
144
|
+
* directives cannot be resolved without type information.
|
|
145
|
+
*
|
|
146
|
+
* @default false
|
|
147
|
+
*/
|
|
148
|
+
useDirective?: boolean;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Controls how `@depth` directives interact with the global `maxDepth`.
|
|
152
|
+
*
|
|
153
|
+
* - `"cap"` — directives can only tighten the limit below the global `maxDepth` (secure default)
|
|
154
|
+
* - `"override"` — the first directive replaces the global limit for its subtree
|
|
155
|
+
*/
|
|
156
|
+
type DirectiveMode = "cap" | "override";
|
|
157
|
+
/**
|
|
158
|
+
* Controls how ignored fields affect depth traversal.
|
|
159
|
+
*
|
|
160
|
+
* - `"exclude"` — skip the depth increment but still traverse children (secure default)
|
|
161
|
+
* - `"skip"` — skip the field and its entire subtree
|
|
162
|
+
*
|
|
163
|
+
* **Caveat:** `"exclude"` on recursive fields allows unbounded depth along
|
|
164
|
+
* ignored edges because the depth counter never advances. See {@link DepthLimitOptions.ignoreMode}
|
|
165
|
+
* for details and mitigation strategies.
|
|
166
|
+
*/
|
|
167
|
+
type IgnoreMode = "exclude" | "skip";
|
|
168
|
+
/**
|
|
169
|
+
* Rule for ignoring fields during depth calculation.
|
|
170
|
+
*
|
|
171
|
+
* - `string` — exact field name match (case-sensitive by default)
|
|
172
|
+
* - `RegExp` — pattern match against field names
|
|
173
|
+
* - `function` — custom predicate receiving the field name
|
|
174
|
+
*
|
|
175
|
+
* **Note:** User-provided `RegExp` patterns are executed against field names.
|
|
176
|
+
* Patterns with catastrophic backtracking (e.g., `/^(a+)+$/`) may cause
|
|
177
|
+
* performance issues. Use simple, linear-time patterns where possible.
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```typescript
|
|
181
|
+
* const rules: IgnoreRule[] = [
|
|
182
|
+
* "metadata",
|
|
183
|
+
* /^internal/,
|
|
184
|
+
* (name) => name.endsWith("Connection"),
|
|
185
|
+
* ];
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
type IgnoreRule = ((fieldName: string) => boolean) | RegExp | string;
|
|
189
|
+
/**
|
|
190
|
+
* Controls which introspection fields are ignored during depth calculation.
|
|
191
|
+
*
|
|
192
|
+
* - `"all"` — ignore every `__`-prefixed field and skip its entire subtree,
|
|
193
|
+
* regardless of `ignoreMode`
|
|
194
|
+
* - `"typename"` — only ignore `__typename` (secure default)
|
|
195
|
+
* - `"none"` — count all introspection fields toward depth
|
|
196
|
+
*
|
|
197
|
+
* **Note:** Scalar introspection fields (e.g., `__typename`) never increase
|
|
198
|
+
* depth regardless of this setting because only composite fields with nested
|
|
199
|
+
* selections contribute to depth. This setting controls whether such fields
|
|
200
|
+
* are _ignored_ (skipping the depth increment entirely), which matters when
|
|
201
|
+
* they appear as nested selections within composite fields.
|
|
202
|
+
*/
|
|
203
|
+
type IntrospectionMode = "all" | "none" | "typename";
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Creates a GraphQL validation rule that limits query depth.
|
|
207
|
+
*
|
|
208
|
+
* Prevents DoS attacks and resource exhaustion from excessively deep queries
|
|
209
|
+
* by enforcing a maximum depth on operations. Supports per-field overrides
|
|
210
|
+
* via the `@depth` directive, customizable ignore rules, and an optional
|
|
211
|
+
* callback for monitoring.
|
|
212
|
+
*
|
|
213
|
+
* Security considerations:
|
|
214
|
+
* - The global `maxDepth` is a hard ceiling by default (`directiveMode: "cap"`)
|
|
215
|
+
* - Correctly handles fragments reused at different depths
|
|
216
|
+
* - Circular fragment references are detected per-path
|
|
217
|
+
* - Only `__typename` is ignored by default (`ignoreIntrospection: "typename"`)
|
|
218
|
+
* - Short-circuits on first violation when no callback is provided
|
|
219
|
+
*
|
|
220
|
+
* Limitations:
|
|
221
|
+
* - Variables in `@depth` directives are not supported (falls back to global limit)
|
|
222
|
+
* - Field names are case-sensitive by default (use `caseInsensitiveIgnore` if needed)
|
|
223
|
+
* - `useDirective: true` requires a schema in the validation context; without one
|
|
224
|
+
* it silently falls back to the global limit (directives cannot be resolved)
|
|
225
|
+
* - RegExp ignore rules are executed against field names; patterns with catastrophic
|
|
226
|
+
* backtracking (e.g., `/^(a+)+$/`) may cause performance issues
|
|
227
|
+
*
|
|
228
|
+
* @param maxDepth - Maximum allowed depth for queries (must be a non-negative integer)
|
|
229
|
+
* @param options - Optional configuration for ignore rules, directives, and case sensitivity
|
|
230
|
+
* @param callback - Optional callback invoked with depth results for each operation
|
|
231
|
+
* @returns A GraphQL validation rule function
|
|
232
|
+
* @throws {Error} If `maxDepth` is not a non-negative integer
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* ```typescript
|
|
236
|
+
* import { depthLimit } from "graphql-query-depth-limit-esm";
|
|
237
|
+
* import { validate } from "graphql";
|
|
238
|
+
*
|
|
239
|
+
* const errors = validate(schema, document, [
|
|
240
|
+
* depthLimit(5, {
|
|
241
|
+
* ignore: ["friends", /.*Connection$/],
|
|
242
|
+
* useDirective: true,
|
|
243
|
+
* }),
|
|
244
|
+
* ]);
|
|
245
|
+
*
|
|
246
|
+
* const withCallback = depthLimit(5, (depths) => {
|
|
247
|
+
* console.log(depths);
|
|
248
|
+
* });
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
declare function depthLimit(maxDepth: number, callback?: DepthCallback): ValidationRule;
|
|
252
|
+
declare function depthLimit(maxDepth: number, options?: DepthLimitOptions, callback?: DepthCallback): ValidationRule;
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* GraphQL SDL type definition for the `@depth` directive.
|
|
256
|
+
*
|
|
257
|
+
* Include this in your schema definition to enable per-field depth
|
|
258
|
+
* overrides when using `depthLimit` with `{ useDirective: true }`.
|
|
3
259
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```ts
|
|
262
|
+
* import { makeExecutableSchema } from "@graphql-tools/schema";
|
|
263
|
+
* import { depthDirectiveTypeDefs } from "graphql-query-depth-limit-esm";
|
|
6
264
|
*
|
|
7
|
-
*
|
|
265
|
+
* const schema = makeExecutableSchema({
|
|
266
|
+
* typeDefs: [depthDirectiveTypeDefs, yourTypeDefs],
|
|
267
|
+
* resolvers,
|
|
268
|
+
* });
|
|
269
|
+
* ```
|
|
8
270
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
271
|
+
declare const depthDirectiveTypeDefs = "\n directive @depth(max: Int!) on FIELD_DEFINITION\n";
|
|
272
|
+
|
|
273
|
+
export { type DepthCallback, type DepthLimitFunction, type DepthLimitOptions, type DirectiveMode, ERROR_CODES, type IgnoreMode, type IgnoreRule, type IntrospectionMode, depthDirectiveTypeDefs, depthLimit };
|