fix: preserve logical mutation intent

This commit is contained in:
DongHyeonka
2026-08-02 01:07:19 +09:00
parent 53d181fbe4
commit cbcc7b5ed7
21 changed files with 724 additions and 96 deletions
@@ -104,38 +104,6 @@ export function classifyProblemEffect<Problem>(
});
}
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,
+3 -3
View File
@@ -4,6 +4,7 @@ import {
type InstalledHttpContract,
} from "../../contracts/external-contract-runtime.ts";
import type { CacheScopeSnapshot } from "../../contracts/server-state-scope.ts";
import type { MutationIntent } from "../../contracts/mutation-intent.ts";
import {
decodeJsonBytes,
isEffectivelyEmpty,
@@ -19,7 +20,6 @@ import {
import {
certaintyForAbandonedAttempt,
classifyProblemEffect,
type MutationIntentContext,
type PhysicalAttemptState,
} from "./http-effect-certainty.ts";
import { parseRetryAfter } from "./retry-policy.ts";
@@ -130,7 +130,7 @@ export type CancellationOwner =
export interface HttpExecutionContext {
readonly signal?: AbortSignal;
readonly scope: CacheScopeSnapshot;
readonly intent?: MutationIntentContext;
readonly intent?: MutationIntent;
}
export interface ContractHttpExecutor {
@@ -370,7 +370,7 @@ export function createContractHttpExecutor(
if (contract.requestBody === "JSON") {
headers["Content-Type"] = "application/json";
}
if (context.intent?.idempotencyKey) {
if (isCommand && context.intent?.idempotencyKey) {
headers["Idempotency-Key"] = context.intent.idempotencyKey;
}