export interface DirtyState { currentMainRevision: number; committedMainRevision: number; dirty: boolean; } export type DirtyStateResult = | { ok: true; state: DirtyState } | { ok: false; state: DirtyState; errorCode: "DIRTY_REVISION_INVALID" | "DIRTY_REVISION_STALE" | "DIRTY_SAVE_REVISION_MISMATCH" }; function validRevision(revision: number): boolean { return Number.isSafeInteger(revision) && revision >= 0; } export function createDirtyState(revision = 0): DirtyState { if (!validRevision(revision)) throw new Error("DIRTY_REVISION_INVALID"); return { currentMainRevision: revision, committedMainRevision: revision, dirty: false }; } export function recoverDirtyState(currentMainRevision: number, committedMainRevision: number): DirtyState { if (!validRevision(currentMainRevision) || !validRevision(committedMainRevision) || currentMainRevision < committedMainRevision) { throw new Error("DIRTY_REVISION_INVALID"); } return { currentMainRevision, committedMainRevision, dirty: currentMainRevision !== committedMainRevision }; } export function acceptMainTransaction(state: DirtyState, revision: number): DirtyStateResult { if (!validRevision(revision)) return { ok: false, state, errorCode: "DIRTY_REVISION_INVALID" }; if (revision <= state.currentMainRevision) return { ok: false, state, errorCode: "DIRTY_REVISION_STALE" }; return { ok: true, state: { ...state, currentMainRevision: revision, dirty: revision !== state.committedMainRevision }, }; } export function acceptMainSave(state: DirtyState, revision: number): DirtyStateResult { if (!validRevision(revision)) return { ok: false, state, errorCode: "DIRTY_REVISION_INVALID" }; if (revision !== state.currentMainRevision) return { ok: false, state, errorCode: "DIRTY_SAVE_REVISION_MISMATCH" }; return { ok: true, state: { currentMainRevision: revision, committedMainRevision: revision, dirty: false } }; } export function acceptHistoryTransaction(state: DirtyState, revision: number, matchesCommittedContent: boolean): DirtyStateResult { if (!validRevision(revision)) return { ok: false, state, errorCode: "DIRTY_REVISION_INVALID" }; if (revision <= state.currentMainRevision) return { ok: false, state, errorCode: "DIRTY_REVISION_STALE" }; return { ok: true, state: { currentMainRevision: revision, committedMainRevision: matchesCommittedContent ? revision : state.committedMainRevision, dirty: !matchesCommittedContent, }, }; }