import { and, eq } from "drizzle-orm"; import { assertCircuitProtectionUpdateProjectCommand, createCircuitProtectionUpdateProjectCommand, type CircuitProtectionSnapshot, } from "../../domain/models/circuit-protection-project-command.model.js"; import type { CircuitProtectionProjectCommandStore, ExecuteCircuitProtectionCommandInput, } from "../../domain/ports/circuit-protection-project-command.store.js"; import type { AppDatabase } from "../database-context.js"; import { circuitLists } from "../schema/circuit-lists.js"; import { circuitProtectionDevices } from "../schema/circuit-protection-devices.js"; import { circuits } from "../schema/circuits.js"; import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js"; export class CircuitProtectionProjectCommandRepository implements CircuitProtectionProjectCommandStore { constructor(private readonly database: AppDatabase) {} execute(input: ExecuteCircuitProtectionCommandInput) { return executeProjectCommandTransaction(this.database, input, (tx) => { assertCircuitProtectionUpdateProjectCommand(input.command); const { circuitId, expected, target } = input.command.payload; if (input.source === "user" && target === null) { throw new Error("User commands cannot remove circuit protection."); } this.assertOwnership(tx, input.projectId, circuitId); const actual = tx .select() .from(circuitProtectionDevices) .where(eq(circuitProtectionDevices.circuitId, circuitId)) .get(); if (!sameNullableSnapshot(expected, actual)) { throw new Error("Circuit protection changed before update."); } if (target === null) { tx.delete(circuitProtectionDevices) .where(eq(circuitProtectionDevices.circuitId, circuitId)) .run(); } else if (actual) { tx.update(circuitProtectionDevices) .set(target) .where(eq(circuitProtectionDevices.circuitId, circuitId)) .run(); } else { tx.insert(circuitProtectionDevices).values(target).run(); } return createCircuitProtectionUpdateProjectCommand( circuitId, target, expected ); }); } private assertOwnership( database: AppDatabase, projectId: string, circuitId: string ) { const circuit = database .select({ projectId: circuitLists.projectId }) .from(circuits) .innerJoin( circuitLists, eq(circuitLists.id, circuits.circuitListId) ) .where( and( eq(circuits.id, circuitId), eq(circuitLists.projectId, projectId) ) ) .get(); if (!circuit) { throw new Error("Circuit protection does not belong to project."); } } } function sameNullableSnapshot( expected: CircuitProtectionSnapshot | null, actual: typeof circuitProtectionDevices.$inferSelect | undefined ) { if (expected === null) { return actual === undefined; } if (!actual) { return false; } return Object.entries(expected).every( ([key, value]) => (actual as Record)[key] === value ); }