import { and, eq } from "drizzle-orm"; import { assertCircuitDeviceRowUpdateProjectCommand, createCircuitDeviceRowUpdateProjectCommand, type CircuitDeviceRowUpdateField, type CircuitDeviceRowUpdatePatch, type CircuitDeviceRowUpdateValues, } from "../../domain/models/circuit-device-row-project-command.model.js"; import type { CircuitDeviceRowProjectCommandStore, ExecuteCircuitDeviceRowUpdateCommandInput, } from "../../domain/ports/circuit-device-row-project-command.store.js"; import { deriveOverriddenFieldsForLocalEdit } from "../../domain/services/project-device-overrides.js"; import type { AppDatabase } from "../database-context.js"; import { circuitDeviceRows } from "../schema/circuit-device-rows.js"; import { circuitLists } from "../schema/circuit-lists.js"; import { circuits } from "../schema/circuits.js"; import { projectDevices } from "../schema/project-devices.js"; import { rooms } from "../schema/rooms.js"; import { toCircuitDeviceRowPatchValues, type CircuitDeviceRowPatchInput, } from "./circuit-device-row.persistence.js"; import { executeProjectCommandTransactionWithAppliedForward } from "./project-command-transaction.persistence.js"; import { updateDerivedCircuitVoltage } from "./project-voltage.persistence.js"; type CircuitDeviceRow = typeof circuitDeviceRows.$inferSelect; export class CircuitDeviceRowProjectCommandRepository implements CircuitDeviceRowProjectCommandStore { constructor(private readonly database: AppDatabase) {} executeUpdate(input: ExecuteCircuitDeviceRowUpdateCommandInput) { assertCircuitDeviceRowUpdateProjectCommand(input.command); return executeProjectCommandTransactionWithAppliedForward( this.database, input, (tx) => this.applyCommand(tx, input) ); } private applyCommand( tx: AppDatabase, input: ExecuteCircuitDeviceRowUpdateCommandInput ) { const current = tx .select() .from(circuitDeviceRows) .where(eq(circuitDeviceRows.id, input.command.payload.rowId)) .get(); if (!current) { throw new Error("Invalid device row id."); } const circuit = tx .select({ circuitListId: circuits.circuitListId }) .from(circuits) .where(eq(circuits.id, current.circuitId)) .get(); const owningList = circuit ? tx .select({ id: circuitLists.id }) .from(circuitLists) .where( and( eq(circuitLists.id, circuit.circuitListId), eq(circuitLists.projectId, input.projectId) ) ) .get() : null; if (!owningList) { throw new Error("Circuit device row does not belong to project."); } const patch = Object.fromEntries( input.command.payload.changes.map((change) => [ change.field, change.value, ]) ) as CircuitDeviceRowPatchInput; this.assertLinkedProjectDevice(tx, input.projectId, patch); this.assertRoom(tx, input.projectId, patch); const overriddenFields = deriveOverriddenFieldsForLocalEdit( current, patch ); if ( overriddenFields !== undefined && patch.overriddenFields === undefined ) { patch.overriddenFields = overriddenFields; } const appliedForward = createCircuitDeviceRowUpdateProjectCommand( current.id, patch as CircuitDeviceRowUpdatePatch ); const inversePatch = Object.fromEntries( appliedForward.payload.changes.map((change) => [ change.field, getCircuitDeviceRowFieldValue(current, change.field), ]) ) as CircuitDeviceRowUpdatePatch; const inverse = createCircuitDeviceRowUpdateProjectCommand( current.id, inversePatch ); const update = tx .update(circuitDeviceRows) .set(toCircuitDeviceRowPatchValues(patch)) .where(eq(circuitDeviceRows.id, current.id)) .run(); if (update.changes !== 1) { throw new Error("Circuit device row changed before command execution."); } if (patch.phaseType !== undefined) { updateDerivedCircuitVoltage( tx, input.projectId, current.circuitId ); } return { forward: appliedForward, inverse, }; } private assertLinkedProjectDevice( database: AppDatabase, projectId: string, patch: CircuitDeviceRowPatchInput ) { if ( !Object.prototype.hasOwnProperty.call(patch, "linkedProjectDeviceId") || patch.linkedProjectDeviceId === null || patch.linkedProjectDeviceId === undefined ) { return; } const device = database .select({ id: projectDevices.id }) .from(projectDevices) .where( and( eq(projectDevices.id, patch.linkedProjectDeviceId), eq(projectDevices.projectId, projectId) ) ) .get(); if (!device) { throw new Error("Invalid linked project device id."); } } private assertRoom( database: AppDatabase, projectId: string, patch: CircuitDeviceRowPatchInput ) { if ( !Object.prototype.hasOwnProperty.call(patch, "roomId") || patch.roomId === null || patch.roomId === undefined ) { return; } const room = database .select({ id: rooms.id }) .from(rooms) .where(and(eq(rooms.id, patch.roomId), eq(rooms.projectId, projectId))) .get(); if (!room) { throw new Error("Invalid room id."); } } } function getCircuitDeviceRowFieldValue< TField extends CircuitDeviceRowUpdateField, >( row: CircuitDeviceRow, field: TField ): CircuitDeviceRowUpdateValues[TField] { return row[field] as unknown as CircuitDeviceRowUpdateValues[TField]; }