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

8.9 KiB

Website Architecture Contract (Strict)

This document is the authoritative contract for apps/website.

If any other website document conflicts with this one, this one wins.

1) Purpose (non-negotiable)

The website is a delivery layer.

It does not:

  • contain business rules
  • make authorization decisions
  • own or persist business truth

It only:

  • renders truth from apps/api
  • collects user intent
  • forwards user intent to apps/api

The API is the single source of truth.

2) System context (hard boundary)

The website never bypasses the API.

Browser
  ↓
Next.js App Router (RSC + Server Actions)
  ↓
HTTP
  ↓
Backend API (Use Cases, Domain, Database)

3) Website presentation model types (strict)

3.1 API Transport DTO

Definition: the shape returned by the backend API over HTTP.

Rules:

  • API Transport DTOs MUST be contained inside infrastructure.
  • API Transport DTOs MUST NOT be imported by Templates.

Canonical placement in this repo:

  • apps/website/lib/types/** (transport DTOs consumed by services and page queries)

3.2 API Transport DTO

Definition: the shape returned by the backend API over HTTP.

Rules:

  • API Transport DTOs MUST be contained inside infrastructure.
  • API Transport DTOs MUST NOT be imported by Templates.

Canonical placement in this repo:

  • apps/website/lib/types/** (transport DTOs consumed by services and page queries)

3.3 ViewData

Definition: the only allowed input type for Templates.

Rules:

  • JSON-serializable only.
  • Contains only template-ready values (mostly strings/numbers/booleans).
  • MUST NOT contain class instances.

See VIEW_DATA.md.

Canonical placement in this repo:

  • apps/website/templates/** (Templates that accept ViewData only)

3.3 ViewModel

Definition: the client-only, UI-owned class representing fully prepared UI state.

Rules:

  • Instantiated only in 'use client' modules.
  • Never serialized.
  • Used for client components that need state management.

See VIEW_MODELS.md.

Canonical placement in this repo:

  • apps/website/lib/view-models/**

4) Data transformation helpers (strict)

4.1 ViewModel Builder

Definition: transforms API Transport DTOs into ViewModels.

Purpose: prepare raw API data for client-side state management.

Rules:

  • MUST be deterministic.
  • MUST be side-effect free.
  • MUST NOT call HTTP.
  • MUST NOT call the API.
  • Input: Result<ApiDto, string> or ApiDto
  • Output: ViewModel

See BUILDERS.md.

Canonical placement in this repo:

  • apps/website/lib/builders/view-models/**

4.2 ViewData Builder

Definition: transforms API DTOs directly into ViewData for templates.

Purpose: prepare API data for server-side rendering.

Rules:

  • MUST be deterministic.
  • MUST be side-effect free.
  • MUST NOT call HTTP.
  • MUST NOT call the API.
  • Input: Result<ApiDto, string> or ApiDto
  • Output: ViewData

See BUILDERS.md.

Canonical placement in this repo:

  • apps/website/lib/builders/view-data/**

4.3 Result Type

Definition: Type-safe error handling for all operations.

Purpose: eliminate exceptions and provide explicit error paths.

Rules:

  • All PageQueries return Result<ApiDto, string>
  • All Mutations return Result<void, string>
  • Use ResultFactory.ok(value) for success
  • Use ResultFactory.error(message) for errors
  • Never throw exceptions

See Result.ts.

Canonical placement in this repo:

  • apps/website/lib/contracts/Result.ts

4.3 Display Object

Definition: deterministic, reusable, UI-only formatting/mapping logic.

Rules:

  • Class-based, immutable, deterministic.
  • MUST NOT call Intl.*.
  • MUST NOT call any toLocale*.
  • MUST NOT implement business rules.

See DISPLAY_OBJECTS.md.

Canonical placement in this repo:

  • apps/website/lib/display-objects/**

5) Read flow (strict)

Server Components (RSC)

RSC page.tsx
  ↓
PageQuery.execute()
  ↓
API client (infra)
  ↓
API Transport DTO
  ↓
Result<ApiDto, string>
  ↓
ViewData Builder (lib/builders/view-data/)
  ↓
ViewData
  ↓
Template

Client Components

Client Component
  ↓
API client (useEffect)
  ↓
API Transport DTO
  ↓
Result<ApiDto, string>
  ↓
ViewModel Builder (lib/builders/view-models/)
  ↓
ViewModel (lib/view-models/)
  ↓
Client State (useState)
  ↓
Template

6) 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 orchestrates services for writes

Server Actions must use Mutations:

// ❌ 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
}

// ✅ 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');
}

Pattern:

  1. Server Action (thin wrapper) - handles framework concerns (revalidation)
  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:

  • Mutations are framework-agnostic (can be tested without Next.js)
  • Consistent pattern with PageQueries
  • Type-safe error handling with Result
  • Makes infrastructure explicit and testable

See MUTATIONS.md and SERVICES.md.

7) Authorization (strict)

  • The website may hide/disable UI for UX.
  • The website MUST NOT enforce security.
  • The API enforces authentication and authorization.

See docs/architecture/shared/BLOCKERS_AND_GUARDS.md and docs/architecture/website/BLOCKERS.md.

7.1) Client state (strict)

Client-side state is allowed only for UI concerns.

Allowed:

  • selection
  • open/closed dialogs
  • transient form state
  • optimistic flags and loading spinners

Forbidden:

  • treating client state as business truth
  • using client state as an authorization decision
  • persisting client state as the source of truth

Hard rule:

  • any truth returned by the API MUST overwrite client assumptions.

Canonical placement in this repo:

  • apps/website/lib/blockers/** for UX-only prevention helpers
  • apps/website/lib/hooks/** for React-only utilities
  • apps/website/lib/command-models/** for transient form models

See CLIENT_STATE.md.

8) DI contract (Inversify) (strict)

The DI system under apps/website/lib/di/index.ts is client-first.

Server execution is concurrent. Any shared singleton container can leak cross-request state.

Rules:

  1. Server app/**/page.tsx MUST NOT access the container.
  2. Page Queries SHOULD prefer manual wiring.
  3. Client modules MAY use DI via ContainerProvider and hooks.
  4. ContainerManager.getContainer() is client-only.
  5. Any server DI usage MUST be request-scoped (a fresh container per request).

Hard constraint:

  • A singleton Inversify container MUST NOT be used to serve concurrent server requests.

See WEBSITE_DI_RULES.md.

9) Non-negotiable rules (final)

  1. The API is the brain.
  2. The website is a terminal.
  3. API Transport DTOs never reach Templates.
  4. Templates accept ViewData only.
  5. Page Queries do not format; they only compose.
  6. All operations return Result<T, E> for type-safe error handling.
  7. ViewData Builders transform API DTO → ViewData (RSC).
  8. ViewModel Builders transform API DTO → ViewModel (Client).
  9. Builders are pure and deterministic.
  10. Server Actions are the only write entry point.
  11. Server Actions must use Mutations (not Services directly).
  12. Mutations orchestrate Services for writes.
  13. Authorization always belongs to the API.