eddeee888 / graphql-code-generator-plugins

List of GraphQL Code Generator plugins that complements the official plugins.
MIT License
51 stars 12 forks source link

[BUG]: Explicit requirement to mention __typename in resolver files for interface and union. #302

Closed Vishwaas closed 3 months ago

Vishwaas commented 3 months ago

Describe the bug Suppose a field expects an interface or a union. Eg:

type Area {
  petDemography: IAnimal
}

type Dog extends IAnimal {
  kind: string
  count: number
}

below does not work

// controller file:
export const getPopulation: Dog  = async () => {
   //stuff
 return {
 __typename: 'Dog',
  kind: 'canine',
 count: 10000
};  // even using alias for eg return {...} as Dog does not work although it is pointless here.
}

// resolver file
{
  population: async() => {
   return await getPopulation();
  }
}

while the below works

// controller file:
export const getPopulation: Dog  = async () => {
   //stuff
 return {
 __typename: 'Dog',
  kind: 'canine',
 count: 10000
};  // even using alias for eg return {...} as Dog does not work although it is pointless here.
}

// resolver file
{
  population: async() => {
   return { ...await getPopulation(), __typename: 'Dog'};
  }
}

Why does the resolver file require an explicit typename in that file even though the function adds it and also returns the type?

Expected behavior The first example should work.

Versions

Additional context This is very important since our schema is starting to get deep and dont want repeated code effort on the same

eddeee888 commented 3 months ago

Hi @Vishwaas,

There are generally three ways to handle abstract types in GraphQL (Similar discussion here):

  1. Use __resolveType in the abstract type
  2. Use __isTypeOf in the implementing type for interface or member type for union
  3. Use __typename in the resolver that returns the node

For now, the server preset picks 3 as the default because it is the easiest config to set up, and the easiest to enforce type safety and runtime safety.

I understand that this approach may not work for everyone, and there's always ways for the user to change the config to match their use case. In your case, I'd recommend adding the following to make __typename optional in return type of resolvers:

defineConfig({
  typesPluginsConfig: {
    // Option 1. Make `__typename` optional for interface and/or union:
    resolversNonOptionalTypename: {
      interfaceImplementingType: false, // resolvers can return interfaces without `__typename`. Note that you'd need to use `__resolveType` or `__isTypeOf` or you may hit runtime error
      unionMember: true,
    },

    // Option 2. Make `__typename` optional for both interface and untion:
    resolversNonOptionalTypename: false,
  }
})
eddeee888 commented 3 months ago

Closing this since there's a few options. Please let me know if it's still an issue and I'll re-open