Graphql-code-generator: Add __typename to interface types

Created on 29 Jan 2020  Â·  11Comments  Â·  Source: dotansimha/graphql-code-generator

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

I'm using apollo.writeQuery to generate a relatively complicated local dataset.

Unfortunately due to the lack of a __typename in the generated type @ts-ignore has to be used in multiple places in order to access the __typename property.

This is a simple example but it illustrates the issue that is occurring without a __typename in the generated type:

Sandbox

import { User, Animal, CanMove } from "./types";

const cheetah: Animal = { id: "1", nickname: "Speedy", speed: 70 };

const bob: User = { id: "1", name: "Bob", speed: 15 };

function whatCanMove(canMove: CanMove): string {
  // @ts-ignore
  if (canMove.__typename === "User") {
    return (canMove as User).name;
  }

  // @ts-ignore
  if (canMove.__typename === "Animal") {
    return (canMove as Animal).nickname;
  }

  return "Can't move";
}

console.log(whatCanMove(cheetah));
console.log(whatCanMove(bob));

Describe the solution you'd like
Add option to have __typename: string in generated interface types for example:

config:
  withInterfaceTypename: true/false

Describe alternatives you've considered
Right now I'm type casting the interface type and using @ts-ignore to stop Typescript from failing for example:

  // @ts-ignore
  if (canMove.__typename === "User") {
    return (canMove as User).name;
  }

Additional context
Related issues:

enhancement plugins

Most helpful comment

So I had more thinking about this issue, and it's been re-opened now.

This thing is - the typescript (and flow) plugins are "base" plugins - the types it generates are meant to be used by other plugins, so it could easily consume it.

If the consumer is typescript-operations, it will depend on the operation you are using. And in that matter, there is no chance that graphql will return the interface name as __typename, because fragment will make sure to resolve it to the actual type. Same with unions.
From the point of view of a GraphQL operation, there will always be __typename that points to a GraphQL type (and not interface or union).
Also, in this case, there is no reason for a developer to use the generated TypeScript interface type directly. Use the types generated for your operation. That means, that you don't need to use CanMove directly.

If you are using writeQuery or a similar method of Apollo, you are basically modifying a specific query or fragment in the store, which means that you need to use the types for that specific query.
If one of the fields in that query is a GraphQL interface, you'll be able to identify the actual type based on __typename, because the selection set represents all the possible output types that you can have.

Let's take for example the following schema:

type Query {
  search(term: String!): [Node!]!
}

interface Node {
  id: ID!
}

type User implements Node {
  id: ID!
  username: String!
  email: String!
}

type Chat implements Node {
  id: ID!
  users: [User!]!
  messages: [ChatMessage!]!
}

type ChatMessage implements Node {
  id: ID!
  content: String!
  user: User!
}

And the following GraphQL operations:

query test {
  search(term: "1") {
     ... on User {
      id 
      username
    }

    ... on Chat {
      id 
    }
  }
}

This will output the following for that operation when you are using typescript-operations:

export type TestQuery = (
  { __typename?: 'Query' }
  & { search: Array<(
    { __typename?: 'User' }
    & Pick<User, 'id' | 'username'>
  ) | (
    { __typename?: 'Chat' }
    & Pick<Chat, 'id'>
  ) | { __typename?: 'ChatMessage' }> }
);

Now, if you want to check what type is search field, you should use it as the base, and not the type generated by typescript:

function doSomething(obj: TestQuery['search'][0]) {
  if (obj.__typename === 'Chat') {

  } else if (obj.__typename === 'ChatMessage') {

  } else if (obj.__typename === 'User') {

  }
}

(You can try this here)

But - if the consumer is typescript-resolvers, it might be a bit different, because in this case, you want to be able to use the TypeScript interface and have __typename pointing to the possible types (it works with unions now because we point to the all possible types, but it's not the case with GraphQL interfaces).
So we can add that option to add __typename for that purpose.

It's important to understand, that if you are using typescript-operations, there is not reason to have those. You are using the wrong types.

All 11 comments

You do have __typename there:

export type Animal = CanMove & {
   __typename?: 'Animal',
  id: Scalars['ID'],
  nickname: Scalars['String'],
  speed: Scalars['Int'],
};

It's an optional field, and you can force it with this configuration: https://graphql-code-generator.com/docs/generated-config/base-visitor#nonoptionaltypename-boolean-default-value-false

Also, what's the use case of using the base types as is? If you are generating client-side code, you should use types that are based on your selection set. In your example, you should use UserQuery type in your code, and not the base types.

I don't see a real need to creating objects that are of type Animal manually in client side.
If you are using writeQuery of Apollo, your code should be based on your query and it's selection set, and not on your schema types. Use fragments to make it simpler and have intermediate types.

Admittedly my use case for using writeQuery might be a bit narrow/overkill 😅.

I'm creating a static website for prototype purposes which has no backend backing it right now so the entire dataset is created locally and no queries/fragments have been written yet.

However, perhaps another more valid use case for having __typename: string in the base type/interface is for utility functions like the whatCanMove function, or for example a print/formatting function like:

function describeMovers(canMove: CanMove): string { 
  // Based on __typename print unique message
}

For anyone curious I ended up solving this by creating a union type for all the implementors of the interface something like:

schema

interface CanMove {
  # ...
}

type User implements CanMove {
  # ...
}

type Animal implements CanMove {
  # ...
}

union Mover =  Users | Animal

Typescript

function describeMovers(mover: Mover): string {
  // ...
}

Maybe not ideal if you have hundreds of implementors...

I would also love to see __typename on the base interface as it would allow to process query results independent of the structure of the initial query.

PS: Thanks for your awesome work, @dotansimha!

I initially came to this issue thinking I needed __typename for interfaces.

My use case was that I was using the server typings with AppSync lambda datasources, and needed to do things like return heterogeneous types all implementing a single interface, but with branching logic on how each type was manipulated.

However, this issue (especially @dotansimha ) challenged me on this, and I realised that what I actually wanted was a union of types implementing each interface; all I was ever going to do with __typename on the interface was guard on a discriminated a union.

I saw some mentions (incarnated as possibleTypes?) for client typings, but not for the server-side. So, taking @ksrb 's example, I put this noddy plug-in together:

function plugin(schema /* , documents, config */) {
  const implementingTypes = {};
  const descriptions = {};

  const typeMap = schema.getTypeMap();
  Object.entries(typeMap).forEach(([name, type]) => {
    if (type.description) descriptions[name] = type.description;
    if (!type.getInterfaces) return;
    const interfaces = type.getInterfaces();
    interfaces.forEach(({ name: int }) => {
      const implementing = implementingTypes[int] || [];
      implementing.push(name);
      implementingTypes[int] = implementing;
    });
  });

  const unions = Object.entries(implementingTypes).map(([name, types]) => {
    const plural = `${name}s`;
    const declare = `export type ${plural} = ${types.join(' | ')};`;
    const comment = `/** types implementing ${descriptions[name] || name} */`;
    return [comment, declare].join('\n');
  });
  return unions.join('\n\n');
}

module.exports = { plugin };

People coming to this issue; is this useful? Do you want me to publish it? Do I even need to publish it- or have I missed something and the typescript plug-in already does this?

So I had more thinking about this issue, and it's been re-opened now.

This thing is - the typescript (and flow) plugins are "base" plugins - the types it generates are meant to be used by other plugins, so it could easily consume it.

If the consumer is typescript-operations, it will depend on the operation you are using. And in that matter, there is no chance that graphql will return the interface name as __typename, because fragment will make sure to resolve it to the actual type. Same with unions.
From the point of view of a GraphQL operation, there will always be __typename that points to a GraphQL type (and not interface or union).
Also, in this case, there is no reason for a developer to use the generated TypeScript interface type directly. Use the types generated for your operation. That means, that you don't need to use CanMove directly.

If you are using writeQuery or a similar method of Apollo, you are basically modifying a specific query or fragment in the store, which means that you need to use the types for that specific query.
If one of the fields in that query is a GraphQL interface, you'll be able to identify the actual type based on __typename, because the selection set represents all the possible output types that you can have.

Let's take for example the following schema:

type Query {
  search(term: String!): [Node!]!
}

interface Node {
  id: ID!
}

type User implements Node {
  id: ID!
  username: String!
  email: String!
}

type Chat implements Node {
  id: ID!
  users: [User!]!
  messages: [ChatMessage!]!
}

type ChatMessage implements Node {
  id: ID!
  content: String!
  user: User!
}

And the following GraphQL operations:

query test {
  search(term: "1") {
     ... on User {
      id 
      username
    }

    ... on Chat {
      id 
    }
  }
}

This will output the following for that operation when you are using typescript-operations:

export type TestQuery = (
  { __typename?: 'Query' }
  & { search: Array<(
    { __typename?: 'User' }
    & Pick<User, 'id' | 'username'>
  ) | (
    { __typename?: 'Chat' }
    & Pick<Chat, 'id'>
  ) | { __typename?: 'ChatMessage' }> }
);

Now, if you want to check what type is search field, you should use it as the base, and not the type generated by typescript:

function doSomething(obj: TestQuery['search'][0]) {
  if (obj.__typename === 'Chat') {

  } else if (obj.__typename === 'ChatMessage') {

  } else if (obj.__typename === 'User') {

  }
}

(You can try this here)

But - if the consumer is typescript-resolvers, it might be a bit different, because in this case, you want to be able to use the TypeScript interface and have __typename pointing to the possible types (it works with unions now because we point to the all possible types, but it's not the case with GraphQL interfaces).
So we can add that option to add __typename for that purpose.

It's important to understand, that if you are using typescript-operations, there is not reason to have those. You are using the wrong types.

I initially came to this issue thinking I needed __typename for interfaces.

My use case was that I was using the server typings with AppSync lambda datasources, and needed to do things like return heterogeneous types all implementing a single interface, but with branching logic on how each type was manipulated.

However, this issue (especially @dotansimha ) challenged me on this, and I realised that what I actually wanted was a union of types implementing each interface; all I was ever going to do with __typename on the interface was guard on a discriminated a union.

I saw some mentions (incarnated as possibleTypes?) for client typings, but not for the server-side. So, taking @ksrb 's example, I put this noddy plug-in together:

function plugin(schema /* , documents, config */) {
  const implementingTypes = {};
  const descriptions = {};

  const typeMap = schema.getTypeMap();
  Object.entries(typeMap).forEach(([name, type]) => {
    if (type.description) descriptions[name] = type.description;
    if (!type.getInterfaces) return;
    const interfaces = type.getInterfaces();
    interfaces.forEach(({ name: int }) => {
      const implementing = implementingTypes[int] || [];
      implementing.push(name);
      implementingTypes[int] = implementing;
    });
  });

  const unions = Object.entries(implementingTypes).map(([name, types]) => {
    const plural = `${name}s`;
    const declare = `export type ${plural} = ${types.join(' | ')};`;
    const comment = `/** types implementing ${descriptions[name] || name} */`;
    return [comment, declare].join('\n');
  });
  return unions.join('\n\n');
}

module.exports = { plugin };

People coming to this issue; is this useful? Do you want me to publish it? Do I even need to publish it- or have I missed something and the typescript plug-in already does this?

This is a good solution for my use case.
Although adding a __typename property with all possible type values to all interfaces would be preferable

I want to push this a little further.

The problem that @ksrb encountered is actually a product of the fact that interfaces in GraphQL are sealed (ie. they can only be implemented by a known, finite list of types) whereas interfaces in typescript are not (anyone can extend an interface wherever they like). GraphQL is designed with the expectation that the client will use this information when processing the response in order to handle each specific type in the response.

Essentially, a GraphQL interface is actually a _combination_ of a Typescript interface and Typescript union. We might represent it something like the following:

GraphQL

interface Bar {
  x: String!
}

type BarA implements Bar {
  a: String!
  x: String!
}

type BarB implements Bar {
  b: String! 
  x: String!
}

type Foo {
  bar: Bar! 
}

TypeScript

interface Bar {
  x: string;
}

interface BarA extends Bar {
  __typename?: "BarA";
  a: string; 
  x: string;
}

interface BarB implements Bar {
  __typename?: "BarB";
  b: string; 
  x: string;
}

type BarUnion = BarA | BarB;

type Foo {
  bar: BarUnion; 
}

@mjeffryes You are right, and that's the exact output we have today. The generated TS type for GraphQL interface doesn't have __typename, and the implementing types does have.

I'm closing this one. It's been around for a long time, and I think we are not going to intentionally change that soon. We are always open for additional configuration flags and customizations, so if someone have a good use-case and want to pick this up and add this - feel free ;)

@dotansimha I’m not sure that @mjeffryes’ example is the output we have today. Taking his example, I get an output that is analogous to

type Foo {
  bar: Bar
}

instead of

type Foo {
  bar: BarUnion
}

Because of this, the __typename fields which are present on BarA and BarB, are missing in the value of bar inside Foo.

Would it be possible to explicitly include all implementations of an interface wherever an interface is used as a value? E.g.,

type Foo {
  bar: BarA | BarB
}

This seems more correct, because the interface itself is typically missing several fields apart from __typename, so without these Foo would be incomplete.

(...)

This seems more correct, because the interface itself is typically missing several fields apart from __typename, so without these Foo would be incomplete.

Nevermind, as usual the problem was between the computer monitor and the chair — I should have used a union instead of an interface in the GraphQL schema.

Was this page helpful?
0 / 5 - 0 ratings