172 lines
5.4 KiB
TypeScript
172 lines
5.4 KiB
TypeScript
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<ProjectDeviceInsertCommandPayload> {
|
|
schemaVersion: typeof projectDeviceStructureCommandSchemaVersion;
|
|
type: typeof projectDeviceInsertCommandType;
|
|
}
|
|
|
|
export interface ProjectDeviceDeleteProjectCommand
|
|
extends SerializedProjectCommand<ProjectDeviceDeleteCommandPayload> {
|
|
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<unknown>
|
|
): 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<string>();
|
|
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<unknown>
|
|
): 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<string, unknown> {
|
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
}
|