Graphql-code-generator: Extract types from type unions

Created on 24 Sep 2019  路  5Comments  路  Source: dotansimha/graphql-code-generator

Is your feature request related to a problem? Please describe.

Currently payload types generally consist of type unions, therefore it is not possible to take only one specific type and use it.

export type MyQuery {
  __typename: 'Query',
  data: Maybe<(
    {
      id: string;
      name: string;
      __typename: 'Data1'
    }
    | {
      id: string;
      type: string;
      __typename: 'Data2'
    }
  )>;
}

Now if I develop a stream that filters out all emission but the one with Data2 __typename, I can't get the type safety right, as I have no direct access to the Data2 type.

Describe the solution you'd like

Similarly as apollo-codegen does it(_I used it up until now_) codegen could extract all the types in the unions in their own "standalone" types, that could be used by the consumer.

Describe alternatives you've considered

The only alternative is to declare the types by hand.

Most helpful comment

@klemenoslaj

You can use the Extract utility type (https://www.typescriptlang.org/docs/handbook/utility-types.html#extracttu)

type Maybe<T> = null | undefined | T;

export type MyQuery =  {
  __typename: 'Query',
  data: Maybe<(
    {
      id: string;
      name: string;
      bar: string;
      __typename: 'Data1'
    }
    | {
      id: string;
      type: string;
      foo: string;
      __typename: 'Data2'
    }
  )>;
}

type MyQueryData1 = Extract<MyQuery["data"], { __typename: "Data1"}>
type MyQueryData2 = Extract<MyQuery["data"], { __typename: "Data2"}>

https://www.typescriptlang.org/play/index.html#code/C4TwDgpgBAsghiARhAPAFQHxQLxQHYCuANkVAD5QF4AmEAZgJZ4TXlRoDcAUFxAB5gA9gCdgUUJFggAigQjCQOKFADeXZQH0NEiHjgBbCAC4oAclnyQpgDTqo1OMDgn4SVAAo7ytct9QG1CYAzsDCTADm3H7KeobBoRFR0YhwwvFheJFevlo6scZmACKOcACMptkAvtkUPtEB6YnZyjqNmUl+dIKCbVnRULngugYFpsVOAEwVftXKAJQY3NVcOlIWCuNlSgCifKFwAMbAKDAycgoA2gBEDk5XALrWqgPaQ-kmV5ulV5UYK0Nrc4gTYTHZ7YSHY6ndYga63OAPJ4qF55EYfEE-P5AA

All 5 comments

@klemenoslaj

You can use the Extract utility type (https://www.typescriptlang.org/docs/handbook/utility-types.html#extracttu)

type Maybe<T> = null | undefined | T;

export type MyQuery =  {
  __typename: 'Query',
  data: Maybe<(
    {
      id: string;
      name: string;
      bar: string;
      __typename: 'Data1'
    }
    | {
      id: string;
      type: string;
      foo: string;
      __typename: 'Data2'
    }
  )>;
}

type MyQueryData1 = Extract<MyQuery["data"], { __typename: "Data1"}>
type MyQueryData2 = Extract<MyQuery["data"], { __typename: "Data2"}>

https://www.typescriptlang.org/play/index.html#code/C4TwDgpgBAsghiARhAPAFQHxQLxQHYCuANkVAD5QF4AmEAZgJZ4TXlRoDcAUFxAB5gA9gCdgUUJFggAigQjCQOKFADeXZQH0NEiHjgBbCAC4oAclnyQpgDTqo1OMDgn4SVAAo7ytct9QG1CYAzsDCTADm3H7KeobBoRFR0YhwwvFheJFevlo6scZmACKOcACMptkAvtkUPtEB6YnZyjqNmUl+dIKCbVnRULngugYFpsVOAEwVftXKAJQY3NVcOlIWCuNlSgCifKFwAMbAKDAycgoA2gBEDk5XALrWqgPaQ-kmV5ulV5UYK0Nrc4gTYTHZ7YSHY6ndYga63OAPJ4qF55EYfEE-P5AA

Yes, this very second I found Extract myself 馃槀
Thanks for the quick reply.

Would however be splitting the types be good idea anyway?

@klemenoslaj you are using inline fragments? you can try to use fragments spreads and then it will generate a sub-type for each possible type in the fragment (use the latest alpha for this, it's just got merged and not released in a stable version yet).

@dotansimha not necessarily that fragments are used at all. It could be just a query itself that can return multiple data types.

Something like:

Schema

interface Data {
  id: ID!
  type: String!
}

type Data1 implements Data {
  id: ID!
  type: String!
  foo: String!
}

type Data2 implements Data {
  id: ID!
  type: String!
  boo: String!
}

extend type Query {
  data(id: ID!): Data
}

Query

query DataQuery($id: ID!) {
  data(id: $id) {
    id
    type
  }
}

Type definition

export type DataQuery = { readonly __typename: 'Query' } & {
  readonly data: Maybe<
    | ({ readonly __typename: 'Data1' } & Pick<Data1, 'id' | 'type'>)
    | ({ readonly __typename: 'Data2' } & Pick<Data2, 'id' | 'type'>)
  >;
};

EDIT: In fact you're right, if fragment is not used in my example types will anyway be identical, therefore no need to pick one.

Bottomline currently solution to the problem would be to not use inline fragments, right?

I think it's fine, but if you'll use a separate fragment and a fragment spread you'll be able to access the inner type as regular typescript type.
And in case of inline fragment or fields specified for interface, you'll have to use Extract to access it (because we don't generate sub-type for that)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

SimenB picture SimenB  路  3Comments

NickClark picture NickClark  路  3Comments

RIP21 picture RIP21  路  3Comments

edorivai picture edorivai  路  3Comments

quolpr picture quolpr  路  3Comments