# Client Code Generation

Automatically generate type-safe clients for Python, TypeScript, Go, and **Swift** from your RPC handlers.

## Quick Start

### Generate All Clients

```bash
# From your Django project root
python manage.py generate_centrifugo_clients --output ./opensdk --all
```

Output:
```
Found 10 RPC methods
  - tasks.list: ListTasksParams -> TaskListResult
  - tasks.create: CreateTaskParams -> TaskResult
  - tasks.update: UpdateTaskParams -> TaskResult
  - tasks.delete: DeleteTaskParams -> SuccessResult
  ...

✓ Generated Python client
✓ Generated TypeScript client
✓ Generated Go client
✓ Generated Swift client

Successfully generated 4 client(s)
```

### Generate Specific Languages

```bash
# Only Python
python manage.py generate_centrifugo_clients --output ./opensdk --python

# Only TypeScript
python manage.py generate_centrifugo_clients --output ./opensdk --typescript

# Only Go
python manage.py generate_centrifugo_clients --output ./opensdk --go

# Only Swift (iOS/macOS)
python manage.py generate_centrifugo_clients --output ./opensdk --swift

# Python + TypeScript
python manage.py generate_centrifugo_clients --output ./opensdk --python --typescript
```

## Integration with Workflow

### Using make api

The recommended way is to use the integrated `make api` command:

```bash
make api
```

This automatically:
1. Generates OpenAPI clients (HTTP REST)
2. **Generates Centrifugo clients (WebSocket RPC)**
3. Copies clients to frontend packages
4. Builds @api package

### Manual Integration

Add to your `generate_api.py`:

```python
# core/management/commands/generate_api.py
from django.core.management import call_command

def handle(self, *args, **options):
    # Step 1: OpenAPI clients
    call_command('generate_clients')

    # Step 2: Centrifugo clients
    call_command('generate_centrifugo_clients',
                output='./opensdk',
                all=True)

    # Step 3: Copy to frontend
    ...
```

## Generated File Structure

### Python Client

```
opensdk/python/
├── __init__.py          # Exports
├── models.py            # Pydantic models (generated)
├── rpc_client.py        # Base RPC client
├── client.py            # Typed API wrapper
├── requirements.txt     # centrifuge, pydantic
└── README.md            # Usage docs
```

**Dependencies:**
- `centrifuge` - Official Centrifugo Python client
- `pydantic>=2.0` - Type validation

### TypeScript Client

```
opensdk/typescript/
├── index.ts             # Exports
├── types.ts             # TypeScript interfaces
├── rpc-client.ts        # Base RPC client
├── client.ts            # Typed API wrapper
├── package.json         # centrifuge dependency
├── tsconfig.json        # TS config
└── README.md            # Usage docs
```

**Dependencies:**
- `centrifuge` - Official Centrifugo JS client

### Go Client

```
opensdk/go/
├── types.go             # Go structs
├── rpc_client.go        # Base RPC client
├── client.go            # Typed API wrapper
├── go.mod               # nhooyr.io/websocket v1.8.10
└── README.md            # Usage docs
```

**Dependencies:**
- `nhooyr.io/websocket v1.8.10` - **NO GitHub dependencies!**

### Swift Client (iOS/macOS)

```
opensdk/swift/
├── Sources/CentrifugoClient/
│   ├── Types.swift          # Codable structs
│   ├── RPCClient.swift      # Actor-based RPC client
│   ├── APIClient.swift      # Typed API wrapper
│   └── AnyCodable.swift     # Dynamic JSON support
├── Package.swift            # Swift Package Manager manifest
├── README.md                # Usage docs
└── CLAUDE.md               # AI documentation
```

**Requirements:**
- Swift 5.9+
- iOS 13+ / macOS 10.15+
- `SwiftCentrifuge` library (optional, for full implementation)

**Key Features:**
- ✅ Swift Concurrency (async/await)
- ✅ Actor-based thread safety
- ✅ Full `Codable` + `Sendable` conformance
- ✅ Automatic `snake_case` ↔ `camelCase` mapping
- ✅ Fire-and-forget method support

## Type Conversion

### Python → TypeScript

| Python Type | TypeScript Type |
|-------------|-----------------|
| `str` | `string` |
| `int`, `float` | `number` |
| `bool` | `boolean` |
| `List[T]` | `T[]` |
| `Dict[str, T]` | `{ [key: string]: T }` |
| `Optional[T]` | `T \| null` |
| `BaseModel` | `interface` |

### Python → Go

| Python Type | Go Type |
|-------------|---------|
| `str` | `string` |
| `int` | `int64` |
| `float` | `float64` |
| `bool` | `bool` |
| `List[T]` | `[]T` |
| `Dict[str, T]` | `map[string]T` |
| `Optional[T]` | `*T` |
| `BaseModel` | `struct` |

### Python → Swift

| Python Type | Swift Type |
|-------------|------------|
| `str` | `String` |
| `int` | `Int` |
| `float` | `Double` |
| `bool` | `Bool` |
| `List[T]` | `[T]` |
| `Dict[str, T]` | `[String: T]` |
| `Optional[T]` | `T?` |
| `BaseModel` | `struct: Codable, Sendable` |
| `Any` | `AnyCodable` |

## Naming Conventions

### Method Names

RPC method `tasks.get_stats` generates:

- **Python**: `api.tasks_get_stats(...)`
- **TypeScript**: `api.tasksGetStats(...)`
- **Go**: `api.TasksGetStats(ctx, ...)`
- **Swift**: `api.tasksGetStats(params:)`

### Model Names

Python model `TaskStatsParams` generates:

- **Python**: `TaskStatsParams` (unchanged)
- **TypeScript**: `TaskStatsParams` (interface)
- **Go**: `TaskStatsParams` (struct)
- **Swift**: `TaskStatsParams` (struct: Codable, Sendable)

## Generated Client Architecture

### Two-Layer Design

**Layer 1: Base RPC Client**
- Handles WebSocket connection
- Implements correlation ID pattern
- Publishes to `rpc.requests`
- Subscribes to `user#{user_id}`
- Matches responses by correlation_id

**Layer 2: Typed API Client**
- Thin wrapper over base client
- One type-safe method per RPC endpoint
- Automatic serialization/deserialization
- Full type checking

### Example Generated Code

**Python Client (`client.py`):**
```python
class APIClient:
    def __init__(self, rpc_client: CentrifugoRPCClient):
        self.rpc = rpc_client

    async def tasks_get_stats(
        self,
        params: TaskStatsParams
    ) -> TaskStatsResult:
        """Get task statistics for a user."""
        result = await self.rpc.call("tasks.get_stats", params.model_dump())
        return TaskStatsResult(**result)
```

**TypeScript Client (`client.ts`):**
```typescript
export class APIClient {
  constructor(private rpc: CentrifugoRPCClient) {}

  async tasksGetStats(params: TaskStatsParams): Promise<TaskStatsResult> {
    const result = await this.rpc.call('tasks.get_stats', params);
    return result as TaskStatsResult;
  }
}
```

**Go Client (`client.go`):**
```go
func (api *APIClient) TasksGetStats(
    ctx context.Context,
    params TaskStatsParams,
) (*TaskStatsResult, error) {
    result, err := api.rpc.Call(ctx, "tasks.get_stats", params)
    if err != nil {
        return nil, err
    }

    var response TaskStatsResult
    if err := json.Unmarshal(result, &response); err != nil {
        return nil, err
    }

    return &response, nil
}
```

**Swift Client (`APIClient.swift`):**
```swift
public actor CentrifugoAPIClient {
    private let rpc: CentrifugoRPCClient

    public init(rpc: CentrifugoRPCClient) {
        self.rpc = rpc
    }

    /// Get task statistics for a user.
    public func tasksGetStats(params: TaskStatsParams) async throws -> TaskStatsResult {
        return try await rpc.call(
            method: "tasks.get_stats",
            params: params
        )
    }
}
```

## Special Features

### Go Client: No GitHub Dependencies

The Go client uses `nhooyr.io/websocket` instead of GitHub-hosted libraries:

**Why?**
- ✅ Better for enterprise proxies
- ✅ Air-gapped environment support
- ✅ Clean module path
- ✅ Minimal dependencies

**go.mod:**
```go
module example.com/centrifugo_client

require (
    nhooyr.io/websocket v1.8.10
)
```

**UUID Generation:**
Uses `crypto/rand` from stdlib (no external UUID library):

```go
func generateUUID() string {
    b := make([]byte, 16)
    rand.Read(b)
    b[6] = (b[6] & 0x0f) | 0x40 // Version 4
    b[8] = (b[8] & 0x3f) | 0x80 // Variant is 10
    return fmt.Sprintf("%x-%x-%x-%x-%x",
        b[0:4], b[4:6], b[6:8], b[8:10], b[10:16])
}
```

### TypeScript: Full Type Safety

Generated TypeScript clients have complete type safety:

```typescript
// Type checking at compile time
const params: TaskStatsParams = {
  user_id: "123",
  include_completed: true
};

const result: TaskStatsResult = await api.tasksGetStats(params);
//    ^-- TypeScript knows the exact type

console.log(result.total);      // ✅ OK
console.log(result.completed);  // ✅ OK
console.log(result.invalid);    // ❌ Compile error
```

### Python: Runtime Validation

Python clients use Pydantic for runtime validation:

```python
# Valid params
params = TaskStatsParams(user_id="123", include_completed=True)
result = await api.tasks_get_stats(params)

# Invalid params - raises ValidationError
params = TaskStatsParams(user_id=123)  # ❌ Wrong type
```

### Swift: Modern Concurrency

Swift clients use modern Swift concurrency features:

```swift
// Actor-based API client (thread-safe)
let api = CentrifugoAPIClient(rpc: rpcClient)

// Async/await with automatic type inference
let result = try await api.tasksGetStats(params: params)
print(result.total)       // ✅ Type-safe
print(result.completed)   // ✅ Type-safe

// Fire-and-forget methods (no return value)
try await api.terminalInput(params: inputParams)  // Returns Void
```

**CodingKeys Auto-Generation:**
```swift
public struct TaskStatsParams: Codable, Sendable {
    public var userId: String      // camelCase in Swift
    public var includeCompleted: Bool

    enum CodingKeys: String, CodingKey {
        case userId = "user_id"    // snake_case in JSON
        case includeCompleted = "include_completed"
    }
}
```

## Testing Generated Clients

### Python

```bash
cd opensdk/python
pip install -r requirements.txt
python -c "from client import APIClient; print('✓ Import OK')"
```

### TypeScript

```bash
cd opensdk/typescript
npm install
npx tsc --noEmit  # Check types
```

### Go

```bash
cd opensdk/go
go mod tidy
go build .        # Compile
go vet .          # Static analysis
```

### Swift

```bash
cd opensdk/swift
swift build                    # Build package
swift test                     # Run tests (if any)
xcodebuild -scheme CentrifugoClient build  # Xcode build
```

Or add as local package in Xcode:
1. File → Add Package Dependencies
2. Choose "Add Local..."
3. Select `opensdk/swift` folder

## Continuous Integration

### GitHub Actions

```yaml
name: Generate Clients

on:
  push:
    paths:
      - 'core/centrifugo_handlers.py'

jobs:
  generate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Setup Python
        uses: actions/setup-python@v2
        with:
          python-version: '3.11'

      - name: Generate Clients
        run: |
          python manage.py generate_centrifugo_clients --output ./opensdk --all

      - name: Test Go Client
        run: |
          cd opensdk/go
          go build .

      - name: Test TS Client
        run: |
          cd opensdk/typescript
          npm install
          npx tsc --noEmit

      - name: Test Swift Client
        run: |
          cd opensdk/swift
          swift build

      - name: Commit Generated Clients
        run: |
          git add opensdk/
          git commit -m "chore: regenerate clients"
          git push
```

## Troubleshooting

### No Handlers Found

**Problem**: `Found 0 RPC methods`

**Solution**:
1. Verify handlers use `@websocket_rpc` decorator
2. Check handlers are imported in `apps.py`
3. Run Django shell:
   ```python
   from django_cfg.modules.django_centrifugo.router import get_global_router
   router = get_global_router()
   print(router.list_methods())
   ```

### Go Build Fails

**Problem**: `package nhooyr.io/websocket is not in GOROOT`

**Solution**:
```bash
cd opensdk/go
go mod tidy  # Download dependencies
```

### TypeScript Type Errors

**Problem**: `Property 'foo' does not exist`

**Solution**:
Regenerate clients after model changes:
```bash
python manage.py generate_centrifugo_clients --output ./opensdk --typescript
```

### Swift Build Errors

**Problem**: `Cannot find type 'AnyCodable' in scope`

**Solution**:
Make sure all generated files are included:
```bash
ls opensdk/swift/Sources/CentrifugoClient/
# Should include: AnyCodable.swift, Types.swift, APIClient.swift, RPCClient.swift
```

**Problem**: `Type 'X' does not conform to protocol 'Sendable'`

**Solution**:
All generated types are `Sendable`. If you're extending them, ensure your extensions are also `Sendable`.

## Next Steps

- **[Frontend Guide](./frontend-guide)** - Use generated clients in your app
- **[Architecture](./architecture)** - Understand client internals
- **[API Reference](./api-reference)** - Complete API docs

---


<Callout type="info">
**Generation Best Practices**
- ✅ Regenerate after handler changes
- ✅ Commit generated clients to git
- ✅ Test clients before deployment
- ✅ Version generated clients with your app
- ✅ Use CI/CD to auto-generate

</Callout>
