chore: initialize from frontend template 4dc033c
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import type {
|
||||
CommandEffectDescriptor,
|
||||
CommandEffectClassification,
|
||||
} from "../../contracts/external-contract-runtime.ts";
|
||||
|
||||
/**
|
||||
* §8.7–§8.9. Mutation effect certainty.
|
||||
*
|
||||
* The frontend never infers "not applied" from an HTTP status alone. Anything
|
||||
* observed after the request was dispatched but before a classified terminal
|
||||
* response is `MAYBE_APPLIED`, which forbids automatic resend.
|
||||
*/
|
||||
|
||||
export type MutationEffectCertainty =
|
||||
| "NOT_STARTED"
|
||||
| "NOT_APPLIED"
|
||||
| "MAYBE_APPLIED"
|
||||
| "APPLIED_CONFIRMED";
|
||||
|
||||
export type PhysicalAttemptState =
|
||||
| "NOT_STARTED"
|
||||
| "PREPARING"
|
||||
| "READY_TO_SEND"
|
||||
| "DISPATCHED"
|
||||
| "RESPONSE_HEADERS"
|
||||
| "READING_BODY"
|
||||
| "VALIDATING"
|
||||
| "MAPPING_READY"
|
||||
| "SETTLED";
|
||||
|
||||
/**
|
||||
* `READY_TO_SEND` is recorded immediately before entering the `fetch()`
|
||||
* invocation expression and `DISPATCHED` immediately after the promise is
|
||||
* returned. A synchronous throw therefore leaves the attempt `NOT_STARTED`.
|
||||
*/
|
||||
export function certaintyForAbandonedAttempt(
|
||||
state: PhysicalAttemptState,
|
||||
isCommand: boolean,
|
||||
): MutationEffectCertainty {
|
||||
if (!isCommand) return "NOT_STARTED";
|
||||
switch (state) {
|
||||
case "NOT_STARTED":
|
||||
case "PREPARING":
|
||||
case "READY_TO_SEND":
|
||||
return "NOT_STARTED";
|
||||
default:
|
||||
return "MAYBE_APPLIED";
|
||||
}
|
||||
}
|
||||
|
||||
export type ProblemEffectInput<Problem> = Readonly<{
|
||||
status: number;
|
||||
problem: Problem;
|
||||
descriptor: CommandEffectDescriptor<Problem> | null;
|
||||
}>;
|
||||
|
||||
export type ProblemEffectOutcome = Readonly<{
|
||||
effect: MutationEffectCertainty;
|
||||
contractRuntimeFailure: boolean;
|
||||
}>;
|
||||
|
||||
const CLASSIFICATIONS: ReadonlySet<CommandEffectClassification> = new Set([
|
||||
"NOT_APPLIED",
|
||||
"APPLIED_CONFIRMED",
|
||||
"MAYBE_APPLIED",
|
||||
]);
|
||||
|
||||
/**
|
||||
* §4.4. The classifier is package-owned and pure. A throw or an unrecognised
|
||||
* return value fails safe to `MAYBE_APPLIED` and is recorded as a contract
|
||||
* runtime failure rather than being silently treated as "not applied".
|
||||
*/
|
||||
export function classifyProblemEffect<Problem>(
|
||||
input: ProblemEffectInput<Problem>,
|
||||
): ProblemEffectOutcome {
|
||||
if (!input.descriptor) {
|
||||
// A read operation carries no command effect; there is nothing to apply.
|
||||
return Object.freeze({
|
||||
effect: "NOT_STARTED" as const,
|
||||
contractRuntimeFailure: false,
|
||||
});
|
||||
}
|
||||
let classification: CommandEffectClassification;
|
||||
try {
|
||||
classification = input.descriptor.classifyProblem({
|
||||
status: input.status,
|
||||
problem: input.problem,
|
||||
});
|
||||
} catch {
|
||||
return Object.freeze({
|
||||
effect: "MAYBE_APPLIED" as const,
|
||||
contractRuntimeFailure: true,
|
||||
});
|
||||
}
|
||||
if (!CLASSIFICATIONS.has(classification)) {
|
||||
return Object.freeze({
|
||||
effect: "MAYBE_APPLIED" as const,
|
||||
contractRuntimeFailure: true,
|
||||
});
|
||||
}
|
||||
return Object.freeze({
|
||||
effect: classification,
|
||||
contractRuntimeFailure: false,
|
||||
});
|
||||
}
|
||||
|
||||
/** §8.10. Certainty to UI intent. The copy itself is owned by the i18n catalog. */
|
||||
export function projectCertaintyToUi(
|
||||
certainty: MutationEffectCertainty,
|
||||
): "RETRYABLE" | "CHECK_STATUS" | "SUCCESS" {
|
||||
switch (certainty) {
|
||||
case "APPLIED_CONFIRMED":
|
||||
return "SUCCESS";
|
||||
case "MAYBE_APPLIED":
|
||||
return "CHECK_STATUS";
|
||||
default:
|
||||
return "RETRYABLE";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user