Spaces:
Running
Running
; | |
Object.defineProperty(exports, '__esModule', { | |
value: true, | |
}); | |
exports.DangerousChangeType = exports.BreakingChangeType = void 0; | |
exports.findBreakingChanges = findBreakingChanges; | |
exports.findDangerousChanges = findDangerousChanges; | |
var _inspect = require('../jsutils/inspect.js'); | |
var _invariant = require('../jsutils/invariant.js'); | |
var _keyMap = require('../jsutils/keyMap.js'); | |
var _printer = require('../language/printer.js'); | |
var _definition = require('../type/definition.js'); | |
var _scalars = require('../type/scalars.js'); | |
var _astFromValue = require('./astFromValue.js'); | |
var _sortValueNode = require('./sortValueNode.js'); | |
var BreakingChangeType; | |
exports.BreakingChangeType = BreakingChangeType; | |
(function (BreakingChangeType) { | |
BreakingChangeType['TYPE_REMOVED'] = 'TYPE_REMOVED'; | |
BreakingChangeType['TYPE_CHANGED_KIND'] = 'TYPE_CHANGED_KIND'; | |
BreakingChangeType['TYPE_REMOVED_FROM_UNION'] = 'TYPE_REMOVED_FROM_UNION'; | |
BreakingChangeType['VALUE_REMOVED_FROM_ENUM'] = 'VALUE_REMOVED_FROM_ENUM'; | |
BreakingChangeType['REQUIRED_INPUT_FIELD_ADDED'] = | |
'REQUIRED_INPUT_FIELD_ADDED'; | |
BreakingChangeType['IMPLEMENTED_INTERFACE_REMOVED'] = | |
'IMPLEMENTED_INTERFACE_REMOVED'; | |
BreakingChangeType['FIELD_REMOVED'] = 'FIELD_REMOVED'; | |
BreakingChangeType['FIELD_CHANGED_KIND'] = 'FIELD_CHANGED_KIND'; | |
BreakingChangeType['REQUIRED_ARG_ADDED'] = 'REQUIRED_ARG_ADDED'; | |
BreakingChangeType['ARG_REMOVED'] = 'ARG_REMOVED'; | |
BreakingChangeType['ARG_CHANGED_KIND'] = 'ARG_CHANGED_KIND'; | |
BreakingChangeType['DIRECTIVE_REMOVED'] = 'DIRECTIVE_REMOVED'; | |
BreakingChangeType['DIRECTIVE_ARG_REMOVED'] = 'DIRECTIVE_ARG_REMOVED'; | |
BreakingChangeType['REQUIRED_DIRECTIVE_ARG_ADDED'] = | |
'REQUIRED_DIRECTIVE_ARG_ADDED'; | |
BreakingChangeType['DIRECTIVE_REPEATABLE_REMOVED'] = | |
'DIRECTIVE_REPEATABLE_REMOVED'; | |
BreakingChangeType['DIRECTIVE_LOCATION_REMOVED'] = | |
'DIRECTIVE_LOCATION_REMOVED'; | |
})( | |
BreakingChangeType || (exports.BreakingChangeType = BreakingChangeType = {}), | |
); | |
var DangerousChangeType; | |
exports.DangerousChangeType = DangerousChangeType; | |
(function (DangerousChangeType) { | |
DangerousChangeType['VALUE_ADDED_TO_ENUM'] = 'VALUE_ADDED_TO_ENUM'; | |
DangerousChangeType['TYPE_ADDED_TO_UNION'] = 'TYPE_ADDED_TO_UNION'; | |
DangerousChangeType['OPTIONAL_INPUT_FIELD_ADDED'] = | |
'OPTIONAL_INPUT_FIELD_ADDED'; | |
DangerousChangeType['OPTIONAL_ARG_ADDED'] = 'OPTIONAL_ARG_ADDED'; | |
DangerousChangeType['IMPLEMENTED_INTERFACE_ADDED'] = | |
'IMPLEMENTED_INTERFACE_ADDED'; | |
DangerousChangeType['ARG_DEFAULT_VALUE_CHANGE'] = 'ARG_DEFAULT_VALUE_CHANGE'; | |
})( | |
DangerousChangeType || | |
(exports.DangerousChangeType = DangerousChangeType = {}), | |
); | |
/** | |
* Given two schemas, returns an Array containing descriptions of all the types | |
* of breaking changes covered by the other functions down below. | |
*/ | |
function findBreakingChanges(oldSchema, newSchema) { | |
// @ts-expect-error | |
return findSchemaChanges(oldSchema, newSchema).filter( | |
(change) => change.type in BreakingChangeType, | |
); | |
} | |
/** | |
* Given two schemas, returns an Array containing descriptions of all the types | |
* of potentially dangerous changes covered by the other functions down below. | |
*/ | |
function findDangerousChanges(oldSchema, newSchema) { | |
// @ts-expect-error | |
return findSchemaChanges(oldSchema, newSchema).filter( | |
(change) => change.type in DangerousChangeType, | |
); | |
} | |
function findSchemaChanges(oldSchema, newSchema) { | |
return [ | |
...findTypeChanges(oldSchema, newSchema), | |
...findDirectiveChanges(oldSchema, newSchema), | |
]; | |
} | |
function findDirectiveChanges(oldSchema, newSchema) { | |
const schemaChanges = []; | |
const directivesDiff = diff( | |
oldSchema.getDirectives(), | |
newSchema.getDirectives(), | |
); | |
for (const oldDirective of directivesDiff.removed) { | |
schemaChanges.push({ | |
type: BreakingChangeType.DIRECTIVE_REMOVED, | |
description: `${oldDirective.name} was removed.`, | |
}); | |
} | |
for (const [oldDirective, newDirective] of directivesDiff.persisted) { | |
const argsDiff = diff(oldDirective.args, newDirective.args); | |
for (const newArg of argsDiff.added) { | |
if ((0, _definition.isRequiredArgument)(newArg)) { | |
schemaChanges.push({ | |
type: BreakingChangeType.REQUIRED_DIRECTIVE_ARG_ADDED, | |
description: `A required arg ${newArg.name} on directive ${oldDirective.name} was added.`, | |
}); | |
} | |
} | |
for (const oldArg of argsDiff.removed) { | |
schemaChanges.push({ | |
type: BreakingChangeType.DIRECTIVE_ARG_REMOVED, | |
description: `${oldArg.name} was removed from ${oldDirective.name}.`, | |
}); | |
} | |
if (oldDirective.isRepeatable && !newDirective.isRepeatable) { | |
schemaChanges.push({ | |
type: BreakingChangeType.DIRECTIVE_REPEATABLE_REMOVED, | |
description: `Repeatable flag was removed from ${oldDirective.name}.`, | |
}); | |
} | |
for (const location of oldDirective.locations) { | |
if (!newDirective.locations.includes(location)) { | |
schemaChanges.push({ | |
type: BreakingChangeType.DIRECTIVE_LOCATION_REMOVED, | |
description: `${location} was removed from ${oldDirective.name}.`, | |
}); | |
} | |
} | |
} | |
return schemaChanges; | |
} | |
function findTypeChanges(oldSchema, newSchema) { | |
const schemaChanges = []; | |
const typesDiff = diff( | |
Object.values(oldSchema.getTypeMap()), | |
Object.values(newSchema.getTypeMap()), | |
); | |
for (const oldType of typesDiff.removed) { | |
schemaChanges.push({ | |
type: BreakingChangeType.TYPE_REMOVED, | |
description: (0, _scalars.isSpecifiedScalarType)(oldType) | |
? `Standard scalar ${oldType.name} was removed because it is not referenced anymore.` | |
: `${oldType.name} was removed.`, | |
}); | |
} | |
for (const [oldType, newType] of typesDiff.persisted) { | |
if ( | |
(0, _definition.isEnumType)(oldType) && | |
(0, _definition.isEnumType)(newType) | |
) { | |
schemaChanges.push(...findEnumTypeChanges(oldType, newType)); | |
} else if ( | |
(0, _definition.isUnionType)(oldType) && | |
(0, _definition.isUnionType)(newType) | |
) { | |
schemaChanges.push(...findUnionTypeChanges(oldType, newType)); | |
} else if ( | |
(0, _definition.isInputObjectType)(oldType) && | |
(0, _definition.isInputObjectType)(newType) | |
) { | |
schemaChanges.push(...findInputObjectTypeChanges(oldType, newType)); | |
} else if ( | |
(0, _definition.isObjectType)(oldType) && | |
(0, _definition.isObjectType)(newType) | |
) { | |
schemaChanges.push( | |
...findFieldChanges(oldType, newType), | |
...findImplementedInterfacesChanges(oldType, newType), | |
); | |
} else if ( | |
(0, _definition.isInterfaceType)(oldType) && | |
(0, _definition.isInterfaceType)(newType) | |
) { | |
schemaChanges.push( | |
...findFieldChanges(oldType, newType), | |
...findImplementedInterfacesChanges(oldType, newType), | |
); | |
} else if (oldType.constructor !== newType.constructor) { | |
schemaChanges.push({ | |
type: BreakingChangeType.TYPE_CHANGED_KIND, | |
description: | |
`${oldType.name} changed from ` + | |
`${typeKindName(oldType)} to ${typeKindName(newType)}.`, | |
}); | |
} | |
} | |
return schemaChanges; | |
} | |
function findInputObjectTypeChanges(oldType, newType) { | |
const schemaChanges = []; | |
const fieldsDiff = diff( | |
Object.values(oldType.getFields()), | |
Object.values(newType.getFields()), | |
); | |
for (const newField of fieldsDiff.added) { | |
if ((0, _definition.isRequiredInputField)(newField)) { | |
schemaChanges.push({ | |
type: BreakingChangeType.REQUIRED_INPUT_FIELD_ADDED, | |
description: `A required field ${newField.name} on input type ${oldType.name} was added.`, | |
}); | |
} else { | |
schemaChanges.push({ | |
type: DangerousChangeType.OPTIONAL_INPUT_FIELD_ADDED, | |
description: `An optional field ${newField.name} on input type ${oldType.name} was added.`, | |
}); | |
} | |
} | |
for (const oldField of fieldsDiff.removed) { | |
schemaChanges.push({ | |
type: BreakingChangeType.FIELD_REMOVED, | |
description: `${oldType.name}.${oldField.name} was removed.`, | |
}); | |
} | |
for (const [oldField, newField] of fieldsDiff.persisted) { | |
const isSafe = isChangeSafeForInputObjectFieldOrFieldArg( | |
oldField.type, | |
newField.type, | |
); | |
if (!isSafe) { | |
schemaChanges.push({ | |
type: BreakingChangeType.FIELD_CHANGED_KIND, | |
description: | |
`${oldType.name}.${oldField.name} changed type from ` + | |
`${String(oldField.type)} to ${String(newField.type)}.`, | |
}); | |
} | |
} | |
return schemaChanges; | |
} | |
function findUnionTypeChanges(oldType, newType) { | |
const schemaChanges = []; | |
const possibleTypesDiff = diff(oldType.getTypes(), newType.getTypes()); | |
for (const newPossibleType of possibleTypesDiff.added) { | |
schemaChanges.push({ | |
type: DangerousChangeType.TYPE_ADDED_TO_UNION, | |
description: `${newPossibleType.name} was added to union type ${oldType.name}.`, | |
}); | |
} | |
for (const oldPossibleType of possibleTypesDiff.removed) { | |
schemaChanges.push({ | |
type: BreakingChangeType.TYPE_REMOVED_FROM_UNION, | |
description: `${oldPossibleType.name} was removed from union type ${oldType.name}.`, | |
}); | |
} | |
return schemaChanges; | |
} | |
function findEnumTypeChanges(oldType, newType) { | |
const schemaChanges = []; | |
const valuesDiff = diff(oldType.getValues(), newType.getValues()); | |
for (const newValue of valuesDiff.added) { | |
schemaChanges.push({ | |
type: DangerousChangeType.VALUE_ADDED_TO_ENUM, | |
description: `${newValue.name} was added to enum type ${oldType.name}.`, | |
}); | |
} | |
for (const oldValue of valuesDiff.removed) { | |
schemaChanges.push({ | |
type: BreakingChangeType.VALUE_REMOVED_FROM_ENUM, | |
description: `${oldValue.name} was removed from enum type ${oldType.name}.`, | |
}); | |
} | |
return schemaChanges; | |
} | |
function findImplementedInterfacesChanges(oldType, newType) { | |
const schemaChanges = []; | |
const interfacesDiff = diff(oldType.getInterfaces(), newType.getInterfaces()); | |
for (const newInterface of interfacesDiff.added) { | |
schemaChanges.push({ | |
type: DangerousChangeType.IMPLEMENTED_INTERFACE_ADDED, | |
description: `${newInterface.name} added to interfaces implemented by ${oldType.name}.`, | |
}); | |
} | |
for (const oldInterface of interfacesDiff.removed) { | |
schemaChanges.push({ | |
type: BreakingChangeType.IMPLEMENTED_INTERFACE_REMOVED, | |
description: `${oldType.name} no longer implements interface ${oldInterface.name}.`, | |
}); | |
} | |
return schemaChanges; | |
} | |
function findFieldChanges(oldType, newType) { | |
const schemaChanges = []; | |
const fieldsDiff = diff( | |
Object.values(oldType.getFields()), | |
Object.values(newType.getFields()), | |
); | |
for (const oldField of fieldsDiff.removed) { | |
schemaChanges.push({ | |
type: BreakingChangeType.FIELD_REMOVED, | |
description: `${oldType.name}.${oldField.name} was removed.`, | |
}); | |
} | |
for (const [oldField, newField] of fieldsDiff.persisted) { | |
schemaChanges.push(...findArgChanges(oldType, oldField, newField)); | |
const isSafe = isChangeSafeForObjectOrInterfaceField( | |
oldField.type, | |
newField.type, | |
); | |
if (!isSafe) { | |
schemaChanges.push({ | |
type: BreakingChangeType.FIELD_CHANGED_KIND, | |
description: | |
`${oldType.name}.${oldField.name} changed type from ` + | |
`${String(oldField.type)} to ${String(newField.type)}.`, | |
}); | |
} | |
} | |
return schemaChanges; | |
} | |
function findArgChanges(oldType, oldField, newField) { | |
const schemaChanges = []; | |
const argsDiff = diff(oldField.args, newField.args); | |
for (const oldArg of argsDiff.removed) { | |
schemaChanges.push({ | |
type: BreakingChangeType.ARG_REMOVED, | |
description: `${oldType.name}.${oldField.name} arg ${oldArg.name} was removed.`, | |
}); | |
} | |
for (const [oldArg, newArg] of argsDiff.persisted) { | |
const isSafe = isChangeSafeForInputObjectFieldOrFieldArg( | |
oldArg.type, | |
newArg.type, | |
); | |
if (!isSafe) { | |
schemaChanges.push({ | |
type: BreakingChangeType.ARG_CHANGED_KIND, | |
description: | |
`${oldType.name}.${oldField.name} arg ${oldArg.name} has changed type from ` + | |
`${String(oldArg.type)} to ${String(newArg.type)}.`, | |
}); | |
} else if (oldArg.defaultValue !== undefined) { | |
if (newArg.defaultValue === undefined) { | |
schemaChanges.push({ | |
type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE, | |
description: `${oldType.name}.${oldField.name} arg ${oldArg.name} defaultValue was removed.`, | |
}); | |
} else { | |
// Since we looking only for client's observable changes we should | |
// compare default values in the same representation as they are | |
// represented inside introspection. | |
const oldValueStr = stringifyValue(oldArg.defaultValue, oldArg.type); | |
const newValueStr = stringifyValue(newArg.defaultValue, newArg.type); | |
if (oldValueStr !== newValueStr) { | |
schemaChanges.push({ | |
type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE, | |
description: `${oldType.name}.${oldField.name} arg ${oldArg.name} has changed defaultValue from ${oldValueStr} to ${newValueStr}.`, | |
}); | |
} | |
} | |
} | |
} | |
for (const newArg of argsDiff.added) { | |
if ((0, _definition.isRequiredArgument)(newArg)) { | |
schemaChanges.push({ | |
type: BreakingChangeType.REQUIRED_ARG_ADDED, | |
description: `A required arg ${newArg.name} on ${oldType.name}.${oldField.name} was added.`, | |
}); | |
} else { | |
schemaChanges.push({ | |
type: DangerousChangeType.OPTIONAL_ARG_ADDED, | |
description: `An optional arg ${newArg.name} on ${oldType.name}.${oldField.name} was added.`, | |
}); | |
} | |
} | |
return schemaChanges; | |
} | |
function isChangeSafeForObjectOrInterfaceField(oldType, newType) { | |
if ((0, _definition.isListType)(oldType)) { | |
return ( | |
// if they're both lists, make sure the underlying types are compatible | |
((0, _definition.isListType)(newType) && | |
isChangeSafeForObjectOrInterfaceField( | |
oldType.ofType, | |
newType.ofType, | |
)) || // moving from nullable to non-null of the same underlying type is safe | |
((0, _definition.isNonNullType)(newType) && | |
isChangeSafeForObjectOrInterfaceField(oldType, newType.ofType)) | |
); | |
} | |
if ((0, _definition.isNonNullType)(oldType)) { | |
// if they're both non-null, make sure the underlying types are compatible | |
return ( | |
(0, _definition.isNonNullType)(newType) && | |
isChangeSafeForObjectOrInterfaceField(oldType.ofType, newType.ofType) | |
); | |
} | |
return ( | |
// if they're both named types, see if their names are equivalent | |
((0, _definition.isNamedType)(newType) && oldType.name === newType.name) || // moving from nullable to non-null of the same underlying type is safe | |
((0, _definition.isNonNullType)(newType) && | |
isChangeSafeForObjectOrInterfaceField(oldType, newType.ofType)) | |
); | |
} | |
function isChangeSafeForInputObjectFieldOrFieldArg(oldType, newType) { | |
if ((0, _definition.isListType)(oldType)) { | |
// if they're both lists, make sure the underlying types are compatible | |
return ( | |
(0, _definition.isListType)(newType) && | |
isChangeSafeForInputObjectFieldOrFieldArg(oldType.ofType, newType.ofType) | |
); | |
} | |
if ((0, _definition.isNonNullType)(oldType)) { | |
return ( | |
// if they're both non-null, make sure the underlying types are | |
// compatible | |
((0, _definition.isNonNullType)(newType) && | |
isChangeSafeForInputObjectFieldOrFieldArg( | |
oldType.ofType, | |
newType.ofType, | |
)) || // moving from non-null to nullable of the same underlying type is safe | |
(!(0, _definition.isNonNullType)(newType) && | |
isChangeSafeForInputObjectFieldOrFieldArg(oldType.ofType, newType)) | |
); | |
} // if they're both named types, see if their names are equivalent | |
return (0, _definition.isNamedType)(newType) && oldType.name === newType.name; | |
} | |
function typeKindName(type) { | |
if ((0, _definition.isScalarType)(type)) { | |
return 'a Scalar type'; | |
} | |
if ((0, _definition.isObjectType)(type)) { | |
return 'an Object type'; | |
} | |
if ((0, _definition.isInterfaceType)(type)) { | |
return 'an Interface type'; | |
} | |
if ((0, _definition.isUnionType)(type)) { | |
return 'a Union type'; | |
} | |
if ((0, _definition.isEnumType)(type)) { | |
return 'an Enum type'; | |
} | |
if ((0, _definition.isInputObjectType)(type)) { | |
return 'an Input type'; | |
} | |
/* c8 ignore next 3 */ | |
// Not reachable, all possible types have been considered. | |
false || | |
(0, _invariant.invariant)( | |
false, | |
'Unexpected type: ' + (0, _inspect.inspect)(type), | |
); | |
} | |
function stringifyValue(value, type) { | |
const ast = (0, _astFromValue.astFromValue)(value, type); | |
ast != null || (0, _invariant.invariant)(false); | |
return (0, _printer.print)((0, _sortValueNode.sortValueNode)(ast)); | |
} | |
function diff(oldArray, newArray) { | |
const added = []; | |
const removed = []; | |
const persisted = []; | |
const oldMap = (0, _keyMap.keyMap)(oldArray, ({ name }) => name); | |
const newMap = (0, _keyMap.keyMap)(newArray, ({ name }) => name); | |
for (const oldItem of oldArray) { | |
const newItem = newMap[oldItem.name]; | |
if (newItem === undefined) { | |
removed.push(oldItem); | |
} else { | |
persisted.push([oldItem, newItem]); | |
} | |
} | |
for (const newItem of newArray) { | |
if (oldMap[newItem.name] === undefined) { | |
added.push(newItem); | |
} | |
} | |
return { | |
added, | |
persisted, | |
removed, | |
}; | |
} | |