Skip to content

Type Safety ​

GQLB provides comprehensive type safety throughout your GraphQL operations. Every field, argument, variable, and result is fully typed based on your schema.

Output Types ​

Extract type-safe result types from your queries using OutputOf:

typescript
import { b, OutputOf } from './generated'

const USER_QUERY = b.query('GetUser', (b) => [
  b.user({ id: "123" }, b => [
    b.id(),
    b.name(),
    b.email(),
  ])
])

type UserData = OutputOf<typeof USER_QUERY>
// Result:
// {
//   readonly user: {
//     readonly id: string
//     readonly name: string
//     readonly email: string | null
//     readonly __typename: "User"
//   } | null
//   readonly __typename: "Query"
// }

Nullability ​

GQLB respects GraphQL's nullability rules:

typescript
const NULLABLE_FIELDS = b.query('NullableFields', (b) => [
  b.user(b => [
    b.id(),        // string (non-null)
    b.name(),      // string (non-null)
    b.email(),     // string | null (nullable)
    b.bio(),       // string | null (nullable)
  ])
])

type Data = OutputOf<typeof NULLABLE_FIELDS>
// user.id and user.name are never null
// user.email and user.bio can be null

Lists and Arrays ​

List types are properly typed as readonly arrays:

typescript
const POSTS_QUERY = b.query('GetPosts', (b) => [
  b.posts(b => [
    b.id(),
    b.title(),
  ])
])

type PostsData = OutputOf<typeof POSTS_QUERY>
// Result:
// {
//   readonly posts: readonly {
//     readonly id: string
//     readonly title: string
//     readonly __typename: "Post"
//   }[]
//   readonly __typename: "Query"
// }

Argument Type Safety ​

Field arguments are validated against your schema:

typescript
// ✅ Correct - matches schema
const VALID_QUERY = b.query('ValidQuery', (b) => [
  b.user({ id: "123" }, b => [
    b.name()
  ])
])

// ❌ TypeScript error - wrong argument type
const INVALID_QUERY = b.query('InvalidQuery', (b) => [
  b.user({ id: 123 }, b => [  // Error: expected string, got number
    b.name()
  ])
])

// ❌ TypeScript error - missing required argument
const MISSING_ARG = b.query('MissingArg', (b) => [
  b.user(b => [  // Error: missing required 'id' argument
    b.name()
  ])
])

Field Availability ​

Only fields that exist in your schema are available:

typescript
const QUERY = b.query('FieldSafety', (b) => [
  b.user(b => [
    b.id(),        // ✅ Field exists
    b.name(),      // ✅ Field exists
    b.invalid(),   // ❌ TypeScript error - field doesn't exist
  ])
])

Nested Type Safety ​

Type safety extends to all levels of nesting:

typescript
const NESTED_QUERY = b.query('NestedSafety', (b) => [
  b.user(b => [
    b.id(),
    b.posts(b => [
      b.id(),
      b.title(),
      b.author(b => [     // Nested object
        b.id(),
        b.name(),
        b.invalidField(), // ❌ TypeScript error
      ])
    ])
  ])
])

IntelliSense Support ​

GQLB provides rich IntelliSense features:

Field Autocompletion ​

Start typing a field name to see available options:

typescript
const QUERY = b.query('AutoComplete', (b) => [
  b.user(b => [
    b.n  // IntelliSense shows: name, notifications, etc.
  ])
])

Hover Documentation ​

Hover over fields to see their GraphQL definitions:

typescript
const QUERY = b.query('Documentation', (b) => [
  b.user(b => [
    b.email()  // Hover shows: "email: String" with description
  ])
])

Deprecation Warnings ​

Deprecated fields are marked with strikethrough:

typescript
const QUERY = b.query('Deprecated', (b) => [
  b.user(b => [
    b.oldField()  // Appears with strikethrough
  ])
])

Union and Interface Types ​

Type safety works with GraphQL unions and interfaces:

typescript
const SEARCH_QUERY = b.query('Search', (b) => [
  b.search({ query: "test" }, b => [
    // Common fields available on all types
    b.__on("User", b => [
      b.name(),
      b.email(),
    ]),
    b.__on("Post", b => [
      b.title(),
      b.content(),
    ])
  ])
])

type SearchData = OutputOf<typeof SEARCH_QUERY>
// Union type properly typed based on inline fragments

Custom Scalars ​

Custom scalar types are mapped to TypeScript types:

typescript
// In gqlb.config.ts
export default {
  generate: {
    scalarTypes: {
      DateTime: 'Date',
      UUID: 'string',
      JSON: 'Record<string, unknown>'
    }
  }
}

// In queries
const EVENT_QUERY = b.query('GetEvent', (b) => [
  b.event(b => [
    b.id(),           // UUID -> string
    b.createdAt(),    // DateTime -> Date
    b.metadata(),     // JSON -> Record<string, unknown>
  ])
])

type EventData = OutputOf<typeof EVENT_QUERY>
// All custom scalars properly typed

Type Narrowing ​

Use TypeScript's type narrowing with __typename:

typescript
type SearchResult = OutputOf<typeof SEARCH_QUERY>

function handleSearchResult(result: SearchResult['search'][0]) {
  if (result.__typename === 'User') {
    // result is now narrowed to User type
    console.log(result.name)    // ✅ Available
    console.log(result.title)   // ❌ TypeScript error
  } else if (result.__typename === 'Post') {
    // result is now narrowed to Post type
    console.log(result.title)   // ✅ Available
    console.log(result.name)    // ❌ TypeScript error
  }
}

Next Steps ​

Released under the MIT License.