357 lines
13 KiB
TypeScript
357 lines
13 KiB
TypeScript
import { ProjectHistoryOperationUnavailableError } from "../errors/project-history-operation-unavailable.error.js";
|
|
import {
|
|
assertCircuitDeviceRowMoveProjectCommand,
|
|
assertCircuitDeviceRowMoveWithNewCircuitProjectCommand,
|
|
circuitDeviceRowMoveCommandType,
|
|
circuitDeviceRowMoveWithNewCircuitCommandType,
|
|
} from "../models/circuit-device-row-move-project-command.model.js";
|
|
import {
|
|
assertCircuitDeviceRowUpdateProjectCommand,
|
|
circuitDeviceRowUpdateCommandType,
|
|
} from "../models/circuit-device-row-project-command.model.js";
|
|
import {
|
|
assertCircuitDeviceRowDeleteProjectCommand,
|
|
assertCircuitDeviceRowInsertProjectCommand,
|
|
circuitDeviceRowDeleteCommandType,
|
|
circuitDeviceRowInsertCommandType,
|
|
} from "../models/circuit-device-row-structure-project-command.model.js";
|
|
import {
|
|
assertCircuitUpdateProjectCommand,
|
|
circuitUpdateCommandType,
|
|
} from "../models/circuit-project-command.model.js";
|
|
import {
|
|
assertCircuitSectionReorderProjectCommand,
|
|
circuitSectionReorderCommandType,
|
|
} from "../models/circuit-section-reorder-project-command.model.js";
|
|
import {
|
|
assertCircuitSectionsReorderProjectCommand,
|
|
circuitSectionsReorderCommandType,
|
|
} from "../models/circuit-sections-reorder-project-command.model.js";
|
|
import {
|
|
assertCircuitSectionRenumberProjectCommand,
|
|
circuitSectionRenumberCommandType,
|
|
} from "../models/circuit-section-renumber-project-command.model.js";
|
|
import {
|
|
assertCircuitDeleteProjectCommand,
|
|
assertCircuitInsertProjectCommand,
|
|
circuitDeleteCommandType,
|
|
circuitInsertCommandType,
|
|
} from "../models/circuit-structure-project-command.model.js";
|
|
import {
|
|
assertDistributionBoardDeleteProjectCommand,
|
|
assertDistributionBoardInsertProjectCommand,
|
|
distributionBoardDeleteCommandType,
|
|
distributionBoardInsertCommandType,
|
|
} from "../models/distribution-board-structure-project-command.model.js";
|
|
import {
|
|
assertSerializedProjectCommand,
|
|
type SerializedProjectCommand,
|
|
} from "../models/project-command.model.js";
|
|
import {
|
|
assertProjectStateRestoreCommand,
|
|
projectStateRestoreCommandType,
|
|
} from "../models/project-state-restore-command.model.js";
|
|
import {
|
|
assertProjectSettingsUpdateProjectCommand,
|
|
projectSettingsUpdateCommandType,
|
|
} from "../models/project-settings-project-command.model.js";
|
|
import {
|
|
assertProjectDeviceRowSyncProjectCommand,
|
|
projectDeviceRowSyncCommandType,
|
|
} from "../models/project-device-row-sync-project-command.model.js";
|
|
import {
|
|
assertProjectDeviceUpdateProjectCommand,
|
|
projectDeviceUpdateCommandType,
|
|
} from "../models/project-device-project-command.model.js";
|
|
import {
|
|
assertProjectDeviceDeleteProjectCommand,
|
|
assertProjectDeviceInsertProjectCommand,
|
|
projectDeviceDeleteCommandType,
|
|
projectDeviceInsertCommandType,
|
|
} from "../models/project-device-structure-project-command.model.js";
|
|
import type { CircuitDeviceRowProjectCommandStore } from "../ports/circuit-device-row-project-command.store.js";
|
|
import type { CircuitDeviceRowMoveProjectCommandStore } from "../ports/circuit-device-row-move-project-command.store.js";
|
|
import type { CircuitDeviceRowStructureProjectCommandStore } from "../ports/circuit-device-row-structure-project-command.store.js";
|
|
import type { CircuitProjectCommandStore } from "../ports/circuit-project-command.store.js";
|
|
import type { CircuitSectionReorderProjectCommandStore } from "../ports/circuit-section-reorder-project-command.store.js";
|
|
import type { CircuitSectionRenumberProjectCommandStore } from "../ports/circuit-section-renumber-project-command.store.js";
|
|
import type { CircuitStructureProjectCommandStore } from "../ports/circuit-structure-project-command.store.js";
|
|
import type { DistributionBoardStructureProjectCommandStore } from "../ports/distribution-board-structure-project-command.store.js";
|
|
import type {
|
|
ExecuteProjectHistoryCommandInput,
|
|
ExecutedProjectCommand,
|
|
ExecuteUserProjectCommandInput,
|
|
ProjectCommandExecutor,
|
|
} from "../ports/project-command.executor.js";
|
|
import type {
|
|
ProjectHistoryDirection,
|
|
ProjectHistoryStore,
|
|
} from "../ports/project-history.store.js";
|
|
import type { ProjectDeviceProjectCommandStore } from "../ports/project-device-project-command.store.js";
|
|
import type { ProjectDeviceRowSyncProjectCommandStore } from "../ports/project-device-row-sync-project-command.store.js";
|
|
import type { ProjectDeviceStructureProjectCommandStore } from "../ports/project-device-structure-project-command.store.js";
|
|
import type { ProjectRevisionSource } from "../ports/project-revision.store.js";
|
|
import type { ProjectSettingsProjectCommandStore } from "../ports/project-settings-project-command.store.js";
|
|
import type { ProjectStateRestoreCommandStore } from "../ports/project-state-restore-command.store.js";
|
|
|
|
interface DispatchProjectCommandInput {
|
|
projectId: string;
|
|
expectedRevision: number;
|
|
source: ProjectRevisionSource;
|
|
description?: string;
|
|
actorId?: string;
|
|
historyTargetChangeSetId?: string;
|
|
command: SerializedProjectCommand<unknown>;
|
|
}
|
|
|
|
export class ProjectCommandService implements ProjectCommandExecutor {
|
|
constructor(
|
|
private readonly circuitStore: CircuitProjectCommandStore,
|
|
private readonly deviceRowStore: CircuitDeviceRowProjectCommandStore,
|
|
private readonly deviceRowStructureStore: CircuitDeviceRowStructureProjectCommandStore,
|
|
private readonly deviceRowMoveStore: CircuitDeviceRowMoveProjectCommandStore,
|
|
private readonly circuitStructureStore: CircuitStructureProjectCommandStore,
|
|
private readonly distributionBoardStructureStore: DistributionBoardStructureProjectCommandStore,
|
|
private readonly circuitSectionReorderStore: CircuitSectionReorderProjectCommandStore,
|
|
private readonly circuitSectionRenumberStore: CircuitSectionRenumberProjectCommandStore,
|
|
private readonly projectDeviceStructureStore: ProjectDeviceStructureProjectCommandStore,
|
|
private readonly projectDeviceStore: ProjectDeviceProjectCommandStore,
|
|
private readonly projectDeviceRowSyncStore: ProjectDeviceRowSyncProjectCommandStore,
|
|
private readonly projectSettingsStore: ProjectSettingsProjectCommandStore,
|
|
private readonly projectStateRestoreStore: ProjectStateRestoreCommandStore,
|
|
private readonly historyStore: ProjectHistoryStore
|
|
) {}
|
|
|
|
executeUser(
|
|
input: ExecuteUserProjectCommandInput
|
|
): ExecutedProjectCommand {
|
|
assertExpectedRevision(input.expectedRevision);
|
|
assertSerializedProjectCommand(input.command);
|
|
if (input.command.type === projectStateRestoreCommandType) {
|
|
throw new Error(
|
|
"Project state restore commands require a stored server snapshot."
|
|
);
|
|
}
|
|
return this.dispatch({
|
|
...input,
|
|
source: "user",
|
|
command: input.command,
|
|
});
|
|
}
|
|
|
|
executeRestore(
|
|
input: ExecuteUserProjectCommandInput
|
|
): ExecutedProjectCommand {
|
|
assertExpectedRevision(input.expectedRevision);
|
|
assertProjectStateRestoreCommand(input.command);
|
|
return this.dispatch({
|
|
...input,
|
|
source: "restore",
|
|
command: input.command,
|
|
});
|
|
}
|
|
|
|
undo(
|
|
input: ExecuteProjectHistoryCommandInput
|
|
): ExecutedProjectCommand {
|
|
return this.executeHistory(input, "undo");
|
|
}
|
|
|
|
redo(
|
|
input: ExecuteProjectHistoryCommandInput
|
|
): ExecutedProjectCommand {
|
|
return this.executeHistory(input, "redo");
|
|
}
|
|
|
|
private executeHistory(
|
|
input: ExecuteProjectHistoryCommandInput,
|
|
direction: ProjectHistoryDirection
|
|
) {
|
|
assertExpectedRevision(input.expectedRevision);
|
|
const step = this.historyStore.getNextCommand(
|
|
input.projectId,
|
|
direction
|
|
);
|
|
if (!step) {
|
|
throw new ProjectHistoryOperationUnavailableError(
|
|
input.projectId,
|
|
direction
|
|
);
|
|
}
|
|
|
|
return this.dispatch({
|
|
...input,
|
|
source: direction,
|
|
description: `${direction === "undo" ? "Undo" : "Redo"} ${step.command.type}`,
|
|
historyTargetChangeSetId: step.changeSetId,
|
|
command: step.command,
|
|
});
|
|
}
|
|
|
|
private dispatch(
|
|
input: DispatchProjectCommandInput
|
|
): ExecutedProjectCommand {
|
|
const revision = this.executeTypedCommand(input);
|
|
const history = this.historyStore.getState(input.projectId);
|
|
if (!history) {
|
|
throw new Error("Project history disappeared after command execution.");
|
|
}
|
|
return { revision, history };
|
|
}
|
|
|
|
private executeTypedCommand(input: DispatchProjectCommandInput) {
|
|
switch (input.command.type) {
|
|
case circuitUpdateCommandType: {
|
|
assertCircuitUpdateProjectCommand(input.command);
|
|
return this.circuitStore.executeUpdate({
|
|
...input,
|
|
command: input.command,
|
|
}).revision;
|
|
}
|
|
case circuitDeviceRowUpdateCommandType: {
|
|
assertCircuitDeviceRowUpdateProjectCommand(input.command);
|
|
return this.deviceRowStore.executeUpdate({
|
|
...input,
|
|
command: input.command,
|
|
}).revision;
|
|
}
|
|
case circuitDeviceRowInsertCommandType: {
|
|
assertCircuitDeviceRowInsertProjectCommand(input.command);
|
|
return this.deviceRowStructureStore.execute({
|
|
...input,
|
|
command: input.command,
|
|
}).revision;
|
|
}
|
|
case circuitDeviceRowDeleteCommandType: {
|
|
assertCircuitDeviceRowDeleteProjectCommand(input.command);
|
|
return this.deviceRowStructureStore.execute({
|
|
...input,
|
|
command: input.command,
|
|
}).revision;
|
|
}
|
|
case circuitDeviceRowMoveCommandType: {
|
|
assertCircuitDeviceRowMoveProjectCommand(input.command);
|
|
return this.deviceRowMoveStore.execute({
|
|
...input,
|
|
command: input.command,
|
|
}).revision;
|
|
}
|
|
case circuitDeviceRowMoveWithNewCircuitCommandType: {
|
|
assertCircuitDeviceRowMoveWithNewCircuitProjectCommand(
|
|
input.command
|
|
);
|
|
return this.deviceRowMoveStore.execute({
|
|
...input,
|
|
command: input.command,
|
|
}).revision;
|
|
}
|
|
case circuitInsertCommandType: {
|
|
assertCircuitInsertProjectCommand(input.command);
|
|
return this.circuitStructureStore.execute({
|
|
...input,
|
|
command: input.command,
|
|
}).revision;
|
|
}
|
|
case circuitDeleteCommandType: {
|
|
assertCircuitDeleteProjectCommand(input.command);
|
|
return this.circuitStructureStore.execute({
|
|
...input,
|
|
command: input.command,
|
|
}).revision;
|
|
}
|
|
case distributionBoardInsertCommandType: {
|
|
assertDistributionBoardInsertProjectCommand(input.command);
|
|
return this.distributionBoardStructureStore.execute({
|
|
...input,
|
|
command: input.command,
|
|
}).revision;
|
|
}
|
|
case distributionBoardDeleteCommandType: {
|
|
assertDistributionBoardDeleteProjectCommand(input.command);
|
|
return this.distributionBoardStructureStore.execute({
|
|
...input,
|
|
command: input.command,
|
|
}).revision;
|
|
}
|
|
case circuitSectionReorderCommandType: {
|
|
assertCircuitSectionReorderProjectCommand(input.command);
|
|
return this.circuitSectionReorderStore.execute({
|
|
...input,
|
|
command: input.command,
|
|
}).revision;
|
|
}
|
|
case circuitSectionsReorderCommandType: {
|
|
assertCircuitSectionsReorderProjectCommand(input.command);
|
|
return this.circuitSectionReorderStore.execute({
|
|
...input,
|
|
command: input.command,
|
|
}).revision;
|
|
}
|
|
case circuitSectionRenumberCommandType: {
|
|
assertCircuitSectionRenumberProjectCommand(input.command);
|
|
return this.circuitSectionRenumberStore.execute({
|
|
...input,
|
|
command: input.command,
|
|
}).revision;
|
|
}
|
|
case projectDeviceRowSyncCommandType: {
|
|
assertProjectDeviceRowSyncProjectCommand(input.command);
|
|
return this.projectDeviceRowSyncStore.execute({
|
|
...input,
|
|
command: input.command,
|
|
}).revision;
|
|
}
|
|
case projectDeviceUpdateCommandType: {
|
|
assertProjectDeviceUpdateProjectCommand(input.command);
|
|
return this.projectDeviceStore.executeUpdate({
|
|
...input,
|
|
command: input.command,
|
|
}).revision;
|
|
}
|
|
case projectDeviceInsertCommandType: {
|
|
assertProjectDeviceInsertProjectCommand(input.command);
|
|
return this.projectDeviceStructureStore.execute({
|
|
...input,
|
|
command: input.command,
|
|
}).revision;
|
|
}
|
|
case projectDeviceDeleteCommandType: {
|
|
assertProjectDeviceDeleteProjectCommand(input.command);
|
|
return this.projectDeviceStructureStore.execute({
|
|
...input,
|
|
command: input.command,
|
|
}).revision;
|
|
}
|
|
case projectSettingsUpdateCommandType: {
|
|
assertProjectSettingsUpdateProjectCommand(input.command);
|
|
return this.projectSettingsStore.executeUpdate({
|
|
...input,
|
|
command: input.command,
|
|
}).revision;
|
|
}
|
|
case projectStateRestoreCommandType: {
|
|
assertProjectStateRestoreCommand(input.command);
|
|
return this.projectStateRestoreStore.execute({
|
|
...input,
|
|
command: input.command,
|
|
}).revision;
|
|
}
|
|
default:
|
|
throw new Error(
|
|
`Unsupported project command type: ${input.command.type}.`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function assertExpectedRevision(expectedRevision: number) {
|
|
if (
|
|
!Number.isSafeInteger(expectedRevision) ||
|
|
expectedRevision < 0
|
|
) {
|
|
throw new Error(
|
|
"Expected project revision must be a non-negative integer."
|
|
);
|
|
}
|
|
}
|