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 { applyProjectHistoryTransition } from "./project-history.persistence.js"; import { appendProjectRevision } from "./project-revision.persistence.js"; type CircuitRow = typeof circuits.$inferSelect; export class CircuitProjectCommandRepository implements CircuitProjectCommandStore { constructor(private readonly database: AppDatabase) {} executeUpdate(input: ExecuteCircuitUpdateCommandInput) { assertCircuitUpdateProjectCommand(input.command); return this.database.transaction((tx) => { 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); this.assertUniqueEquipmentIdentifier( tx, current, patch.equipmentIdentifier ); const inversePatch = Object.fromEntries( input.command.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."); } 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 }; }); } 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]; }