import type { SerializedProjectCommand } from "./project-command.model.js"; export const circuitUpdateCommandType = "circuit.update" as const; export const circuitUpdateCommandSchemaVersion = 1 as const; export interface CircuitUpdateValues { sectionId: string; equipmentIdentifier: string; displayName: string | null; sortOrder: number; cableType: string | null; cableCrossSection: string | null; cableLength: number | null; rcdAssignment: string | null; terminalDesignation: string | null; voltage: number | null; controlRequirement: string | null; status: string | null; isReserve: boolean; remark: string | null; } export type CircuitUpdateField = keyof CircuitUpdateValues; export type CircuitUpdatePatch = Partial; export interface CircuitUpdateFieldChange< TField extends CircuitUpdateField = CircuitUpdateField, > { field: TField; value: CircuitUpdateValues[TField]; } export interface CircuitUpdateCommandPayload { circuitId: string; changes: CircuitUpdateFieldChange[]; } export interface CircuitUpdateProjectCommand extends SerializedProjectCommand { schemaVersion: typeof circuitUpdateCommandSchemaVersion; type: typeof circuitUpdateCommandType; } export function createCircuitUpdateProjectCommand( circuitId: string, patch: CircuitUpdatePatch ): CircuitUpdateProjectCommand { const changes = Object.entries(patch).map(([field, value]) => ({ field: field as CircuitUpdateField, value, })) as CircuitUpdateFieldChange[]; const command: CircuitUpdateProjectCommand = { schemaVersion: circuitUpdateCommandSchemaVersion, type: circuitUpdateCommandType, payload: { circuitId, changes }, }; assertCircuitUpdateProjectCommand(command); return command; } export function assertCircuitUpdateProjectCommand( command: SerializedProjectCommand ): asserts command is CircuitUpdateProjectCommand { if ( command.schemaVersion !== circuitUpdateCommandSchemaVersion || command.type !== circuitUpdateCommandType ) { throw new Error("Unsupported circuit update command."); } if (!isPlainObject(command.payload)) { throw new Error("Circuit update payload must be an object."); } const circuitId = command.payload.circuitId; const changes = command.payload.changes; if (typeof circuitId !== "string" || !circuitId.trim()) { throw new Error("Circuit update command requires a circuit id."); } if (!Array.isArray(changes) || changes.length === 0) { throw new Error("Circuit update command requires at least one change."); } const seenFields = new Set(); for (const change of changes) { if (!isPlainObject(change) || typeof change.field !== "string") { throw new Error("Circuit update command contains an invalid change."); } if (!isCircuitUpdateField(change.field) || seenFields.has(change.field)) { throw new Error("Circuit update command contains an invalid or duplicate field."); } assertCircuitUpdateFieldValue(change.field, change.value); seenFields.add(change.field); } } function assertCircuitUpdateFieldValue( field: CircuitUpdateField, value: unknown ) { if (field === "sectionId" || field === "equipmentIdentifier") { if (typeof value !== "string" || !value.trim()) { throw new Error(`${field} must be a non-empty string.`); } return; } if (field === "sortOrder") { if (typeof value !== "number" || !Number.isFinite(value)) { throw new Error("sortOrder must be a finite number."); } return; } if (field === "isReserve") { if (typeof value !== "boolean") { throw new Error("isReserve must be a boolean."); } return; } if ( field === "cableLength" || field === "voltage" ) { if (value === null) { return; } if (typeof value !== "number" || !Number.isFinite(value)) { throw new Error(`${field} must be a finite number or null.`); } if ( (field === "voltage" && value <= 0) || (field !== "voltage" && value < 0) ) { throw new Error(`${field} is outside its allowed range.`); } return; } if (value !== null && typeof value !== "string") { throw new Error(`${field} must be a string or null.`); } } function isCircuitUpdateField(value: string): value is CircuitUpdateField { return [ "sectionId", "equipmentIdentifier", "displayName", "sortOrder", "cableType", "cableCrossSection", "cableLength", "rcdAssignment", "terminalDesignation", "voltage", "controlRequirement", "status", "isReserve", "remark", ].includes(value); } function isPlainObject(value: unknown): value is Record { return value !== null && typeof value === "object" && !Array.isArray(value); }