diff --git a/AGENTS.md b/AGENTS.md index 95a0c26..edbfd94 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. -- All supported runtime project-command stores share +- All supported runtime project-command stores, including full snapshot + restoration, share `src/db/repositories/project-command-transaction.persistence.ts` for the atomic domain-write, revision and history transition boundary. Its applied forward-command variant preserves derived CircuitDeviceRow override metadata. diff --git a/docs/current-architecture.md b/docs/current-architecture.md index d3b0cb8..79b5762 100644 --- a/docs/current-architecture.md +++ b/docs/current-architecture.md @@ -84,7 +84,8 @@ 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. -Alle unterstützten Runtime-Project-Command-Stores verwenden dabei +Alle unterstützten Runtime-Project-Command-Stores einschließlich der +vollständigen Snapshot-Wiederherstellung 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 diff --git a/docs/spec/07-implementation-phases-todo.md b/docs/spec/07-implementation-phases-todo.md index 6e5576a..97ec57d 100644 --- a/docs/spec/07-implementation-phases-todo.md +++ b/docs/spec/07-implementation-phases-todo.md @@ -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 -- all supported runtime project-command stores share one tested transaction - wrapper for domain mutation, revision append and history transition; its - applied-forward variant retains derived CircuitDeviceRow override metadata +- all supported runtime project-command stores, including complete snapshot + restore, share one tested transaction wrapper for domain mutation, revision + append and history transition; its applied-forward variant retains derived + CircuitDeviceRow override metadata - 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 diff --git a/src/db/repositories/project-state-restore-command.repository.ts b/src/db/repositories/project-state-restore-command.repository.ts index 51ee2f9..8a67fac 100644 --- a/src/db/repositories/project-state-restore-command.repository.ts +++ b/src/db/repositories/project-state-restore-command.repository.ts @@ -25,8 +25,7 @@ import { legacyConsumerMigrationReports } from "../schema/legacy-consumer-migrat import { projectDevices } from "../schema/project-devices.js"; import { projects } from "../schema/projects.js"; import { rooms } from "../schema/rooms.js"; -import { applyProjectHistoryTransition } from "./project-history.persistence.js"; -import { appendProjectRevision } from "./project-revision.persistence.js"; +import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js"; import { hashProjectStatePayload, readProjectStateSnapshot, @@ -43,49 +42,43 @@ export class ProjectStateRestoreCommandRepository throw new Error("Restore state belongs to a different project."); } - return this.database.transaction((tx) => { - const current = readProjectStateSnapshot(tx, input.projectId); - if (!current) { - throw new Error("Project not found."); - } - if ( - current.payloadSha256 !== - input.command.payload.expectedStateSha256 - ) { - throw new ProjectStateConflictError( - input.projectId, - input.command.payload.expectedStateSha256, - current.payloadSha256 - ); - } + return executeProjectCommandTransaction( + this.database, + input, + (tx) => this.applyCommand(tx, input) + ); + } - const targetPayloadJson = serializeProjectStateSnapshot( - input.command.payload.targetState - ); - const inverse = createProjectStateRestoreCommand( - hashProjectStatePayload(targetPayloadJson), - current.state + private applyCommand( + tx: AppDatabase, + input: ExecuteProjectStateRestoreCommandInput + ) { + const current = readProjectStateSnapshot(tx, input.projectId); + if (!current) { + throw new Error("Project not found."); + } + if ( + current.payloadSha256 !== + input.command.payload.expectedStateSha256 + ) { + throw new ProjectStateConflictError( + input.projectId, + input.command.payload.expectedStateSha256, + current.payloadSha256 ); + } - replaceProjectState(tx, input.command.payload.targetState); + const targetPayloadJson = serializeProjectStateSnapshot( + input.command.payload.targetState + ); + const inverse = createProjectStateRestoreCommand( + hashProjectStatePayload(targetPayloadJson), + current.state + ); - 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 }; - }); + replaceProjectState(tx, input.command.payload.targetState); + + return inverse; } }