Add persistent history stacks
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
import { and, desc, eq, max } from "drizzle-orm";
|
||||
import type { ProjectRevisionSource } from "../../domain/ports/project-revision.store.js";
|
||||
import type { AppDatabase } from "../database-context.js";
|
||||
import { projectHistoryStackEntries } from "../schema/project-history-stack-entries.js";
|
||||
|
||||
export interface ApplyProjectHistoryTransitionInput {
|
||||
projectId: string;
|
||||
source: ProjectRevisionSource;
|
||||
recordedChangeSetId: string;
|
||||
targetChangeSetId?: string;
|
||||
}
|
||||
|
||||
export function applyProjectHistoryTransition(
|
||||
database: AppDatabase,
|
||||
input: ApplyProjectHistoryTransitionInput
|
||||
) {
|
||||
if (input.source === "user") {
|
||||
database
|
||||
.delete(projectHistoryStackEntries)
|
||||
.where(
|
||||
and(
|
||||
eq(projectHistoryStackEntries.projectId, input.projectId),
|
||||
eq(projectHistoryStackEntries.stack, "redo")
|
||||
)
|
||||
)
|
||||
.run();
|
||||
push(
|
||||
database,
|
||||
input.projectId,
|
||||
input.recordedChangeSetId,
|
||||
"undo"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.source === "undo" || input.source === "redo") {
|
||||
if (!input.targetChangeSetId) {
|
||||
throw new Error(
|
||||
`${input.source} requires a target change set.`
|
||||
);
|
||||
}
|
||||
const sourceStack = input.source;
|
||||
const targetStack = input.source === "undo" ? "redo" : "undo";
|
||||
const top = database
|
||||
.select({
|
||||
changeSetId: projectHistoryStackEntries.changeSetId,
|
||||
})
|
||||
.from(projectHistoryStackEntries)
|
||||
.where(
|
||||
and(
|
||||
eq(projectHistoryStackEntries.projectId, input.projectId),
|
||||
eq(projectHistoryStackEntries.stack, sourceStack)
|
||||
)
|
||||
)
|
||||
.orderBy(desc(projectHistoryStackEntries.position))
|
||||
.limit(1)
|
||||
.get();
|
||||
if (!top || top.changeSetId !== input.targetChangeSetId) {
|
||||
throw new Error(
|
||||
`${input.source} target is not the top ${sourceStack} command.`
|
||||
);
|
||||
}
|
||||
|
||||
const nextPosition = getNextPosition(
|
||||
database,
|
||||
input.projectId,
|
||||
targetStack
|
||||
);
|
||||
const moved = database
|
||||
.update(projectHistoryStackEntries)
|
||||
.set({ stack: targetStack, position: nextPosition })
|
||||
.where(
|
||||
and(
|
||||
eq(projectHistoryStackEntries.projectId, input.projectId),
|
||||
eq(
|
||||
projectHistoryStackEntries.changeSetId,
|
||||
input.targetChangeSetId
|
||||
)
|
||||
)
|
||||
)
|
||||
.run();
|
||||
if (moved.changes !== 1) {
|
||||
throw new Error("Project history stack changed during transition.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function push(
|
||||
database: AppDatabase,
|
||||
projectId: string,
|
||||
changeSetId: string,
|
||||
stack: "undo" | "redo"
|
||||
) {
|
||||
database
|
||||
.insert(projectHistoryStackEntries)
|
||||
.values({
|
||||
projectId,
|
||||
changeSetId,
|
||||
stack,
|
||||
position: getNextPosition(database, projectId, stack),
|
||||
})
|
||||
.run();
|
||||
}
|
||||
|
||||
function getNextPosition(
|
||||
database: AppDatabase,
|
||||
projectId: string,
|
||||
stack: "undo" | "redo"
|
||||
) {
|
||||
const current = database
|
||||
.select({ position: max(projectHistoryStackEntries.position) })
|
||||
.from(projectHistoryStackEntries)
|
||||
.where(
|
||||
and(
|
||||
eq(projectHistoryStackEntries.projectId, projectId),
|
||||
eq(projectHistoryStackEntries.stack, stack)
|
||||
)
|
||||
)
|
||||
.get();
|
||||
return (current?.position ?? 0) + 1;
|
||||
}
|
||||
Reference in New Issue
Block a user