import { and, eq, ne } from "drizzle-orm"; import { assertCircuitUpdateProjectCommand, createCircuitUpdateProjectCommand, type CircuitUpdateField, type CircuitUpdatePatch, type CircuitUpdateValues, } from "../../domain/models/circuit-project-command.model.js"; import type { CircuitProjectCommandStore, ExecuteCircuitUpdateCommandInput, } from "../../domain/ports/circuit-project-command.store.js"; import type { AppDatabase } from "../database-context.js"; import { circuitLists } from "../schema/circuit-lists.js"; import { circuitSections } from "../schema/circuit-sections.js"; import { circuits } from "../schema/circuits.js"; import { toCircuitPatchValues, type CircuitPatchPersistenceInput, } from "./circuit.persistence.js"; import { executeProjectCommandTransactionWithAppliedForward } from "./project-command-transaction.persistence.js"; import { resolveCircuitVoltage } from "./project-voltage.persistence.js"; import { circuitDeviceRows } from "../schema/circuit-device-rows.js"; type CircuitRow = typeof circuits.$inferSelect; export class CircuitProjectCommandRepository implements CircuitProjectCommandStore { constructor(private readonly database: AppDatabase) {} executeUpdate(input: ExecuteCircuitUpdateCommandInput) { assertCircuitUpdateProjectCommand(input.command); return executeProjectCommandTransactionWithAppliedForward( this.database, input, (tx) => this.applyCommand(tx, input) ); } private applyCommand( tx: AppDatabase, input: ExecuteCircuitUpdateCommandInput ) { const current = tx .select() .from(circuits) .where(eq(circuits.id, input.command.payload.circuitId)) .get(); if (!current) { throw new Error("Invalid circuit id."); } const owningList = tx .select({ id: circuitLists.id }) .from(circuitLists) .where( and( eq(circuitLists.id, current.circuitListId), eq(circuitLists.projectId, input.projectId) ) ) .get(); if (!owningList) { throw new Error("Circuit does not belong to project."); } const patch = Object.fromEntries( input.command.payload.changes.map((change) => [ change.field, change.value, ]) ) as CircuitPatchPersistenceInput; this.assertSectionInCircuitList(tx, current, patch.sectionId); if (input.source === "user") { if ( input.command.payload.changes.every( (change) => change.field === "voltage" ) ) { throw new Error("Circuit voltage is derived and cannot be edited."); } const devicePhaseTypes = tx .select({ phaseType: circuitDeviceRows.phaseType }) .from(circuitDeviceRows) .where(eq(circuitDeviceRows.circuitId, current.id)) .all() .map((row) => row.phaseType); const derivedVoltage = resolveCircuitVoltage( tx, input.projectId, patch.sectionId ?? current.sectionId, devicePhaseTypes ); if (derivedVoltage === current.voltage) { delete patch.voltage; } else { patch.voltage = derivedVoltage; } } this.assertUniqueEquipmentIdentifier( tx, current, patch.equipmentIdentifier ); const appliedForward = createCircuitUpdateProjectCommand( current.id, patch as CircuitUpdatePatch ); const inversePatch = Object.fromEntries( appliedForward.payload.changes.map((change) => [ change.field, getCircuitFieldValue(current, change.field), ]) ) as CircuitUpdatePatch; const inverse = createCircuitUpdateProjectCommand( current.id, inversePatch ); const update = tx .update(circuits) .set(toCircuitPatchValues(patch)) .where(eq(circuits.id, current.id)) .run(); if (update.changes !== 1) { throw new Error("Circuit changed before command execution."); } return { forward: appliedForward, inverse }; } private assertSectionInCircuitList( database: AppDatabase, circuit: CircuitRow, sectionId: string | undefined ) { if (sectionId === undefined || sectionId === circuit.sectionId) { return; } const section = database .select({ id: circuitSections.id }) .from(circuitSections) .where( and( eq(circuitSections.id, sectionId), eq(circuitSections.circuitListId, circuit.circuitListId) ) ) .get(); if (!section) { throw new Error("Section does not belong to circuit list."); } } private assertUniqueEquipmentIdentifier( database: AppDatabase, circuit: CircuitRow, equipmentIdentifier: string | undefined ) { if ( equipmentIdentifier === undefined || equipmentIdentifier === circuit.equipmentIdentifier ) { return; } const duplicate = database .select({ id: circuits.id }) .from(circuits) .where( and( eq(circuits.circuitListId, circuit.circuitListId), eq(circuits.equipmentIdentifier, equipmentIdentifier), ne(circuits.id, circuit.id) ) ) .get(); if (duplicate) { throw new Error("Duplicate equipmentIdentifier in circuit list."); } } } function getCircuitFieldValue( circuit: CircuitRow, field: TField ): CircuitUpdateValues[TField] { if (field === "isReserve") { return Boolean(circuit.isReserve) as CircuitUpdateValues[TField]; } return circuit[field] as unknown as CircuitUpdateValues[TField]; }