import { eq } from "drizzle-orm"; import { assertEstimateBalanceDeleteProjectCommand, assertEstimateBalanceInsertProjectCommand, assertEstimateBalanceUpdateProjectCommand, createEstimateBalanceDeleteProjectCommand, createEstimateBalanceInsertProjectCommand, createEstimateBalanceUpdateProjectCommand, estimateBalanceDeleteCommandType, estimateBalanceInsertCommandType, estimateBalanceUpdateCommandType, normalizeEstimateBalanceSnapshot, sameEstimateBalance, type EstimateBalanceProjectCommand, } from "../../estimate-balance/domain/estimate-balance-command.model.js"; import type { EstimateBalanceProjectCommandStore, ExecuteEstimateBalanceCommandInput, } from "../../estimate-balance/domain/estimate-balance-project-command.store.js"; import type { EstimateBalanceSnapshot } from "../../shared/estimate-balance/estimate-balance.js"; import type { AppDatabase } from "../database-context.js"; import { estimateBalances } from "../schema/estimate-balances.js"; import { projects } from "../schema/projects.js"; import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js"; import { deleteEstimateBalance, insertEstimateBalance, readEstimateBalances, } from "./estimate-balance.persistence.js"; export class EstimateBalanceProjectCommandRepository implements EstimateBalanceProjectCommandStore { constructor(private readonly database: AppDatabase) {} execute(input: ExecuteEstimateBalanceCommandInput) { return executeProjectCommandTransaction(this.database, input, (tx) => this.applyCommand(tx, input.projectId, input.command) ); } private applyCommand( database: AppDatabase, projectId: string, command: EstimateBalanceProjectCommand ): EstimateBalanceProjectCommand { switch (command.type) { case estimateBalanceInsertCommandType: assertEstimateBalanceInsertProjectCommand(command); return this.insert(database, projectId, command.payload.balance); case estimateBalanceDeleteCommandType: assertEstimateBalanceDeleteProjectCommand(command); return this.delete(database, projectId, command.payload.balance); case estimateBalanceUpdateCommandType: assertEstimateBalanceUpdateProjectCommand(command); return this.update( database, projectId, command.payload.expected, command.payload.target ); } } private insert( database: AppDatabase, projectId: string, balance: EstimateBalanceSnapshot ) { if (balance.projectId !== projectId) { throw new Error("Estimate-balance snapshot does not belong to project."); } const project = database .select({ id: projects.id }) .from(projects) .where(eq(projects.id, projectId)) .get(); if (!project) { throw new Error("Project does not exist."); } const existing = database .select({ id: estimateBalances.id }) .from(estimateBalances) .where(eq(estimateBalances.id, balance.id)) .get(); if (existing) { throw new Error("Estimate-balance id already exists."); } const normalized = normalizeEstimateBalanceSnapshot(balance); insertEstimateBalance(database, normalized); return createEstimateBalanceDeleteProjectCommand(normalized); } private delete( database: AppDatabase, projectId: string, expected: EstimateBalanceSnapshot ) { this.assertPersisted(database, projectId, expected, "deletion"); if (deleteEstimateBalance(database, projectId, expected.id) !== 1) { throw new Error("Estimate balance could not be deleted."); } return createEstimateBalanceInsertProjectCommand(expected); } private update( database: AppDatabase, projectId: string, expected: EstimateBalanceSnapshot, target: EstimateBalanceSnapshot ) { this.assertPersisted(database, projectId, expected, "update"); // Group and row ids stay stable; replacing the whole document keeps the // command exact and its inverse trivially the swapped pair. deleteEstimateBalance(database, projectId, expected.id); const normalized = normalizeEstimateBalanceSnapshot(target); insertEstimateBalance(database, normalized); return createEstimateBalanceUpdateProjectCommand(normalized, expected); } private assertPersisted( database: AppDatabase, projectId: string, expected: EstimateBalanceSnapshot, action: string ) { const [persisted] = readEstimateBalances(database, projectId, expected.id); if (!persisted || !sameEstimateBalance(persisted, expected)) { throw new Error(`Estimate balance changed before ${action}.`); } } }