Centralize snapshot restore transactions
This commit is contained in:
@@ -21,7 +21,8 @@ It must support circuits, device rows, project devices, drag-and-drop restructur
|
|||||||
`src/frontend/components/circuit-grid-*.ts` modules.
|
`src/frontend/components/circuit-grid-*.ts` modules.
|
||||||
- Critical multi-write commands use injected transaction repositories with real
|
- Critical multi-write commands use injected transaction repositories with real
|
||||||
SQLite commit/rollback tests.
|
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
|
`src/db/repositories/project-command-transaction.persistence.ts` for the
|
||||||
atomic domain-write, revision and history transition boundary. Its applied
|
atomic domain-write, revision and history transition boundary. Its applied
|
||||||
forward-command variant preserves derived CircuitDeviceRow override metadata.
|
forward-command variant preserves derived CircuitDeviceRow override metadata.
|
||||||
|
|||||||
@@ -84,7 +84,8 @@ absteigend paginierte Revisions-Timeline ist ohne Befehls-Payloads über
|
|||||||
`GET /api/projects/:projectId/history/revisions` lesbar. Ein zentraler
|
`GET /api/projects/:projectId/history/revisions` lesbar. Ein zentraler
|
||||||
Dispatcher führt die nachfolgend beschriebenen typisierten Kommandos über
|
Dispatcher führt die nachfolgend beschriebenen typisierten Kommandos über
|
||||||
öffentliche Command-, Undo- und Redo-Endpunkte aus.
|
ö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
|
`project-command-transaction.persistence.ts` als gemeinsame äußere
|
||||||
Transaktionsgrenze. Sie führt Fachänderung, Revisions-Append und
|
Transaktionsgrenze. Sie führt Fachänderung, Revisions-Append und
|
||||||
History-Transition in fester Reihenfolge innerhalb derselben SQLite-Transaktion
|
History-Transition in fester Reihenfolge innerhalb derselben SQLite-Transaktion
|
||||||
|
|||||||
@@ -346,9 +346,10 @@ Implemented foundation:
|
|||||||
- obsolete direct Circuit, CircuitDeviceRow, CircuitList, DistributionBoard and
|
- obsolete direct Circuit, CircuitDeviceRow, CircuitList, DistributionBoard and
|
||||||
Project repository writes are removed; integration fixtures live under
|
Project repository writes are removed; integration fixtures live under
|
||||||
`tests/support` instead of production repositories
|
`tests/support` instead of production repositories
|
||||||
- all supported runtime project-command stores share one tested transaction
|
- all supported runtime project-command stores, including complete snapshot
|
||||||
wrapper for domain mutation, revision append and history transition; its
|
restore, share one tested transaction wrapper for domain mutation, revision
|
||||||
applied-forward variant retains derived CircuitDeviceRow override metadata
|
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
|
- 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
|
- retained legacy rows are accessible only through explicit database upgrade tooling
|
||||||
- project devices no longer persist duplicate legacy power, phase, cosPhi or remark fields
|
- project devices no longer persist duplicate legacy power, phase, cosPhi or remark fields
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ import { legacyConsumerMigrationReports } from "../schema/legacy-consumer-migrat
|
|||||||
import { projectDevices } from "../schema/project-devices.js";
|
import { projectDevices } from "../schema/project-devices.js";
|
||||||
import { projects } from "../schema/projects.js";
|
import { projects } from "../schema/projects.js";
|
||||||
import { rooms } from "../schema/rooms.js";
|
import { rooms } from "../schema/rooms.js";
|
||||||
import { applyProjectHistoryTransition } from "./project-history.persistence.js";
|
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
||||||
import { appendProjectRevision } from "./project-revision.persistence.js";
|
|
||||||
import {
|
import {
|
||||||
hashProjectStatePayload,
|
hashProjectStatePayload,
|
||||||
readProjectStateSnapshot,
|
readProjectStateSnapshot,
|
||||||
@@ -43,49 +42,43 @@ export class ProjectStateRestoreCommandRepository
|
|||||||
throw new Error("Restore state belongs to a different project.");
|
throw new Error("Restore state belongs to a different project.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.database.transaction((tx) => {
|
return executeProjectCommandTransaction(
|
||||||
const current = readProjectStateSnapshot(tx, input.projectId);
|
this.database,
|
||||||
if (!current) {
|
input,
|
||||||
throw new Error("Project not found.");
|
(tx) => this.applyCommand(tx, input)
|
||||||
}
|
);
|
||||||
if (
|
}
|
||||||
current.payloadSha256 !==
|
|
||||||
input.command.payload.expectedStateSha256
|
|
||||||
) {
|
|
||||||
throw new ProjectStateConflictError(
|
|
||||||
input.projectId,
|
|
||||||
input.command.payload.expectedStateSha256,
|
|
||||||
current.payloadSha256
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetPayloadJson = serializeProjectStateSnapshot(
|
private applyCommand(
|
||||||
input.command.payload.targetState
|
tx: AppDatabase,
|
||||||
);
|
input: ExecuteProjectStateRestoreCommandInput
|
||||||
const inverse = createProjectStateRestoreCommand(
|
) {
|
||||||
hashProjectStatePayload(targetPayloadJson),
|
const current = readProjectStateSnapshot(tx, input.projectId);
|
||||||
current.state
|
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, {
|
replaceProjectState(tx, input.command.payload.targetState);
|
||||||
projectId: input.projectId,
|
|
||||||
expectedRevision: input.expectedRevision,
|
return inverse;
|
||||||
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 };
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user