Advance M7 workflows and release operations
This commit is contained in:
81
web/protocol/save-transaction.ts
Normal file
81
web/protocol/save-transaction.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
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 } };
|
||||
}
|
||||
Reference in New Issue
Block a user