Skip to content
/ qlc Public

A super fast and multithreaded GraphQL codegenerator

License

Notifications You must be signed in to change notification settings

notarize/qlc

Repository files navigation

👌 QL Compiler (qlc)

The QL compiler is a fun codegenerator for GraphQL clients. Specifically, it is capable of reading .graphql query, mutation, and fragment files and combining this with schema introspection JSON to produce ad-hoc type definitions for TypeScript. It is similar to the tools Apollo Tooling CLI and GraphQL Code Generator, but smaller in scope (and faster).

Motivating Example

Say you have a query that looks like this:

query CommentQuery($id: ID!) {
  comment(id: $id) {
    author {
      name
      avatar: profilePictureUrl
    }
    content
  }
}

If you are using TypeScript and a GraphQL client, it would be useful to get the type of this query. You could write one out by hand (and then maintain this definition as the query changes). But since GraphQL supports introspection and has a schema, we already know the type for the above! qlc enables you to automate the codegen of the following types:

export type CommentQuery_comment_author = {
  name: string;
  avatar: string | null;
};

export type CommentQuery_comment = {
  author: CommentQuery_comment_author;
  content: string;
};

export type CommentQuery = {
  comment: CommentQuery_comment;
};

export type CommentQueryVariables = {
  id: string;
};

Usage

You can download some prebuilt binaries on the releases page. You will need to build from source with cargo for other platforms.

For convenience, it is also available as an NPM package that supports x64/aarch64 MacOS and x64 linux:

yarn add @notarize/qlc-cli

qlc will recursively scan directories, finding .graphql files and producing .graphql.d.ts files. By default, it starts at the working directory but you can optionally provide it a directory argument. qlc supports fragment imports with the #import "<file>.graphql" syntax at the top of your files; it supports both relative imports and absolute imports starting at the root directory supplied to qlc.

You will need to supply qlc with the JSON result of the introspection query. Most, if not all, GraphQL servers support producing this query result, and the canonical implementation can even be found in the official graphql NPM package. See this blog post for more information. For simplicity, the NPM package comes with a helper script that should be suitable for most users. See below.

Example

# Download a schema JSON from an endpoint and write to my_schema.json
yarn run qlc-download-schema https://<FQDN>/graphql my_schema.json

# Run qlc searching the src/ directory with schema JSON located at my_schema.json
yarn run qlc -s my_schema.json src

# There are some other options available for more complex requirements.
yarn run qlc --help

Many of the options can also be configured through a camelcased JSON file (by default .qlcrc.json). For example:

{ "useCustomScalars": true, "numThreads": 2 }

Typed Document Nodes

qlc outputs "typed" GraphQL document nodes so that clients can auto infer result and variable types. Reference implementations are included in the @notarize/qlc-cli/typed-documentnode module. This requires the optional graphql dependency. One can choose not to use this implementation by overriding the --typed-graphql-documentnode-module-name option. For example:

yarn run qlc --typed-graphql-documentnode-module-name 'my-typenode'
// In my-typenode.ts, something like:

export type QueryDocumentNode<Data, Vars> = unknown;
// more types for Mutation, Subscription, etc.

Benchmarking

How much faster is "faster"? All results below are collected on MacOS, a 2.8 GHz quad-core machine with an NVMe storage device, with the operating system's IO cache hot. The hyperfine utility measured runtime. The directory in question has 4523 files and 534 .graphql files.

Command Version Mean Time ± σ NPM Dependencies
qlc 0.6.0 118.8 ms ± 10.8 ms 1 (itself)
apollo client:codegen --target=typescript 2.31.1 (node 14.15.0) 4.817 s ± 0.475 s 355

Development

To develop qlc, one needs a working cargo, rustc, node, and yarn installation. The repository provides this via a Nix shell (shell.nix) for ease. node and yarn are used for some tasks, like packaging the NPM release (see pkg/npm), as well as creating a mock schema JSON for testing.

Here are a number of reminders for useful commands, most of which also are executed in CI:

# If using the nix shell, the `just` command runner can be utilized to list recipes
just

# Static tools
just lint
just format

# Benchmarking on a `src` directory (using hyperfine)
hyperfine --warmup 2 -p 'find src -name "*.graphql.d.ts" -type f -exec rm {} +' '../qlc/target/release/qlc src'

# Testing
## Test Setup
just build-test-schema

## Run all tests
just test

## Run matching test
just test union_with_typename

## Instruct cargo test not to capture stdout/stderr so that one can see `dbg!()` output, etc.
just test -- --nocapture

## Instruct the test harness not to delete temporary directories created during testing for debugging
KEEP_TEST_TEMPDIRS=t just test

## Instruct the test harness overwrite expected fixtures with actual output -- useful for large swath compiler output changes
## Warning: will change repo files on disk
OVERWRITE_FIXTURES=t just test