Skip to content
Graffle is a work in progress. Learn more.

Static

This example demonstrates the Static Document Builder. * Generate typed GraphQL documents at compile-time! No client needed (•̀ᴗ•́)و

ts
// Our website uses Vitepress+Twoslash. Twoslash does not discover the generated Graffle modules.
// Perhaps we can configure Twoslash to include them. Until we figure that out, we have to
// explicitly import them like this.
import './graffle/modules/global.js'
// ---cut---

import { $, Graffle as PlainGraffle } from 'graffle'
//                                ^
// Variable marker with flexible API
import { Graffle } from './graffle/$.js'

/*

1. Basic Selection
------------------------------------------------------------------------

query {
  pokemons {
    name
    hp
    trainer {
      name
    }
  }
}

*/

const doc1 = Graffle.query.pokemons({
  name: true,
  hp: true,
  trainer: { name: true },
})

console.log(doc1)

/*

2. Variables with $var
------------------------------------------------------------------------

query ($pokemonName: String!) {
  pokemonByName(name: $pokemonName) {
    name
    type
  }
}

Variables type: { pokemonName: string }

*/

const doc2 = Graffle.query.pokemonByName({
  $: { name: $('pokemonName').required() },
  //         ^^^^^^^^^^^^^^^^^^^^^^^^^
  // Named variable with required modifier
  name: true,
  type: true,
})

console.log(doc2)

/*

3. Filter Arguments
------------------------------------------------------------------------

query ($in: [String!]!) {
  pokemons(filter: { name: { in: $in } }) {
    name
    hp
  }
}

Variables type: { in: string[] }

*/

const doc3 = Graffle.query.pokemons({
  $: {
    filter: {
      name: { in: $.required() },
      //          ^^^^^^^^^^^^
      //          Required variable for name filter
    },
  },
  name: true,
  hp: true,
})

console.log(doc3)

/*

4. Variables with Default Values
------------------------------------------------------------------------

This example console.logs how to use default values for variables.

query ($trainerName: String = "Ash") {
  trainerByName(name: $trainerName) {
    name
    class
    pokemon {
      name
      type
    }
  }
}

*/

const doc4 = Graffle.query.trainerByName({
  $: {
    name: $.default('Ash'),
    //     ^^^^^^^^^^^^^^^^
    //     Variable with default value
  },
  name: true,
  class: true,
  pokemon: {
    name: true,
    type: true,
  },
})

console.log(doc4)

/*

5. Controlling Optional/Required Variables
------------------------------------------------------------------------

mutation ($name: String = "Pikachu", $attack: Int!, $defense: Int!, $hp: Int!, $type: PokemonType!) {
  addPokemon(name: $name, attack: $attack, defense: $defense, hp: $hp, type: $type) {
    name
    type
  }
}

*/

const doc5 = Graffle.mutation.addPokemon({
  $: {
    name: $.default('Pikachu'),
    // Make required field optional with default value
    attack: $.required(),
    defense: $.required(),
    hp: $.required(),
    // Make optional fields required
    $type: $.required(),
  },
  name: true,
  type: true,
})

console.log(doc5)

/*

6. Full Document with Multiple Operations
------------------------------------------------------------------------

Build a complete document with multiple named queries and send them:

*/

const multiOpDoc = Graffle.gql({
  query: {
    // First operation: get all pokemons
    allPokemons: {
      pokemons: {
        name: true,
        hp: true,
      },
    },
    // Second operation: get specific pokemon by name
    specificPokemon: {
      pokemonByName: {
        $: { name: $('pokemonName').required() },
        name: true,
        type: true,
        hp: true,
      },
    },
  },
})

console.log(multiOpDoc)

const client = Graffle.create()

// Execute first operation (no variables needed)
const allPokemonsResult = await client.gql(multiOpDoc).allPokemons()
console.log(allPokemonsResult)

// Execute second operation (variables required)
const specificResult = await client.gql(multiOpDoc).specificPokemon({ pokemonName: 'Pikachu' })
console.log(specificResult)

/*

7. Single Operation Document (no operation name needed)
------------------------------------------------------------------------

When document has only one operation, no operation name needed:

*/

const singleOpDoc = Graffle.gql({
  query: {
    getTrainers: {
      trainers: {
        name: true,
        pokemon: { name: true },
      },
    },
  },
})

console.log(singleOpDoc)

// Send without operation name since there's only one
const trainers = await client.gql(singleOpDoc).$send()
console.log(trainers)

/*

8. Mixed Query and Mutation Document
------------------------------------------------------------------------

Combine queries and mutations in a single document:

*/

const mixedDoc = Graffle.gql({
  query: {
    getPokemon: {
      pokemonByName: {
        $: { name: $('name').required() },
        name: true,
        hp: true,
        attack: true,
      },
    },
  },
  mutation: {
    addNewPokemon: {
      addPokemon: {
        $: {
          name: $.required(),
          $type: $.required(),
          hp: $.required(),
          attack: $.required(),
          defense: $.required(),
        },
        name: true,
        type: true,
      },
    },
  },
})

console.log(mixedDoc)

// Execute query operation
const pokemon = await client.gql(mixedDoc).getPokemon({ name: 'Charizard' })
console.log(pokemon)

// Execute mutation operation
const newPokemon = await client.gql(mixedDoc).addNewPokemon({
  name: 'Mew',
  type: 'electric',
  hp: 100,
  attack: 100,
  defense: 100,
})
console.log(newPokemon)

/*

9. SDDM Type Safety
------------------------------------------------------------------------

SDDM (Schema-Driven Data Map) enables type-safe custom scalar handling.
Documents generated with SDDM require SDDM-enabled clients.

*/

const doc = Graffle.query.pokemons({
  name: true,
  birthday: true, // Custom Date scalar - requires SDDM for encoding/decoding
})

console.log(doc)

const plainClient = PlainGraffle
  .create()
  .transport({ url: 'https://example.com/graphql' })

// @ts-expect-error - plain client lacks SDDM support
plainClient.gql(doc)

Outputs

txt
{
  pokemons {
    name
    hp
    trainer {
      name
    }
  }
}
txt
query ($pokemonName: String!) {
  pokemonByName(name: $pokemonName) {
    name
    type
  }
}
txt
query ($filter: PokemonFilter) {
  pokemons(filter: $filter) {
    name
    hp
  }
}
txt
query ($name: String! = "Ash") {
  trainerByName(name: $name) {
    name
    class
    pokemon {
      name
      type
    }
  }
}
txt
mutation ($name: String! = "Pikachu", $attack: Int, $defense: Int, $hp: Int, $type: PokemonType!) {
  addPokemon(
    name: $name
    attack: $attack
    defense: $defense
    hp: $hp
    type: $type
  ) {
    name
    type
  }
}
txt
query allPokemons {
  pokemons {
    name
    hp
  }
}

query specificPokemon($pokemonName: String!) {
  pokemonByName(name: $pokemonName) {
    name
    type
    hp
  }
}
txt
{
  pokemons: [
    { name: 'Pikachu', hp: 35 },
    { name: 'Charizard', hp: 78 },
    { name: 'Squirtle', hp: 44 },
    { name: 'Bulbasaur', hp: 45 },
    { name: 'Caterpie', hp: 45 },
    { name: 'Weedle', hp: 40 }
  ]
}
txt
{ pokemonByName: [ { name: 'Pikachu', type: 'electric', hp: 35 } ] }
txt
query getTrainers {
  trainers {
    name
    pokemon {
      name
    }
  }
}
txt
{
  trainers: [
    {
      name: 'Ash',
      pokemon: [ { name: 'Pikachu' }, { name: 'Charizard' } ]
    },
    { name: 'Misty', pokemon: [ { name: 'Squirtle' } ] },
    { name: 'Brock', pokemon: [] },
    { name: 'Gary', pokemon: [] }
  ]
}
txt
query getPokemon($name: String!) {
  pokemonByName(name: $name) {
    name
    hp
    attack
  }
}

mutation addNewPokemon($name: String!, $type: PokemonType!, $hp: Int, $attack: Int, $defense: Int) {
  addPokemon(
    name: $name
    type: $type
    hp: $hp
    attack: $attack
    defense: $defense
  ) {
    name
    type
  }
}
txt
{ pokemonByName: [ { name: 'Charizard', hp: 78, attack: 84 } ] }
txt
{ addPokemon: { name: 'Mew', type: 'electric' } }
txt
{
  pokemons {
    name
    birthday
  }
}

Released under the MIT License.