npm package: @honorestjs/contract
Description: Contract-first API definitions for HonorestJS - Framework-agnostic contract definitions with full type safety
Author: ctrhub
Latest version: 0.1.1

# @honorestjs/contract

> Framework-agnostic contract definitions for type-safe APIs

[![npm version](https://img.shields.io/npm/v/@honorestjs/contract.svg)](https://www.npmjs.com/package/@honorestjs/contract)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

`@honorestjs/contract` provides a powerful, type-safe way to define API contracts that can be shared between servers and clients. Built on top of [Zod](https://github.com/colinhacks/zod), it enables **contract-first development** with full TypeScript type inference and runtime validation.

## Features

✨ **Contract-First Development** - Define your API before implementation  
🔒 **Full Type Safety** - Complete TypeScript type inference from contracts  
✅ **Runtime Validation** - Built-in validation using Zod schemas  
🎯 **Framework Agnostic** - Use with any server or client framework  
📝 **OpenAPI Compatible** - Generate OpenAPI specs from contracts  
🚀 **Zero Code Generation** - Pure TypeScript, no build steps required  

## Installation

```bash
npm install @honorestjs/contract zod
# or
yarn add @honorestjs/contract zod
# or
pnpm add @honorestjs/contract zod
# or
bun add @honorestjs/contract zod
```

## Quick Start

### 1. Define Your Contract

```typescript
import { defineContract, endpoint } from '@honorestjs/contract'
import { z } from 'zod'

// Define schemas
const UserSchema = z.object({
  id: z.string().uuid(),
  name: z.string(),
  email: z.string().email()
})

const CreateUserSchema = z.object({
  name: z.string().min(1),
  email: z.string().email()
})

// Define contract
export const UsersContract = defineContract({
  name: 'users',
  path: '/users',
  description: 'User management endpoints',
  endpoints: {
    getUser: endpoint({
      method: 'GET',
      path: '/:id',
      params: z.object({ id: z.string().uuid() }),
      output: UserSchema,
      description: 'Get a user by ID',
      errors: {
        404: z.object({ message: z.string() })
      }
    }),
    
    listUsers: endpoint({
      method: 'GET',
      path: '/',
      query: z.object({
        page: z.number().int().positive().default(1),
        limit: z.number().int().positive().max(100).default(20)
      }),
      output: z.object({
        users: z.array(UserSchema),
        total: z.number()
      })
    }),
    
    createUser: endpoint({
      method: 'POST',
      path: '/',
      body: CreateUserSchema,
      output: UserSchema,
      errors: {
        400: z.object({ message: z.string(), errors: z.array(z.any()) })
      }
    })
  }
})
```

### 2. Create an App Contract

```typescript
import { defineApp } from '@honorestjs/contract'
import { UsersContract } from './contracts/users'
import { PostsContract } from './contracts/posts'

export const AppContract = defineApp({
  version: 1,
  prefix: '/api',
  contracts: {
    users: UsersContract,
    posts: PostsContract
  },
  metadata: {
    title: 'My API',
    version: '1.0.0',
    description: 'RESTful API for my application'
  }
})
```

### 3. Use Type Inference

```typescript
import type { ContractInput, ContractOutput } from '@honorestjs/contract'

// Extract input types
type GetUserInput = ContractInput<typeof UsersContract, 'getUser'>
// { params: { id: string }, query: never, body: never, headers: never }

// Extract output types
type GetUserOutput = ContractOutput<typeof UsersContract, 'getUser'>
// { id: string, name: string, email: string }

// Use in your implementation
function getUser(input: GetUserInput['params']): Promise<GetUserOutput> {
  // Implementation
}
```

## API Reference

### Builders

#### `endpoint(definition)`

Creates an endpoint definition.

```typescript
const getUserEndpoint = endpoint({
  method: 'GET',                              // HTTP method
  path: '/:id',                               // Endpoint path
  params: z.object({ id: z.string() }),      // Path parameters schema
  query: z.object({ include: z.string() }),  // Query parameters schema (optional)
  body: z.object({ ... }),                    // Request body schema (optional)
  headers: z.object({ ... }),                 // Headers schema (optional)
  output: z.object({ ... }),                  // Response schema
  errors: {                                   // Error responses (optional)
    404: z.object({ message: z.string() })
  },
  description: 'Get a user by ID',            // Description (optional)
  summary: 'Get user',                        // Summary (optional)
  tags: ['users'],                            // Tags (optional)
  deprecated: false                           // Deprecated flag (optional)
})
```

#### `defineContract(definition)`

Creates a contract (collection of endpoints).

```typescript
const UsersContract = defineContract({
  name: 'users',                    // Contract name
  path: '/users',                   // Base path
  version: 1,                       // API version (optional)
  description: 'User endpoints',    // Description (optional)
  tags: ['users'],                  // Tags (optional)
  endpoints: {                      // Endpoint definitions
    getUser: getUserEndpoint,
    createUser: createUserEndpoint
  }
})
```

#### `defineApp(definition)`

Creates an app (collection of contracts).

```typescript
const AppContract = defineApp({
  version: 1,                       // Global API version (optional)
  prefix: '/api',                   // Global prefix (optional)
  contracts: {                      // Contract definitions
    users: UsersContract,
    posts: PostsContract
  },
  metadata: {                       // App metadata (optional)
    title: 'My API',
    version: '1.0.0',
    description: 'API description',
    contact: { name: 'Support', email: 'support@example.com' },
    license: { name: 'MIT' }
  }
})
```

### Type Utilities

#### `ContractInput<TContract, TEndpoint>`

Extracts input type from a contract endpoint.

```typescript
type Input = ContractInput<typeof UsersContract, 'getUser'>
// { params: {...}, query: {...}, body: {...}, headers: {...} }
```

#### `ContractOutput<TContract, TEndpoint>`

Extracts output type from a contract endpoint.

```typescript
type Output = ContractOutput<typeof UsersContract, 'getUser'>
// { id: string, name: string, email: string }
```

#### `ContractErrors<TContract, TEndpoint>`

Extracts error types from a contract endpoint.

```typescript
type Errors = ContractErrors<typeof UsersContract, 'getUser'>
// { 404: { message: string } }
```

### Validation

#### `validate(schema, data)`

Validates data against a Zod schema (async).

```typescript
const result = await validate(UserSchema, userData)
if (result.success) {
  console.log(result.data) // Validated data
} else {
  console.error(result.error) // Validation error
}
```

#### `validateEndpointInput(endpoint, input)`

Validates all inputs for an endpoint.

```typescript
const validation = await validateEndpointInput(
  UsersContract.endpoints.getUser,
  {
    params: { id: '123' },
    query: { include: 'posts' }
  }
)

if (!validation.isValid) {
  console.error(validation.errors)
}
```

#### `validateEndpointOutput(endpoint, output)`

Validates endpoint output.

```typescript
const result = await validateEndpointOutput(
  UsersContract.endpoints.getUser,
  responseData
)
```

## Integration

### With HonorestJS Server

```typescript
import { Controller } from 'honorestjs'
import { Contract } from 'honorestjs/contract'
import { UsersContract } from '@myapp/api-contract'

@Controller()
export class UsersController {
  @Contract(UsersContract.endpoints.getUser)
  async getUser(@Param('id') id: string) {
    // Implementation with automatic validation
  }
}
```

### With @honorest/client

```typescript
import { createClient } from '@honorest/client'
import { AppContract } from '@myapp/api-contract'

const api = createClient(AppContract, {
  baseUrl: 'http://localhost:3000'
})

// Fully type-safe
const user = await api.users.getUser({ params: { id: '123' }})
```

## Best Practices

### 1. Organize Contracts by Domain

```
/contracts
  /users
    schemas.ts
    contract.ts
  /posts
    schemas.ts
    contract.ts
  index.ts  # Export AppContract
```

### 2. Reuse Schemas

```typescript
// Define shared schemas
export const UserSchema = z.object({ ... })
export const CreateUserSchema = UserSchema.omit({ id: true })
export const UpdateUserSchema = CreateUserSchema.partial()
```

### 3. Document Everything

```typescript
const endpoint = endpoint({
  // ...
  description: 'Detailed description of what this endpoint does',
  summary: 'Short summary',
  tags: ['users', 'admin'],
  examples: {
    request: { id: '123' },
    response: { id: '123', name: 'John' }
  }
})
```

### 4. Version Your Contracts

```typescript
const UsersContractV1 = defineContract({
  name: 'users',
  version: 1,
  // ...
})

const UsersContractV2 = defineContract({
  name: 'users',
  version: 2,
  // ...
})
```

## TypeScript Configuration

For best results, enable strict mode in your `tsconfig.json`:

```json
{
  "compilerOptions": {
    "strict": true,
    "strictNullChecks": true
  }
}
```

## Contributing

Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details.

## License

MIT © [HonorestJS](https://github.com/honorestjs)

## Related Packages

- [`honorestjs`](https://github.com/honorestjs/honorest) - Server framework
- [`@honorest/client`](https://github.com/honorestjs/client) - Type-safe client SDK
- [`@honorest/openapi`](https://github.com/honorestjs/openapi) - OpenAPI generation (coming soon)