Reuse command transaction boundary

This commit is contained in:
2026-07-26 11:21:01 +02:00
parent 266bdb4749
commit 0f306d9a90
7 changed files with 314 additions and 349 deletions
+2 -1
View File
@@ -21,7 +21,8 @@ It must support circuits, device rows, project devices, drag-and-drop restructur
`src/frontend/components/circuit-grid-*.ts` modules.
- Critical multi-write commands use injected transaction repositories with real
SQLite commit/rollback tests.
- Structural command stores share
- Compatible multi-write command stores for structure, device-row moves,
section reorder/renumber and project-device row synchronization share
`src/db/repositories/project-command-transaction.persistence.ts` for the
atomic domain-write, revision and history transition boundary.
- General Circuit, CircuitDeviceRow, CircuitList and DistributionBoard
+4 -2
View File
@@ -84,8 +84,10 @@ absteigend paginierte Revisions-Timeline ist ohne Befehls-Payloads über
`GET /api/projects/:projectId/history/revisions` lesbar. Ein zentraler
Dispatcher führt die nachfolgend beschriebenen typisierten Kommandos über
öffentliche Command-, Undo- und Redo-Endpunkte aus.
Die Identitäts-/Struktur-Stores für Circuit, CircuitDeviceRow,
DistributionBoard, ProjectDevice sowie Geschoss/Raum verwenden dabei
Die kompatiblen Multi-Write-Stores für Circuit-/CircuitDeviceRow-Struktur,
Gerätezeilen-Moves, Abschnittssortierung/-umnummerierung,
DistributionBoard-Struktur, ProjectDevice-Struktur und -Zeilensynchronisierung
sowie Geschoss/Raum verwenden dabei
`project-command-transaction.persistence.ts` als gemeinsame äußere
Transaktionsgrenze. Sie führt Fachänderung, Revisions-Append und
History-Transition in fester Reihenfolge innerhalb derselben SQLite-Transaktion
+4 -3
View File
@@ -346,9 +346,10 @@ Implemented foundation:
- obsolete direct Circuit, CircuitDeviceRow, CircuitList, DistributionBoard and
Project repository writes are removed; integration fixtures live under
`tests/support` instead of production repositories
- structural Circuit, CircuitDeviceRow, DistributionBoard, ProjectDevice and
project-location stores share one tested transaction wrapper for domain
mutation, revision append and history transition
- compatible structure, device-row move, section reorder/renumber,
ProjectDevice row-sync and project-location stores share one tested
transaction wrapper for domain mutation, revision append and history
transition
- the legacy consumer UI and application read/write endpoints are removed after verified data cutover
- retained legacy rows are accessible only through explicit database upgrade tooling
- project devices no longer persist duplicate legacy power, phase, cosPhi or remark fields
@@ -19,8 +19,7 @@ import { circuitDeviceRows } from "../schema/circuit-device-rows.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";
interface PersistedMoveRow {
id: string;
@@ -36,29 +35,12 @@ export class CircuitDeviceRowMoveProjectCommandRepository
execute(input: ExecuteCircuitDeviceRowMoveCommandInput) {
this.assertSupportedCommand(input.command);
return this.database.transaction((tx) => {
const inverse = this.applyCommand(
tx,
input.projectId,
input.command
return executeProjectCommandTransaction(
this.database,
input,
(tx) =>
this.applyCommand(tx, input.projectId, input.command)
);
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 assertSupportedCommand(
@@ -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,7 +22,17 @@ export class CircuitSectionRenumberProjectCommandRepository
execute(input: ExecuteCircuitSectionRenumberCommandInput) {
assertCircuitSectionRenumberProjectCommand(input.command);
return this.database.transaction((tx) => {
return executeProjectCommandTransaction(
this.database,
input,
(tx) => this.applyCommand(tx, input)
);
}
private applyCommand(
tx: AppDatabase,
input: ExecuteCircuitSectionRenumberCommandInput
) {
const section = tx
.select({
id: circuitSections.id,
@@ -190,23 +199,7 @@ export class CircuitSectionRenumberProjectCommandRepository
}
}
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(
@@ -21,8 +21,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";
type ReorderHistoryCommand =
| CircuitSectionReorderProjectCommand
@@ -36,7 +35,17 @@ export class CircuitSectionReorderProjectCommandRepository
execute(input: ExecuteCircuitSectionReorderCommandInput) {
this.assertSupportedCommand(input.command);
return this.database.transaction((tx) => {
return executeProjectCommandTransaction(
this.database,
input,
(tx) => this.applyCommand(tx, input)
);
}
private applyCommand(
tx: AppDatabase,
input: ExecuteCircuitSectionReorderCommandInput
) {
const commandSections = this.getCommandSections(input.command);
const inverseSections = commandSections.map((section) => {
this.assertCompleteCurrentSection(
@@ -67,23 +76,7 @@ export class CircuitSectionReorderProjectCommandRepository
: createCircuitSectionsReorderProjectCommand(
inverseSections
);
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 assertSupportedCommand(
@@ -15,8 +15,7 @@ import { circuitDeviceRows } from "../schema/circuit-device-rows.js";
import { circuitLists } from "../schema/circuit-lists.js";
import { circuits } from "../schema/circuits.js";
import { projectDevices } from "../schema/project-devices.js";
import { applyProjectHistoryTransition } from "./project-history.persistence.js";
import { appendProjectRevision } from "./project-revision.persistence.js";
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
type CircuitDeviceRow = typeof circuitDeviceRows.$inferSelect;
@@ -28,7 +27,17 @@ export class ProjectDeviceRowSyncProjectCommandRepository
execute(input: ExecuteProjectDeviceRowSyncCommandInput) {
assertProjectDeviceRowSyncProjectCommand(input.command);
return this.database.transaction((tx) => {
return executeProjectCommandTransaction(
this.database,
input,
(tx) => this.applyCommand(tx, input)
);
}
private applyCommand(
tx: AppDatabase,
input: ExecuteProjectDeviceRowSyncCommandInput
) {
const projectDevice = tx
.select({ id: projectDevices.id })
.from(projectDevices)
@@ -121,23 +130,7 @@ export class ProjectDeviceRowSyncProjectCommandRepository
}
}
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;
}
}