# Migration Guide: Rapid → Balanced

This guide helps you migrate from Rapid level to Balanced level architecture.

## Overview

The main changes when moving to Balanced:

1. **New Packages**: Add `services/` and `validators/` packages
2. **Thin Routers**: Refactor routers from 100 lines to max 20 lines per procedure
3. **Service Layer**: Extract business logic to dedicated service functions
4. **Validators**: Centralize Zod schemas
5. **Testing**: Add comprehensive unit tests for services (70% coverage)

## Step-by-Step Migration

### Step 1: Create New Packages

```bash
# Create services package
mkdir -p packages/services/src
cp templates/balanced-web/packages/services/package.json packages/services/
cp templates/balanced-web/packages/services/tsconfig.json packages/services/
cp templates/balanced-web/packages/services/vitest.config.ts packages/services/

# Create validators package
mkdir -p packages/validators/src
cp templates/balanced-web/packages/validators/package.json packages/validators/
cp templates/balanced-web/packages/validators/tsconfig.json packages/validators/
```

### Step 2: Extract Validation Schemas

**Before (Rapid):**
```typescript
// packages/api/src/routers/auth.ts
import { z } from 'zod';

export const authRouter = router({
  register: publicProcedure
    .input(
      z.object({
        email: z.string().email(),
        password: z.string().min(8),
        name: z.string().optional(),
      })
    )
    .mutation(async ({ input }) => {
      // business logic here
    }),
});
```

**After (Balanced):**
```typescript
// packages/validators/src/auth.validator.ts
import { z } from 'zod';

export const registerSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
  name: z.string().optional(),
});

export type RegisterInput = z.infer<typeof registerSchema>;
```

### Step 3: Extract Business Logic to Services

**Before (Rapid):**
```typescript
// packages/api/src/routers/auth.ts
export const authRouter = router({
  register: publicProcedure
    .input(registerSchema)
    .mutation(async ({ input }) => {
      const data = await signUp(input);
      return {
        user: data.user,
        session: data.session,
      };
    }),
});
```

**After (Balanced):**

Create the service:
```typescript
// packages/services/src/auth.service.ts
import { signUp } from '@crafted/auth';
import type { RegisterInput } from '@crafted/validators';

export const registerUser = async (input: RegisterInput) => {
  const data = await signUp(input);
  return {
    user: data.user,
    session: data.session,
  };
};
```

Refactor the router:
```typescript
// packages/api/src/routers/auth.ts
import { publicProcedure, router } from '../trpc';
import { registerSchema } from '@crafted/validators';
import { registerUser } from '@crafted/services';

export const authRouter = router({
  register: publicProcedure
    .input(registerSchema)
    .mutation(async ({ input }) => registerUser(input)),
});
```

### Step 4: Add Service Tests

```typescript
// packages/services/src/auth.service.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { signUp } from '@crafted/auth';
import { registerUser } from './auth.service';

vi.mock('@crafted/auth');

describe('AuthService', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('registerUser', () => {
    it('should register a new user', async () => {
      const mockUser = { id: '1', email: 'test@example.com' };
      const mockSession = { id: 'session-1', userId: '1' };
      vi.mocked(signUp).mockResolvedValue({
        user: mockUser,
        session: mockSession,
      });

      const result = await registerUser({
        email: 'test@example.com',
        password: 'password123',
      });

      expect(result).toEqual({
        user: mockUser,
        session: mockSession,
      });
    });
  });
});
```

### Step 5: Update Package Dependencies

Add to routers package.json:
```json
{
  "dependencies": {
    "@crafted/services": "*",
    "@crafted/validators": "*"
  }
}
```

Add to services package.json:
```json
{
  "dependencies": {
    "@crafted/validators": "*",
    "@crafted/auth": "*",
    "@crafted/database": "*",
    "@crafted/payments": "*"
  },
  "devDependencies": {
    "vitest": "^1.0.4"
  }
}
```

### Step 6: Update Configuration

Update `.craftedrc.json`:
```json
{
  "level": "balanced",
  "rules": {
    "maxLinesPerFunction": 50,
    "maxParameters": 3,
    "testCoverage": 70
  }
}
```

### Step 7: Run Tests

```bash
# Install new dependencies
pnpm install

# Run tests
pnpm --filter @crafted/services test

# Check coverage
pnpm --filter @crafted/services test:coverage
```

## Migration Checklist

- [ ] Create `packages/services/` directory
- [ ] Create `packages/validators/` directory
- [ ] Extract all Zod schemas to validators
- [ ] Create service functions for each router
- [ ] Refactor routers to be thin (max 20 lines)
- [ ] Write unit tests for all services
- [ ] Achieve 70% test coverage
- [ ] Update package.json dependencies
- [ ] Update .craftedrc.json
- [ ] Run all tests successfully
- [ ] Update documentation

## Common Patterns

### Pattern 1: Simple CRUD Operations

**Rapid:**
```typescript
getAll: protectedProcedure.query(async ({ ctx }) => {
  const items = await db.item.findMany({
    where: { userId: ctx.userId },
  });
  return items;
});
```

**Balanced:**
```typescript
// Service
export const getAllUserItems = async (userId: string) => {
  return db.item.findMany({
    where: { userId },
  });
};

// Router
getAll: protectedProcedure.query(async ({ ctx }) =>
  getAllUserItems(ctx.userId)
),
```

### Pattern 2: Complex Business Logic

**Rapid:**
```typescript
cancel: protectedProcedure.mutation(async ({ ctx }) => {
  const subscription = await db.subscription.findFirst({
    where: { userId: ctx.userId, status: 'active' },
  });

  if (!subscription) {
    throw new Error('No active subscription');
  }

  await db.subscription.update({
    where: { id: subscription.id },
    data: { cancelAtPeriodEnd: true },
  });

  return { success: true };
});
```

**Balanced:**
```typescript
// Service
export const cancelUserSubscription = async (userId: string) => {
  const subscription = await db.subscription.findFirst({
    where: { userId, status: 'active' },
  });

  if (!subscription) {
    throw new Error('No active subscription');
  }

  await db.subscription.update({
    where: { id: subscription.id },
    data: { cancelAtPeriodEnd: true },
  });

  return { success: true };
};

// Router
cancel: protectedProcedure.mutation(async ({ ctx }) =>
  cancelUserSubscription(ctx.userId)
),
```

## Benefits After Migration

1. **Better Testability**: Services can be unit tested in isolation
2. **Code Reusability**: Services can be shared (future web+mobile)
3. **Cleaner Routers**: Easier to understand API structure
4. **Type Safety**: Centralized validators improve type inference
5. **Maintainability**: Clear separation of concerns
6. **Scalability**: Easier to add features without bloat

## Troubleshooting

### Issue: Circular Dependencies

**Problem**: Services import from database, database imports from services

**Solution**: Keep dependencies unidirectional:
- Routers → Services → Database/Auth/Payments
- Never reverse the flow

### Issue: Tests Failing

**Problem**: Mocks not working correctly

**Solution**: Ensure you're using `vi.mock()` at the top level:
```typescript
vi.mock('@crafted/auth');  // Top level, before describe

describe('AuthService', () => {
  beforeEach(() => {
    vi.clearAllMocks();  // Clear in beforeEach
  });
});
```

### Issue: Type Errors

**Problem**: TypeScript can't find validator types

**Solution**: Export types from validators:
```typescript
export const registerSchema = z.object({ /* ... */ });
export type RegisterInput = z.infer<typeof registerSchema>;
```

## Next Steps

After successful migration to Balanced:

1. Monitor test coverage (aim for 70%+)
2. Consider adding E2E tests for critical flows
3. Add integration tests for routers
4. Set up CI/CD with test coverage checks
5. Consider migrating to Crafted when team > 10 developers
