npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@medflyt/eslint-plugin-graphql-type-checker

v5.1.3

Published

ESLint plugin that generates & validates TypeScript type annotations for GraphQL queries

Downloads

173

Readme

eslint-plugin-graphql-type-checker npm version Build Status

The eslint-plugin-graphql-type-checker package is an ESLint plugin that generates and validates TypeScript type annotations for GraphQL queries. It contains a single rule @medflyt/graphql-type-checker/check-query-types, which triggers on configured annotation targets (e.g. useQuery) and inspects queries passed as gql tagged templates (i.e. gql`query ..` ). From the query and the schema associated with the anotation target, it infers an annotation for the result and argument types, which can be applied to the code as an ESLint fix.

NOTE: The plugin is still a work in progress, and currently only supports query operations, without fragments, union types or interfaces.

Example

As an example, consider this basic schema:

type Query {
  greeting(language: String!): Greeting!
}

type Greeting {
  greeting_id: ID!
  message: String!
}

We can perform an untyped query with useQuery like this:

const { data } = useQuery(
  gql`
    query GetGreeting($language: String!) {
      greeting(language: $language) {
        __typename
        message
      }
    }
  `,
  {
    variables: { language: 'english' }, // Strongly-typed variables
  },
)

If the plugin is configured for useQuery with the appropriate schema, the code above will trigger this lint error:

Target should have a type annotation that matches the GraphQL query type

with a suggestion to fix the code to

const { data } = useQuery<{ greeting: { __typename: 'Greeting'; message: string } }, { language: string }>(
  gql`
    query GetGreeting($language: String!) {
      greeting(language: $language) {
        __typename
        message
      }
    }
  `,
  {
    variables: { language: 'english' },
  },
)

Both data and variables are now strongly typed according to the query in the gql tagged template.

If the useQuery call already has a type annotation, the plugin will compare it to the inferred one (disregarding layout and redundant syntax, like extra parentheses), and propose a fix in case of a difference.

To minimize the need to reformat after applying a fix, the suggested code fixes are formatted with prettier, using the target project's prettier configuration, if it has one.

Installation

Install the plugin with

npm install -D @medflyt/eslint-plugin-graphql-type-checker

Configuration

The plugin only has a single rule @medflyt/graphql-type-checker/check-query-types, which has the following configuration (expressed as a TypeScript type):

export type RuleOptions = [
  {
    annotationTargets: Array<
      (FunctionTarget | MethodTarget | TaggedTemplateTarget) & {
        schemaFilePath: string
      }
    >
  },
]

type FunctionTarget = { function: { name: string } }
type MethodTarget = { method: { objectName: string; methodName: string } }
type TaggedTemplateTarget = { taggedTemplate: { name: string } }

An annotation target can be either a function name, an object/method name pair, or a tagged-template tag name, together with a schema file path.

Function target

A function target will the trigger the plugin everywhere that function gets called. For example, to have the plugin target the useQuery call, use a configuration like this:

module.exports = {
  parser: "@typescript-eslint/parser",
  plugins: ["@typescript-eslint", "@medflyt/graphql-type-checker"],
  extends: [],
  rules: {
    "@medflyt/graphql-type-checker/check-query-types": [
      "error", {
        annotationTargets: [
          {
            function: { name: 'useQuery' },
            schemaFilePath: 'src/schemas/some-schema.graphql',
          },
          // ... other annotation targets
        ],
      },
    ],
  // ... other rules
  },
};

This will provide a type annotation to every useQuery call with a gql tagged template argument (i.e. useQuery(gql`..`)), for example:

const { data } = useQuery<{ greeting: { message: string } }, { language: string }>(
  gql`
    query GetGreeting($language: String!) {
      greeting(language: $language) {
        message
      }
    }
  `,
  { variables: { language: 'english' } },
)

Both data and variables here are strongly typed. See src/demo/queries/apollo/useQueryExample.tsx for the full source of this example.

The configuration above only works for useQuery calls that have a direct gql tagged-template argument. If the query is large, inlining it in the call clutters the code, and it makes more sense to declare it in a separate constant. To type queries declared separately, we can write a helper function to be be targeted by the plugin:

const annotateQuery = <TData, TVariables>(gql: Apollo.DocumentNode): Apollo.TypedDocumentNode<TData, TVariables> => gql

If the plugin configuration specifies a function target for annotateQuery, the plugin will provide annotations for queries wrapped in it, like this:

const greetingQuery = annotateQuery<{ greeting: { message: string } }, { language: string }>(gql`
  query GetGreeting($language: String!) {
    greeting(language: $language) {
      message
    }
  }
`)

The resulting greetingQuery will have type Apollo.TypedDocumentNode<{ greeting: { message: string } }, { language: string }> and can be used in a useQuery call to make it strongly typed:

const { data } = useQuery(greetingQuery, { variables: { language: 'english' } })

(Full source: src/demo/queries/apollo/annotateQueryExample.tsx).

Object/method target

An object/method target only applies to direct method calls on objects (i.e. OBJECT.METHOD). This can be useful if a project has several schemas, since you can configure a separate object/method target for each schema:

annotationTargets: [
  {
    objectName: 'Schema1',
    methodName: 'useQuery',
    schemaFilePath: 'src/schemas/schema-1.graphql',
  },
  {
    objectName: 'Schema2',
    methodName: 'useQuery',
    schemaFilePath: 'src/schemas/schema-2.graphql',
  },
  // ... other annotation targets
]

If you now use a named import import * as Schema1 from '@apollo/client', all Schema1.useQuery calls in the module will trigger the plugin with the src/schemas/schema-1.graphql schema.

Tagged-template target

It is also possible to directly target tagged templates such as gql:

annotationTargets: [
  {
    taggedTemplate: { name: 'gql' },
    schemaFilePath: 'src/schemas/some-schema.graphql',
  },
  // ... other annotation targets
]

This will annotate the gql call, so we need a generic version of it:

const gql = function <TData = Record<string, any>, TVariables = Record<string, any>>(
  literals: string | readonly string[],
  ...args: any[]
): Apollo.TypedDocumentNode<TData, TVariables> {
  return Apollo.gql(literals, ...args)
}

We can now use this generic gql to write queries, and the plugin will provide type annotations (project wide, so all gql tagged templates will need to be the generic one):

const greetingQuery = gql<{ greeting: { message: string } }, { language: string }>`#graphql
  query GetGreeting($language: String!) {
    greeting(language: $language) {
      message
    }
  }
`

Note that the VSCode GraphQL plugin currently does not recognize generic gql tagged templates as GraphQL, so an extra #graphql comment is necessary to enable syntax coloring and other GraphQL functionality.

It is also possible to target tagged templates with different tag names, for example to support different schemas, or to be able to still use the non-generic gql. The example in src/demo/queries/apollo/tgqlTaggedTemplateExample.tsx uses tgql to avoid annotating every instance of gql in the project.

For more examples, see the src/demo/.eslintrc.js configuration, and the query samples in src/demo/queries.

Demo

To run the plugin directly from the sources, clone this repository, and run

npm install
npm run install-demo

followed by either npm run build or npm run build-watch.

The plugin can now be called from the command line on the examples in src/demo/queries, for example with:

npx eslint src/demo/queries/apollo/useQueryExample.tsx

(To see an error message, try changing { message: string } to { message: number } in the type annotation.)

If you have an ESLint editor extension, you can also open the samples in src/demo/queries in your editor and use the quick-fix suggestions to update the type annotations. Note that after changing the plugin sources and rebuilding, you will have to reload or restart the editor to see the effects.