Centralize update command transactions

This commit is contained in:
2026-07-26 11:24:19 +02:00
parent 0f306d9a90
commit e4d22caf09
6 changed files with 175 additions and 195 deletions
+3 -2
View File
@@ -21,8 +21,9 @@ 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.
- Compatible multi-write command stores for structure, device-row moves,
section reorder/renumber and project-device row synchronization share
- Compatible command stores for Circuit and ProjectDevice field updates,
project settings, 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
+5 -4
View File
@@ -84,10 +84,11 @@ 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 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
Die kompatiblen Command-Stores für Circuit- und ProjectDevice-Feldänderungen,
Projekteinstellungen, 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 -4
View File
@@ -346,10 +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
- 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
- compatible Circuit/ProjectDevice update, project-settings, 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
@@ -18,8 +18,7 @@ import {
toCircuitPatchValues,
type CircuitPatchPersistenceInput,
} from "./circuit.persistence.js";
import { applyProjectHistoryTransition } from "./project-history.persistence.js";
import { appendProjectRevision } from "./project-revision.persistence.js";
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
type CircuitRow = typeof circuits.$inferSelect;
@@ -31,7 +30,17 @@ export class CircuitProjectCommandRepository
executeUpdate(input: ExecuteCircuitUpdateCommandInput) {
assertCircuitUpdateProjectCommand(input.command);
return this.database.transaction((tx) => {
return executeProjectCommandTransaction(
this.database,
input,
(tx) => this.applyCommand(tx, input)
);
}
private applyCommand(
tx: AppDatabase,
input: ExecuteCircuitUpdateCommandInput
) {
const current = tx
.select()
.from(circuits)
@@ -88,24 +97,7 @@ export class CircuitProjectCommandRepository
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 };
});
return inverse;
}
private assertSectionInCircuitList(
@@ -12,8 +12,7 @@ import type {
} from "../../domain/ports/project-device-project-command.store.js";
import type { AppDatabase } from "../database-context.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 ProjectDeviceRow = typeof projectDevices.$inferSelect;
@@ -25,7 +24,17 @@ export class ProjectDeviceProjectCommandRepository
executeUpdate(input: ExecuteProjectDeviceUpdateCommandInput) {
assertProjectDeviceUpdateProjectCommand(input.command);
return this.database.transaction((tx) => {
return executeProjectCommandTransaction(
this.database,
input,
(tx) => this.applyCommand(tx, input)
);
}
private applyCommand(
tx: AppDatabase,
input: ExecuteProjectDeviceUpdateCommandInput
) {
const current = tx
.select()
.from(projectDevices)
@@ -78,23 +87,7 @@ export class ProjectDeviceProjectCommandRepository
);
}
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;
}
}
@@ -9,8 +9,7 @@ import type {
} from "../../domain/ports/project-settings-project-command.store.js";
import type { AppDatabase } from "../database-context.js";
import { projects } from "../schema/projects.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 ProjectSettingsProjectCommandRepository
implements ProjectSettingsProjectCommandStore
@@ -19,7 +18,17 @@ export class ProjectSettingsProjectCommandRepository
executeUpdate(input: ExecuteProjectSettingsUpdateCommandInput) {
assertProjectSettingsUpdateProjectCommand(input.command);
return this.database.transaction((tx) => {
return executeProjectCommandTransaction(
this.database,
input,
(tx) => this.applyCommand(tx, input)
);
}
private applyCommand(
tx: AppDatabase,
input: ExecuteProjectSettingsUpdateCommandInput
) {
const current = tx
.select()
.from(projects)
@@ -50,22 +59,6 @@ export class ProjectSettingsProjectCommandRepository
throw new Error("Project changed before settings update.");
}
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;
}
}