npm package: @pineliner/uni-rest
Description: Unified type-safe REST API framework for seamless client/server communication
Latest version: 1.0.7

# uni-rest

Type-safe REST API framework with shared contracts between client and server.

## Design Philosophy

1. **Single endpoint** - One POST endpoint handles all requests
2. **Instruction-based routing** - `{ from: "resource", instruction: { name: "action", payload: {...} } }`
3. **Shared contracts** - Single source of truth using Zod schemas
4. **Type inference** - Full TypeScript types from contracts
5. **Zero boilerplate** - Automatic validation and serialization

## Quick Start

### 1. Define Contract

```typescript
// contracts/products.contract.ts
import { z } from 'zod'
import { defineContract } from '@pineliner/uni-rest'

const ProductSchema = z.object({
  id: z.number(),
  name: z.string(),
  price: z.number()
})

export const productsContract = defineContract('products', {
  getAll: {
    input: z.object({ limit: z.number().optional() }),
    output: z.array(ProductSchema)
  },
  getById: {
    input: z.object({ id: z.number() }),
    output: ProductSchema
  },
  create: {
    input: ProductSchema.omit({ id: true }),
    output: ProductSchema
  }
})
```

### 2. Implement Server

```typescript
// server/products.handlers.ts
import { createHandlers } from '@pineliner/uni-rest/server'
import { productsContract } from '../contracts/products.contract'

export const productsHandlers = createHandlers(productsContract, {
  getAll: async ({ limit = 50 }, req, ctx) => {
    const products = await db.query('SELECT * FROM products LIMIT ?', [limit])
    return products
  },

  getById: async ({ id }, req, ctx) => {
    const product = await db.query('SELECT * FROM products WHERE id = ?', [id])
    if (!product) throw new Error('Not found')
    return product
  },

  create: async (data, req, ctx) => {
    const result = await db.insert('products', data)
    return { id: result.insertId, ...data }
  }
})
```

```typescript
// server/index.ts
import { createManager } from '@pineliner/uni-rest/server'
import { productsHandlers } from './products.handlers'
import express from 'express'

const app = express()
const manager = createManager()

manager.register('products', productsHandlers)

app.use('/api/v1', manager.middleware())
app.listen(3000)
```

### 3. Use Client

```typescript
// client/products.service.ts
import { createClient } from '@pineliner/uni-rest/client'
import { productsContract } from '../contracts/products.contract'

const client = createClient(productsContract, {
  baseUrl: '/api/v1'
})

// Fully typed!
const products = await client.getAll({ limit: 10 })
const product = await client.getById({ id: 1 })
const newProduct = await client.create({ name: 'Test', price: 99.99 })
```

## Request/Response Format

### Request
```json
{
  "from": "products",
  "instruction": {
    "name": "getAll",
    "payload": { "limit": 10 }
  }
}
```

### Success Response
```json
{
  "status": "success",
  "data": [...]
}
```

### Error Response
```json
{
  "status": "error",
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input",
    "details": {...}
  }
}
```

## API Reference

### `defineContract(name, endpoints)`

Define a contract with Zod schemas:

```typescript
const contract = defineContract('users', {
  list: {
    input: z.object({ page: z.number() }),
    output: z.array(UserSchema)
  }
})
```

### `createHandlers(contract, handlers)`

Implement typed handlers:

```typescript
const handlers = createHandlers(contract, {
  list: async ({ page }, req, ctx) => {
    return await fetchUsers(page)
  }
})
```

Handler signature: `(input, req, ctx) => Promise<output>`

### `createManager()`

Create manager for single endpoint:

```typescript
const manager = createManager()
manager.register('products', productsHandlers)
manager.register('users', usersHandlers)

app.use('/api', manager.middleware())
```

### `createClient(contract, config)`

Create typed client:

```typescript
const client = createClient(contract, {
  baseUrl: '/api/v1'
})

await client.actionName(input)
```

## Features

- ✅ Type-safe contracts with Zod
- ✅ Automatic input validation
- ✅ Single endpoint architecture
- ✅ Full TypeScript inference
- ✅ Express middleware integration
- ✅ Error handling with status codes
- ✅ Context injection (auth, db, etc.)