# Write Flow Update (Mutation Pattern) This document updates the write flow section of WEBSITE_CONTRACT.md to use the Mutation pattern. ## Write Flow (Strict) All writes MUST enter through **Next.js Server Actions**. ### Forbidden - client components performing write HTTP requests - client components calling API clients for mutations ### Allowed - client submits intent (FormData, button action) - server action performs UX validation - **server action calls a Mutation** (not Services directly) - Mutation creates infrastructure and calls Service - Service orchestrates API calls and business logic ### Server Actions must use Mutations ```typescript // ❌ WRONG - Direct service usage 'use server'; import { AdminService } from '@/lib/services/admin/AdminService'; export async function updateUserStatus(userId: string, status: string) { const service = new AdminService(...); await service.updateUserStatus(userId, status); // ❌ Should use mutation } ``` ```typescript // ✅ CORRECT - Mutation usage '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'); } ``` ### Mutation Pattern ```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> { try { await this.service.updateUserStatus(userId, status); return ResultFactory.ok(undefined); } catch (error) { return ResultFactory.error('UPDATE_USER_STATUS_FAILED'); } } } ``` ### Flow 1. **Server Action** (thin wrapper) - handles framework concerns (revalidation, redirects) 2. **Mutation** (framework-agnostic) - creates infrastructure, calls service 3. **Service** (business logic) - orchestrates API calls 4. **API Client** (infrastructure) - makes HTTP requests 5. **Result** - type-safe error handling ### Rationale - **Framework independence**: Mutations can be tested without Next.js - **Consistency**: Mirrors PageQuery pattern for reads/writes - **Type-safe errors**: Result pattern eliminates exceptions - **Migration ease**: Can switch frameworks without rewriting business logic - **Testability**: Can unit test mutations in isolation - **Reusability**: Can be called from other contexts (cron jobs, etc.) ### 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` | `Result` | | Revalidation | N/A | Server Action handles it | ### See Also - [`MUTATIONS.md`](MUTATIONS.md) - Full mutation pattern documentation - [`SERVICES.md`](SERVICES.md) - Service layer documentation - [`WEBSITE_CONTRACT.md`](WEBSITE_CONTRACT.md) - Main contract