import { assertCircuitDeviceRowInsertProjectCommand, circuitDeviceRowInsertCommandType, circuitDeviceRowStructureCommandSchemaVersion, type CircuitDeviceRowSnapshot, } from "./circuit-device-row-structure-project-command.model.js"; import { assertProjectDeviceUpdateProjectCommand, projectDeviceUpdateCommandSchemaVersion, projectDeviceUpdateCommandType, projectDeviceUpdateFields, type ProjectDeviceUpdateValues, } from "./project-device-project-command.model.js"; import type { SerializedProjectCommand } from "./project-command.model.js"; export const projectDeviceInsertCommandType = "project-device.insert" as const; export const projectDeviceDeleteCommandType = "project-device.delete" as const; export const projectDeviceStructureCommandSchemaVersion = 1 as const; export interface ProjectDeviceSnapshot extends ProjectDeviceUpdateValues { id: string; projectId: string; } export interface ProjectDeviceInsertCommandPayload { projectDevice: ProjectDeviceSnapshot; linkedRows: CircuitDeviceRowSnapshot[]; } export interface ProjectDeviceDeleteCommandPayload { projectDeviceId: string; expectedProjectId: string; } export interface ProjectDeviceInsertProjectCommand extends SerializedProjectCommand { schemaVersion: typeof projectDeviceStructureCommandSchemaVersion; type: typeof projectDeviceInsertCommandType; } export interface ProjectDeviceDeleteProjectCommand extends SerializedProjectCommand { schemaVersion: typeof projectDeviceStructureCommandSchemaVersion; type: typeof projectDeviceDeleteCommandType; } export type ProjectDeviceStructureProjectCommand = | ProjectDeviceInsertProjectCommand | ProjectDeviceDeleteProjectCommand; export function createProjectDeviceInsertProjectCommand( projectDevice: ProjectDeviceSnapshot, linkedRows: CircuitDeviceRowSnapshot[] = [] ): ProjectDeviceInsertProjectCommand { const command: ProjectDeviceInsertProjectCommand = { schemaVersion: projectDeviceStructureCommandSchemaVersion, type: projectDeviceInsertCommandType, payload: { projectDevice, linkedRows }, }; assertProjectDeviceInsertProjectCommand(command); return command; } export function createProjectDeviceDeleteProjectCommand( projectDeviceId: string, expectedProjectId: string ): ProjectDeviceDeleteProjectCommand { const command: ProjectDeviceDeleteProjectCommand = { schemaVersion: projectDeviceStructureCommandSchemaVersion, type: projectDeviceDeleteCommandType, payload: { projectDeviceId, expectedProjectId }, }; assertProjectDeviceDeleteProjectCommand(command); return command; } export function assertProjectDeviceInsertProjectCommand( command: SerializedProjectCommand ): asserts command is ProjectDeviceInsertProjectCommand { if ( command.schemaVersion !== projectDeviceStructureCommandSchemaVersion || command.type !== projectDeviceInsertCommandType ) { throw new Error("Unsupported project-device insert command."); } if (!isPlainObject(command.payload)) { throw new Error("Project-device insert payload must be an object."); } const { projectDevice, linkedRows } = command.payload; if (!isPlainObject(projectDevice)) { throw new Error( "Project-device insert command requires a project device." ); } assertNonEmptyString(projectDevice.id, "projectDevice.id"); assertNonEmptyString( projectDevice.projectId, "projectDevice.projectId" ); assertProjectDeviceUpdateProjectCommand({ schemaVersion: projectDeviceUpdateCommandSchemaVersion, type: projectDeviceUpdateCommandType, payload: { projectDeviceId: projectDevice.id, changes: projectDeviceUpdateFields.map((field) => ({ field, value: projectDevice[field], })), }, }); if (!Array.isArray(linkedRows)) { throw new Error( "Project-device insert linkedRows must be an array." ); } const rowIds = new Set(); for (const row of linkedRows) { assertCircuitDeviceRowInsertProjectCommand({ schemaVersion: circuitDeviceRowStructureCommandSchemaVersion, type: circuitDeviceRowInsertCommandType, payload: { row }, }); if (row.linkedProjectDeviceId !== null) { throw new Error( "Restored project-device rows must currently be disconnected." ); } if (rowIds.has(row.id)) { throw new Error( "Project-device insert command contains duplicate row ids." ); } rowIds.add(row.id); } } export function assertProjectDeviceDeleteProjectCommand( command: SerializedProjectCommand ): asserts command is ProjectDeviceDeleteProjectCommand { if ( command.schemaVersion !== projectDeviceStructureCommandSchemaVersion || command.type !== projectDeviceDeleteCommandType ) { throw new Error("Unsupported project-device delete command."); } if (!isPlainObject(command.payload)) { throw new Error("Project-device delete payload must be an object."); } assertNonEmptyString( command.payload.projectDeviceId, "projectDeviceId" ); assertNonEmptyString( command.payload.expectedProjectId, "expectedProjectId" ); } function assertNonEmptyString(value: unknown, field: string) { if (typeof value !== "string" || !value.trim()) { throw new Error(`${field} must be a non-empty string.`); } } function isPlainObject(value: unknown): value is Record { return value !== null && typeof value === "object" && !Array.isArray(value); }