UNPKG

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
"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;