231 lines
7.1 KiB
TypeScript
231 lines
7.1 KiB
TypeScript
import type { SerializedProjectCommand } from "./project-command.model.js";
|
|
import {
|
|
assertCircuitInsertProjectCommand,
|
|
circuitInsertCommandType,
|
|
circuitStructureCommandSchemaVersion,
|
|
type CircuitSnapshot,
|
|
} from "./circuit-structure-project-command.model.js";
|
|
|
|
export const circuitDeviceRowMoveCommandType =
|
|
"circuit-device-row.move" as const;
|
|
export const circuitDeviceRowMoveWithNewCircuitCommandType =
|
|
"circuit-device-row.move-with-new-circuit" as const;
|
|
export const circuitDeviceRowMoveCommandSchemaVersion = 1 as const;
|
|
|
|
export interface CircuitDeviceRowMoveAssignment {
|
|
rowId: string;
|
|
expectedCircuitId: string;
|
|
expectedSortOrder: number;
|
|
targetCircuitId: string;
|
|
targetSortOrder: number;
|
|
}
|
|
|
|
export interface CircuitDeviceRowMoveCommandPayload {
|
|
moves: CircuitDeviceRowMoveAssignment[];
|
|
}
|
|
|
|
export interface CircuitDeviceRowMoveProjectCommand
|
|
extends SerializedProjectCommand<CircuitDeviceRowMoveCommandPayload> {
|
|
schemaVersion: typeof circuitDeviceRowMoveCommandSchemaVersion;
|
|
type: typeof circuitDeviceRowMoveCommandType;
|
|
}
|
|
|
|
export type CircuitDeviceRowMoveTargetCircuitAction =
|
|
| "create"
|
|
| "delete";
|
|
|
|
export interface CircuitDeviceRowMoveWithNewCircuitCommandPayload {
|
|
targetCircuitAction: CircuitDeviceRowMoveTargetCircuitAction;
|
|
targetCircuit: CircuitSnapshot;
|
|
moves: CircuitDeviceRowMoveAssignment[];
|
|
}
|
|
|
|
export interface CircuitDeviceRowMoveWithNewCircuitProjectCommand
|
|
extends SerializedProjectCommand<CircuitDeviceRowMoveWithNewCircuitCommandPayload> {
|
|
schemaVersion: typeof circuitDeviceRowMoveCommandSchemaVersion;
|
|
type: typeof circuitDeviceRowMoveWithNewCircuitCommandType;
|
|
}
|
|
|
|
export type CircuitDeviceRowMoveHistoryProjectCommand =
|
|
| CircuitDeviceRowMoveProjectCommand
|
|
| CircuitDeviceRowMoveWithNewCircuitProjectCommand;
|
|
|
|
export function createCircuitDeviceRowMoveProjectCommand(
|
|
moves: CircuitDeviceRowMoveAssignment[]
|
|
): CircuitDeviceRowMoveProjectCommand {
|
|
const command: CircuitDeviceRowMoveProjectCommand = {
|
|
schemaVersion: circuitDeviceRowMoveCommandSchemaVersion,
|
|
type: circuitDeviceRowMoveCommandType,
|
|
payload: { moves },
|
|
};
|
|
assertCircuitDeviceRowMoveProjectCommand(command);
|
|
return command;
|
|
}
|
|
|
|
export function createCircuitDeviceRowMoveWithNewCircuitProjectCommand(
|
|
targetCircuitAction: CircuitDeviceRowMoveTargetCircuitAction,
|
|
targetCircuit: CircuitSnapshot,
|
|
moves: CircuitDeviceRowMoveAssignment[]
|
|
): CircuitDeviceRowMoveWithNewCircuitProjectCommand {
|
|
const command: CircuitDeviceRowMoveWithNewCircuitProjectCommand = {
|
|
schemaVersion: circuitDeviceRowMoveCommandSchemaVersion,
|
|
type: circuitDeviceRowMoveWithNewCircuitCommandType,
|
|
payload: {
|
|
targetCircuitAction,
|
|
targetCircuit,
|
|
moves,
|
|
},
|
|
};
|
|
assertCircuitDeviceRowMoveWithNewCircuitProjectCommand(command);
|
|
return command;
|
|
}
|
|
|
|
export function assertCircuitDeviceRowMoveProjectCommand(
|
|
command: SerializedProjectCommand<unknown>
|
|
): asserts command is CircuitDeviceRowMoveProjectCommand {
|
|
if (
|
|
command.schemaVersion !== circuitDeviceRowMoveCommandSchemaVersion ||
|
|
command.type !== circuitDeviceRowMoveCommandType
|
|
) {
|
|
throw new Error("Unsupported circuit device-row move command.");
|
|
}
|
|
if (!isPlainObject(command.payload)) {
|
|
throw new Error(
|
|
"Circuit device-row move payload must be an object."
|
|
);
|
|
}
|
|
const moves = command.payload.moves;
|
|
if (!Array.isArray(moves) || moves.length === 0) {
|
|
throw new Error(
|
|
"Circuit device-row move command requires at least one move."
|
|
);
|
|
}
|
|
|
|
const rowIds = new Set<string>();
|
|
for (const move of moves) {
|
|
if (!isPlainObject(move)) {
|
|
throw new Error(
|
|
"Circuit device-row move command contains an invalid move."
|
|
);
|
|
}
|
|
assertNonEmptyString(move.rowId, "rowId");
|
|
assertNonEmptyString(
|
|
move.expectedCircuitId,
|
|
"expectedCircuitId"
|
|
);
|
|
assertFiniteNumber(
|
|
move.expectedSortOrder,
|
|
"expectedSortOrder"
|
|
);
|
|
assertNonEmptyString(move.targetCircuitId, "targetCircuitId");
|
|
assertFiniteNumber(move.targetSortOrder, "targetSortOrder");
|
|
if (rowIds.has(move.rowId)) {
|
|
throw new Error(
|
|
"Circuit device-row move command contains duplicate row ids."
|
|
);
|
|
}
|
|
if (
|
|
move.expectedCircuitId === move.targetCircuitId &&
|
|
move.expectedSortOrder === move.targetSortOrder
|
|
) {
|
|
throw new Error(
|
|
"Circuit device-row move command contains a no-op move."
|
|
);
|
|
}
|
|
|
|
rowIds.add(move.rowId);
|
|
}
|
|
}
|
|
|
|
export function assertCircuitDeviceRowMoveWithNewCircuitProjectCommand(
|
|
command: SerializedProjectCommand<unknown>
|
|
): asserts command is CircuitDeviceRowMoveWithNewCircuitProjectCommand {
|
|
if (
|
|
command.schemaVersion !== circuitDeviceRowMoveCommandSchemaVersion ||
|
|
command.type !== circuitDeviceRowMoveWithNewCircuitCommandType
|
|
) {
|
|
throw new Error(
|
|
"Unsupported circuit device-row move-with-new-circuit command."
|
|
);
|
|
}
|
|
if (!isPlainObject(command.payload)) {
|
|
throw new Error(
|
|
"Circuit device-row move-with-new-circuit payload must be an object."
|
|
);
|
|
}
|
|
const { targetCircuitAction, targetCircuit, moves } =
|
|
command.payload;
|
|
if (
|
|
targetCircuitAction !== "create" &&
|
|
targetCircuitAction !== "delete"
|
|
) {
|
|
throw new Error(
|
|
"Target circuit action must be create or delete."
|
|
);
|
|
}
|
|
|
|
assertCircuitInsertProjectCommand({
|
|
schemaVersion: circuitStructureCommandSchemaVersion,
|
|
type: circuitInsertCommandType,
|
|
payload: { circuit: targetCircuit },
|
|
});
|
|
const validatedTargetCircuit = targetCircuit as CircuitSnapshot;
|
|
if (
|
|
validatedTargetCircuit.isReserve !== true ||
|
|
validatedTargetCircuit.deviceRows.length !== 0
|
|
) {
|
|
throw new Error(
|
|
"Move target circuit snapshot must describe an empty reserve circuit."
|
|
);
|
|
}
|
|
|
|
assertCircuitDeviceRowMoveProjectCommand({
|
|
schemaVersion: circuitDeviceRowMoveCommandSchemaVersion,
|
|
type: circuitDeviceRowMoveCommandType,
|
|
payload: { moves },
|
|
});
|
|
const validatedMoves = moves as CircuitDeviceRowMoveAssignment[];
|
|
for (const move of validatedMoves) {
|
|
if (
|
|
targetCircuitAction === "create" &&
|
|
(move.expectedCircuitId === validatedTargetCircuit.id ||
|
|
move.targetCircuitId !== validatedTargetCircuit.id)
|
|
) {
|
|
throw new Error(
|
|
"Create-target moves must move rows into the new circuit."
|
|
);
|
|
}
|
|
if (
|
|
targetCircuitAction === "delete" &&
|
|
(move.expectedCircuitId !== validatedTargetCircuit.id ||
|
|
move.targetCircuitId === validatedTargetCircuit.id)
|
|
) {
|
|
throw new Error(
|
|
"Delete-target moves must move rows out of the created circuit."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function assertNonEmptyString(
|
|
value: unknown,
|
|
field: string
|
|
): asserts value is string {
|
|
if (typeof value !== "string" || !value.trim()) {
|
|
throw new Error(`${field} must be a non-empty string.`);
|
|
}
|
|
}
|
|
|
|
function assertFiniteNumber(
|
|
value: unknown,
|
|
field: string
|
|
): asserts value is number {
|
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
throw new Error(`${field} must be a finite number.`);
|
|
}
|
|
}
|
|
|
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
}
|