Summary
When a *.mappers.ts mapper aliases a type whose import cannot be resolved (a TS error type), fixObjectTypeResolvers: 'smart' silently treats the mapper as having zero fields and injects a "resolver is required because … does not" stub for every field of the schema type — overwriting/adding stubs in the hand-maintained resolver file. The generated stubs return Promise<void> and break tsc.
This is not hypothetical: it fires whenever codegen runs before the mapped type's module exists — e.g. a Prisma client generated into node_modules that hasn't been generated yet on a fresh install. The result looks like data corruption of hand-written files and is easy to commit unnoticed.
Root cause
getGraphQLObjectTypeResolversToGenerate builds the mapper's property set from getNodePropertyMap, which does:
typeChecker.getTypeAtLocation(node).getProperties().map((p) => p.getName())
When node resolves to a TS error type (unresolvable import), getProperties() returns []. The code then cannot distinguish "mapper genuinely has no fields" from "mapper type could not be resolved", so every schema field takes the if (!typeMapperProperty) branch and gets a stub.
Minimal reproduction
// user-defined schema type `Foo { id: ID!, name: String! }`
// mapper file:
export type FooMapper = Foo // Foo imported from a module that isn't installed / not generated yet
Standalone ts-morph proof of the misclassification:
import { Project, Node } from 'ts-morph'
const project = new Project({ skipAddingFilesFromTsConfig: true })
const sf = project.createSourceFile(
'mapper.ts',
`import type { Foo } from './does-not-exist'\nexport type FooMapper = Foo\n`
)
const alias = sf.getTypeAliasOrThrow('FooMapper')
const node = Node.isTypeReference(alias.getTypeNodeOrThrow())
? alias.getNameNode().getDefinitionNodes()[0]
: alias
const props = project.getTypeChecker().getTypeAtLocation(node).getProperties().map((p) => p.getName())
console.log(props) // => [] (error type → zero props → stubs injected for every field)
Suggested fix
Detect the error/unresolved type before deriving the property map and skip stub generation for that mapper, emitting a warning naming the mapper (so the real problem — the unresolved import — surfaces) instead of silently rewriting the resolver file. A type resolving to the intrinsic error/any-from-unresolved case is distinguishable via the type checker; when detected, getGraphQLObjectTypeResolversToGenerate should treat the mapper's property map as "unknown" (leave existing resolvers untouched) rather than "empty".
Happy to send a PR if you agree with the direction.
Summary
When a
*.mappers.tsmapper aliases a type whose import cannot be resolved (a TS error type),fixObjectTypeResolvers: 'smart'silently treats the mapper as having zero fields and injects a "resolver is required because … does not" stub for every field of the schema type — overwriting/adding stubs in the hand-maintained resolver file. The generated stubs returnPromise<void>and breaktsc.This is not hypothetical: it fires whenever codegen runs before the mapped type's module exists — e.g. a Prisma client generated into
node_modulesthat hasn't been generated yet on a fresh install. The result looks like data corruption of hand-written files and is easy to commit unnoticed.Root cause
getGraphQLObjectTypeResolversToGeneratebuilds the mapper's property set fromgetNodePropertyMap, which does:When
noderesolves to a TS error type (unresolvable import),getProperties()returns[]. The code then cannot distinguish "mapper genuinely has no fields" from "mapper type could not be resolved", so every schema field takes theif (!typeMapperProperty)branch and gets a stub.Minimal reproduction
Standalone ts-morph proof of the misclassification:
Suggested fix
Detect the error/unresolved type before deriving the property map and skip stub generation for that mapper, emitting a warning naming the mapper (so the real problem — the unresolved import — surfaces) instead of silently rewriting the resolver file. A type resolving to the intrinsic
error/any-from-unresolved case is distinguishable via the type checker; when detected,getGraphQLObjectTypeResolversToGenerateshould treat the mapper's property map as "unknown" (leave existing resolvers untouched) rather than "empty".Happy to send a PR if you agree with the direction.