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 { TypegenInfo } from './builder' import { isNexusPrintedGenTyping, isNexusPrintedGenTypingImport } from './definitions/wrapping' import { NexusGraphQLSchema } from './definitions/_types' import { StringLike } from './plugin' import { eachObj, getOwnPackage, graphql15InterfaceType, GroupedTypes, groupTypes, mapObj, mapValues, PrintedGenTypingImport, resolveImportPath, } from './utils' const SpecifiedScalars = { ID: 'string', String: 'string', Float: 'number', Int: 'number', Boolean: 'boolean', } type SpecifiedScalarNames = keyof typeof SpecifiedScalars type TypeFieldMapping = Record> type TypeMapping = Record type RootTypeMapping = Record> interface TypegenInfoWithFile extends TypegenInfo { typegenFile: string } /** * 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 { groupedTypes: GroupedTypes printImports: Record> hasDiscriminatedTypes: boolean constructor(protected schema: NexusGraphQLSchema, protected typegenInfo: TypegenInfoWithFile) { this.groupedTypes = groupTypes(schema) this.printImports = {} this.hasDiscriminatedTypes = false } print() { const body = [ 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(), this.printPlugins(), ].join('\n\n') return [this.printHeaders(), body].join('\n\n') } printHeaders() { const fieldDefs = [ this.printDynamicInputFieldDefinitions(), this.printDynamicOutputFieldDefinitions(), this.printDynamicOutputPropertyDefinitions(), ] return [ this.typegenInfo.headers.join('\n'), this.typegenInfo.imports.join('\n'), this.printDynamicImport(), ...fieldDefs, GLOBAL_DECLARATION, ].join('\n') } printGenTypeMap() { return [`export interface NexusGenTypes {`] .concat([ ` context: ${this.printContext()};`, ` inputTypes: NexusGenInputs;`, ` rootTypes: NexusGenRootTypes;`, ` 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') } printDynamicImport() { const { rootTypings, dynamicFields: { dynamicInputFields, dynamicOutputFields }, } = this.schema.extensions.nexus.config const { contextTypeImport } = this.typegenInfo const imports: string[] = [] const importMap: Record> = {} const outputPath = this.typegenInfo.typegenFile const nexusSchemaImportId = this.typegenInfo.nexusSchemaImportId ?? getOwnPackage().name if (!this.printImports[nexusSchemaImportId]) { if ( [dynamicInputFields, dynamicOutputFields].some((o) => Object.keys(o).length > 0) || this.hasDiscriminatedTypes === true ) { this.printImports[nexusSchemaImportId] = { core: true, } } } if (contextTypeImport) { const importPath = resolveImportPath(contextTypeImport, 'context', outputPath) importMap[importPath] = importMap[importPath] || new Set() importMap[importPath].add( contextTypeImport.alias ? `${contextTypeImport.name} as ${contextTypeImport.alias}` : contextTypeImport.name ) } eachObj(rootTypings, (rootType, typeName) => { if (typeof rootType !== 'string') { const importPath = resolveImportPath(rootType, typeName, outputPath) importMap[importPath] = importMap[importPath] || new Set() importMap[importPath].add(rootType.alias ? `${rootType.name} as ${rootType.alias}` : rootType.name) } }) eachObj(importMap, (val, key) => { imports.push(`import { ${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 ${idents.join(', ')} from ${JSON.stringify(key)}`) }) return imports.join('\n') } 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 {`] .concat( mapObj(dynamicInputFields, (val, key) => { if (typeof val === 'string') { const baseType = this.schema.getType(val) return this.prependDoc( ` ${key}(fieldName: FieldName, opts?: core.CommonInputFieldConfig): void // ${JSON.stringify( val )};`, baseType?.description ) } return this.prependDoc( ` ${key}${val.value.typeDefinition || `(...args: any): void`}`, val.value.typeDescription ) }) ) .concat([` }`, `}`]) .join('\n') } 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 {`] .concat( mapObj(dynamicOutputFields, (val, key) => { if (typeof val === 'string') { const baseType = this.schema.getType(val) return this.prependDoc( ` ${key}(fieldName: FieldName, ...opts: core.ScalarOutSpread): void // ${JSON.stringify( val )};`, baseType?.description ) } return this.prependDoc( ` ${key}${val.value.typeDefinition || `(...args: any): void`}`, val.value.typeDescription ) }) ) .concat([` }`, `}`]) .join('\n') } 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}` } 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 {`] .concat( mapObj(dynamicOutputProperties, (val, key) => { return this.prependDoc( ` ${key}${val.value.typeDefinition || `: any`}`, val.value.typeDescription ) }) ) .concat([` }`, `}`]) .join('\n') } printInheritedFieldMap() { const hasInterfaces: ( | (GraphQLInterfaceType & { getInterfaces(): 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') } printContext() { return this.typegenInfo.contextType || '{}' } buildResolveSourceTypeMap() { 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) => `NexusGenRootTypes['${val}']`).join(' | ') } } else { sourceMap[type.name] = type .getTypes() .map((t) => `NexusGenRootTypes['${t.name}']`) .join(' | ') } }) return sourceMap } printAbstractTypeMembers() { return this.printTypeInterface('NexusGenAbstractTypeMembers', this.buildAbstractTypeMembers()) } 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 } 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};` } 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};` } 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};` } 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') } buildEnumTypeMap() { const enumMap: TypeMapping = {} this.groupedTypes.enum.forEach((e) => { const backingType = this.resolveBackingType(e.name) if (backingType) { enumMap[e.name] = backingType } else { const values = e.getValues().map((val) => JSON.stringify(val.value)) enumMap[e.name] = values.join(' | ') } }) return enumMap } 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 } buildScalarTypeMap() { const scalarMap: TypeMapping = {} this.groupedTypes.scalar.forEach((e) => { if (isSpecifiedScalarType(e)) { scalarMap[e.name] = SpecifiedScalars[e.name as SpecifiedScalarNames] return } const backingType = this.resolveBackingType(e.name) if (backingType) { scalarMap[e.name] = backingType } else { scalarMap[e.name] = 'any' } }) return scalarMap } printInputTypeMap() { return this.printTypeFieldInterface('NexusGenInputs', this.buildInputTypeMap(), 'input type') } printEnumTypeMap() { return this.printTypeInterface('NexusGenEnums', this.buildEnumTypeMap()) } printScalarTypeMap() { return this.printTypeInterface('NexusGenScalars', this.buildScalarTypeMap()) } 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' } 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}'>` } buildRootTypeMap(hasFields: Array) { const rootTypeMap: RootTypeMapping = {} hasFields.forEach((type) => { const rootTyping = this.resolveBackingType(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 } resolveBackingType(typeName: string): string | undefined { const rootTyping = this.schema.extensions.nexus.config.rootTypings[typeName] if (rootTyping) { return typeof rootTyping === 'string' ? rootTyping : rootTyping.name } return (this.typegenInfo.backingTypeMap as any)[typeName] } hasResolver( field: GraphQLField, // Used in test mocking _type: GraphQLObjectType ) { if (field.extensions && field.extensions.nexus) { return field.extensions.nexus.hasDefinedResolver } return Boolean(field.resolve) } printObjectTypeMap() { return this.printRootTypeFieldInterface( 'NexusGenObjects', this.buildRootTypeMap(this.groupedTypes.object) ) } printInterfaceTypeMap() { return this.printRootTypeFieldInterface( 'NexusGenInterfaces', this.buildRootTypeMap(this.groupedTypes.interface) ) } printUnionTypeMap() { return this.printRootTypeFieldInterface('NexusGenUnions', this.buildRootTypeMap(this.groupedTypes.union)) } 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(' & ')}` } 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(' & ')}` } buildArgTypeMap() { const argTypeMap: Record = {} 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) } }) }) return argTypeMap } printArgTypeMap() { return this.printArgTypeFieldInterface(this.buildArgTypeMap()) } 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 } 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 } 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}` } 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)) { typing.push(`NexusGenEnums['${type.name}']`) } else if (isObjectType(type) || isInterfaceType(type) || isUnionType(type)) { typing.push(`NexusGenRootTypes['${type.name}']`) } return typing } printFieldTypesMap() { return this.printTypeFieldInterface('NexusGenFieldTypes', this.buildReturnTypeMap(), 'field return type') } printFieldTypeNamesMap() { return this.printTypeFieldInterface( 'NexusGenFieldTypeNames', this.buildReturnTypeNamesMap(), 'field return type name' ) } normalizeArg(arg: GraphQLInputField | GraphQLArgument): [string, string] { return [this.argSeparator(arg.type, Boolean(arg.defaultValue)), this.argTypeRepresentation(arg.type)] } argSeparator(type: GraphQLInputType, hasDefaultValue: boolean) { if (hasDefaultValue || isNonNullType(type)) { return ':' } return '?:' } 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}` } 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)) { typing.push(`NexusGenEnums['${arg.name}']`) } else if (isInputObjectType(arg)) { typing.push(`NexusGenInputs['${arg.name}']`) } return typing } printTypeInterface(interfaceName: string, typeMapping: TypeMapping) { return [`export interface ${interfaceName} {`] .concat(mapObj(typeMapping, (val, key) => ` ${key}: ${val}`)) .concat('}') .join('\n') } 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') } printTypeFieldInterface(interfaceName: string, typeMapping: TypeFieldMapping, source: string) { return [`export interface ${interfaceName} {`] .concat(mapObj(typeMapping, this.printObj(' ', source))) .concat('}') .join('\n') } printArgTypeFieldInterface(typeMapping: Record) { return [`export interface NexusGenArgTypes {`] .concat( mapObj(typeMapping, (val, key) => { return [` ${key}: {`] .concat(mapObj(val, this.printObj(' ', 'args'))) .concat(' }') .join('\n') }) ) .concat('}') .join('\n') } printObj = (space: string, source: string) => (val: Record, key: string) => { return [`${space}${key}: { // ${source}`] .concat( mapObj(val, (v2, k2) => { return `${space} ${k2}${v2[0]} ${v2[1]}` }) ) .concat(`${space}}`) .join('\n') } printScalar(type: GraphQLScalarType) { if (isSpecifiedScalarType(type)) { return SpecifiedScalars[type.name as SpecifiedScalarNames] } return `NexusGenScalars['${type.name}']` } printPlugins() { const pluginFieldExt: string[] = [ ` interface NexusGenPluginFieldConfig {`, ] const pluginInputFieldExt: string[] = [ ` interface NexusGenPluginInputFieldConfig {`, ] const pluginArgExt: string[] = [` interface NexusGenPluginArgConfig {`] const pluginSchemaExt: string[] = [` interface NexusGenPluginSchemaConfig {`] const pluginTypeExt: string[] = [` interface NexusGenPluginTypeConfig {`] 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.argTypeDefTypes) { pluginArgExt.push(padLeft(this.printType(plugin.config.argTypeDefTypes), ' ')) } }) return [ printInlineDefs.join('\n'), [ 'declare global {', [ pluginTypeExt.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') } 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 } 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 { return [ '{', mapObj(object, (val, key) => { return ` ${key}: ${val}` }).join('\n'), ' }', ].join('\n') }