graphql-static-analysis
v0.1.0
Published
A composable static-analysis engine for GraphQL operations
Maintainers
Readme
graphql-static-analysis
graphql-static-analysis is a composable static-analysis engine for validated
GraphQL operations. It implements the TreeSummary model from
graphql-lean.
The engine owns the GraphQL-specific reasoning: possible runtime object types,
@include and @skip, fragments, field collection by response name, and merged
child selection sets. Each analysis only defines how a collected field is summarized,
how simultaneous summaries combine, and how runtime alternatives join.
The package includes:
- IBM GraphQL Cost Directives analysis with separate type and field costs;
- maximum response-size analysis under a configurable list bound;
- a custom-analysis API;
- a migration layer for
graphql-query-complexity.
It uses graphql-js for parsing, validation, operation selection, argument coercion,
and variable coercion. Both the flat GraphQL.js 16 variable map and the wrapped
GraphQL.js 17 variable container are supported and covered by the CI matrix.
Installation
npm install graphql graphql-static-analysisNode.js 20 or newer is required. The package provides both ES module and CommonJS entry points.
Recommended API
The source-oriented entry points validate and coerce their inputs. For analyses that
support ahead-of-execution use, omit variables to consider every feasible
@include/@skip assignment. Pass variables: {} (or a request map) to analyze one
concrete request and apply operation defaults. IBM cost always requires variables
because argument values affect its result.
Maximum response size
import { buildSchema } from "graphql";
import { estimateMaxResponseSize } from "graphql-static-analysis";
const schema = buildSchema(`
type Query { products: [Product!]! }
type Product { id: ID!, name: String! }
`);
const fields = estimateMaxResponseSize({
schema,
document: `{ products { id name } }`,
listSize: 100,
});The estimate counts response-object fields at every level. Every list wrapper
multiplies selected children by listSize; arithmetic saturates at
Number.MAX_SAFE_INTEGER.
IBM GraphQL Cost Directives
import { CostModel, estimateCost } from "graphql-static-analysis";
const model = CostModel.fromSchema(schema);
const result = estimateCost({
schema,
costModel: model,
document,
variables: requestVariables,
// Optional finite deployment fallback for otherwise-unbounded lists.
defaultListSize: 100,
});
console.log(result.typeCost, result.fieldCost);Without defaultListSize, a list without an applicable @listSize bound has
infinite cost. Type and field costs are intentionally independent; an unbounded list
of objects whose selected scalar fields all have zero field weight can have infinite
type cost and finite field cost.
Construct CostModel, CostEstimator, or MaxResponseSizeEstimator once and reuse
them when many operations share a schema.
Migrating from graphql-query-complexity
For the smallest first step, change the import and retain the familiar estimators:
import {
createComplexityRule,
fieldExtensionsEstimator,
simpleEstimator,
} from "graphql-static-analysis/compat";
const rule = createComplexityRule({
maximumComplexity: 1_000,
variables,
estimators: [fieldExtensionsEstimator(), simpleEstimator()],
});The compatibility entry point exports getComplexity, createComplexityRule,
simpleEstimator, fieldExtensionsEstimator, directiveEstimator, and
createComplexityDirective. Its option names and threshold behavior match the
established package.
The calculation is intentionally not bug-for-bug identical. TreeSummary first
collects overlapping fields with the same response name, merges their children, and
then calls an estimator once for the resolver field. It also keeps Boolean decisions
correlated in the ExactCase mode (see below). Compare observed scores before reusing an old production
threshold.
The recommended transition is:
- Switch to
graphql-static-analysis/compat, keeping existing estimators and logging scores without changing enforcement. - Compare scores for representative and adversarial operations, then choose a new threshold.
- Move policy to
estimateCost(IBM spec) or your own domain-specificanalyzeOperationalgebra so list cardinality, field work, and returned type cost are modeled explicitly rather than compressed into an opaque scalar.
See the migration guide for semantic differences and side-by-side examples.
Precision and performance modes
| | AnalysisMode.ExactCase | AnalysisMode.Syntactic |
| --- | --- | --- |
| Field collection | Groups every field that executes together under one response name. | Keeps fields from distinct cumulative conditions separate. |
| Boolean scope | Shares unknown assignments across the whole operation. | Summarizes recursive selection siblings independently. |
| Precision | Most precise; the default. | Conservative for nonnegative/additive analyses and potentially less precise. |
| Cost | Can enumerate many feasible Boolean cases. | Usually less expensive. |
Use exact mode unless analysis latency is more important than precision:
import { AnalysisMode } from "graphql-static-analysis";
const fields = estimateMaxResponseSize({
schema,
document,
listSize: 100,
mode: AnalysisMode.Syntactic,
});Low-level and custom analyses
Use Analyzer when inputs have already been parsed, validated, selected, and
coerced:
const analyzer = new Analyzer(schema);
const summary = analyzer
.operation(document, operation)
.mode(AnalysisMode.ExactCase)
.variableValues(coercedVariables)
.analyze(algebra);This path assumes the schema, document, operation, and variables are already valid.
Use analyzeOperation when the library should parse, validate, select, and coerce.
See Adding a custom analysis and
Engine architecture.
Differential fuzzing
The engine has a bounded structural-input harness that compares canonical TypeScript observations with a native oracle built from the Lean model. It supports deterministic exhaustive and seeded checks, exact-schedule comparison, retained regression inputs, and coverage-guided Jazzer.js campaigns. See TreeSummary differential fuzzing.
Formal model and TypeScript implementation confidence
The implementation follows the condition/tree-summary split and analysis algebras of
the Lean model. It was reviewed against
Lean revision fbf060d8.
The Lean proofs establish properties of that model, not of this TypeScript source
directly. This repository therefore maintains behavioral regression tests for field
collection, Boolean correlation, abstract runtime types, IBM cost, and the
compatibility API.
License
Licensed under the MIT License. See the license file.
