@graphql-inspector/cli
Version:
Tooling for GraphQL. Compare GraphQL Schemas, check documents, find breaking changes, find similar types.
455 lines (454 loc) • 22.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.errors = void 0;
exports.patchSchema = patchSchema;
exports.groupByCoordinateAST = groupByCoordinateAST;
exports.patchCoordinatesAST = patchCoordinatesAST;
exports.patch = patch;
const tslib_1 = require("tslib");
const graphql_1 = require("graphql");
const core_1 = require("@graphql-inspector/core");
const utils_1 = require("@graphql-tools/utils");
const errors_js_1 = require("./errors.js");
const directive_usages_js_1 = require("./patches/directive-usages.js");
const directives_js_1 = require("./patches/directives.js");
const enum_js_1 = require("./patches/enum.js");
const fields_js_1 = require("./patches/fields.js");
const inputs_js_1 = require("./patches/inputs.js");
const interfaces_js_1 = require("./patches/interfaces.js");
const schema_js_1 = require("./patches/schema.js");
const types_js_1 = require("./patches/types.js");
const unions_js_1 = require("./patches/unions.js");
const utils_js_1 = require("./utils.js");
exports.errors = tslib_1.__importStar(require("./errors.js"));
/**
* Wraps converting a schema to AST safely, patching, then rebuilding the schema from AST.
* The schema is not validated in this function. That it is the responsibility of the caller.
*/
function patchSchema(schema, changes, config) {
const ast = (0, graphql_1.parse)((0, utils_1.printSchemaWithDirectives)(schema, { assumeValid: true }));
const patchedAst = patch(ast, changes, config);
return (0, graphql_1.buildASTSchema)(patchedAst, { assumeValid: true, assumeValidSDL: true });
}
/**
* Extracts all the root definitions from a DocumentNode and creates a mapping of their coordinate
* to the defined ASTNode. E.g. A field's coordinate is "Type.field".
*/
function groupByCoordinateAST(ast) {
const schemaNodes = [];
const nodesByCoordinate = new Map();
const pathArray = [];
(0, graphql_1.visit)(ast, {
enter(node, key) {
switch (node.kind) {
case graphql_1.Kind.ARGUMENT:
case graphql_1.Kind.ENUM_TYPE_DEFINITION:
case graphql_1.Kind.ENUM_TYPE_EXTENSION:
case graphql_1.Kind.ENUM_VALUE_DEFINITION:
case graphql_1.Kind.FIELD_DEFINITION:
case graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION:
case graphql_1.Kind.INPUT_OBJECT_TYPE_EXTENSION:
case graphql_1.Kind.INPUT_VALUE_DEFINITION:
case graphql_1.Kind.INTERFACE_TYPE_DEFINITION:
case graphql_1.Kind.INTERFACE_TYPE_EXTENSION:
case graphql_1.Kind.OBJECT_FIELD:
case graphql_1.Kind.OBJECT_TYPE_DEFINITION:
case graphql_1.Kind.OBJECT_TYPE_EXTENSION:
case graphql_1.Kind.SCALAR_TYPE_DEFINITION:
case graphql_1.Kind.SCALAR_TYPE_EXTENSION:
case graphql_1.Kind.UNION_TYPE_DEFINITION:
case graphql_1.Kind.UNION_TYPE_EXTENSION: {
pathArray.push(node.name.value);
const path = pathArray.join('.');
nodesByCoordinate.set(path, node);
break;
}
case graphql_1.Kind.DIRECTIVE_DEFINITION: {
pathArray.push(`@${node.name.value}`);
const path = pathArray.join('.');
nodesByCoordinate.set(path, node);
break;
}
case graphql_1.Kind.DIRECTIVE: {
/**
* Check if this directive is on the schema node. If so, then push an empty path
* to distinguish it from the definitions
*/
const isRoot = pathArray.length === 0;
if (isRoot) {
pathArray.push(`.@${node.name.value}[${key}]`);
}
else {
pathArray.push(`@${node.name.value}[${key}]`);
}
// const path = pathArray.join('.');
// nodesByCoordinate.set(path, node);
// @note skip setting the node for directives because repeat directives screw this up.
break;
}
case graphql_1.Kind.DOCUMENT: {
break;
}
case graphql_1.Kind.SCHEMA_EXTENSION:
case graphql_1.Kind.SCHEMA_DEFINITION: {
// @todo There can be only one. Replace `schemaNodes` with using `nodesByCoordinate.get('')`.
schemaNodes.push(node);
nodesByCoordinate.set('', node);
break;
}
// default: {
// // by definition this things like return types, names, named nodes...
// // it's nothing we want to collect.
// return false;
// }
}
},
leave(node) {
switch (node.kind) {
case graphql_1.Kind.ARGUMENT:
case graphql_1.Kind.ENUM_TYPE_DEFINITION:
case graphql_1.Kind.ENUM_TYPE_EXTENSION:
case graphql_1.Kind.ENUM_VALUE_DEFINITION:
case graphql_1.Kind.FIELD_DEFINITION:
case graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION:
case graphql_1.Kind.INPUT_OBJECT_TYPE_EXTENSION:
case graphql_1.Kind.INPUT_VALUE_DEFINITION:
case graphql_1.Kind.INTERFACE_TYPE_DEFINITION:
case graphql_1.Kind.INTERFACE_TYPE_EXTENSION:
case graphql_1.Kind.OBJECT_FIELD:
case graphql_1.Kind.OBJECT_TYPE_DEFINITION:
case graphql_1.Kind.OBJECT_TYPE_EXTENSION:
case graphql_1.Kind.SCALAR_TYPE_DEFINITION:
case graphql_1.Kind.SCALAR_TYPE_EXTENSION:
case graphql_1.Kind.UNION_TYPE_DEFINITION:
case graphql_1.Kind.UNION_TYPE_EXTENSION:
case graphql_1.Kind.DIRECTIVE_DEFINITION:
case graphql_1.Kind.DIRECTIVE: {
pathArray.pop();
break;
}
}
},
});
return [schemaNodes, nodesByCoordinate];
}
function patchCoordinatesAST(schemaNodes, nodesByCoordinate, changes, patchConfig = {}) {
const config = {
onError: errors_js_1.defaultErrorHandler,
debug: false,
...patchConfig,
};
const context = {
removedDirectiveNodes: [],
};
for (const change of changes) {
if (config.debug) {
(0, utils_js_1.debugPrintChange)(change, nodesByCoordinate);
}
switch (change.type) {
case core_1.ChangeType.SchemaMutationTypeChanged: {
(0, schema_js_1.schemaMutationTypeChanged)(change, schemaNodes, config, context);
break;
}
case core_1.ChangeType.SchemaQueryTypeChanged: {
(0, schema_js_1.schemaQueryTypeChanged)(change, schemaNodes, config, context);
break;
}
case core_1.ChangeType.SchemaSubscriptionTypeChanged: {
(0, schema_js_1.schemaSubscriptionTypeChanged)(change, schemaNodes, config, context);
break;
}
case core_1.ChangeType.DirectiveAdded: {
(0, directives_js_1.directiveAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveRemoved: {
(0, directives_js_1.directiveRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveArgumentAdded: {
(0, directives_js_1.directiveArgumentAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveArgumentRemoved: {
(0, directives_js_1.directiveArgumentRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveLocationAdded: {
(0, directives_js_1.directiveLocationAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveLocationRemoved: {
(0, directives_js_1.directiveLocationRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.EnumValueAdded: {
(0, enum_js_1.enumValueAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.FieldAdded: {
(0, fields_js_1.fieldAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.FieldRemoved: {
(0, fields_js_1.fieldRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.FieldTypeChanged: {
(0, fields_js_1.fieldTypeChanged)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.FieldArgumentAdded: {
(0, fields_js_1.fieldArgumentAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.FieldArgumentTypeChanged: {
(0, fields_js_1.fieldArgumentTypeChanged)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.FieldArgumentRemoved: {
(0, fields_js_1.fieldArgumentRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.FieldArgumentDescriptionChanged: {
(0, fields_js_1.fieldArgumentDescriptionChanged)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.FieldArgumentDefaultChanged: {
(0, fields_js_1.fieldArgumentDefaultChanged)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.FieldDescriptionAdded: {
(0, fields_js_1.fieldDescriptionAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.FieldDescriptionChanged: {
(0, fields_js_1.fieldDescriptionChanged)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.InputFieldAdded: {
(0, inputs_js_1.inputFieldAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.InputFieldRemoved: {
(0, inputs_js_1.inputFieldRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.InputFieldDescriptionAdded: {
(0, inputs_js_1.inputFieldDescriptionAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.InputFieldTypeChanged: {
(0, inputs_js_1.inputFieldTypeChanged)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.InputFieldDescriptionChanged: {
(0, inputs_js_1.inputFieldDescriptionChanged)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.InputFieldDescriptionRemoved: {
(0, inputs_js_1.inputFieldDescriptionRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.InputFieldDefaultValueChanged: {
(0, inputs_js_1.inputFieldDefaultValueChanged)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.ObjectTypeInterfaceAdded: {
(0, interfaces_js_1.objectTypeInterfaceAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.ObjectTypeInterfaceRemoved: {
(0, interfaces_js_1.objectTypeInterfaceRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.TypeDescriptionAdded: {
(0, types_js_1.typeDescriptionAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.TypeDescriptionChanged: {
(0, types_js_1.typeDescriptionChanged)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.TypeDescriptionRemoved: {
(0, types_js_1.typeDescriptionRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.TypeAdded: {
(0, types_js_1.typeAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.UnionMemberAdded: {
(0, unions_js_1.unionMemberAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.UnionMemberRemoved: {
(0, unions_js_1.unionMemberRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.TypeRemoved: {
(0, types_js_1.typeRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.EnumValueRemoved: {
(0, enum_js_1.enumValueRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.EnumValueDescriptionChanged: {
(0, enum_js_1.enumValueDescriptionChanged)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.FieldDescriptionRemoved: {
(0, fields_js_1.fieldDescriptionRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveArgumentDefaultValueChanged: {
(0, directives_js_1.directiveArgumentDefaultValueChanged)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveArgumentDescriptionChanged: {
(0, directives_js_1.directiveArgumentDescriptionChanged)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveArgumentTypeChanged: {
(0, directives_js_1.directiveArgumentTypeChanged)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveDescriptionChanged: {
(0, directives_js_1.directiveDescriptionChanged)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveRepeatableAdded: {
(0, directives_js_1.directiveRepeatableAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveRepeatableRemoved: {
(0, directives_js_1.directiveRepeatableRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageArgumentDefinitionAdded: {
(0, directive_usages_js_1.directiveUsageArgumentDefinitionAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageArgumentDefinitionRemoved: {
(0, directive_usages_js_1.directiveUsageArgumentDefinitionRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageEnumAdded: {
(0, directive_usages_js_1.directiveUsageEnumAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageEnumRemoved: {
(0, directive_usages_js_1.directiveUsageEnumRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageEnumValueAdded: {
(0, directive_usages_js_1.directiveUsageEnumValueAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageEnumValueRemoved: {
(0, directive_usages_js_1.directiveUsageEnumValueRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageFieldAdded: {
(0, directive_usages_js_1.directiveUsageFieldAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageFieldDefinitionAdded: {
(0, directive_usages_js_1.directiveUsageFieldDefinitionAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageFieldDefinitionRemoved: {
(0, directive_usages_js_1.directiveUsageFieldDefinitionRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageFieldRemoved: {
(0, directive_usages_js_1.directiveUsageFieldRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageInputFieldDefinitionAdded: {
(0, directive_usages_js_1.directiveUsageInputFieldDefinitionAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageInputFieldDefinitionRemoved: {
(0, directive_usages_js_1.directiveUsageInputFieldDefinitionRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageInputObjectAdded: {
(0, directive_usages_js_1.directiveUsageInputObjectAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageInputObjectRemoved: {
(0, directive_usages_js_1.directiveUsageInputObjectRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageInterfaceAdded: {
(0, directive_usages_js_1.directiveUsageInterfaceAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageInterfaceRemoved: {
(0, directive_usages_js_1.directiveUsageInterfaceRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageObjectAdded: {
(0, directive_usages_js_1.directiveUsageObjectAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageObjectRemoved: {
(0, directive_usages_js_1.directiveUsageObjectRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageScalarAdded: {
(0, directive_usages_js_1.directiveUsageScalarAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageScalarRemoved: {
(0, directive_usages_js_1.directiveUsageScalarRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageSchemaAdded: {
(0, directive_usages_js_1.directiveUsageSchemaAdded)(change, schemaNodes, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageSchemaRemoved: {
(0, directive_usages_js_1.directiveUsageSchemaRemoved)(change, schemaNodes, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageUnionMemberAdded: {
(0, directive_usages_js_1.directiveUsageUnionMemberAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageUnionMemberRemoved: {
(0, directive_usages_js_1.directiveUsageUnionMemberRemoved)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageArgumentAdded: {
(0, directive_usages_js_1.directiveUsageArgumentAdded)(change, nodesByCoordinate, config, context);
break;
}
case core_1.ChangeType.DirectiveUsageArgumentRemoved: {
(0, directive_usages_js_1.directiveUsageArgumentRemoved)(change, nodesByCoordinate, config, context);
break;
}
default: {
console.log(`${change.type} is not implemented yet.`);
}
}
}
for (const node of context.removedDirectiveNodes) {
node.directives = node.directives?.filter(d => d != null);
}
return {
kind: graphql_1.Kind.DOCUMENT,
// filter out the non-definition nodes (e.g. field definitions)
definitions: [
...schemaNodes,
...Array.from(nodesByCoordinate.values()).filter(graphql_1.isDefinitionNode),
],
};
}
/** This method wraps groupByCoordinateAST and patchCoordinatesAST for convenience. */
function patch(ast, changes, patchConfig) {
const [schemaNodes, nodesByCoordinate] = groupByCoordinateAST(ast);
return patchCoordinatesAST(schemaNodes, nodesByCoordinate, changes, patchConfig);
}