graphql-query-depth-limit-esm 2.0.1 → 2.0.4
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 +43 -5
- package/dist/index.cjs +213 -127
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -7
- package/dist/index.d.ts +35 -7
- package/dist/index.js +211 -126
- package/dist/index.js.map +1 -1
- package/package.json +9 -4
package/README.md
CHANGED
|
@@ -11,7 +11,19 @@
|
|
|
11
11
|
|
|
12
12
|
Production-ready GraphQL query depth limiting as a validation rule. Prevents denial-of-service attacks from deeply nested queries by enforcing a configurable maximum depth.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
**[Interactive demo](https://lafittemehdy.github.io/graphql-query-depth-limit-esm/)** — see depth analysis in action with preset queries, an AST tree viewer, and a depth gauge.
|
|
15
|
+
|
|
16
|
+
## Interactive Demo
|
|
17
|
+
|
|
18
|
+
**[Try it live](https://lafittemehdy.github.io/graphql-query-depth-limit-esm/)** or run locally:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
cd examples/visualization
|
|
22
|
+
npm install
|
|
23
|
+
npm run dev
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Includes preset queries (simple lookups through deeply nested attacks), an AST tree viewer with depth badges, and a depth gauge with pass/fail verdict.
|
|
15
27
|
|
|
16
28
|
## Features
|
|
17
29
|
|
|
@@ -29,18 +41,18 @@ Explore the library's internal function architecture with the **[interactive nod
|
|
|
29
41
|
## Installation
|
|
30
42
|
|
|
31
43
|
```bash
|
|
32
|
-
|
|
44
|
+
npm install graphql-query-depth-limit-esm graphql
|
|
33
45
|
```
|
|
34
46
|
|
|
35
47
|
```bash
|
|
36
|
-
|
|
48
|
+
pnpm add graphql-query-depth-limit-esm graphql
|
|
37
49
|
```
|
|
38
50
|
|
|
39
51
|
```bash
|
|
40
52
|
yarn add graphql-query-depth-limit-esm graphql
|
|
41
53
|
```
|
|
42
54
|
|
|
43
|
-
##
|
|
55
|
+
## Quickstart
|
|
44
56
|
|
|
45
57
|
```ts
|
|
46
58
|
import { specifiedRules, validate } from "graphql";
|
|
@@ -302,7 +314,7 @@ Monitor query depths with an optional [`callback`](#depthcallback) that receives
|
|
|
302
314
|
|
|
303
315
|
```ts
|
|
304
316
|
const rule = depthLimit(10, {}, (depths) => {
|
|
305
|
-
// { "GetUser": 3, "ListPosts": 5, "anonymous": 2 }
|
|
317
|
+
// { "GetUser": 3, "ListPosts": 5, "[anonymous]": 2 }
|
|
306
318
|
for (const [operation, depth] of Object.entries(depths)) {
|
|
307
319
|
console.log(`Operation "${operation}" has depth ${depth}`);
|
|
308
320
|
}
|
|
@@ -482,6 +494,21 @@ query {
|
|
|
482
494
|
# Maximum depth: 3
|
|
483
495
|
```
|
|
484
496
|
|
|
497
|
+
## Performance
|
|
498
|
+
|
|
499
|
+
The depth engine uses **iterative DFS** (not recursion) to prevent stack overflow on adversarial queries and to keep traversal overhead constant regardless of nesting depth.
|
|
500
|
+
|
|
501
|
+
| Characteristic | Detail |
|
|
502
|
+
|---|---|
|
|
503
|
+
| **Algorithm** | Iterative depth-first search with explicit stack |
|
|
504
|
+
| **Time complexity** | O(n) where n = total selection nodes in the query |
|
|
505
|
+
| **Short-circuit** | Stops on first violation when no callback is provided |
|
|
506
|
+
| **Fragment cycles** | Detected per-path with minimal Set allocations |
|
|
507
|
+
| **Stack safety** | No recursion — immune to stack overflow on deep queries |
|
|
508
|
+
| **Runtime dependencies** | Zero — only `graphql` as a peer dependency |
|
|
509
|
+
|
|
510
|
+
Typical validation completes in **sub-millisecond** time for standard queries (< 50 fields). Even adversarial queries with hundreds of nested selections are analyzed in **single-digit milliseconds** thanks to early termination and per-path fragment cycle detection.
|
|
511
|
+
|
|
485
512
|
## Migrating from v1 to v2
|
|
486
513
|
|
|
487
514
|
v2 introduces three **breaking changes** with more secure defaults:
|
|
@@ -518,6 +545,17 @@ depthLimit(10, { directiveMode: "override", useDirective: true });
|
|
|
518
545
|
|
|
519
546
|
**Impact:** This is transparent to most users. The only observable difference is that error messages may report the depth at the first violation rather than the deepest violation when multiple branches exceed the limit. If you need the true maximum depth, provide a callback.
|
|
520
547
|
|
|
548
|
+
## Related Packages
|
|
549
|
+
|
|
550
|
+
This package is part of a suite of GraphQL security tools that work independently or together to protect your API:
|
|
551
|
+
|
|
552
|
+
| Package | Purpose |
|
|
553
|
+
|---|---|
|
|
554
|
+
| [`graphql-query-complexity-esm`](https://github.com/lafittemehdy/graphql-query-complexity-esm) | Complexity analysis — assign cost scores to fields and reject expensive queries |
|
|
555
|
+
| [`graphql-rate-limit-redis-esm`](https://github.com/lafittemehdy/graphql-rate-limit-redis-esm) | Rate limiting — Redis-backed per-field rate limiting via `@rateLimit` directive |
|
|
556
|
+
|
|
557
|
+
**Recommended layering:** Use depth limiting as a fast, cheap first gate, complexity analysis for fine-grained cost control, and rate limiting for per-client throttling.
|
|
558
|
+
|
|
521
559
|
## License
|
|
522
560
|
|
|
523
561
|
[MIT](LICENSE)
|
package/dist/index.cjs
CHANGED
|
@@ -22,7 +22,8 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
ERROR_CODES: () => ERROR_CODES,
|
|
24
24
|
depthDirectiveTypeDefs: () => depthDirectiveTypeDefs,
|
|
25
|
-
depthLimit: () => depthLimit
|
|
25
|
+
depthLimit: () => depthLimit,
|
|
26
|
+
isUnsafeRegExp: () => isUnsafeRegExp
|
|
26
27
|
});
|
|
27
28
|
module.exports = __toCommonJS(index_exports);
|
|
28
29
|
|
|
@@ -40,9 +41,10 @@ var import_graphql2 = require("graphql");
|
|
|
40
41
|
|
|
41
42
|
// src/directives.ts
|
|
42
43
|
var import_graphql = require("graphql");
|
|
43
|
-
var depthDirectiveTypeDefs =
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
var depthDirectiveTypeDefs = (
|
|
45
|
+
/* GraphQL */
|
|
46
|
+
`directive @depth(max: Int!) on FIELD_DEFINITION`
|
|
47
|
+
);
|
|
46
48
|
function getDepthFromDirective(field) {
|
|
47
49
|
if (!field?.astNode?.directives) {
|
|
48
50
|
return void 0;
|
|
@@ -62,6 +64,83 @@ function getDepthFromDirective(field) {
|
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
// src/ignore.ts
|
|
67
|
+
var QUANTIFIER_CHARS = /* @__PURE__ */ new Set(["+", "*", "?"]);
|
|
68
|
+
var BRACE_QUANTIFIER = /^\{(\d+)(?:,(\d*))?\}/;
|
|
69
|
+
function isUnsafeRegExp(regex) {
|
|
70
|
+
const source = regex.source;
|
|
71
|
+
const length = source.length;
|
|
72
|
+
const groupStack = [];
|
|
73
|
+
for (let i = 0; i < length; i++) {
|
|
74
|
+
const char = source.charAt(i);
|
|
75
|
+
if (char === "\\") {
|
|
76
|
+
i++;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (char === "[") {
|
|
80
|
+
while (i < length && source[i] !== "]") {
|
|
81
|
+
if (source[i] === "\\") i++;
|
|
82
|
+
i++;
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (char === "(") {
|
|
87
|
+
groupStack.push(false);
|
|
88
|
+
if (source[i + 1] === "?") {
|
|
89
|
+
i++;
|
|
90
|
+
if (source[i + 1] === "<" && (source[i + 2] === "=" || source[i + 2] === "!")) {
|
|
91
|
+
i += 2;
|
|
92
|
+
} else if (source[i + 1] === ":" || source[i + 1] === "=" || source[i + 1] === "!") {
|
|
93
|
+
i++;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (char === ")") {
|
|
99
|
+
const groupHadQuantifier = groupStack.pop() ?? false;
|
|
100
|
+
if (groupHadQuantifier && isFollowedByQuantifier(source, i + 1, length)) {
|
|
101
|
+
return "nested quantifier: group with inner quantifier followed by outer quantifier";
|
|
102
|
+
}
|
|
103
|
+
if (groupStack.length > 0 && isFollowedByQuantifier(source, i + 1, length)) {
|
|
104
|
+
groupStack[groupStack.length - 1] = true;
|
|
105
|
+
}
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (QUANTIFIER_CHARS.has(char) || char === "{") {
|
|
109
|
+
if (char === "{") {
|
|
110
|
+
const match = BRACE_QUANTIFIER.exec(source.slice(i));
|
|
111
|
+
if (!match) continue;
|
|
112
|
+
const minStr = match[1] ?? "0";
|
|
113
|
+
const min = Number.parseInt(minStr, 10);
|
|
114
|
+
const fullMatch = match[0] ?? "";
|
|
115
|
+
const hasComma = fullMatch.includes(",");
|
|
116
|
+
if (!hasComma && min <= 1) continue;
|
|
117
|
+
if (hasComma && match[2] !== void 0 && match[2] !== "" && Number.parseInt(match[2], 10) <= 1)
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (groupStack.length > 0) {
|
|
121
|
+
groupStack[groupStack.length - 1] = true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
function isFollowedByQuantifier(source, pos, length) {
|
|
128
|
+
if (pos >= length) return false;
|
|
129
|
+
const char = source[pos];
|
|
130
|
+
if (char === "+" || char === "*") return true;
|
|
131
|
+
if (char === "{") {
|
|
132
|
+
const match = BRACE_QUANTIFIER.exec(source.slice(pos));
|
|
133
|
+
if (match) {
|
|
134
|
+
const minStr = match[1] ?? "0";
|
|
135
|
+
const min = Number.parseInt(minStr, 10);
|
|
136
|
+
const fullMatch = match[0] ?? "";
|
|
137
|
+
const hasComma = fullMatch.includes(",");
|
|
138
|
+
if (!hasComma && min <= 1) return false;
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
65
144
|
function shouldIgnoreField(fieldName, ignore, caseInsensitive = false, introspectionMode = "typename") {
|
|
66
145
|
if (introspectionMode === "all" && fieldName.startsWith("__")) {
|
|
67
146
|
return true;
|
|
@@ -132,8 +211,11 @@ function calculateDepth(caches, config, fragments, maxDepth, node, parentType, s
|
|
|
132
211
|
if (!frame.node.selectionSet) {
|
|
133
212
|
continue;
|
|
134
213
|
}
|
|
135
|
-
|
|
136
|
-
|
|
214
|
+
for (let selectionIndex = frame.node.selectionSet.selections.length - 1; selectionIndex >= 0; selectionIndex--) {
|
|
215
|
+
const selection = frame.node.selectionSet.selections[selectionIndex];
|
|
216
|
+
if (!selection) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
137
219
|
switch (selection.kind) {
|
|
138
220
|
case import_graphql2.Kind.FIELD: {
|
|
139
221
|
const fieldName = selection.name.value;
|
|
@@ -214,7 +296,7 @@ function calculateDepth(caches, config, fragments, maxDepth, node, parentType, s
|
|
|
214
296
|
deepestViolation = violation;
|
|
215
297
|
}
|
|
216
298
|
}
|
|
217
|
-
|
|
299
|
+
stack.push({
|
|
218
300
|
currentDepth: newDepth,
|
|
219
301
|
hasDirectiveLimit,
|
|
220
302
|
ignoredFieldsOnPath,
|
|
@@ -238,7 +320,7 @@ function calculateDepth(caches, config, fragments, maxDepth, node, parentType, s
|
|
|
238
320
|
const fragmentVisited = new Set(frame.visitedFragments);
|
|
239
321
|
fragmentVisited.add(fragmentName);
|
|
240
322
|
const parentType2 = fragment.typeCondition ? resolveTypeCondition(fragment.typeCondition.name.value, schema, frame.parentType) : frame.parentType;
|
|
241
|
-
|
|
323
|
+
stack.push({
|
|
242
324
|
currentDepth: frame.currentDepth,
|
|
243
325
|
hasDirectiveLimit: frame.hasDirectiveLimit,
|
|
244
326
|
ignoredFieldsOnPath: frame.ignoredFieldsOnPath,
|
|
@@ -252,7 +334,7 @@ function calculateDepth(caches, config, fragments, maxDepth, node, parentType, s
|
|
|
252
334
|
}
|
|
253
335
|
case import_graphql2.Kind.INLINE_FRAGMENT: {
|
|
254
336
|
const parentType2 = selection.typeCondition ? resolveTypeCondition(selection.typeCondition.name.value, schema, frame.parentType) : frame.parentType;
|
|
255
|
-
|
|
337
|
+
stack.push({
|
|
256
338
|
currentDepth: frame.currentDepth,
|
|
257
339
|
hasDirectiveLimit: frame.hasDirectiveLimit,
|
|
258
340
|
ignoredFieldsOnPath: frame.ignoredFieldsOnPath,
|
|
@@ -270,12 +352,6 @@ function calculateDepth(caches, config, fragments, maxDepth, node, parentType, s
|
|
|
270
352
|
}
|
|
271
353
|
}
|
|
272
354
|
}
|
|
273
|
-
for (let index = nextFrames.length - 1; index >= 0; index--) {
|
|
274
|
-
const nextFrame = nextFrames[index];
|
|
275
|
-
if (nextFrame) {
|
|
276
|
-
stack.push(nextFrame);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
355
|
}
|
|
280
356
|
return { depth: globalMaxDepth, violation: deepestViolation };
|
|
281
357
|
}
|
|
@@ -376,10 +452,129 @@ function resolveTypeCondition(typeConditionName, schema, currentParentType) {
|
|
|
376
452
|
return currentParentType;
|
|
377
453
|
}
|
|
378
454
|
|
|
379
|
-
// src/depth-
|
|
455
|
+
// src/depth-options.ts
|
|
380
456
|
var DIRECTIVE_MODES = /* @__PURE__ */ new Set(["cap", "override"]);
|
|
381
457
|
var IGNORE_MODES = /* @__PURE__ */ new Set(["exclude", "skip"]);
|
|
382
458
|
var INTROSPECTION_MODES = /* @__PURE__ */ new Set(["all", "none", "typename"]);
|
|
459
|
+
function assertBooleanOption(name, value) {
|
|
460
|
+
if (value !== void 0 && typeof value !== "boolean") {
|
|
461
|
+
throw new TypeError(`Invalid ${name}: expected boolean, received ${typeof value}.`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function isIgnoreRule(rule) {
|
|
465
|
+
return typeof rule === "function" || rule instanceof RegExp || typeof rule === "string";
|
|
466
|
+
}
|
|
467
|
+
function normalizeIgnoreRules(ignore) {
|
|
468
|
+
if (ignore == null) {
|
|
469
|
+
return void 0;
|
|
470
|
+
}
|
|
471
|
+
const rules = Array.isArray(ignore) ? ignore : [ignore];
|
|
472
|
+
for (const [index, rule] of rules.entries()) {
|
|
473
|
+
if (!isIgnoreRule(rule)) {
|
|
474
|
+
const receivedType = Array.isArray(rule) ? "array" : rule === null ? "null" : typeof rule;
|
|
475
|
+
throw new TypeError(
|
|
476
|
+
`Invalid ignore rule at index ${index}: expected string, RegExp, or function, received ${receivedType}.`
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
if (rule instanceof RegExp) {
|
|
480
|
+
const reason = isUnsafeRegExp(rule);
|
|
481
|
+
if (reason) {
|
|
482
|
+
throw new TypeError(
|
|
483
|
+
`Unsafe RegExp ignore rule at index ${index}: /${rule.source}/${rule.flags} - ${reason}. Use a simpler pattern to avoid catastrophic backtracking.`
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return rules;
|
|
489
|
+
}
|
|
490
|
+
function normalizeDepthLimitOptions(options) {
|
|
491
|
+
assertBooleanOption("caseInsensitiveIgnore", options.caseInsensitiveIgnore);
|
|
492
|
+
if (options.directiveMode !== void 0 && !DIRECTIVE_MODES.has(options.directiveMode)) {
|
|
493
|
+
throw new TypeError(
|
|
494
|
+
`Invalid directiveMode: "${options.directiveMode}". Must be "cap" or "override".`
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
if (options.ignoreIntrospection !== void 0 && !INTROSPECTION_MODES.has(options.ignoreIntrospection)) {
|
|
498
|
+
throw new TypeError(
|
|
499
|
+
`Invalid ignoreIntrospection: "${options.ignoreIntrospection}". Must be "all", "none", or "typename".`
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
if (options.ignoreMode !== void 0 && !IGNORE_MODES.has(options.ignoreMode)) {
|
|
503
|
+
throw new TypeError(
|
|
504
|
+
`Invalid ignoreMode: "${options.ignoreMode}". Must be "exclude" or "skip".`
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
assertBooleanOption("limitIgnoredRecursion", options.limitIgnoredRecursion);
|
|
508
|
+
assertBooleanOption("shortCircuit", options.shortCircuit);
|
|
509
|
+
assertBooleanOption("useDirective", options.useDirective);
|
|
510
|
+
const ignore = normalizeIgnoreRules(options.ignore);
|
|
511
|
+
return { ...options, ignore };
|
|
512
|
+
}
|
|
513
|
+
function normalizeDepthLimitArgs(options, callback) {
|
|
514
|
+
if (callback !== void 0 && typeof callback !== "function") {
|
|
515
|
+
throw new TypeError("Invalid callback: expected a function.");
|
|
516
|
+
}
|
|
517
|
+
if (typeof options === "function") {
|
|
518
|
+
if (callback) {
|
|
519
|
+
throw new TypeError("Invalid depthLimit arguments: callback provided twice.");
|
|
520
|
+
}
|
|
521
|
+
return { callback: options, options: void 0 };
|
|
522
|
+
}
|
|
523
|
+
if (options !== void 0 && (options === null || typeof options !== "object" || Array.isArray(options))) {
|
|
524
|
+
const receivedType = Array.isArray(options) ? "array" : options === null ? "null" : typeof options;
|
|
525
|
+
throw new TypeError(`Invalid options: expected an object, received ${receivedType}.`);
|
|
526
|
+
}
|
|
527
|
+
return {
|
|
528
|
+
callback,
|
|
529
|
+
options: options ? normalizeDepthLimitOptions(options) : void 0
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
function createDepthResultRecord() {
|
|
533
|
+
return /* @__PURE__ */ Object.create(null);
|
|
534
|
+
}
|
|
535
|
+
function createOperationNameAllocator(operations) {
|
|
536
|
+
const explicitNames = /* @__PURE__ */ new Set();
|
|
537
|
+
for (const operation of operations) {
|
|
538
|
+
if (operation.name?.value) {
|
|
539
|
+
explicitNames.add(operation.name.value);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
543
|
+
const namedCounts = /* @__PURE__ */ new Map();
|
|
544
|
+
let anonymousCount = 0;
|
|
545
|
+
return (operation) => {
|
|
546
|
+
const explicitName = operation.name?.value;
|
|
547
|
+
if (explicitName) {
|
|
548
|
+
let suffix = namedCounts.get(explicitName) ?? 0;
|
|
549
|
+
let candidate2 = suffix === 0 ? explicitName : `${explicitName}_${suffix}`;
|
|
550
|
+
while (usedNames.has(candidate2) || suffix > 0 && explicitNames.has(candidate2)) {
|
|
551
|
+
suffix++;
|
|
552
|
+
candidate2 = `${explicitName}_${suffix}`;
|
|
553
|
+
}
|
|
554
|
+
namedCounts.set(explicitName, suffix + 1);
|
|
555
|
+
usedNames.add(candidate2);
|
|
556
|
+
return candidate2;
|
|
557
|
+
}
|
|
558
|
+
anonymousCount++;
|
|
559
|
+
let candidate = anonymousCount === 1 ? "[anonymous]" : `[anonymous:${anonymousCount}]`;
|
|
560
|
+
while (usedNames.has(candidate) || explicitNames.has(candidate)) {
|
|
561
|
+
anonymousCount++;
|
|
562
|
+
candidate = `[anonymous:${anonymousCount}]`;
|
|
563
|
+
}
|
|
564
|
+
usedNames.add(candidate);
|
|
565
|
+
return candidate;
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
function setDepthResult(target, key, value) {
|
|
569
|
+
Object.defineProperty(target, key, {
|
|
570
|
+
configurable: true,
|
|
571
|
+
enumerable: true,
|
|
572
|
+
value,
|
|
573
|
+
writable: true
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// src/depth-limit.ts
|
|
383
578
|
function depthLimit(maxDepth, options, callback) {
|
|
384
579
|
if (!Number.isInteger(maxDepth) || maxDepth < 0) {
|
|
385
580
|
throw new Error(`Invalid maxDepth: ${maxDepth}. Must be a non-negative integer.`);
|
|
@@ -387,11 +582,6 @@ function depthLimit(maxDepth, options, callback) {
|
|
|
387
582
|
const normalized = normalizeDepthLimitArgs(options, callback);
|
|
388
583
|
return createValidationRule(maxDepth, normalized.options, normalized.callback);
|
|
389
584
|
}
|
|
390
|
-
function assertBooleanOption(name, value) {
|
|
391
|
-
if (value !== void 0 && typeof value !== "boolean") {
|
|
392
|
-
throw new TypeError(`Invalid ${name}: expected boolean, received ${typeof value}.`);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
585
|
function createValidationRule(maxDepth, options, callback) {
|
|
396
586
|
const shortCircuit = options?.shortCircuit ?? callback == null;
|
|
397
587
|
return function depthLimitValidationRule(context) {
|
|
@@ -473,115 +663,11 @@ function createValidationRule(maxDepth, options, callback) {
|
|
|
473
663
|
return {};
|
|
474
664
|
};
|
|
475
665
|
}
|
|
476
|
-
function isIgnoreRule(rule) {
|
|
477
|
-
return typeof rule === "function" || rule instanceof RegExp || typeof rule === "string";
|
|
478
|
-
}
|
|
479
|
-
function normalizeDepthLimitArgs(options, callback) {
|
|
480
|
-
if (callback !== void 0 && typeof callback !== "function") {
|
|
481
|
-
throw new TypeError("Invalid callback: expected a function.");
|
|
482
|
-
}
|
|
483
|
-
if (typeof options === "function") {
|
|
484
|
-
if (callback) {
|
|
485
|
-
throw new TypeError("Invalid depthLimit arguments: callback provided twice.");
|
|
486
|
-
}
|
|
487
|
-
return { callback: options, options: void 0 };
|
|
488
|
-
}
|
|
489
|
-
if (options !== void 0 && (options === null || typeof options !== "object" || Array.isArray(options))) {
|
|
490
|
-
const receivedType = Array.isArray(options) ? "array" : options === null ? "null" : typeof options;
|
|
491
|
-
throw new TypeError(`Invalid options: expected an object, received ${receivedType}.`);
|
|
492
|
-
}
|
|
493
|
-
return {
|
|
494
|
-
callback,
|
|
495
|
-
options: options ? normalizeDepthLimitOptions(options) : void 0
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
function normalizeDepthLimitOptions(options) {
|
|
499
|
-
assertBooleanOption("caseInsensitiveIgnore", options.caseInsensitiveIgnore);
|
|
500
|
-
if (options.directiveMode !== void 0 && !DIRECTIVE_MODES.has(options.directiveMode)) {
|
|
501
|
-
throw new TypeError(
|
|
502
|
-
`Invalid directiveMode: "${options.directiveMode}". Must be "cap" or "override".`
|
|
503
|
-
);
|
|
504
|
-
}
|
|
505
|
-
if (options.ignoreIntrospection !== void 0 && !INTROSPECTION_MODES.has(options.ignoreIntrospection)) {
|
|
506
|
-
throw new TypeError(
|
|
507
|
-
`Invalid ignoreIntrospection: "${options.ignoreIntrospection}". Must be "all", "none", or "typename".`
|
|
508
|
-
);
|
|
509
|
-
}
|
|
510
|
-
if (options.ignoreMode !== void 0 && !IGNORE_MODES.has(options.ignoreMode)) {
|
|
511
|
-
throw new TypeError(
|
|
512
|
-
`Invalid ignoreMode: "${options.ignoreMode}". Must be "exclude" or "skip".`
|
|
513
|
-
);
|
|
514
|
-
}
|
|
515
|
-
assertBooleanOption("limitIgnoredRecursion", options.limitIgnoredRecursion);
|
|
516
|
-
assertBooleanOption("shortCircuit", options.shortCircuit);
|
|
517
|
-
assertBooleanOption("useDirective", options.useDirective);
|
|
518
|
-
const ignore = normalizeIgnoreRules(options.ignore);
|
|
519
|
-
return { ...options, ignore };
|
|
520
|
-
}
|
|
521
|
-
function normalizeIgnoreRules(ignore) {
|
|
522
|
-
if (ignore == null) {
|
|
523
|
-
return void 0;
|
|
524
|
-
}
|
|
525
|
-
const rules = Array.isArray(ignore) ? ignore : [ignore];
|
|
526
|
-
for (const [index, rule] of rules.entries()) {
|
|
527
|
-
if (!isIgnoreRule(rule)) {
|
|
528
|
-
const receivedType = Array.isArray(rule) ? "array" : rule === null ? "null" : typeof rule;
|
|
529
|
-
throw new TypeError(
|
|
530
|
-
`Invalid ignore rule at index ${index}: expected string, RegExp, or function, received ${receivedType}.`
|
|
531
|
-
);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
return rules;
|
|
535
|
-
}
|
|
536
|
-
function createDepthResultRecord() {
|
|
537
|
-
return /* @__PURE__ */ Object.create(null);
|
|
538
|
-
}
|
|
539
|
-
function createOperationNameAllocator(operations) {
|
|
540
|
-
const explicitNames = /* @__PURE__ */ new Set();
|
|
541
|
-
for (const operation of operations) {
|
|
542
|
-
if (operation.name?.value) {
|
|
543
|
-
explicitNames.add(operation.name.value);
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
const usedNames = /* @__PURE__ */ new Set();
|
|
547
|
-
const namedCounts = /* @__PURE__ */ new Map();
|
|
548
|
-
let anonymousCount = 0;
|
|
549
|
-
return (operation) => {
|
|
550
|
-
const explicitName = operation.name?.value;
|
|
551
|
-
if (explicitName) {
|
|
552
|
-
let suffix2 = namedCounts.get(explicitName) ?? 0;
|
|
553
|
-
let candidate2 = suffix2 === 0 ? explicitName : `${explicitName}_${suffix2}`;
|
|
554
|
-
while (usedNames.has(candidate2) || suffix2 > 0 && explicitNames.has(candidate2)) {
|
|
555
|
-
suffix2++;
|
|
556
|
-
candidate2 = `${explicitName}_${suffix2}`;
|
|
557
|
-
}
|
|
558
|
-
namedCounts.set(explicitName, suffix2 + 1);
|
|
559
|
-
usedNames.add(candidate2);
|
|
560
|
-
return candidate2;
|
|
561
|
-
}
|
|
562
|
-
let suffix = anonymousCount;
|
|
563
|
-
let candidate = suffix === 0 ? "anonymous" : `anonymous_${suffix}`;
|
|
564
|
-
while (usedNames.has(candidate) || explicitNames.has(candidate)) {
|
|
565
|
-
suffix++;
|
|
566
|
-
candidate = `anonymous_${suffix}`;
|
|
567
|
-
}
|
|
568
|
-
anonymousCount = suffix + 1;
|
|
569
|
-
usedNames.add(candidate);
|
|
570
|
-
return candidate;
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
function setDepthResult(target, key, value) {
|
|
574
|
-
Object.defineProperty(target, key, {
|
|
575
|
-
configurable: true,
|
|
576
|
-
enumerable: true,
|
|
577
|
-
value,
|
|
578
|
-
writable: true
|
|
579
|
-
});
|
|
580
|
-
}
|
|
581
666
|
// Annotate the CommonJS export names for ESM import in node:
|
|
582
667
|
0 && (module.exports = {
|
|
583
668
|
ERROR_CODES,
|
|
584
669
|
depthDirectiveTypeDefs,
|
|
585
|
-
depthLimit
|
|
670
|
+
depthLimit,
|
|
671
|
+
isUnsafeRegExp
|
|
586
672
|
});
|
|
587
673
|
//# sourceMappingURL=index.cjs.map
|