chore(deps): update npm packages within declared ranges; reformat for prettier 3.9.4
Some checks failed
CI / frontend (push) Successful in 1m46s
CI / storybook-a11y (push) Successful in 4m23s
CI / backend (push) Successful in 1m14s
CI / codeql (csharp) (push) Has been cancelled
CI / codeql (javascript-typescript) (push) Has been cancelled
CI / api-client-drift (push) Has been cancelled
CI / e2e (push) Has been cancelled
Some checks failed
CI / frontend (push) Successful in 1m46s
CI / storybook-a11y (push) Successful in 4m23s
CI / backend (push) Successful in 1m14s
CI / codeql (csharp) (push) Has been cancelled
CI / codeql (javascript-typescript) (push) Has been cancelled
CI / api-client-drift (push) Has been cancelled
CI / e2e (push) Has been cancelled
npm update brought every package to the latest version its existing package.json range allows (Angular tooling 22.0.2/22.0.4 -> 22.0.5, prettier 3.8.4 -> 3.9.4, typescript-eslint 8.62.0 -> 8.62.1); package.json itself needed no range changes. Auditing actual deprecation warnings (not just outdated versions) found nothing further to fix: @angular/platform-browser-dynamic and @angular-devkit/build-angular are deprecated by Angular but still required peer dependencies of the latest published @storybook/angular (10.4.6 — peer range still `>=18.0.0 < 22.0.0`, already why .npmrc sets legacy-peer-deps); jest-process-manager/expect-playwright are transitive-only through @storybook/test-runner's latest stable (0.24.4). No newer version of either Storybook package exists yet that drops them. The remaining npm audit advisory (@babel/core, low severity) is the same already-documented, deliberately-left issue in README.md (fixing it downgrades Angular). Left package.json's overrides untouched. The prettier bump alone changed formatting opinions on files this session didn't otherwise touch (a stale markdown italics marker, a few object-literal wrap points) — reformatted everything so `format:check` (part of CI) doesn't regress. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -82,7 +82,7 @@ model a discriminated union instead.** Three tools, all in `shared/application`:
|
|||||||
machine's State/Msg types are context-prefixed (`ChangeRequestState`,
|
machine's State/Msg types are context-prefixed (`ChangeRequestState`,
|
||||||
`ChangeRequestMsg`), never bare `State`/`Msg`; a top-level machine exports
|
`ChangeRequestMsg`), never bare `State`/`Msg`; a top-level machine exports
|
||||||
`initial` + `reduce`. A **composable sub-machine** embedded inside a parent
|
`initial` + `reduce`. A **composable sub-machine** embedded inside a parent
|
||||||
model keeps prefixed *value* exports instead (`initialUpload`/`reduceUpload`,
|
model keeps prefixed _value_ exports instead (`initialUpload`/`reduceUpload`,
|
||||||
see `upload.machine.ts`) — prefixing there avoids alias noise at the
|
see `upload.machine.ts`) — prefixing there avoids alias noise at the
|
||||||
composition site.
|
composition site.
|
||||||
- **`Result<E,T>` + value objects** ("parse, don't validate") — raw input becomes a
|
- **`Result<E,T>` + value objects** ("parse, don't validate") — raw input becomes a
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Status: done (69880ef)
|
|||||||
Phase: 2 — CIBG fidelity
|
Phase: 2 — CIBG fidelity
|
||||||
|
|
||||||
> **Deviation:** file-input's label-button was already reworked to `.btn-primary
|
> **Deviation:** file-input's label-button was already reworked to `.btn-primary
|
||||||
> .btn-upload` by the earlier out-of-order "CIBG UI fidelity pass" (WP-11/12) — the
|
.btn-upload` by the earlier out-of-order "CIBG UI fidelity pass" (WP-11/12) — the
|
||||||
> vendored upload vocabulary (`.btn-upload`) supersedes this WP's original
|
> vendored upload vocabulary (`.btn-upload`) supersedes this WP's original
|
||||||
> `.btn-secondary` assumption, so no change was needed there. Icon affordances
|
> `.btn-secondary` assumption, so no change was needed there. Icon affordances
|
||||||
> (chevron/pijl classes) are verified present in the vendored CSS, but no in-scope
|
> (chevron/pijl classes) are verified present in the vendored CSS, but no in-scope
|
||||||
|
|||||||
4237
package-lock.json
generated
4237
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -79,7 +79,10 @@ describe('BriefStore action state (Idle | Busy | Failed)', () => {
|
|||||||
|
|
||||||
approveResult = {
|
approveResult = {
|
||||||
ok: true,
|
ok: true,
|
||||||
value: { ...view, brief: { ...brief, status: { tag: 'approved', approvedBy: 'u2', approvedAt: 't' } } },
|
value: {
|
||||||
|
...view,
|
||||||
|
brief: { ...brief, status: { tag: 'approved', approvedBy: 'u2', approvedAt: 't' } },
|
||||||
|
},
|
||||||
};
|
};
|
||||||
await store.approve();
|
await store.approve();
|
||||||
expect(store.busy()).toBe(false);
|
expect(store.busy()).toBe(false);
|
||||||
|
|||||||
@@ -8,10 +8,16 @@ import { parsePostcode } from '@registratie/domain/value-objects/postcode';
|
|||||||
const postcode = parsePostcode('2514 EA');
|
const postcode = parsePostcode('2514 EA');
|
||||||
if (!postcode.ok) throw new Error('fixture postcode should parse');
|
if (!postcode.ok) throw new Error('fixture postcode should parse');
|
||||||
|
|
||||||
const data: Valid = { straat: 'Lange Voorhout 9', postcode: postcode.value, woonplaats: 'Den Haag' };
|
const data: Valid = {
|
||||||
|
straat: 'Lange Voorhout 9',
|
||||||
|
postcode: postcode.value,
|
||||||
|
woonplaats: 'Den Haag',
|
||||||
|
};
|
||||||
|
|
||||||
function setup(adapter: Partial<ChangeRequestAdapter>) {
|
function setup(adapter: Partial<ChangeRequestAdapter>) {
|
||||||
TestBed.configureTestingModule({ providers: [{ provide: ChangeRequestAdapter, useValue: adapter }] });
|
TestBed.configureTestingModule({
|
||||||
|
providers: [{ provide: ChangeRequestAdapter, useValue: adapter }],
|
||||||
|
});
|
||||||
return TestBed.runInInjectionContext(() => createSubmitChangeRequest());
|
return TestBed.runInInjectionContext(() => createSubmitChangeRequest());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ describe('change-request reduce', () => {
|
|||||||
it('SetField updates the draft while editing', () => {
|
it('SetField updates the draft while editing', () => {
|
||||||
const s = reduce(initial, { tag: 'SetField', key: 'straat', value: 'Lange Voorhout 9' });
|
const s = reduce(initial, { tag: 'SetField', key: 'straat', value: 'Lange Voorhout 9' });
|
||||||
expect(s.tag).toBe('Editing');
|
expect(s.tag).toBe('Editing');
|
||||||
expect((s as Extract<ChangeRequestState, { tag: 'Editing' }>).draft.straat).toBe('Lange Voorhout 9');
|
expect((s as Extract<ChangeRequestState, { tag: 'Editing' }>).draft.straat).toBe(
|
||||||
|
'Lange Voorhout 9',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Submit with an invalid draft stays Editing and reports field errors', () => {
|
it('Submit with an invalid draft stays Editing and reports field errors', () => {
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import { parseAantekening } from './big-register.adapter';
|
|||||||
|
|
||||||
describe('big-register.adapter parse boundary', () => {
|
describe('big-register.adapter parse boundary', () => {
|
||||||
it('parses known aantekening types', () => {
|
it('parses known aantekening types', () => {
|
||||||
expect(parseAantekening({ type: 'Specialisme', omschrijving: 'x', datum: '2026-01-01' })).toEqual({
|
expect(
|
||||||
|
parseAantekening({ type: 'Specialisme', omschrijving: 'x', datum: '2026-01-01' }),
|
||||||
|
).toEqual({
|
||||||
ok: true,
|
ok: true,
|
||||||
value: { type: 'Specialisme', omschrijving: 'x', datum: '2026-01-01' },
|
value: { type: 'Specialisme', omschrijving: 'x', datum: '2026-01-01' },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,7 +10,12 @@ import {
|
|||||||
} from '@registratie/ui/address-fields/address-fields.component';
|
} from '@registratie/ui/address-fields/address-fields.component';
|
||||||
import { createStore } from '@shared/application/store';
|
import { createStore } from '@shared/application/store';
|
||||||
import { whenTag } from '@shared/kernel/fp';
|
import { whenTag } from '@shared/kernel/fp';
|
||||||
import { ChangeRequestState, ChangeRequestMsg, initial, reduce } from '@registratie/domain/change-request.machine';
|
import {
|
||||||
|
ChangeRequestState,
|
||||||
|
ChangeRequestMsg,
|
||||||
|
initial,
|
||||||
|
reduce,
|
||||||
|
} from '@registratie/domain/change-request.machine';
|
||||||
import { createSubmitChangeRequest } from '@registratie/application/submit-change-request';
|
import { createSubmitChangeRequest } from '@registratie/application/submit-change-request';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ export function assertNever(x: never): never {
|
|||||||
/** A computation that either succeeded with a value or failed with an error.
|
/** A computation that either succeeded with a value or failed with an error.
|
||||||
Plain objects (no classes) to match the signal/httpResource ergonomics. */
|
Plain objects (no classes) to match the signal/httpResource ergonomics. */
|
||||||
export type Result<E, T> =
|
export type Result<E, T> =
|
||||||
| { readonly ok: true; readonly value: T }
|
{ readonly ok: true; readonly value: T } | { readonly ok: false; readonly error: E };
|
||||||
| { readonly ok: false; readonly error: E };
|
|
||||||
|
|
||||||
export const ok = <T>(value: T): Result<never, T> => ({ ok: true, value });
|
export const ok = <T>(value: T): Result<never, T> => ({ ok: true, value });
|
||||||
export const err = <E>(error: E): Result<E, never> => ({ ok: false, error });
|
export const err = <E>(error: E): Result<E, never> => ({ ok: false, error });
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ const meta: Meta<WizardShellComponent> = {
|
|||||||
cibgGap: true,
|
cibgGap: true,
|
||||||
docs: {
|
docs: {
|
||||||
description: {
|
description: {
|
||||||
component:
|
component: 'CIBG-gap extension (error summary only) — see Foundations/CIBG Gap Register.',
|
||||||
'CIBG-gap extension (error summary only) — see Foundations/CIBG Gap Register.',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -245,10 +245,7 @@ export class RichTextEditorComponent {
|
|||||||
}
|
}
|
||||||
if (!(e.ctrlKey || e.metaKey) || e.altKey) return;
|
if (!(e.ctrlKey || e.metaKey) || e.altKey) return;
|
||||||
const cmd = { b: 'bold', i: 'italic', u: 'underline' }[e.key.toLowerCase()] as
|
const cmd = { b: 'bold', i: 'italic', u: 'underline' }[e.key.toLowerCase()] as
|
||||||
| 'bold'
|
'bold' | 'italic' | 'underline' | undefined;
|
||||||
| 'italic'
|
|
||||||
| 'underline'
|
|
||||||
| undefined;
|
|
||||||
if (!cmd) return;
|
if (!cmd) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.format(cmd);
|
this.format(cmd);
|
||||||
|
|||||||
@@ -83,8 +83,7 @@ export type UploadMsg =
|
|||||||
type: 'BackgroundUploadsReturned';
|
type: 'BackgroundUploadsReturned';
|
||||||
results: Array<
|
results: Array<
|
||||||
{ localId: string } & (
|
{ localId: string } & (
|
||||||
| { success: true; documentId: string }
|
{ success: true; documentId: string } | { success: false; reason: string }
|
||||||
| { success: false; reason: string }
|
|
||||||
)
|
)
|
||||||
>;
|
>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,17 +21,17 @@ placed above the `@Component` decorator, plus `parameters: { cibgGap: true }` an
|
|||||||
|
|
||||||
## The register
|
## The register
|
||||||
|
|
||||||
| Component | Closest CIBG concept | Why hand-rolled |
|
| Component | Closest CIBG concept | Why hand-rolled |
|
||||||
| --- | --- | --- |
|
| --------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
| `skeleton` | Laadindicatie | No loading-skeleton class in the vendored build. |
|
| `skeleton` | Laadindicatie | No loading-skeleton class in the vendored build. |
|
||||||
| `spinner` | Laadindicatie | No loading-spinner class in the vendored build. |
|
| `spinner` | Laadindicatie | No loading-spinner class in the vendored build. |
|
||||||
| `rich-text-editor` | Tekstgebied | No rich-text/WYSIWYG pattern; toolbar buttons still use vendored `.btn-ghost` (WP-10). |
|
| `rich-text-editor` | Tekstgebied | No rich-text/WYSIWYG pattern; toolbar buttons still use vendored `.btn-ghost` (WP-10). |
|
||||||
| `wizard-shell` (error summary only) | Foutmelding | No error-summary/Veldvalidatie list class; renders inside a vendored `.feedback-error` alert. |
|
| `wizard-shell` (error summary only) | Foutmelding | No error-summary/Veldvalidatie list class; renders inside a vendored `.feedback-error` alert. |
|
||||||
| `application-link` (non-navigating row) | Aanvragen | The vendored `.dashboard-block.applications li a` chain only styles `<a>`; `.static-row` mirrors it from tokens for the informational (non-link) case. |
|
| `application-link` (non-navigating row) | Aanvragen | The vendored `.dashboard-block.applications li a` chain only styles `<a>`; `.static-row` mirrors it from tokens for the informational (non-link) case. |
|
||||||
| `debug-state` | n/a | Dev-only tool, deliberately off-theme — see the component's own `ponytail:` note. |
|
| `debug-state` | n/a | Dev-only tool, deliberately off-theme — see the component's own `ponytail:` note. |
|
||||||
| `status-badge` | n/a | Deliberate custom status dot, not Bootstrap's `.badge` (pill padding/colour don't fit). |
|
| `status-badge` | n/a | Deliberate custom status dot, not Bootstrap's `.badge` (pill padding/colour don't fit). |
|
||||||
| `card` (`.app-card`) | n/a | No vendored generic-card class; prefer the vendored **Datablock** (`app-data-block`, WP-12) for application/user data. |
|
| `card` (`.app-card`) | n/a | No vendored generic-card class; prefer the vendored **Datablock** (`app-data-block`, WP-12) for application/user data. |
|
||||||
| `placeholder-chip` | n/a | No vendored inline-chip/tag class. |
|
| `placeholder-chip` | n/a | No vendored inline-chip/tag class. |
|
||||||
|
|
||||||
Not a gap: `confirmation` renders entirely with vendored `.confirmation*` classes (no `styles:
|
Not a gap: `confirmation` renders entirely with vendored `.confirmation*` classes (no `styles:
|
||||||
[...]` block) — its header comment names the pattern, no marker needed. The `upload/` suite
|
[...]` block) — its header comment names the pattern, no marker needed. The `upload/` suite
|
||||||
|
|||||||
@@ -33,13 +33,13 @@ Never the other way — `registratie` may not import `herregistratie`, and no co
|
|||||||
|
|
||||||
## Five layers, one direction
|
## Five layers, one direction
|
||||||
|
|
||||||
| Layer | Job | Angular allowed? |
|
| Layer | Job | Angular allowed? |
|
||||||
| --- | --- | --- |
|
| ----------------- | ----------------------------------------- | ----------------- |
|
||||||
| `domain/` | business rules + data types | **No — pure TS.** |
|
| `domain/` | business rules + data types | **No — pure TS.** |
|
||||||
| `application/` | coordinate state/tasks (stores, commands) | yes (signals) |
|
| `application/` | coordinate state/tasks (stores, commands) | yes (signals) |
|
||||||
| `infrastructure/` | where data comes from (HTTP adapters) | yes (HTTP) |
|
| `infrastructure/` | where data comes from (HTTP adapters) | yes (HTTP) |
|
||||||
| `contracts/` | wire DTOs (the FE⇄BE seam) | no |
|
| `contracts/` | wire DTOs (the FE⇄BE seam) | no |
|
||||||
| `ui/` | how it looks (components, pages) | yes |
|
| `ui/` | how it looks (components, pages) | yes |
|
||||||
|
|
||||||
`ui → application → domain`; `ui`/`layout` never import `infrastructure/` directly — they
|
`ui → application → domain`; `ui`/`layout` never import `infrastructure/` directly — they
|
||||||
reach data through an application store or command.
|
reach data through an application store or command.
|
||||||
|
|||||||
@@ -36,7 +36,11 @@ dispatches a message describing the outcome:
|
|||||||
// command = "go do it, then say what happened" — reduce never sees the HTTP call itself
|
// command = "go do it, then say what happened" — reduce never sees the HTTP call itself
|
||||||
async function submit(store: Store<WizardState, WizardMsg>) {
|
async function submit(store: Store<WizardState, WizardMsg>) {
|
||||||
const r = await adapter.submit(toDto(store.model()));
|
const r = await adapter.submit(toDto(store.model()));
|
||||||
store.dispatch(r.ok ? { tag: 'SubmitConfirmed', referentie: r.value } : { tag: 'SubmitFailed', error: r.error });
|
store.dispatch(
|
||||||
|
r.ok
|
||||||
|
? { tag: 'SubmitConfirmed', referentie: r.value }
|
||||||
|
: { tag: 'SubmitFailed', error: r.error },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -60,7 +64,7 @@ worse name, and it's the thing a newcomer copies if two idioms are visible side
|
|||||||
Wire every machine through `createStore`, full stop.
|
Wire every machine through `createStore`, full stop.
|
||||||
|
|
||||||
`dispatch` uses `model.update(…)`, not `model.set(reduce(model(), msg))` — the latter
|
`dispatch` uses `model.update(…)`, not `model.set(reduce(model(), msg))` — the latter
|
||||||
reads `model()` *inside* the call, which means an `effect()` that both reads `model` and
|
reads `model()` _inside_ the call, which means an `effect()` that both reads `model` and
|
||||||
calls `dispatch` would subscribe to its own write and livelock. `.update()`'s callback
|
calls `dispatch` would subscribe to its own write and livelock. `.update()`'s callback
|
||||||
receives the current value directly, untracked.
|
receives the current value directly, untracked.
|
||||||
|
|
||||||
@@ -73,7 +77,7 @@ receives the current value directly, untracked.
|
|||||||
- A top-level machine exports `initial` (the starting Model) and `reduce` — unprefixed,
|
- A top-level machine exports `initial` (the starting Model) and `reduce` — unprefixed,
|
||||||
since the file/module already disambiguates them at the import site
|
since the file/module already disambiguates them at the import site
|
||||||
(`import { initial, reduce } from './herregistratie.machine'`).
|
(`import { initial, reduce } from './herregistratie.machine'`).
|
||||||
- A **composable sub-machine** — one embedded *inside* a parent Model, like
|
- A **composable sub-machine** — one embedded _inside_ a parent Model, like
|
||||||
`upload.machine.ts`'s upload-widget state living inside the registratie wizard's own
|
`upload.machine.ts`'s upload-widget state living inside the registratie wizard's own
|
||||||
Model — keeps **prefixed value exports** instead: `initialUpload`, `reduceUpload`.
|
Model — keeps **prefixed value exports** instead: `initialUpload`, `reduceUpload`.
|
||||||
The parent machine already imports several machines' `initial`/`reduce`; prefixing the
|
The parent machine already imports several machines' `initial`/`reduce`; prefixing the
|
||||||
@@ -90,7 +94,7 @@ a function of what I already have?"
|
|||||||
## Where RemoteData fits in
|
## Where RemoteData fits in
|
||||||
|
|
||||||
A machine owns the **domain** lifecycle of what it holds once it exists (draft →
|
A machine owns the **domain** lifecycle of what it holds once it exists (draft →
|
||||||
submitted → approved, in the brief's case). It should generally *not* also own the
|
submitted → approved, in the brief's case). It should generally _not_ also own the
|
||||||
**fetch** lifecycle (loading/failed) for the initial GET that produces it — that's a
|
**fetch** lifecycle (loading/failed) for the initial GET that produces it — that's a
|
||||||
generic concern `RemoteData` already models once, consistently, across the app (see
|
generic concern `RemoteData` already models once, consistently, across the app (see
|
||||||
[Foundations/RemoteData & Async](?path=/docs/foundations-remotedata-async--docs)). Where
|
[Foundations/RemoteData & Async](?path=/docs/foundations-remotedata-async--docs)). Where
|
||||||
|
|||||||
@@ -37,7 +37,11 @@ enters.
|
|||||||
```ts
|
```ts
|
||||||
// before — the wire's `type` string is trusted outright
|
// before — the wire's `type` string is trusted outright
|
||||||
function toAantekening(n: AantekeningDto): Aantekening {
|
function toAantekening(n: AantekeningDto): Aantekening {
|
||||||
return { type: n.type as AantekeningType, omschrijving: n.omschrijving ?? '', datum: n.datum ?? '' };
|
return {
|
||||||
|
type: n.type as AantekeningType,
|
||||||
|
omschrijving: n.omschrijving ?? '',
|
||||||
|
datum: n.datum ?? '',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -48,7 +52,11 @@ const AANTEKENING_TYPES: readonly AantekeningType[] = ['Specialisme', 'Aantekeni
|
|||||||
export function parseAantekening(n: AantekeningDto): Result<string, Aantekening> {
|
export function parseAantekening(n: AantekeningDto): Result<string, Aantekening> {
|
||||||
if (!n.type || !AANTEKENING_TYPES.includes(n.type as AantekeningType))
|
if (!n.type || !AANTEKENING_TYPES.includes(n.type as AantekeningType))
|
||||||
return err(`aantekening: unknown type ${n.type}`);
|
return err(`aantekening: unknown type ${n.type}`);
|
||||||
return ok({ type: n.type as AantekeningType, omschrijving: n.omschrijving ?? '', datum: n.datum ?? '' });
|
return ok({
|
||||||
|
type: n.type as AantekeningType,
|
||||||
|
omschrijving: n.omschrijving ?? '',
|
||||||
|
datum: n.datum ?? '',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -86,7 +94,9 @@ policyResource() {
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
// after — a domain-side type + a validated parse; the resource never surfaces raw wire shape
|
// after — a domain-side type + a validated parse; the resource never surfaces raw wire shape
|
||||||
export interface IntakePolicy { readonly scholingThreshold: number }
|
export interface IntakePolicy {
|
||||||
|
readonly scholingThreshold: number;
|
||||||
|
}
|
||||||
|
|
||||||
export function parseIntakePolicy(json: unknown): Result<string, IntakePolicy> {
|
export function parseIntakePolicy(json: unknown): Result<string, IntakePolicy> {
|
||||||
if (typeof json !== 'object' || json === null) return err('intake-policy: not an object');
|
if (typeof json !== 'object' || json === null) return err('intake-policy: not an object');
|
||||||
@@ -99,7 +109,7 @@ export function parseIntakePolicy(json: unknown): Result<string, IntakePolicy> {
|
|||||||
|
|
||||||
## The sanctioned exception
|
## The sanctioned exception
|
||||||
|
|
||||||
Narrowing `unknown` to `Partial<Dto>` so you can *start* checking fields is fine — that's not a
|
Narrowing `unknown` to `Partial<Dto>` so you can _start_ checking fields is fine — that's not a
|
||||||
trust decision, it's just giving the compiler a shape to probe (`const dto = json as
|
trust decision, it's just giving the compiler a shape to probe (`const dto = json as
|
||||||
Partial<DashboardViewDto>`, see `dashboard-view.adapter.ts`). What's never fine is casting a
|
Partial<DashboardViewDto>`, see `dashboard-view.adapter.ts`). What's never fine is casting a
|
||||||
field to its final domain type without having checked it first.
|
field to its final domain type without having checked it first.
|
||||||
|
|||||||
@@ -12,7 +12,11 @@ believe?). `src/app/shared/application/remote-data.ts` closes that off with one
|
|||||||
union instead:
|
union instead:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
type RemoteData<E, T> = { tag: 'Loading' } | { tag: 'Empty' } | { tag: 'Failure'; error: E } | { tag: 'Success'; value: T };
|
type RemoteData<E, T> =
|
||||||
|
| { tag: 'Loading' }
|
||||||
|
| { tag: 'Empty' }
|
||||||
|
| { tag: 'Failure'; error: E }
|
||||||
|
| { tag: 'Success'; value: T };
|
||||||
```
|
```
|
||||||
|
|
||||||
## Combining sources
|
## Combining sources
|
||||||
@@ -50,7 +54,7 @@ flashes it; override with an `appAsyncLoading` template. `appAsyncEmpty` and
|
|||||||
This is a real Angular constraint, not an oversight: a structural directive's type
|
This is a real Angular constraint, not an oversight: a structural directive's type
|
||||||
parameter can only be inferred from an **input bound on that same element** (this is how
|
parameter can only be inferred from an **input bound on that same element** (this is how
|
||||||
`*ngFor="let x of items"` and `*ngIf="x as y"` work — the type comes from `ngForOf`/`ngIf`,
|
`*ngFor="let x of items"` and `*ngIf="x as y"` work — the type comes from `ngForOf`/`ngIf`,
|
||||||
inputs on the very same tag). `<ng-template appAsyncLoaded let-p>` sits on a *different*
|
inputs on the very same tag). `<ng-template appAsyncLoaded let-p>` sits on a _different_
|
||||||
node than `<app-async [data]="…">`, so `p` cannot inherit a type from that sibling input,
|
node than `<app-async [data]="…">`, so `p` cannot inherit a type from that sibling input,
|
||||||
even though they're nested in the same template. Angular types it `unknown`, and
|
even though they're nested in the same template. Angular types it `unknown`, and
|
||||||
`ngTemplateContextGuard` can't fix that without an input to seed it from — the shared
|
`ngTemplateContextGuard` can't fix that without an input to seed it from — the shared
|
||||||
@@ -72,7 +76,7 @@ protected readonly loaded = computed(() => {
|
|||||||
```html
|
```html
|
||||||
<!-- in the template, inside <ng-template appAsyncLoaded> -->
|
<!-- in the template, inside <ng-template appAsyncLoaded> -->
|
||||||
@if (loaded(); as s) {
|
@if (loaded(); as s) {
|
||||||
<app-letter-composer [brief]="s.brief" ... />
|
<app-letter-composer [brief]="s.brief" ... />
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -93,5 +97,5 @@ fetch's loading/failure, which is a generic concern `RemoteData` already models.
|
|||||||
machine's own `loading`/`failed` tags purely mirror the fetch (nothing extra beyond "not
|
machine's own `loading`/`failed` tags purely mirror the fetch (nothing extra beyond "not
|
||||||
loaded yet" / "the GET failed"), project them onto a `RemoteData` computed at the store
|
loaded yet" / "the GET failed"), project them onto a `RemoteData` computed at the store
|
||||||
layer for `<app-async>` to render, the way `BriefStore.remoteData` does — the machine
|
layer for `<app-async>` to render, the way `BriefStore.remoteData` does — the machine
|
||||||
keeps deciding what the *letter* is doing, `RemoteData` keeps deciding what the *fetch* is
|
keeps deciding what the _letter_ is doing, `RemoteData` keeps deciding what the _fetch_ is
|
||||||
doing.
|
doing.
|
||||||
|
|||||||
Reference in New Issue
Block a user