Correlation id becomes real ASP.NET Core middleware instead of a per-endpoint
read: every request gets one (client-supplied or generated), it's echoed as
an X-Correlation-Id response header, and pushed into the logging scope so
every log line for that request carries it — not just the Submit helper's,
verified against LogBrief which never threads it explicitly.
Idempotency-Key moves from per-HTTP-attempt (defeating its own purpose) to
per-logical-submit: runSubmit mints one key and threads it through a small
bridge (withIdempotencyKey/currentIdempotencyKey) since the NSwag-generated
client has no per-call header hook. Backend gains an IdempotencyStore that
short-circuits a replayed key to the first call's result instead of minting
a second reference — scoped to the Submit-helper endpoints per the WP's own
decision.
GET requests now retry transient failures (rxjs retry({count:2, delay:500}));
writes never auto-retry. Proven with a fake-HttpClient spec
(api-client.provider.spec.ts) rather than a manual network-tab check — the
WP's suggested `?scenario=error` check turned out not to exercise a real
network call at all (the interceptor throws before calling next()), so the
automated test is the actual proof.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
29 lines
1.2 KiB
TypeScript
29 lines
1.2 KiB
TypeScript
import { Result, ok, err } from '@shared/kernel/fp';
|
|
import { problemDetail } from '@shared/infrastructure/api-error';
|
|
import { withIdempotencyKey } from '@shared/infrastructure/api-client.provider';
|
|
|
|
/**
|
|
* Run a mutating API call and fold it into a `Result` — the one place the
|
|
* try/catch + ProblemDetails-mapping lives, so every `submit-*` command is just
|
|
* its own payload mapping. The backend re-validates and returns a 422
|
|
* ProblemDetails on rejection, surfaced here as the error string.
|
|
*
|
|
* Also the one place a logical submit's Idempotency-Key is minted — once per
|
|
* `runSubmit` call, not per HTTP attempt — so a retry of this same submit
|
|
* dedupes on the backend (see `withIdempotencyKey`).
|
|
*/
|
|
export async function runSubmit<T>(
|
|
fn: () => Promise<T>,
|
|
fallback: string,
|
|
): Promise<Result<string, T>> {
|
|
try {
|
|
return ok(await withIdempotencyKey(crypto.randomUUID(), fn));
|
|
} catch (e) {
|
|
return err(problemDetail(e, fallback));
|
|
}
|
|
}
|
|
|
|
// Single shared default for a failed submit; the @@id dedupes it at the
|
|
// translation layer.
|
|
export const SUBMIT_FAILED = $localize`:@@submit.failed:Het indienen is niet gelukt. Probeer het later opnieuw.`;
|