120 lines
3.6 KiB
TypeScript
120 lines
3.6 KiB
TypeScript
import { UniqueID } from "@repo/rdx-ddd";
|
|
import { z } from "zod/v4";
|
|
|
|
import { WorkProfileValues } from "../../domain/value-objects/work-profile-values.vo";
|
|
import { WorkProfile } from "../../domain/work-profile.aggregate";
|
|
import { WorkProfileRevision } from "../../domain/work-profile-revision.entity";
|
|
import { WorkProfileInputSchema } from "../../domain/work-profile-values";
|
|
|
|
import { profileSnapshot } from "./configuration-snapshots";
|
|
import { snapshotValues } from "./scalar-snapshot-normalization";
|
|
|
|
export function profileIdentity(value: string) {
|
|
const id = UniqueID.create(value);
|
|
|
|
if (!id.isSuccess) {
|
|
throw new Error("Identidad de perfil persistida inconsistente.");
|
|
}
|
|
|
|
return id.data;
|
|
}
|
|
|
|
export function persistedJson(value: unknown): unknown {
|
|
return typeof value === "string" ? JSON.parse(value) : value;
|
|
}
|
|
|
|
export type ProfileRow = {
|
|
id: string;
|
|
companyId: string;
|
|
personId: string;
|
|
accountId: string | null;
|
|
assetId: string;
|
|
values?: unknown;
|
|
currentKey: string | null;
|
|
};
|
|
|
|
export type ProfileRevisionRow = {
|
|
id: string;
|
|
profileId: string;
|
|
accountId: string | null;
|
|
position: number;
|
|
actorId: string;
|
|
recordedAt: string;
|
|
values?: unknown;
|
|
};
|
|
|
|
function valuesOf(raw: unknown) {
|
|
const values = WorkProfileValues.create(WorkProfileInputSchema.parse(persistedJson(raw)));
|
|
|
|
if (!values.isSuccess) {
|
|
throw new Error("Valores de perfil persistidos inconsistentes.");
|
|
}
|
|
|
|
return values.data;
|
|
}
|
|
|
|
export class WorkProfilePersistenceMapper {
|
|
toDomain(row: ProfileRow, revisions: readonly ProfileRevisionRow[]) {
|
|
const values = valuesOf(snapshotValues(profileSnapshot, row));
|
|
const ordered = [...revisions].sort((a, b) => a.position - b.position);
|
|
const history = ordered.map((revision, position) => {
|
|
const revisionValues = valuesOf(snapshotValues(profileSnapshot, revision));
|
|
|
|
if (
|
|
revision.position !== position ||
|
|
!z.iso.datetime().safeParse(revision.recordedAt).success ||
|
|
revision.recordedAt < (ordered[position - 1]?.recordedAt ?? "") ||
|
|
!values.sameTarget(revisionValues) ||
|
|
revision.accountId !== revisionValues.toPrimitive().accountId
|
|
) {
|
|
throw new Error("Historial de perfil persistido inconsistente.");
|
|
}
|
|
|
|
return WorkProfileRevision.rehydrate(
|
|
{
|
|
values: revisionValues,
|
|
actorId: profileIdentity(revision.actorId),
|
|
recordedAt: revision.recordedAt,
|
|
},
|
|
profileIdentity(revision.id)
|
|
);
|
|
});
|
|
|
|
if (
|
|
!history.at(-1)?.values.equals(values) ||
|
|
row.personId !== values.personId.toPrimitive() ||
|
|
row.accountId !== values.toPrimitive().accountId ||
|
|
row.assetId !== values.assetId.toPrimitive() ||
|
|
row.currentKey !== (values.active ? [row.personId, row.assetId].join(":") : null)
|
|
) {
|
|
throw new Error("Referencias de perfil persistidas inconsistentes.");
|
|
}
|
|
|
|
return WorkProfile.rehydrate(
|
|
{ companyId: profileIdentity(row.companyId), values, history },
|
|
profileIdentity(row.id)
|
|
);
|
|
}
|
|
|
|
toPersistence(item: WorkProfile) {
|
|
const values = item.values.toPrimitive();
|
|
|
|
return {
|
|
root: {
|
|
id: item.id.toPrimitive(),
|
|
companyId: item.companyId.toPrimitive(),
|
|
...values,
|
|
currentKey: item.values.active ? [values.personId, values.assetId].join(":") : null,
|
|
},
|
|
revisions: item.history.map((revision, position) => ({
|
|
id: revision.id.toPrimitive(),
|
|
profileId: item.id.toPrimitive(),
|
|
position,
|
|
actorId: revision.actorId.toPrimitive(),
|
|
recordedAt: revision.recordedAt,
|
|
...revision.values.toPrimitive(),
|
|
})),
|
|
};
|
|
}
|
|
}
|