feat(boundaries): WP-03 — contracts purity + ApiClient confinement
Lint-enforce two architecture rules that were only documented (ADR-0001), landing the rules with the fixes so the build stays green: - contracts/ imports nothing: dashboard-view.dto.ts is now pure wire shapes (inline string-union enums, no domain imports). The DashboardView FE-view type moves to the adapter, which maps wire → domain (compiler-enforced seam). - ApiClient lives only in infrastructure: change-request-form (UI) no longer injects ApiClient — a new ChangeRequestAdapter owns the client and the submit becomes a createSubmitChangeRequest() command factory (createDraftSync shape). draft-sync's wire-DTO import becomes type-only (allowed via allowTypeImports). - Role type moves to shared/domain/role.ts; the ?role= reader stays in shared/infrastructure/role.ts. - eslint: contracts import-ban + @typescript-eslint/no-restricted-imports on api-client (value-only; type imports permitted; infra + shared/upload exempt). Also fixes a PRE-EXISTING bug found while verifying the flow: change-request-form never imported FormsModule, so (ngSubmit) didn't bind and the submit button did a native form submit (page reload) instead of submitting. Verified end-to-end in the running app: submit → command → adapter → backend → reference, success alert shown. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,36 +1,59 @@
|
||||
import { Registration } from '@registratie/domain/registration';
|
||||
import { Person } from '@registratie/domain/person';
|
||||
import { BigProfile } from '@registratie/domain/big-profile';
|
||||
|
||||
/**
|
||||
* WIRE CONTRACT for the dashboard screen — the "BFF-lite" response.
|
||||
*
|
||||
* In production this type is GENERATED from the OpenAPI/TypeSpec spec (one source
|
||||
* of truth for both sides), and the `decisions` block is computed BY THE BACKEND
|
||||
* — never recomputed on the client. The frontend renders decisions; it does not
|
||||
* own the rules. See docs/architecture/0001-bff-lite-decision-dtos.md.
|
||||
* PURE wire shapes: this file imports NOTHING (CLAUDE.md §1, ADR-0001). Enums are
|
||||
* inlined string-literal unions that describe the wire, not the domain. The
|
||||
* adapter's `parseDashboardView` validates this untrusted shape and MAPS it onto
|
||||
* the FE domain model (Registration/Person/BigProfile) — that map is the
|
||||
* decoupling seam: the wire can change without the domain following.
|
||||
*
|
||||
* In production these types are GENERATED from the OpenAPI/TypeSpec spec (one
|
||||
* source of truth for both sides), and the `decisions` block is computed BY THE
|
||||
* BACKEND — never recomputed on the client. The frontend renders decisions; it
|
||||
* does not own the rules. See docs/architecture/0001-bff-lite-decision-dtos.md.
|
||||
*
|
||||
* One screen-shaped call replaces the previous three (BIG-register + BRP + …),
|
||||
* so the page always sees one consistent snapshot instead of three independently
|
||||
* loading/erroring resources.
|
||||
*/
|
||||
export interface DashboardViewDto {
|
||||
registration: Registration;
|
||||
person: Person;
|
||||
decisions: HerregistratieDecisions;
|
||||
|
||||
/** Registration status on the wire: the discriminant tags as they arrive. */
|
||||
export type RegistrationStatusDto =
|
||||
| { tag: 'Geregistreerd'; herregistratieDatum: string } // ISO date
|
||||
| { tag: 'Geschorst'; geschorstTot: string; reden: string }
|
||||
| { tag: 'Doorgehaald'; doorgehaaldOp: string; reden: string };
|
||||
|
||||
export interface RegistrationDto {
|
||||
bigNummer: string;
|
||||
naam: string;
|
||||
beroep: string;
|
||||
registratiedatum: string; // ISO date
|
||||
geboortedatum: string;
|
||||
status: RegistrationStatusDto;
|
||||
}
|
||||
|
||||
/** Server-computed decisions. The eligibility rule lives on the backend; the
|
||||
optional reason lets the UI explain itself without knowing the rule. */
|
||||
export interface AdresDto {
|
||||
straat: string;
|
||||
postcode: string;
|
||||
woonplaats: string;
|
||||
}
|
||||
|
||||
export interface PersonDto {
|
||||
naam: string;
|
||||
geboortedatum: string; // ISO date
|
||||
adres: AdresDto;
|
||||
}
|
||||
|
||||
/** Server-computed decisions. Rendered by the FE as-is (decision DTO, ADR-0001):
|
||||
the eligibility rule lives on the backend; the optional reason lets the UI
|
||||
explain itself without knowing the rule. */
|
||||
export interface HerregistratieDecisions {
|
||||
eligibleForHerregistratie: boolean;
|
||||
herregistratieReason?: string;
|
||||
}
|
||||
|
||||
/** The parsed, frontend-side view (DTO mapped onto our own domain model). Keeping
|
||||
this distinct from DashboardViewDto is the decoupling seam: the wire shape can
|
||||
change without the FE domain following, and vice-versa. */
|
||||
export interface DashboardView {
|
||||
profile: BigProfile;
|
||||
export interface DashboardViewDto {
|
||||
registration: RegistrationDto;
|
||||
person: PersonDto;
|
||||
decisions: HerregistratieDecisions;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user