Add project device update history

This commit is contained in:
2026-07-24 08:57:57 +02:00
parent 2668fc2f16
commit 0bc6c7372f
14 changed files with 785 additions and 13 deletions
@@ -0,0 +1,108 @@
import { and, eq } from "drizzle-orm";
import {
assertProjectDeviceUpdateProjectCommand,
createProjectDeviceUpdateProjectCommand,
type ProjectDeviceUpdateField,
type ProjectDeviceUpdatePatch,
type ProjectDeviceUpdateValues,
} from "../../domain/models/project-device-project-command.model.js";
import type {
ExecuteProjectDeviceUpdateCommandInput,
ProjectDeviceProjectCommandStore,
} from "../../domain/ports/project-device-project-command.store.js";
import type { AppDatabase } from "../database-context.js";
import { projectDevices } from "../schema/project-devices.js";
import { applyProjectHistoryTransition } from "./project-history.persistence.js";
import { appendProjectRevision } from "./project-revision.persistence.js";
type ProjectDeviceRow = typeof projectDevices.$inferSelect;
export class ProjectDeviceProjectCommandRepository
implements ProjectDeviceProjectCommandStore
{
constructor(private readonly database: AppDatabase) {}
executeUpdate(input: ExecuteProjectDeviceUpdateCommandInput) {
assertProjectDeviceUpdateProjectCommand(input.command);
return this.database.transaction((tx) => {
const current = tx
.select()
.from(projectDevices)
.where(
and(
eq(
projectDevices.id,
input.command.payload.projectDeviceId
),
eq(projectDevices.projectId, input.projectId)
)
)
.get();
if (!current) {
throw new Error(
"Project device does not belong to project."
);
}
const patch = Object.fromEntries(
input.command.payload.changes.map((change) => [
change.field,
change.value,
])
) as ProjectDeviceUpdatePatch;
const inversePatch = Object.fromEntries(
input.command.payload.changes.map((change) => [
change.field,
getProjectDeviceFieldValue(current, change.field),
])
) as ProjectDeviceUpdatePatch;
const inverse = createProjectDeviceUpdateProjectCommand(
current.id,
inversePatch
);
const updated = tx
.update(projectDevices)
.set(patch)
.where(
and(
eq(projectDevices.id, current.id),
eq(projectDevices.projectId, input.projectId)
)
)
.run();
if (updated.changes !== 1) {
throw new Error(
"Project device changed before command execution."
);
}
const revision = appendProjectRevision(tx, {
projectId: input.projectId,
expectedRevision: input.expectedRevision,
source: input.source,
description: input.description,
actorId: input.actorId,
forward: input.command,
inverse,
});
applyProjectHistoryTransition(tx, {
projectId: input.projectId,
source: input.source,
recordedChangeSetId: revision.changeSetId,
targetChangeSetId: input.historyTargetChangeSetId,
});
return { revision, inverse };
});
}
}
function getProjectDeviceFieldValue<
TField extends ProjectDeviceUpdateField,
>(
projectDevice: ProjectDeviceRow,
field: TField
): ProjectDeviceUpdateValues[TField] {
return projectDevice[field] as ProjectDeviceUpdateValues[TField];
}