122 lines
3.2 KiB
TypeScript
122 lines
3.2 KiB
TypeScript
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" || input.source === "restore") {
|
|
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;
|
|
}
|