77 lines
3.3 KiB
TypeScript
77 lines
3.3 KiB
TypeScript
import { withConfirmedCandidate } from "./birth-time-evidence.ts";
|
|
import { replayedDynamicAction } from "./birth-time-dynamic-action-replay.ts";
|
|
import { withDynamicAction } from "./birth-time-dynamic-transitions.ts";
|
|
import { BirthTimeDynamicActionError } from "./birth-time-dynamic-actions.ts";
|
|
import { storedDynamicJourneyResponse } from "./birth-time-journey-response.ts";
|
|
import type {
|
|
BirthTimeJourneyPorts,
|
|
DynamicCandidateConfirmationCommand,
|
|
DynamicStoredRectificationCase,
|
|
} from "./birth-time-journey-service.ts";
|
|
import { StaleJourneyTurnError } from "./birth-time-journey-store-errors.ts";
|
|
|
|
function stale(stored: DynamicStoredRectificationCase, expectedVersion: number) {
|
|
return new StaleJourneyTurnError(stored.id, expectedVersion, stored.turnVersion);
|
|
}
|
|
|
|
export function createDynamicCandidateConfirmation(ports: BirthTimeJourneyPorts) {
|
|
return {
|
|
async confirm(input: DynamicCandidateConfirmationCommand) {
|
|
const stored = await ports.store.loadCase(input.userId, input.caseId);
|
|
if (!stored) throw new BirthTimeDynamicActionError("case_not_found");
|
|
if (stored.journeyProtocol !== "dynamic-choice-v2") {
|
|
throw new BirthTimeDynamicActionError("invalid_turn");
|
|
}
|
|
const receipt = stored.dynamicControl.lastActionReceipt;
|
|
if (replayedDynamicAction(stored, input.actionId, input.expectedVersion, () => (
|
|
stored.dynamicTurnState.nextAction.kind === "ready"
|
|
&& stored.dynamicTurnState.nextAction.activeTime === input.time
|
|
&& stored.snapshot.activeTime === input.time
|
|
&& receipt?.kind === "confirm_candidate"
|
|
&& receipt.actionId === input.actionId.toLowerCase()
|
|
&& receipt.turnVersion === input.expectedVersion
|
|
&& receipt.resultId === input.resultId
|
|
&& receipt.time === input.time
|
|
))) return storedDynamicJourneyResponse(stored);
|
|
|
|
const action = stored.dynamicTurnState.nextAction;
|
|
const candidate = stored.candidateResult;
|
|
if (
|
|
stored.turnVersion !== input.expectedVersion
|
|
|| action.kind !== "request_candidate_confirmation"
|
|
|| action.resultId !== input.resultId
|
|
|| !stored.dynamicTurnState.permissions.canConfirmCandidate
|
|
|| candidate?.resultId !== input.resultId
|
|
|| candidate.canApply !== true
|
|
|| candidate.winningSegment?.representativeTime !== input.time
|
|
) throw stale(stored, input.expectedVersion);
|
|
|
|
const transitioned = withDynamicAction(
|
|
stored,
|
|
{ kind: "ready", activeTime: input.time },
|
|
input.expectedVersion + 1,
|
|
);
|
|
const updated = {
|
|
...transitioned,
|
|
snapshot: withConfirmedCandidate(stored.snapshot, candidate, input.time),
|
|
currentChoiceQuestion: null,
|
|
dynamicTurnState: {
|
|
...transitioned.dynamicTurnState,
|
|
permissions: { canConfirmCandidate: false },
|
|
},
|
|
dynamicControl: {
|
|
...stored.dynamicControl,
|
|
lastActionReceipt: {
|
|
actionId: input.actionId.toLowerCase(),
|
|
kind: "confirm_candidate" as const,
|
|
turnVersion: input.expectedVersion,
|
|
resultId: input.resultId,
|
|
time: input.time,
|
|
},
|
|
},
|
|
} satisfies DynamicStoredRectificationCase;
|
|
return storedDynamicJourneyResponse(await ports.store.confirmDynamicCandidate(updated, input));
|
|
},
|
|
};
|
|
}
|