Tangramsangrams

Getting Started

Assemble type-safe TanStack pieces for your data layers.

Assemble the Pieces

Every data layer is a puzzle. Your API schema defines the shapes, but you still need perfectly-fitting pieces for your applications - TanStack provides type-safe queryOptions, mutationOptions, formOptions, etc that snap right into place.

Tangrams generates those pieces from your GraphQL or OpenAPI schemas.

The Puzzle Pieces

Tangrams generates code that integrates seamlessly with the TanStack ecosystem:

PieceWhat It GeneratesFits Into
Query OptionsqueryOptions() with typed variables and responsesuseQuery, useSuspenseQuery
Mutation OptionsmutationOptions() with typed inputsuseMutation
Form OptionsformOptions() with schema validationuseForm
DB CollectionsCollection options with persistence handlersTanStack DB

Quick Start

# Install the CLI
bun add -D tangrams

# Initialize your config (interactive)
bunx tangrams init

The init command will guide you through setting up your configuration, including:

  • Choosing a validation library (Zod, Valibot, ArkType, or Effect)
  • Selecting your API source type (GraphQL or OpenAPI)
  • Pointing to your schema or spec

Once configured, generate your code:

# Generate the pieces
bunx tangrams generate

Tip: Use bunx tangrams init --skip to generate a template config file for manual editing.

Installation

bun add -D tangrams
npm install -D tangrams
pnpm add -D tangrams

Additional peer dependencies are required based on which TanStack library you're generating for. See the individual library guides for details.

How It Works

  1. Bring your schema - Point Tangrams at your GraphQL SDL or OpenAPI spec
  2. Configure what to generate - Choose which pieces you need (Query, Form, and DB are currently supported)
  3. Run the generator - Get fully-typed, ready-to-use code
  4. Import and use - The pieces fit perfectly into your TanStack hooks
// Generated pieces snap right in
import { useQuery, useMutation } from "@tanstack/react-query"
import { getUserQueryOptions, updateUserMutationOptions } from "./tangrams/my-api/query/options"

function UserProfile({ id }: { id: string }) {
  // Type-safe query with automatic cache keys
  const { data } = useQuery(getUserQueryOptions({ id }))
  
  // Type-safe mutation
  const { mutate } = useMutation(updateUserMutationOptions())
  
  return <div>{data?.user.name}</div>
}

Configuration Reference

Tangrams uses a tangrams.config.ts file in your project root. Run bunx tangrams init to create one, or create it manually.

Complete Configuration Example

Here's a comprehensive example showing all available configuration options:

import { defineConfig } from "tangrams"

export default defineConfig({
  // Directory where the tangrams folder will be generated (default: ".")
  // With ".", files are generated at ./tangrams/<source>/...
  // With "./src", files are generated at ./src/tangrams/<source>/...
  output: ".",

  // Validation library for generated schemas (default: "zod")
  // Supported: "zod", "valibot", "arktype", "effect"
  // All four support Standard Schema for TanStack Form compatibility
  validator: "zod",

  // Array of data sources to generate from
  sources: [
    // ===================
    // GraphQL Source
    // ===================
    {
      // Unique name for this source (lowercase alphanumeric with hyphens)
      name: "graphql",
      // Source type discriminator
      type: "graphql",

      // Schema configuration - choose ONE of the following:

      // Option A: URL-based (introspection)
      schema: {
        url: "http://localhost:4000/graphql",
        // Optional headers for introspection request
        headers: {
          "x-api-key": process.env.API_KEY,
        },
      },

      // Option B: File-based (local SDL files)
      // schema: {
      //   file: "./schema.graphql",
      //   // Or multiple files with glob patterns:
      //   // file: ["./schema.graphql", "./extensions/**/*.graphql"],
      // },

      // Glob pattern(s) for GraphQL document files (.graphql)
      documents: "./src/graphql/**/*.graphql",
      // Or multiple patterns:
      // documents: ["./src/graphql/**/*.graphql", "./src/queries/**/*.graphql"],

      // What to generate from this source
      // Options: "query", "form", "db"
      // Note: "db" automatically enables "query"
      generates: ["query", "form", "db"],

      // Optional overrides
      overrides: {
        // Custom scalar mappings (GraphQL only)
        // Values must be valid expressions for the configured validator
        scalars: {
          DateTime: "z.string()",  // Use z.coerce.date() for Date objects
          JSON: "z.unknown()",
          Cursor: "z.string()",
        },

        // TanStack DB overrides
        db: {
          collections: {
            // Key is the entity name (e.g., "User", "Post")
            User: {
              // Override the key field (default: auto-detected "id")
              keyField: "rowId",
              // Sync mode: "full" (default) or "on-demand"
              syncMode: "on-demand",
              // Predicate mapping preset for on-demand mode
              // Options: "hasura", "prisma", "rest-simple", "jsonapi"
              predicateMapping: "hasura",
              // Override selector path for extracting array from response
              // Use dot notation for nested paths
              selectorPath: "users.data",
            },
          },
        },

        // TanStack Form overrides
        form: {
          // Validator timing (default: "onSubmitAsync")
          // Options: "onChange", "onChangeAsync", "onBlur", "onBlurAsync",
          //          "onSubmit", "onSubmitAsync", "onDynamic"
          validator: "onDynamic",
          // Validation logic (only used with "onDynamic" validator)
          validationLogic: {
            // When to run initial validation: "change", "blur", "submit"
            mode: "submit",
            // When to revalidate after first submission
            modeAfterSubmission: "change",
          },
        },
      },
    },

    // ===================
    // OpenAPI Source
    // ===================
    {
      name: "api",
      type: "openapi",

      // OpenAPI spec - local file path or remote URL
      spec: "./openapi.yaml",
      // spec: "https://api.example.com/openapi.json",

      // Optional headers for fetching remote spec
      headers: {
        Authorization: `Bearer ${process.env.API_TOKEN}`,
      },

      // Optional path filtering with glob patterns
      include: ["/users/**", "/posts/**"],
      exclude: ["/internal/**", "/admin/**"],

      generates: ["query", "form"],

      overrides: {
        db: {
          collections: {
            Pet: {
              keyField: "petId",
              syncMode: "on-demand",
              predicateMapping: "rest-simple",
            },
          },
        },
        form: {
          validator: "onBlurAsync",
        },
      },
    },
  ],
})

Root Options

OptionTypeDefaultDescription
outputstring"."Directory where the tangrams folder will be generated
validator"zod" | "valibot" | "arktype" | "effect""zod"Validation library for generated schemas
sourcesSourceConfig[](required)Array of data sources (minimum 1 required)

Validator Libraries

Tangrams supports four validation libraries that all implement Standard Schema:

LibraryImportDescription
ZodzodThe default. Full-featured schema validation with great TypeScript inference.
ValibotvalibotLightweight alternative with modular design and smaller bundle size.
ArkTypearktypeType-first validation with runtime safety and excellent TypeScript integration.
EffecteffectPart of the Effect ecosystem. Requires Schema.standardSchemaV1() wrapper for TanStack Form.

All four libraries work seamlessly with TanStack Form since they implement the Standard Schema protocol. Choose based on your preferences for bundle size, API style, or existing usage in your project.

Note: Effect Schema requires wrapping with Schema.standardSchemaV1() for Standard Schema compliance. Tangrams handles this automatically in generated form options.

// Use Zod (default)
export default defineConfig({
  validator: "zod",
  sources: [/* ... */],
})

// Use Valibot for smaller bundles
export default defineConfig({
  validator: "valibot",
  sources: [/* ... */],
})

// Use ArkType for type-first validation
export default defineConfig({
  validator: "arktype",
  sources: [/* ... */],
})

// Use Effect for the Effect ecosystem
export default defineConfig({
  validator: "effect",
  sources: [/* ... */],
})

Install your chosen validator as a peer dependency:

bun add zod
bun add valibot
bun add arktype
bun add effect

GraphQL Source Options

OptionTypeRequiredDescription
namestringYesUnique source name (lowercase alphanumeric with hyphens, starting with a letter)
type"graphql"YesSource type discriminator
schemaobjectYesSchema configuration (see below)
documentsstring | string[]YesGlob pattern(s) for .graphql operation files
generatesarrayYesWhat to generate: ["query"], ["query", "form"], ["db"], etc.
overridesobjectNoOverride scalars, DB, and form settings (see below)

GraphQL Schema Options

Configure your GraphQL schema using one of these approaches:

URL-based (introspection):

OptionTypeRequiredDescription
schema.urlstringYesGraphQL endpoint URL for introspection
schema.headersRecord<string, string>NoHeaders to send with introspection request

File-based (local SDL files):

OptionTypeRequiredDescription
schema.filestring | string[]YesPath or glob pattern(s) for .graphql schema files

OpenAPI Source Options

OptionTypeRequiredDescription
namestringYesUnique source name (lowercase alphanumeric with hyphens, starting with a letter)
type"openapi"YesSource type discriminator
specstringYesPath to OpenAPI spec (local file or URL)
headersRecord<string, string>NoHeaders for fetching remote spec
includestring[]NoGlob patterns for paths to include (e.g., ["/users/**"])
excludestring[]NoGlob patterns for paths to exclude
generatesarrayYesWhat to generate: ["query"], ["query", "form"], ["db"], etc.
overridesobjectNoOverride DB and form settings (see below)

Generates Options

The generates array specifies which TanStack artifacts to generate:

ValueDescriptionDependencies
"query"TanStack Query (queryOptions, mutationOptions)None
"form"TanStack Form (formOptions with schema validation)None
"db"TanStack DB (queryCollectionOptions)Auto-enables "query"

Examples:

generates: ["query"]              // Query only
generates: ["query", "form"]      // Query and Form
generates: ["db"]                 // DB (auto-enables Query)
generates: ["query", "form", "db"] // All three

Overrides Configuration

Scalars (GraphQL only)

Map custom GraphQL scalars to validator expressions. Values must be valid expressions for your configured validator library:

overrides: {
  scalars: {
    DateTime: "z.string()",      // ISO date string
    JSON: "z.unknown()",         // Unknown JSON data
    Cursor: "z.string()",        // Pagination cursor
  },
}
overrides: {
  scalars: {
    DateTime: "v.string()",
    JSON: "v.unknown()",
    Cursor: "v.string()",
  },
}
overrides: {
  scalars: {
    DateTime: 'type("string")',
    JSON: 'type("unknown")',
    Cursor: 'type("string")',
  },
}
overrides: {
  scalars: {
    DateTime: "Schema.String",
    JSON: "Schema.Unknown",
    Cursor: "Schema.String",
  },
}

Note: Scalar values must start with a valid prefix for your validator: z. for Zod, v. for Valibot, type( / type. for ArkType, or Schema. for Effect. Using raw TypeScript types like "string" or "Date" will throw a validation error with a helpful suggestion.

Built-in Scalar Mappings:

The following GraphQL scalars are handled automatically and don't need to be configured:

GraphQL ScalarGenerated Schema
IDz.string()
Stringz.string()
Intz.number()
Floatz.number()
Booleanz.boolean()
DateTimez.string()
Datez.string()
JSONz.unknown()
BigIntz.bigint()
UUIDz.string()

DB Overrides

Configure TanStack DB collection generation:

overrides: {
  db: {
    collections: {
      EntityName: {
        keyField: "uuid",           // Override key field (default: auto-detected "id")
        syncMode: "on-demand",      // "full" (default) or "on-demand"
        predicateMapping: "hasura", // Preset for on-demand mode
        selectorPath: "data.items", // Path to extract array from response
      },
    },
  },
}
OptionTypeDefaultDescription
keyFieldstringAuto-detected "id"Field to use as unique key
syncMode"full" | "on-demand""full"Data sync strategy
predicateMappingstringAuto-detectedPredicate translation preset
selectorPathstringAuto-detectedDot-notation path to extract array from response

Predicate Mapping Presets:

PresetUse CaseExample Output
"hasura"GraphQL Hasurawhere: { field: { _eq: value } }
"prisma"GraphQL Prismawhere: { field: { equals: value } }
"rest-simple"REST APIsfield_eq=value, sort=field:asc
"jsonapi"JSON:APIfilter[field]=value, sort=-field

Form Overrides

Configure TanStack Form generation:

overrides: {
  form: {
    validator: "onDynamic",
    validationLogic: {
      mode: "submit",
      modeAfterSubmission: "change",
    },
  },
}
OptionTypeDefaultDescription
validatorstring"onSubmitAsync"When validation runs
validationLogicobject-Config for "onDynamic" validator only
validationLogic.modestring"submit"Initial validation trigger
validationLogic.modeAfterSubmissionstring"change"Revalidation trigger after first submit

Available Validators:

ValidatorDescription
"onChange"Validate on every change (sync)
"onChangeAsync"Validate on every change (async)
"onBlur"Validate when field loses focus (sync)
"onBlurAsync"Validate when field loses focus (async)
"onSubmit"Validate on form submit (sync)
"onSubmitAsync"Validate on form submit (async) - default
"onDynamic"Dynamic validation with revalidateLogic

Output Directory Structure

Generated files are organized by source name:

tangrams/
└── <source-name>/
    ├── client.ts          # API client (GraphQL or REST)
    ├── schema.ts          # Validation schemas + TypeScript types
    ├── functions.ts       # Standalone fetch functions
    ├── query/
    │   └── options.ts     # queryOptions, mutationOptions
    ├── form/
    │   └── options.ts     # formOptions
    └── db/
        └── collections.ts # TanStack DB collections

CLI Reference

tangrams init

Initialize a new tangrams.config.ts file:

tangrams init [options]

Options:
  -f, --force    Overwrite existing config file
  -s, --skip     Skip interactive prompts and generate a template config

By default, the init command runs interactively, prompting you for configuration values. Use --skip to generate a template config file with placeholder values that you can edit manually.

tangrams generate

Generate code from your configured sources:

tangrams generate [options]

Options:
  -c, --config <path>    Path to config file
  -f, --force            Force regeneration of all files including client
  -w, --watch            Watch for file changes and regenerate
  --clean                Remove stale source directories from previous generations
  -y, --yes              Skip confirmation prompts (use with --clean)
  --env-file <path>      Path to env file (can be specified multiple times)
  --no-dotenv            Disable automatic .env file loading

Cleanup Mode:

When using --clean, Tangrams detects and removes stale source directories from previous generations. This is useful when you rename or remove sources from your configuration:

# Remove stale artifacts (prompts for confirmation)
tangrams generate --clean

# Remove stale artifacts without prompting
tangrams generate --clean --yes

If Tangrams detects that a source was renamed (same schema/spec, different name), it will automatically copy the client.ts file to the new source directory before removing the old one. This preserves any customizations you've made to the client.

Watch Mode:

When using --watch, Tangrams watches your config file, GraphQL documents, and OpenAPI specs for changes. Press r to force a full refresh, or q to quit. When combined with --clean, stale artifacts are automatically removed on config changes without prompting.

Next Steps

Choose your TanStack library to get started:

On this page