# Builders (Strict) This document defines the **Builder** pattern for `apps/website`. Builders exist to transform data between presentation model types. ## 1) Definition A **Builder** is a deterministic, side-effect free transformation between website presentation models. There are two types of builders: ### 1.1 ViewModel Builders Transform API Transport DTOs into ViewModels. **Purpose**: Prepare raw API data for client-side state management. **Location**: `apps/website/lib/builders/view-models/**` **Pattern**: ```typescript export class AdminViewModelBuilder { static build(dto: UserDto): AdminUserViewModel { return new AdminUserViewModel(dto); } } ``` ### 1.2 ViewData Builders Transform API DTOs directly into ViewData for templates. **Purpose**: Prepare API data for server-side rendering without ViewModels. **Location**: `apps/website/lib/builders/view-data/**` **Pattern**: ```typescript export class LeagueViewDataBuilder { static build(apiDto: LeagueApiDto): LeagueDetailViewData { return { leagueId: apiDto.id, name: apiDto.name, // ... more fields }; } } ``` ## 2) Non-negotiable rules ### ViewModel Builders 1. MUST be deterministic 2. MUST be side-effect free 3. MUST NOT perform HTTP 4. MUST NOT call API clients 5. MUST NOT access cookies/headers 6. Input: API Transport DTO 7. Output: ViewModel 8. MUST live in `lib/builders/view-models/**` ### ViewData Builders 1. MUST be deterministic 2. MUST be side-effect free 3. MUST NOT perform HTTP 4. MUST NOT call API clients 5. MUST NOT access cookies/headers 6. Input: API DTO 7. Output: ViewData 8. MUST live in `lib/builders/view-data/**` ## 3) Why two builder types? **ViewModel Builders** (API → Client State): - Bridge the API boundary - Convert transport types to client classes - Add client-only fields if needed - Run in client code **ViewData Builders** (API → Render Data): - Bridge the presentation boundary - Transform API data directly for templates - Format values for display - Run in server code (RSC) ## 4) Relationship to other patterns ``` API Transport DTO ↓ ViewModel Builder (lib/builders/view-models/) ↓ ViewModel (lib/view-models/) ↓ (for client components) API Transport DTO ↓ ViewData Builder (lib/builders/view-data/) ↓ ViewData (lib/templates/) ↓ Template (lib/templates/) ``` ## 5) Naming convention **ViewModel Builders**: `*ViewModelBuilder` - `AdminViewModelBuilder` - `RaceViewModelBuilder` **ViewData Builders**: `*ViewDataBuilder` - `LeagueViewDataBuilder` - `RaceViewDataBuilder` ## 6) File structure ``` lib/ builders/ view-models/ AdminViewModelBuilder.ts RaceViewModelBuilder.ts index.ts view-data/ LeagueViewDataBuilder.ts RaceViewDataBuilder.ts index.ts ``` ## 7) Usage examples ### ViewModel Builder (Client Component) ```typescript 'use client'; import { AdminViewModelBuilder } from '@/lib/builders/view-models/AdminViewModelBuilder'; import { AdminApiClient } from '@/lib/api/admin/AdminApiClient'; export function AdminPage() { const [users, setUsers] = useState([]); useEffect(() => { const apiClient = new AdminApiClient(); const dto = await apiClient.getUsers(); const viewModels = dto.map(d => AdminViewModelBuilder.build(d)); setUsers(viewModels); }, []); // ... render with viewModels } ``` ### ViewData Builder (Server Component) ```typescript import { LeagueViewDataBuilder } from '@/lib/builders/view-data/LeagueViewDataBuilder'; import { LeagueDetailPageQuery } from '@/lib/page-queries/LeagueDetailPageQuery'; export default async function LeagueDetailPage({ params }) { const apiDto = await LeagueDetailPageQuery.execute(params.id); const viewData = LeagueViewDataBuilder.build(apiDto); return ; } ``` ## 8) Common mistakes ❌ **Wrong**: Using "Presenter" for DTO → ViewModel ```typescript // DON'T export class AdminPresenter { static createViewModel(dto: UserDto): AdminUserViewModel { ... } } ``` ✅ **Correct**: Use ViewModelBuilder ```typescript export class AdminViewModelBuilder { static build(dto: UserDto): AdminUserViewModel { ... } } ``` ❌ **Wrong**: Using "Transformer" for ViewModel → ViewData ```typescript // DON'T export class RaceResultsDataTransformer { static transform(...): TransformedData { ... } } ``` ✅ **Correct**: Use ViewDataBuilder ```typescript export class RaceResultsViewDataBuilder { static build(...): RaceResultsViewData { ... } } ``` ## 9) Enforcement These rules are enforced by ESLint: - `gridpilot-rules/view-model-builder-contract` - `gridpilot-rules/view-data-builder-contract` - `gridpilot-rules/filename-view-model-builder-match` - `gridpilot-rules/filename-view-data-builder-match` See [`docs/architecture/website/WEBSITE_GUARDRAILS.md`](WEBSITE_GUARDRAILS.md) for details.