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 { executeProjectCommandTransactionWithAppliedForward } from "./project-command-transaction.persistence.js"; import { resolveProjectDeviceVoltage } from "./project-voltage.persistence.js"; type ProjectDeviceRow = typeof projectDevices.$inferSelect; export class ProjectDeviceProjectCommandRepository implements ProjectDeviceProjectCommandStore { constructor(private readonly database: AppDatabase) {} executeUpdate(input: ExecuteProjectDeviceUpdateCommandInput) { assertProjectDeviceUpdateProjectCommand(input.command); return executeProjectCommandTransactionWithAppliedForward( this.database, input, (tx) => this.applyCommand(tx, input) ); } private applyCommand( tx: AppDatabase, input: ExecuteProjectDeviceUpdateCommandInput ) { 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; if (input.source === "user") { if ( input.command.payload.changes.every( (change) => change.field === "voltageV" ) ) { throw new Error( "Project-device voltage is derived and cannot be edited." ); } const targetPhaseType = patch.phaseType ?? current.phaseType; if ( targetPhaseType !== "single_phase" && targetPhaseType !== "three_phase" ) { throw new Error("Project-device phase type is invalid."); } const derivedVoltage = resolveProjectDeviceVoltage( tx, input.projectId, targetPhaseType ); if (derivedVoltage === current.voltageV) { delete patch.voltageV; } else { patch.voltageV = derivedVoltage; } } const appliedForward = createProjectDeviceUpdateProjectCommand( current.id, patch ); const inversePatch = Object.fromEntries( appliedForward.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." ); } return { forward: appliedForward, inverse }; } } function getProjectDeviceFieldValue< TField extends ProjectDeviceUpdateField, >( projectDevice: ProjectDeviceRow, field: TField ): ProjectDeviceUpdateValues[TField] { return projectDevice[field] as ProjectDeviceUpdateValues[TField]; }