Graphql-code-generator: Wrong intersection type generated for Fragment spread

Created on 4 Sep 2019  路  9Comments  路  Source: dotansimha/graphql-code-generator

Describe the bug

Intersection type is generated when spreading Fragment of different types of an interface.

It will cause __typename become undefined and impossible to distinguish types.

For example, the following code will break:

export function getFooBar(user: UserFragment) {
  switch (user.__typename) {
    case "Tom":
      return user.foo;
    case "Jerry":
      return user.bar;
  }
}

TSC error:

usage.ts:5:10 - error TS2678: Type '"Tom"' is not comparable to type 'undefined'.
usage.ts:7:10 - error TS2678: Type '"Jerry"' is not comparable to type 'undefined'.

To Reproduce
Steps to reproduce the behavior:

Sandbox:
https://codesandbox.io/embed/graphql-codegen-issue-template-9j73m

  1. My GraphQL schema:
interface User {
    id: ID!
}

type Tom implements User {
    id: ID!
    foo: String!
}

type Jerry implements User {
    id: ID!
    bar: String!
}
  1. My GraphQL operations:
fragment tom on Tom {
    id
    foo
}

fragment jerry on Jerry {
    id
    bar
}

fragment user on User {
    ...tom
    ...jerry
}
  1. My codegen.yml config file:
schema: schema.graphql
documents: document.graphql
generates:
  types.ts:
    plugins:
      - typescript
      - typescript-operations

Actual behavior

export type UserFragment = ({ __typename?: 'Tom' } | { __typename?: 'Jerry' })
  & TomFragment
  & JerryFragment
;

Expected behavior

Intersection type TomFragment & JerryFragment should not be generated.

Environment:

  • OS: macOS
  • @graphql-codegen/...: v1.7.0
  • NodeJS: v10.15.0

Additional context

It was working fine using graphql-codegen v1.5.0.

This is probably a regression of https://github.com/dotansimha/graphql-code-generator/issues/1533

bug plugins waiting-for-release

Most helpful comment

export type UserFragment = (({ __typename?: 'Tom' } & TomFragment) | ({ __typename?: 'Jerry' } & JerryFragment));

Would this be the correct solution?

All 9 comments

You must add __typename to your selection set, otherwise it might be undefined. Clients like Apollo do automatically add the __typename field, that鈥榮 why it is declared as optional by default

@n1ru4l the problem is not about undefined. the problem is intersection of TomFragment & JerryFragment will result in incorrect type of UserFragment, regardless of whether __typename is optional or not.

There might be a misunderstanding about type intersection. TomFragment & JerryFragment means the data must satisfy Tom and Jerry at the same time, which is impossible because the actual data from GraphQL will either have type Tom or type Jerry.

To give a simplified example:

interface A {
  __typename: "A";
}
interface B {
  __typename: "B";
}
type C = B & A;

// Error: Type '"A"' is not assignable to type '"B"'.
const c: C = { __typename: "A" }; 

Duplicate of #2495

export type UserFragment = (({ __typename?: 'Tom' } & TomFragment) | ({ __typename?: 'Jerry' } & JerryFragment));

Would this be the correct solution?

What if a fragment is defined for a interface instead of a object type?

It's not exactly the same issue as #2495 but could be related.

Would this be the correct solution?

Yes I think it's correct in this case. Basically the merged fragment should be a _union_ of different __typename instead of a _intersection_.

What if a fragment is defined for a interface instead of a object type?

I don't think it will make a difference, but would be great if there's an example.

I think we should apply | between fragments on different types, I'll try to fix that.

Fixed in 1.8.0 馃殌

Was this page helpful?
0 / 5 - 0 ratings