Files
workinf_Blender_Wasm/web/protocol/save-transaction.ts
mes123456 7c16b279ae
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
Advance M7 workflows and release operations
2026-08-15 17:43:53 -04:00

82 lines
3.6 KiB
TypeScript

export const SAVE_ATTEMPT_STAGES = ["SERIALIZE", "OPFS_STAGE", "SCENE_COMMIT", "METADATA_COMMIT"] as const;
export type SaveAttemptStage = typeof SAVE_ATTEMPT_STAGES[number];
export interface SaveCommitIdentity {
revision: number;
sha256: string | null;
}
export interface SaveTransactionState {
status: "IDLE" | "RUNNING" | "SUCCEEDED" | "FAILED";
stage: SaveAttemptStage | null;
committed: SaveCommitIdentity;
candidate: SaveCommitIdentity | null;
errorCode: string | null;
}
export type SaveTransactionResult =
| { ok: true; state: SaveTransactionState }
| { ok: false; state: SaveTransactionState; errorCode: "SAVE_TRANSACTION_INVALID_TRANSITION" | "SAVE_TRANSACTION_COMMIT_MISMATCH" };
const SHA256 = /^[a-f0-9]{64}$/;
function validIdentity(identity: SaveCommitIdentity, allowNullHash: boolean): boolean {
return Number.isSafeInteger(identity.revision) && identity.revision >= 0 &&
(allowNullHash ? identity.sha256 === null || SHA256.test(identity.sha256) : typeof identity.sha256 === "string" && SHA256.test(identity.sha256));
}
export function createSaveTransactionState(committed: SaveCommitIdentity): SaveTransactionState {
if (!validIdentity(committed, true)) throw new Error("SAVE_TRANSACTION_COMMIT_INVALID");
return { status: "IDLE", stage: null, committed, candidate: null, errorCode: null };
}
export function beginSaveTransaction(state: SaveTransactionState, targetRevision: number): SaveTransactionResult {
if (state.status === "RUNNING" || !Number.isSafeInteger(targetRevision) || targetRevision < state.committed.revision) {
return { ok: false, state, errorCode: "SAVE_TRANSACTION_INVALID_TRANSITION" };
}
return {
ok: true,
state: {
status: "RUNNING",
stage: "SERIALIZE",
committed: state.committed,
candidate: { revision: targetRevision, sha256: null },
errorCode: null,
},
};
}
export function advanceSaveTransaction(state: SaveTransactionState, stage: SaveAttemptStage, candidateSha256?: string): SaveTransactionResult {
if (state.status !== "RUNNING" || !state.stage || !state.candidate) {
return { ok: false, state, errorCode: "SAVE_TRANSACTION_INVALID_TRANSITION" };
}
const currentIndex = SAVE_ATTEMPT_STAGES.indexOf(state.stage);
const nextIndex = SAVE_ATTEMPT_STAGES.indexOf(stage);
if (nextIndex !== currentIndex + 1 || (stage === "OPFS_STAGE" && (!candidateSha256 || !SHA256.test(candidateSha256)))) {
return { ok: false, state, errorCode: "SAVE_TRANSACTION_INVALID_TRANSITION" };
}
return {
ok: true,
state: {
...state,
stage,
candidate: stage === "OPFS_STAGE" ? { ...state.candidate, sha256: candidateSha256! } : state.candidate,
},
};
}
export function failSaveTransaction(state: SaveTransactionState, errorCode: string): SaveTransactionState {
if (state.status !== "RUNNING" || !/^[A-Z][A-Z0-9_]*$/.test(errorCode)) return state;
return { ...state, status: "FAILED", errorCode };
}
export function commitSaveTransaction(state: SaveTransactionState, persisted: SaveCommitIdentity): SaveTransactionResult {
if (state.status !== "RUNNING" || state.stage !== "METADATA_COMMIT" || !state.candidate || !validIdentity(persisted, false)) {
return { ok: false, state, errorCode: "SAVE_TRANSACTION_INVALID_TRANSITION" };
}
if (persisted.revision !== state.candidate.revision || persisted.sha256 !== state.candidate.sha256) {
return { ok: false, state, errorCode: "SAVE_TRANSACTION_COMMIT_MISMATCH" };
}
return { ok: true, state: { status: "SUCCEEDED", stage: "METADATA_COMMIT", committed: persisted, candidate: persisted, errorCode: null } };
}