# Type Safety Setup and Configuration

This document describes how to set up and maintain type consistency between the Python Pydantic backend and TypeScript frontend.

## Files Overview

### 1. TypeScript Types (`front/src/types/types.ts`)
- Complete TypeScript type definitions for all entities
- Includes enums, interfaces, and utility types
- Used by React components and API calls

### 2. Pydantic Models (`src/models.py`)
- Python data models with validation
- Backend API request/response validation
- Database schema definitions

### 3. Type Generation Tool (`src/generate_types.py`)
- Automated TypeScript generation from Pydantic models
- Keeps types in sync
- Watch mode for development

## Setup Instructions

### 2. Generate TypeScript Types

Run the type generation tool:

```bash
# One-time generation
docker compose exec backstory shell "python src/generate_types.py --source src/models.py --output frontend/src/types/types.ts"

# Watch mode for development
docker compose exec backstory shell "python src/generate_types.py --source src/models.py --output frontend/src/types/types.ts --watch"
```

### 3. API Client Setup

Create an API client that uses the types:

```typescript
// api/client.ts
import * as Types from '../types/types';
import { formatApiRequest, parseApiResponse } from '../types/conversion';

class ApiClient {
  private baseUrl: string;

  constructor(baseUrl: string) {
    this.baseUrl = baseUrl;
  }

  async createCandidate(candidate: Types.Candidate): Promise<Types.Candidate> {
    const response = await fetch(`${this.baseUrl}/candidates`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(formatApiRequest(candidate))
    });

    const data = await response.json();
    const apiResponse = parseApiResponse<Types.Candidate>(data);
    
    if (!apiResponse.success) {
      throw new Error(apiResponse.error?.message || 'API request failed');
    }
    
    return apiResponse.data!;
  }

  async getCandidates(request: Types.PaginatedRequest): Promise<Types.PaginatedResponse<Types.Candidate>> {
    const params = new URLSearchParams(formatApiRequest(request));
    const response = await fetch(`${this.baseUrl}/candidates?${params}`);
    const data = await response.json();
    
    return parseApiResponse<Types.PaginatedResponse<Types.Candidate>>(data).data!;
  }
}

export default ApiClient;
```

### 4. Backend API Setup

Use Pydantic models in your FastAPI/Flask routes:

```python
# api/routes.py (FastAPI example)
from fastapi import FastAPI, HTTPException
from typing import List
from models import Candidate, PaginatedRequest, PaginatedResponse, ApiResponse

app = FastAPI()

@app.post("/candidates", response_model=ApiResponse[Candidate])
async def create_candidate(candidate: Candidate):
    try:
        # Validate and save candidate
        saved_candidate = await save_candidate(candidate)
        return ApiResponse(success=True, data=saved_candidate)
    except Exception as e:
        return ApiResponse(success=False, error={"code": "CREATION_FAILED", "message": str(e)})

@app.get("/candidates", response_model=ApiResponse[PaginatedResponse[Candidate]])
async def get_candidates(request: PaginatedRequest):
    try:
        candidates = await fetch_candidates(request)
        return ApiResponse(success=True, data=candidates)
    except Exception as e:
        return ApiResponse(success=False, error={"code": "FETCH_FAILED", "message": str(e)})
```

## Development Workflow

### 1. Making Changes

When you modify data structures:

1. **Update Pydantic models first** in `models.py`
2. **Regenerate TypeScript types** using the generation tool
3. **Update API endpoints** to use new models
4. **Update frontend components** to use new types
5. **Run validation** to ensure consistency

### 2. Type Generation Automation

For automatic type generation in development, add to your package.json:

```json
{
  "scripts": {
    "dev": "concurrently \"npm run dev:frontend\" \"npm run dev:types\"",
    "dev:frontend": "react-scripts start",
    "dev:types": "python ../backend/pydantic_to_typescript.py --input ../backend/models.py --output src/types/types.ts --watch",
    "generate-types": "python ../backend/pydantic_to_typescript.py --input ../backend/models.py --output src/types/types.ts"
  }
}
```

### 3. Validation Strategy

Use validation at multiple layers:

```typescript
// Component validation example
import { validateData } from '../types/validation';

const UserForm: React.FC = () => {
  const [formData, setFormData] = useState<Partial<Types.Candidate>>({});
  const [errors, setErrors] = useState<ValidationError[]>([]);

  const handleSubmit = async () => {
    const validation = validateData<Types.Candidate>(formData, 'Candidate');
    
    if (!validation.isValid) {
      setErrors(validation.errors);
      return;
    }

    try {
      await apiClient.createCandidate(validation.data!);
      // Success handling
    } catch (error) {
      // Error handling
    }
  };

  return (
    // Form JSX with error display
  );
};
```

## Testing and Validation

### 1. Type Consistency Tests

Create tests to ensure types stay in sync:

```typescript
// tests/type-consistency.test.ts
import { candidateFromPydantic, candidateToPydantic } from '../types/conversion';

describe('Type Consistency', () => {
  test('candidate conversion roundtrip', () => {
    const originalCandidate: Types.Candidate = {
      id: '123e4567-e89b-12d3-a456-426614174000',
      email: 'test@example.com',
      userType: 'candidate',
      firstName: 'John',
      lastName: 'Doe',
      skills: [],
      experience: [],
      education: [],
      preferredJobTypes: ['full-time'],
      location: { city: 'Austin', country: 'USA' },
      languages: [],
      certifications: [],
      createdAt: new Date(),
      updatedAt: new Date(),
      status: 'active'
    };

    // Convert to Python format and back
    const pythonFormat = candidateToPydantic(originalCandidate);
    const backToTypeScript = candidateFromPydantic(pythonFormat);

    expect(backToTypeScript).toEqual(originalCandidate);
  });

  test('validation consistency', () => {
    const invalidCandidate = {
      id: 'invalid-uuid',
      email: 'not-an-email',
      userType: 'invalid-type'
    };

    const validation = validateData<Types.Candidate>(invalidCandidate, 'Candidate');
    expect(validation.isValid).toBe(false);
    expect(validation.errors.length).toBeGreaterThan(0);
  });
});
```

### 2. API Integration Tests

Test the full API integration:

```python
# tests/test_api_integration.py
import pytest
from fastapi.testclient import TestClient
from api.routes import app
from models import Candidate

client = TestClient(app)

def test_candidate_creation():
    candidate_data = {
        "email": "test@example.com",
        "user_type": "candidate",
        "first_name": "John",
        "last_name": "Doe",
        "skills": [],
        "experience": [],
        "education": [],
        "preferred_job_types": ["full-time"],
        "location": {"city": "Austin", "country": "USA"},
        "languages": [],
        "certifications": []
    }
    
    response = client.post("/candidates", json=candidate_data)
    assert response.status_code == 200
    
    data = response.json()
    assert data["success"] is True
    assert data["data"]["email"] == candidate_data["email"]

def test_type_validation():
    invalid_data = {
        "email": "not-an-email",
        "user_type": "invalid"
    }
    
    response = client.post("/candidates", json=invalid_data)
    assert response.status_code == 422  # Validation error
```

## Best Practices

### 1. Field Naming Conventions

- **TypeScript**: Use `camelCase` for consistency with JavaScript conventions
- **Python**: Use `snake_case` for consistency with Python conventions
- **API**: Always use the conversion utilities to transform between formats

### 2. Date Handling

```typescript
// Always use Date objects in TypeScript
const user: Types.BaseUser = {
  // ...
  createdAt: new Date(),
  updatedAt: new Date()
};

// Convert to ISO string for API
const apiData = formatApiRequest(user);
// apiData.created_at will be "2024-01-01T00:00:00.000Z"
```

```python
# Use datetime objects in Python
from datetime import datetime
from models import BaseUser

user = BaseUser(
    # ...
    created_at=datetime.utcnow(),
    updated_at=datetime.utcnow()
)
```

### 3. Optional Fields

Handle optional fields consistently:

```typescript
// TypeScript - use optional chaining
const user: Types.Candidate = getUser();
const profileImage = user.profileImage ?? '/default-avatar.png';
```

```python
# Python - use Optional type hints
from typing import Optional
from pydantic import BaseModel

class User(BaseModel):
    profile_image: Optional[str] = None
```

### 4. Enum Synchronization

Keep enums in sync between TypeScript and Python:

```typescript
// TypeScript
export type UserStatus = 'active' | 'inactive' | 'pending' | 'banned';
```

```python
# Python
from enum import Enum

class UserStatus(str, Enum):
    ACTIVE = "active"
    INACTIVE = "inactive" 
    PENDING = "pending"
    BANNED = "banned"
```

## CI/CD Integration

### 1. Type Generation in CI

Add type generation to your CI pipeline:

```yaml
# .github/workflows/ci.yml
name: CI/CD Pipeline

on: [push, pull_request]

jobs:
  type-sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Python
        uses: actions/setup-python@v3
        with:
          python-version: '3.9'
          
      - name: Install Python dependencies
        run: pip install pydantic typing-inspect jinja2
        
      - name: Generate TypeScript types
        run: python backend/pydantic_to_typescript.py --input backend/models.py --output frontend/src/types/types.ts
        
      - name: Check for type changes
        run: |
          if git diff --exit-code frontend/src/types/types.ts; then
            echo "Types are in sync"
          else
            echo "Types are out of sync!"
            exit 1
          fi

  test:
    needs: type-sync
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      # ... rest of test steps
```

### 2. Pre-commit Hooks

Set up pre-commit hooks to ensure types stay in sync:

```yaml
# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: generate-types
        name: Generate TypeScript types
        entry: python backend/pydantic_to_typescript.py --input backend/models.py --output frontend/src/types/types.ts
        language: system
        files: backend/models.py
        pass_filenames: false
        
      - id: validate-types
        name: Validate type consistency
        entry: npm run test:types
        language: system
        files: 'frontend/src/types/.*\.(ts|js)
        pass_filenames: false
```

## Troubleshooting

### Common Issues

1. **Date Serialization Errors**
   - Ensure dates are converted to ISO strings before API calls
   - Use the conversion utilities consistently

2. **Field Name Mismatches**
   - Always use the conversion utilities
   - Check that Pydantic field aliases match TypeScript property names

3. **Type Generation Failures**
   - Ensure all dependencies are installed
   - Check that the input Python file is valid
   - Verify that all Pydantic models extend BaseModel

4. **Validation Inconsistencies**
   - Keep validation rules in sync between TypeScript and Python
   - Use the same enum values and constraints

### Debugging Tools

1. **Type Validation Debugging**
```typescript
// Add debug logging
const validation = validateData<Types.Candidate>(data, 'Candidate');
if (!validation.isValid) {
  console.log('Validation errors:', validation.errors);
  validation.errors.forEach(error => {
    console.log(`Field: ${error.field}, Message: ${error.message}, Value:`, error.value);
  });
}
```

2. **Conversion Debugging**
```typescript
// Log conversion results
const originalData = { firstName: 'John', lastName: 'Doe' };
const converted = toSnakeCase(originalData);
console.log('Original:', originalData);
console.log('Converted:', converted);
const backConverted = toCamelCase(converted);
console.log('Back converted:', backConverted);
```

## Performance Considerations

### 1. Type Generation
- Run type generation only when models change
- Use file watching in development
- Cache generated types in production builds

### 2. Validation
- Use validation strategically (form submission, API boundaries)
- Consider lazy validation for large datasets
- Cache validation results when appropriate

### 3. Conversion
- Minimize conversions in hot paths
- Consider keeping data in the appropriate format for the layer
- Use object pooling for frequently converted objects

## Maintenance

### Regular Tasks

1. **Weekly**: Review type generation logs for any issues
2. **Monthly**: Update dependencies and test compatibility
3. **Quarterly**: Review and optimize validation rules
4. **Annually**: Evaluate new tools and migration paths

### Monitoring

Set up monitoring for:
- Type generation failures
- Validation error rates
- API request/response format mismatches
- Performance impacts of type operations

This setup ensures type safety and consistency across your full-stack application while maintaining developer productivity and code quality.