diff --git a/.changeset/sharp-files-sin.md b/.changeset/sharp-files-sin.md new file mode 100644 index 0000000000..1fdee7b05c --- /dev/null +++ b/.changeset/sharp-files-sin.md @@ -0,0 +1,7 @@ +--- +'@graphql-inspector/patch': minor +'@graphql-inspector/core': minor +--- + +Adjust SCHEMA\_\*\_TYPE_CHANGED changes to use null instead of 'unknown' when these types are not +defined and improve the change messages. diff --git a/.changeset/twelve-ties-pretend.md b/.changeset/twelve-ties-pretend.md new file mode 100644 index 0000000000..8b1e025b9e --- /dev/null +++ b/.changeset/twelve-ties-pretend.md @@ -0,0 +1,5 @@ +--- +'@graphql-inspector/core': minor +--- + +diff can be passed null schemas. This lets it output the full list of additions on the new schema. diff --git a/packages/core/__tests__/diff/schema.test.ts b/packages/core/__tests__/diff/schema.test.ts index da75efb265..66307aaeeb 100644 --- a/packages/core/__tests__/diff/schema.test.ts +++ b/packages/core/__tests__/diff/schema.test.ts @@ -1,7 +1,5 @@ import { buildClientSchema, buildSchema, introspectionFromSchema } from 'graphql'; import { Change, CriticalityLevel, diff } from '../../src/index.js'; -import { findBestMatch } from '../../src/utils/string.js'; -import { findChangesByPath, findFirstChangeByPath } from '../../utils/testing.js'; test('same schema', async () => { const schemaA = buildSchema(/* GraphQL */ ` @@ -69,7 +67,7 @@ test('renamed query', async () => { expect(changed).toBeDefined(); expect(changed.criticality.level).toEqual(CriticalityLevel.Breaking); - expect(changed.message).toEqual(`Schema query root has changed from 'Query' to 'RootQuery'`); + expect(changed.message).toEqual(`Schema query root type was changed from 'Query' to 'RootQuery'`); }); test('new field and field changed', async () => { @@ -767,10 +765,10 @@ test('adding root type should not be breaking', async () => { "criticality": { "level": "NON_BREAKING", }, - "message": "Schema subscription root has changed from 'unknown' to 'Subscription'", + "message": "Schema subscription type was set to 'Subscription'.", "meta": { "newSubscriptionTypeName": "Subscription", - "oldSubscriptionTypeName": "unknown", + "oldSubscriptionTypeName": null, }, "type": "SCHEMA_SUBSCRIPTION_TYPE_CHANGED", }, @@ -803,3 +801,161 @@ test('adding root type should not be breaking', async () => { ] `); }); + +test('null old schema', async () => { + const schemaA = null; + + const schemaB = buildSchema(/* GraphQL */ ` + type Query { + foo: String + } + + type Subscription { + onFoo: String + } + `); + + const changes = await diff(schemaA, schemaB); + expect(changes).toMatchInlineSnapshot(` + [ + { + "criticality": { + "level": "NON_BREAKING", + }, + "message": "Schema query root type was set to 'Query'.", + "meta": { + "newQueryTypeName": "Query", + "oldQueryTypeName": null, + }, + "type": "SCHEMA_QUERY_TYPE_CHANGED", + }, + { + "criticality": { + "level": "NON_BREAKING", + }, + "message": "Schema subscription type was set to 'Subscription'.", + "meta": { + "newSubscriptionTypeName": "Subscription", + "oldSubscriptionTypeName": null, + }, + "type": "SCHEMA_SUBSCRIPTION_TYPE_CHANGED", + }, + { + "criticality": { + "level": "NON_BREAKING", + }, + "message": "Type 'Query' was added", + "meta": { + "addedTypeKind": "ObjectTypeDefinition", + "addedTypeName": "Query", + }, + "path": "Query", + "type": "TYPE_ADDED", + }, + { + "criticality": { + "level": "NON_BREAKING", + }, + "message": "Field 'foo' was added to object type 'Query'", + "meta": { + "addedFieldName": "foo", + "addedFieldReturnType": "String", + "typeName": "Query", + "typeType": "object type", + }, + "path": "Query.foo", + "type": "FIELD_ADDED", + }, + { + "criticality": { + "level": "NON_BREAKING", + }, + "message": "Type 'Subscription' was added", + "meta": { + "addedTypeKind": "ObjectTypeDefinition", + "addedTypeName": "Subscription", + }, + "path": "Subscription", + "type": "TYPE_ADDED", + }, + { + "criticality": { + "level": "NON_BREAKING", + }, + "message": "Field 'onFoo' was added to object type 'Subscription'", + "meta": { + "addedFieldName": "onFoo", + "addedFieldReturnType": "String", + "typeName": "Subscription", + "typeType": "object type", + }, + "path": "Subscription.onFoo", + "type": "FIELD_ADDED", + }, + ] + `); +}); + +test('null new schema', async () => { + const schemaA = buildSchema(/* GraphQL */ ` + type Query { + foo: String + } + + type Subscription { + onFoo: String + } + `); + + const schemaB = null; + + const changes = await diff(schemaA, schemaB); + expect(changes).toMatchInlineSnapshot(` + [ + { + "criticality": { + "level": "BREAKING", + }, + "message": "Schema query root type 'Query' was removed.", + "meta": { + "newQueryTypeName": null, + "oldQueryTypeName": "Query", + }, + "type": "SCHEMA_QUERY_TYPE_CHANGED", + }, + { + "criticality": { + "level": "BREAKING", + }, + "message": "Schema subscription type 'Subscription' was removed.", + "meta": { + "newSubscriptionTypeName": null, + "oldSubscriptionTypeName": "Subscription", + }, + "type": "SCHEMA_SUBSCRIPTION_TYPE_CHANGED", + }, + { + "criticality": { + "level": "BREAKING", + }, + "message": "Type 'Query' was removed", + "meta": { + "removedTypeName": "Query", + }, + "path": "Query", + "type": "TYPE_REMOVED", + }, + { + "criticality": { + "level": "BREAKING", + }, + "message": "Type 'Subscription' was removed", + "meta": { + "removedTypeName": "Subscription", + }, + "path": "Subscription", + "type": "TYPE_REMOVED", + }, + ] + `); +}); diff --git a/packages/core/src/diff/changes/change.ts b/packages/core/src/diff/changes/change.ts index ba7815bfa3..edd40328bd 100644 --- a/packages/core/src/diff/changes/change.ts +++ b/packages/core/src/diff/changes/change.ts @@ -564,24 +564,24 @@ export type ObjectTypeInterfaceRemovedChange = { export type SchemaQueryTypeChangedChange = { type: typeof ChangeType.SchemaQueryTypeChanged; meta: { - oldQueryTypeName: string; - newQueryTypeName: string; + oldQueryTypeName: string | null; + newQueryTypeName: string | null; }; }; export type SchemaMutationTypeChangedChange = { type: typeof ChangeType.SchemaMutationTypeChanged; meta: { - oldMutationTypeName: string; - newMutationTypeName: string; + oldMutationTypeName: string | null; + newMutationTypeName: string | null; }; }; export type SchemaSubscriptionTypeChangedChange = { type: typeof ChangeType.SchemaSubscriptionTypeChanged; meta: { - oldSubscriptionTypeName: string; - newSubscriptionTypeName: string; + oldSubscriptionTypeName: string | null; + newSubscriptionTypeName: string | null; }; }; diff --git a/packages/core/src/diff/changes/directive-usage.ts b/packages/core/src/diff/changes/directive-usage.ts index b6d8da22d8..28d6bf89df 100644 --- a/packages/core/src/diff/changes/directive-usage.ts +++ b/packages/core/src/diff/changes/directive-usage.ts @@ -108,7 +108,7 @@ type KindToPayload = { change: DirectiveUsageEnumValueAddedChange | DirectiveUsageEnumValueRemovedChange; }; [Kind.SCHEMA_DEFINITION]: { - input: GraphQLSchema; + input: GraphQLSchema | null; change: DirectiveUsageSchemaAddedChange | DirectiveUsageSchemaRemovedChange; }; [Kind.SCALAR_TYPE_DEFINITION]: { @@ -836,9 +836,9 @@ export function directiveUsageAdded( type: ChangeType.DirectiveUsageSchemaAdded, meta: { addedDirectiveName: directive.name.value, - schemaTypeName: payload.getQueryType()?.name || '', + schemaTypeName: payload?.getQueryType()?.name || '', addedToNewType, - directiveRepeatedTimes: directiveRepeatTimes(payload.astNode?.directives ?? [], directive), + directiveRepeatedTimes: directiveRepeatTimes(payload?.astNode?.directives ?? [], directive), }, }); } @@ -1016,8 +1016,8 @@ export function directiveUsageRemoved( type: ChangeType.DirectiveUsageSchemaRemoved, meta: { removedDirectiveName: directive.name.value, - schemaTypeName: payload.getQueryType()?.name || '', - directiveRepeatedTimes: directiveRepeatTimes(payload.astNode?.directives ?? [], directive), + schemaTypeName: payload?.getQueryType()?.name || '', + directiveRepeatedTimes: directiveRepeatTimes(payload?.astNode?.directives ?? [], directive), }, }); } diff --git a/packages/core/src/diff/changes/schema.ts b/packages/core/src/diff/changes/schema.ts index ea349d384a..c5ed0eeea2 100644 --- a/packages/core/src/diff/changes/schema.ts +++ b/packages/core/src/diff/changes/schema.ts @@ -9,7 +9,13 @@ import { } from './change.js'; function buildSchemaQueryTypeChangedMessage(args: SchemaQueryTypeChangedChange['meta']): string { - return `Schema query root has changed from '${args.oldQueryTypeName}' to '${args.newQueryTypeName}'`; + if (args.oldQueryTypeName === null) { + return `Schema query root type was set to '${args.newQueryTypeName}'.`; + } + if (args.newQueryTypeName === null) { + return `Schema query root type '${args.oldQueryTypeName}' was removed.`; + } + return `Schema query root type was changed from '${args.oldQueryTypeName}' to '${args.newQueryTypeName}'`; } export function schemaQueryTypeChangedFromMeta(args: SchemaQueryTypeChangedChange) { @@ -17,7 +23,7 @@ export function schemaQueryTypeChangedFromMeta(args: SchemaQueryTypeChangedChang type: ChangeType.SchemaQueryTypeChanged, criticality: { level: - args.meta.oldQueryTypeName === 'unknown' + args.meta.oldQueryTypeName === null ? CriticalityLevel.NonBreaking : CriticalityLevel.Breaking, }, @@ -27,11 +33,11 @@ export function schemaQueryTypeChangedFromMeta(args: SchemaQueryTypeChangedChang } export function schemaQueryTypeChanged( - oldSchema: GraphQLSchema, - newSchema: GraphQLSchema, + oldSchema: GraphQLSchema | null, + newSchema: GraphQLSchema | null, ): Change { - const oldName = (oldSchema.getQueryType() || ({} as any)).name || 'unknown'; - const newName = (newSchema.getQueryType() || ({} as any)).name || 'unknown'; + const oldName = oldSchema?.getQueryType()?.name || null; + const newName = newSchema?.getQueryType()?.name || null; return schemaQueryTypeChangedFromMeta({ type: ChangeType.SchemaQueryTypeChanged, @@ -45,7 +51,13 @@ export function schemaQueryTypeChanged( function buildSchemaMutationTypeChangedMessage( args: SchemaMutationTypeChangedChange['meta'], ): string { - return `Schema mutation root has changed from '${args.oldMutationTypeName}' to '${args.newMutationTypeName}'`; + if (args.oldMutationTypeName === null) { + return `Schema mutation type was set to '${args.newMutationTypeName}'.`; + } + if (args.newMutationTypeName === null) { + return `Schema mutation type '${args.oldMutationTypeName}' was removed.`; + } + return `Schema mutation type was changed from '${args.oldMutationTypeName}' to '${args.newMutationTypeName}'`; } export function schemaMutationTypeChangedFromMeta(args: SchemaMutationTypeChangedChange) { @@ -53,7 +65,7 @@ export function schemaMutationTypeChangedFromMeta(args: SchemaMutationTypeChange type: ChangeType.SchemaMutationTypeChanged, criticality: { level: - args.meta.oldMutationTypeName === 'unknown' + args.meta.oldMutationTypeName === null ? CriticalityLevel.NonBreaking : CriticalityLevel.Breaking, }, @@ -63,11 +75,11 @@ export function schemaMutationTypeChangedFromMeta(args: SchemaMutationTypeChange } export function schemaMutationTypeChanged( - oldSchema: GraphQLSchema, - newSchema: GraphQLSchema, + oldSchema: GraphQLSchema | null, + newSchema: GraphQLSchema | null, ): Change { - const oldName = (oldSchema.getMutationType() || ({} as any)).name || 'unknown'; - const newName = (newSchema.getMutationType() || ({} as any)).name || 'unknown'; + const oldName = oldSchema?.getMutationType()?.name || null; + const newName = newSchema?.getMutationType()?.name || null; return schemaMutationTypeChangedFromMeta({ type: ChangeType.SchemaMutationTypeChanged, @@ -81,7 +93,13 @@ export function schemaMutationTypeChanged( function buildSchemaSubscriptionTypeChangedMessage( args: SchemaSubscriptionTypeChangedChange['meta'], ): string { - return `Schema subscription root has changed from '${args.oldSubscriptionTypeName}' to '${args.newSubscriptionTypeName}'`; + if (args.oldSubscriptionTypeName === null) { + return `Schema subscription type was set to '${args.newSubscriptionTypeName}'.`; + } + if (args.newSubscriptionTypeName === null) { + return `Schema subscription type '${args.oldSubscriptionTypeName}' was removed.`; + } + return `Schema subscription type was changed from '${args.oldSubscriptionTypeName}' to '${args.newSubscriptionTypeName}'`; } export function schemaSubscriptionTypeChangedFromMeta(args: SchemaSubscriptionTypeChangedChange) { @@ -89,7 +107,7 @@ export function schemaSubscriptionTypeChangedFromMeta(args: SchemaSubscriptionTy type: ChangeType.SchemaSubscriptionTypeChanged, criticality: { level: - args.meta.oldSubscriptionTypeName === 'unknown' + args.meta.oldSubscriptionTypeName === null ? CriticalityLevel.NonBreaking : CriticalityLevel.Breaking, }, @@ -99,11 +117,11 @@ export function schemaSubscriptionTypeChangedFromMeta(args: SchemaSubscriptionTy } export function schemaSubscriptionTypeChanged( - oldSchema: GraphQLSchema, - newSchema: GraphQLSchema, + oldSchema: GraphQLSchema | null, + newSchema: GraphQLSchema | null, ): Change { - const oldName = (oldSchema.getSubscriptionType() || ({} as any)).name || 'unknown'; - const newName = (newSchema.getSubscriptionType() || ({} as any)).name || 'unknown'; + const oldName = oldSchema?.getSubscriptionType()?.name || null; + const newName = newSchema?.getSubscriptionType()?.name || null; return schemaSubscriptionTypeChangedFromMeta({ type: ChangeType.SchemaSubscriptionTypeChanged, diff --git a/packages/core/src/diff/index.ts b/packages/core/src/diff/index.ts index c8e7ce4c9c..74cf7f2f06 100644 --- a/packages/core/src/diff/index.ts +++ b/packages/core/src/diff/index.ts @@ -11,8 +11,8 @@ export * from './onComplete/types.js'; export type { UsageHandler } from './rules/consider-usage.js'; export function diff( - oldSchema: GraphQLSchema, - newSchema: GraphQLSchema, + oldSchema: GraphQLSchema | null, + newSchema: GraphQLSchema | null, rules: Rule[] = [], config?: rules.ConsiderUsageConfig, ): Promise { diff --git a/packages/core/src/diff/rules/safe-unreachable.ts b/packages/core/src/diff/rules/safe-unreachable.ts index 062eb94226..69c9457f6f 100644 --- a/packages/core/src/diff/rules/safe-unreachable.ts +++ b/packages/core/src/diff/rules/safe-unreachable.ts @@ -4,7 +4,7 @@ import { CriticalityLevel } from '../changes/change.js'; import { Rule } from './types.js'; export const safeUnreachable: Rule = ({ changes, oldSchema }) => { - const reachable = getReachableTypes(oldSchema); + const reachable = oldSchema ? getReachableTypes(oldSchema) : new Set(); return changes.map(change => { if (change.criticality.level === CriticalityLevel.Breaking && change.path) { diff --git a/packages/core/src/diff/rules/suppress-removal-of-deprecated-field.ts b/packages/core/src/diff/rules/suppress-removal-of-deprecated-field.ts index e36c0faa81..29c9a6e7fa 100644 --- a/packages/core/src/diff/rules/suppress-removal-of-deprecated-field.ts +++ b/packages/core/src/diff/rules/suppress-removal-of-deprecated-field.ts @@ -12,7 +12,7 @@ export const suppressRemovalOfDeprecatedField: Rule = ({ changes, oldSchema, new change.path ) { const [typeName, fieldName] = parsePath(change.path); - const type = oldSchema.getType(typeName); + const type = oldSchema?.getType(typeName); if (isObjectType(type) || isInterfaceType(type)) { const field = type.getFields()[fieldName]; @@ -35,7 +35,7 @@ export const suppressRemovalOfDeprecatedField: Rule = ({ changes, oldSchema, new change.path ) { const [enumName, enumItem] = parsePath(change.path); - const type = oldSchema.getType(enumName); + const type = oldSchema?.getType(enumName); if (isEnumType(type)) { const item = type.getValue(enumItem); @@ -58,7 +58,7 @@ export const suppressRemovalOfDeprecatedField: Rule = ({ changes, oldSchema, new change.path ) { const [inputName, inputItem] = parsePath(change.path); - const type = oldSchema.getType(inputName); + const type = oldSchema?.getType(inputName); if (isInputObjectType(type)) { const item = type.getFields()[inputItem]; @@ -81,7 +81,7 @@ export const suppressRemovalOfDeprecatedField: Rule = ({ changes, oldSchema, new change.path ) { const [typeName] = parsePath(change.path); - const type = newSchema.getType(typeName); + const type = newSchema?.getType(typeName); if (!type) { return { diff --git a/packages/core/src/diff/rules/types.ts b/packages/core/src/diff/rules/types.ts index ea70bb9413..b85e8824a5 100644 --- a/packages/core/src/diff/rules/types.ts +++ b/packages/core/src/diff/rules/types.ts @@ -3,7 +3,7 @@ import { Change } from '../changes/change.js'; export type Rule = (input: { changes: Change[]; - oldSchema: GraphQLSchema; - newSchema: GraphQLSchema; + oldSchema: GraphQLSchema | null; + newSchema: GraphQLSchema | null; config: TConfig; }) => Change[] | Promise; diff --git a/packages/core/src/diff/schema.ts b/packages/core/src/diff/schema.ts index d29b49d03b..54e55bd8ea 100644 --- a/packages/core/src/diff/schema.ts +++ b/packages/core/src/diff/schema.ts @@ -7,11 +7,12 @@ import { isInterfaceType, isObjectType, isScalarType, + isSpecifiedDirective, isUnionType, Kind, } from 'graphql'; import { compareDirectiveLists, compareLists, isNotEqual, isVoid } from '../utils/compare.js'; -import { isPrimitive } from '../utils/graphql.js'; +import { isForIntrospection, isPrimitive } from '../utils/graphql.js'; import { Change } from './changes/change.js'; import { directiveUsageAdded, @@ -42,7 +43,10 @@ import { changesInUnion } from './union.js'; export type AddChange = (change: Change) => void; -export function diffSchema(oldSchema: GraphQLSchema, newSchema: GraphQLSchema): Change[] { +export function diffSchema( + oldSchema: GraphQLSchema | null, + newSchema: GraphQLSchema | null, +): Change[] { const changes: Change[] = []; function addChange(change: Change) { @@ -52,8 +56,12 @@ export function diffSchema(oldSchema: GraphQLSchema, newSchema: GraphQLSchema): changesInSchema(oldSchema, newSchema, addChange); compareLists( - Object.values(oldSchema.getTypeMap()).filter(t => !isPrimitive(t)), - Object.values(newSchema.getTypeMap()).filter(t => !isPrimitive(t)), + Object.values(oldSchema?.getTypeMap() ?? {}).filter( + t => !isPrimitive(t) && !isForIntrospection(t), + ), + Object.values(newSchema?.getTypeMap() ?? {}).filter( + t => !isPrimitive(t) && !isForIntrospection(t), + ), { onAdded(type) { addChange(typeAdded(type)); @@ -68,45 +76,57 @@ export function diffSchema(oldSchema: GraphQLSchema, newSchema: GraphQLSchema): }, ); - compareLists(oldSchema.getDirectives(), newSchema.getDirectives(), { - onAdded(directive) { - addChange(directiveAdded(directive)); - changesInDirective(null, directive, addChange); - }, - onRemoved(directive) { - addChange(directiveRemoved(directive)); - }, - onMutual(directive) { - changesInDirective(directive.oldVersion, directive.newVersion, addChange); + compareLists( + (oldSchema?.getDirectives() ?? []).filter(t => !isSpecifiedDirective(t)), + (newSchema?.getDirectives() ?? []).filter(t => !isSpecifiedDirective(t)), + { + onAdded(directive) { + addChange(directiveAdded(directive)); + changesInDirective(null, directive, addChange); + }, + onRemoved(directive) { + addChange(directiveRemoved(directive)); + }, + onMutual(directive) { + changesInDirective(directive.oldVersion, directive.newVersion, addChange); + }, }, - }); + ); - compareDirectiveLists(oldSchema.astNode?.directives || [], newSchema.astNode?.directives || [], { - onAdded(directive) { - addChange(directiveUsageAdded(Kind.SCHEMA_DEFINITION, directive, newSchema, false)); - directiveUsageChanged(null, directive, addChange); - }, - onMutual(directive) { - directiveUsageChanged(directive.oldVersion, directive.newVersion, addChange); - }, - onRemoved(directive) { - addChange(directiveUsageRemoved(Kind.SCHEMA_DEFINITION, directive, oldSchema)); + compareDirectiveLists( + oldSchema?.astNode?.directives || [], + newSchema?.astNode?.directives || [], + { + onAdded(directive) { + addChange(directiveUsageAdded(Kind.SCHEMA_DEFINITION, directive, newSchema, false)); + directiveUsageChanged(null, directive, addChange); + }, + onMutual(directive) { + directiveUsageChanged(directive.oldVersion, directive.newVersion, addChange); + }, + onRemoved(directive) { + addChange(directiveUsageRemoved(Kind.SCHEMA_DEFINITION, directive, oldSchema)); + }, }, - }); + ); return changes; } -function changesInSchema(oldSchema: GraphQLSchema, newSchema: GraphQLSchema, addChange: AddChange) { +function changesInSchema( + oldSchema: GraphQLSchema | null, + newSchema: GraphQLSchema | null, + addChange: AddChange, +) { const oldRoot = { - query: (oldSchema.getQueryType() || ({} as GraphQLObjectType)).name, - mutation: (oldSchema.getMutationType() || ({} as GraphQLObjectType)).name, - subscription: (oldSchema.getSubscriptionType() || ({} as GraphQLObjectType)).name, + query: (oldSchema?.getQueryType() || ({} as GraphQLObjectType)).name, + mutation: (oldSchema?.getMutationType() || ({} as GraphQLObjectType)).name, + subscription: (oldSchema?.getSubscriptionType() || ({} as GraphQLObjectType)).name, }; const newRoot = { - query: (newSchema.getQueryType() || ({} as GraphQLObjectType)).name, - mutation: (newSchema.getMutationType() || ({} as GraphQLObjectType)).name, - subscription: (newSchema.getSubscriptionType() || ({} as GraphQLObjectType)).name, + query: (newSchema?.getQueryType() || ({} as GraphQLObjectType)).name, + mutation: (newSchema?.getMutationType() || ({} as GraphQLObjectType)).name, + subscription: (newSchema?.getSubscriptionType() || ({} as GraphQLObjectType)).name, }; if (isNotEqual(oldRoot.query, newRoot.query)) { diff --git a/packages/patch/src/patches/schema.ts b/packages/patch/src/patches/schema.ts index c24572c249..93d56e1b09 100644 --- a/packages/patch/src/patches/schema.ts +++ b/packages/patch/src/patches/schema.ts @@ -16,27 +16,25 @@ export function schemaMutationTypeChanged( ({ operation }) => operation === OperationTypeNode.MUTATION, ); if (!mutation) { - if (change.meta.oldMutationTypeName !== 'unknown') { + if (change.meta.oldMutationTypeName !== null) { config.onError( - new ValueMismatchError( - Kind.SCHEMA_DEFINITION, - change.meta.oldMutationTypeName, - 'unknown', - ), + new ValueMismatchError(Kind.SCHEMA_DEFINITION, change.meta.oldMutationTypeName, null), change, ); } - (schemaNode.operationTypes as OperationTypeDefinitionNode[]) = [ - ...(schemaNode.operationTypes ?? []), - { - kind: Kind.OPERATION_TYPE_DEFINITION, - operation: OperationTypeNode.MUTATION, - type: { - kind: Kind.NAMED_TYPE, - name: nameNode(change.meta.newMutationTypeName), + if (change.meta.newMutationTypeName) { + (schemaNode.operationTypes as OperationTypeDefinitionNode[]) = [ + ...(schemaNode.operationTypes ?? []), + { + kind: Kind.OPERATION_TYPE_DEFINITION, + operation: OperationTypeNode.MUTATION, + type: { + kind: Kind.NAMED_TYPE, + name: nameNode(change.meta.newMutationTypeName), + }, }, - }, - ]; + ]; + } } else { if (mutation.type.name.value !== change.meta.oldMutationTypeName) { config.onError( @@ -48,6 +46,13 @@ export function schemaMutationTypeChanged( change, ); } + if (change.meta.newMutationTypeName === null) { + (schemaNode.operationTypes as OperationTypeDefinitionNode[] | undefined) = + schemaNode.operationTypes?.filter( + ({ operation }) => operation !== OperationTypeNode.MUTATION, + ); + return; + } (mutation.type.name as NameNode) = nameNode(change.meta.newMutationTypeName); } } @@ -64,23 +69,25 @@ export function schemaQueryTypeChanged( ({ operation }) => operation === OperationTypeNode.MUTATION, ); if (!query) { - if (change.meta.oldQueryTypeName !== 'unknown') { + if (change.meta.oldQueryTypeName !== null) { config.onError( - new ValueMismatchError(Kind.SCHEMA_DEFINITION, change.meta.oldQueryTypeName, 'unknown'), + new ValueMismatchError(Kind.SCHEMA_DEFINITION, change.meta.oldQueryTypeName, null), change, ); } - (schemaNode.operationTypes as OperationTypeDefinitionNode[]) = [ - ...(schemaNode.operationTypes ?? []), - { - kind: Kind.OPERATION_TYPE_DEFINITION, - operation: OperationTypeNode.QUERY, - type: { - kind: Kind.NAMED_TYPE, - name: nameNode(change.meta.newQueryTypeName), + if (change.meta.newQueryTypeName) { + (schemaNode.operationTypes as OperationTypeDefinitionNode[]) = [ + ...(schemaNode.operationTypes ?? []), + { + kind: Kind.OPERATION_TYPE_DEFINITION, + operation: OperationTypeNode.QUERY, + type: { + kind: Kind.NAMED_TYPE, + name: nameNode(change.meta.newQueryTypeName), + }, }, - }, - ]; + ]; + } } else { if (query.type.name.value !== change.meta.oldQueryTypeName) { config.onError( @@ -92,6 +99,13 @@ export function schemaQueryTypeChanged( change, ); } + if (change.meta.newQueryTypeName === null) { + (schemaNode.operationTypes as OperationTypeDefinitionNode[] | undefined) = + schemaNode.operationTypes?.filter( + ({ operation }) => operation !== OperationTypeNode.QUERY, + ); + return; + } (query.type.name as NameNode) = nameNode(change.meta.newQueryTypeName); } } @@ -108,27 +122,25 @@ export function schemaSubscriptionTypeChanged( ({ operation }) => operation === OperationTypeNode.SUBSCRIPTION, ); if (!sub) { - if (change.meta.oldSubscriptionTypeName !== 'unknown') { + if (change.meta.oldSubscriptionTypeName !== null) { config.onError( - new ValueMismatchError( - Kind.SCHEMA_DEFINITION, - change.meta.oldSubscriptionTypeName, - 'unknown', - ), + new ValueMismatchError(Kind.SCHEMA_DEFINITION, change.meta.oldSubscriptionTypeName, null), change, ); } - (schemaNode.operationTypes as OperationTypeDefinitionNode[]) = [ - ...(schemaNode.operationTypes ?? []), - { - kind: Kind.OPERATION_TYPE_DEFINITION, - operation: OperationTypeNode.QUERY, - type: { - kind: Kind.NAMED_TYPE, - name: nameNode(change.meta.newSubscriptionTypeName), + if (change.meta.newSubscriptionTypeName) { + (schemaNode.operationTypes as OperationTypeDefinitionNode[]) = [ + ...(schemaNode.operationTypes ?? []), + { + kind: Kind.OPERATION_TYPE_DEFINITION, + operation: OperationTypeNode.QUERY, + type: { + kind: Kind.NAMED_TYPE, + name: nameNode(change.meta.newSubscriptionTypeName), + }, }, - }, - ]; + ]; + } } else { if (sub.type.name.value !== change.meta.oldSubscriptionTypeName) { config.onError( @@ -140,6 +152,13 @@ export function schemaSubscriptionTypeChanged( change, ); } + if (change.meta.newSubscriptionTypeName === null) { + (schemaNode.operationTypes as OperationTypeDefinitionNode[] | undefined) = + schemaNode.operationTypes?.filter( + ({ operation }) => operation !== OperationTypeNode.SUBSCRIPTION, + ); + return; + } (sub.type.name as NameNode) = nameNode(change.meta.newSubscriptionTypeName); } }