4.8 KiB
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:
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:
export class LeagueViewDataBuilder {
static build(apiDto: LeagueApiDto): LeagueDetailViewData {
return {
leagueId: apiDto.id,
name: apiDto.name,
// ... more fields
};
}
}
2) Non-negotiable rules
ViewModel Builders
- MUST be deterministic
- MUST be side-effect free
- MUST NOT perform HTTP
- MUST NOT call API clients
- MUST NOT access cookies/headers
- Input: API Transport DTO
- Output: ViewModel
- MUST live in
lib/builders/view-models/**
ViewData Builders
- MUST be deterministic
- MUST be side-effect free
- MUST NOT perform HTTP
- MUST NOT call API clients
- MUST NOT access cookies/headers
- Input: API DTO
- Output: ViewData
- 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
AdminViewModelBuilderRaceViewModelBuilder
ViewData Builders: *ViewDataBuilder
LeagueViewDataBuilderRaceViewDataBuilder
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)
'use client';
import { AdminViewModelBuilder } from '@/lib/builders/view-models/AdminViewModelBuilder';
import { AdminApiClient } from '@/lib/api/admin/AdminApiClient';
export function AdminPage() {
const [users, setUsers] = useState<AdminUserViewModel[]>([]);
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)
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 <LeagueDetailTemplate viewData={viewData} />;
}
8) Common mistakes
❌ Wrong: Using "Presenter" for DTO → ViewModel
// DON'T
export class AdminPresenter {
static createViewModel(dto: UserDto): AdminUserViewModel { ... }
}
✅ Correct: Use ViewModelBuilder
export class AdminViewModelBuilder {
static build(dto: UserDto): AdminUserViewModel { ... }
}
❌ Wrong: Using "Transformer" for ViewModel → ViewData
// DON'T
export class RaceResultsDataTransformer {
static transform(...): TransformedData { ... }
}
✅ Correct: Use ViewDataBuilder
export class RaceResultsViewDataBuilder {
static build(...): RaceResultsViewData { ... }
}
9) Enforcement
These rules are enforced by ESLint:
gridpilot-rules/view-model-builder-contractgridpilot-rules/view-data-builder-contractgridpilot-rules/filename-view-model-builder-matchgridpilot-rules/filename-view-data-builder-match
See docs/architecture/website/WEBSITE_GUARDRAILS.md for details.