Skip to content

fixObjectTypeResolvers: 'smart' treats an unresolvable mapper type as an empty mapper and injects a stub for every field #446

Description

@GlebYavorski

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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions