diff --git a/src/builder.ts b/src/builder.ts index ce08fa5a..f1e9abf5 100644 --- a/src/builder.ts +++ b/src/builder.ts @@ -84,6 +84,9 @@ import { finalizeWrapping, AllNexusNamedInputTypeDefs, AllNexusNamedOutputTypeDefs, + AllNexusArgsDefs, + isNexusWrappingType, + NexusFinalWrapKind, } from './definitions/wrapping' import { MissingType, @@ -1184,11 +1187,21 @@ export class SchemaBuilder { ...rest, } if (typeof type !== 'undefined') { - const { wrapping } = unwrapGraphQLDef(config.fields[field].type) - interfaceFieldsMap[field].type = rewrapAsGraphQLType( - this.getOrBuildType(type), - wrapping - ) as GraphQLOutputType + let interfaceReplacement: GraphQLOutputType + if (isNexusWrappingType(type)) { + const { wrapping, namedType } = unwrapNexusDef(type) + interfaceReplacement = rewrapAsGraphQLType( + this.getOrBuildType(namedType as any), + wrapping as NexusFinalWrapKind[] + ) as GraphQLOutputType + } else { + const { wrapping } = unwrapGraphQLDef(config.fields[field].type) + interfaceReplacement = rewrapAsGraphQLType( + this.getOutputType(type), + wrapping + ) as GraphQLOutputType + } + interfaceFieldsMap[field].type = interfaceReplacement } if (typeof args !== 'undefined') { interfaceFieldsMap[field].args = { @@ -1565,7 +1578,7 @@ export class SchemaBuilder { }, addField: (f) => this.maybeTraverseOutputFieldType(f), addDynamicOutputMembers: (block, wrapping) => this.addDynamicOutputMembers(block, 'walk', wrapping), - addModification: (o) => (o.type && typeof o.type !== 'string' ? this.addType(o.type) : null), + addModification: (o) => this.maybeTraverseModification(o), warn: () => {}, }) obj.definition(definitionBlock) @@ -1575,7 +1588,7 @@ export class SchemaBuilder { protected walkInterfaceType(obj: NexusInterfaceTypeConfig) { const definitionBlock = new InterfaceDefinitionBlock({ typeName: obj.name, - addModification: (o) => (o.type && typeof o.type !== 'string' ? this.addType(o.type) : null), + addModification: (o) => this.maybeTraverseModification(o), addInterfaces: (i) => { i.forEach((j) => { if (typeof j !== 'string') { @@ -1591,6 +1604,19 @@ export class SchemaBuilder { return obj } + protected maybeTraverseModification(mod: FieldModificationDef) { + const { type, args } = mod + if (type) { + const namedFieldType = getNexusNamedType(mod.type) + if (typeof namedFieldType !== 'string') { + this.addType(namedFieldType) + } + } + if (args) { + this.traverseArgs(args) + } + } + protected maybeTraverseOutputFieldType(type: NexusOutputFieldDef) { const { args, type: fieldType } = type const namedFieldType = getNexusNamedType(fieldType) @@ -1598,19 +1624,22 @@ export class SchemaBuilder { this.addType(namedFieldType) } if (args) { - eachObj(args, (val) => { - const namedArgType = getArgNamedType(val) - if (typeof namedArgType !== 'string') { - this.addType(namedArgType) - } - }) + this.traverseArgs(args) } } + private traverseArgs(args: Record) { + eachObj(args, (val) => { + const namedArgType = getArgNamedType(val) + if (typeof namedArgType !== 'string') { + this.addType(namedArgType) + } + }) + } + protected maybeTraverseInputFieldType(type: NexusInputFieldDef) { const { type: fieldType } = type const namedFieldType = getNexusNamedType(fieldType) - if (typeof namedFieldType !== 'string') { this.addType(namedFieldType) } diff --git a/src/definitions/list.ts b/src/definitions/list.ts index 198ebf83..b3843140 100644 --- a/src/definitions/list.ts +++ b/src/definitions/list.ts @@ -15,7 +15,7 @@ export class NexusListDef { constructor(readonly ofNexusType: TypeName) { /* istanbul ignore if */ - if (!isNexusStruct(ofNexusType) && !isType(ofNexusType) && typeof ofNexusType !== 'string') { + if (typeof ofNexusType !== 'string' && !isNexusStruct(ofNexusType) && !isType(ofNexusType)) { throw new Error('Cannot wrap unknown types in list(). Saw ' + ofNexusType) } } diff --git a/src/definitions/nonNull.ts b/src/definitions/nonNull.ts index 9c58db58..d8d875c6 100644 --- a/src/definitions/nonNull.ts +++ b/src/definitions/nonNull.ts @@ -8,7 +8,7 @@ export class NexusNonNullDef { private _isNexusNonNullDef: boolean = true constructor(readonly ofNexusType: TypeName) { - if (!isNexusStruct(ofNexusType) && !isType(ofNexusType) && typeof ofNexusType !== 'string') { + if (typeof ofNexusType !== 'string' && !isNexusStruct(ofNexusType) && !isType(ofNexusType)) { throw new Error('Cannot wrap unknown types in a nonNull(). Saw ' + ofNexusType) } } diff --git a/src/definitions/nullable.ts b/src/definitions/nullable.ts index c2822a2f..6eb62056 100644 --- a/src/definitions/nullable.ts +++ b/src/definitions/nullable.ts @@ -8,7 +8,7 @@ export class NexusNullDef { private _isNexusNullDef: boolean = true constructor(readonly ofNexusType: TypeName) { - if (!isNexusStruct(ofNexusType) && !isType(ofNexusType) && typeof ofNexusType !== 'string') { + if (typeof ofNexusType !== 'string' && !isNexusStruct(ofNexusType) && !isType(ofNexusType)) { throw new Error('Cannot wrap unknown types in nullable(). Saw ' + ofNexusType) } } diff --git a/tests/modify.spec.ts b/tests/modify.spec.ts index 2f6bf3e9..730f501d 100644 --- a/tests/modify.spec.ts +++ b/tests/modify.spec.ts @@ -1,14 +1,14 @@ import { graphql } from 'graphql' import { + generateSchema, idArg, interfaceType, makeSchema, NexusGraphQLSchema, + nonNull, objectType, queryField, stringArg, - nonNull, - generateSchema, } from '../src/core' describe('modify', () => { @@ -29,6 +29,7 @@ describe('modify', () => { t.field('subNode', { type: 'Node', }) + t.string('requiredInSubtype') }, resolveType: (o) => o.__typename, }), @@ -43,6 +44,9 @@ describe('modify', () => { t.field('subNode', { type: 'User', }) + t.modify('requiredInSubtype', { + type: nonNull('String'), + }) }, }), objectType({ @@ -186,6 +190,47 @@ describe('modify', () => { ) }) + it('can modify nullability of an abstract type field inherited from an interface', async () => { + const result = await graphql( + schema, + ` + { + node: __type(name: "Node") { + fields { + name + description + type { + name + } + } + } + user: __type(name: "User") { + fields { + name + description + type { + kind + ofType { + name + } + } + } + } + } + ` + ) + + expect( + result.data?.node.fields.find((f: { name: string }) => f.name === 'requiredInSubtype').type.name + ).toEqual('String') + expect( + result.data?.user.fields.find((f: { name: string }) => f.name === 'requiredInSubtype').type.kind + ).toEqual('NON_NULL') + expect( + result.data?.user.fields.find((f: { name: string }) => f.name === 'requiredInSubtype').type.ofType.name + ).toEqual('String') + }) + describe('interfaces implementing interfaces', () => { let schemaTypes: string beforeAll(async () => {