metis-desk/modules/companies/src/api/infrastructure/persistence/application-configuration-persistence.mapper.ts
2026-10-10 13:32:40 +02:00

135 lines
4.2 KiB
TypeScript

import { z } from "zod/v4";
import { ApplicationConfiguration } from "../../domain/application-configuration.aggregate";
import { ApplicationConfigurationRevision } from "../../domain/application-configuration-revision.entity";
import {
APPLICATION_STATE_TRANSITIONS,
ApplicationConfigurationInputSchema,
} from "../../domain/application-configuration-values";
import { ApplicationConfigurationValues } from "../../domain/value-objects/application-configuration-values.vo";
import { applicationSnapshot } from "./configuration-snapshots";
import { snapshotValues } from "./scalar-snapshot-normalization";
import { persistedJson, profileIdentity } from "./work-profile-persistence.mapper";
export type ApplicationRow = {
id: string;
companyId: string;
profileId: string;
resourceId: string;
values?: unknown;
currentKey: string | null;
};
export type ApplicationRevisionRow = {
id: string;
configurationId: string;
position: number;
values?: unknown;
actorId: string;
recordedAt: string;
};
function mapped(raw: unknown) {
const parsed = ApplicationConfigurationValues.create(
ApplicationConfigurationInputSchema.parse(persistedJson(raw))
);
if (!parsed.isSuccess) {
throw new Error("Valores de aplicación persistidos inconsistentes.");
}
return parsed.data;
}
export class ApplicationConfigurationPersistenceMapper {
toDomain(row: ApplicationRow, revisions: readonly ApplicationRevisionRow[]) {
const values = mapped(snapshotValues(applicationSnapshot, row));
const ordered = [...revisions].sort((a, b) => a.position - b.position);
const history = ordered.map((revision, index) => {
const value = mapped(snapshotValues(applicationSnapshot, revision));
const previous = ordered[index - 1];
if (
revision.configurationId !== row.id ||
revision.position !== index ||
!z.iso.datetime().safeParse(revision.recordedAt).success ||
revision.recordedAt < (previous?.recordedAt ?? "") ||
!values.sameTarget(value)
) {
throw new Error("Historial de aplicación persistido inconsistente.");
}
return ApplicationConfigurationRevision.rehydrate(
{
values: value,
actorId: profileIdentity(revision.actorId),
recordedAt: revision.recordedAt,
},
profileIdentity(revision.id)
);
});
if (!(history.length && ["pending", "configured"].includes(history[0]?.values.state ?? ""))) {
throw new Error("Estado inicial de aplicación persistido inconsistente.");
}
for (const [index, revision] of history.entries()) {
const previous = history[index - 1]?.values;
if (
previous &&
(!APPLICATION_STATE_TRANSITIONS[previous.state].some(
(state) => state === revision.values.state
) ||
(previous.closed && !previous.equals(revision.values)))
) {
throw new Error("Estados de aplicación históricos inconsistentes.");
}
}
const keys = applicationKeys(values);
if (
!history.at(-1)?.values.equals(values) ||
row.profileId !== values.profileId.toPrimitive() ||
row.resourceId !== values.resourceId.toPrimitive() ||
row.currentKey !== keys.currentKey
) {
throw new Error("Referencias de aplicación persistidas inconsistentes.");
}
return ApplicationConfiguration.rehydrate(
{ companyId: profileIdentity(row.companyId), values, history },
profileIdentity(row.id)
);
}
toPersistence(item: ApplicationConfiguration) {
const values = item.values.toPrimitive();
return {
root: {
id: item.id.toPrimitive(),
companyId: item.companyId.toPrimitive(),
...values,
...applicationKeys(item.values),
},
revisions: item.history.map((revision, position) => ({
id: revision.id.toPrimitive(),
configurationId: item.id.toPrimitive(),
position,
actorId: revision.actorId.toPrimitive(),
recordedAt: revision.recordedAt,
...revision.values.toPrimitive(),
})),
};
}
}
function applicationKeys(values: ApplicationConfigurationValues) {
return {
currentKey: values.closed
? null
: `${values.profileId.toPrimitive()}:${values.resourceId.toPrimitive()}`,
};
}