Add circuit group commands

This commit is contained in:
2026-07-30 19:46:55 +02:00
parent 69b020339e
commit 1250f9a6af
16 changed files with 864 additions and 5 deletions
@@ -0,0 +1,192 @@
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);
}