Add project command API
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
import { ProjectHistoryOperationUnavailableError } from "../errors/project-history-operation-unavailable.error.js";
|
||||
import {
|
||||
assertCircuitDeviceRowUpdateProjectCommand,
|
||||
circuitDeviceRowUpdateCommandType,
|
||||
} from "../models/circuit-device-row-project-command.model.js";
|
||||
import {
|
||||
assertCircuitUpdateProjectCommand,
|
||||
circuitUpdateCommandType,
|
||||
} from "../models/circuit-project-command.model.js";
|
||||
import {
|
||||
assertSerializedProjectCommand,
|
||||
type SerializedProjectCommand,
|
||||
} from "../models/project-command.model.js";
|
||||
import type { CircuitDeviceRowProjectCommandStore } from "../ports/circuit-device-row-project-command.store.js";
|
||||
import type { CircuitProjectCommandStore } from "../ports/circuit-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 { ProjectRevisionSource } from "../ports/project-revision.store.js";
|
||||
|
||||
interface DispatchProjectCommandInput {
|
||||
projectId: string;
|
||||
expectedRevision: number;
|
||||
source: ProjectRevisionSource;
|
||||
description?: string;
|
||||
actorId?: string;
|
||||
historyTargetChangeSetId?: string;
|
||||
command: SerializedProjectCommand;
|
||||
}
|
||||
|
||||
export class ProjectCommandService implements ProjectCommandExecutor {
|
||||
constructor(
|
||||
private readonly circuitStore: CircuitProjectCommandStore,
|
||||
private readonly deviceRowStore: CircuitDeviceRowProjectCommandStore,
|
||||
private readonly historyStore: ProjectHistoryStore
|
||||
) {}
|
||||
|
||||
executeUser(
|
||||
input: ExecuteUserProjectCommandInput
|
||||
): ExecutedProjectCommand {
|
||||
assertExpectedRevision(input.expectedRevision);
|
||||
assertSerializedProjectCommand(input.command);
|
||||
return this.dispatch({
|
||||
...input,
|
||||
source: "user",
|
||||
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;
|
||||
}
|
||||
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."
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user