typescriptenumsgraphqlguardunion-types

Check in Typescript guard if GraphQL object has typename property is allowed enum


I have created a Typescript guard to check if a GraphQL object is of a certain type (by checking the __typename attribute).

type GraphQLObject = { __typename?: string };

type GraphQLTypename<O extends GraphQLObject> = O['__typename'];

export const isType = <O extends GraphQLObject, T extends GraphQLTypename<O>>(
  obj: O,
  typename: T
): obj is Extract<O, { __typename: T }> => obj.__typename === typename;

So I can correctly have a guard for the following query.

query RetrieveNode($id: ID!) {
  node(key: $key) {
    __typename

    ... on Article {
      properties {
        name
      }
    }

    ... on Page {
      properties {
        name
      }
    } 
  }
}
type Node = { __typename: 'Article', properties: { name: string } } | { __typename: 'Person', properties: { email: string } }

const data: { node: Node } = { node: { __typename: 'Article', properties: { name: 'Foo' } } } 

isType(data.node, 'Article')

data.node.properties.name // No error as the guard succeeded

I also have an enum of allowed content types.

enum ContentTypes {
  Article = 'Article',
  Post = 'Post',
}

But I am not allowed to pass the enum directly to the guard.

isType(data.node, ContentTypes.Article) // raises Typescript error

I would like to create a guard which wraps the isType one.

export const isContentType = <O extends GraphQLObject, T extends GraphQLTypename<O>>(
  obj: O,
  typename: T & ContentTypes
): obj is Extract<O, { __typename: T }> =>
  isType(obj, typename)

But this unfortunately always results in never.


Solution

  • Typescript raises an error because you expect an Enum (ContentTypes.Article) to match a string literal type 'Article'.

    You have to pick one strategy or the other :

    I don't exactly know your use case, but as data will propbably be the result of your query, you might want to use string literal types.

    For more information about the differences between enum and string literal types, see Difference between string enums and string literal types in TS