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/dist/index.js CHANGED
@@ -20,9 +20,10 @@ import {
20
20
 
21
21
  // src/directives.ts
22
22
  import { Kind } from "graphql";
23
- var depthDirectiveTypeDefs = `
24
- directive @depth(max: Int!) on FIELD_DEFINITION
25
- `;
23
+ var depthDirectiveTypeDefs = (
24
+ /* GraphQL */
25
+ `directive @depth(max: Int!) on FIELD_DEFINITION`
26
+ );
26
27
  function getDepthFromDirective(field) {
27
28
  if (!field?.astNode?.directives) {
28
29
  return void 0;
@@ -42,6 +43,83 @@ function getDepthFromDirective(field) {
42
43
  }
43
44
 
44
45
  // src/ignore.ts
46
+ var QUANTIFIER_CHARS = /* @__PURE__ */ new Set(["+", "*", "?"]);
47
+ var BRACE_QUANTIFIER = /^\{(\d+)(?:,(\d*))?\}/;
48
+ function isUnsafeRegExp(regex) {
49
+ const source = regex.source;
50
+ const length = source.length;
51
+ const groupStack = [];
52
+ for (let i = 0; i < length; i++) {
53
+ const char = source.charAt(i);
54
+ if (char === "\\") {
55
+ i++;
56
+ continue;
57
+ }
58
+ if (char === "[") {
59
+ while (i < length && source[i] !== "]") {
60
+ if (source[i] === "\\") i++;
61
+ i++;
62
+ }
63
+ continue;
64
+ }
65
+ if (char === "(") {
66
+ groupStack.push(false);
67
+ if (source[i + 1] === "?") {
68
+ i++;
69
+ if (source[i + 1] === "<" && (source[i + 2] === "=" || source[i + 2] === "!")) {
70
+ i += 2;
71
+ } else if (source[i + 1] === ":" || source[i + 1] === "=" || source[i + 1] === "!") {
72
+ i++;
73
+ }
74
+ }
75
+ continue;
76
+ }
77
+ if (char === ")") {
78
+ const groupHadQuantifier = groupStack.pop() ?? false;
79
+ if (groupHadQuantifier && isFollowedByQuantifier(source, i + 1, length)) {
80
+ return "nested quantifier: group with inner quantifier followed by outer quantifier";
81
+ }
82
+ if (groupStack.length > 0 && isFollowedByQuantifier(source, i + 1, length)) {
83
+ groupStack[groupStack.length - 1] = true;
84
+ }
85
+ continue;
86
+ }
87
+ if (QUANTIFIER_CHARS.has(char) || char === "{") {
88
+ if (char === "{") {
89
+ const match = BRACE_QUANTIFIER.exec(source.slice(i));
90
+ if (!match) continue;
91
+ const minStr = match[1] ?? "0";
92
+ const min = Number.parseInt(minStr, 10);
93
+ const fullMatch = match[0] ?? "";
94
+ const hasComma = fullMatch.includes(",");
95
+ if (!hasComma && min <= 1) continue;
96
+ if (hasComma && match[2] !== void 0 && match[2] !== "" && Number.parseInt(match[2], 10) <= 1)
97
+ continue;
98
+ }
99
+ if (groupStack.length > 0) {
100
+ groupStack[groupStack.length - 1] = true;
101
+ }
102
+ }
103
+ }
104
+ return null;
105
+ }
106
+ function isFollowedByQuantifier(source, pos, length) {
107
+ if (pos >= length) return false;
108
+ const char = source[pos];
109
+ if (char === "+" || char === "*") return true;
110
+ if (char === "{") {
111
+ const match = BRACE_QUANTIFIER.exec(source.slice(pos));
112
+ if (match) {
113
+ const minStr = match[1] ?? "0";
114
+ const min = Number.parseInt(minStr, 10);
115
+ const fullMatch = match[0] ?? "";
116
+ const hasComma = fullMatch.includes(",");
117
+ if (!hasComma && min <= 1) return false;
118
+ return true;
119
+ }
120
+ }
121
+ return false;
122
+ }
45
123
  function shouldIgnoreField(fieldName, ignore, caseInsensitive = false, introspectionMode = "typename") {
46
124
  if (introspectionMode === "all" && fieldName.startsWith("__")) {
47
125
  return true;
@@ -112,8 +190,11 @@ function calculateDepth(caches, config, fragments, maxDepth, node, parentType, s
112
190
  if (!frame.node.selectionSet) {
113
191
  continue;
114
192
  }
115
- const nextFrames = [];
116
- for (const selection of frame.node.selectionSet.selections) {
193
+ for (let selectionIndex = frame.node.selectionSet.selections.length - 1; selectionIndex >= 0; selectionIndex--) {
194
+ const selection = frame.node.selectionSet.selections[selectionIndex];
195
+ if (!selection) {
196
+ continue;
197
+ }
117
198
  switch (selection.kind) {
118
199
  case Kind2.FIELD: {
119
200
  const fieldName = selection.name.value;
@@ -194,7 +275,7 @@ function calculateDepth(caches, config, fragments, maxDepth, node, parentType, s
194
275
  deepestViolation = violation;
195
276
  }
196
277
  }
197
- nextFrames.push({
278
+ stack.push({
198
279
  currentDepth: newDepth,
199
280
  hasDirectiveLimit,
200
281
  ignoredFieldsOnPath,
@@ -218,7 +299,7 @@ function calculateDepth(caches, config, fragments, maxDepth, node, parentType, s
218
299
  const fragmentVisited = new Set(frame.visitedFragments);
219
300
  fragmentVisited.add(fragmentName);
220
301
  const parentType2 = fragment.typeCondition ? resolveTypeCondition(fragment.typeCondition.name.value, schema, frame.parentType) : frame.parentType;
221
- nextFrames.push({
302
+ stack.push({
222
303
  currentDepth: frame.currentDepth,
223
304
  hasDirectiveLimit: frame.hasDirectiveLimit,
224
305
  ignoredFieldsOnPath: frame.ignoredFieldsOnPath,
@@ -232,7 +313,7 @@ function calculateDepth(caches, config, fragments, maxDepth, node, parentType, s
232
313
  }
233
314
  case Kind2.INLINE_FRAGMENT: {
234
315
  const parentType2 = selection.typeCondition ? resolveTypeCondition(selection.typeCondition.name.value, schema, frame.parentType) : frame.parentType;
235
- nextFrames.push({
316
+ stack.push({
236
317
  currentDepth: frame.currentDepth,
237
318
  hasDirectiveLimit: frame.hasDirectiveLimit,
238
319
  ignoredFieldsOnPath: frame.ignoredFieldsOnPath,
@@ -250,12 +331,6 @@ function calculateDepth(caches, config, fragments, maxDepth, node, parentType, s
250
331
  }
251
332
  }
252
333
  }
253
- for (let index = nextFrames.length - 1; index >= 0; index--) {
254
- const nextFrame = nextFrames[index];
255
- if (nextFrame) {
256
- stack.push(nextFrame);
257
- }
258
- }
259
334
  }
260
335
  return { depth: globalMaxDepth, violation: deepestViolation };
261
336
  }
@@ -356,10 +431,129 @@ function resolveTypeCondition(typeConditionName, schema, currentParentType) {
356
431
  return currentParentType;
357
432
  }
358
433
 
359
- // src/depth-limit.ts
434
+ // src/depth-options.ts
360
435
  var DIRECTIVE_MODES = /* @__PURE__ */ new Set(["cap", "override"]);
361
436
  var IGNORE_MODES = /* @__PURE__ */ new Set(["exclude", "skip"]);
362
437
  var INTROSPECTION_MODES = /* @__PURE__ */ new Set(["all", "none", "typename"]);
438
+ function assertBooleanOption(name, value) {
439
+ if (value !== void 0 && typeof value !== "boolean") {
440
+ throw new TypeError(`Invalid ${name}: expected boolean, received ${typeof value}.`);
441
+ }
442
+ }
443
+ function isIgnoreRule(rule) {
444
+ return typeof rule === "function" || rule instanceof RegExp || typeof rule === "string";
445
+ }
446
+ function normalizeIgnoreRules(ignore) {
447
+ if (ignore == null) {
448
+ return void 0;
449
+ }
450
+ const rules = Array.isArray(ignore) ? ignore : [ignore];
451
+ for (const [index, rule] of rules.entries()) {
452
+ if (!isIgnoreRule(rule)) {
453
+ const receivedType = Array.isArray(rule) ? "array" : rule === null ? "null" : typeof rule;
454
+ throw new TypeError(
455
+ `Invalid ignore rule at index ${index}: expected string, RegExp, or function, received ${receivedType}.`
456
+ );
457
+ }
458
+ if (rule instanceof RegExp) {
459
+ const reason = isUnsafeRegExp(rule);
460
+ if (reason) {
461
+ throw new TypeError(
462
+ `Unsafe RegExp ignore rule at index ${index}: /${rule.source}/${rule.flags} - ${reason}. Use a simpler pattern to avoid catastrophic backtracking.`
463
+ );
464
+ }
465
+ }
466
+ }
467
+ return rules;
468
+ }
469
+ function normalizeDepthLimitOptions(options) {
470
+ assertBooleanOption("caseInsensitiveIgnore", options.caseInsensitiveIgnore);
471
+ if (options.directiveMode !== void 0 && !DIRECTIVE_MODES.has(options.directiveMode)) {
472
+ throw new TypeError(
473
+ `Invalid directiveMode: "${options.directiveMode}". Must be "cap" or "override".`
474
+ );
475
+ }
476
+ if (options.ignoreIntrospection !== void 0 && !INTROSPECTION_MODES.has(options.ignoreIntrospection)) {
477
+ throw new TypeError(
478
+ `Invalid ignoreIntrospection: "${options.ignoreIntrospection}". Must be "all", "none", or "typename".`
479
+ );
480
+ }
481
+ if (options.ignoreMode !== void 0 && !IGNORE_MODES.has(options.ignoreMode)) {
482
+ throw new TypeError(
483
+ `Invalid ignoreMode: "${options.ignoreMode}". Must be "exclude" or "skip".`
484
+ );
485
+ }
486
+ assertBooleanOption("limitIgnoredRecursion", options.limitIgnoredRecursion);
487
+ assertBooleanOption("shortCircuit", options.shortCircuit);
488
+ assertBooleanOption("useDirective", options.useDirective);
489
+ const ignore = normalizeIgnoreRules(options.ignore);
490
+ return { ...options, ignore };
491
+ }
492
+ function normalizeDepthLimitArgs(options, callback) {
493
+ if (callback !== void 0 && typeof callback !== "function") {
494
+ throw new TypeError("Invalid callback: expected a function.");
495
+ }
496
+ if (typeof options === "function") {
497
+ if (callback) {
498
+ throw new TypeError("Invalid depthLimit arguments: callback provided twice.");
499
+ }
500
+ return { callback: options, options: void 0 };
501
+ }
502
+ if (options !== void 0 && (options === null || typeof options !== "object" || Array.isArray(options))) {
503
+ const receivedType = Array.isArray(options) ? "array" : options === null ? "null" : typeof options;
504
+ throw new TypeError(`Invalid options: expected an object, received ${receivedType}.`);
505
+ }
506
+ return {
507
+ callback,
508
+ options: options ? normalizeDepthLimitOptions(options) : void 0
509
+ };
510
+ }
511
+ function createDepthResultRecord() {
512
+ return /* @__PURE__ */ Object.create(null);
513
+ }
514
+ function createOperationNameAllocator(operations) {
515
+ const explicitNames = /* @__PURE__ */ new Set();
516
+ for (const operation of operations) {
517
+ if (operation.name?.value) {
518
+ explicitNames.add(operation.name.value);
519
+ }
520
+ }
521
+ const usedNames = /* @__PURE__ */ new Set();
522
+ const namedCounts = /* @__PURE__ */ new Map();
523
+ let anonymousCount = 0;
524
+ return (operation) => {
525
+ const explicitName = operation.name?.value;
526
+ if (explicitName) {
527
+ let suffix = namedCounts.get(explicitName) ?? 0;
528
+ let candidate2 = suffix === 0 ? explicitName : `${explicitName}_${suffix}`;
529
+ while (usedNames.has(candidate2) || suffix > 0 && explicitNames.has(candidate2)) {
530
+ suffix++;
531
+ candidate2 = `${explicitName}_${suffix}`;
532
+ }
533
+ namedCounts.set(explicitName, suffix + 1);
534
+ usedNames.add(candidate2);
535
+ return candidate2;
536
+ }
537
+ anonymousCount++;
538
+ let candidate = anonymousCount === 1 ? "[anonymous]" : `[anonymous:${anonymousCount}]`;
539
+ while (usedNames.has(candidate) || explicitNames.has(candidate)) {
540
+ anonymousCount++;
541
+ candidate = `[anonymous:${anonymousCount}]`;
542
+ }
543
+ usedNames.add(candidate);
544
+ return candidate;
545
+ };
546
+ }
547
+ function setDepthResult(target, key, value) {
548
+ Object.defineProperty(target, key, {
549
+ configurable: true,
550
+ enumerable: true,
551
+ value,
552
+ writable: true
553
+ });
554
+ }
555
+
556
+ // src/depth-limit.ts
363
557
  function depthLimit(maxDepth, options, callback) {
364
558
  if (!Number.isInteger(maxDepth) || maxDepth < 0) {
365
559
  throw new Error(`Invalid maxDepth: ${maxDepth}. Must be a non-negative integer.`);
@@ -367,11 +561,6 @@ function depthLimit(maxDepth, options, callback) {
367
561
  const normalized = normalizeDepthLimitArgs(options, callback);
368
562
  return createValidationRule(maxDepth, normalized.options, normalized.callback);
369
563
  }
370
- function assertBooleanOption(name, value) {
371
- if (value !== void 0 && typeof value !== "boolean") {
372
- throw new TypeError(`Invalid ${name}: expected boolean, received ${typeof value}.`);
373
- }
374
- }
375
564
  function createValidationRule(maxDepth, options, callback) {
376
565
  const shortCircuit = options?.shortCircuit ?? callback == null;
377
566
  return function depthLimitValidationRule(context) {
@@ -453,114 +642,10 @@ function createValidationRule(maxDepth, options, callback) {
453
642
  return {};
454
643
  };
455
644
  }
456
- function isIgnoreRule(rule) {
457
- return typeof rule === "function" || rule instanceof RegExp || typeof rule === "string";
458
- }
459
- function normalizeDepthLimitArgs(options, callback) {
460
- if (callback !== void 0 && typeof callback !== "function") {
461
- throw new TypeError("Invalid callback: expected a function.");
462
- }
463
- if (typeof options === "function") {
464
- if (callback) {
465
- throw new TypeError("Invalid depthLimit arguments: callback provided twice.");
466
- }
467
- return { callback: options, options: void 0 };
468
- }
469
- if (options !== void 0 && (options === null || typeof options !== "object" || Array.isArray(options))) {
470
- const receivedType = Array.isArray(options) ? "array" : options === null ? "null" : typeof options;
471
- throw new TypeError(`Invalid options: expected an object, received ${receivedType}.`);
472
- }
473
- return {
474
- callback,
475
- options: options ? normalizeDepthLimitOptions(options) : void 0
476
- };
477
- }
478
- function normalizeDepthLimitOptions(options) {
479
- assertBooleanOption("caseInsensitiveIgnore", options.caseInsensitiveIgnore);
480
- if (options.directiveMode !== void 0 && !DIRECTIVE_MODES.has(options.directiveMode)) {
481
- throw new TypeError(
482
- `Invalid directiveMode: "${options.directiveMode}". Must be "cap" or "override".`
483
- );
484
- }
485
- if (options.ignoreIntrospection !== void 0 && !INTROSPECTION_MODES.has(options.ignoreIntrospection)) {
486
- throw new TypeError(
487
- `Invalid ignoreIntrospection: "${options.ignoreIntrospection}". Must be "all", "none", or "typename".`
488
- );
489
- }
490
- if (options.ignoreMode !== void 0 && !IGNORE_MODES.has(options.ignoreMode)) {
491
- throw new TypeError(
492
- `Invalid ignoreMode: "${options.ignoreMode}". Must be "exclude" or "skip".`
493
- );
494
- }
495
- assertBooleanOption("limitIgnoredRecursion", options.limitIgnoredRecursion);
496
- assertBooleanOption("shortCircuit", options.shortCircuit);
497
- assertBooleanOption("useDirective", options.useDirective);
498
- const ignore = normalizeIgnoreRules(options.ignore);
499
- return { ...options, ignore };
500
- }
501
- function normalizeIgnoreRules(ignore) {
502
- if (ignore == null) {
503
- return void 0;
504
- }
505
- const rules = Array.isArray(ignore) ? ignore : [ignore];
506
- for (const [index, rule] of rules.entries()) {
507
- if (!isIgnoreRule(rule)) {
508
- const receivedType = Array.isArray(rule) ? "array" : rule === null ? "null" : typeof rule;
509
- throw new TypeError(
510
- `Invalid ignore rule at index ${index}: expected string, RegExp, or function, received ${receivedType}.`
511
- );
512
- }
513
- }
514
- return rules;
515
- }
516
- function createDepthResultRecord() {
517
- return /* @__PURE__ */ Object.create(null);
518
- }
519
- function createOperationNameAllocator(operations) {
520
- const explicitNames = /* @__PURE__ */ new Set();
521
- for (const operation of operations) {
522
- if (operation.name?.value) {
523
- explicitNames.add(operation.name.value);
524
- }
525
- }
526
- const usedNames = /* @__PURE__ */ new Set();
527
- const namedCounts = /* @__PURE__ */ new Map();
528
- let anonymousCount = 0;
529
- return (operation) => {
530
- const explicitName = operation.name?.value;
531
- if (explicitName) {
532
- let suffix2 = namedCounts.get(explicitName) ?? 0;
533
- let candidate2 = suffix2 === 0 ? explicitName : `${explicitName}_${suffix2}`;
534
- while (usedNames.has(candidate2) || suffix2 > 0 && explicitNames.has(candidate2)) {
535
- suffix2++;
536
- candidate2 = `${explicitName}_${suffix2}`;
537
- }
538
- namedCounts.set(explicitName, suffix2 + 1);
539
- usedNames.add(candidate2);
540
- return candidate2;
541
- }
542
- let suffix = anonymousCount;
543
- let candidate = suffix === 0 ? "anonymous" : `anonymous_${suffix}`;
544
- while (usedNames.has(candidate) || explicitNames.has(candidate)) {
545
- suffix++;
546
- candidate = `anonymous_${suffix}`;
547
- }
548
- anonymousCount = suffix + 1;
549
- usedNames.add(candidate);
550
- return candidate;
551
- };
552
- }
553
- function setDepthResult(target, key, value) {
554
- Object.defineProperty(target, key, {
555
- configurable: true,
556
- enumerable: true,
557
- value,
558
- writable: true
559
- });
560
- }
561
645
  export {
562
646
  ERROR_CODES,
563
647
  depthDirectiveTypeDefs,
564
- depthLimit
648
+ depthLimit,
649
+ isUnsafeRegExp
565
650
  };
566
651
  //# sourceMappingURL=index.js.map