Advance M7 workflows and release operations
This commit is contained in:
79
web/protocol/project-action-mutex.ts
Normal file
79
web/protocol/project-action-mutex.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
export const PROJECT_ACTION_KINDS = ["OPEN", "SAVE", "CLOSE"] as const;
|
||||
export type ProjectActionKind = typeof PROJECT_ACTION_KINDS[number];
|
||||
|
||||
export interface ProjectActionIdentity {
|
||||
actionId: string;
|
||||
kind: ProjectActionKind;
|
||||
}
|
||||
|
||||
export interface ProjectActionMutexState {
|
||||
owner: ProjectActionIdentity | null;
|
||||
}
|
||||
|
||||
export type ProjectActionConflictReason =
|
||||
| "REPEATED_OPEN"
|
||||
| "CONCURRENT_SAVE"
|
||||
| "CLOSE_DURING_SAVE"
|
||||
| "PROJECT_ACTION_EXCLUSIVE";
|
||||
|
||||
export interface ProjectActionConflict {
|
||||
code: "USER_ACTION_CONFLICT";
|
||||
reason: ProjectActionConflictReason;
|
||||
requested: ProjectActionIdentity;
|
||||
owner: ProjectActionIdentity;
|
||||
}
|
||||
|
||||
export type ProjectActionAcquireResult =
|
||||
| { granted: true; state: ProjectActionMutexState; conflict: null }
|
||||
| { granted: false; state: ProjectActionMutexState; conflict: ProjectActionConflict };
|
||||
|
||||
export type ProjectActionReleaseResult =
|
||||
| { released: true; state: ProjectActionMutexState; errorCode: null }
|
||||
| {
|
||||
released: false;
|
||||
state: ProjectActionMutexState;
|
||||
errorCode: "PROJECT_ACTION_LOCK_NOT_HELD" | "PROJECT_ACTION_LOCK_IDENTITY_MISMATCH";
|
||||
};
|
||||
|
||||
export const PROJECT_ACTION_CONFLICT_MATRIX: Readonly<Record<ProjectActionKind, Readonly<Record<ProjectActionKind, boolean>>>> = {
|
||||
OPEN: { OPEN: true, SAVE: true, CLOSE: true },
|
||||
SAVE: { OPEN: true, SAVE: true, CLOSE: true },
|
||||
CLOSE: { OPEN: true, SAVE: true, CLOSE: true },
|
||||
};
|
||||
|
||||
export function createProjectActionMutexState(): ProjectActionMutexState {
|
||||
return { owner: null };
|
||||
}
|
||||
|
||||
function conflictReason(requested: ProjectActionKind, owner: ProjectActionKind): ProjectActionConflictReason {
|
||||
if (requested === "OPEN" && owner === "OPEN") return "REPEATED_OPEN";
|
||||
if (requested === "SAVE" && owner === "SAVE") return "CONCURRENT_SAVE";
|
||||
if (requested === "CLOSE" && owner === "SAVE") return "CLOSE_DURING_SAVE";
|
||||
return "PROJECT_ACTION_EXCLUSIVE";
|
||||
}
|
||||
|
||||
export function acquireProjectAction(state: ProjectActionMutexState, requested: ProjectActionIdentity): ProjectActionAcquireResult {
|
||||
const owner = state.owner;
|
||||
if (!owner) return { granted: true, state: { owner: requested }, conflict: null };
|
||||
if (!PROJECT_ACTION_CONFLICT_MATRIX[requested.kind][owner.kind]) {
|
||||
return { granted: true, state: { owner: requested }, conflict: null };
|
||||
}
|
||||
return {
|
||||
granted: false,
|
||||
state,
|
||||
conflict: {
|
||||
code: "USER_ACTION_CONFLICT",
|
||||
reason: conflictReason(requested.kind, owner.kind),
|
||||
requested,
|
||||
owner,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function releaseProjectAction(state: ProjectActionMutexState, identity: ProjectActionIdentity): ProjectActionReleaseResult {
|
||||
if (!state.owner) return { released: false, state, errorCode: "PROJECT_ACTION_LOCK_NOT_HELD" };
|
||||
if (state.owner.kind !== identity.kind || state.owner.actionId !== identity.actionId) {
|
||||
return { released: false, state, errorCode: "PROJECT_ACTION_LOCK_IDENTITY_MISMATCH" };
|
||||
}
|
||||
return { released: true, state: createProjectActionMutexState(), errorCode: null };
|
||||
}
|
||||
Reference in New Issue
Block a user