Files
gridpilot.gg/docs/architecture/website/MUTATIONS.md
2026-01-13 01:36:27 +01:00

170 lines
4.3 KiB
Markdown

# Mutations (Strict)
This document defines the **Mutation** pattern for `apps/website`.
Mutations exist to provide framework-agnostic write operations.
## 1) Definition
A **Mutation** is a framework-agnostic operation that orchestrates writes.
Mutations are the write equivalent of PageQueries.
## 2) Relationship to Next.js Server Actions
**Server Actions are the entry point**, but they should be thin wrappers:
```typescript
// app/admin/actions.ts (Next.js framework code)
'use server';
import { AdminUserMutation } from '@/lib/mutations/admin/AdminUserMutation';
import { revalidatePath } from 'next/cache';
export async function updateUserStatus(userId: string, status: string) {
const mutation = new AdminUserMutation();
const result = await mutation.updateUserStatus(userId, status);
if (result.isErr()) {
console.error('updateUserStatus failed:', result.getError());
throw new Error('Failed to update user status');
}
revalidatePath('/admin/users');
}
```
## 3) Mutation Structure
```typescript
// lib/mutations/admin/AdminUserMutation.ts
import { Result, ResultFactory } from '@/lib/contracts/Result';
export class AdminUserMutation {
private service: AdminService;
constructor() {
// Manual DI for serverless
const logger = new ConsoleLogger();
const errorReporter = new EnhancedErrorReporter(logger, {
showUserNotifications: true,
logToConsole: true,
reportToExternal: process.env.NODE_ENV === 'production',
});
const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001';
const apiClient = new AdminApiClient(baseUrl, errorReporter, logger);
this.service = new AdminService(apiClient);
}
async updateUserStatus(userId: string, status: string): Promise<Result<void, string>> {
try {
await this.service.updateUserStatus(userId, status);
return ResultFactory.ok(undefined);
} catch (error) {
return ResultFactory.error('UPDATE_USER_STATUS_FAILED');
}
}
async deleteUser(userId: string): Promise<Result<void, string>> {
try {
await this.service.deleteUser(userId);
return ResultFactory.ok(undefined);
} catch (error) {
return ResultFactory.error('DELETE_USER_FAILED');
}
}
}
```
## 4) Why This Pattern?
**Benefits:**
1. **Framework independence** - Mutations can be tested without Next.js
2. **Consistent pattern** - Mirrors PageQueries for reads/writes
3. **Easy migration** - Can switch frameworks without rewriting business logic
4. **Testable** - Can unit test mutations in isolation
5. **Reusable** - Can be called from other contexts (cron jobs, etc.)
## 5) Naming Convention
- Mutations: `*Mutation.ts`
- Server Actions: `actions.ts` (thin wrappers)
## 6) File Structure
```
lib/
mutations/
admin/
AdminUserMutation.ts
AdminLeagueMutation.ts
league/
LeagueJoinMutation.ts
team/
TeamUpdateMutation.ts
```
## 7) Non-negotiable Rules
1. **Server Actions are thin wrappers** - They only handle framework concerns (revalidation, redirects)
2. **Mutations handle infrastructure** - They create services, handle errors
3. **Services handle business logic** - They orchestrate API calls
4. **Mutations are framework-agnostic** - No Next.js imports except in tests
5. **Mutations must be deterministic** - Same inputs = same outputs
## 8) Comparison with PageQueries
| Aspect | PageQuery | Mutation |
|--------|-----------|----------|
| Purpose | Read data | Write data |
| Location | `lib/page-queries/` | `lib/mutations/` |
| Framework | Called from RSC | Called from Server Actions |
| Infrastructure | Manual DI | Manual DI |
| Returns | `Result<ApiDto, string>` | `Result<void, string>` |
| Revalidation | N/A | Server Action handles it |
## 9) Example Flow
**Read (RSC):**
```
RSC page.tsx
PageQuery.execute()
Service
API Client
Result<ApiDto, string>
ViewDataBuilder
Template
```
**Write (Server Action):**
```
Client Component
Server Action
Mutation.execute()
Service
API Client
Result<void, string>
Revalidation
```
## 10) Enforcement
ESLint rules should enforce:
- Server Actions must call Mutations (not Services directly)
- Mutations must not import Next.js (except in tests)
- Mutations must use services
See `docs/architecture/website/WEBSITE_GUARDRAILS.md` for details.