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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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