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

@shopify/graphql-codegen

v0.1.0

Published

A codegen plugin and preset for generating TypeScript types from GraphQL queries in a `d.ts` file. It does not require any function wrapper and adds no runtime overhead (0 bytes to the bundle).

Downloads

134,167

Readme

Shopify GraphQL Codegen

A codegen plugin and preset for generating TypeScript types from GraphQL queries in a d.ts file. It does not require any function wrapper and adds no runtime overhead (0 bytes to the bundle).

This package was originally extracted from @shopify/hydrogen-codegen to be agnostic of Hydrogen and to be used with any GraphQL client.

The GraphQL client must use TypeScript interfaces that are extended in the generated d.ts file. This package also exports utility types to create GraphQL clients that comply to these interfaces.

// `shop` is inferred as {name: string, description: string}
const {shop} = await clientQuery(`#graphql
  query layout {
    shop {
      name
      description
    }
  }
`);

Benefits

  • It does not require wrapping your queries or transforming them to AST with graphql-tag. As long as the GraphQL client complies with the TypeScript interfaces, you can simply pass plain strings.

  • It does not add any runtime overhead or size to the production bundle. The generated d.ts file is used only for type checking, and optionally for importing types in your code.

  • Generated types can be imported in your components to declare the shape of the data you expect to receive. For example:

    import type {LayoutQuery} from './my-api.generated';
    
    export function Layout({shop}: LayoutQuery) {
      return (
        <div>
          <h1>{shop.name}</h1>
          <p>{shop.description}</p>
        </div>
      );
    }

Caveats

  • This plugin is intended for server-side queries. Since it doesn't rely on AST transformations, it might not be compatible with normalized GraphQL caches in the browser or persisted queries.

  • If you are using string interpolation to build your queries, you will need to use as const on the strings to avoid assigning a generic string type to the variable. For example:

    const fragment = `#graphql
      fragment MyFragment on Shop {
        name
        description
      }
    `;
    
    const query = `#graphql
      ${fragment}
      
      query layout {
        shop {
          ...MyFragment
        }
      }
    ` as const; // <--- Add `as const` here

Usage

With GraphQL CLI

You can use the following example configuration with GraphQL CLI:

// <root>/codegen.ts

import type {CodegenConfig} from '@graphql-codegen/cli';
import {pluckConfig, preset} from '@shopify/graphql-codegen';

export default {
  overwrite: true,
  pluckConfig,
  generates: {
    'my-api.generated.d.ts': {
      preset,
      schema: './path/to/my-api-schema.json',
      documents: ['./src/graphql/my-api/*.{ts,tsx,js,jsx}'],
      presetConfig: {
        // Generate the line `import type * as MyAPI from 'path/to/my-api-types';`.
        // If you don't have generated types for your API beforehand,
        // omit this parameter to generate the types inline.
        importTypes: {
          namespace: 'MyAPI',
          from: 'path/to/my-api-types',
        },

        // Skip the __typename property from generated types:
        skipTypenameInOperations: true,

        // Add a custom interface extension to connect
        // the generated types to the GraphQL client:
        interfaceExtension: ({queryType, mutationType}) => `
          declare module 'my-api-client' {
            interface MyAPIQueries extends ${queryType} {}
            interface MyAPIMutations extends ${mutationType} {}
          }
        `,
      },
    },
  },
} as CodegenConfig;

[!TIP] To use enums instead of types, change the generated file extension to .ts instead of .d.ts.

Then, include queries in your app that match the given schema and documents paths. For example, for a query layout like the one in the example above, the generated d.ts file will look like this:

// my-api.generated.d.ts
import type * as MyAPI from 'path/to/my-api-types';

export type LayoutQueryVariables = MyAPI.Exact<{[key: string]: never}>;

export type LayoutQuery = {
  shop: Pick<MyAPI.Shop, 'name' | 'description'>;
};

interface GeneratedQueryTypes {
  '#graphql\n  query layout {\n    shop {\n      name\n      description\n    }\n  }\n': {
    return: LayoutQuery;
    variables: LayoutQueryVariables;
  };
}

interface GeneratedMutationTypes {}

declare module 'my-api-client' {
  interface MyAPIQueries extends GeneratedQueryTypes {}
  interface MyAPIMutations extends GeneratedMutationTypes {}
}

Therefore, when passing the query to the GraphQL client, TypeScript will infer the type of the response and the variables parameter.

Making GraphQL clients

To make a GraphQL client that complies with the generated types, you can use the following utility types:

// my-api-client.ts
import type {
  ClientReturn,
  ClientVariablesInRestParams,
} from '@shopify/graphql-codegen';

// Empty interface to be extended with the generated queries and mutations in user projects.
export interface MyAPIQueries {}

export async function clientQuery<
  OverrideReturnType extends any = never,
  RawGqlString extends string = string,
>(
  query: RawGqlString,
  ...params: ClientVariablesInRestParams<MyAPIQueries, RawGqlString>
): Promise<ClientReturn<MyAPIQueries, RawGqlString, OverrideReturnType>> {
  // The 'params' and 'params.variables' are optional
  // if the query has no variables, required otherwise.
  const {variables} = params[0] ?? {};

  // Client implementation (this could be forwarding the query to Apollo, urql, useQuery, etc.)
  const response = await fetch('https://my-api.com/graphql', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({query, variables}),
  });

  return response.json();
}

This allows to use the clientQuery function with any query string and TypeScript will infer the type of the response and the variables parameter. It's also possible to override the return type of the query by passing a type argument to clientQuery:

// Inferred type from the query:
data = await clientQuery('query layout {shop {name}}');

// Overwritten return type:
data = await clientQuery<{shop: {name: string}}>('...');

The type utilities accept other optional type params:

ClientVariablesInRestParams<
  // Interface extended with queries in the user project
  MyAPIQueries,
  // The raw query string that comes from the user
  RawGqlString,
  // -- Optional:
  // Additional properties to the params object.
  // E.g. `{cache: Cache; debug?: boolean}`
  AdditionalParamOptions,
  // Query variables that the client automatically injects.
  // E.g. `'locale' | 'currency'`
  AutoInjectedVariables
>;

To see a full example of a GraphQL client using these types, check the Hydrogen's Storefront client.

Wrapping this preset

This preset can be wrapped in a package that generates types for a specific API and adds default values to the preset. For example, an all-in-one solution for an "Example API" would be a package that provides:

  • The schema file for the Example API (in JSON or any format that GraphQL Codegen supports).
  • Optionally, pre-generated TypeScript types for the given schema. It's also possible to let this preset generate the types inline by omitting the importTypes option, or use the standalone @graphql-codegen/typescript plugin to generate the types in a separate file.
  • An implementation of a GraphQL client that complies with the generated types.
  • A custom preset that wraps @shopify/graphql-codegen's preset and provides default values like in the following example.
// example-api/preset.ts

import {preset as internalPreset} from '@shopify/graphql-codegen';
export {pluckConfig} from '@shopify/graphql-codegen';

// Export a custom preset that adds default values to @shopify/graphql-codegen:
export const preset = {
  buildGeneratesSection: (options) => {
    return internalPreset.buildGeneratesSection({
      ...options,
      // Known schema path from the example-api package:
      schema: 'example-api/schema.json'
      presetConfig: {
        importTypes: {
          namespace: 'ExampleAPI',
          // Known types path from the example-api package:
          from: 'example-api/types',
        },
        interfaceExtension: ({queryType, mutationType}) => `
          declare module 'example-api/client' {
            interface AdminQueries extends ${queryType} {}
            interface AdminMutations extends ${mutationType} {}
          }`,
      },
    });
  },
};

When the user imports this new preset, they don't need to specify the schema and types paths:

// <root>/codegen.ts

// This uses @shopify/graphql-codegen internally:
import {preset, pluckConfig} from 'example-api/preset';

export default {
  overwrite: true,
  pluckConfig,
  generates: {
    'example-api.generated.d.ts': {
      preset,
      documents: ['**/*.ts'],
    },
  },
};