UNPKG

@graphql-hive/core

Version:
229 lines (228 loc) • 9.62 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.collectSchemaCoordinates = collectSchemaCoordinates; const graphql_1 = require("graphql"); /** Collect all schema coordinates within a DocumentNode */ function collectSchemaCoordinates(args) { const { variables } = args; const entries = new Set(); const collected_entire_named_types = new Set(); const shouldAnalyzeVariableValues = args.processVariables === true && variables !== null; function markAsUsed(id) { if (!entries.has(id)) { entries.add(id); } } function makeId(...names) { return names.join('.'); } const collectedInputTypes = {}; function collectInputType(inputType, fieldName) { if (!collectedInputTypes[inputType]) { collectedInputTypes[inputType] = { all: false, fields: new Set(), }; } if (fieldName) { collectedInputTypes[inputType].fields.add(fieldName); } else { collectedInputTypes[inputType].all = true; } } function collectNode(node) { const inputType = args.typeInfo.getInputType(); if (!inputType) { throw new Error('Expected an Input type, got nothing'); } const inputTypeName = resolveTypeName(inputType); if (node.value.kind === graphql_1.Kind.ENUM) { // Collect only a specific enum value collectInputType(inputTypeName, node.value.value); } else if (node.value.kind !== graphql_1.Kind.OBJECT && node.value.kind !== graphql_1.Kind.LIST) { // When processing of variables is enabled, // we want to skip collecting full input types of variables // and only collect specific fields. // That's why the following condition is added. // Otherwise we would mark entire input types as used, and not granular fields. if (node.value.kind === graphql_1.Kind.VARIABLE && shouldAnalyzeVariableValues) { return; } collectInputType(inputTypeName); } } function markEntireTypeAsUsed(type) { const namedType = (0, graphql_1.getNamedType)(type); if (collected_entire_named_types.has(namedType.name)) { // No need to mark this type as used again return; } // Add this type to the set of types that have been marked as used // to avoid infinite loops collected_entire_named_types.add(namedType.name); if ((0, graphql_1.isScalarType)(namedType)) { markAsUsed(makeId(namedType.name)); return; } if ((0, graphql_1.isEnumType)(namedType)) { namedType.getValues().forEach(value => { markAsUsed(makeId(namedType.name, value.name)); }); return; } const fieldsMap = namedType.getFields(); for (const fieldName in fieldsMap) { const field = fieldsMap[fieldName]; markAsUsed(makeId(namedType.name, field.name)); markEntireTypeAsUsed(field.type); } } function collectVariable(namedType, variableValue) { const variableValueArray = Array.isArray(variableValue) ? variableValue : [variableValue]; if ((0, graphql_1.isInputObjectType)(namedType)) { variableValueArray.forEach(variable => { if (variable) { // Collect only the used fields for (const fieldName in variable) { const field = namedType.getFields()[fieldName]; if (field) { collectInputType(namedType.name, fieldName); collectVariable((0, graphql_1.getNamedType)(field.type), variable[fieldName]); } } } else { // Collect type without fields markAsUsed(makeId(namedType.name)); } }); } else { collectInputType(namedType.name); } } (0, graphql_1.visit)(args.documentNode, (0, graphql_1.visitWithTypeInfo)(args.typeInfo, { Field(node, _key, _parent, path, ancestors) { const parent = args.typeInfo.getParentType(); const field = args.typeInfo.getFieldDef(); if (!parent) { throw new Error(`Could not find a parent type of a field at ${printPath(path, ancestors, node.name)}`); } if (!field) { throw new Error(`Could not find a field definition of a field at ${printPath(path, ancestors, node.name)}`); } markAsUsed(makeId(parent.name, field.name)); // Collect the entire type if it's an enum. // Deleting an enum value that is used, // should be a breaking change // as it changes the output of the field. const fieldType = (0, graphql_1.getNamedType)(field.type); if (fieldType instanceof graphql_1.GraphQLEnumType) { markEntireTypeAsUsed(fieldType); } }, VariableDefinition(node) { const inputType = args.typeInfo.getInputType(); if (!inputType) { throw new Error(`Could not find an input type of a variable $${node.variable.name}`); } if (shouldAnalyzeVariableValues) { // Granular collection of variable values is enabled const variableName = node.variable.name.value; const variableValue = variables[variableName]; const namedType = (0, graphql_1.getNamedType)(inputType); collectVariable(namedType, variableValue); } else { // Collect the entire type without processing the variables collectInputType(resolveTypeName(inputType)); } }, Directive(node) { return Object.assign(Object.assign({}, node), { arguments: [] }); }, Argument(node, _key, _parent, path, ancestors) { const parent = args.typeInfo.getParentType(); const field = args.typeInfo.getFieldDef(); const arg = args.typeInfo.getArgument(); if (!parent) { throw new Error(`Could not find a parent type of an argument at ${printPath(path, ancestors, node.name)}`); } if (!field) { throw new Error(`Could not find a field definition of an argument at ${printPath(path, ancestors, node.name)}`); } if (!arg) { throw new Error(`Could not find an argument definition of an argument at ${printPath(path, ancestors, node.name)}`); } markAsUsed(makeId(parent.name, field.name, arg.name)); collectNode(node); }, ListValue(node, _key, _parent, path, ancestors) { const inputType = args.typeInfo.getInputType(); if (!inputType) { throw new Error(`Could not find an input type of a list value at ${printPath(path, ancestors)}`); } const inputTypeName = resolveTypeName(inputType); node.values.forEach(value => { if (value.kind === graphql_1.Kind.ENUM) { collectInputType(inputTypeName, value.value); } else if (value.kind !== graphql_1.Kind.OBJECT) { // if a value is not an object we need to collect all fields collectInputType(inputTypeName); } }); }, ObjectField(node, _key, _parent, path, ancestors) { const parentInputType = args.typeInfo.getParentInputType(); if (!parentInputType) { throw new Error(`Could not find an input type of an object field at ${printPath(path, ancestors, node.name)}`); } const parentInputTypeName = resolveTypeName(parentInputType); if ((0, graphql_1.isScalarType)(parentInputType)) { collectInputType(parentInputTypeName); // Prevent the visitor into going deeper into ObjectField return null; } collectNode(node); collectInputType(parentInputTypeName, node.name.value); }, })); for (const inputTypeName in collectedInputTypes) { const { fields, all } = collectedInputTypes[inputTypeName]; if (all) { markEntireTypeAsUsed(args.schema.getType(inputTypeName)); } else { fields.forEach(field => { markAsUsed(makeId(inputTypeName, field)); }); } } return entries; } function resolveTypeName(inputType) { return (0, graphql_1.getNamedType)(inputType).name; } function printPath(path, ancestors, leafNameNode) { var _a; const result = []; for (let i = 0; i < path.length; i++) { const key = path[i]; const ancestor = ancestors[i]; if (!ancestor) { break; } if (key === 'selectionSet') { if ('name' in ancestor && ((_a = ancestor.name) === null || _a === void 0 ? void 0 : _a.value)) { result.push(ancestor.name.value); } } } if (leafNameNode) { result.push(leafNameNode.value); } return result.join('.'); }