UNPKG

nexus

Version:

Scalable, strongly typed GraphQL schema development

1,045 lines (950 loc) 34.7 kB
import { getNamedType, GraphQLAbstractType, GraphQLArgument, GraphQLField, GraphQLInputField, GraphQLInputType, GraphQLInterfaceType, GraphQLNamedType, GraphQLObjectType, GraphQLOutputType, GraphQLScalarType, GraphQLUnionType, isEnumType, isInputObjectType, isInterfaceType, isListType, isNonNullType, isObjectType, isScalarType, isSpecifiedScalarType, isUnionType, } from 'graphql' import type { TypegenInfo } from './builder' import { isNexusPrintedGenTyping, isNexusPrintedGenTypingImport } from './definitions/wrapping' import type { NexusGraphQLSchema } from './definitions/_types' import { TYPEGEN_HEADER } from './lang' import type { StringLike } from './plugin' import { hasNexusExtension } from './extensions' import { eachObj, getOwnPackage, graphql15InterfaceType, GroupedTypes, groupTypes, mapObj, mapValues, PrintedGenTypingImport, relativePathTo, resolveImportPath, } from './utils' const SpecifiedScalars = { ID: 'string', String: 'string', Float: 'number', Int: 'number', Boolean: 'boolean', } type SpecifiedScalarNames = keyof typeof SpecifiedScalars type TypeFieldMapping = Record<string, Record<string, [string, string]>> type TypeMapping = Record<string, string> type RootTypeMapping = Record<string, string | Record<string, [string, string]>> interface TypegenInfoWithFile extends TypegenInfo { typegenPath: string globalsPath?: string globalsHeaders?: string[] declareInputs?: boolean } /** * We track and output a few main things: * * 1. "root" types, or the values that fill the first argument for a given object type 2. "arg" types, the * values that are arguments to output fields. 3. "return" types, the values returned from the resolvers... * usually just list/nullable variations on the * "root" types for other types 4. The names of all types, grouped by type. * * - Non-scalar types will get a dedicated "Root" type associated with it */ export class TypegenPrinter { private groupedTypes: GroupedTypes private printImports: Record<string, Record<string, boolean | string>> private hasDiscriminatedTypes: boolean constructor(protected schema: NexusGraphQLSchema, protected typegenInfo: TypegenInfoWithFile) { this.groupedTypes = groupTypes(schema) this.printImports = {} this.hasDiscriminatedTypes = false } print() { const body = [this.printCommon(), this.printPlugins()].join('\n\n') return [this.printHeaders(), body].join('\n\n') } printConfigured() { if (this.typegenInfo.globalsPath) { const plugins = this.printPlugins() const globalTypes = [this.printHeadersGlobal(), this.printDynamicImport(true), plugins].join('\n\n') // Reset printImports for the imports needed in the types this.printImports = {} const common = this.printCommon() const tsTypes = [this.printHeadersCommon(), common].join('\n\n') return { tsTypes, globalTypes, } } return { tsTypes: this.print(), globalTypes: null, } } private printCommon() { return [ this.printInputTypeMap(), this.printEnumTypeMap(), this.printScalarTypeMap(), this.printObjectTypeMap(), this.printInterfaceTypeMap(), this.printUnionTypeMap(), this.printRootTypeDef(), this.printAllTypesMap(), this.printFieldTypesMap(), this.printFieldTypeNamesMap(), this.printArgTypeMap(), this.printAbstractTypeMembers(), this.printInheritedFieldMap(), this.printTypeNames('object', 'NexusGenObjectNames', 'NexusGenObjects'), this.printTypeNames('input', 'NexusGenInputNames', 'NexusGenInputs'), this.printTypeNames('enum', 'NexusGenEnumNames', 'NexusGenEnums'), this.printTypeNames('interface', 'NexusGenInterfaceNames', 'NexusGenInterfaces'), this.printTypeNames('scalar', 'NexusGenScalarNames', 'NexusGenScalars'), this.printTypeNames('union', 'NexusGenUnionNames', 'NexusGenUnions'), this.printIsTypeOfObjectTypeNames('NexusGenObjectsUsingAbstractStrategyIsTypeOf'), this.printResolveTypeAbstractTypes('NexusGenAbstractsUsingStrategyResolveType'), this.printFeaturesConfig('NexusGenFeaturesConfig'), this.printGenTypeMap(), ].join('\n\n') } private printHeaders() { return [this.printHeadersCommon(), this.printHeadersGlobal()].join('\n') } private printHeadersCommon() { return [ this.typegenInfo.headers.join('\n'), this.typegenInfo.imports.join('\n'), this.printDynamicImport(), ].join('\n') } private printHeadersGlobal() { const headers = [ this.printDynamicInputFieldDefinitions(), this.printDynamicOutputFieldDefinitions(), this.printDynamicOutputPropertyDefinitions(), GLOBAL_DECLARATION, ] if (this.typegenInfo.globalsPath) { headers.unshift( `import type { NexusGenTypes } from '${relativePathTo( this.typegenInfo.typegenPath, this.typegenInfo.globalsPath ?? '' )}'` ) headers.unshift(...(this.typegenInfo.globalsHeaders ?? [])) headers.unshift(TYPEGEN_HEADER) } return headers.join('\n') } private printGenTypeMap() { return [`export interface NexusGenTypes {`] .concat([ ` context: ${this.printContext()};`, ` inputTypes: NexusGenInputs;`, ` rootTypes: NexusGenRootTypes;`, ` inputTypeShapes: NexusGenInputs & NexusGenEnums & NexusGenScalars;`, ` argTypes: NexusGenArgTypes;`, ` fieldTypes: NexusGenFieldTypes;`, ` fieldTypeNames: NexusGenFieldTypeNames;`, ` allTypes: NexusGenAllTypes;`, ` typeInterfaces: NexusGenTypeInterfaces;`, ` objectNames: NexusGenObjectNames;`, ` inputNames: NexusGenInputNames;`, ` enumNames: NexusGenEnumNames;`, ` interfaceNames: NexusGenInterfaceNames;`, ` scalarNames: NexusGenScalarNames;`, ` unionNames: NexusGenUnionNames;`, ` allInputTypes: NexusGenTypes['inputNames'] | NexusGenTypes['enumNames'] | NexusGenTypes['scalarNames'];`, ` allOutputTypes: NexusGenTypes['objectNames'] | NexusGenTypes['enumNames'] | NexusGenTypes['unionNames'] | NexusGenTypes['interfaceNames'] | NexusGenTypes['scalarNames'];`, ` allNamedTypes: NexusGenTypes['allInputTypes'] | NexusGenTypes['allOutputTypes']`, ` abstractTypes: NexusGenTypes['interfaceNames'] | NexusGenTypes['unionNames'];`, ` abstractTypeMembers: NexusGenAbstractTypeMembers;`, ` objectsUsingAbstractStrategyIsTypeOf: NexusGenObjectsUsingAbstractStrategyIsTypeOf;`, ` abstractsUsingStrategyResolveType: NexusGenAbstractsUsingStrategyResolveType;`, ` features: NexusGenFeaturesConfig;`, ]) .concat('}') .join('\n') } private printDynamicImport(forGlobal = false) { const { sourceTypings } = this.schema.extensions.nexus.config const { contextTypeImport } = this.typegenInfo const imports: string[] = [] const importMap: Record<string, Set<string>> = {} const outputPath = this.typegenInfo.typegenPath const nexusSchemaImportId = this.typegenInfo.nexusSchemaImportId ?? getOwnPackage().name if (!this.printImports[nexusSchemaImportId]) { this.maybeAddCoreImport(forGlobal) } if (!forGlobal) { if (contextTypeImport) { const importPath = resolveImportPath(contextTypeImport, 'context', outputPath) importMap[importPath] = importMap[importPath] || new Set() importMap[importPath].add( contextTypeImport.alias ? `${contextTypeImport.export} as ${contextTypeImport.alias}` : contextTypeImport.export ) } eachObj(sourceTypings, (rootType, typeName) => { if (typeof rootType !== 'string') { const importPath = resolveImportPath(rootType, typeName, outputPath) importMap[importPath] = importMap[importPath] || new Set() importMap[importPath].add( rootType.alias ? `${rootType.export} as ${rootType.alias}` : rootType.export ) } }) eachObj(importMap, (val, key) => { imports.push(`import type { ${Array.from(val).join(', ')} } from ${JSON.stringify(key)}`) }) } eachObj(this.printImports, (val, key) => { const { default: def, ...rest } = val const idents = [] if (def) { idents.push(def) } let bindings: string[] = [] eachObj(rest, (alias, binding) => { bindings.push(alias !== true ? `${binding} as ${alias}` : `${binding}`) }) if (bindings.length) { idents.push(`{ ${bindings.join(', ')} }`) } imports.push(`import type ${idents.join(', ')} from ${JSON.stringify(key)}`) }) return imports.join('\n') } private maybeAddCoreImport(forGlobal = false) { const nexusSchemaImportId = this.typegenInfo.nexusSchemaImportId ?? getOwnPackage().name const { dynamicFields: { dynamicInputFields, dynamicOutputFields }, } = this.schema.extensions.nexus.config let shouldAdd = false const hasDynamicFields = [dynamicInputFields, dynamicOutputFields].some((o) => Object.keys(o).length > 0) if (!this.typegenInfo.globalsPath) { shouldAdd = hasDynamicFields || this.hasDiscriminatedTypes } else { shouldAdd = forGlobal ? hasDynamicFields : this.hasDiscriminatedTypes } if (shouldAdd) { this.printImports[nexusSchemaImportId] = { core: true, } } } private printDynamicInputFieldDefinitions() { const { dynamicInputFields } = this.schema.extensions.nexus.config.dynamicFields // If there is nothing custom... exit if (!Object.keys(dynamicInputFields).length) { return [] } return [`declare global {`, ` interface NexusGenCustomInputMethods<TypeName extends string> {`] .concat( mapObj(dynamicInputFields, (val, key) => { if (typeof val === 'string') { const baseType = this.schema.getType(val) return this.prependDoc( ` ${key}<FieldName extends string>(fieldName: FieldName, opts?: core.CommonInputFieldConfig<TypeName, FieldName>): void // ${JSON.stringify( val )};`, baseType?.description ) } return this.prependDoc( ` ${key}${val.value.typeDefinition || `(...args: any): void`}`, val.value.typeDescription ) }) ) .concat([` }`, `}`]) .join('\n') } private printDynamicOutputFieldDefinitions() { const { dynamicOutputFields } = this.schema.extensions.nexus.config.dynamicFields // If there is nothing custom... exit if (!Object.keys(dynamicOutputFields).length) { return [] } return [`declare global {`, ` interface NexusGenCustomOutputMethods<TypeName extends string> {`] .concat( mapObj(dynamicOutputFields, (val, key) => { if (typeof val === 'string') { const baseType = this.schema.getType(val) return this.prependDoc( ` ${key}<FieldName extends string>(fieldName: FieldName, ...opts: core.ScalarOutSpread<TypeName, FieldName>): void // ${JSON.stringify( val )};`, baseType?.description ) } return this.prependDoc( ` ${key}${val.value.typeDefinition || `(...args: any): void`}`, val.value.typeDescription ) }) ) .concat([` }`, `}`]) .join('\n') } private prependDoc(typeDef: string, typeDescription?: string | null) { let outStr = '' if (typeDescription) { let parts = typeDescription.split('\n').map((f) => f.trimLeft()) if (parts[0] === '') { parts = parts.slice(1) } if (parts[parts.length - 1] === '') { parts = parts.slice(0, -1) } outStr = [' /**', ...parts.map((p) => ` *${p ? ` ${p}` : ''}`), ' */'].join('\n') + '\n' } return `${outStr}${typeDef}` } private printDynamicOutputPropertyDefinitions() { const { dynamicOutputProperties } = this.schema.extensions.nexus.config.dynamicFields // If there is nothing custom... exit if (!Object.keys(dynamicOutputProperties).length) { return [] } return [`declare global {`, ` interface NexusGenCustomOutputProperties<TypeName extends string> {`] .concat( mapObj(dynamicOutputProperties, (val, key) => { return this.prependDoc( ` ${key}${val.value.typeDefinition || `: any`}`, val.value.typeDescription ) }) ) .concat([` }`, `}`]) .join('\n') } private printInheritedFieldMap() { const hasInterfaces: ( | (GraphQLInterfaceType & { getInterfaces(): ReadonlyArray<GraphQLInterfaceType> }) | GraphQLObjectType )[] = [] const withInterfaces = hasInterfaces .concat(this.groupedTypes.object, this.groupedTypes.interface.map(graphql15InterfaceType)) .map((o) => { if (o.getInterfaces().length) { return [o.name, o.getInterfaces().map((i) => i.name)] } return null }) .filter((f) => f) as [string, string[]][] return ['export interface NexusGenTypeInterfaces {'] .concat( withInterfaces.map(([name, interfaces]) => { return ` ${name}: ${interfaces.map((i) => JSON.stringify(i)).join(' | ')}` }) ) .concat('}') .join('\n') } private printContext() { return this.typegenInfo.contextTypeImport?.alias || this.typegenInfo.contextTypeImport?.export || 'any' } private printAbstractTypeMembers() { return this.printTypeInterface('NexusGenAbstractTypeMembers', this.buildAbstractTypeMembers()) } private buildAbstractTypeMembers() { const sourceMap: TypeMapping = {} const abstractTypes: (GraphQLInterfaceType | GraphQLUnionType)[] = [] abstractTypes .concat(this.groupedTypes.union) .concat(this.groupedTypes.interface) .forEach((type) => { if (isInterfaceType(type)) { const possibleNames = this.schema.getPossibleTypes(type).map((t) => t.name) if (possibleNames.length > 0) { sourceMap[type.name] = possibleNames.map((val) => JSON.stringify(val)).join(' | ') } } else { sourceMap[type.name] = type .getTypes() .map((t) => JSON.stringify(t.name)) .join(' | ') } }) return sourceMap } private printTypeNames(name: keyof GroupedTypes, exportName: string, source: string) { const obj = this.groupedTypes[name] as GraphQLNamedType[] const typeDef = obj.length === 0 ? 'never' : `keyof ${source}` return `export type ${exportName} = ${typeDef};` } private printIsTypeOfObjectTypeNames(exportName: string) { const objectTypes = this.groupedTypes.object.filter((o) => o.isTypeOf !== undefined) const typeDef = objectTypes.length === 0 ? 'never' : objectTypes .map((o) => JSON.stringify(o.name)) .sort() .join(' | ') return `export type ${exportName} = ${typeDef};` } private printResolveTypeAbstractTypes(exportName: string) { const abstractTypes = [...this.groupedTypes.interface, ...this.groupedTypes.union].filter( (o) => o.resolveType !== undefined ) const typeDef = abstractTypes.length === 0 ? 'never' : abstractTypes .map((o) => JSON.stringify(o.name)) .sort() .join(' | ') return `export type ${exportName} = ${typeDef};` } private printFeaturesConfig(exportName: string) { const abstractTypes = this.schema.extensions.nexus.config.features?.abstractTypeStrategies ?? {} const unionProps = renderObject(mapValues(abstractTypes, (val) => val ?? false)) return [`export type ${exportName} = {`] .concat(` abstractTypeStrategies: ${unionProps}`) .concat('}') .join('\n') } private buildEnumTypeMap() { const enumMap: TypeMapping = {} this.groupedTypes.enum.forEach((e) => { const sourceType = this.resolveSourceType(e.name) if (sourceType) { enumMap[e.name] = sourceType } else { const values = e.getValues().map((val) => JSON.stringify(val.value)) enumMap[e.name] = values.join(' | ') } }) return enumMap } private buildInputTypeMap() { const inputObjMap: TypeFieldMapping = {} this.groupedTypes.input.forEach((input) => { eachObj(input.getFields(), (field) => { inputObjMap[input.name] = inputObjMap[input.name] || {} inputObjMap[input.name][field.name] = this.normalizeArg(field) }) }) return inputObjMap } private buildScalarTypeMap() { const scalarMap: TypeMapping = {} this.groupedTypes.scalar.forEach((e) => { if (isSpecifiedScalarType(e)) { scalarMap[e.name] = this.resolveSourceType(e.name) ?? SpecifiedScalars[e.name as SpecifiedScalarNames] return } const sourceType = this.resolveSourceType(e.name) if (sourceType) { scalarMap[e.name] = sourceType } else { scalarMap[e.name] = 'any' } }) return scalarMap } private printInputTypeMap() { const inputTypeMap = this.buildInputTypeMap() if (this.typegenInfo.declareInputs) { const declaredInputs: string[] = mapObj(inputTypeMap, (fields, inputName) => this.printNamedObj(inputName, fields) ) return [...declaredInputs, this.printNamedMap('NexusGenInputs', inputTypeMap)].join('\n\n') } return this.printTypeFieldInterface('NexusGenInputs', inputTypeMap, 'input type') } private printEnumTypeMap() { const enumTypeMap = this.buildEnumTypeMap() if (this.typegenInfo.declareInputs) { return [ ...mapObj(enumTypeMap, (val, name) => `export type ${name} = ${val}`), this.printNamedMap('NexusGenEnums', enumTypeMap), ].join('\n\n') } return this.printTypeInterface('NexusGenEnums', enumTypeMap) } private printScalarTypeMap() { return this.printTypeInterface('NexusGenScalars', this.buildScalarTypeMap()) } private shouldDiscriminateType( abstractType: GraphQLAbstractType, objectType: GraphQLObjectType ): 'required' | 'optional' | false { if (!this.schema.extensions.nexus.config.features?.abstractTypeStrategies?.__typename) { return false } if (abstractType.resolveType !== undefined) { return 'optional' } if (objectType.isTypeOf !== undefined) { return 'optional' } return 'required' } private maybeDiscriminate(abstractType: GraphQLAbstractType, objectType: GraphQLObjectType) { const requiredOrOptional = this.shouldDiscriminateType(abstractType, objectType) if (requiredOrOptional === false) { return `NexusGenRootTypes['${objectType.name}']` } this.hasDiscriminatedTypes = true return `core.Discriminate<'${objectType.name}', '${requiredOrOptional}'>` } private buildRootTypeMap(hasFields: Array<GraphQLInterfaceType | GraphQLObjectType | GraphQLUnionType>) { const rootTypeMap: RootTypeMapping = {} hasFields.forEach((type) => { const rootTyping = this.resolveSourceType(type.name) if (rootTyping) { rootTypeMap[type.name] = rootTyping return } if (isUnionType(type)) { rootTypeMap[type.name] = type .getTypes() .map((t) => this.maybeDiscriminate(type, t)) .join(' | ') } else if (isInterfaceType(type)) { const possibleRoots = this.schema.getPossibleTypes(type).map((t) => this.maybeDiscriminate(type, t)) if (possibleRoots.length > 0) { rootTypeMap[type.name] = possibleRoots.join(' | ') } else { rootTypeMap[type.name] = 'any' } } else if (type.name === 'Query' || type.name === 'Mutation') { rootTypeMap[type.name] = '{}' } else { eachObj(type.getFields(), (field) => { const obj = (rootTypeMap[type.name] = rootTypeMap[type.name] || {}) if (!this.hasResolver(field, type)) { if (typeof obj !== 'string') { obj[field.name] = [ this.argSeparator(field.type as GraphQLInputType, false), this.printOutputType(field.type), ] } } }) } }) return rootTypeMap } private resolveSourceType(typeName: string): string | undefined { const rootTyping = this.schema.extensions.nexus.config.sourceTypings[typeName] if (rootTyping) { return typeof rootTyping === 'string' ? rootTyping : rootTyping.export } return (this.typegenInfo.sourceTypeMap as any)[typeName] } private hasResolver( field: GraphQLField<any, any>, // Used in test mocking _type: GraphQLObjectType ) { if (field.extensions && hasNexusExtension(field.extensions.nexus)) { return field.extensions.nexus.hasDefinedResolver } return Boolean(field.resolve) } private printObjectTypeMap() { return this.printRootTypeFieldInterface( 'NexusGenObjects', this.buildRootTypeMap(this.groupedTypes.object) ) } private printInterfaceTypeMap() { return this.printRootTypeFieldInterface( 'NexusGenInterfaces', this.buildRootTypeMap(this.groupedTypes.interface) ) } private printUnionTypeMap() { return this.printRootTypeFieldInterface('NexusGenUnions', this.buildRootTypeMap(this.groupedTypes.union)) } private printRootTypeDef() { const toJoin: string[] = [] if (this.groupedTypes.interface.length) { toJoin.push('NexusGenInterfaces') } if (this.groupedTypes.object.length) { toJoin.push('NexusGenObjects') } if (this.groupedTypes.union.length) { toJoin.push('NexusGenUnions') } return `export type NexusGenRootTypes = ${toJoin.join(' & ')}` } private printAllTypesMap() { const toJoin: string[] = ['NexusGenRootTypes'] if (this.groupedTypes.scalar.length) { toJoin.push('NexusGenScalars') } if (this.groupedTypes.enum.length) { toJoin.push('NexusGenEnums') } return `export type NexusGenAllTypes = ${toJoin.join(' & ')}` } private buildArgTypeMap() { const argTypeMap: Record<string, TypeFieldMapping> = {} const hasFields: (GraphQLInterfaceType | GraphQLObjectType)[] = [] hasFields .concat(this.groupedTypes.object) .concat(this.groupedTypes.interface) .forEach((type) => { eachObj(type.getFields(), (field) => { if (field.args.length > 0) { argTypeMap[type.name] = argTypeMap[type.name] || {} argTypeMap[type.name][field.name] = field.args.reduce((obj, arg) => { obj[arg.name] = this.normalizeArg(arg) return obj }, {} as Record<string, [string, string]>) } }) }) return argTypeMap } private printArgTypeMap() { const argTypeMap = this.buildArgTypeMap() if (this.typegenInfo.declareInputs) { const declaredArgs: string[] = [] eachObj(argTypeMap, (fields, typeName) => { eachObj(fields, (args, fieldName) => { declaredArgs.push(this.printNamedObj(this.getArgsName(typeName, fieldName), args)) }) }) return [...declaredArgs, this.printArgTypeFieldInterface(argTypeMap)].join('\n\n') } return this.printArgTypeFieldInterface(argTypeMap) } private getArgsName(typeName: string, fieldName: string) { return `${typeName}${fieldName.slice(0, 1).toUpperCase().concat(fieldName.slice(1))}Args` } private printNamedObj(name: string, obj: Record<string, [string, string]>) { return [ `export interface ${name} {`, ...mapObj(obj, (val, key) => ` ${key}${val[0]} ${val[1]}`), `}`, ].join('\n') } private printNamedMap(name: string, obj: Record<string, any>) { return [`export interface ${name} {`, ...mapObj(obj, (val, key) => ` ${key}: ${key}`), `}`].join('\n') } private buildReturnTypeMap() { const returnTypeMap: TypeFieldMapping = {} const hasFields: (GraphQLInterfaceType | GraphQLObjectType)[] = [] hasFields .concat(this.groupedTypes.object) .concat(this.groupedTypes.interface) .forEach((type) => { eachObj(type.getFields(), (field) => { returnTypeMap[type.name] = returnTypeMap[type.name] || {} returnTypeMap[type.name][field.name] = [':', this.printOutputType(field.type)] }) }) return returnTypeMap } private buildReturnTypeNamesMap() { const returnTypeMap: TypeFieldMapping = {} const hasFields: (GraphQLInterfaceType | GraphQLObjectType)[] = [] hasFields .concat(this.groupedTypes.object) .concat(this.groupedTypes.interface) .forEach((type) => { eachObj(type.getFields(), (field) => { returnTypeMap[type.name] = returnTypeMap[type.name] || {} returnTypeMap[type.name][field.name] = [':', `'${getNamedType(field.type).name}'`] }) }) return returnTypeMap } private printOutputType(type: GraphQLOutputType) { const returnType = this.typeToArr(type) function combine(item: any[]): string { if (item.length === 1) { if (Array.isArray(item[0])) { const toPrint = combine(item[0]) return toPrint.indexOf('null') === -1 ? `${toPrint}[]` : `Array<${toPrint}>` } return item[0] } if (Array.isArray(item[1])) { const toPrint = combine(item[1]) return toPrint.indexOf('null') === -1 ? `${toPrint}[] | null` : `Array<${toPrint}> | null` } return `${item[1]} | null` } return `${combine(returnType)}; // ${type}` } private typeToArr(type: GraphQLOutputType): any[] { const typing = [] if (isNonNullType(type)) { type = type.ofType } else { typing.push(null) } if (isListType(type)) { typing.push(this.typeToArr(type.ofType)) } else if (isScalarType(type)) { typing.push(this.printScalar(type)) } else if (isEnumType(type)) { if (this.typegenInfo.declareInputs) { typing.push(type.name) } else { typing.push(`NexusGenEnums['${type.name}']`) } } else if (isObjectType(type) || isInterfaceType(type) || isUnionType(type)) { typing.push(`NexusGenRootTypes['${type.name}']`) } return typing } private printFieldTypesMap() { return this.printTypeFieldInterface('NexusGenFieldTypes', this.buildReturnTypeMap(), 'field return type') } private printFieldTypeNamesMap() { return this.printTypeFieldInterface( 'NexusGenFieldTypeNames', this.buildReturnTypeNamesMap(), 'field return type name' ) } private normalizeArg(arg: GraphQLInputField | GraphQLArgument): [string, string] { return [this.argSeparator(arg.type, Boolean(arg.defaultValue)), this.argTypeRepresentation(arg.type)] } private argSeparator(type: GraphQLInputType, hasDefaultValue: boolean) { if (hasDefaultValue || isNonNullType(type)) { return ':' } return '?:' } private argTypeRepresentation(arg: GraphQLInputType): string { const argType = this.argTypeArr(arg) function combine(item: any[]): string { if (item.length === 1) { if (Array.isArray(item[0])) { const toPrint = combine(item[0]) return toPrint.indexOf('null') === -1 ? `${toPrint}[]` : `Array<${toPrint}>` } return item[0] } if (Array.isArray(item[1])) { const toPrint = combine(item[1]) return toPrint.indexOf('null') === -1 ? `${toPrint}[] | null` : `Array<${toPrint}> | null` } return `${item[1]} | null` } return `${combine(argType)}; // ${arg}` } private argTypeArr(arg: GraphQLInputType): any[] { const typing = [] if (isNonNullType(arg)) { arg = arg.ofType } else { typing.push(null) } if (isListType(arg)) { typing.push(this.argTypeArr(arg.ofType)) } else if (isScalarType(arg)) { typing.push(this.printScalar(arg)) } else if (isEnumType(arg)) { if (this.typegenInfo.declareInputs) { typing.push(arg.name) } else { typing.push(`NexusGenEnums['${arg.name}']`) } } else if (isInputObjectType(arg)) { if (this.typegenInfo.declareInputs) { typing.push(arg.name) } else { typing.push(`NexusGenInputs['${arg.name}']`) } } return typing } private printTypeInterface(interfaceName: string, typeMapping: TypeMapping) { return [`export interface ${interfaceName} {`] .concat(mapObj(typeMapping, (val, key) => ` ${key}: ${val}`)) .concat('}') .join('\n') } private printRootTypeFieldInterface(interfaceName: string, typeMapping: RootTypeMapping) { return [`export interface ${interfaceName} {`] .concat( mapObj(typeMapping, (val, key) => { if (typeof val === 'string') { return ` ${key}: ${val};` } if (Object.keys(val).length === 0) { return ` ${key}: {};` } return this.printObj(' ', 'root type')(val, key) }) ) .concat('}') .join('\n') } private printTypeFieldInterface(interfaceName: string, typeMapping: TypeFieldMapping, source: string) { return [`export interface ${interfaceName} {`] .concat(mapObj(typeMapping, this.printObj(' ', source))) .concat('}') .join('\n') } private printArgTypeFieldInterface(typeMapping: Record<string, TypeFieldMapping>) { return [`export interface NexusGenArgTypes {`] .concat( mapObj(typeMapping, (val, typeName) => { if (this.typegenInfo.declareInputs) { return [` ${typeName}: {`] .concat( mapObj(val, (_, fieldName) => ` ${fieldName}: ${this.getArgsName(typeName, fieldName)}`) ) .concat(' }') .join('\n') } return [` ${typeName}: {`] .concat(mapObj(val, this.printObj(' ', 'args'))) .concat(' }') .join('\n') }) ) .concat('}') .join('\n') } private printObj = (space: string, source: string) => (val: Record<string, [string, string]>, key: string) => { return [`${space}${key}: { // ${source}`] .concat( mapObj(val, (v2, k2) => { return `${space} ${k2}${v2[0]} ${v2[1]}` }) ) .concat(`${space}}`) .join('\n') } private printScalar(type: GraphQLScalarType) { if (isSpecifiedScalarType(type)) { return this.resolveSourceType(type.name) ?? SpecifiedScalars[type.name as SpecifiedScalarNames] } return `NexusGenScalars['${type.name}']` } private printPlugins() { const pluginFieldExt: string[] = [ ` interface NexusGenPluginFieldConfig<TypeName extends string, FieldName extends string> {`, ] const pluginInputFieldExt: string[] = [ ` interface NexusGenPluginInputFieldConfig<TypeName extends string, FieldName extends string> {`, ] const pluginArgExt: string[] = [` interface NexusGenPluginArgConfig {`] const pluginSchemaExt: string[] = [` interface NexusGenPluginSchemaConfig {`] const pluginTypeExt: string[] = [` interface NexusGenPluginTypeConfig<TypeName extends string> {`] const pluginInputTypeExt: string[] = [ ` interface NexusGenPluginInputTypeConfig<TypeName extends string> {`, ] const printInlineDefs: string[] = [] const plugins = this.schema.extensions.nexus.config.plugins || [] plugins.forEach((plugin) => { if (plugin.config.fieldDefTypes) { pluginFieldExt.push(padLeft(this.printType(plugin.config.fieldDefTypes), ' ')) } if (plugin.config.inputFieldDefTypes) { pluginInputFieldExt.push(padLeft(this.printType(plugin.config.inputFieldDefTypes), ' ')) } if (plugin.config.objectTypeDefTypes) { pluginTypeExt.push(padLeft(this.printType(plugin.config.objectTypeDefTypes), ' ')) } if (plugin.config.inputObjectTypeDefTypes) { pluginInputTypeExt.push(padLeft(this.printType(plugin.config.inputObjectTypeDefTypes), ' ')) } if (plugin.config.argTypeDefTypes) { pluginArgExt.push(padLeft(this.printType(plugin.config.argTypeDefTypes), ' ')) } }) return [ printInlineDefs.join('\n'), [ 'declare global {', [ pluginTypeExt.concat(' }').join('\n'), pluginInputTypeExt.concat(' }').join('\n'), pluginFieldExt.concat(' }').join('\n'), pluginInputFieldExt.concat(' }').join('\n'), pluginSchemaExt.concat(' }').join('\n'), pluginArgExt.concat(' }').join('\n'), ].join('\n'), '}', ].join('\n'), ].join('\n') } private printType(strLike: StringLike | StringLike[]): string { if (Array.isArray(strLike)) { return strLike.map((s) => this.printType(s)).join('\n') } if (isNexusPrintedGenTyping(strLike)) { strLike.imports.forEach((i) => { this.addImport(i) }) return strLike.toString() } if (isNexusPrintedGenTypingImport(strLike)) { this.addImport(strLike) return '' } return strLike } private addImport(i: PrintedGenTypingImport) { /* istanbul ignore if */ if (!isNexusPrintedGenTypingImport(i)) { console.warn(`Expected printedGenTypingImport, saw ${i}`) return } this.printImports[i.config.module] = this.printImports[i.config.module] || {} if (i.config.default) { this.printImports[i.config.module].default = i.config.default } if (i.config.bindings) { i.config.bindings.forEach((binding) => { if (typeof binding === 'string') { this.printImports[i.config.module][binding] = true } else { this.printImports[i.config.module][binding[0]] = binding[1] } }) } } } function padLeft(str: string, padding: string) { return str .split('\n') .map((s) => `${padding}${s}`) .join('\n') } const GLOBAL_DECLARATION = ` declare global { interface NexusGen extends NexusGenTypes {} }` function renderObject(object: Record<string, any>): string { return [ '{', mapObj(object, (val, key) => { return ` ${key}: ${val}` }).join('\n'), ' }', ].join('\n') }