refactor(shared): split runResult out of runSubmit (RB-17)
runSubmit did two things at once: fold a call into a Result, and mint an Idempotency-Key for it. Five call sites are reads and had no business minting one — brief.adapter.ts:load, org-template.adapter.ts :list/:load, and stamdata.adapter.ts:list/:load. stamdata.adapter.ts's own docstring already said "Both endpoints are reads … There is no write method" while both called runSubmit; that mismatch is the sharpest evidence, and the reason the baseline's original "~13 mutations" count (derived from the helper's name, not the code) was wrong by five in one direction. Split submit.ts in place: runResult is the try/catch + problemDetail fold with no mint; runSubmit is runResult wrapping withIdempotencyKey. Zero behaviour change for the 8 real mutations (brief save/submit/approve/reject/send/reset, org-template save/publish/rollback) — same fold, same mint, same timing. The five reads now run the fold with no pendingIdempotencyKey touched. submit.spec.ts asserts the split behaviourally via currentIdempotencyKey() (two reads inside the same call agree only when a key was minted and reused) rather than mocking a relative import, matching this repo's existing vitest convention. Verified red without the fix by temporarily reintroducing the mint into runResult. ApplicationsStore.cancel/AdminCasesStore.delete (RB-20) and FeatureFlagStore.set are out of scope and untouched — the latter already calls runSubmit correctly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Result, ok, err } from '@shared/kernel/fp';
|
||||
import { runSubmit } from '@shared/application/submit';
|
||||
import { runResult, runSubmit } from '@shared/application/submit';
|
||||
import {
|
||||
ApiClient,
|
||||
BriefDecisionsDto,
|
||||
@@ -33,8 +33,9 @@ import { Mark, Paragraph, RichTextBlock, RichTextNode } from '@shared/kernel/ric
|
||||
* The only place brief HTTP lives (ADR-0001 anti-corruption boundary). The wire
|
||||
* uses FLAT unions (a `type`/`tag` string + nullable fields, the repo convention);
|
||||
* the `parse*` boundary narrows them into the domain's proper discriminated unions
|
||||
* and rejects malformed shapes. Mutations go through `runSubmit` (ProblemDetails →
|
||||
* error string), then parse the returned brief.
|
||||
* and rejects malformed shapes. `load` (the only read) folds through `runResult`;
|
||||
* every mutation folds through `runSubmit` (ProblemDetails → error string, plus the
|
||||
* Idempotency-Key mint), then parses the returned brief.
|
||||
*/
|
||||
|
||||
export interface BriefView {
|
||||
@@ -53,7 +54,7 @@ export class BriefAdapter {
|
||||
private client = inject(ApiClient);
|
||||
|
||||
async load(): Promise<Result<string, BriefView>> {
|
||||
const r = await runSubmit(() => this.client.briefGET(), BRIEF_LOAD_FAILED);
|
||||
const r = await runResult(() => this.client.briefGET(), BRIEF_LOAD_FAILED);
|
||||
return r.ok ? parseBriefView(r.value) : r;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Injectable, inject, isDevMode } from '@angular/core';
|
||||
import { Result, ok, err } from '@shared/kernel/fp';
|
||||
import { runSubmit } from '@shared/application/submit';
|
||||
import { runResult, runSubmit } from '@shared/application/submit';
|
||||
import { currentRole } from '@shared/infrastructure/role';
|
||||
import { problemDetail } from '@shared/infrastructure/api-error';
|
||||
import { environment } from '@shared/environments/environment';
|
||||
@@ -39,7 +39,7 @@ export class OrgTemplateAdapter {
|
||||
private client = inject(ApiClient);
|
||||
|
||||
async list(): Promise<Result<string, SubOrgSummary[]>> {
|
||||
const r = await runSubmit(() => this.client.orgTemplates(), FAILED);
|
||||
const r = await runResult(() => this.client.orgTemplates(), FAILED);
|
||||
if (!r.ok) return r;
|
||||
const out: SubOrgSummary[] = [];
|
||||
for (const s of r.value ?? []) {
|
||||
@@ -51,7 +51,7 @@ export class OrgTemplateAdapter {
|
||||
}
|
||||
|
||||
async load(subOrgId: string): Promise<Result<string, OrgTemplateAdminView>> {
|
||||
const r = await runSubmit(() => this.client.orgTemplateGET(subOrgId), FAILED);
|
||||
const r = await runResult(() => this.client.orgTemplateGET(subOrgId), FAILED);
|
||||
return r.ok ? parseAdminView(r.value) : r;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user