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 🙏

© 2026 – Pkg Stats / Ryan Hefner

graphql-typescript-deluxe

v0.1.0

Published

Fast GraphQL TypeScript code generator

Readme

graphql-typescript-deluxe

This is an experimental and opinionated code generator for generating TypeScript code for GraphQL operations and fragments.

Example outputs

Features

  • Types: Generates types for operations, variables, fragments, input types and enums
  • Operations: Optionally generates fully valid operations with inlined fragments
  • Performance: Fast initial execution (at least 100x faster than graphql-codegen)
  • Readability: The generated types are easy to read and understand
  • Stateful: The class keeps track of already generated types, allowing it to update only types that need to be rebuilt
  • TypeDoc: Adds comments for fragments, operations and fields (description from schema)
  • Output: Option to generate a single .ts file or separate .js and .d.ts files

Caveats

This is still very much beta and experimental. While there are a lot of tests that cover both basic queries and edge cases, I'm sure there are plenty that I missed. Feel free to report anything you find, ideally by creating a pull request that implements a test case that shows the buggy output.

Usage

Basic

To just generate types use the static generateOnce method:

import { Generator } from 'graphql-typescript-deluxe'
import { parse } from 'graphql'
import { loadSchemaSync } from '@graphql-tools/load'

const SCHEMA = `
type Query {
  ping: Boolean!
}
`

const DOC = `
query myQuery {
  ping
}
`

// Contains the compiled TS types as a string.
const schema = loadSchemaSync(SCHEMA, { loaders: [] })
const result = Generator.generateOnce(schema, DOC)

Stateful

More interesting, especially in a local dev enviroment, is the stateful usage:

import { Generator } from 'graphql-typescript-deluxe'
import { parse } from 'graphql'
import { loadSchemaSync } from '@graphql-tools/load'

const SCHEMA = `
type Image {
  url: String!
  alt: String
}

type Query {
  getRandomImage: Image!
  ping: Boolean!
}
`

const DOC = `
query myQuery {
  getRandomImage {
    ...image
  }
}`

const FRAGMENT = `
fragment myImage on Image {
  url
}`

const schema = loadSchemaSync(SCHEMA, { loaders: [] })
const generator = new Generator(schema)

// Add the documents.
generator.add([
  {
    documentNode: parse(DOC),
    filePath: './somewhere/in/repo/myQuery.graphql',
  },
  {
    documentNode: parse(FRAGMENT),
    filePath: './somewhere/in/repo/fragment.image.graphql',
  },
])

// Build the initial output.
console.log(generator.build().getOperations().getSource())

// Add a new one.
generator.add({
  documentNode: parse('query myNewQuery { ping }'),
  filePath: './somewhere/in/repo/myNewQuery.graphql',
})

// Will only generate code for the new query and reuse all existing code.
console.log(generator.build().getOperations().getSource())

// Update a previously added document.
generator.update({
  documentNode: parse('fragment myImage on Image { url, alt }'),
  filePath: './somewhere/in/repo/fragment.image.graphql',
})

// Will update both the fragment type *and* the existing query type (since it depends on it).
console.log(generator.build().getOperations().getSource())

Getting GraphQL Documents

You can also retrieve the full GraphQL document string for any operation or fragment, including all required fragment dependencies:

const output = generator.build()

// Get operation with all its fragments.
const operationDoc = output.getOperationDocumentWithFragments('myQuery')

// Get fragment with all nested fragment dependencies.
const fragmentDoc = output.getFragmentDocumentWithFragments('myImage')

Why?

Mostly performance: On some of my projects with large schemas and tons of operations, running graphql-codegen takes up to 10s, other team members with less powerful machines report even higher numbers, sometimes up to one minute.

In addition, I was never truly happy with the output, no matter what options I tried. The output also tends to be extremely verbose and large, affecting LSP and IDE performance.

Performance

To test the performance I've used a quite large project (~ 15k lines of schema, with 135 fragments and 173 operations) as reference. On average, this generator only takes around 35ms to produce the string with all TypeScript code. Compared to graphql-codegen, which averages around 6000ms for me for the same set of operations/fragments.

Output

The output is very opinionated and the available options to customise the output are fairly limited. I am open to adding new options, as long as they don't negatively affect performance.

Fragment Types

A type is generated for every fragment and this type is also used whenever possible.

query fragmentInterface {
  getRandomEntity {
    ...entity
  }
}

fragment entity on Entity {
  id
}
export type FragmentInterfaceQuery = {
  getRandomEntity: EntityFragment | null
}

export type EntityFragment = {
  id: string
}

Inlined Fragment Fields

Sometimes it's not possible to use fragment types in an intersection:

query fieldMergingNested {
  entityById(id: "1", entityType: NODE) {
    ...nodeArticleOne
    ...nodeArticleTwo
  }
}

fragment nodeArticleOne on NodeArticle {
  title
  categories {
    label
  }
}

fragment nodeArticleTwo on NodeArticle {
  tags
  categories {
    url
  }
}

Here both fragments define a nested array field (categories), but pick different fields inside this array.

If we were to intersect both types:

type NodeArticleOneFragment = {
  title: string
  categories: Array<{
    label: string
  }> | null
}

type NodeArticleTwoFragment = {
  tags: Array<string | null> | null
  categories: Array<{
    url: string | null
  }> | null
}

type Intersected = NodeArticleOneFragment & NodeArticleTwoFragment

Then the type of categories would be:

const categories:
  | ({
      label: string
    }[] &
      {
        url: string | null
      }[])
  | null

So, either an array of { label: string } or an array of { url: string }.

In such a case where properties that would result in an invalid intersection are inlined, while still referencing the fragment type by omitting the inlined properties:

export type FieldMergingNestedQuery = {
  entityById:
    | object
    | ((Omit<NodeArticleOneFragment, 'categories'> &
        Omit<NodeArticleTwoFragment, 'categories'>) & {
        categories: Array<{
          label: string
          url: string | null
        }> | null
      })
    | null
}

The final resolved type in the IDE will be:

const entityById:
  | object
  | {
      title: string
      tags: Array<string | null> | null
      categories: Array<{
        label: string
        url: string | null
      }> | null
    }

__typename

By default a string literal type is generated for every type and an additional union of these for every GraphQL interface or union type.

interface Entity {
  id: String!
}

interface Node implements Entity {
  id: String!
  title: String!
}

type NodePage implements Entity & Node {
  id: String!
  title: String!
  body: String
}

type NodeArticle implements Entity & Node {
  id: String!
  title: String!
  date: String!
  body: String!
}

type Image implements Entity {
  id: String!
  title: String!
  imageUrl: String!
}

type Document implements Entity {
  id: String!
  title: String!
  videoUrl: String!
}

union SearchResult = NodeArticle | Document

This will generate the following:

// Types
type NodePage = 'NodePage'
type NodeArticle = 'NodeArticle'
type Image = 'Image'
type Document = 'Document'

// Interfaces
export type Entity = NodePage | NodeArticle | Image | Document
export type Node = NodePage | NodeArticle

// Unions
export type SearchResult = NodeArticle | Document

When using this query for example:

query getRandomEntity {
  getRandomEntity {
    __typename
    ... on NodePage {
      body
    }
  }
}

These types are referenced for every __typename field:

export type GetRandomEntity = {
  getRandomEntity:
    | { __typename: Exclude<Entity, NodePage> }
    | { __typename: NodePage; body: string | null }
    | null
}

This prevents generating identical object shapes where only the __typename changes, which can very quickly bloat the TypeScript code, making it difficult to make sense of the types. The type above is identical to this one:

export type GetRandomEntity = {
  getRandomEntity:
    | { __typename: 'Document' }
    | { __typename: 'Image' }
    | { __typename: 'NodeArticle' }
    | { __typename: 'User' }
    | { __typename: 'Comment' }
    | { __typename: 'NodePage'; body: string | null }
    | null
}

Enums

By default, enums are exported as a const (vs. a TypeScript enum) and exported as a string literal union type with the same name.

enum ContactMethod {
  PHONE
  MAIL
}
export const ContactMethod = {
  /* Contact via phone. */
  PHONE: 'PHONE',
  /* Contact via email. */
  MAIL: 'MAIL',
} as const
export type ContactMethod = (typeof ContactMethod)[keyof typeof ContactMethod]

This is the output when calling result.getOperations(), which defaults to generating a .ts file. It's also possible to create separate .js and .d.ts files:

const output = generator.build()
const js = output.getOperations('js').getSource())
const dts = output.getOperations('d.ts').getSource())

Which produces this:

operations.js

export const ContactMethod = Object.freeze({
  /* Contact via phone. */
  PHONE: 'PHONE',
  /* Contact via email. */
  MAIL: 'MAIL',
})

operations.d.ts

export declare const ContactMethod: {
  /* Contact via phone. */
  PHONE: 'PHONE'
  /* Contact via email. */
  MAIL: 'MAIL'
}
export type ContactMethod = (typeof ContactMethod)[keyof typeof ContactMethod]

Options

No options are required. The defaults are "sane" and picked to produce the smallest and fastest output possible.

Check out all options.