Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support JSDoc to Generate Types for GQL Scalars #2544

Open
nalchevanidze opened this issue Jan 14, 2022 · 1 comment · May be fixed by #2545
Open

Support JSDoc to Generate Types for GQL Scalars #2544

nalchevanidze opened this issue Jan 14, 2022 · 1 comment · May be fixed by #2545

Comments

@nalchevanidze
Copy link

Support JSDoc to Generate Types for GQL Scalars

Note: This is a brief summary of Post: typed-scalars.

Motivation

One of the fundamental strengths of GraphQL is that we have control over the depth of the structure by selecting fields in your query. This limitation stops graph databases and recursive data types with independent resolvers running into the loop.

Nevertheless, this design can be obstructive in some cases. The first example is introspection queries, where the client has to query the fields nine levels deep to cover the most useful types.

fragment TypeRef on __Type {
  kind
  name
  ofType {
    kind
    name
    ofType {
      kind
      name
      ofType {
        kind
        name
        ofType {
          kind
          name
          ofType {
            kind
            name
            ofType {
              kind
              name
              ofType {
                kind
                name
              }
            }
          }
        }
      }
    }
  }
}

Hypothetically, we could create a schema with type [[[[[User]!]!]!]! where we could break clients. Since this scenario is doubtful, it was never a big issue in GraphQL. However, there are other recursive types (Tree Types), where this issue can be challenging. They can have hundreds or even thousands of nesting levels before reaching the leaf nodes, which sometimes makes them impossible to query. Let us consider one instance: the RichText. We want to create RichText in our WebApp where we use GraphQL BFF.

enum RichTextNodeType {
  Label
  Paragraph
  Image
}

type RichTextNode {
  type: RichTextNodeType!
  src: String
  text: String
  children: [RichTextNode!]
}

For this case, the solution presented above (see TypeRef) is no longer applicable, as it can have hundreds of nesting levels depending on the content.

Solution in GraphQL

The straightforward solution to this problem is to represent RichText by custom scalar and map it to the specific type. However, this works well when server and client are packages in the same Monorepo and use the same language. If we target third-party clients, we need to define the library "@types/rich-text" and publish it on npm for them.

// apollo.config.yaml

config:
  scalars:
    RichTextNode: import('@types/rich-text').RichTextNode

However, this approach has the following problems:

  • What if we want to target different languages (Java, TS, Flow, Elm ... )? Should we manually provide a type definitions library for each particular language? Even if we do that, we have to maintain each of them to introduce updates in the data types.
  • are we sure we have the correct version of the type definitions for the API?
  • The validity of the values is not checked by GraphQL automatically, but the developer has to check it manually.
  • Never the less, in Apollo Codegen, we have to map library types to scalar types by hand.

A general solution in GraphQL is typed scalars (which we have in Iris as data types). A typed scalar will represent JSON values without getting its dedicated resolvers. GraphQL compiler will only check if the values match type definitions and will not automatically resolve their fields. That way, we would not run into the loop but still have type safety guaranteed by the compiler.

One attempt of solving this problem in GraphQL is to provide type annotations with JSDoc in the scalar description, where a type generator could parse annotations and generate corresponding types. In addition, a server with the directive @JSDoc could use these annotations to validate scalar (inputs/outputs) values.

enum RichTextNodeType = {
  Label
  Paragraph
  Image
}

"""
@type {{  
  type: RichTextNodeType,
  src: ?string,
  text: ?string,
  children: ?RichTextNode[]
  }}
"""
scalar @JSDoc RichTextNode
// __generated__/globalTypes.ts

export type RichTextNodeType = "Label" | "Paragraph" | "Image"

export type RichTextNode = {
  type: RichTextNodeType,
  src: string | undefined,
  text: string | undefined,
  children: RichTextNode[] | undefined
}
@nalchevanidze
Copy link
Author

Furthermore, this would allow my language Iris to function without dedicated code-gen CLI.

PR for this feature is already in progress #2545

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant