apollo-tooling icon indicating copy to clipboard operation
apollo-tooling copied to clipboard

[Flow] Export each possible variant of a union as a separate type

Open FokkeZB opened this issue 5 years ago • 3 comments

When I generate flow types for the following query:

query.graphql
  query contentQuery {
    helpDocs_Content {
      __typename
      ... on HelpDocs_Faq {
        id
        question
        answer
      }
      ... on HelpDocs_Tutorial {
        id
        title
        introduction
        conclusion
      }
    }
  }

Based on the following schema:

schema.graphql
  type HelpDocs_Faq {
    id: ID!
    question: String!
    answer: String!
  }

  type HelpDocs_Tutorial {
    id: ID!
    title: String!
    introduction: String!
    conclusion: String!
  }

  union HelpDocs_Content =
      HelpDocs_Faq
    | HelpDocs_Tutorial

  type Query {
    helpDocs_Content(slug: String!): HelpDocs_Content
  }

I get the following types:

types.js
export type contentQuery_helpDocs_Content = {
  __typename: "HelpDocs_Faq",
  id: string,
  question: string,
  answer: string,
} | {
  __typename: "HelpDocs_Tutorial",
  id: string,
  title: string,
  introduction: string,
  conclusion: string,
};

export type contentQuery = {
  helpDocs_Content: ?contentQuery_helpDocs_Content
};

In my code, I can use __typename to tell which variant I got and as long as I do that before using a field that is not found in all types, Flow will accept that:

if (obj.__typename === 'HelpDocs_Faq') {
  console.log(obj.question);
}

However, if I pass the data down to another function I'd still need to test for the __typename there as well:

function handleFaq(obj: contentQuery_helpDocs_Content) {
  // $FlowFixMe
  console.log(obj.question);
}

if (obj.__typename === 'HelpDocs_Faq') {
  handleFaq(obj);
}

What I'd like for codegen to generate instead is:

export type contentQuery_helpDocs_Content_HelpDocs_Faq = {
  __typename: "HelpDocs_Faq",
  id: string,
  question: string,
  answer: any,
};

export type contentQuery_helpDocs_Content_HelpDocs_Tutorial = {
  __typename: "HelpDocs_Tutorial",
  id: string,
  title: string,
  introduction: any,
  conclusion: any,
};

export type contentQuery_helpDocs_Content =
    contentQuery_helpDocs_Content_HelpDocs_Faq
  | contentQuery_helpDocs_Content_HelpDocs_Tutorial;

export type contentQuery = {
  helpDocs_Content: ?contentQuery_helpDocs_Content
};

That way, I can use the variant types in the function without further checks, as Flow can see that what I pass to the function is the type it expects and within the function, it can rely on getting that type only.

function handleFaq(obj: contentQuery_helpDocs_Content_HelpDocs_Faq) {
  console.log(obj.question);
}

if (obj.__typename === 'HelpDocs_Faq') {
  handleFaq(obj);
}

FokkeZB avatar May 01 '19 13:05 FokkeZB

This is a great suggestion 👍 I'd be very open to a PR that implements this! Happy to provide guidance to anyone interested.

trevor-scheer avatar Jun 02 '19 21:06 trevor-scheer

For reference; I opened a similar issue for graphql-code-generator.com: https://github.com/dotansimha/graphql-code-generator/issues/1870

FokkeZB avatar Jun 03 '19 09:06 FokkeZB

Also need this.

andrewmclagan avatar Apr 13 '22 08:04 andrewmclagan