refactor: 리펙토링

This commit is contained in:
DongHyeonka
2026-08-01 19:39:59 +09:00
parent 9c959ea2a5
commit c6da03369c
171 changed files with 20329 additions and 782 deletions
+151
View File
@@ -0,0 +1,151 @@
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,
});
}
export type MutationIntent = Readonly<{
intentId: string;
operationId: string;
canonicalInputIdentity: string;
idempotencyKey?: string;
createdAtMonotonicMs: number;
}>;
export type MutationIntentContext = Readonly<{
intentId: string;
idempotencyKey?: string;
startedBy: "USER" | "FOREGROUND_RETRY" | "OUTBOX_REPLAY";
}>;
export function createMutationIntent(
input: Readonly<{
operationId: string;
canonicalInputIdentity: string;
idempotencyKey?: string;
monotonicNow?: () => number;
}>,
): MutationIntent {
const now = input.monotonicNow ?? (() => performance.now());
return Object.freeze({
intentId: crypto.randomUUID(),
operationId: input.operationId,
canonicalInputIdentity: input.canonicalInputIdentity,
...(input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}),
createdAtMonotonicMs: now(),
});
}
/** §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";
}
}