Files
skills/docs/api-stack/api/api-graphql-apollo/src/guards/depth-and-complexity.md
2026-05-29 08:20:56 +01:00

2.7 KiB

guards/depth-and-complexity — query-shape rejection

(no submodules)

Purpose

depth-and-complexity walks a parsed DocumentNode and computes two coarse static metrics — the maximum selection-set nesting depth and the total field-count cost. The engine consults both before handing the document to Apollo Server; queries that exceed maxQueryDepth or maxQueryComplexity are rejected with the corresponding GRAPHQL_APOLLO.DEPTH_EXCEEDED / COMPLEXITY_EXCEEDED codes.

The walk follows fragment spreads through their definitions and includes inline-fragment subselections, so a clever query that hides depth or cost behind a deeply nested fragment cannot bypass the guards. The heuristic matches the Yoga sibling so the two engines reject the same query set under identical limits.

Features

  • Depth counter increments per nested selectionSet, never on fragment spreads or inline fragments themselves (matches conventional depth-limiter implementations).
  • Complexity counter adds 1 per selected field, plus the recursive cost of its subselection, plus the expansion of fragment spreads. Missing fragment spreads contribute zero.
  • assertDepthAndComplexity evaluates depth first; the more localised failure wins so operators see the rule they triggered.
  • Pure-functional — no instance state, no this. Safe to call from any request lifecycle phase.

Usage: Basic

import { parse } from "graphql";
import { assertDepthAndComplexity } from "@suaveplan/api-graphql-apollo/guards";

const doc = parse("{ a { b { c } } }");
assertDepthAndComplexity(doc, { maxDepth: 10, maxComplexity: 1000 });

Usage: Advanced

Run the metrics in isolation to feed an OTel histogram before the engine even routes the document:

import { parse } from "graphql";
import {
    maxQueryDepth,
    queryComplexity,
} from "@suaveplan/api-graphql-apollo/guards";

const doc = parse(req.body.query);
metrics.histogram("graphql.depth").record(maxQueryDepth(doc));
metrics.histogram("graphql.complexity").record(queryComplexity(doc));

API Reference

  • maxQueryDepth(doc: DocumentNode): number
  • queryComplexity(doc: DocumentNode): number
  • assertDepthAndComplexity(doc, { maxDepth, maxComplexity }): void — throws GraphqlApolloError on first violation.

Implementation Notes

Fragment definitions are collected up-front into a Map so spread resolution is O(1). Inline fragments share the parent depth — they do not add a level. The complexity heuristic intentionally undercounts list multiplicity; production deployments wire a richer estimator via a host-supplied costExtension (out of scope for the foundation engine).