gqlcheck
Version:
Performs additional checks on your GraphQL documents and operations to ensure they conform to your rules, whilst allow-listing existing operations and their constituent parts (and allowing overrides on a per-field basis). Rules include max selection set d
172 lines (171 loc) • 7.03 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RulesContext = void 0;
const tslib_1 = require("tslib");
const graphqlLibrary = tslib_1.__importStar(require("graphql"));
const graphql_1 = require("graphql");
class RulesContext extends graphql_1.ValidationContext {
typeInfo;
resolvedPreset;
graphqlLibrary = graphqlLibrary;
constructor(schema, ast, typeInfo, resolvedPreset, onError) {
super(schema, ast, typeInfo, (error) => onError(error));
this.typeInfo = typeInfo;
this.resolvedPreset = resolvedPreset;
}
_meta = Object.create(null);
addMeta(key, value) {
this._meta[key] = value;
}
getMeta() {
return this._meta;
}
getTypeInfo() {
return this.typeInfo;
}
getOperationPath() {
return this.typeInfo.getOperationPath();
}
getResolvedPreset() {
return this.resolvedPreset;
}
isIntrospection() {
return this.typeInfo.isIntrospection();
}
// Operation path but only relative to the root (which could be a fragment)
subPathByNode = new Map();
operationPathsByNodeByOperation = new Map();
operationNamesByNode = new Map();
_fragmentsByRoot = new Map();
_nodesByRoot = new Map();
_operationDefinitions = [];
_fragmentDefinitions = [];
initEnterNode(node) {
const root = this.typeInfo.getCurrentRoot();
if (root != null) {
let list = this._nodesByRoot.get(root);
if (!list) {
list = [];
this._nodesByRoot.set(root, list);
}
list.push(node);
}
if (node.kind === graphql_1.Kind.OPERATION_DEFINITION) {
this._operationDefinitions.push(node);
}
if (node.kind === graphql_1.Kind.FRAGMENT_DEFINITION) {
this._fragmentDefinitions.push(node);
}
if (node.kind === graphql_1.Kind.FRAGMENT_SPREAD) {
if (!this.typeInfo.currentRoot) {
throw new Error("Cannot have a fragment spread without being inside a fragment definition/operation");
}
let list = this._fragmentsByRoot.get(this.typeInfo.currentRoot);
if (!list) {
list = [];
this._fragmentsByRoot.set(this.typeInfo.currentRoot, list);
}
list.push(node);
}
this.subPathByNode.set(node, this.typeInfo.getOperationPath());
}
initLeaveNode(node) {
if (node.kind === graphql_1.Kind.DOCUMENT) {
// Finalize
for (const operationDefinition of this._operationDefinitions) {
const operationName = operationDefinition.name?.value;
const operationPathsByNode = new Map();
this.operationPathsByNodeByOperation.set(operationDefinition, operationPathsByNode);
const walk = (root, path, visited) => {
// This runs before we've ensured there's no cycles, so we must protect ourself
if (visited.has(root)) {
return;
}
visited.add(root);
// Every node in this root is within operationName
const nodes = this._nodesByRoot.get(root);
if (nodes) {
for (const node of nodes) {
let list = this.operationNamesByNode.get(node);
if (!list) {
list = [];
this.operationNamesByNode.set(node, list);
}
list.push(operationName);
const subpath = this.subPathByNode.get(node);
if (subpath != null) {
const fullPath = path + subpath;
let list = operationPathsByNode.get(node);
if (!list) {
list = [];
operationPathsByNode.set(node, list);
}
list.push(fullPath);
}
}
}
const fragSpreads = this._fragmentsByRoot.get(root);
if (fragSpreads) {
for (const fragSpread of fragSpreads) {
const frag = this._fragmentDefinitions.find((d) => d.name.value === fragSpread.name.value);
if (frag) {
const subpath = this.subPathByNode.get(fragSpread);
const fullPath = path + subpath + ">";
walk(frag, fullPath, visited);
}
}
}
visited.delete(root);
};
walk(operationDefinition, "", new Set());
}
}
}
getOperationNamesForNodes(nodes) {
return nodes
? nodes.flatMap((node) => this.operationNamesByNode.get(node) ?? [])
: [];
}
getErrorOperationLocationsForNodes(nodes, limitToOperations) {
if (nodes == null) {
return [];
}
const map = new Map();
for (const node of nodes) {
const nodeOperationNames = this.operationNamesByNode.get(node);
if (nodeOperationNames) {
for (const operationName of nodeOperationNames) {
if (limitToOperations && !limitToOperations.includes(operationName)) {
continue;
}
let set = map.get(operationName);
if (!set) {
set = new Set();
map.set(operationName, set);
}
const op = this._operationDefinitions.find((o) => o.name?.value === operationName);
if (op) {
const operationPathsByNode = this.operationPathsByNodeByOperation.get(op);
if (operationPathsByNode) {
const nodeOperationCoordinates = operationPathsByNode.get(node);
if (nodeOperationCoordinates) {
for (const c of nodeOperationCoordinates) {
set.add(c);
}
}
}
}
}
}
}
const operations = [];
for (const [operationName, operationCoordinates] of map) {
operations.push({
operationName,
operationCoordinates: [...operationCoordinates],
});
}
return operations;
}
}
exports.RulesContext = RulesContext;