193 lines
5.9 KiB
TypeScript
193 lines
5.9 KiB
TypeScript
import {
|
|
circuitGroupCategories,
|
|
type CircuitGroupCategory,
|
|
} from "../../shared/constants/circuit-group.js";
|
|
import { formatCircuitGroupPrefix } from "../services/circuit-group-numbering.js";
|
|
import type { SerializedProjectCommand } from "./project-command.model.js";
|
|
|
|
export const circuitGroupInsertCommandType = "circuit-group.insert" as const;
|
|
export const circuitGroupDeleteCommandType = "circuit-group.delete" as const;
|
|
export const circuitGroupUpdateCommandType = "circuit-group.update" as const;
|
|
export const circuitGroupStructureCommandSchemaVersion = 1 as const;
|
|
|
|
export interface CircuitGroupSnapshot {
|
|
id: string;
|
|
circuitListId: string;
|
|
key: string;
|
|
displayName: string;
|
|
prefix: string;
|
|
sortOrder: number;
|
|
category: CircuitGroupCategory;
|
|
groupNumber: number;
|
|
}
|
|
|
|
interface SnapshotPayload {
|
|
snapshot: CircuitGroupSnapshot;
|
|
}
|
|
|
|
interface UpdatePayload {
|
|
expected: CircuitGroupSnapshot;
|
|
target: CircuitGroupSnapshot;
|
|
}
|
|
|
|
export interface CircuitGroupInsertProjectCommand
|
|
extends SerializedProjectCommand<SnapshotPayload> {
|
|
schemaVersion: typeof circuitGroupStructureCommandSchemaVersion;
|
|
type: typeof circuitGroupInsertCommandType;
|
|
}
|
|
|
|
export interface CircuitGroupDeleteProjectCommand
|
|
extends SerializedProjectCommand<SnapshotPayload> {
|
|
schemaVersion: typeof circuitGroupStructureCommandSchemaVersion;
|
|
type: typeof circuitGroupDeleteCommandType;
|
|
}
|
|
|
|
export interface CircuitGroupUpdateProjectCommand
|
|
extends SerializedProjectCommand<UpdatePayload> {
|
|
schemaVersion: typeof circuitGroupStructureCommandSchemaVersion;
|
|
type: typeof circuitGroupUpdateCommandType;
|
|
}
|
|
|
|
export type CircuitGroupStructureProjectCommand =
|
|
| CircuitGroupInsertProjectCommand
|
|
| CircuitGroupDeleteProjectCommand
|
|
| CircuitGroupUpdateProjectCommand;
|
|
|
|
export function createCircuitGroupInsertProjectCommand(
|
|
snapshot: CircuitGroupSnapshot
|
|
): CircuitGroupInsertProjectCommand {
|
|
const command: CircuitGroupInsertProjectCommand = {
|
|
schemaVersion: circuitGroupStructureCommandSchemaVersion,
|
|
type: circuitGroupInsertCommandType,
|
|
payload: { snapshot },
|
|
};
|
|
assertCircuitGroupInsertProjectCommand(command);
|
|
return command;
|
|
}
|
|
|
|
export function createCircuitGroupDeleteProjectCommand(
|
|
snapshot: CircuitGroupSnapshot
|
|
): CircuitGroupDeleteProjectCommand {
|
|
const command: CircuitGroupDeleteProjectCommand = {
|
|
schemaVersion: circuitGroupStructureCommandSchemaVersion,
|
|
type: circuitGroupDeleteCommandType,
|
|
payload: { snapshot },
|
|
};
|
|
assertCircuitGroupDeleteProjectCommand(command);
|
|
return command;
|
|
}
|
|
|
|
export function createCircuitGroupUpdateProjectCommand(
|
|
expected: CircuitGroupSnapshot,
|
|
target: CircuitGroupSnapshot
|
|
): CircuitGroupUpdateProjectCommand {
|
|
const command: CircuitGroupUpdateProjectCommand = {
|
|
schemaVersion: circuitGroupStructureCommandSchemaVersion,
|
|
type: circuitGroupUpdateCommandType,
|
|
payload: { expected, target },
|
|
};
|
|
assertCircuitGroupUpdateProjectCommand(command);
|
|
return command;
|
|
}
|
|
|
|
export function assertCircuitGroupInsertProjectCommand(
|
|
command: SerializedProjectCommand<unknown>
|
|
): asserts command is CircuitGroupInsertProjectCommand {
|
|
assertSnapshotCommand(command, circuitGroupInsertCommandType);
|
|
}
|
|
|
|
export function assertCircuitGroupDeleteProjectCommand(
|
|
command: SerializedProjectCommand<unknown>
|
|
): asserts command is CircuitGroupDeleteProjectCommand {
|
|
assertSnapshotCommand(command, circuitGroupDeleteCommandType);
|
|
}
|
|
|
|
export function assertCircuitGroupUpdateProjectCommand(
|
|
command: SerializedProjectCommand<unknown>
|
|
): asserts command is CircuitGroupUpdateProjectCommand {
|
|
if (
|
|
command.schemaVersion !== circuitGroupStructureCommandSchemaVersion ||
|
|
command.type !== circuitGroupUpdateCommandType ||
|
|
!isPlainObject(command.payload) ||
|
|
Object.keys(command.payload).length !== 2
|
|
) {
|
|
throw new Error("Unsupported circuit-group update command.");
|
|
}
|
|
assertCircuitGroupSnapshot(command.payload.expected);
|
|
assertCircuitGroupSnapshot(command.payload.target);
|
|
for (const field of [
|
|
"id",
|
|
"circuitListId",
|
|
"key",
|
|
"prefix",
|
|
"sortOrder",
|
|
"category",
|
|
"groupNumber",
|
|
] as const) {
|
|
if (command.payload.expected[field] !== command.payload.target[field]) {
|
|
throw new Error(
|
|
"Circuit-group updates can only change the display name."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function assertSnapshotCommand(
|
|
command: SerializedProjectCommand<unknown>,
|
|
type:
|
|
| typeof circuitGroupInsertCommandType
|
|
| typeof circuitGroupDeleteCommandType
|
|
) {
|
|
if (
|
|
command.schemaVersion !== circuitGroupStructureCommandSchemaVersion ||
|
|
command.type !== type ||
|
|
!isPlainObject(command.payload) ||
|
|
Object.keys(command.payload).length !== 1
|
|
) {
|
|
throw new Error("Unsupported circuit-group structure command.");
|
|
}
|
|
assertCircuitGroupSnapshot(command.payload.snapshot);
|
|
}
|
|
|
|
export function assertCircuitGroupSnapshot(
|
|
value: unknown
|
|
): asserts value is CircuitGroupSnapshot {
|
|
if (!isPlainObject(value) || Object.keys(value).length !== 8) {
|
|
throw new Error("Circuit-group snapshot is invalid.");
|
|
}
|
|
for (const field of [
|
|
"id",
|
|
"circuitListId",
|
|
"key",
|
|
"displayName",
|
|
"prefix",
|
|
] as const) {
|
|
if (typeof value[field] !== "string" || !value[field].trim()) {
|
|
throw new Error(`${field} must be a non-empty string.`);
|
|
}
|
|
}
|
|
if (
|
|
!circuitGroupCategories.includes(
|
|
value.category as CircuitGroupCategory
|
|
) ||
|
|
!Number.isInteger(value.groupNumber) ||
|
|
(value.groupNumber as number) < 1 ||
|
|
!Number.isFinite(value.sortOrder)
|
|
) {
|
|
throw new Error("Circuit-group category, number or order is invalid.");
|
|
}
|
|
if (
|
|
value.prefix !==
|
|
formatCircuitGroupPrefix(
|
|
value.category as CircuitGroupCategory,
|
|
value.groupNumber as number
|
|
)
|
|
) {
|
|
throw new Error("Circuit-group prefix does not match its category and number.");
|
|
}
|
|
}
|
|
|
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
}
|