# Technical Documentation: graffle-js/graffle

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/graffle-js/graffle)

### Overview (`README.md`)

> [!NOTE]
> Looking for `graphql-request`? It's been renamed to Graffle and the old version is available on the [`graphql-request` branch](https://github.com/graffle-js/graffle/tree/graphql-request).

<p align="center">
  <img src="./website/content/public/_assets/logo-dark.png" width="200" alt="Graffle Logo">
</p>

<h1 align="center">Graffle</h1>

<p align="center">
  <b>Minimal. Extensible. Type Safe. GraphQL client for JavaScript.</b>
</p>

<p align="center">
  ✨ Document Builder with full type inference • 🧰 Powerful extension system<br/>
  🚛 Multi-transport (HTTP + in-memory) • 🎭 Native GraphQL syntax support<br/>
  📦 Rich ecosystem of extensions • 🧙 Custom scalar codecs
</p>

<p align="center">
  <a href="https://graffle.js.org">Documentation</a> •
  <a href="https://graffle.js.org/guides/getting-started">Getting Started</a> •
  <a href="https://graffle.js.org/examples">Examples</a>
</p>

<p align="center">
  <a href="https://www.npmjs.com/package/graffle"><img src="https://img.shields.io/npm/v/graffle.svg?style=flat&colorA=18181B&colorB=28CF8D" alt="Version"></a>
  <a href="https://www.npmjs.com/package/graffle"><img src="https://img.shields.io/npm/dm/graffle.svg?style=flat&colorA=18181B&colorB=28CF8D" alt="Downloads"></a>
  <a href="https://github.com/graffle-js/graffle/blob/main/LICENSE"><img src="https://img.shields.io/github/license/graffle-js/graffle.svg?style=flat&colorA=18181B&colorB=28CF8D" alt="License"></a>
  <a href="https://github.com/graffle-js/graffle/actions/workflows/trunk.yml"><img src="https://img.shields.io/github/actions/workflow/status/graffle-js/graffle/trunk.yml?style=flat&colorA=18181B&colorB=28CF8D" alt="CI"></a>
  <a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-Ready-blue.svg?style=flat&colorA=18181B&colorB=28CF8D" alt="TypeScript"></a>
</p>

## Getting Started

Visit **[graffle.js.org/guides/getting-started](https://graffle.js.org/guides/getting-started)** for installation instructions, quick start guide, and examples.

> **Note:** Graffle is currently in pre-release. Follow the documentation for the correct installation command.

## Learn More

Visit **[graffle.js.org](https://graffle.js.org)** for full documentation, guides, and examples.

## License

MIT

## Sponsors

<p align="center">
  Graffle is supported by these generous sponsors.<br/>
  If you find Graffle valuable and would like to support its development,<br/>
  please consider <a href="https://github.com/sponsors/jasonkuhrt">becoming a sponsor</a>.
</p>

<p align="center" style="font-size:21px; color:black;">
  Browser testing via<br/>
  <a href="https://www.lambdatest.com/?utm_source=graffle&utm_medium=sponsor" target="_blank">
    <img src="https://www.lambdatest.com/blue-logo.png" style="vertical-align: middle;" width="250" height="45" />
  </a>
</p>

---

### Overview (`tools/vitest-plugin-examples/README.md`)

# Vitest Examples Plugin

A Vite plugin for testing executable documentation examples with type-safe configuration. This plugin automatically discovers example files, generates TypeScript types for type-safe encoder configuration, executes examples, captures their output, and integrates with Vitest's snapshot testing.

## Features

- **Automatic Type Generation**: Generates TypeScript types in `@generated/test-examples` for type-safe encoder configuration
- **Automatic Discovery**: Finds example files using glob patterns
- **Output Capture**: Executes examples and captures stdout/stderr using Effect Platform
- **Dynamic Value Masking**: Type-safe encoder configuration for masking non-deterministic values
- **Snapshot Testing**: Integrates seamlessly with Vitest inline snapshots
- **Lifecycle Hooks**: Support for setup/teardown before/after each example
- **Sequential Execution**: Runs examples sequentially to avoid race conditions
- **IDE Autocomplete**: Full TypeScript autocomplete for example file paths
- **Configurable Execution**: Customize executor command, cwd, and environment

## Installation

```bash
pnpm add -D @jasonkuhrt/vitest-plugin-examples
```

Or use it locally within the Graffle monorepo.

## Usage

### 1. Configure Vitest to Use the Plugin

In your `vitest.config.ts`:

```typescript
import { examplesPlugin } from '@jasonkuhrt/vitest-plugin-examples'
import { defineConfig } from 'vitest/config'

export default defineConfig({
  plugins: [
    examplesPlugin({
      pattern: './*/*.ts',
      outputDir: './__outputs__',
    }),
  ],
  // ... rest of your config
})
```

The plugin runs during Vite's `buildStart` lifecycle, generating types before TypeScript compilation.

### 2. Use the Convenience Test Function

In your test file:

```typescript
import type { ExamplePath } from '@generated/test-examples'
import { createExamplesTest } from '@jasonkuhrt/vitest-plugin-examples'
import { test } from 'vitest'

// Type-safe encoder configuration with autocomplete
const encoders = {
  './examples/dynamic-timestamp.ts': (value: string) => {
    return value.replace(/timestamp: \d+/g, 'timestamp: MASKED')
  },
} satisfies Partial<Record<ExamplePath, EncoderFunction>>

createExamplesTest(test, {
  timeout: 300000,
  config: {
    pattern: './*/*.ts',
    outputDir: './__outputs__',
    ignore: ['./$', './__outputs__', './__tests__'],
    encoders,
    beforeEach: async () => {
      // Reset database before each example
      await db.reset()
    },
  },
})
```

That's it! The `createExamplesTest()` function:

- Automatically runs all discovered examples
- Creates a single Vitest test case
- Generates individual snapshots for each example
- Handles all assertions internally

### Type-Safe Encoders

The plugin generates TypeScript types for all discovered example files in `node_modules/@generated/test-examples/`:

```typescript
// Auto-generated - provides autocomplete for example paths
export interface ExampleFiles {
  './examples/example1.ts': true
  './examples/example2.ts': true
  // ... all discovered examples
}

export type ExamplePath = keyof ExampleFiles
```

Use these types for type-safe encoder configuration:

```typescript
import type { EncoderFunction, ExamplePath } from '@generated/test-examples'

const encoders = {
  './examples/dynamic-values.ts': (output: string) => {
    return output.replace(/'x-timestamp', '\d+'/gi, `'x-timestamp', 'MASKED'`)
  },
  // ✅ TypeScript autocomplete works here!
  // ✅ Typos are caught at compile time!
} satisfies Partial<Record<ExamplePath, EncoderFunction>>
```

### Advanced: Manual Test Setup

If you need more control, you can use the lower-level APIs:

```typescript
import { runExamples } from '@jasonkuhrt/vitest-plugin-examples'
import { expect, test } from 'vitest'

test('examples', async () => {
  const results = await runExamples({
    pattern: './*/*.ts',
    outputDir: './__outputs__',
  })

  for (const result of results) {
    expect(result.encoded).toMatchSnapshot(
      `${result.file.group}/${result.file.name}`,
    )
  }
}, { timeout: 300000 })
```

## Configuration

### `ExamplesPluginConfig`

```
/* Detailed source-code truncated for AI context efficiency. */
```

### `ExampleResult`

```typescript
interface ExampleResult {
  /** The example file that was executed */
  file: ExampleFile
  /** Captured stdout output */
  stdout: string
  /** Captured stderr output */
  stderr: string
  /** Whether the example failed */
  failed: boolean
  /** Exit code from the process */
  exitCode: number | null
  /** Encoded output (after applying encoders and masking) */
  encoded: string
}
```

## How It Works

1. **Type Generation** (Vite plugin, `buildStart` hook):
   - Discovers example files using Kit's `Fs.glob()`
   - Generates TypeScript types to `node_modules/@generated/test-examples/`
   - Creates proper npm package with `package.json` for TypeScript module resolution
   - Runs before TypeScript compilation, ensuring types are available during type checking

2. **Test Execution** (Vitest runtime):
   - Discovers example files matching the pattern
   - Runs each example using Effect Platform's `Command` API (configurable executor)
   - Captures stdout/stderr using Effect Streams
   - Applies custom encoders + default masking for determinism
   - Creates individual inline snapshots for each example

### Default Encoding

The plugin applies default encoding to mask common dynamic values:

- File paths → `/some/path/to/file.ts:XX:XX`
- Node.js version → `Node.js vXX.XX.XX`
- Node internal module line numbers
- Dynamic error formatting differences

## API

### Plugin API

#### `examplesPlugin(config?)`

Vite plugin that generates TypeScript types for example files during build.

**Usage**: Add to `vitest.config.ts` plugins array

**Configuration**: Same as `ExamplesPluginConfig`

### Test Helper API

#### `createExamplesTest(test, options?)`

Convenience function that creates a complete test case for all examples.

**Parameters**:

- `test` - Vitest's `test` function
- `options` - Configuration object:
  - `config?: ExamplesPluginConfig` - Plugin configuration
  - `timeout?: number` - Test timeout in milliseconds (default: 300000)
  - `testName?: string` - Name of the test case (default: 'examples')

**Returns**: void (creates test internally)

#### `testExamples(config?)`

Creates a function that runs all examples with the given configuration.

**Returns**: `() => Promise<ExampleResult[]>`

#### `runExamples(config)`

Runs all examples and returns their results.

**Returns**: `Promise<ExampleResult[]>`

#### `runExample(relativePath, config)`

Runs a single example by its relative path.

**Returns**: `Promise<ExampleResult>`

#### `discoverExamples(config)`

Discovers example files without executing them.

**Returns**: `Promise<ExampleFile[]>`

#### `captureExample(file, config)`

Executes a single example file and captures its output.

**Returns**: `Promise<ExampleResult>`

## Migration from Old System

The old system used:

- `scripts/generate-examples-derivatives/generate-outputs.ts` - Manual output generation
- Custom `runExample()` and `runExampleForTest()` functions
- Separate snapshot path construction
- Manual encoder loading

The new plugin simplifies this to:

- Single `testExamples()` call
- Automatic snapshot integration
- Inline encoder configuration
- No manual output file generation needed

## Performance

- Examples run **sequentially** (not in parallel) to prevent race conditions
- Typical run time: ~2-5 minutes for ~30 examples
- Set test timeout to at least 300000ms (5 minutes)

## Dependencies

This plugin requires:

- **Effect Platform** (`@effect/platform`, `@effect/platform-node`, `effect`) - For command execution and stream handling
- **@wollybeard/kit** - For file system operations and FsLoc types
- **Vite & Vitest** (peer dependencies)

## Limitations

- Executes examples via configurable executor (default: `tsx`)
- Sequential execution means slower than parallel (by design, for safety)
- Snapshot comparison happens in-memory (inline snapshots)
- Generated types live in `node_modules/@generated/` - not committed to git
- Requires Effect Platform and Kit dependencies

## Future Improvements

- Support for parallel execution with opt-in
- Option to commit generated types to source control
- Comparison utilities for validating against old system
- Support for custom snapshot serializers
- Lighter-weight alternative without Effect/Kit dependencies

---

### Guides Typescript Type Diagnostics (`docs/guides/typescript-type-diagnostics.md`)

# TypeScript Type Diagnostics

## Quick Check

**Check specific file:**

```bash
./scripts/check-file-types.sh examples/10_transport-http/transport-http_abort.ts
```

**Check entire project:**

```bash
pnpm ts:check:stats
```

## Tools

### 1. `pnpm ts:check:stats` - Quick Stats

Shows instantiation counts and type checker time.

**When to use:** Quick sanity check for type performance.

**Output:**

```
Files:              500
Lines of Library:   200000
Instantiations:     150000
Time in Type Checker: 5.2s
```

**What it means:**

- **Instantiations** = amount of type work. Higher = slower.
- **Time in Type Checker** = actual compilation time spent on types.

### 2. `pnpm ts:check:trace` - Generate Detailed Trace

Creates detailed trace of where TypeScript spends time.

**When to use:** Need to identify specific type hot spots.

**Usage:**

```bash
pnpm ts:check:trace
pnpm ts:analyze
```

**Output:** Opens detailed breakdown showing which types are expensive.

### 3. `./scripts/check-file-types.sh` - Single File Check

Quick check for a specific file.

**When to use:** Debugging types in one file.

**Example:**

```bash
./scripts/check-file-types.sh src/client/methods/transport.ts
```

## Interpreting Results

### Instantiation Counts

- **< 1,000**: Fast, no issues
- **1,000 - 10,000**: Normal for complex types
- **10,000 - 50,000**: Watch for performance issues
- **> 50,000**: Likely hitting performance problems

### Common Problems

**Large unions in constraints:**

```typescript
// SLOW: Quadratic comparison
type Process<T extends Type1 | Type2 | ... | Type50> = ...

// FAST: Structural constraint
type Process<T extends BaseType> = ...
```

**Intersection constraints:**

```typescript
// SLOW: Not cached
type Process<T extends Base & { x: X } & { y: Y }> = ...

// FAST: Cached
interface Constraint extends Base { x: X; y: Y }
type Process<T extends Constraint> = ...
```

**Deep conditional nesting:**

- TypeScript evaluates conditionals eagerly
- Lift conditionals out or use mapped types

## Workflow

### Debugging Type Issues

1. **Run quick check:**
   ```bash
   ./scripts/check-file-types.sh path/to/file.ts
   ```

2. **If instantiations > 10,000, generate trace:**
   ```bash
   pnpm ts:check:trace
   pnpm ts:analyze
   ```

3. **Identify hot spots** in trace output

4. **Apply fixes:**
   - Replace intersection constraints with interfaces
   - Remove large union constraints
   - Simplify conditional types

5. **Verify improvement:**
   ```bash
   ./scripts/check-file-types.sh path/to/file.ts
   ```

### Before Committing

Run full check to catch regressions:

```bash
pnpm check:types
```

## Tools Used

- **`tsc --extendedDiagnostics`** - Official TypeScript compiler stats
- **`tsc --generateTrace`** - Official TypeScript trace generation
- **`@typescript/analyze-trace`** - Microsoft's trace analyzer

These are the state-of-the-art tools for TypeScript type performance analysis.

---

### Guides Typescript Variance (`docs/guides/typescript-variance.md`)

# TypeScript Variance Guide

## Introduction

**Variance** describes how type parameter relationships (subtype/supertype) affect the assignability of generic types. Understanding variance is critical when designing type-safe APIs with phantom types.

This guide explains the four types of variance and provides practical examples using Graffle's variance helper types.

## Quick Reference Table

| Variance          | Helper             | Assignment Direction        | Example (`1` and `number`)                          | Use Case                           |
| ----------------- | ------------------ | --------------------------- | --------------------------------------------------- | ---------------------------------- |
| **Covariant**     | `Covariant<T>`     | Subtype → Supertype ✅      | `T<1>` → `T<number>` ✅<br/>`T<number>` → `T<1>` ❌ | Return values, readonly properties |
| **Contravariant** | `Contravariant<T>` | Supertype → Subtype ✅      | `T<1>` → `T<number>` ❌<br/>`T<number>` → `T<1>` ✅ | Function parameters                |
| **Invariant**     | `Invariant<T>`     | No assignments ❌           | `T<1>` → `T<number>` ❌<br/>`T<number>` → `T<1>` ❌ | Mutable properties, exact matches  |
| **Bivariant**     | `Bivariant<T>`     | Both directions ✅ (unsafe) | `T<1>` → `T<number>` ✅<br/>`T<number>` → `T<1>` ✅ | Legacy DOM compatibility (avoid)   |

## Understanding with `1` and `number`

The literal type `1` and base type `number` clearly show variance direction:

```typescript
// Type relationship
type IsSubtype = 1 extends number ? true : false // true - 1 is a subtype of number
```

### Covariant Example

```typescript
import type { Covariant } from 'graffle/prelude'

interface Container<T> {
  readonly __type?: Covariant<T>
}

let narrow: Container<1> = {}
let wide: Container<number> = {}

// Natural direction: narrow → wide
wide = narrow // ✅ Works (1 extends number)
narrow = wide // ❌ Error: Type 'number' is not assignable to type '1'
```

**Why it works**: A container that produces `1` can be used wherever a container that produces `number` is expected, because `1` is always a valid `number`.

### Contravariant Example

```typescript
import type { Contravariant } from 'graffle/prelude'

interface Handler<T> {
  readonly __type?: Contravariant<T>
}

let narrow: Handler<1> = {}
let wide: Handler<number> = {}

// Reversed direction: wide → narrow
narrow = wide // ✅ Works (reversed!)
wide = narrow // ❌ Error
```

**Why it works**: A handler that can accept ANY `number` can substitute for a handler that accepts only `1`, because it can handle the `1` case. But a handler that only accepts `1` cannot handle all numbers.

### Invariant Example

```typescript
import type { Invariant } from 'graffle/prelude'

interface Exact<T> {
  readonly __type?: Invariant<T>
}

let one: Exact<1> = {}
let num: Exact<number> = {}

// No direction works
num = one // ❌ Error
one = num // ❌ Error
```

**Why it's strict**: Invariance is used for mutable containers where both reading AND writing occur. Neither direction is safe because:

- If `Exact<1>` → `Exact<number>` was allowed, you could write `2` into a container expecting only `1`
- If `Exact<number>` → `Exact<1>` was allowed, you could write `1` into a container expecting any number (this direction is actually safe, but TypeScript makes it invariant to prevent the other direction)

### Bivariant Example (Unsafe!)

```typescript
import type { Bivariant } from 'graffle/prelude'

interface Unsafe<T> {
  readonly __type?: Bivariant<T>
}

let narrow: Unsafe<1> = {}
let wide: Unsafe<number> = {}

// Both directions work - DANGEROUS!
wide = narrow // ✅ Works (normal covariance)
narrow = wide // ✅ Works (UNSAFE!)
```

**Why it's unsafe**: The second assignment allows `number` → `1`, which means you could later receive `2`, `3`, etc. where only `1` is expected, causing runtime errors.

## How Variance is Determined

TypeScript determines variance based on where the type parameter appears:

### Covariant Positions (Output)

- Function return types: `() => T`
- Readonly properties: `readonly prop: T`
- Promise results: `Promise<T>`

### Contravariant Positions (Input)

- Function parameters: `(param: T) => void`
- Setter parameters: `set value(v: T)`

### Invariant Positions (Both)

- Mutable properties: `value: T` (can read AND write)
- Function with parameter and return: `(param: T) => T`

### Bivariant Positions (Special Case)

- Method parameters (not function properties!): `method(param: T): void`

## Practical Example: Variable Builder

Graffle's variable builder uses **covariance** to allow specific default values to work with general variable types:

```typescript
import { $var } from 'graffle'

// $var.default(1) returns Builder<1>
// This can be assigned to fields expecting Builder<number>
const query = Schema.query.users({
  $: {
    limit: $var.default(10), // Builder<10> → Builder<number> ✅
  },
})

// But incompatible types are rejected
const badQuery = Schema.query.users({
  $: {
    limit: $var.default('ten'), // ❌ Builder<'ten'> → Builder<number> ERROR
  },
})
```

Without the covariant phantom type (`__type?: Covariant<$Type>`), TypeScript would not enforce this relationship because all `Builder<T>` instances would be structurally identical regardless of `T`.

## When to Use Each Variance

| Variance          | Use When                          | Example                                           |
| ----------------- | --------------------------------- | ------------------------------------------------- |
| **Covariant**     | Values flow out (producers)       | Return types, readonly data, immutable containers |
| **Contravariant** | Values flow in (consumers)        | Event handlers, callbacks, validators             |
| **Invariant**     | Values flow both ways (mutable)   | Mutable arrays, read-write properties             |
| **Bivariant**     | **Avoid!** (legacy compatibility) | Only for specific DOM compatibility cases         |

## Implementation Details

### The Phantom Type Trick

Phantom types add structural requirements without runtime overhead:

```typescript
interface Builder<T> {
  // Without phantom type - all Builder<T> are identical
  readonly _state: SomeState

  // With phantom type - Builder<T> depends on T
  readonly __type?: Covariant<T> // ← Forces type checking
}
```

The `__type` property:

- Is optional (`?`) so it doesn't need runtime values
- Uses a function signature to establish variance
- Never exists at runtime (phantom)
- Forces TypeScript to check `T` compatibility

### Why Function Signatures?

Function signatures make variance explicit:

```typescript
// Covariant - return type
type Covariant<T> = () => T

// Contravariant - parameter type
type Contravariant<T> = (value: T) => void

// Invariant - both positions
type Invariant<T> = (value: T) => T
```

This is clearer than using properties directly and follows ecosystem conventions for phantom/branded types.

### The Bivariant Hack - Indexed Access Question

The bivariant implementation uses indexed access to extract the method signature:

```typescript
type Bivariant<T> = { bivariantHack(value: T): void }['bivariantHack']
```

**Why the indexed access?**

The indexed access `['bivariantHack']` extracts the method type, converting it from:

- **Method**: `{ bivariantHack(value: T): void }`
- **To function type**: `(value: T) => void`

**Does this preserve bivariance?**

Based on our tests, the extracted signature **does preserve bivariant behavior** when used as an optional property. The TypeScript compiler appears to remember the method origin and maintains the bivariant parameter checking.

However, note that:

1. The optional `?` modifier on the phantom property makes TypeScript somewhat permissive
2. In practice, the covariant phantom type works correctly for the Builder use case
3. Contravariant and invariant helpers may be less reliable with optional properties in structural type checking

### Practical Considerations

For real-world usage (like in Graffle's Builder):

- **Covariant** is reliable and works as expected ✅
- **Bivariant** works for both directions as expected ✅
- **Contravariant** and **Invariant** may behave unexpectedly with optional phantom properties

The helpers are most useful for:

1. Documenting intent in type definitions
2. Understanding variance relationships
3. Teaching TypeScript variance concepts

## References

- [TypeScript Handbook: Type Compatibility](https://www.typescriptlang.org/docs/handbook/type-compatibility.html)
- [TypeScript FAQ: Function Parameter Bivariance](https://github.com/microsoft/TypeScript/wiki/FAQ#why-are-function-parameters-bivariant)
- [Variance in Programming Languages](https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science))

## See Also

- [Variable Builder Documentation](../variables.md)
- [Type-Safe Document Generation](../static-generation.md)

---

--- METRICS ---
- Files Extracted: 4
- Estimated Token Budget: ~5868 tokens
- Canonical Reference: https://codewiki.google/github.com/graffle-js/graffle
