Reuse command transaction boundary

This commit is contained in:
Julian Appel 2026-07-26 11:21:01 +02:00
parent 266bdb4749
commit 0f306d9a90
7 changed files with 314 additions and 349 deletions

View file

@ -12,8 +12,7 @@ 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 { applyProjectHistoryTransition } from "./project-history.persistence.js";
import { appendProjectRevision } from "./project-revision.persistence.js";
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
export class CircuitSectionRenumberProjectCommandRepository
implements CircuitSectionRenumberProjectCommandStore
@ -23,190 +22,184 @@ export class CircuitSectionRenumberProjectCommandRepository
execute(input: ExecuteCircuitSectionRenumberCommandInput) {
assertCircuitSectionRenumberProjectCommand(input.command);
return this.database.transaction((tx) => {
const section = tx
.select({
id: circuitSections.id,
circuitListId: circuitSections.circuitListId,
projectId: circuitLists.projectId,
})
.from(circuitSections)
.innerJoin(
circuitLists,
eq(circuitLists.id, circuitSections.circuitListId)
)
.where(eq(circuitSections.id, input.command.payload.sectionId))
.get();
if (!section || section.projectId !== input.projectId) {
throw new Error(
"Circuit section does not belong to project."
);
}
return executeProjectCommandTransaction(
this.database,
input,
(tx) => this.applyCommand(tx, input)
);
}
const sectionCircuits = tx
.select({
id: circuits.id,
circuitListId: circuits.circuitListId,
equipmentIdentifier: circuits.equipmentIdentifier,
})
.from(circuits)
.where(eq(circuits.sectionId, section.id))
.all();
const assignments = input.command.payload.assignments;
private applyCommand(
tx: AppDatabase,
input: ExecuteCircuitSectionRenumberCommandInput
) {
const section = tx
.select({
id: circuitSections.id,
circuitListId: circuitSections.circuitListId,
projectId: circuitLists.projectId,
})
.from(circuitSections)
.innerJoin(
circuitLists,
eq(circuitLists.id, circuitSections.circuitListId)
)
.where(eq(circuitSections.id, input.command.payload.sectionId))
.get();
if (!section || section.projectId !== input.projectId) {
throw new Error(
"Circuit section does not belong to project."
);
}
const sectionCircuits = tx
.select({
id: circuits.id,
circuitListId: circuits.circuitListId,
equipmentIdentifier: circuits.equipmentIdentifier,
})
.from(circuits)
.where(eq(circuits.sectionId, section.id))
.all();
const assignments = input.command.payload.assignments;
if (
sectionCircuits.length !== assignments.length ||
sectionCircuits.some(
(circuit) =>
circuit.circuitListId !== section.circuitListId
)
) {
throw new Error(
"Circuit renumber must include every circuit in the section."
);
}
const circuitsById = new Map(
sectionCircuits.map((circuit) => [circuit.id, circuit])
);
for (const assignment of assignments) {
const circuit = circuitsById.get(assignment.circuitId);
if (
sectionCircuits.length !== assignments.length ||
sectionCircuits.some(
(circuit) =>
circuit.circuitListId !== section.circuitListId
)
!circuit ||
circuit.equipmentIdentifier !==
assignment.expectedEquipmentIdentifier
) {
throw new Error(
"Circuit renumber must include every circuit in the section."
"Circuit changed before section renumber execution."
);
}
const circuitsById = new Map(
sectionCircuits.map((circuit) => [circuit.id, circuit])
);
for (const assignment of assignments) {
const circuit = circuitsById.get(assignment.circuitId);
if (
!circuit ||
circuit.equipmentIdentifier !==
assignment.expectedEquipmentIdentifier
) {
throw new Error(
"Circuit changed before section renumber execution."
);
}
}
}
const listCircuits = tx
.select({
id: circuits.id,
equipmentIdentifier: circuits.equipmentIdentifier,
})
.from(circuits)
.where(eq(circuits.circuitListId, section.circuitListId))
.all();
const sectionCircuitIds = new Set(
assignments.map((assignment) => assignment.circuitId)
const listCircuits = tx
.select({
id: circuits.id,
equipmentIdentifier: circuits.equipmentIdentifier,
})
.from(circuits)
.where(eq(circuits.circuitListId, section.circuitListId))
.all();
const sectionCircuitIds = new Set(
assignments.map((assignment) => assignment.circuitId)
);
const targetIdentifiers = new Set(
assignments.map(
(assignment) => assignment.targetEquipmentIdentifier
)
);
const conflictingCircuit = listCircuits.find(
(circuit) =>
!sectionCircuitIds.has(circuit.id) &&
targetIdentifiers.has(circuit.equipmentIdentifier)
);
if (conflictingCircuit) {
throw new Error(
"Target equipment identifier already exists in circuit list."
);
const targetIdentifiers = new Set(
assignments.map(
(assignment) => assignment.targetEquipmentIdentifier
}
const inverse = createCircuitSectionRenumberProjectCommand(
section.id,
assignments.map((assignment) => ({
circuitId: assignment.circuitId,
expectedEquipmentIdentifier:
assignment.targetEquipmentIdentifier,
targetEquipmentIdentifier:
assignment.expectedEquipmentIdentifier,
}))
);
const temporaryIdentifiers = this.createTemporaryIdentifiers(
section.id,
assignments,
listCircuits.map((circuit) => circuit.equipmentIdentifier)
);
for (const assignment of assignments) {
if (
assignment.expectedEquipmentIdentifier ===
assignment.targetEquipmentIdentifier
) {
continue;
}
const temporaryIdentifier = temporaryIdentifiers.get(
assignment.circuitId
)!;
const updated = tx
.update(circuits)
.set({ equipmentIdentifier: temporaryIdentifier })
.where(
and(
eq(circuits.id, assignment.circuitId),
eq(circuits.sectionId, section.id),
eq(circuits.circuitListId, section.circuitListId),
eq(
circuits.equipmentIdentifier,
assignment.expectedEquipmentIdentifier
)
)
)
);
const conflictingCircuit = listCircuits.find(
(circuit) =>
!sectionCircuitIds.has(circuit.id) &&
targetIdentifiers.has(circuit.equipmentIdentifier)
);
if (conflictingCircuit) {
.run();
if (updated.changes !== 1) {
throw new Error(
"Target equipment identifier already exists in circuit list."
"Circuit changed during section renumber execution."
);
}
}
const inverse = createCircuitSectionRenumberProjectCommand(
section.id,
assignments.map((assignment) => ({
circuitId: assignment.circuitId,
expectedEquipmentIdentifier:
for (const assignment of assignments) {
if (
assignment.expectedEquipmentIdentifier ===
assignment.targetEquipmentIdentifier
) {
continue;
}
const temporaryIdentifier = temporaryIdentifiers.get(
assignment.circuitId
)!;
const updated = tx
.update(circuits)
.set({
equipmentIdentifier:
assignment.targetEquipmentIdentifier,
targetEquipmentIdentifier:
assignment.expectedEquipmentIdentifier,
}))
);
const temporaryIdentifiers = this.createTemporaryIdentifiers(
section.id,
assignments,
listCircuits.map((circuit) => circuit.equipmentIdentifier)
);
for (const assignment of assignments) {
if (
assignment.expectedEquipmentIdentifier ===
assignment.targetEquipmentIdentifier
) {
continue;
}
const temporaryIdentifier = temporaryIdentifiers.get(
assignment.circuitId
)!;
const updated = tx
.update(circuits)
.set({ equipmentIdentifier: temporaryIdentifier })
.where(
and(
eq(circuits.id, assignment.circuitId),
eq(circuits.sectionId, section.id),
eq(circuits.circuitListId, section.circuitListId),
eq(
circuits.equipmentIdentifier,
assignment.expectedEquipmentIdentifier
)
})
.where(
and(
eq(circuits.id, assignment.circuitId),
eq(circuits.sectionId, section.id),
eq(circuits.circuitListId, section.circuitListId),
eq(
circuits.equipmentIdentifier,
temporaryIdentifier
)
)
.run();
if (updated.changes !== 1) {
throw new Error(
"Circuit changed during section renumber execution."
);
}
)
.run();
if (updated.changes !== 1) {
throw new Error(
"Circuit changed during final section renumber execution."
);
}
}
for (const assignment of assignments) {
if (
assignment.expectedEquipmentIdentifier ===
assignment.targetEquipmentIdentifier
) {
continue;
}
const temporaryIdentifier = temporaryIdentifiers.get(
assignment.circuitId
)!;
const updated = tx
.update(circuits)
.set({
equipmentIdentifier:
assignment.targetEquipmentIdentifier,
})
.where(
and(
eq(circuits.id, assignment.circuitId),
eq(circuits.sectionId, section.id),
eq(circuits.circuitListId, section.circuitListId),
eq(
circuits.equipmentIdentifier,
temporaryIdentifier
)
)
)
.run();
if (updated.changes !== 1) {
throw new Error(
"Circuit changed during final section renumber 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 };
});
return inverse;
}
private createTemporaryIdentifiers(