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:
@@ -92,4 +92,55 @@ export default [
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// contracts/ is the FE⇄BE wire seam: pure DTO shapes that must import NOTHING
|
||||
// (CLAUDE.md §1, ADR-0001) — not Angular, not a context alias, not relative app
|
||||
// code. Enums are inlined string-literal unions; the adapter's parse* maps them.
|
||||
// (This comes after the per-context rules so it wins for contracts files.)
|
||||
{
|
||||
files: ['src/app/**/contracts/**/*.ts'],
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
patterns: [
|
||||
{
|
||||
group: ['@angular/**', '@shared/**', '@auth/**', '@registratie/**', '@herregistratie/**', '@brief/**', './*', '../*', './**', '../**'],
|
||||
message: 'contracts/ is the wire seam — it must import NOTHING (pure DTO shapes). Map wire → domain in the infrastructure adapter, not here.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// BFF-lite anti-corruption boundary (ADR-0001): the ApiClient (the network
|
||||
// client) may be imported as a VALUE only from infrastructure-role files.
|
||||
// Type-only imports of generated wire DTOs are allowed anywhere — they grant no
|
||||
// network access. UI/application reach the network through an adapter or command.
|
||||
{
|
||||
files: ['src/app/**/*.ts'],
|
||||
plugins: { '@typescript-eslint': tseslint.plugin },
|
||||
rules: {
|
||||
'@typescript-eslint/no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
patterns: [
|
||||
{
|
||||
group: ['@shared/infrastructure/api-client'],
|
||||
allowTypeImports: true,
|
||||
message: 'The ApiClient lives only in infrastructure/ adapters (ADR-0001). UI/application call an adapter or a command, not the network client. (Type-only DTO imports are fine: use `import type`.)',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// …the infrastructure adapters ARE that boundary and own the client. shared/upload
|
||||
// is a feature-scoped adapter that lives outside a /infrastructure/ folder.
|
||||
{
|
||||
files: ['src/app/**/infrastructure/**/*.ts', 'src/app/shared/upload/**/*.ts'],
|
||||
plugins: { '@typescript-eslint': tseslint.plugin },
|
||||
rules: { '@typescript-eslint/no-restricted-imports': 'off' },
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user