Search code examples
typescriptgraphql-codegen

Reference Complex type from union in Typescript


I'm using graphql-codegen to generate types from my graphQL queries.

The result is sometimes pretty complex specifically when unions are involved.

Here is a concrete example

export type GroupQuery = { __typename?: 'Query' } & {
  group?: Maybe<
    { __typename?: 'Group' } & Pick<
      Group,
      'id' | 'name' 
    > & {
        criterions: Array<
          { __typename?: 'kindA' } & Pick<SomeModel, 'id' | 'type'> | 
          { __typename?: 'kindB' } & Pick<SomeOtherModel, 'id' | 'type' | 'count'>
        >
    }
  }

So I'm trying to do is be able to refer to a specific case of the union based on the __typename

let kindB: NonNullable<GroupQuery['group']>['criterions'][0]// not sure where to go from here.

Maybe a utility type?


Solution

  • This type:

    type T = NonNullable<GroupQuery['group']>['criterions'][0]`
    

    Would be resolved to this type:

    type T = {
        __typename?: "kindA" | undefined;
        id: number;
        name: string;
    } | {
        __typename?: "kindB" | undefined;
        id: number;
        name: string;
    }
    

    So what you are really asking how to get the branch of the union where:

    __typename === 'kindB'
    

    In that case you can use an intersection & to filter a union type. In general that works like this:

    type T = ("A" | "B" | "C") & "A" // "A"
    

    Playground

    So you can use an intersection to make the union resolve to only the type that could match the intersected type.

    type KindB =
        NonNullable<GroupQuery['group']>['criterions'][0] & { __typename: 'kindB' }
    

    Now KindB resolves to this type:

    type KindB = {
        __typename?: "kindB" | undefined;
        id: number;
        name: string;
    } & {
        __typename: 'kindB';
    }
    

    As you can see, the kindA member of the union is no longer present, and the remaining member of the union is being intersected with { __typename: 'kindB' }. If you apply that intersection, it reduces to:

    type KindB = {
        __typename: "kindB";
        id: number;
        name: string;
    }
    

    Playground with working code


    With some refactoring, you could even make this quite nice with a nice generic type alias:

    // Union of all criterion types
    type GroupQueryCriterions =
        NonNullable<GroupQuery['group']>['criterions'][number]
    
    // Get the branch of the criterions union that has a specific typename.
    type GroupQueryCriterionType<T extends GroupQueryCriterions['__typename']> =
        GroupQueryCriterions & { __typename: T }
    
    // Get a specific criterion type.
    type KindB = GroupQueryCriterionType<'kindB'>
    

    Playground