--- url: /get-started.md --- # Introduction `gql.tada` aims to tie GraphQL and TypeScript closer together and minimize friction, by improving the experience of writing and using GraphQL with more editor feedback, automatically derived types, and additional built-in tools that don't get in your way. Once `gql.tada` is set up, we write our GraphQL queries in pure TypeScript, queries automatically infer their types, and editors provide immediate feedback, auto-completion, diagnostics, and GraphQL type hints. All on-the-fly in TypeScript with as few setup steps as possible. ### A demo in 128 seconds --- --- url: /get-started/installation.md description: How to get set up and ready --- # Installation We’ll go through the steps to get `gql.tada` set up properly. A quick demo of what this looks like can be found [in an example project in the `gql.tada` repository.](https://github.com/0no-co/gql.tada/blob/main/examples/example-pokemon-api/) With `gql.tada`, you'll mainly interact with three different parts of the library: * the library code you import from the `gql.tada` package * the TypeScript plugin, `gql.tada/ts-plugin` * and [the `gql.tada` CLI](/get-started/workflows) ## Step 1 — Installing packages We’ll start by installing `gql.tada` as a dependency. ::: code-group ```sh [npm] npm install gql.tada ``` ```sh [pnpm] pnpm add gql.tada ``` ```sh [yarn] yarn add gql.tada ``` ```sh [bun] bun add gql.tada ``` ::: Next, we’ll add the TypeScrpt plugin to our `tsconfig.json` to set it up in TypeScript’s language server. This is the main configuration for both the TypeScript plugin and the `gql.tada` CLI at the same time. ::: code-group ```json [tsconfig.json] { "compilerOptions": { "strict": true, "plugins": [ // [!code ++] { // [!code ++] "name": "gql.tada/ts-plugin", // [!code ++] "schema": "./schema.graphql", // [!code ++] "tadaOutputLocation": "./src/graphql-env.d.ts" // [!code ++] } // [!code ++] ] // [!code ++] } } ``` ::: Setting up `gql.tada/ts-plugin` will start up a [“TypeScript Language Service Plugin”](https://github.com/microsoft/TypeScript/wiki/Writing-a-Language-Service-Plugin#whats-a-language-service-plugin) when TypeScript is analyzing a file in our IDE or editor. This provides editor hints, such as diagnostics, auto-completions, and type hovers for GraphQL. > \[!NOTE] VSCode Setup > There may be extra steps you should take when you're using VSCode. > [Read about these steps in the "VSCode Setup" section below.](#vscode-setup) > \[!NOTE] Prior to TypeScript 5.5 > There are extra steps you must take when your TypeScript version is older than 5.5. > [Read about these steps in the "Prior to TypeScript 5.5" section below.](#prior-to-typescript-5-5) ## Step 2 — Configuring a schema We’ll need to set up a GraphQL schema for `gql.tada` to function correctly. Without a schema, no typings and no editor hints will be available, since the schema provides the GraphQL types, fields, and description information of your GraphQL API. To add a GraphQL schema to `gql.tada`, we'll be the `tsconfig.json`'s plugin section we've just added and modify the `schema` option. ::: code-group ```json twoslash [tsconfig.json] {6} { "compilerOptions": { "plugins": [ { // @annotate: Configure your schema here "name": "gql.tada/ts-plugin", "schema": "./schema.graphql", "tadaOutputLocation": "./src/graphql-env.d.ts" } ] } } ``` ::: The `schema` option currently allows for three different formats to load a schema. It accepts either: * a path to a `.graphql` file containing a schema definition (in GraphQL SDL format) * a path to a `.json` file containing a schema’s introspection query data * a URL to a GraphQL API that can be introspected ::: code-group ```json [.graphql file] {6} { "compilerOptions": { "plugins": [ { "name": "gql.tada/ts-plugin", "schema": "./schema.graphql" } ] } } ``` ```json [.json file] {6} { "compilerOptions": { "plugins": [ { "name": "gql.tada/ts-plugin", "schema": "./introspection.json" } ] } } ``` ```json [URL] {6} { "compilerOptions": { "plugins": [ { "name": "gql.tada/ts-plugin", "schema": "http://localhost:4321/graphql" } ] } } ``` ```json [URL with headers] {6-11} { "compilerOptions": { "plugins": [ { "name": "gql.tada/ts-plugin", "schema": { "url": "http://localhost:4321/graphql", "headers": { "Accept": "application/graphql-response+json" } } } ] } } ``` ::: ## Step 3 — Configuring typings We're now ready to let `gql.tada` output a typings file. This file is generated on the fly by the TypeScript plugin, and can [also be generated using the CLI](/get-started/workflows#generating-the-output-file). Where this file will be saved to is configured in the `tsconfig.json` file as well using the `tadaOutputLocation` option. ::: code-group ```json twoslash [tsconfig.json] {7} { "compilerOptions": { "plugins": [ { "name": "gql.tada/ts-plugin", // @annotate: Configure the output typings file location here "schema": "./schema.graphql", "tadaOutputLocation": "./src/graphql-env.d.ts" } ] } } ``` ::: Depending on the `tadaOutputLocation`'s configured file extension, there's [two separate formats](/reference/config-format#tadaoutputlocation) this file can be saved in. For most cases the `.d.ts` format is recommended for best performance however. Once we start up our editor, the TypeScript plugin will run and create the output file. In this example, we’ve created a `src/graphql-env.d.ts` file. When opening this file we should see code that looks like the following: ::: code-group ```ts [graphql-env.d.ts] declare const introspection: { __schema: { /*...*/ }; }; import * as gqlTada from 'gql.tada'; declare module 'gql.tada' { interface setupSchema { introspection: typeof introspection; } } ``` ::: The typings file is a representation of an introspected GraphQL schema and allows types to be inferred for GraphQL documents in the TypeScript type system. After this file is created by automatically, `gql.tada` is set up project-wide and is **ready to be used.** ### Initializing `gql.tada` manually With the prior instructions, we can import `graphql()` from `gql.tada` directly and start writing GraphQL documents, but this default setup limits what we can do. In a full setup, we want to customize scalars or pass further type configuration to `gql.tada`. To customize `gql.tada`, we’ll create a file that imports the output typings manually and uses the `initGraphQLTada()` function to create our own `graphql()` function: :::code-group ```ts twoslash [src/graphql.ts] {4-6} import { initGraphQLTada } from 'gql.tada'; import type { introspection } from './graphql/graphql-env.d.ts'; export const graphql = initGraphQLTada<{ introspection: introspection; }>(); export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada'; export { readFragment } from 'gql.tada'; ``` ::: This setup is also necessary if we're setting up [multiple schemas](/guides/multiple-schemas) (for example, in a monorepo), since we'd have multiple output typings files if we're trying to use `gql.tada` for multiple GraphQL schemas. Instead of importing `graphql()` from `gql.tada`, we should now import it from our custom `src/graphql.ts` file. ### Customizing scalar types Now that we’ve set up a `src/graphql.ts` file, which uses `initGraphQLTada<>()` to create a custom `graphql` function, we may also use this function to customize our scalars. By default, `gql.tada` will have types defined for the [built-in scalars](https://spec.graphql.org/October2021/#sec-Scalars.Built-in-Scalars) in GraphQL. However, it won’t be able to know the serialized type of your custom scalars. For instance, our schema may contain a `DateTime` scalar which, when queried, becomes a string of `new Date().toISOString()`, however, `gql.tada` won’t know that this type is a string. :::code-group ```ts twoslash [src/graphql.ts] {3-6} import { initGraphQLTada } from 'gql.tada'; import type { introspection } from './graphql/graphql-env.d.ts'; // ---cut-before--- export const graphql = initGraphQLTada<{ // @annotate: Define scalar types here introspection: introspection; scalars: { DateTime: string; JSON: any; }; }>(); // ---cut-after--- export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada'; export { readFragment } from 'gql.tada'; ``` ::: When using these scalars, they’ll now be mapped to the types in the `scalars` object type. *** ## Extra Steps A few extra steps may be necessary to install and use `gql.tada`. These are called out, as needed, in the above sections, so you'll only need to follow these steps depending on your workspace and setup. ### Prior to TypeScript 5.5 If you're using a TypeScript version that's **older** than [TypeScript 5.5](https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/) you will have to set up the TypeScript plugin differently. Instead, of using `gql.tada/ts-plugin`, with older versions of TypeScript we'll install `@0no-co/graphqlsp` directly. This is a package that contains the TypeScript plugin that `gql.tada/ts-plugin` uses and aliases. ::: code-group ```sh [npm] npm install --save-dev @0no-co/graphqlsp ``` ```sh [pnpm] pnpm add --save-dev @0no-co/graphqlsp ``` ```sh [yarn] yarn add --dev @0no-co/graphqlsp ``` ```sh [bun] bun add --dev @0no-co/graphqlsp ``` ::: Once `@0no-co/graphqlsp` is installed as a direct dependency, we'll update the `tsconfig.json` to use it. ::: code-group ```json [tsconfig.json] { "compilerOptions": { "strict": true, "plugins": [ { "name": "gql.tada/ts-plugin", // [!code --] "name": "@0no-co/graphqlsp", // [!code ++] "schema": "./schema.graphql", "tadaOutputLocation": "./src/graphql-env.d.ts" } ] } } ``` ::: *** ### VSCode Setup As shown above, `gql.tada` has a TypeScript plugin to provide editor hints, such as diagnostics, auto-completions, and type hovers for GraphQL. This plugin will load up when your workspace's TypeScript installation is used by your editor's TypeScript server. However, VSCode won't by default load up your workspace's TypeScript installation and may instead load up a global installation, which prevents the plugin from being loaded up. To resolve this, you should create a `.vscode/settings.json` file to prompt you [to use the workspace version of TypeScript](https://code.visualstudio.com/docs/typescript/typescript-compiling#_using-the-workspace-version-of-typescript). ::: code-group ```js [.vscode/settings.json] {2-3} { "typescript.tsdk": "node_modules/typescript/lib", "typescript.enablePromptUseWorkspaceTsdk": true } ``` ::: To enable syntax highlighting for GraphQL, you can install the official [“GraphQL: Syntax Highlighting” VSCode extension.](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql-syntax) *** ### Vue and Svelte Support If you're using Vue's `.vue` files and Svelte's `.svelte` files, the TypeScript plugin won't be able to run in your editor under normal circumstances. While some implementations exist for TypeScript to run and type check Vue and Svelte files, `gql.tada` won't have any output in these cases. However, while the TypeScript plugin may not support `.vue` and `.svelte` files, [the `gql-tada check` command does.](/get-started/workflows#running-diagnostics) To enable support for either files, you'll need to install the corresponding support packages. ::: code-group ```sh [npm] # for Vue npm install -D @gql.tada/vue-support # for Svelte npm install -D @gql.tada/svelte-support ``` ```sh [pnpm] # for Vue pnpm add -D @gql.tada/vue-support # for Svelte pnpm add -D @gql.tada/svelte-support ``` ```sh [yarn] # for Vue yarn add -D @gql.tada/vue-support # for Svelte yarn add -D @gql.tada/svelte-support ``` ```sh [bun] # for Vue bun add -d @gql.tada/vue-support # for Svelte bun add -d @gql.tada/svelte-support ``` ::: Once these are installed, the CLI's `check` and other commands will be able to parse and check external files for `gql.tada` errors. --- --- url: /get-started/writing-graphql.md description: How to get set up and ready --- # Writing GraphQL In `gql.tada`, we write our GraphQL documents using the `graphql()` function and receive the result and variables types inferred from the document itself. In this section, we'll see what operation types look like, how we write fragments, and how to use scalar, enum, and input object types. ::: info Imports Some code examples may import `graphql()` from `gql.tada`. However, if you’ve previously followed the steps on the “Installation” page [to initialize `gql.tada` manually](./installation#initializing-gqltada-manually), you’ll instead have to import your custom `graphql()` function, as returned by `initGraphQLTada()`. ::: ## Queries When passing a query to `graphql()`, it will be parsed in TypeScript’s type system and the schema that’s set up is used to map this document over to a type. ```ts twoslash import './graphql/graphql-env.d.ts'; // ---cut-before--- import { graphql } from 'gql.tada'; const PokemonsQuery = graphql(` query PokemonsList($limit: Int = 10) { pokemons(limit: $limit) { id name } } `); ``` The `PokemonsQuery` variable will have an inferred type that defines the type of the data result of the query. When adding variables, the types of variables are added to the inferred type as well. The resulting type is known as a [`TypedDocumentNode`](https://github.com/dotansimha/graphql-typed-document-node) and is supported by most GraphQL clients. When passing a `gql.tada` query to a GraphQL client, the type of input variables and result data are inferred automatically. For example, with `urql` and React, this may look like the following: ```tsx twoslash import './graphql/graphql-env.d.ts'; // ---cut-before--- import { useQuery } from 'urql'; import { graphql } from 'gql.tada'; // @annotate: PokemonsQuery carries a type for data and variables: const PokemonsQuery = graphql(` query PokemonsList($limit: Int = 10) { pokemons(limit: $limit) { id name } } `); const PokemonsListComponent = () => { // @annotate: Types for data and variables are applied from PokemonsQuery: const [result] = useQuery({ query: PokemonsQuery, variables: { limit: 5 }, }); return ( ); }; ``` The same applies to mutation operations, subscription operations, and fragment definitions. The `graphql()` function will parse your GraphQL definitions, take the first definition it finds and infers its type automatically. ```ts twoslash import './graphql/graphql-env.d.ts'; // ---cut-before--- import { graphql, ResultOf, VariablesOf } from 'gql.tada'; // @annotate: The first definition’s types are inferred: const MarkCollectedMutation = graphql(` mutation MarkCollected($id: ID!) { markCollected(id: $id) { id name collected } } `); // @annotate: Inferring the definition’s variables… type variables = VariablesOf; // @annotate: …and the definition’s result type. type result = ResultOf; ``` The above example uses the `ResultOf` and `VariablesOf` types for illustrative purposes. These type utilities may be used to manually unwrap the types of a GraphQL `DocumentNode` returned by `graphql()`. *** ## Fragments The `graphql()` function allows for fragment composition, which means we’re able to create a fragment and spread it into our definitions or other fragments. Creating a fragment is the same as any other operation definition. The type of the first definition, in this case a fragment, will be used to infer the result type of the returned document: ```ts twoslash import './graphql/graphql-env.d.ts'; // ---cut-before--- import { graphql } from 'gql.tada'; const PokemonFragment = graphql(` fragment Pokemon on Pokemon { id name collected } `); ``` Spreading this fragment into another fragment or operation definition requires us to pass the fragment into a tuple array on the `graphql()` function’s second argument. ```ts twoslash import './graphql/graphql-env.d.ts'; import { graphql } from 'gql.tada'; const PokemonFragment = graphql(` fragment Pokemon on Pokemon { id name collected } `); // ---cut-before--- const PokemonsList = graphql(` query PokemonsList { pokemons(limit: 10) { id ...Pokemon } } `, [PokemonFragment]); ``` Here we spread our `PokemonFragment` into `PokemonsList` by passing it into the `graphql()` function and then using its name in the GraphQL document. ### Fragment Masking However, in `gql.tada` a pattern called **“Fragment Masking”** applies. `PokemonsList`’s result type does not contain the `name` and `collected` field from the spread fragment and instead contains a reference to the `PokemonFragment`. This forces us to unwrap, or rather “unmask”, the fragment first. ```tsx twoslash import './graphql/graphql-env.d.ts'; // ---cut-before--- import { useQuery } from 'urql'; import { graphql, readFragment } from 'gql.tada'; const PokemonFragment = graphql(` fragment Pokemon on Pokemon { id name collected } `); const PokemonsList = graphql(` query PokemonsList { pokemons(limit: 10) { id ...Pokemon } } `, [PokemonFragment]); const PokemonsListComponent = () => { const [result] = useQuery({ query: PokemonsList }); // @annotate: The data here does not contain our fragment’s fields: const { pokemons } = result.data!; return pokemons?.map((item) => { // @annotate: Calling readFragment() unwraps the type of the fragment: const pokemon = readFragment(PokemonFragment, item); return pokemon?.name; }); }; ``` When spreading a fragment into a parent definition, the parent only contains a reference to the fragment. This means that we’re isolating fragments. Any spread fragment data cannot be accessed directly until the fragment is unmasked. ```ts twoslash import './graphql/graphql-env.d.ts'; declare var client: import('@urql/core').Client; // ---cut-before--- import { graphql, readFragment } from 'gql.tada'; const PokemonFragment = graphql(` fragment Pokemon on Pokemon { id name collected } `); const PokemonQuery = graphql(` query Pokemon($id: ID!) { pokemon(id: $id) { id ...Pokemon } } `, [PokemonFragment]); const result = await client.query(PokemonQuery, { id: '001' }); // @annotate: Pokemon’s data is only accessible once unmasked with readFragment() const pokemon = readFragment(PokemonFragment, result.data?.pokemon); ``` `PokemonFragment`’s fragment mask in `PokemonQuery` is only unmasked and accessible as its plain result type once we call `readFragment()` on the fragment mask. In this case, we’re passing `data.pokemon`, which is an object containing the fragment mask. This all only happens and is enforced at a type level, meaning that we don’t incur any overhead during runtime for masking our fragments. ### Fragment Composition Fragment Masking is a concept that only exists to enforce proper **Fragment Composition**. In a componentized app, fragments may be used to define the data requirements of UI components, which means, we’ll define fragments, colocate them with our components, and compose them into other fragments or our query. Since all fragments are masked in our types, this colocation is enforced and we maintain our data requirements to UI component relationship. For example, our `PokemonFragment` may be associated with a `Pokemon` component rendering individual items: ::: code-group ```tsx twoslash [components/Pokemon.tsx] import './graphql/graphql-env.d.ts'; // ---cut-before--- // @filename: components/Pokemon.tsx // ---cut--- import { graphql, readFragment, FragmentOf } from 'gql.tada'; export const PokemonFragment = graphql(` fragment Pokemon on Pokemon { id name collected } `); interface Props { // @annotate: The component accepts a fragment mask of PokemonFragment: data: FragmentOf; } export const PokemonComponent = ({ data }: Props) => { // @annotate: In the component body we unwrap the fragment mask: const pokemon = readFragment(PokemonFragment, data); return
  • {pokemon.name}
  • ; }; ``` ::: The `FragmentOf` type is used as an input type above. This type accepts our fragment document and creates the fragment mask that a fragment spread would create as well. We can then use our new `PokemonComponent` in our `PokemonsListComponent` and compose its `PokemonFragment` into our query: ::: code-group ```tsx twoslash [components/PokemonsList.tsx] import './graphql/graphql-env.d.ts'; // ---cut-before--- // @filename: components/Pokemon.tsx import { graphql, readFragment, FragmentOf } from 'gql.tada'; export const PokemonFragment = graphql(` fragment Pokemon on Pokemon { id name collected } `); interface Props { // @annotate: The component accepts a fragment mask of PokemonFragment: data: FragmentOf; } export const PokemonComponent = ({ data }: Props) => { // @annotate: In the component body we unwrap the fragment mask: const pokemon = readFragment(PokemonFragment, data); return
  • {pokemon.name}
  • ; }; // @filename: components/PokemonsList.tsx // ---cut--- import { graphql } from 'gql.tada'; import { useQuery } from 'urql'; import { PokemonFragment, PokemonComponent } from './Pokemon'; const PokemonsListQuery = graphql(` query PokemonsList { pokemons(limit: 10) { id ...Pokemon } } `, [PokemonFragment]); export const PokemonsListComponent = () => { const [result] = useQuery({ query: PokemonsListQuery }); return (
      {result.data?.pokemons?.map((pokemon) => ( // @annotate: The masked fragment data is accepted as defined by FragmentOf: ))}
    ); }; ``` ::: Meaning, while we can unmask and use the `PokemonFragment`’s data in the `PokemonComponent`, the `PokemonsListComponent` cannot access any of the data requirements defined by and meant for the `PokemonComponent`. ## Scalars As we've seen in prior examples, when selection fields, `gql.tada` infers the type of fields from the given schema automatically. Fields will be nullable if the schema doesn't mark them as non-nullable. The default scalars will be typed by their [JSON serialization](https://spec.graphql.org/draft/#sec-JSON-Serialization) value. | Scalar | Type | | --| -- | | `String` | `string` | | `Boolean` | `boolean` | | `Int` | `number` | | `Float` | `number` | When customizing a scalar, the inferred value type of a field will change according to the type we pass in the `scalars` mapping type however, as seen in the ["Customizing scalar types" section](./installation#customizing-scalar-types). :::code-group ```ts twoslash [src/graphql.ts] import { initGraphQLTada } from 'gql.tada'; import type { introspection } from './graphql/graphql-env.d.ts'; // ---cut-before--- export const graphql = initGraphQLTada<{ introspection: introspection; // @annotate: The ID type now takes on a special type scalars: { ID: `${number}`; }; }>(); export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada'; export { readFragment } from 'gql.tada'; ``` ::: Now, creating the `PokemonFragment` with our custom `graphql()` function, the `id` field changes to the specified `ID` type. ::: code-group ```tsx twoslash [components/Pokemon.tsx] // @filename: src/graphql.ts import { initGraphQLTada } from 'gql.tada'; import type { introspection } from '../graphql/graphql-env.d.ts'; export const graphql = initGraphQLTada<{ introspection: introspection; scalars: { ID: `${number}`; }; }>(); export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada'; export { readFragment } from 'gql.tada'; // ---cut--- // @filename: components/Pokemon.tsx // ---cut--- import { graphql, readFragment, FragmentOf } from '../src/graphql'; export const PokemonFragment = graphql(` fragment Pokemon on Pokemon { id name } `); interface Props { data: FragmentOf; } export const PokemonComponent = ({ data }: Props) => { const pokemon = readFragment(PokemonFragment, data); return
  • {pokemon.name}
  • ; }; ``` ::: In this case, we've defined all `ID` types to instead use a more specific type to say that they're stringified numbers, to demonstrate how IDs are structured in this example API. ### Reusing Scalar Types Scalar types are often reused in utility functions and it isn't always possible to [write a fragment](../guides/fragment-colocation) for all of our utility functions, as some of them may not be consuming more than a single scalar value. Following from our last code example, we'd like to now reuse the `ID` type in a small function that accepts the type and parses it further. To do this, we can use the [`graphql.scalar()` helper function](../reference/gql-tada-api#graphql-scalar) to retrieve the type of the scalar. ::: code-group ```tsx twoslash [utils/parseId.ts] // @filename: src/graphql.ts import { initGraphQLTada } from 'gql.tada'; import type { introspection } from '../graphql/graphql-env.d.ts'; export const graphql = initGraphQLTada<{ introspection: introspection; scalars: { ID: `${number}`; }; }>(); export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada'; export { readFragment } from 'gql.tada'; // ---cut--- // @filename: utils/parseId.ts // ---cut--- import { graphql } from '../src/graphql'; export type ID = ReturnType>; export const parseId = (id: ID): number => { return Number(id); }; ``` ::: In the above example, we've used `ReturnType>` to retrieve the type of our scalar directly from our configuration. But `graphql.scalar` is also useful when we wish to repeat the type across the codebase instead. If we want to passively check that the GraphQL scalar type is compatible to a local type, we can also call `graphql.scalar()` directly and let TypeScript check for our value to be compatible with the configured type instead. ::: code-group ```tsx twoslash [utils/parseId.ts] // @filename: src/graphql.ts import { initGraphQLTada } from 'gql.tada'; import type { introspection } from '../graphql/graphql-env.d.ts'; export const graphql = initGraphQLTada<{ introspection: introspection; scalars: { ID: `${number}`; }; }>(); export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada'; export { readFragment } from 'gql.tada'; // ---cut--- // @filename: utils/parseId.ts // ---cut--- import { graphql } from '../src/graphql'; export type ID = `${number}`; export const parseId = (id: ID): number => { const value = graphql.scalar('ID', id); return Number(value); }; ``` ::: ### Enum Types When `gql.tada` infers the type of an enum, the output type becomes a union of all possible literal values. ```graphql enum PokemonType { Bug Dark Dragon # ... } ``` ```ts type PokemonType = | 'Bug' | 'Dark' | 'Dragon'; /* ...*/ ``` And similarly to scalars, we can retrieve the type of enums with the `graphql.scalar()` helper and reuse them. ```tsx twoslash // @filename: src/graphql.ts import { initGraphQLTada } from 'gql.tada'; import type { introspection } from '../graphql/graphql-env.d.ts'; export const graphql = initGraphQLTada<{ introspection: introspection; }>(); // ---cut--- // @filename: utils/pokemonType.ts // ---cut--- import { graphql } from '../src/graphql'; export type PokemonType = ReturnType>; export const isBugType = (input: 'Bug') => { const pokemonType = graphql.scalar('PokemonType', input); return input === 'Bug'; }; ``` Calling `graphql.scalar()` like in the above example also allows us to check a hardcoded subset of values against our scalar while implementing other utility functions. Since `graphql.scalar()` will enforce the second argument to be typed as the scalar itself, we can also use it to enforce compatibility of local types to GraphQL types. ::: info TypeScript Enums Enum types can only be output as unions of string literals in `gql.tada`, but if you're migrating from a different code generator you may instead be using and importing generated TypeScript `enum`s or `const enum`s. You can still use your own values for enum types and configure them using the `scalars` option to replace their inferred values. But if you do this, `gql.tada` won't be able to keep them up-to-date for you. ::: ### Input Objects Lastly, the most complex types that `graphql.scalar()` can return for us are `input` types. ```tsx twoslash // @filename: src/graphql.ts import { initGraphQLTada } from 'gql.tada'; import type { introspection } from '../graphql/graphql-env.d.ts'; export const graphql = initGraphQLTada<{ introspection: introspection; }>(); // ---cut--- // @filename: utils/searchPokemon.ts // ---cut--- import { graphql } from '../src/graphql'; export type SearchPokemon = ReturnType>; ``` Reusing input types is common when we create local state that isn't immediately used as operation variables, or doesn't match the variables types of some operations. ## Abstract Types When a field returns an abstract type (a union or interface) we select its possible types using inline fragments (`... on Type`). Selecting `__typename` alongside them gives TypeScript a discriminant it can use to narrow the result to a single variant. ```ts twoslash import { initGraphQLTada, type ResultOf } from 'gql.tada'; type introspection = { name: 'sample'; query: 'Query'; mutation: never; subscription: never; types: { String: { kind: 'SCALAR'; name: 'String' }; Query: { kind: 'OBJECT'; name: 'Query'; fields: { search: { name: 'search'; type: { kind: 'UNION'; name: 'SearchResult'; ofType: null } }; }; }; SearchResult: { kind: 'UNION'; name: 'SearchResult'; fields: {}; possibleTypes: 'Article' | 'Photo' }; Article: { kind: 'OBJECT'; name: 'Article'; fields: { title: { name: 'title'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null } } }; }; }; Photo: { kind: 'OBJECT'; name: 'Photo'; fields: { url: { name: 'url'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null } } }; }; }; }; }; const graphql = initGraphQLTada<{ introspection: introspection }>(); // ---cut-before--- const SearchQuery = graphql(` query Search { search { __typename ... on Article { title } ... on Photo { url } } } `); declare const data: ResultOf; if (data.search?.__typename === 'Article') { // @annotate: data.search is narrowed to Article here, so title is available data.search.title; } ``` Without `__typename` in the selection set, we have no discriminant to let TypeScript tell the variants apart. In some cases `gql.tada` may automatically add optionally-typed `__typename` fields, so the result types are still readable. But, select `__typename` manually when you encounter unions or interfaces, so your code can switch between them directly. --- --- url: /get-started/workflows.md description: How to use and adapt the CLI in your workflows --- # Essential Workflows ## 1. Setup ### Downloading Schemas The [`schema` setting](/reference/config-format#schema) supports loading your GraphQL schema from an SDL or introspection file, as well as making a GraphQL introspection request to a URL. While this is convenient you may not want your schema to be introspected from an API running locally or remotely indefinitely. However, as you may not be maintaining your GraphQL API in the same repository or your GraphQL server may not output an SDL file itself, the `generate schema` CLI command exists to close this gap. To introspect an API and download your schema, run the `generate schema` command while passing your API's URL. ```sh gql-tada generate schema 'http://api.test/graphql' --output './schema.graphql' ``` When no `--output` argument is passed, the command will attempt to use your configuration's `schema` setting, provided it's a file path. You may also pass `--header` arguments, which define headers sent during the GraphQL introspection request. If a GraphQL endpoint requires authentication headers, you may use this to pass in tokens or authorization headers. ```sh gql-tada generate schema 'http://api.test/graphql' --header "Authorization: $ENV_TOKEN" ``` *** ### The `doctor` command Since you've run through the steps on the [Installation page](/get-started/installation), you may have seen that there are several moving parts to `gql.tada`, including needing an output file to be generated and relying on the TypeScript plugin to display diagnostics. To prevent any of these parts working improperly, and to detect whether there are any issues in your configuration or with your setup, the `doctor` command exists. ```sh gql-tada doctor ``` The `doctor` command runs through several environment checks, loads your configuration, and checks your schema to make sure you don't run into any unexpected issues. While it's entirely optional, it doesn't hurt to run it before you get started or when onboarding a new team member onto `gql.tada`. ## 2. Editing Usually while editing your code, the TypeScript plugin takes care of several things automatically: * it generates the output typings file * it provides type hints and suggestions * it displays diagnostics when it detects a problem However, you can also generate the output typings file or get diagnostics outside of your editor. This is especially important if you need diagnostics or the output file before or without opening your editor, or if your editor does not support TypeScript plugins. ### Generating the output file As we've learned on the [Installation page](/get-started/installation#step-3-—-configuring-typings), the output typings file is necessary for `gql.tada` to infer types of GraphQL documents as it contains an introspection type of your schema. To generate the output typings file, use the `gql-tada` CLI's `generate turbo` command. ```sh gql-tada generate output ``` The `generate output` command loads your schema, generates introspection output and finally saves the output typings file. Just like the TypeScript plugin, the command will use the `tadaOutputLocation` setting to determine where to write the output file to, and will load your schema using the `schema` setting: ::: code-group ```json [tsconfig.json] {7} { "compilerOptions": { "plugins": [ { "name": "gql.tada/ts-plugin", "schema": "./schema.graphql" "tadaOutputLocation": "./src/graphql-env.d.ts" } ] } } ``` ::: The output typings file essentially contains the regular introspection data of your schema. If the format is changed from a `.d.ts` to a `.ts` file, the introspection data is even reusable for runtime code. However, the `d.ts` file type is recommended as it's more efficient for the TypeScript type checker for larger schemas. This format is also preprocessed into an intermediary format. ::: info Should the output tyings file be committed? You can decide yourself whether you want to check the output typings file into version control. Without the typings file, GraphQL types can't be inferred and you'll get errors when running type checks, for example with the `tsc` command. Committing the output file to your repository has the advantage that you'll always be in a state to run type checks. ::: *** ### Running diagnostics The TypeScript plugin runs several checks on your code, providing diagnostics releant to your GraphQL schema right in your editor. But to run `gql.tada`'s diagnostics as a standalone process we can use the CLI's `check` command instead. The command runs all diagnostics and gives us an idea of GraphQL-related issues across a whole workspace. ```sh gql-tada check ``` The `check` command loads your schema then runs diagnostics on your code. This includes both diagnostics specific to `gql.tada` as well as GraphQL validation, and checks against your GraphQL schema. The GraphQL checks that are run are basically the same that your GraphQL server would run during [GraphQL Validation](https://graphql.org/learn/validation/). For example, when you write a query that selects fields that don't exist on your schema, or you pass invalid arguments to a field an error will be displayed. ::: info Why doesn't `tsc` show me diagnostics? TypeScript plugins are hook into the TypeScript language server API, which is specific to editor and IDE features. During other tasks, like when you run `tsc` or other TypeScript compiler tools, TypeScript plugins aren't loaded. The `gql-tada check` command exists to be a standalone version of GraphQL-related diagnostics instead and itself loads the TypeScript plugin's diagnostics code. ::: Two diagnostics that feature in `gql.tada` output opinionated warnings that may not be relevant to your codebase: * [`trackFieldUsage`](/reference/config-format#trackfieldusage) * [`shouldCheckForColocatedFragments`](/reference/config-format#shouldcheckforcolocatedfragments) ## 3. Committing `gql.tada` usually infers the types of GraphQL documents entirely in TypeScript's type system. It has types to parses documents and convert them to types. If you worked with TypeScript before you may have already spotted a problem here. For each GraphQL document you add more, TypeScript's type checker has more work to do, and over time type checking will get slower and slower as your codebase grows. ### Turbo Mode To help with this and prevent performance issues, the `gql-tada` CLI has a `turbo` command that pre-processes types and outputs a type cache. ```sh gql-tada turbo ``` The `turbo` command scans your codebase for GraphQL documents and evaluates their TypeScript types ahead of time. It will then write these types to a type cache, which contains all your documents' pre-evaluated types. You can update your configuration to change where this type cache gets written to with the `tadaTurboLocation` setting: ::: code-group ```json [tsconfig.json] { "compilerOptions": { "plugins": [ { "name": "gql.tada/ts-plugin", "schema": "./schema.graphql" "tadaOutputLocation": "./src/graphql-env.d.ts", "tadaTurboLocation": "./src/graphql-cache.d.ts" // [!code ++] } ] } } ``` ::: If you inspect the written file, you'll see that it's a regular `d.ts` typings file which contains a map of GraphQL document strings, as they appear in your code, to TypeScript type literals. If you're familiar with GraphQL type generation tools, you may recognize that this is essentially a compromise between "codegen tools", which [are explained further on the page on "Typed Documents"](/guides/typed-documents#type-generation), and the pure type inference approach that `gql.tada` takes out of the box. ::: info Should the type cache be committed? You can decide yourself whether you want to check the type cache file into version control. Committing it to your repository has the advantage that when you or someone else starts working on a new set of changes, TypeScript's type checks will be as fast as they can be, which can improve the Developer Experience on larger codebases. ::: ## 4. CI Checks Integrating the `gql-tada` CLI into your continuous integration pipeline takes just adding a few commands and should effectively replicate the errors you may see in an editor when using `gql.tada`. At the very least, you'll likely want to run the `check` command in your CI environment. ```sh gql-tada generate output gql-tada check ``` ::: details GitHub Actions Example If you're using GitHub Actions, you can run the commands in a simple step in your workflow's jobs. ```yaml - name: "gql.tada Checks" run: | gql-tada generate output gql-tada check ``` On GitHub Actions, the `check` command will also integrate with GitHub's, and annotate errors and warnings inside the GitHub UI on pull requests, for instance. ::: The `generate output` command, as [previously mentioned](#generating-the-output-file), generates the typings output file and since this file is necessary for type inference, if it's not generated and missing, running type checks (for instance, with `tsc`) will likely fail with type errors. ### Verifying committed output files As you've seen on this page, there are two different output files we're concerned with when running inside an continuous integration environment. * the output typings file (via [the `generate output` command](#generating-the-output-file)) * the type cache file (via [the `turbo` command](#turbo-mode)) Checking these files into your repository makes sure that your codebase is less reliant on running `gql.tada`, and that anyone who clones your code does not have to even know how to use `gql.tada`. This may mean however that you want to keep these files up-to-date and check for them in your CI's checks as well. ```sh gql-tada generate-output gql-tada turbo git diff --name-status --exit-code . ``` ::: details GitHub Actions Example If you're using GitHub Actions, you can run the commands in a simple step in your workflow's jobs. ```yaml - name: "gql.tada Checks" run: | gql-tada generate output gql-tada generate turbo git diff --name-status --exit-code . ``` ::: The `git diff` command added at the end will fail if any unstaged changes are present. Adding this can help fail your CI step, if any of `gql.tada`'s files need to be updated. --- --- url: /guides/typed-documents.md description: How GraphQL documents and TypeScript come together --- # Typed Documents Although GraphQL defines conventions and guarantees for the client-side GraphQL query language and the server-side GraphQL type system, it’s still ultimately an API specifaction for client-server applications. In GraphQL, we create schemas that describe the type system that queries are executed against, and said schema describes a shape of GraphQL types and scalars. As such, even if we use strong types on the client-side and strong types on the server-side, we still have to bridge the gap between both ends. ## Schemas and Queries Given a GraphQL schema, expressed here in the Schema Definition Language (“SDL”), in its simplest form, we define fields on objects that, when queried, may resolve to scalar values. ```graphql type Query { helloWorld: String numberOfRequests: Int! } ``` When querying this schema we may write a query that requests our defined fields: ```graphql query { helloWorld numberOfRequests } ``` Simplifying this, a GraphQL query, which has been validated against a GraphQL schema, matches a subset of the structure our GraphQL schema defines. However, while it “selects” fields and defines how to execute their resolvers, type information is only present on the schema. As such, as per the specification, a GraphQL API with the above SDL may only return data matching our query, such as: ```json { "helloWorld": "Hello!", "numberOfRequests: 1 } ``` It’s the server-side type system’s responsibility to ensure that when this schema is executed against a valid query, that the execution result matches the types defined. As such, while the server-side, written in any library for any language implementing the GraphQL specification, has complete knowledge of the schemas types and structure, queries are subject to these types *implicitly*. ## Type Generation If we’re using TypeScript on the client-side and have a set of GraphQL documents that we may execute against our schema, we can only know the documents’ result types by looking comparing them to the schema. In GraphQL, this is often done ahead of time in with “Type Generation”. This means that we input our schema and our queries into a process at compile-time and convert the shape of the query to TypeScript’s type system. As such, the shape of data in the queries above would match a TypeScript type looking like the following: ```ts interface Result { helloWorld: string | null; numberOfRequests: number; } ``` In many tools this is done using code generation, a process that, like many concepts in GraphQL, has already been established with JSON Schemas, or other API-shape specifications. In code generation, we would have to ensure that a compile-time tool generates or connects our TypeScript type to what we use at runtime — a query string or AST. With `gql.tada`, this all happens in the TypeScript type system and is more invisible since no files are automatically generated per GraphQL document. ## Integration with Clients If we don’t integrate GraphQL on the client-side with Type Generation, then a minimal example using GraphQL is quite simple. In this example, we’ll have a GraphQL query sent to an API using a simple `fetch` call: ::: code-group ```ts twoslash [query.ts] import { DocumentNode, parse, print } from 'graphql'; const query = parse(/* GraphQL */ ` { helloWorld numberOfRequests } `); async function execute(query: DocumentNode, variables?: any): Promise { const response = await fetch('/graphql', { method: 'POST', body: JSON.stringify({ query: print(query), variables, }), }); return (await response.json()).data; } const data = await execute(query); ``` ::: In TypeScript however, we’re lacking two vital integration points here. Without Type Generations, neither `data` nor `variables` are typed according to our GraphQL schema, although with GraphQL’s guarantees these types should be unambiguous. ### Manual Type Generation With manual code generation tools, a separate tool would output a file containing our query’s types. For instance, it may output a separate file that contains the `Result` and `Variables` types we need: ::: code-group ```ts twoslash [query.generated.ts] export type Result = { helloWorld: string | null; numberOfRequests: number; }; export type Variables = {}; ``` ::: However, this now requires us to make an effort to include these types manually in our `execute` function: ::: code-group ```ts twoslash [query.ts] // @filename: query.generated.ts export type Result = { helloWorld: string | null; numberOfRequests: number; }; export type Variables = {}; // @filename: query.ts // ---cut--- import { DocumentNode, parse, print } from 'graphql'; import type { Result, Variables } from './query.generated'; const query = parse(/* GraphQL */ ` { helloWorld numberOfRequests } `); // @annotate: We add generics to our function: async function execute( query: DocumentNode, variables: Variables ): Promise { const response = await fetch('/graphql', { method: 'POST', body: JSON.stringify({ query: print(query), variables, }), }); return (await response.json()).data; } // @annotate: We pass our generated types to these generics const data = await execute(query, {}); ``` ::: This is a very manual process though that doesn’t match our expectation that **GraphQL is strongly typed and types should hence be inferred implicitly.** ### `TypedDocumentNode` types What we really wish to do with client-side GraphQL is to attach our generated types to the `DocumentNode` type directly. This would mean that our `query` above can only lead to the correct types being used. Furthermore, by using said query, its types could be inferred automatically. Instead of having a `DocumentNode` type, ideally, we’d want the query to be typed as `TypedDocumentNode`. As a result, GraphQL in TypeScript has two ways of attaching types to GraphQL documents: * the [`@graphql-typed-document-node/core`](https://github.com/dotansimha/graphql-typed-document-node) type * the [`graphql` package’s](https://github.com/graphql/graphql-js/blob/2aedf25/src/utilities/typedQueryDocumentNode.ts) type Our type generation tools can now output a `TypedDocumentNode` that has types attached to it directly: ::: code-group ```ts twoslash [query.generated.ts] import { parse } from 'graphql'; import { TypedDocumentNode } from '@graphql-typed-document-node/core'; type Result = { helloWorld: string | null; numberOfRequests: number; }; type Variables = {}; export const query: TypedDocumentNode = parse( '{ helloWorld, numberOfRequests }' ); ``` ::: Which for clients executing a GraphQL query means, that they can infer the types of a given GraphQL query by matching it against `TypedDocumentNode` instead. In our example, we can now infer the generic types from the `query` instead: ::: code-group ```ts twoslash [query.ts] // @filename: query.generated.ts import { parse } from 'graphql'; import { TypedDocumentNode } from '@graphql-typed-document-node/core'; interface Result { helloWorld: string | null; numberOfRequests: number; } interface Variables {} export const query: TypedDocumentNode = parse( '{ helloWorld, numberOfRequests }' ); // @filename: query.ts // ---cut--- import { TypedDocumentNode } from '@graphql-typed-document-node/core'; import { DocumentNode, parse, print } from 'graphql'; import { query } from './query.generated'; async function execute( query: TypedDocumentNode, variables: Variables ): Promise { const response = await fetch('/graphql', { method: 'POST', body: JSON.stringify({ query: print(query), variables, }), }); return (await response.json()).data; } // @annotate: Types are now inferred from the query argument const data = await execute(query, {}); ``` ::: ### `gql.tada` type inference Having types output by a separate code generation tool again doesn’t quite match our expectation that **GraphQL is strongly typed and types should hence be inferred implicitly.** In `gql.tada` however, the idea is that we get from writing a query to having a `TypedDocumentNode` type just via TypeScript inference, without running a separate tool or having files be generated for each query. ```ts twoslash // @filename: graphql-env.d.ts export type introspection = { "__schema": { "queryType": { "name": "Query" }, "mutationType": null, "subscriptionType": null, "types": [ { "kind": "OBJECT", "name": "Query", "fields": [ { "name": "helloWorld", "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "args": [] }, { "name": "numberOfRequests", "type": { "kind": "NON_NULL", "ofType": { "kind": "SCALAR", "name": "Int", "ofType": null } }, "args": [] } ], "interfaces": [] }, { "kind": "SCALAR", "name": "Int" }, { "kind": "SCALAR", "name": "String" } ], "directives": [] } }; import * as gqlTada from 'gql.tada'; declare module 'gql.tada' { interface setupSchema { introspection: introspection } } // @filename: index.ts import './graphql-env.d.ts'; // ---cut--- import { graphql } from 'gql.tada'; // @annotate: We get a TypedDocumentNode without extra imports const query = graphql(` { helloWorld numberOfRequests } `); ``` In essence, what `gql.tada` does is give you a fully typed GraphQL document that tells TypeScript what the `Result` and `Variables` types are just via inference. ## Client Support Today, supporting typed documents in GraphQL is an accepted and de-facto standard, and below you can find a non-exhaustive list of GraphQL clients that support typed documents and will hence also work well with `gql.tada`. ::: code-group ```ts twoslash [@apollo/client] import './graphql/graphql-env.d.ts'; // ---cut-before--- import { graphql } from 'gql.tada'; import { useQuery } from '@apollo/client/react'; const getPokemonsQuery = graphql(` query GetPokemons { pokemons { id name } } `); function Pokemons() { const { loading, error, data } = useQuery(getPokemonsQuery); } ``` ```ts twoslash [@urql/core] import './graphql/graphql-env.d.ts'; declare var client: import('@urql/core').Client; // ---cut-before--- import { graphql } from 'gql.tada'; const getPokemonsQuery = graphql(` query GetPokemons { pokemons { id name } } `); async function getPokemons() { const { data } = await client.query(getPokemonsQuery, {}); } ``` ```ts twoslash [urql] import './graphql/graphql-env.d.ts'; // ---cut-before--- import { graphql } from 'gql.tada'; import { useQuery } from 'urql'; const getPokemonsQuery = graphql(` query GetPokemons { pokemons { id name } } `); function Pokemons() { const [{ fetching, error, data }] = useQuery({ query: getPokemonsQuery }); } ``` ```ts twoslash [graphql-request] import './graphql/graphql-env.d.ts'; // ---cut-before--- import { graphql } from 'gql.tada'; import request from 'graphql-request'; const getPokemonsQuery = graphql(` query GetPokemons { pokemons { id name } } `); async function getPokemons() { const data = await request('/graphql', getPokemonsQuery); } ``` ::: --- --- url: /guides/fragment-colocation.md description: How GraphQL fragments are effectively used in componentized apps. --- # Fragment Colocation When presenting GraphQL, its features often turn into a box-ticking exercise of comparing it to alternative solutions of server-client API design, until we may ask ourselves whether GraphQL’s strengths mostly lie in bringing a community together with clever decisions we can now all agree and rely on… However, while some of what makes GraphQL great is that many of its core principles aren’t new ideas, its less talked about strength lies in fragment composition and hierarchical schema design, which matches our data needs for componentized apps. ## Introduction to Fragments In GraphQL, fragments have many uses, and the uses of “Fragment Colocation” are basically a combination of many of the other uses for fragments. ### Reusing Selection Sets At their most fundamental, fragments allow us to define a selection set and reuse this set in multiple places of our GraphQL document. ```ts import { graphql } from 'gql.tada'; const query = graphql(` query PostsOverview { latestPosts { ...PostCard } trendingPosts { ...PostCard } } fragment PostCard on Post { id text createdAt } `); ``` In the prior example, we’ve extracted two selection sets into a `PostCard` fragment. When a query we’re writing uses the same data in multiple code paths, we may use fragments to only write a re-used selection set once. ### Type Conditions Fragments are also used whenever we’re trying to specify that a certain selection set only applies to one possible type of an abstract type, like a `union` or `interface`. ```ts import { graphql } from 'gql.tada'; const query = graphql(` query PostsOverview { latestPosts { id ...MediaCard ... on MediaPost { videoUrl } ...TextCard ... on TextPost { text } } } `); ``` The above example shows a query for a schema where `latestPosts` exposes an interface that is implemented by two types; `MediaPost` and `TextPost`. We may use fragments to conditionally apply a selection set to either of these types, which is like “Type Narrowing” in GraphQL. > \[!TIP] > The above example uses an inline fragment spread, however, the > same principle of type conditions applies to regular fragments > and fragment spreads. ### `@include` & `@skip` Conditions GraphQL also features two built-in directives, `@include` and `@skip`, which we can use to conditionally include a fragment, based on a variable we pass to our query. ```ts import { graphql } from 'gql.tada'; const query = graphql(` query PostsOverview($showDetails: Boolean!) { latestPosts { id text ...PostDetails @include(if: $showDetails) } } fragment PostDetails on Post { id author { name } location { city } } `); ``` Here, we only include a `PostDetails` fragment if `$showDetails` is set, which means, fragments also allow us to alter the query based on some input variables. We can use this to slightly alter the result shape based on what components we know we’ll render, while keeping the query itself the same. ## Fragment Colocation All the above examples of how we can use fragments may feel vaguely familiar to us, even if this is the first time we’re seeing fragments in action. That might be because fragments are structured very similarly to how components in componentized apps work. While querying fields is similar to how we *access* data in front-end code, and hence map the hierarchy of data we need; Fragments are similar to how we may structure components. ::: code-group ```tsx twoslash [PokemonTypes.tsx] import './graphql/graphql-env.d.ts'; // ---cut-before--- import { FragmentOf, graphql } from 'gql.tada'; export const pokemonTypesFragment = graphql(` fragment PokemonTypes on Pokemon @_unmask { types } `); export const PokemonTypes = (props: { data: FragmentOf }) => { const { data } = props; return (

    Types

      {data.types?.map((typing) =>
    • {typing}
    • )}

    ); }; ``` ::: With fragments, like our `pokemonTypesFragment` above, we can define the data a component *requires to render* right next to the component itself, which keeps concerns on how to fetch this data away from our presentational components, while still defining what data the component requires. ### Nested Fragment Composition While colocating fragments is interesting on its own, it really becomes useful once we define more nested components, and compose their fragments. Let’s create a `Pokemon` component that renders the `PokemonTypes` component we’ve already defined above: ::: code-group ```tsx twoslash [Pokemon.tsx] // @filename: PokemonTypes.tsx import './graphql/graphql-env.d.ts'; import { FragmentOf, graphql } from 'gql.tada'; export const pokemonTypesFragment = graphql(` fragment PokemonTypes on Pokemon @_unmask { types } `); export const PokemonTypes = (props: { data: FragmentOf }) => null; // @filename: Pokemon.tsx // ---cut--- import { FragmentOf, graphql } from 'gql.tada'; import { pokemonTypesFragment, PokemonTypes } from './PokemonTypes'; export const pokemonFragment = graphql(` fragment Pokemon on Pokemon @_unmask { id name ...PokemonTypes } `, [pokemonTypesFragment]); export const Pokemon = (props: { data: FragmentOf }) => { const { data } = props; return (

    {data.name}

    ); }; ``` ::: As we can see, defining reusing and composing fragments, is just as easy as reusing and composing components. No matter whether where we’re using the `Pokemon` or `PokemonTypes` components, as long as we compose fragments upwards, we’ll eventually be able to compose them into a query, at the level of our screen’s code, and hence combine the data requirements of all of our components. ### Fragment Masking In the previous examples, you may have noticed the `@_unmask` directive. In `gql.tada`, a technique called “Fragment Masking” is applied to the generated types of your fragments, and `@_unmask` disables this for the purpose of our example code. Fragment Masking hides the types of a fragment on the fragment’s derived type. This prevents leaking data when composing fragments. Let’s consider what happens if the `Pokemon` component started to accidentally depend on data that only the `PokemonTypes`’s fragment defines. ::: code-group ```tsx twoslash [Pokemon.tsx] // @filename: PokemonTypes.tsx import './graphql/graphql-env.d.ts'; import { FragmentOf, graphql } from 'gql.tada'; export const pokemonTypesFragment = graphql(` fragment PokemonTypes on Pokemon @_unmask { types } `); export const PokemonTypes = (props: { data: FragmentOf }) => null; // @filename: Pokemon.tsx import { FragmentOf, graphql } from 'gql.tada'; import { pokemonTypesFragment, PokemonTypes } from './PokemonTypes'; export const pokemonFragment = graphql(` fragment Pokemon on Pokemon @_unmask { id name ...PokemonTypes } `, [pokemonTypesFragment]); // ---cut-before--- export const Pokemon = (props: { data: FragmentOf }) => { const { data } = props; return (

    {data.name}

    // @error: Pokemon now accidentally depends on PokemonTypes’s data: {data.types?.length}
    ); }; ``` ::: We can fix this by removing `@_unmask` on the `PokemonTypes` component’s fragment to re-enable fragment masking. This will effectively “hide” the `pokemonTypesFragment`’s data from the `Pokemon` component to keep the fragments isolated from one another on a type-level. ::: code-group ```tsx twoslash [PokemonTypes.tsx] import './graphql/graphql-env.d.ts'; // ---cut-before--- import { FragmentOf, graphql, readFragment } from 'gql.tada'; // @annotate: Removing @_unmask isolates this fragment’s data. export const pokemonTypesFragment = graphql(` fragment PokemonTypes on Pokemon { types } `); export const PokemonTypes = (props: { data: FragmentOf }) => { // @annotate: We now have to add readFragment() to unwrap the masked fragment: const pokemon = readFragment(pokemonTypesFragment, props.data); return (

    Types

      {pokemon.types?.map((typing) =>
    • {typing}
    • )}

    ); }; ``` ::: Inside the inferred TypeScript types, when fragment masking *isn’t disabled* using `@_unmask`, then `gql.tada` will infer masked types. In TypeScript, the type that `FragmentOf<>` returns may look like the following: ```ts // FragmentType with @_unmask: type unmaskedPokemonTypes = { types: ("Bug" | "Dark" | /*...*/ null)[] | null; }; // FragmentType without @_unmask: type maskedPokemonTypes = { [$tada.fragmentRefs]: { PokemonTypes: 'Pokemon'; }; }; ``` The `$tada.fragmentRefs` property above is just a stand-in for the fragment that we've used in our GraphQL document and all selections inside that fragment are not present and hidden. > \[!TIP] Why is this the default behaviour? > This is the default behaviour in `gql.tada`, and happens unless you add `@_unmask` > to a fragment. Not only is this a great pattern to prevent mistakes, it also improves > TypeScript's inference performance! > > We recommend you not to disable Fragment Masking unless you absolutely have to, > to enforce fragment composition safety. ::: details Disabling Fragment Masking globally While fragment masking is the default, you can also switch it off globally, which is equivalent to adding `@_unmask` to every fragment. We don't necessarily recommend starting out with this, since it makes it harder to switch and migrate to fragment masking incrementally, if you decide to do so in the future. However, if this isn't a concern to you, you can pass a `disableMasking` flag to the `initGraphQLTada` call: ```ts twoslash [src/graphql.ts] import { initGraphQLTada } from 'gql.tada'; import type { introspection } from './graphql/graphql-env.d.ts'; // ---cut-before--- export const graphql = initGraphQLTada<{ disableMasking: true; // [!code ++] introspection: introspection; scalars: { DateTime: string; }; }>(); ``` ::: ### Import Diagnostic Fragment colocation and masking helps us manage large amounts of GraphQL documents when creating and composing queries, while keeping data usage isolated and minimal, right next to our UI components. However, a common mistake with this method is that sometimes we may forget to import and use a fragment. To prevent us from leaving out fragments, the TypeScript plugin has a diagnostic called `shouldCheckForColocatedFragments`. This diagnostic will issue a warning if any imports in your documents don't include an exported fragment. ::: code-group ```tsx twoslash {4} [Without importing a fragment] import './graphql/graphql-env.d.ts'; // ---cut-before--- // @filename: ./src/PokemonsList.tsx // ---cut--- // @warn: GraphQLSP: Unused co-located fragment definition(s) import { PokemonItem } from './PokemonItem'; ``` ```tsx twoslash {4} [With importing a fragment] import './graphql/graphql-env.d.ts'; // ---cut-before--- // @filename: ./src/PokemonsList.tsx // ---cut--- import { PokemonItem, PokemonItemFragment } from './PokemonItem'; ``` ::: This ties together the last loose end for the fragment colocation and masking patterns. --- --- url: /guides/persisted-documents.md description: How to integrate with and generate persisted documents --- # Persisted Documents APIs that support Persisted Documents use identifiers that are sent to the GraphQL API instead of the complete GraphQL documents. This requires that the identifiers are embedded in our code and that the documents are known to our GraphQL API. ::: details What are Persisted Documents? We call a document a "persisted document" if it has an ID that identifies it. A GraphQL API that implements persisted documents will typically accept an ID instead of a full `query` parameter, containing the full GraphQL document. When used for CDN caching, a GraphQL API may accept a request with a document ID as a `GET` HTTP request, making CDN caching trivial, since it turns GraphQL requests into CDN-cacheable RPC calls. Some GraphQL API frameworks may implement optimizations for persisted documents. Since the documents are known ahead of time, they can parse and validate the document just once. When the API is running and receives a persisted document ID, it may already assume that the document is valid. When used as a security measure, a GraphQL API may reject unknown queries by checking the ID against a list of allowed documents. This effectively limits the GraphQL queries your API accepts to just documents you've written yourself. ::: ::: details How do they differ from Automatic Persisted Queries? **Persisted Documents** are not the same as **Automatic Persisted Queries**. Automatic Persisted Queries are a protocol extension for which the ID for documents are hashed and generated on the client-side, during runtime, automatically, and registered with the API if it does not recognize an ID. If your API supports them, you won't need to modify your `gql.tada` code to make use of this feature. However, you also won't be able to implement any of the security benefits of Persisted Documents, as documents are registered dynamically with the API, instead of ahead of time. ::: *** ## Defining Persisted Documents We may define persisted documents by using the `graphql.persisted()` API. This call wraps around a GraphQL document and annotates it with a document ID that we pass to the call. ```ts twoslash import './graphql/graphql-env.d.ts'; // ---cut-before--- import { graphql } from 'gql.tada'; const pokemonsQuery = graphql(` query PokemonsList($limit: Int = 10) { pokemons(limit: $limit) { id name } } `); const persistedQuery = graphql.persisted("POKEMONS_LIST_ID", pokemonsQuery); ``` The replacement document - `persistedQuery` in our example - copies the type of the document it receives, so type inference will still work as usual when we use it instead of the original query. However, the returned document will also carry a `documentId` property with it, which is set to the ID we passed to `graphql.persisted`. In this case, it'll be set to `"POKEMONS_LIST_ID"`. ### Compiling away GraphQL documents When using **Persisted Documents** as a security measure, the API enforces them and only accepts known document IDs. You may wish to combine this with a technique to obscure GraphQL documents, by omitting them from your client-side output bundles entirely. Compiling GraphQL documents is often done to completely obscure the arguments and types shape of your GraphQL schema. ::: tip Compatibility with GraphQL clients Check whether your GraphQL clients supports omitting the original GraphQL document. Many GraphQL client caches rely on the original document and its `definitions` to be available to them, either to provide normalized caching, or to identify the document uniquely. ::: This can be achieved by passing the original GraphQL document as a type to a `graphql.persisted()` call. ```ts twoslash import './graphql/graphql-env.d.ts'; // ---cut-before--- import { graphql } from 'gql.tada'; const pokemonsQuery = graphql(` query PokemonsList($limit: Int = 10) { pokemons(limit: $limit) { id name } } `); const persistedQuery = graphql.persisted( "POKEMONS_LIST_ID" ); ``` When passing the original document as a generic, the return type of `graphql.persisted()` remains identical, but the document string itself will be omitted from your compiled output bundle, provided the original document - `pokemonsQuery` in our example - isn't referenced anywhere else in your code. ::: details When and why does this work? When we refer to the document using `typeof`, this refers to a value by type instead of by value. Since a `graphql()` call is side effectless and `typeof` only refers to it by type, no reference to the original value remain in TypeScript's transpiled output code. This lets **tree-shaking and minification** remove the original value, which effectively removes the original GraphQL document definition from your compiled output bundle, as long as either of these mechanisms work properly in your bundles or app framework. ::: *** ### Using generated IDs Many GraphQL APIs choose to use hashes as GraphQL document IDs, since the IDs for documents don't necessarily have to be human-readable, and often need to change when the document changes. Since it's tedious to manually generate hashes for a GraphQL document and to keep track of when it changes, the TypeScript plugin has to mechanisms to help us with hashed document IDs: * it provides a **code action** that generates a SHA256 hash of your document * a **diagnostic** warns you if this SHA256 hash needs to be updated The code action will be reported to your editor once you have defined a `graphql.persisted()` call. When activated, it will replace the current document ID passed to the call with a new hash. In our example above, we'd end up with the following code after: ```ts const persistedQuery = graphql.persisted( "sha256:89e47d4f32b4ff76296844ff260d2878bf1829d30706fc7fc92de0fc66c2a4cf", pokemonsQuery ); ``` ## Generating Persisted Manifests Embedding document IDs with our `gql.tada` documents allows us to send them to our GraphQL API. However, the other half of making Persisted Documents work is extracting and registering GraphQL documents from our codebase. To generate a persisted JSON manifest file, use the `gql.tada` CLI's `generate persisted` command. ```sh gql-tada generate persisted --output persisted.json ``` The `generate persisted` command scans your codebase for persisted GraphQL documents by looking for `graphql.persisted()` calls, and evaluates and extracts them into a JSON file. To omit the `--output` argument, you can update your configuration to change where this persisted manifest file gets written to with the `tadaPersistedLocation` setting: ::: code-group ```json [tsconfig.json] { "compilerOptions": { "plugins": [ { "name": "gql.tada/ts-plugin", "schema": "./schema.graphql", "tadaOutputLocation": "./src/graphql-env.d.ts", "tadaPersistedLocation": "./persisted.json" // [!code ++] } ] } } ``` ::: ### Using the persisted manifest file The persisted manifest file is a JSON file that contains document entries. Each entry is keyed by a document ID and has a value of the GraphQL detected document. In our example code this would result in a file containing a `"POKEMONS_LIST_ID"` with our document as a GraphQL document value: ```json { "POKEMONS_LIST_ID": "\n query Pokemons ($limit: Int = 10) {\n pokemons(limit: $limit) {\n id\n name\n }\n }\n\n\nfragment PokemonItem on Pokemon {\n id\n name\n}" } ``` The document string is a combination of the original string that your `graphql()` call receives with all fragments it references appended to it. ::: tip Formatting Documents The persisted manifest file may not contain the documents exactly how your GraphQL client would format it. GraphQL clients often format documents to add introspection fields to them, most commonly adding `__typename` fields to selection sets. As such, you may want to format and modify the GraphQL document strings before registering them with your GraphQL API. ::: *** ## Integration with GraphQL Clients The ["GraphQL over HTTP" specification](https://github.com/graphql/graphql-over-http/blob/persisted-documents/spec/Appendix%20A%20--%20Persisted%20Documents.md) is looking to standardize how persisted documents are sent to GraphQL APIs via HTTP. If your GraphQL client supports this specification, you likely won't have to do anything else to send persisted documents to your API, as long as your API supports them. > \[!NOTE] > "GraphQL over HTTP" is currently a *Stage 2* proposal and is not fully implemented > by all GraphQL clients and servers yet. The Persisted Documents appendix of the > specification is an early RFC and not implemented by most servers yet. *** ### `urql` Client By default, `@urql/core` will omit the `query` property and send a `documentId` property containing the document ID instead when you're using persisted documents. If your API supports this request format, there's nothing else you have to do. #### Formatting Persisted Documents Before you can register the documents in your persisted manifest file with your GraphQL API, you should format the documents the same way `@urql/core` does, if you're using a `cacheExchange`. ```ts twoslash import { print, parse } from '@0no-co/graphql.web'; import { formatDocument } from '@urql/core'; export function formatClientDocument(document: string) { return print(formatDocument(parse(document))); } ``` Before `urql` sends a GraphQL document to your API, it formats the document to add `__typename` fields to the selection set. Applying the above transform to your persisted JSON manifest file's documents ensures that your API will process the same GraphQL operation that `urql` expects to receive a result for. #### `@urql/exchange-persisted` If your API supports the unofficial [Apollo Automatic Persisted Queries protocol](https://github.com/apollographql/apollo-link-persisted-queries#apollo-engine) instead, you'll have to use the `@urql/exchange-persisted` exchange. ::: details Automatic Persisted Queries protocol The Automatic Persisted Queries protocol sends omits the `query` property from requests, and sends the document ID under the `extensions.persistedQuery.sha256Hash` property. ```json { "variables": null, "extensions": { "persistedQuery": { "version": 1, "sha256Hash": "DOCUMENT_ID" } } } ``` ::: First, install the `@urql/exchange-persisted` package: ::: code-group ```sh [npm] npm install @urql/exchange-persisted ``` ```sh [pnpm] pnpm add @urql/exchange-persisted ``` ```sh [yarn] yarn add @urql/exchange-persisted ``` ```sh [bun] bun add @urql/exchange-persisted ``` ::: You'll then need to add the `persistedExchange` to your exchanges, in front of the `fetchExchange`. ```ts twoslash import type { TadaPersistedDocumentNode } from 'gql.tada'; import { Client, fetchExchange, cacheExchange } from 'urql'; import { persistedExchange } from '@urql/exchange-persisted'; export const client = new Client({ url: '/graphql', exchanges: [ cacheExchange, persistedExchange({ async generateHash(_, document) { return (document as TadaPersistedDocumentNode).documentId; }, preferGetForPersistedQueries: true, enforcePersistedQueries: true, enableForMutation: true, enableForSubscriptions: true, }), fetchExchange, ], }); ``` When `preferGetForPersistedQueries` is enabled, query operations will be sent as `GET` HTTP requests instead of `POST` requests, which makes CDN caching simpler to enable. *** ### Apollo Client You'll have to use the built-in `createPersistedQueryLink` function and add the link in front of your HTTP link. ```ts twoslash import type { TadaPersistedDocumentNode } from 'gql.tada'; import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client'; import { createPersistedQueryLink } from '@apollo/client/link/persisted-queries'; const link = createPersistedQueryLink({ generateHash(document) { return (document as TadaPersistedDocumentNode).documentId; }, useGETForHashedQueries: true, }).concat(new HttpLink({ uri: '/graphql' })); export const client = new ApolloClient({ cache: new InMemoryCache(), link, }); ``` This will send your persisted documents using the unofficial [Apollo Automatic Persisted Queries protocol](https://github.com/apollographql/apollo-link-persisted-queries#apollo-engine). ::: details Automatic Persisted Queries protocol The Automatic Persisted Queries protocol sends omits the `query` property from requests, and sends the document ID under the `extensions.persistedQuery.sha256Hash` property. ```json { "variables": null, "extensions": { "persistedQuery": { "version": 1, "sha256Hash": "DOCUMENT_ID" } } } ``` ::: When `useGETForHashedQueries` is enabled, query operations will be sent as `GET` HTTP requests instead of `POST` requests, which makes CDN caching simpler to enable. #### Formatting Persisted Documents Before you can register the documents in your persisted manifest file with your GraphQL API, you should format the documents the same way the Apollo Client does. ```ts twoslash import { print, parse } from 'graphql'; import { addTypenameToDocument } from '@apollo/client/utilities'; export function formatClientDocument(document: string) { return print(addTypenameToDocument(parse(document))); } ``` Before Apollo Client sends a GraphQL document to your API, it formats the document to add `__typename` fields to the selection set. Applying the above transform to your persisted JSON manifest file's documents ensures that your API will process the same GraphQL operation that Apollo Client expects to receive a result for. --- --- url: /guides/multiple-schemas.md description: How to set up multiple schemas and GraphQL APIs --- # Multiple Schemas When first getting started you're probably setting up `gql.tada` for a single schema - your own GraphQL API's schema. However, once you're interacting with another GraphQL API you can set up `gql.tada` for it as well. ::: details When would you setup multiple schemas? Interacting with multiple GraphQL APIs isn't uncommon anymore. * Maybe you have a public GraphQL API and a private, admin GraphQL API? * Maybe you're in a monorepo and your GraphQL API is calling another third-party GraphQL API? While consuming multiple GraphQL APIs on your front-end simultaneously makes you lose out of a lot of GraphQL's benefits, there are still many reasons for a your codebase to communicate with multiple GraphQL APIs. Whenever you're writing GraphQL documents for different schemas, you'll probably want to set them up with `gql.tada` to rely on its types and diagnostics. ::: *** ## 1. Configuring Multiple Schemas If you've followed the instructions on the [Installation page](/get-started/installation#step-2-—-configuring-a-schema), your configuration contains only one set of [schema options](/reference/config-format#schema-options) in the plugin configuration. To add multiple schemas, you'll have to update your configuration and move all schema options onto a `schemas[]` array. ::: code-group ```json [tsconfig.json] { "compilerOptions": { "plugins": [ { // [!code focus:15] "name": "gql.tada/ts-plugin", "schemas": [ { "name": "pokemon", "schema": "./graphql/pokemon.graphql", "tadaOutputLocation": "./src/graphql/pokemon-env.d.ts" }, { "name": "simple", "schema": "./graphql/simple.graphql", "tadaOutputLocation": "./src/graphql/simple-env.d.ts" } ] } ] } } ``` ::: All entries in the `schemas[]` list are entirely separate and load their own GraphQL schemas and have their own file locations. You'll have to add a unique `name` to each entry. These names are only used in `gql.tada`'s internal tooling, and in errors and diagnostics messages, so they're entirely arbitrary. After you've updated your configuration you can use the `doctor` command to make sure everything's working properly. ```sh gql-tada doctor ``` ## 2. Initializing `gql.tada` per schema Once multiple schemas are configured, we cannot import and use the `graphql()` function from `gql.tada` anymore and have to instead create a `graphql()` function per schema manually. This is because the GraphQL schema types are different for each schema we've set up, and `gql.tada`'s tooling also needs to be able to identify each schema per document. ::: info Installation Steps For this section, you'll basically repeating the steps from [the Installation page's "Intializing `gql.tada` manually" section](/get-started/installation#initializing-gql-tada-manually) for each schema you're setting up. ::: To do this, we'll call `initGraphQLTada()` to create a new `graphql()` function for each schema we're setting up. :::code-group ```ts twoslash [src/graphql/pokemon.ts] {2} // @filename: pokemon-env.d.ts export type introspection = import('./graphql/graphql-env.d.ts').introspection; // @filename: index.ts // ---cut--- import { initGraphQLTada } from 'gql.tada'; import type { introspection } from './pokemon-env.d.ts'; export const graphql = initGraphQLTada<{ introspection: introspection; }>(); ``` ::: We'll have to repeat this for each schema we're setting up. For our prior example, we'd be repeating this for our `simple.graphql` schema. :::code-group ```ts twoslash [src/graphql/simple.ts] {2} // @filename: simple-env.d.ts export type introspection = { name: 'simple'; query: 'Query'; types: { String: unknown; Query: { kind: 'OBJECT'; name: 'Query'; fields: { helloWorld: { name: 'helloWorld'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; }; }; }; }; // @filename: index.ts // ---cut--- import { initGraphQLTada } from 'gql.tada'; import type { introspection } from './simple-env.d.ts'; export const graphql = initGraphQLTada<{ introspection: introspection; }>(); ``` ::: After setting up each schema, each individual `graphql()` function imported from these new files will point to a different schema and infer to the corresponding types. ```ts twoslash [src/graphql/simple.ts] // @filename: src/graphql/simple-env.d.ts export type introspection = { name: 'simple'; query: 'Query'; types: { String: unknown; Query: { kind: 'OBJECT'; name: 'Query'; fields: { helloWorld: { name: 'helloWorld'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; }; }; }; }; // @filename: src/graphql/simple.ts // ---cut--- import { initGraphQLTada } from 'gql.tada'; import type { introspection } from './simple-env.d.ts'; export const graphql = initGraphQLTada<{ introspection: introspection; }>(); // @filename: src/index.ts // ---cut--- import { graphql } from './graphql/simple'; const query = graphql(` { helloWorld } `); ``` *** ### CLI Commands ```sh gql-tada check gql-tada generate output gql-tada generate turbo gql-tada generate persisted ``` All of the `gql-tada` CLI commands still work the exact same when multiple schemas are set up. However, while all `generate` commands accept an `--output` argument when only one schema is configured, with multiple schemas, you'll have to configure their output file paths in your schema options instead. * [`tadaOutputLocation`](/reference/config-format#tadaoutputlocation) for the [`generate output`](/reference/gql-tada-cli#generate-output) command * [`tadaTurboLocation`](/reference/config-format#tadaturbolocation) for the [`generate turbo`](/reference/gql-tada-cli#generate-turbo) command * [`tadaPersistedLocation`](/reference/config-format#tadapersistedlocation) for the [`generate persisted`](/reference/gql-tada-cli#generate-persisted) command ::: code-group ```json [tsconfig.json] {10-12} { "compilerOptions": { "plugins": [ { "name": "gql.tada/ts-plugin", "schemas": [ { "name": "pokemon", "schema": "./graphql/pokemon.graphql", "tadaOutputLocation": "./src/graphql/pokemon-env.d.ts", "tadaTurboLocation": "./src/graphql/pokemon-cache.d.ts", "tadaPersistedLocation": "./graphql/pokemon-persisted.json" }, /*...*/ ] } ] } } ``` ::: --- --- url: /guides/testing.md description: How to write type-safe test fixtures and fake data with fragment masking. --- # Testing With [fragment masking](/guides/fragment-colocation#fragment-masking), fragments are opaque and isolated at the type-level, which enforces that a parent never sees the data its children selected. This helps in app code, but complicates tests. Mock data written by hand has the shape of the *unmasked* fragment result, which won't match the masked types the document expects. The `gql.tada/testing` entrypoint exports three helpers to bridge this gap. They are meant for tests, stories, fixtures, and cache updaters. ::: tip Reach for these only at the boundary where you construct mock data. Inside the component or function under test, keep using [`readFragment()`](/reference/gql-tada-api#readfragment) as you normally would. ::: *** ## Masking a fragment's data [`maskFragments()`](/reference/gql-tada-api#maskfragments) takes a list of fragments and the (unmasked) data for them, and returns that data typed as a masked fragment reference. This is the helper you want when a component under test accepts a `FragmentOf` prop and you need to hand it a fixture: ```ts twoslash [pokemon.test.ts] import './graphql/graphql-env.d.ts'; // ---cut-before--- import { graphql } from 'gql.tada'; import { maskFragments } from 'gql.tada/testing'; const pokemonItemFragment = graphql(` fragment PokemonItem on Pokemon { id name } `); // Ready to pass as a prop: const data = maskFragments([pokemonItemFragment], { id: '001', name: 'Bulbasaur', }); ``` `maskFragments()` also accepts `null`, `undefined`, and arrays of data, so you can build fixtures for nullable or list-typed fragment props directly. *** ## Building a document's result [`readResult()`](/reference/gql-tada-api#readresult) builds a type-safe fixture for a whole document. You pass it the document, the data with fragment fields inlined, and the list of fragments the document uses. It resolves the references so your data is fully type checked, including fragments that themselves spread other fragments. ```ts twoslash [pokemon.test.ts] import './graphql/graphql-env.d.ts'; // ---cut-before--- import { graphql } from 'gql.tada'; import { readResult } from 'gql.tada/testing'; const pokemonNameFragment = graphql(` fragment PokemonName on Pokemon { name } `); const pokemonItemFragment = graphql(` fragment PokemonItem on Pokemon { id ...PokemonName } `, [pokemonNameFragment]); const query = graphql(` query { pokemon(id: "001") { ...PokemonItem } } `, [pokemonItemFragment]); // Fully type-checked, including nested fragments: const data = readResult( query, { pokemon: { id: '001', name: 'Bulbasaur' } }, [pokemonItemFragment, pokemonNameFragment] ); ``` Pass **every** fragment you want inlined, including ones spread transitively by other fragments. Any fragment you leave out of the list stays masked: instead of inlined fields, it shows up as a still-masked reference in the expected data. This is handy when you'd rather build part of the result with [`maskFragments()`](#masking-a-fragment-s-data). Leave that fragment out, and slot the masked value in instead, if you already have mocked data for an individual fragment. ### Casting results unsafely [`unsafe_readResult()`](/reference/gql-tada-api#unsafe_readresult) casts data to a document's result type **without type checking** the data nested inside fragment references. It's a slightly safer alternative to `as any as ResultOf`. ```ts twoslash [pokemon.test.ts] import './graphql/graphql-env.d.ts'; // ---cut-before--- import { graphql } from 'gql.tada'; import { unsafe_readResult } from 'gql.tada/testing'; const pokemonItemFragment = graphql(` fragment PokemonItem on Pokemon { id name } `); const query = graphql( ` query { pokemon(id: "001") { ...PokemonItem } } `, [pokemonItemFragment] ); // ⚠️ data is cast to the result type WITHOUT checking the fragment fields: const data = unsafe_readResult(query, { pokemon: { id: '001', name: 'Bulbasaur' }, }); ``` > \[!CAUTION] > Because `unsafe_readResult()` doesn't check the data inside fragment masks, a > typo or missing field won't be caught. Prefer > [`readResult()`](#building-a-document-s-result) whenever you can, and reach for > this only when listing every fragment is impractical. --- --- url: /guides/recipebook.md description: 'A collection of tips, tricks, and patterns for common gql.tada use-cases.' --- # Recipebook ## Customizing Scalars By default, `gql.tada` maps the [built-in GraphQL scalars](https://spec.graphql.org/October2021/#sec-Scalars.Built-in-Scalars) to their TypeScript equivalents, and maps any custom scalars it doesn't recognize to `unknown`. The `scalars` option on `initGraphQLTada<>()` overrides either of these. ### Overriding scalar types The same `scalars` option maps both built-in and custom scalars to whatever TypeScript type matches what your API serializes. A common adjustment is the built-in `ID` scalar, which `gql.tada` types as `string | number`. The [GraphQL specification](https://spec.graphql.org/draft/#sec-ID) allows an `ID` to be serialized from either, but most APIs only return strings. Custom scalars like `DateTime` or `JSON` default to `unknown`, since their type isn't automatically known, and usually we'd want to map these too. ::: code-group ```ts twoslash [src/graphql.ts] import { initGraphQLTada } from 'gql.tada'; import type { introspection } from './graphql/graphql-env.d.ts'; export const graphql = initGraphQLTada<{ introspection: introspection; scalars: { ID: string; // [!code ++] DateTime: string; // [!code ++] JSON: unknown; // [!code ++] JSONObject: Record; // [!code ++] }; }>(); // ---cut-after--- export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada'; export { readFragment } from 'gql.tada'; ``` ::: Since these are only type-level modifications, they don't change any runtime values. Make sure each matches what your GraphQL API actually returns. ### Opaque (branded) scalar types Mapping a scalar to a primitive like `string` makes it indistinguishable from any other string. For scalars that need (de)serialization you could consider using an **opaque type** (also known as "branded types"). For example, `DateTime` is a common type in GraphQL that usually is serialized as an ISO string. A branded type lets us annotate these strings with a unique symbol, which TypeScript treats as distinct from plain `string`s. This allows us to enforce serialization and deserialization. ::: code-group ```ts twoslash [src/graphql.ts] import { initGraphQLTada } from 'gql.tada'; import type { introspection } from './graphql/graphql-env.d.ts'; declare const tag: unique symbol; export type DateTime = string & { readonly [tag]: 'DateTime' }; export const graphql = initGraphQLTada<{ introspection: introspection; scalars: { DateTime: DateTime; // [!code ++] }; }>(); // ---cut-after--- export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada'; export { readFragment } from 'gql.tada'; ``` ::: The `DateTime` example type above makes this type distinct from just a `string` which means we can create utilities to deserialize and serialize this scalar. ```ts twoslash declare const tag: unique symbol; type DateTime = string & { readonly [tag]: 'DateTime' }; // ---cut-before--- export const fromDateTime = (value: DateTime) => new Date(value); export const toDateTime = (date: Date) => date.toISOString() as DateTime; ``` This keeps all marshalling logic in a single place and naturally enforces this as a zero-cost typesystem abstraction, without any additional conversion overhead. Without branding, a raw `string` may flow straight into `new Date(...)` or can be missed at various code sites, scattering (and maybe diverging) its parsing logic across a large codebase. With branding, we enforce that specific utility functions are used with these scalars. The same can be useful for other unique types that serialize to `string`s or `number`s, such as `URL`, `UUID`, or `EmailAddress`. > \[!TIP] > The cast inside `toDateTime()` is the one place the brand is asserted. Keeping > that assertion isolated in a utility is the point. The rest of your code never > casts. *** ## Working with Enums By default, `gql.tada` infers GraphQL enums as a union of string literals, e.g. `'Bug' | 'Dark' | 'Dragon'`. This is the right representation for new code: string literal unions are forward-compatible and compile away to nothing. `initGraphQLTada<>()` however allows remapping enum types to another TypeScript type. This is most useful when migrating an existing codebase onto `gql.tada` — for example from [GraphQL Code Generator](https://the-guild.dev/graphql/codegen), which by default may emit a large amount of TypeScript `enum`s. The `scalars` option can remap enum types by name, so you can slot your existing enum back in and `gql.tada` will infer the exact type your code expects. This lets you adopt it without rewriting every reference up front. ::: code-group ```ts twoslash [src/graphql.ts] import { initGraphQLTada } from 'gql.tada'; import type { introspection } from './graphql/graphql-env.d.ts'; // The enum your existing code already imports: export enum PokemonType { Fire = 'Fire', Water = 'Water', Grass = 'Grass', } export const graphql = initGraphQLTada<{ introspection: introspection; scalars: { PokemonType: PokemonType; // [!code ++] }; }>(); // ---cut-after--- export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada'; export { readFragment } from 'gql.tada'; ``` ::: Wherever this enum's GraphQL type is selected, `gql.tada` now infers your `PokemonType` enum instead of the default string literal union, so existing call sites keep type checking unchanged. > \[!WARNING] > Treat this as a migration aid, not a default. TypeScript `enum`s have > [well-documented drawbacks](https://www.totaltypescript.com/why-i-dont-like-typescript-enums). > They emit runtime code and are reference-incompatible with their own values, since > each enum value is an independent symbol in the TypeScript type checker. > Once a part of your codebase is migrated, drop the override and use the default > string literal unions, which align with GraphQL's backwards-compatibility > guarantees. *** ## TypeScript Performance `gql.tada` infers everything in TypeScript's type system, so type-checking speed scales with how much GraphQL you write. As a project grows, the editor or `tsc` can slow down, and very large documents can hit `Type instantiation is excessively deep and possibly infinite.ts(2589)`. A few habits keep things fast: * **Keep `gql.tada` up to date.** Inference performance and document-size limits improve across releases, and upgrading is the most common fix for `ts(2589)`. * **Enable Turbo Mode.** [`gql-tada turbo`](/get-started/workflows#turbo-mode) pre-computes a cache of all document types. Checking it into your repository lets the plugin and CLI reuse the snapshot instead of re-inferring every type. * **Use the `.d.ts` output format.** The [`tadaOutputLocation`](/reference/config-format#tadaoutputlocation) `.d.ts` format is much more efficient for the type-checker than `.ts`. Only use `.ts` if another tool needs the introspection data at runtime. * **Compose with fragments.** Splitting a large query into [colocated fragments](/guides/fragment-colocation) keeps each selection set small, which is easier on inference. --- --- url: /reference/gql-tada-api.md --- # `gql.tada` API ## Functions ### `graphql()` | | Description | | -------------------- | ----------------------------------------------------------------------- | | `input` argument | A string of a GraphQL document. | | `fragments` argument | An optional list of other GraphQL fragments created with this function. | | returns | A GraphQL `DocumentNode` with result and variables types. | Creates a `DocumentNode` with result and variables types. You can compose fragments into this function by passing them and a fragment mask will be created for them. When creating queries, the returned document of queries can be passed into GraphQL clients which will then automatically infer the result and variables types. It is used with your schema in `setupSchema` to create a result type of your queries, fragments, and variables. If you instead would like to manually create a `graphql` function with an explicit schema type, [use `initGraphQLTada` instead.](#initgraphqltada) #### Example ```ts twoslash // @filename: graphq-env.d.ts export type introspection = { __schema: { queryType: { name: 'Query'; }; mutationType: null; subscriptionType: null; types: [ { kind: 'OBJECT'; name: 'Query'; fields: [ { name: 'hello'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; args: []; }, { name: 'world'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; args: []; }, ]; interfaces: []; }, { kind: 'SCALAR'; name: 'String'; }, ]; directives: []; }; }; import * as gqlTada from 'gql.tada'; declare module 'gql.tada' { interface setupSchema { introspection: introspection; } } // @filename: index.ts import './graphql-env.d.ts'; // ---cut--- import { graphql } from 'gql.tada'; const fragment = graphql(` fragment HelloWorld on Query { hello world } `); const query = graphql( ` query HelloQuery { hello ...HelloWorld } `, [fragment] ); ``` *** ### `graphql.scalar()` | | Description | | ---------------- | ---------------------------------------------- | | `name` argument | A name of a GraphQL scalar or enum. | | `value` argument | The value to be type-checked against the type. | | returns | The `value` will be returned directly. | Type checks a given input value to be of a scalar or enum type and returns the value directly. You can use this utility to add a type check for a scalar or enum value, or to retrieve the type of a scalar or enum. This is useful if you’re writing a function or component that only accepts a scalar or enum, but not a full fragment. > \[!NOTE] > It’s not recommended to use this utiliy to replace fragments, i.e. to > create your own object types. Try to use fragments where appropriate > instead. #### Example ```ts twoslash // @filename: graphq-env.d.ts export type introspection = { __schema: { queryType: { name: 'Query'; }; mutationType: null; subscriptionType: null; types: [ { kind: 'OBJECT'; name: 'Query'; fields: []; interfaces: []; }, { kind: 'ENUM'; name: 'Media'; enumValues: [{ name: 'Book' }, { name: 'Song' }, { name: 'Video' }]; }, ]; directives: []; }; }; import * as gqlTada from 'gql.tada'; declare module 'gql.tada' { interface setupSchema { introspection: introspection; } } // @filename: index.ts // ---cut-before--- import { graphql } from 'gql.tada'; function validateMediaEnum(value: 'Book' | 'Song' | 'Video') { const media = graphql.scalar('Media', value); } type Media = ReturnType>; ``` *** ### `graphql.persisted()` | | Description | | ---------------------------- | --------------------------------------------------------------------------- | | `hash` argument | A hash associated with this query. | | `document` optional argument | Optionally, the document, if it's supposed to be accessible during runtime. | Generates a faux-document containing a property called `documentId` which can be used to send off queries as [Persisted Operations](https://github.com/graphql/graphql-over-http/blob/main/rfcs/PersistedOperations.md). We must either pass the document as a generic type argument or as the second argument: * `graphql.persisted("abc...")` * `graphql.persisted("abc...", document)` The TypeScript plugin and the [`gql-tada check` command](/reference/gql-tada-cli#check) run a diagnostic which can check that the document is passed into `graphql.persisted()` correctly. Furthermore, the TypeScript plugin offers a code action to automatically update the `hash` argument to a SHA256-hash computed from the document. This is useful to implement and extract persisted operations using the CLI. Additionally, when the document is passed as a generic — as long as our GraphQL cache supports this — it can be fully omitted during runtime from the client-side bundle. > \[!WARNING] Client Compatibility > When passing a document by type as a generic to `graphql.persisted("...")`, your runtime code > won’t see any `definitions` on the AST. > > This may cause problems with GraphQL clients (especially normalized caches) that rely on the AST to be available, > since the full document will be transpile away. > For such clients, you may want to preserve the document by passing it as a second argument instead. #### Example ```ts twoslash // @filename: graphq-env.d.ts export type introspection = { __schema: { queryType: { name: 'Query'; }; mutationType: null; subscriptionType: null; types: [ { kind: 'OBJECT'; name: 'Query'; fields: [ { name: 'hello'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; args: []; }, { name: 'world'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; args: []; }, ]; interfaces: []; }, { kind: 'SCALAR'; name: 'String'; }, ]; directives: []; }; }; import * as gqlTada from 'gql.tada'; declare module 'gql.tada' { interface setupSchema { introspection: introspection; } } // @filename: index.ts // ---cut-before--- import { graphql } from 'gql.tada'; const query = graphql(` query Hello { hello } `); // You can now use this in your `useQuery` calls. const persistedOperation = graphql.persisted('sha256:x'); ``` *** ### `readFragment()` | | Description | | ----------------------------- | ------------------------------------------------------------------------ | | `_document` optional argument | A GraphQL document of a fragment, created using [`graphql()`](#graphql). | | `fragment` argument | A mask of the fragment, which can be wrapped in arrays, or nullable. | | returns | The unmasked data of the fragment. | When [`graphql()`](#graphql) is used to create a fragment and is spread into another fragment or query, their result types will only contain a “reference” to the fragment. This encourages isolation and is known as “fragment masking.” This means that you must use `readFragment()` to unmask these fragment masks and get to the data. This encourages isolation and only using the data you define a part of your codebase to require: ```ts const unmaskedData = readFragment(Fragment, maskedData); ``` When passing `fragment` masks to `readFragment()`, you may also pass nullable, optional data, or data wrapped in arrays to `readFragment()` and the result type will be unwrapped and inferred accordingly. Instead of passing the fragment document as the first argument, you may also pass it as a generic, since it's not used as a runtime value anyway: ```ts const unmaskedData = readFragment(maskedData); ``` #### Example ```ts twoslash import './graphql/graphql-env.d.ts'; // ---cut-before--- import { FragmentOf, ResultOf, graphql, readFragment } from 'gql.tada'; const pokemonItemFragment = graphql(` fragment PokemonItem on Pokemon { id name } `); const getPokemonItem = (data: FragmentOf | null) => { // @annotate: Unmasks the fragment and casts to the result type: const pokemon = readFragment(pokemonItemFragment, data); }; const pokemonQuery = graphql( ` query Pokemon($id: ID!) { pokemon(id: $id) { id ...PokemonItem } } `, [pokemonItemFragment] ); const getQuery = (data: ResultOf) => { getPokemonItem(data.pokemon); }; ``` *** ### `initGraphQLTada()` | | Description | | --------------- | --------------------------------------------------------------------- | | `Setup` generic | An [`AbstractSetupSchema` configuration object](#abstractsetupschema) | | returns | A typed [`graphql()`](#graphql) function. | `initGraphQLTada` accepts an [`AbstractSetupSchema` configuration object](#abstractsetupschema) as a generic and returns [a `graphql()` function](#graphql) that may be used to create documents typed using your GraphQL schema. You should use and re-export the resulting function named as `graphql` or `gql` for your editor and the TypeScript language server to recognize your GraphQL documents correctly. #### Example ```ts twoslash import { initGraphQLTada } from 'gql.tada'; import type { introspection } from './graphql/graphql-env.d.ts'; export const graphql = initGraphQLTada<{ introspection: introspection; scalars: { DateTime: string; Json: any; }; }>(); const query = graphql(` { __typename } `); ``` ## Types ### `ResultOf` | | Description | | ------------------ | --------------------------------------------------------------- | | `Document` generic | The document type of a `DocumentNode` carrying the result type. | This accepts a [`TadaDocumentNode`](#tadadocumentnode) and returns the attached `Result` type of GraphQL documents. *** ### `VariablesOf` | | Description | | ------------------ | ------------------------------------------------------------------ | | `Document` generic | The document type of a `DocumentNode` carrying the variables type. | This accepts a [`TadaDocumentNode`](#tadadocumentnode) and returns the attached `Variables` type of GraphQL documents. *** ### `FragmentOf` | | Description | | ------------------ | -------------------------------------------------- | | `Document` generic | A `DocumentNode` containing a fragment definition. | Creates a fragment mask for a given fragment document. When [`graphql()`](#graphql) is used to create a fragment and is spread into another fragment or query, their result types will only contain a “reference” to the fragment. This encourages isolation and is known as “fragment masking.” While [`readFragment()`](#readfragment) is used to unmask these fragment masks, this utility creates a fragment mask, so you can accept the masked data in the part of your codebase that defines a fragment. #### Example ```ts twoslash import './graphql/graphql-env.d.ts'; // ---cut-before--- import { FragmentOf, graphql, readFragment } from 'gql.tada'; const pokemonItemFragment = graphql(` fragment PokemonItem on Pokemon { id name } `); // May be called with any data that contains the mask const getPokemonItem = (data: FragmentOf) => { // Unmasks the fragment and casts to the result type const pokemon = readFragment(pokemonItemFragment, data); }; ``` *** ### `TadaDocumentNode` | | Description | | ------------------- | --------------------------------------------------------------------------- | | `Result` generic | The type of GraphQL results, as returned by GraphQL APIs for a given query. | | `Variables` generic | The type of variables, as accepted by GraphQL APIs for a given query. | A GraphQL `DocumentNode` with attached types for results and variables. This is a GraphQL `DocumentNode` with attached types for results and variables. This is used by GraphQL clients to infer the types of results and variables and provide type-safety in GraphQL documents. You can create typed GraphQL documents using the [`graphql()` function.](#graphql) *** ### `setupSchema` You may extend this interface via declaration merging with your `IntrospectionQuery` data and optionally your scalars to get proper type inference. This is done by declaring a declaration for it as per the following example. Configuring scalars is optional and by default the standard scalrs are already defined. This will configure the default `graphql()` export to infer types from your schema. Alternatively, if you don’t want to define your schema project-wide, you may call [`initGraphQLTada()`](#initgraphqltada) instead. [Read more about setting up your schema on the “Installation” page.](../get-started/installation#step-3-configuring-typings) #### Example ```ts twoslash import type { introspection } from './graphql/graphql-env.d.ts'; declare module 'gql.tada' { interface setupSchema { introspection: introspection; scalars: { DateTime: string; Json: any; }; } } ``` *** ### `AbstractSetupSchema` | | Description | | ------------------------ | ----------------------------------------------------------------------------------------------- | | `introspection` property | Introspection of your schema in the `IntrospectionQuery` format. | | `scalars` property | An optional object type with scalar names as keys and the corresponding scalar types as values. | | `disableMasking` flag | This may be set to `true` to disable fragment masking globally. | This is used either via [`setupSchema`](#setupschema) or [`initGraphQLTada()`](#initgraphqltada) to set up your schema and scalars. Your configuration objects must match the shape of this type. The `scalars` option is optional and can be used to set up custom scalar and enum types. It must be an object map of scalar names to their desired TypeScript types. When a scalar or enum is missing in your custom `scalars` object, a fallback will be used for the built-in scalars (`Int`, `Float`, `String`, `Boolean`, and `ID`) and for enums, the `enumValues` defined by the schema will be used. The `disableMasking` flag may be set to `true` instead of using `@_unmask` on individual fragments and allows fragment masking to be disabled globally. ## Testing Functions These functions are all exported from `gql.tada/testing`, and are meant for tests, stories, fixtures, or cache updaters, rather than your regular app code. ### `maskFragments()` | | Description | | --------------------- | -------------------------------------------------------------------------------- | | `_fragments` argument | A list of GraphQL documents of fragments, created using [`graphql()`](#graphql). | | `data` argument | The combined result data of the fragments, which can be wrapped in arrays. | | returns | The masked data of the fragments. | > \[!NOTE] > While useful, `maskFragments()` is mostly meant to be used in tests or as > an escape hatch to convert data to masked fragments. > > You shouldn’t have to use it in your regular component code. When [`graphql()`](#graphql) is used to compose fragments into another fragment or operation, the resulting type will by default be masked, [unless the `@_unmask` directive is used.](../guides/fragment-colocation#fragment-masking) This means that when we’re writing tests or are creating “fake data” without inferring types from a full document, the types in TypeScript may not match, since our testing data will not be masked and will be equal to [the result type](#resultof) of the fragments. To address this, the `maskFragments` utility takes a list of fragments and masks data (or an array of data) to match the masked fragment types of the fragments. * [Read more about fragment masking on the “Writing GraphQL” page.](../get-started/writing-graphql#fragment-masking) * [For the reverse operation, see `readFragment()`.](#readfragment) #### Example ```ts twoslash import './graphql/graphql-env.d.ts'; // ---cut-before--- import { graphql, maskFragments } from 'gql.tada'; const pokemonItemFragment = graphql(` fragment PokemonItem on Pokemon { id name } `); const data = maskFragments([pokemonItemFragment], { id: '001', name: 'Bulbasaur', }); ``` *** ### `unsafe_readResult()` | | Description | | -------------------- | -------------------------------------------------------------------- | | `_document` argument | A GraphQL document, created using [`graphql()`](#graphql). | | `data` argument | The result data of the GraphQL document with optional fragment refs. | | returns | The masked result data of the document. | > \[!CAUTION] > Unlike, [`maskFragments()`](#maskfragments), this utility is unsafe, and > should only be used when you know that data matches the expected shape > of a GraphQL query you created. > > While useful, this utility is only a slightly safer alternative to `as any` > and doesn’t type check the result shape against the masked fragments in your > document. > > You shouldn’t have to use it in your regular app code. When [`graphql()`](#graphql) is used to compose fragments into a document, the resulting type will by default be masked, [unless the `@_unmask` directive is used.](../guides/fragment-colocation#fragment-masking) This means that when we’re writing tests and are creating “fake data”, for instance for a query, that we cannot convert this data to the query’s result type, if it contains masked fragment refs. To address this, the `unsafe_readResult` utility accepts the document and converts a query’s data to masked data. #### Example ```ts twoslash import './graphql/graphql-env.d.ts'; // ---cut-before--- import { graphql, unsafe_readResult } from 'gql.tada'; const pokemonItemFragment = graphql(` fragment PokemonItem on Pokemon { id name } `); const query = graphql( ` query { pokemon(id: "001") { ...PokemonItem } } `, [pokemonItemFragment] ); // @warn: data will be cast (unsafely!) to the result type const data = unsafe_readResult(query, { pokemon: { id: '001', name: 'Bulbasaur' }, }); ``` *** ### `readResult()` | | Description | | -------------------- | ------------------------------------------------------------------ | | `document` argument | A GraphQL document, created using [`graphql()`](#graphql). | | `data` argument | The result data of the document, with fragment data inlined. | | `fragments` argument | A list of every fragment used in the document, transitively. | | returns | The result data, typed as the document’s [result type](#resultof). | When [`graphql()`](#graphql) composes fragments into a document, the result type only contains opaque references to those fragments, rather than the fragments’ fields. This makes it hard to assemble a full result as “fake data”. Unlike [`unsafe_readResult()`](#unsafe_readresult), which discards all fragment references and doesn’t type check the data nested inside them, `readResult()` is type-safe. You pass it the fragments used in the document, and it recursively resolves their references, so the data you write is fully type checked — including data for nested fragments and fragments that themselves spread other fragments. Pass every fragment you’d like to inline — including fragments spread by other fragments — to the `fragments` argument. Any fragment you leave out stays masked in the expected data, merged into its surrounding object as a fragment reference. This keeps the result’s shape intact and makes it easy to spot which fragments are still missing, and lets you fill them with [`maskFragments()`](#maskfragments) instead. * [For masking a single level of fragment data, see `maskFragments()`.](#maskfragments) * [For the unsafe variant that performs no checks, see `unsafe_readResult()`.](#unsafe_readresult) #### Example ```ts twoslash import './graphql/graphql-env.d.ts'; // ---cut-before--- import { graphql } from 'gql.tada'; import { readResult } from 'gql.tada/testing'; const pokemonNameFragment = graphql(` fragment PokemonName on Pokemon { name } `); const pokemonItemFragment = graphql( ` fragment PokemonItem on Pokemon { id ...PokemonName } `, [pokemonNameFragment] ); const query = graphql( ` query { pokemon(id: "001") { ...PokemonItem } } `, [pokemonItemFragment] ); // @log: data is fully type-checked, including the nested fragment fields const data = readResult( query, { pokemon: { id: '001', name: 'Bulbasaur' } }, [pokemonItemFragment, pokemonNameFragment] ); ``` --- --- url: /reference/gql-tada-cli.md --- # `gql-tada` CLI ## Commands ### `init` > \[!NOTE] > > The `gql-tada init` command is still a work in progress. > If you run into any trouble, feel free to let us know what you’d like to see added or changed. | Option | Description | | ------ | ------------------------------------------------------------------------------------------------ | | `dir` | A relative location from your current working directory where the project should be initialized. | The `init` command takes care of everything required to setup a `gql-tada` project. The main tasks involved here are: * Locating the schema * Locating where `gql.tada`’s `graphql-env.d.ts` shall be placed * Configuring the `tsconfig.json` * Installing required dependencies You can run this command with your preferred package manager: ::: code-group ```sh [npm] npx gql-tada init ./my-project ``` ```sh [pnpm] pnpx gql-tada init ./my-project ``` ```sh [bun] bunx gql-tada init ./my-project ``` ::: ### `doctor` > \[!NOTE] > > The `gql-tada doctor` command is still a work in progress. > If you run into any trouble, feel free to let us know what you’d like to see added or changed. The `doctor` command will check for common mistakes in the `gql-tada`’s setup and configuration. It will check installed versions of packages, check the configuration, and check the schema. ### `check` | Option | Description | | ------------------- | ------------------------------------------------------------------------------------------------- | | `--tsconfig,-c` | Optionally, a `tsconfig.json` file to use instead of an automatically discovered one. | | `--fail-on-warn,-w` | Triggers an error and a non-zero exit code if any warnings have been reported (default: `false`). | | `--level,-l` | The minimum severity of diagnostics to display: `info`, `warn` or `error` (default: `info`). | Usually, the TypeScript plugin will run inside your editor's TypeScript language server process and will report warnings and errors. However, these diagnostics aren't run when `tsc` or other TypeScript compiler processes are used, since those neither load plugins nor are part of the TypeScript language service API. The `gql-tada check` command exists to run these diagnostics in a standalone command, outside of editing the relevant files and reports these errors to the console. When this command is run inside a GitHub Action, [workflow commands](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions) are used to annotate errors within the GitHub UI. ### `generate-schema` | Option | Description | | --------------- | ---------------------------------------------------------------------------------------------------- | | `schema` | URL to a GraphQL API or a path to a `.graphql` SDL file or introspection JSON. | | `--tsconfig,-c` | Optionally, a `tsconfig.json` file to use instead of an automatically discovered one. | | `--output,-o` | An output location to write the `.graphql` SDL file to. (Default: The `schema` configuration option) | | `--header,` | A `key:value` header entry to use when retrieving the introspection from a GraphQL API. | Oftentimes, an API may not be running in development, is maintained in a separate repository, or requires authorization headers, and specifying a URL in the `schema` configuration can slow down development. The `gql-tada generate-schema` command introspects a targeted GraphQL API by URL, a `.graphql` SDL or introspection JSON file, and outputs a `.graphql` SDL file. Generating a `.graphql` SDL file is useful if we're trying to avoid adding a URL as the `schema` configuration option. The SDL file will be written to the location specified by the `schema` configuration option, which can be overridden using the `--output` argument. ### `generate-output` | Option | Description | | ------------------------- | --------------------------------------------------------------------------------------------- | | `--disable-preprocessing` | Whether to use the less efficient `.d.ts` introspection format. (Default: false) | | `--tsconfig,-c` | Optionally, a `tsconfig.json` file to use instead of an automatically discovered one. | | `--output,-o` | Specify where to output the file to. (Default: The `tadaOutputLocation` configuration option) | The `gql-tada generate-output` command programmatically outputs the `gql.tada` output typings file. It will load the schema from the specified `schema` configuration option first then write the typings file to the specified location. The output file will be written to the location specified by the `tadaOutputLocation` configuration option, which can be overridden using the `--output` argument. ### `turbo` | Option | Description | | ------------------- | -------------------------------------------------------------------------------------------- | | `--tsconfig,-c` | Optionally, a `tsconfig.json` file to use instead of an automatically discovered one. | | `--fail-on-warn,-w` | Triggers an error and a non-zero exit code if any warnings have been reported. | | `--output,-o` | Specify where to output the file to. (Default: The `tadaTurboLocation` configuration option) | The `turbo` command generates a cache for all GraphQL document types ahead of time. This cache speeds up type evaluation and is especially useful when it's checked into the repository after making changes to GraphQL documents, which speeds up all further type checks and evaluation. The cache is a snapshot of all current `gql.tada` types. As you edit GraphQL documents, `gql.tada` will still infer types dynamically until a new cache file is generated. The cache file will be written to the location specified by the `tadaTurboLocation` configuration option, which can be overridden using the `--output` argument. When this command is run inside a GitHub Action, [workflow commands](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions) are used to annotate errors within the GitHub UI. ### `generate-persisted` | Option | Description | | ------------------- | ------------------------------------------------------------------------------------------------ | | `--disable-normalization` | Whether to disable normalizing the GraphQL document. (Default: false) | | `--tsconfig,-c` | Optionally, a `tsconfig.json` file to use instead of an automatically discovered one. | | `--fail-on-warn,-w` | Triggers an error and a non-zero exit code if any warnings have been reported. | | `--output,-o` | Specify where to output the file to. (Default: The `tadaPersistedLocation` configuration option) | The `gql-tada generate-persisted` command will scan your code for `graphql.persisted()` calls and generate a JSON manifest file containing a mapping of document IDs to the GraphQL document strings. These can then be used to register known and accepted documents (known as “persisted operations”) with your GraphQL API to lock down accepted documents that are allowed to be sent. The manifest file will be written to the location specified by the `tadaPersistedLocation` configuration option, which can be overridden using the `--output` argument. When this command is run inside a GitHub Action, [workflow commands](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions) are used to annotate errors within the GitHub UI. ### `scan` > \[!NOTE] > > The `gql-tada scan` command is experimental, and its rules and output may still change. > If you run into any trouble or have ideas for insights you’d like to see, feel free to let us know. | Option | Description | | ------------------- | -------------------------------------------------------------------------------------- | | `--tsconfig,-c` | Optionally, a `tsconfig.json` file to use instead of an automatically discovered one. | | `--format,-f` | Emit the machine-readable `json` report instead of the terminal report. | | `--graph` | Emit only the relationship graph as JSON. Implies machine output. | | `--output,-o` | Specify where to write machine output to. (Default: standard output) | | `--fail-on-warn,-w` | Triggers an error and a non-zero exit code if any warnings have been reported. | The `gql-tada scan` command analyzes all GraphQL documents and fragments across your project and keys every field selection back to the schema, producing project-level insights into how your schema is used. By default, it prints a human-readable report to the terminal. Each insight is produced by a rule, covering field usage and reach, input (enum value and input-object field) usage, deprecated-field usage, orphan and cross-feature fragments, operation complexity, fetch depth, and directive usage. Passing `--format json` writes a machine-readable report — a project overview, operation and fragment identities, and every rule’s datapoints — to standard output, or to the file given by `--output`. Passing `--graph` instead emits the module ↔ document ↔ fragment ↔ schema relationship graph on its own, for feeding into other tooling or visualizations. When this command is run inside a GitHub Action, [workflow commands](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions) are used to annotate warnings within the GitHub UI. ## Functions The CLI is packaged as a module that `gql.tada` depends on published as `@gql.tada/cli-utils`. If you're looking to generate the file that the CLI generates in your own scripts, you can use the functions it exports directly. ### `generateOutput()` | | Description | | ----------------------------- | ------------------------------------------------------------------------------------------------- | | `output` option | The filename to write the output file to (Default: the `tadaOutputLocation` configuration option) | | `tsconfig` option | The `tsconfig.json` to use instead of an automatically discovered one. | | `disablePreprocessing` option | Whether to disable the optimized output format for `.d.ts` files. | | `silent` option | Whether to disable terminal output when using the programmatic API. | | returns | A `Promise` that resolves when the task completes. | The `generateOutput()` function outputs the `gql.tada` output file manually. It will load the schema from the specified `schema` configuration option and write the output file. The output file will be written to the location specified by the `tadaOutputLocation` configuration option, which can be overridden using the `output` option. ```ts twoslash import { generateOutput } from '@gql.tada/cli-utils'; await generateOutput({ output: './src/graphql-env.d.ts', disablePreprocessing: false, tsconfig: undefined, silent: false, }); ``` *** ### `generatePersisted()` | | Description | | ------------------- | --------------------------------------------------------------------------------------------------------------------- | | `disableNormalization` | Disables normalizing the GraphQL document | | `output` option | The filename to write the persisted JSON manifest file to (Default: the `tadaPersistedLocation` configuration option) | | `tsconfig` option | The `tsconfig.json` to use instead of an automatically discovered one. | | `failOnWarn` option | Whether to throw an error instead of logging warnings. | | returns | A `Promise` that resolves when the task completes. | The `generatePersisted()` function will scan your code for `graphql.persisted()` calls and generate a JSON manifest file containing a mapping of document IDs to the GraphQL document strings. These can then be used to register known and accepted documents (known as “persisted operations”) with your GraphQL API to lock down accepted documents that are allowed to be sent. The manifest file will be written to the location specified by the `tadaPersistedLocation` configuration option, which can be overridden using the `output` option. ```ts twoslash import { generatePersisted } from '@gql.tada/cli-utils'; await generatePersisted({ output: './persisted.json', failOnWarn: false, tsconfig: undefined, }); ``` *** ### `generateSchema()` | | Description | | ----------------- | ------------------------------------------------------------------------------------------------------ | | `input` option | The filename to a `.graphql` SDL file, introspection JSON, or URL to a GraphQL API to introspect. | | `headers` option | Optionally, an object of headers to send when introspecting a GraphQL API. | | `output` option | The filename to write the persisted JSON manifest file to (Default: the `schema` configuration option) | | `tsconfig` option | The `tsconfig.json` to use instead of an automatically discovered one. | | `silent` option | Whether to disable terminal output when using the programmatic API. | | returns | A `Promise` that resolves when the task completes. | The `generateSchema()` function introspects a targeted GraphQL API by URL, a `.graphql` SDL or introspection JSON file, and outputs a `.graphql` SDL file. Generating a `.graphql` SDL file is useful if we're trying to avoid adding a URL as the `schema` configuration option. The SDL file will be written to the location specified by the `schema` configuration option, which can be overridden using the `output` option. ```ts twoslash import { generateSchema } from '@gql.tada/cli-utils'; await generateSchema({ input: 'https://trygql.formidable.dev/graphql/basic-pokedex', output: './schema.graphql', headers: undefined, tsconfig: undefined, silent: false, }); ``` *** ### `generateTurbo()` | | Description | | ------------------- | ----------------------------------------------------------------------------------------------- | | `output` option | The filename to write the cache file to (Default: the `tadaTurboLocation` configuration option) | | `tsconfig` option | The `tsconfig.json` to use instead of an automatically discovered one. | | `failOnWarn` option | Whether to throw an error instead of logging warnings. | | returns | A `Promise` that resolves when the task completes. | The `generateTurbo()` function generates a cache for all GraphQL document types ahead of time. This cache speeds up type evaluation and is especially useful when it's checked into the repository after making changes to GraphQL documents, which speeds up all further type checks and evaluation. The cache is a snapshot of all current `gql.tada` types. As you edit GraphQL documents, `gql.tada` will still infer types dynamically until a new cache file is generated. The cache file will be written to the location specified by the `tadaTurboLocation` configuration option, which can be overridden using the `output` option. ```ts twoslash import { generateTurbo } from '@gql.tada/cli-utils'; await generateTurbo({ output: './src/graphql-cache.d.ts', failOnWarn: false, tsconfig: undefined, }); ``` --- --- url: /reference/config-format.md --- # Configuration Format Both `gql.tada`'s [CLI](/reference/gql-tada-cli) as well as the TypeScript plugin are configured using an entry in your `tsconfig.json` file. Their configurations are part of the TypeScript plugin entry: ::: code-group ```json [tsconfig.json] {4-10} { "compilerOptions": { "strict": true, "plugins": [ { "name": "gql.tada/ts-plugin", "schema": "./schema.graphql", "tadaOutputLocation": "./src/graphql-env.d.ts" } ] } } ``` ::: The section marked containing `schema` is what you can populate with [**Schema Options**](#schema-options), as described in the next section. This will set up a default schema for `gql.tada` to use and the only required options are [`schema`](#schema) and [`tadaOutputLocation`](#tadaoutputlocation). If you have multiple schemas you'd like to use with `gql.tada`, then you'll instead want to create a `schemas` array. ::: code-group ```json [tsconfig.json] {7-12} { "compilerOptions": { "strict": true, "plugins": [ { "name": "gql.tada/ts-plugin", "schemas": [ { "name": "your-schema-1", "schema": "./schema-1.graphql", "tadaOutputLocation": "./src/graphql-env-1.d.ts" }, { "name": "your-schema-2", "schema": "./schema-2.graphql", "tadaOutputLocation": "./src/graphql-env-2.d.ts" } ] } ] } } ``` ::: The `name` property in each `schemas[]` entry is arbitrary. It's important that you give each of your schemas a name here, but this can be any name you want and is only used to identify the schema internally and to you in error messages. ::: info Optional Schema Options Don't worry about setting up more than the required `schema` and `tadaOutputLocation` configuration option. All optional schema options are mostly used by the `gql.tada` CLI, which will tell you if you're missing any of the extra configuration options. Additionally, to validate your configuration, you can always run the [`gql-tada doctor` CLI command](/reference/gql-tada-cli#doctor). ::: ## Schema Options This section documents all of the schema-specific configuration options. These options are specific to a single schema and are added either under the main plugin config or inside the `schemas[]` array items. ### `schema` The `schema` option specifies how to load your GraphQL schema and currently allows for three different schema formats. It accepts either: * a path to a `.graphql` file containing a schema definition (in GraphQL SDL format) * a path to a `.json` file containing a schema’s introspection query data * a URL to a GraphQL API that can be introspected ::: code-group ```json [.graphql file] {6} { "compilerOptions": { "plugins": [ { "name": "gql.tada/ts-plugin", "schema": "./schema.graphql" } ] } } ``` ```json [.json file] {6} { "compilerOptions": { "plugins": [ { "name": "gql.tada/ts-plugin", "schema": "./introspection.json" } ] } } ``` ```json [URL] {6} { "compilerOptions": { "plugins": [ { "name": "gql.tada/ts-plugin", "schema": "http://localhost:4321/graphql" } ] } } ``` ```json [URL with headers] {6-11} { "compilerOptions": { "plugins": [ { "name": "gql.tada/ts-plugin", "schema": { "url": "http://localhost:4321/graphql", "headers": { "Accept": "application/graphql-response+json" } } } ] } } ``` ::: Since this option defines which GraphQL schema is used, it's required and both the CLI and the TypeScript plugin will not function without it. *** ### `tadaOutputLocation` The `tadaOutputLocation` specifies the output path to write a typings output file to, which `gql.tada` uses to infer GraphQL types within the TypeScript type system. The `tadaOutputLocation` option supports two different formats dependent on the file path you pass: the `.d.ts` format, and the `.ts` format. Depending on the file path's extension, either of these output formats are used. When the option only specifies a directory, a `introspection.d.ts` file will automatically be written to the output directory. #### Format 1 — `.d.ts` file The `.d.ts` output format is only a declaration file, which will also contain a declaration that automatically declares [a `setupSchema` interface on `gql.tada`](./gql-tada-api#setupschema). When this format is used, [declaration merging in TypeScript](https://www.typescriptlang.org/docs/handbook/declaration-merging.html), kicks in, which means that - without any additional configuration - we can then start importing `graphql()` from `gql.tada` and use it. The resulting file will have the following shape: ::: code-group ```ts [graphql-env.d.ts] export type introspection = { __schema: { /*...*/ }; }; import * as gqlTada from 'gql.tada'; declare module 'gql.tada' { interface setupSchema { introspection: introspection; } } ``` ::: If we want to now customize `gql.tada`, for instance to set up our scalar types, we’ll need to create our own `graphql()` function by importing the output typings manually and passing it to [`gql.tada`’s `initGraphQLTada<>()` function](./gql-tada-api#initgraphqltada): ::: code-group ```ts [graphql.ts] import { initGraphQLTada } from 'gql.tada'; import type { introspection } from './graphql-env.d.ts'; export const graphql = initGraphQLTada<{ introspection: introspection; scalars: { DateTime: string; JSON: any; }; }>(); ``` ::: Since this is just a declaration file, the easiest way to indicate this in our code is to use a `import type` statement and to refer to the file using its full file extension. #### Format 2 — `.ts` file > \[!WARNING] A note on performance > > We strongly recommend you to use the `.d.ts` format instead. While it's less reusable, the format will > be more efficient and increase TypeScript inference performance. When writing a `.ts` file instead, a regular TypeScript file will be created exporting an `introspection` object. This format is supported because, while this object may cause a large increase in bundlesize, occasionally other tools may also depend on the raw GraphQL introspection output during runtime. The resulting file will have the following shape: ::: code-group ```ts [introspection.ts] const introspection = { __schema: { /*...*/ }, } as const; export { introspection }; ``` ::: Because this file doesn't include a `declare module` typings directive, with this format we're always required to set up `gql.tada` manually using the [`initGraphQLTada<>()` function](./gql-tada-api#initgraphqltada). *** ### `tadaTurboLocation` The `tadaOutputLocation` specifies the output path that the [`gql-tada turbo`](/reference/gql-tada-cli#turbo) command will write the type cache output file to. Type cache files are `.d.ts` files that cache `gql.tada`'s inferred types This means that when you run `gql-tada turbo` after making your changes, TypeScript will be able to start up and type check your GraphQL documents much more quickly than without the type cache. *** ### `tadaPersistedLocation` The `tadaPersistedLocation` specifies the output path that the [`gql-tada generate persisted` command](/reference/gql-tada-cli#generate-persisted) will write the persisted JSON manifest file to. Persisted manifest files are `.json` files that contain all GraphQL documents referenced using a [`graphql.persisted` call](/reference/gql-tada-api#graphql-persisted). This is useful to implement persisted operations, as all documents will be extracted into the manifest file at compile-time. *** ## Global Options This section documents all of the plugin-wide configuration options. These options aren't specific to a single schema and configure both global features for the `gql.tada` CLI and the TypeScript plugin. ### `trackFieldUsage` ::: code-group ```json [tsconfig.json] { "compilerOptions": { "plugins": [ { "name": "gql.tada/ts-plugin", "schema": "./schema.graphql", "tadaOutputLocation": "./src/graphql-env.d.ts", "trackFieldUsage": true // [!code ++] } ] } } ``` ::: By default, this option is enabled. When enabled, your usage of fields will be tracked as you consume data typed using a GraphQL document. The TypeScript plugin and the [`gql-tada check` command](/reference/gql-tada-cli#check) will run a diagnostic that issues warnings when any fields in your selection sets aren't used in your TypeScript code. ```tsx twoslash {8} import './graphql/graphql-env.d.ts'; // ---cut-before--- import { FragmentOf, graphql, readFragment } from 'gql.tada'; // @warn: GraphQLSP: Field 'maxHP is not used. export const PokemonItemFragment = graphql(` fragment PokemonItem on Pokemon { id name maxHP } `); interface Props { data: FragmentOf; } export const PokemonItem = ({ data }: Props) => { const pokemon = readFragment(PokemonItemFragment, data); return
  • ; }; ``` In the above example, we add a `maxHP` field to a fragment that the component’s code does not actually access, which causes a warning to be displayed. ::: info When should `trackFieldUsage` be disabled? Usage of any fields is based on heuristics. As such, depending on your coding patterns this warning can sometimes be triggered erroneously and support of more coding patterns is still being expanded. If you see any false-positive warnings, feel free to disable `trackFieldUsage` or report the problematic code pattern to us in an issue. ::: *** ### `shouldCheckForColocatedFragments` ::: code-group ```json [tsconfig.json] { "compilerOptions": { "plugins": [ { "name": "gql.tada/ts-plugin", "schema": "./schema.graphql", "tadaOutputLocation": "./src/graphql-env.d.ts", "shouldCheckForColocatedFragments": true // [!code ++] } ] } } ``` ::: By default, this option is enabled. When enabled, your imports will be scanned for exported fragments. The TypeScript plugin and the [`gql-tada check` command](/reference/gql-tada-cli#check) will issue warnings when you're missing imports to a GraphQL fragment exported by will run a diagnostic that issues warnings when any imports statements don't import a GraphQL fragment exported by another module. This is important to help with [fragment co-location](/guides/fragment-colocation) as many component modules may export fragments that you should be importing and use in the importer's GraphQL documents. ```tsx twoslash {4} import './graphql/graphql-env.d.ts'; // ---cut-before--- // @filename: ./src/PokemonItem.tsx export const PokemonItem = () => null; // @filename: ./src/PokemonsList.tsx // ---cut--- import { useQuery } from 'urql'; import { graphql } from 'gql.tada'; // @warn: GraphQLSP: Unused co-located fragment definition(s) import { PokemonItem } from './PokemonItem'; const PokemonsQuery = graphql(` query Pokemons($limit: Int = 10) { pokemons(limit: $limit) { id name } } `, []); export const PokemonList = () => { const [result] = useQuery({ query: PokemonsQuery }); return null; // ... }; ``` In the above example, we add an import to a `PokemonItem` component. If the file contains a fragment that we have to use in our query a warning is displayed. ::: info When should `shouldCheckForColocatedFragments` be disabled? This warning is context-sensitive. It can be very helpful when you're following our recommended [fragment co-location patterns](/guides/fragment-colocation). However, if you're not using co-located fragments, or if you have many "mixed" files that contain both components with fragments and other code, this warning can get confusing and annoying. We recommend you to disable this check if you know that you're not going to follow [fragment co-location](/guides/fragment-colocation). :::