Persist Revit CSV configuration
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
import { assertExternalCsvConfiguration } from "../../external-model/csv/external-csv-configuration.js";
|
||||
import type { ExternalCsvConfiguration } from "../../external-model/csv/external-csv-contracts.js";
|
||||
import type { SerializedProjectCommand } from "./project-command.model.js";
|
||||
|
||||
export const externalCsvConfigurationUpdateCommandType =
|
||||
"external-csv-configuration.update" as const;
|
||||
export const externalCsvConfigurationCommandSchemaVersion = 1 as const;
|
||||
|
||||
export interface ExternalCsvConfigurationSnapshot {
|
||||
id: string;
|
||||
projectId: string;
|
||||
configurationVersion: number;
|
||||
configuration: ExternalCsvConfiguration;
|
||||
}
|
||||
|
||||
export interface ExternalCsvConfigurationUpdatePayload {
|
||||
expected: ExternalCsvConfigurationSnapshot | null;
|
||||
target: ExternalCsvConfigurationSnapshot | null;
|
||||
}
|
||||
|
||||
export interface ExternalCsvConfigurationUpdateProjectCommand
|
||||
extends SerializedProjectCommand<ExternalCsvConfigurationUpdatePayload> {
|
||||
schemaVersion: typeof externalCsvConfigurationCommandSchemaVersion;
|
||||
type: typeof externalCsvConfigurationUpdateCommandType;
|
||||
}
|
||||
|
||||
export function createExternalCsvConfigurationUpdateProjectCommand(
|
||||
expected: ExternalCsvConfigurationSnapshot | null,
|
||||
target: ExternalCsvConfigurationSnapshot | null
|
||||
): ExternalCsvConfigurationUpdateProjectCommand {
|
||||
const command: ExternalCsvConfigurationUpdateProjectCommand = {
|
||||
schemaVersion: externalCsvConfigurationCommandSchemaVersion,
|
||||
type: externalCsvConfigurationUpdateCommandType,
|
||||
payload: { expected, target },
|
||||
};
|
||||
assertExternalCsvConfigurationUpdateProjectCommand(command);
|
||||
return command;
|
||||
}
|
||||
|
||||
export function assertExternalCsvConfigurationUpdateProjectCommand(
|
||||
command: SerializedProjectCommand<unknown>
|
||||
): asserts command is ExternalCsvConfigurationUpdateProjectCommand {
|
||||
if (
|
||||
command.schemaVersion !== externalCsvConfigurationCommandSchemaVersion ||
|
||||
command.type !== externalCsvConfigurationUpdateCommandType ||
|
||||
!isRecord(command.payload)
|
||||
) {
|
||||
throw new Error("Unsupported external CSV configuration update command.");
|
||||
}
|
||||
assertSnapshot(command.payload.expected, "expected");
|
||||
assertSnapshot(command.payload.target, "target");
|
||||
if (command.payload.expected === null && command.payload.target === null) {
|
||||
throw new Error("External CSV configuration command must change state.");
|
||||
}
|
||||
if (
|
||||
command.payload.expected !== null &&
|
||||
command.payload.target !== null &&
|
||||
(command.payload.expected.id !== command.payload.target.id ||
|
||||
command.payload.expected.projectId !== command.payload.target.projectId)
|
||||
) {
|
||||
throw new Error("External CSV configuration identity must remain stable.");
|
||||
}
|
||||
}
|
||||
|
||||
function assertSnapshot(
|
||||
value: unknown,
|
||||
field: string
|
||||
): asserts value is ExternalCsvConfigurationSnapshot | null {
|
||||
if (value === null) {
|
||||
return;
|
||||
}
|
||||
if (!isRecord(value)) {
|
||||
throw new Error(`${field} external CSV configuration must be an object or null.`);
|
||||
}
|
||||
assertId(value.id, `${field}.id`);
|
||||
assertId(value.projectId, `${field}.projectId`);
|
||||
if (!Number.isSafeInteger(value.configurationVersion) || (value.configurationVersion as number) < 1) {
|
||||
throw new Error(`${field}.configurationVersion must be a positive integer.`);
|
||||
}
|
||||
assertExternalCsvConfiguration(value.configuration);
|
||||
if (Object.keys(value).length !== 4) {
|
||||
throw new Error(`${field} external CSV configuration contains unknown fields.`);
|
||||
}
|
||||
}
|
||||
|
||||
function assertId(value: unknown, field: string) {
|
||||
if (typeof value !== "string" || !value.trim()) {
|
||||
throw new Error(`${field} must be a non-empty string.`);
|
||||
}
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
@@ -18,9 +18,12 @@ import {
|
||||
resolveCircuitPhaseType,
|
||||
resolveProjectVoltage,
|
||||
} from "../services/project-voltage.service.js";
|
||||
import { validateExternalCsvConfiguration } from "../../external-model/csv/external-csv-configuration.js";
|
||||
import type { ExternalCsvConfiguration } from "../../external-model/csv/external-csv-contracts.js";
|
||||
|
||||
export const projectStateSnapshotSchemaVersion = 2 as const;
|
||||
const previousProjectStateSnapshotSchemaVersion = 1 as const;
|
||||
export const projectStateSnapshotSchemaVersion = 3 as const;
|
||||
const previousProjectStateSnapshotSchemaVersion = 2 as const;
|
||||
const baselineProjectStateSnapshotSchemaVersion = 1 as const;
|
||||
|
||||
const idSchema = z.string().trim().min(1);
|
||||
const nullableStringSchema = z.string().nullable();
|
||||
@@ -249,6 +252,17 @@ const componentProtectionDeviceSchema = z
|
||||
.strict()
|
||||
.superRefine(validatePersistedProtectionDevice);
|
||||
|
||||
const externalCsvConfigurationSnapshotSchema = z
|
||||
.object({
|
||||
id: idSchema,
|
||||
projectId: idSchema,
|
||||
configurationVersion: z.number().int().positive(),
|
||||
configuration: z.custom<ExternalCsvConfiguration>(
|
||||
(value) => validateExternalCsvConfiguration(value).success
|
||||
),
|
||||
})
|
||||
.strict();
|
||||
|
||||
const projectStateSnapshotContents = {
|
||||
circuitLists: z.array(circuitListSchema),
|
||||
circuitSections: z.array(circuitSectionSchema),
|
||||
@@ -268,6 +282,7 @@ export const projectStateSnapshotSchema = z
|
||||
schemaVersion: z.literal(projectStateSnapshotSchemaVersion),
|
||||
project: projectSchema,
|
||||
distributionBoards: z.array(distributionBoardSchema),
|
||||
externalCsvConfiguration: externalCsvConfigurationSnapshotSchema.nullable(),
|
||||
...projectStateSnapshotContents,
|
||||
})
|
||||
.strict();
|
||||
@@ -275,6 +290,15 @@ export const projectStateSnapshotSchema = z
|
||||
const previousProjectStateSnapshotSchema = z
|
||||
.object({
|
||||
schemaVersion: z.literal(previousProjectStateSnapshotSchemaVersion),
|
||||
project: projectSchema,
|
||||
distributionBoards: z.array(distributionBoardSchema),
|
||||
...projectStateSnapshotContents,
|
||||
})
|
||||
.strict();
|
||||
|
||||
const baselineProjectStateSnapshotSchema = z
|
||||
.object({
|
||||
schemaVersion: z.literal(baselineProjectStateSnapshotSchemaVersion),
|
||||
project: previousProjectSchema,
|
||||
distributionBoards: z.array(distributionBoardSchema),
|
||||
...projectStateSnapshotContents,
|
||||
@@ -301,20 +325,29 @@ function upgradePreviousProjectStateSnapshot(value: unknown): unknown {
|
||||
value === null ||
|
||||
typeof value !== "object" ||
|
||||
Array.isArray(value) ||
|
||||
(value as { schemaVersion?: unknown }).schemaVersion !==
|
||||
previousProjectStateSnapshotSchemaVersion
|
||||
typeof (value as { schemaVersion?: unknown }).schemaVersion !== "number"
|
||||
) {
|
||||
return value;
|
||||
}
|
||||
const previous = previousProjectStateSnapshotSchema.parse(value);
|
||||
return {
|
||||
...previous,
|
||||
schemaVersion: projectStateSnapshotSchemaVersion,
|
||||
project: {
|
||||
...previous.project,
|
||||
isPublicBuilding: false,
|
||||
},
|
||||
};
|
||||
const schemaVersion = (value as { schemaVersion: number }).schemaVersion;
|
||||
if (schemaVersion === previousProjectStateSnapshotSchemaVersion) {
|
||||
const previous = previousProjectStateSnapshotSchema.parse(value);
|
||||
return {
|
||||
...previous,
|
||||
schemaVersion: projectStateSnapshotSchemaVersion,
|
||||
externalCsvConfiguration: null,
|
||||
};
|
||||
}
|
||||
if (schemaVersion === baselineProjectStateSnapshotSchemaVersion) {
|
||||
const baseline = baselineProjectStateSnapshotSchema.parse(value);
|
||||
return {
|
||||
...baseline,
|
||||
schemaVersion: projectStateSnapshotSchemaVersion,
|
||||
project: { ...baseline.project, isPublicBuilding: false },
|
||||
externalCsvConfiguration: null,
|
||||
};
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function normalizeSnapshotPhaseTypes(
|
||||
@@ -377,6 +410,12 @@ function assertProjectStateSnapshotRelations(
|
||||
snapshot: ProjectStateSnapshot
|
||||
) {
|
||||
const projectId = snapshot.project.id;
|
||||
if (
|
||||
snapshot.externalCsvConfiguration !== null &&
|
||||
snapshot.externalCsvConfiguration.projectId !== projectId
|
||||
) {
|
||||
throw new Error("Snapshot external CSV configuration belongs to a different project.");
|
||||
}
|
||||
const boardIds = uniqueIds(
|
||||
snapshot.distributionBoards,
|
||||
"distribution board"
|
||||
|
||||
@@ -94,6 +94,14 @@ export function remapProjectState(
|
||||
return parseProjectStateSnapshot({
|
||||
...source,
|
||||
project: { ...source.project, id: targetProjectId, name },
|
||||
externalCsvConfiguration:
|
||||
source.externalCsvConfiguration === null
|
||||
? null
|
||||
: {
|
||||
...source.externalCsvConfiguration,
|
||||
id: createId(),
|
||||
projectId: targetProjectId,
|
||||
},
|
||||
distributionBoards: source.distributionBoards.map((board) => ({
|
||||
...board,
|
||||
id: requiredId(boardIds, board.id),
|
||||
|
||||
Reference in New Issue
Block a user