## The Problem

Full-stack TypeScript teams hit the same wall: **keeping types in sync**.

You add a route on the server, update the DTO, then write matching types on the client by hand. Eventually someone renames a field and the client silently breaks.

```ts
// Server: you add a new route
@Post('signup')
signup(@Body() body: SignupDto): Promise<AuthSession> { ... }

// Client: you manually write the matching types... or forget to
type SignupReq = { email: string; password: string }  // hope this matches SignupDto
type SignupRes = { token: string }                     // hope this matches AuthSession
```

Duck Gen and Duck Query close this gap.

***

## Duck Gen -- The Compiler Extension

Duck Gen reads your server source and emits TypeScript definition files (`.d.ts`) for every API route and message key it finds.

The contract layer stops being something you write by hand.

### What it generates

| Category | Output |
| --- | --- |
| **API route types** | A route map with typed request shapes (body, query, params, headers) and response types for every controller method. |
| **Message registry types** | Strongly-typed i18n dictionaries derived from `@duckgen` message tags in your code. |

Both outputs are `.d.ts` files you import directly -- no runtime cost, just types.

### How it works

Duck Gen parses your source statically with [ts-morph](https://ts-morph.com/). It finds every `@Controller` class, extracts the HTTP method decorators (`@Get`, `@Post`, `@Put`, `@Delete`, `@Patch`), resolves the full route path, and reads parameter decorators (`@Body`, `@Query`, `@Param`, `@Headers`) for request shapes. Response types come from the method's return type.

The result is a route map:

```ts
// Generated by duck-gen -- do not edit
export interface ApiRoutes {
  '/api/auth/signin': {
    POST: {
      body: SigninDto
      res: AuthSession
    }
  }
  '/api/auth/signup': {
    POST: {
      body: SignupDto
      res: AuthSession
    }
  }
  '/api/users/:id': {
    GET: {
      params: { id: string }
      res: UserProfile
    }
    PATCH: {
      params: { id: string }
      body: UpdateUserDto
      res: UserProfile
    }
  }
}
```

### Configuration

Duck Gen reads a `duck-gen.json` file in your project root:

```json
{
  "framework": "nestjs",
  "srcDir": "./src",
  "outputDir": "./generated",
  "apiPrefix": "/api",
  "include": ["**/*.controller.ts"],
  "exclude": ["**/*.spec.ts"]
}
```

One command generates everything:

```bash
bunx @gentleduck/gen
```

***

## Duck Query -- The Type-Safe Client

Duck Query is an HTTP client built on [Axios](https://axios-http.com/). It consumes the route map from Duck Gen so request paths, bodies, query parameters, and responses are checked at compile time.

### Before and after

**Before (manual types, no safety):**

```ts
const res = await axios.post('/api/auth/signin', {
  emial: 'test@example.com',  // typo -- no error
  password: '123',
})
// res.data is `any` -- no type checking
```

**After (Duck Query + Duck Gen):**

```ts
const res = await client.post('/api/auth/signin', {
  emial: 'test@example.com',  // TypeScript error: 'emial' does not exist on SigninDto
  password: '123',
})
// res.data is AuthSession -- fully typed
```

### The full pipeline

### Setup

```ts
import { createDuckQuery } from '@gentleduck/query'
import type { ApiRoutes } from './generated/api-routes'

const client = createDuckQuery<ApiRoutes>({
  baseURL: 'https://api.example.com',
})

// Every method is fully typed
const session = await client.post('/api/auth/signin', {
  body: { email: 'user@example.com', password: 'secret' },
})
// session.data -> AuthSession

const user = await client.get('/api/users/:id', {
  params: { id: '123' },
})
// user.data -> UserProfile
```

***

## Getting Started

### Install

```bash
# Install Duck Gen (dev dependency -- only runs at build time)
bun add -d @gentleduck/gen

# Install Duck Query (runtime dependency)
bun add @gentleduck/query
```

Duck Gen is framework-aware. NestJS ships first; more adapters are planned.

Duck Gen emits `.d.ts` files only -- types that disappear at compile time. Duck Query wraps Axios and adds type inference without bundle weight.