# Builders (Strict) This document defines the **Builder** pattern for `apps/website`. Builders exist to transform raw API data into flat, serializable **ViewData**. ## 1) Definition A **Builder** is a deterministic, side-effect free transformation that bridges the boundary between the API (DTOs) and the Template (ViewData). ### 1.1 ViewData Builders Transform API DTOs directly into ViewData for templates. **Purpose**: Prepare API data for server-side rendering. They ensure that logic-rich behavior is stripped away, leaving only a "dumb" JSON structure safe for SSR and hydration. **Location**: `apps/website/lib/builders/view-data/**` **Pattern**: ```typescript import { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder'; export class LeagueViewDataBuilder { static build(apiDto: LeagueApiDto): LeagueDetailViewData { return { leagueId: apiDto.id, name: apiDto.name, // ... more fields }; } } // Enforce static compliance without dummy instances LeagueViewDataBuilder satisfies ViewDataBuilder; ``` ## 2) Non-negotiable rules ### ViewData Builders 1. MUST be deterministic. 2. MUST be side-effect free. 3. MUST NOT perform HTTP or call API clients. 4. Input: API DTO. 5. Output: ViewData (Plain JSON). 6. MUST live in `lib/builders/view-data/**`. 7. MUST use `static build()` and `satisfies ViewDataBuilder`. 8. MUST use **Formatters** for primitive output (strings/numbers). ## 3) Why no ViewModel Builders? **ViewModels are self-building.** A ViewModel is a class that wraps data to provide behavior. Instead of a separate builder class, ViewModels are instantiated directly from ViewData using their **Constructor**. This removes unnecessary "ceremony" and keeps the API unambiguous. **❌ Redundant Pattern (Forbidden):** ```typescript // Why have this extra class? export class TeamViewModelBuilder { static build(data: TeamViewData): TeamViewModel { return new TeamViewModel(data); } } ``` **✅ Clean Pattern (Required):** ```typescript // Just use the class itself in the ClientWrapper const vm = new TeamViewModel(viewData); ``` ## 4) Relationship to other patterns ``` API Transport DTO ↓ ViewData Builder (lib/builders/view-data/) ↓ Formatters (lib/display-objects/) -- [primitive output] ↓ ViewData (Plain JSON) ↓ Template (SSR) ↓ ViewModel (lib/view-models/) -- [new ViewModel(viewData)] ↓ Display Objects (lib/display-objects/) -- [rich API] ``` ## 5) Naming convention **ViewData Builders**: `*ViewDataBuilder` - `LeagueViewDataBuilder` - `RaceViewDataBuilder` ## 6) Usage example (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); // Transform to flat JSON for SSR const viewData = LeagueViewDataBuilder.build(apiDto); return ; } ``` ## 7) Enforcement These rules are enforced by ESLint: - `gridpilot-rules/view-data-builder-contract` - `gridpilot-rules/filename-view-data-builder-match` - `gridpilot-rules/formatters-must-return-primitives` See [`docs/architecture/website/WEBSITE_CONTRACT.md`](WEBSITE_CONTRACT.md) for the authoritative contract.