Static
Examples -> Alias Arguments Batch Directive Document Domains Interface Root Field Root Level Builders Static Union
Overview
The static document builder lets you build GraphQL documents statically without needing a client instance. It supports two syntaxes:
- String - GraphQL syntax with type inference
- Object - TypeScript objects with type-safe selection sets
import { Graffle } from 'graffle'
// String syntax
const doc1 = Graffle.gql(`query { pokemons { name hp } }`)
// Object syntax
const doc2 = Graffle.gql({
query: {
getPokemons: {
pokemons: { name: true, hp: true },
},
},
})
Once built, documents can be sent using the Instance API.
Root-Level Builders
For simpler queries and mutations, Graffle provides dedicated root-level builders that let you skip the operation wrapper:
Single Field Operations
Build queries and mutations for individual fields directly:
import { mutation, query } from 'graffle'
// Query a single field
const getUserDoc = query.user({ id: true, name: true })
// Generates: { user { id name } }
// Mutate a single field
const createUserDoc = mutation.createUser({ id: true })
// Generates: mutation { createUser { id } }
Multi-Field Operations with $batch
Use $batch
to select multiple root fields in a single operation:
import { mutation, query } from 'graffle'
// Multiple queries
const batchQueryDoc = query.$batch({
user: { id: true, name: true },
posts: { id: true, title: true },
comments: { id: true, text: true },
})
// Generates:
// {
// user { id name }
// posts { id title }
// comments { id text }
// }
// Multiple mutations
const batchMutationDoc = mutation.$batch({
createUser: { id: true },
updatePost: { success: true },
deleteComment: { success: true },
})
// Generates:
// mutation {
// createUser { id }
// updatePost { success }
// deleteComment { success }
// }
When to Use Root-Level Builders
Use query
and mutation
for:
- Quick, focused operations on single or multiple fields
- Simple cases without complex nesting or variables
- Schema-less workflows where you don't need generation
Use Graffle.gql()
for:
- Complex documents with variables and arguments
- Multiple operations with different names
- Full control over the document structure
Fields
Fields control which data appears in your GraphQL document using boolean values:
const doc = Graffle.gql({
query: {
getPokemons: {
pokemons: {
name: true,
hp: true,
},
},
},
})
How it works:
true
- Adds the field to the documentfalse
- Removes the field from the document (useful with type spread patterns)- Nested objects - Select fields on related types
const doc = Graffle.gql({
query: {
getPokemons: {
pokemons: {
name: true,
trainer: {
name: true,
class: true,
},
},
},
},
})
Multiple Operations
const doc = Graffle.gql({
query: {
getPokemon: {
pokemonByName: {
$: { name: $('name').required() },
name: true,
hp: true,
},
},
},
mutation: {
addPokemon: {
addPokemon: {
$: {
name: $.required(),
type: $.required(),
},
name: true,
},
},
},
})
Aliases
Aliases let you rename fields in your response or request the same field multiple times with different arguments.
Full Syntax
Works for all fields. Required when the field has arguments or non-scalar selection.
const doc = Graffle.gql({
query: {
pokemons: [
['fire', {
$: { filter: { type: 'FIRE' } },
name: true,
}],
['water', {
$: { filter: { type: 'WATER' } },
name: true,
}],
],
},
})
Short Array
Only for scalars/enums without required arguments. Equivalent to ['alias', true]
.
const doc = Graffle.gql({
query: {
pokemon: {
name: ['pokemonName'],
hp: ['hitPoints'],
},
},
})
String Only
Only for scalars/enums without required arguments. Equivalent to ['alias', true]
. Most concise.
const doc = Graffle.gql({
query: {
pokemon: {
id: 'pokemonId',
name: 'pokemonName',
},
},
})
Directives
GraphQL directives like @skip
and @include
are written using special $
prefixed properties.
const doc = Graffle.gql({
query: {
getTrainers: {
trainers: {
name: true,
id: {
$skip: true,
},
pokemon: {
id: {
$include: false,
},
name: true,
},
},
},
},
})
You can also apply directives to entire field groups using the special ___
key:
const doc = Graffle.gql({
query: {
getPokemons: {
___: {
$skip: true,
pokemons: {
name: true,
},
},
},
},
})
Enums
Enum values are passed as strings and automatically validated by TypeScript based on your schema.
const doc = Graffle.gql({
query: {
getPokemons: {
pokemons: {
$: { filter: { type: 'FIRE' } },
name: true,
type: true,
},
},
},
})
TypeScript will provide autocomplete for valid enum values and show errors for invalid ones.
Inline Fragments
Inline fragments are used to select fields on specific types in unions and interfaces. Use the ___on_TypeName
syntax.
Unions
const doc = Graffle.gql({
query: {
getBattles: {
battles: {
__typename: true,
___on_BattleRoyale: {
date: true,
combatants: {
trainer: { name: true },
},
},
___on_BattleTrainer: {
date: true,
combatant1: {
trainer: { name: true },
},
},
},
},
},
})
Interfaces
Interface fragments work the same way as unions, using ___on_TypeName
for each implementing type.
const doc = Graffle.gql({
query: {
getBeings: {
beings: {
__typename: true,
id: true,
name: true,
___on_Patron: {
money: true,
},
___on_Trainer: {
class: true,
},
___on_Pokemon: {
type: true,
},
},
},
},
})
Field Groups
Field groups allow you to apply directives to multiple fields at once using the special ___
key.
const doc = Graffle.gql({
query: {
getPokemons: {
___: {
$skip: true,
pokemons: {
name: true,
},
},
},
},
})
Arguments
const doc = Graffle.gql({
query: {
getPokemon: {
pokemonByName: {
$: { name: 'Pikachu' },
name: true,
hp: true,
attack: true,
},
},
},
})
Variables
Defer Execution with Variables
When using variables with generated method calls (not Graffle.gql()
), Graffle automatically switches to deferred execution, giving you a DocumentRunner
object to inspect the document and execute it multiple times with different variables.
Basic Usage
import { $ } from 'graffle'
const doc = Graffle.gql({
query: {
getPokemon: {
pokemonByName: {
$: { name: $('pokemonName').required() },
name: true,
type: true,
},
},
},
})
Modifiers (Objects Only)
The object syntax provides additional control over variables:
import { $ } from 'graffle'
// Required variable
$('name').required()
// Optional variable
$('name').optional()
// Variable with default value
$.default('Ash')
// Anonymous variable
$.required()
Schema-Less Mode Type Hints
When using the static builder without a generated schema, you can provide explicit type hints for variables using typed builder methods:
import { $ } from 'graffle'
const doc = Graffle.gql({
query: {
getPokemon: {
pokemonByName: {
$: {
name: $.String(), // → string
level: $.Int(), // → number
isShiny: $.Boolean(), // → boolean
id: $.ID(), // → string
},
name: true,
},
},
},
})
Available type hints:
$.String()
- Maps to TypeScriptstring
$.Int()
- Maps to TypeScriptnumber
$.Float()
- Maps to TypeScriptnumber
$.Boolean()
- Maps to TypeScriptboolean
$.ID()
- Maps to TypeScriptstring
Type inference rules:
- With schema (generated client): Plain
$
infers types from the schema - Without schema (static mode):
- Plain
$
→unknown
(no type information) $.String()
, etc. → trusts the type hint you provide
- Plain
Type hints can be combined with modifiers:
$.String().required() // Required string variable
$.Int().default(10) // Optional number with default
$.Boolean().as('flag') // Boolean with custom name
Variable Hoisting
Graffle supports both manual hoisting (explicit $
markers) and automatic hoisting (extracting all arguments as variables). For a comprehensive guide on how hoisting works, configuration options, and choosing between approaches, see Variable Hoisting.
Picking a Syntax
Use Object when:
- You want IDE autocomplete for all fields
- You're building tools or libraries
- You need programmatic document generation
- You prefer TypeScript over GraphQL syntax
Use String when:
- You're copying queries from GraphQL Playground
- You have existing GraphQL documents
- You prefer standard GraphQL syntax
- You're migrating from another GraphQL client
Both syntaxes are equally powerful and type-safe - choose based on your preference and workflow.
Sending
Once built, pass documents to a client instance. See Sending for details.