apollographql / apollographql/apollo-tooling

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

Open
#1,223 3 comments 2 reactions 0 assignees View on GitHub
:1st_place_medal: good-first-issue :tada: feature 🤖 component - codegen
Dominant language
TypeScript
Stars
3k
Forks
460
PR merge metrics
No merged PRs in 30d

Description

When I generate flow types for the following query:

query.graphql

```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

```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

```javascript
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:

```javascript
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:

```javascript
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:

```javascript
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.

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

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

Contributor guide

Open the contributing guide

Research direction

Reproduce the request using the query.graphql and schema.graphql examples, then inspect the code-generation entry point that produces types.js and locate its union-type tests. Done means generated output exports a distinct named type for each union variant while retaining the union alias and allowing the shown Flow narrowing and function call.

Written by the indexing model from the issue text.

Assessment

Tech stack
graphql, typescript
Domain
developer-experience, tooling
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.