graphql-code-generator icon indicating copy to clipboard operation
graphql-code-generator copied to clipboard

#9962 causes issues with our schema: parts of the types in ResolversType map contradict other parts of the type (repro)

Open mx-bernhard opened this issue 8 months ago • 6 comments

Which packages are impacted by your issue?

@graphql-codegen/visitor-plugin-common, @graphql-codegen/typescript-resolvers

Describe the bug

With the update of @graphql-codegen/visitor-plugin-common to 5.2.0 and from that very likely this PR we run into an issue with the generated types. To me, it looks like the resolver types are improved, by replacing the base interface with its concrete implementations. This does not seem to be generated correctly in all cases.

Take the following graphql.schema (also in the Github repro repo):

type Assignment {
  resource: HumanResource
}

interface Resource {
  id: ID!
  attributes: [Attribute!]!
}

type VehicleResource implements Resource {
  id: ID!
  attributes: [Attribute!]!
  plateNumber: String!
}

type HumanResource implements Resource {
  id: ID!
  attributes: [Attribute!]!
  fullName: String!
}

interface Attribute {
  id: ID!
}

type JSONAttribute implements Attribute {
  id: ID!
  json: String!
}

type TextAttribute implements Attribute {
  id: ID!
  text: String!
}

type Query {
  assignment: Assignment!
  humanResource: HumanResource!
  resource: Resource!
  attribute: Attribute!
}

It contains two levels of interface to type mappings. When you look at the type generated for the Assignment resolver, the resource points to the schema base type instead of the resolver type:

export type ResolversTypes = {

  // has resource: HumanResource instead of resource: ResolversTypes<'HumanResource'>
  Assignment: ResolverTypeWrapper<Assignment>; // !!!

  Attribute: ResolverTypeWrapper<ResolversInterfaceTypes<ResolversTypes>['Attribute']>;
  Boolean: ResolverTypeWrapper<Scalars['Boolean']['output']>;
  HumanResource: ResolverTypeWrapper<Omit<HumanResource, 'attributes'> & { attributes: Array<ResolversTypes['Attribute']> }>;
  ID: ResolverTypeWrapper<Scalars['ID']['output']>;
  JSONAttribute: ResolverTypeWrapper<JsonAttribute>;
  Query: ResolverTypeWrapper<{}>;
  Resource: ResolverTypeWrapper<ResolversInterfaceTypes<ResolversTypes>['Resource']>;
  String: ResolverTypeWrapper<Scalars['String']['output']>;
  TextAttribute: ResolverTypeWrapper<TextAttribute>;
  VehicleResource: ResolverTypeWrapper<Omit<VehicleResource, 'attributes'> & { attributes: Array<ResolversTypes['Attribute']> }>;
};

What it should do, is:

export type ResolversTypes = {

  // change type of resource to ResolversTypes['HumanResource']
  Assignment: ResolverTypeWrapper<Omit<Assignment, 'resource'> & { resource: ResolversTypes['HumanResource'] }>;

  Attribute: ResolverTypeWrapper<ResolversInterfaceTypes<ResolversTypes>['Attribute']>;
  Boolean: ResolverTypeWrapper<Scalars['Boolean']['output']>;
  HumanResource: ResolverTypeWrapper<Omit<HumanResource, 'attributes'> & { attributes: Array<ResolversTypes['Attribute']> }>;
  ID: ResolverTypeWrapper<Scalars['ID']['output']>;
  JSONAttribute: ResolverTypeWrapper<JsonAttribute>;
  Query: ResolverTypeWrapper<{}>;
  Resource: ResolverTypeWrapper<ResolversInterfaceTypes<ResolversTypes>['Resource']>;
  String: ResolverTypeWrapper<Scalars['String']['output']>;
  TextAttribute: ResolverTypeWrapper<TextAttribute>;
  VehicleResource: ResolverTypeWrapper<Omit<VehicleResource, 'attributes'> & { attributes: Array<ResolversTypes['Attribute']> }>;
};

Otherwise, a mixture of types from ResolversTypes and schema types clash with each other when trying to implement a resolver for assignment:

 Type 'Omit<HumanResource, "attribute"> & { attribute: ResolverTypeWrapper<JsonAttribute | TextAttribute>[]; }' is not assignable to type 'Maybe<HumanResource>'.
   Type 'Omit<HumanResource, "attribute"> & { attribute: ResolverTypeWrapper<JsonAttribute | TextAttribute>[]; }' is not assignable to type 'HumanResource'.
     Type 'Omit<HumanResource, "attribute"> & { attribute: ResolverTypeWrapper<JsonAttribute | TextAttribute>[]; }' is not assignable to type 'Resource'.
       Types of property 'attribute' are incompatible.
         Type 'ResolverTypeWrapper<JsonAttribute | TextAttribute>[]' is not assignable to type 'Attribute[]'.
           Type 'ResolverTypeWrapper<JsonAttribute | TextAttribute>' is not assignable to type 'Attribute'.
             Property 'id' is missing in type 'Promise<JsonAttribute | TextAttribute>' but required in type 'Attribute'.ts(2322)

See also this commit for how, I think, it should have been generated. To me, it looks like the logic for generation does not consider nested cases of references between interfaces that need to be resolved to their types, but I might be wrong, it's just a feeling.

Your Example Website or App

https://github.com/mx-bernhard/graphql-codegen-resolver-types

Steps to Reproduce the Bug or Issue

See repro repo. The error is visible in resolvers-types-server.ts in line 57.

I checked in the generated files. They were generated with this:

yarn install
yarn generate

Expected behavior

See this commit for how, I think, it should have been generated.

Screenshots or Videos

No response

Platform

  • OS: [Windows WSL 2]
  • NodeJS: [e.g. 20.13.1]
  • @graphql-codegen/cli 5.0.2
  • @graphql-codegen/typescript 4.0.7
  • @graphql-codegen/typescript-resolvers 4.1.0
  • graphql 16.8.1

Codegen Config File

No response

Additional context

The behavior cannot be observed with version 4.0.6 of @graphql-codegen-typescript-resolvers

mx-bernhard avatar Jun 14 '24 10:06 mx-bernhard