Feat workspace migration maintains flat object metadata maps (#13620)

# Introduction
The workspace migration runner v2 now computes the next
flatObjectMetadataMaps post current ws migration action has been run
So the following one have an up to date informations

## Still TODO
~~Remove any contextual information from workspace migration v2 and
consume the optimistic cache to retrieve them~~

## Next steps
- Create flat-object-metadata-maps testing toolbox and flat-object/field
testing toolbox
- Implement strong coverage on every created utils/transpiler and
applyWorkspaceMigrationAction method
This commit is contained in:
Paul Rastoin
2025-08-06 15:10:14 +02:00
committed by GitHub
parent e1d169e51e
commit ca3383cd86
38 changed files with 897 additions and 592 deletions
@@ -0,0 +1,15 @@
import {
appendCommonExceptionCode,
CustomException,
} from 'src/utils/custom-exception';
export class FlatObjectMetadataMapsException extends CustomException<
keyof typeof FlatObjectMetadataMapsExceptionCode
> {}
export const FlatObjectMetadataMapsExceptionCode = appendCommonExceptionCode({
FIELD_METADATA_NOT_FOUND: 'FIELD_METADATA_NOT_FOUND',
OBJECT_METADATA_NOT_FOUND: 'OBJECT_METADATA_NOT_FOUND',
FIELD_METADATA_ALREADY_EXISTS: 'FIELD_METADATA_ALREADY_EXISTS',
OBJECT_METADATA_ALREADY_EXISTS: 'OBJECT_METADATA_ALREADY_EXISTS',
} as const);
@@ -0,0 +1,39 @@
import { isDefined } from 'twenty-shared/utils';
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import {
FlatObjectMetadataMapsException,
FlatObjectMetadataMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-object-metadata-maps/flat-object-metadata-maps.exception';
import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { addFlatFieldMetadataToFlatObjectMetadataWithFlatFieldMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-to-flat-object-metadata-with-flat-field-maps-or-throw.util';
export const addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow = ({
flatFieldMetadata,
flatObjectMetadataMaps,
}: {
flatFieldMetadata: FlatFieldMetadata;
flatObjectMetadataMaps: FlatObjectMetadataMaps;
}): FlatObjectMetadataMaps | undefined => {
const flatObjectMetadataWithFlatFieldMaps =
flatObjectMetadataMaps.byId[flatFieldMetadata.objectMetadataId];
if (!isDefined(flatObjectMetadataWithFlatFieldMaps)) {
throw new FlatObjectMetadataMapsException(
'addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow field parent object metadata not found',
FlatObjectMetadataMapsExceptionCode.OBJECT_METADATA_NOT_FOUND,
);
}
return {
byId: {
...flatObjectMetadataMaps.byId,
[flatFieldMetadata.objectMetadataId]:
addFlatFieldMetadataToFlatObjectMetadataWithFlatFieldMapsOrThrow({
flatFieldMetadata,
flatObjectMetadataWithFlatFieldMaps,
}),
},
idByNameSingular: flatObjectMetadataMaps.idByNameSingular,
};
};
@@ -0,0 +1,70 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { isFlatFieldMetadataEntityOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
import {
FlatObjectMetadataMapsException,
FlatObjectMetadataMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-object-metadata-maps/flat-object-metadata-maps.exception';
import {
FlatFieldMetadataMaps,
FlatObjectMetadataWithFlatFieldMaps,
} from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-with-flat-field-metadata-maps.type';
export const addFlatFieldMetadataToFlatObjectMetadataWithFlatFieldMapsOrThrow =
({
flatFieldMetadata,
flatObjectMetadataWithFlatFieldMaps,
}: {
flatFieldMetadata: FlatFieldMetadata;
flatObjectMetadataWithFlatFieldMaps: FlatObjectMetadataWithFlatFieldMaps;
}): FlatObjectMetadataWithFlatFieldMaps => {
if (
isDefined(
flatObjectMetadataWithFlatFieldMaps.fieldsById[flatFieldMetadata.id],
)
) {
throw new FlatObjectMetadataMapsException(
'addFlatFieldMetadataToFlatObjectMetadataWithFlatFieldMaps added flatFieldMetadata already exists',
FlatObjectMetadataMapsExceptionCode.FIELD_METADATA_ALREADY_EXISTS,
);
}
let updatedFieldIdByJoinColumnName:
| FlatFieldMetadataMaps['fieldIdByJoinColumnName']
| undefined = undefined;
if (
isFlatFieldMetadataEntityOfType(
flatFieldMetadata,
FieldMetadataType.RELATION,
) ||
isFlatFieldMetadataEntityOfType(
flatFieldMetadata,
FieldMetadataType.MORPH_RELATION,
)
) {
if (isDefined(flatFieldMetadata.settings?.joinColumnName)) {
updatedFieldIdByJoinColumnName = {
[flatFieldMetadata.settings.joinColumnName]: flatFieldMetadata.id,
};
}
}
return {
...flatObjectMetadataWithFlatFieldMaps,
fieldIdByJoinColumnName: {
...flatObjectMetadataWithFlatFieldMaps.fieldIdByJoinColumnName,
...updatedFieldIdByJoinColumnName,
},
fieldIdByName: {
...flatObjectMetadataWithFlatFieldMaps.fieldIdByName,
[flatFieldMetadata.name]: flatFieldMetadata.id,
},
fieldsById: {
...flatObjectMetadataWithFlatFieldMaps.fieldsById,
[flatFieldMetadata.id]: flatFieldMetadata,
},
};
};
@@ -0,0 +1,39 @@
import { isDefined } from 'twenty-shared/utils';
import {
FlatObjectMetadataMapsException,
FlatObjectMetadataMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-object-metadata-maps/flat-object-metadata-maps.exception';
import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { fromFlatObjectMetadataToFlatObjectMetadataWithFlatFieldMaps } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-to-flat-object-metadata-with-flat-field-maps.util';
type AddFlatObjectMetadataToFlatObjectMetadataMapsOrThrowArgs = {
flatObjectMetadata: FlatObjectMetadata;
flatObjectMetadataMaps: FlatObjectMetadataMaps;
};
export const addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow = ({
flatObjectMetadata,
flatObjectMetadataMaps,
}: AddFlatObjectMetadataToFlatObjectMetadataMapsOrThrowArgs): FlatObjectMetadataMaps => {
if (isDefined(flatObjectMetadataMaps.byId[flatObjectMetadata.id])) {
throw new FlatObjectMetadataMapsException(
'addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow: flat object metadata to add already exists',
FlatObjectMetadataMapsExceptionCode.OBJECT_METADATA_ALREADY_EXISTS,
);
}
return {
byId: {
...flatObjectMetadataMaps.byId,
[flatObjectMetadata.id]:
fromFlatObjectMetadataToFlatObjectMetadataWithFlatFieldMaps(
flatObjectMetadata,
),
},
idByNameSingular: {
...flatObjectMetadataMaps.idByNameSingular,
[flatObjectMetadata.nameSingular]: flatObjectMetadata.id,
},
};
};
@@ -0,0 +1,41 @@
import { isDefined } from 'twenty-shared/utils';
import {
FlatObjectMetadataMapsException,
FlatObjectMetadataMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-object-metadata-maps/flat-object-metadata-maps.exception';
import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { deleteFieldFromFlatObjectMetadataWithFlatFieldMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-with-flat-field-maps-or-throw.util';
type DeleteFieldFromFlatObjectMetadataMapsArgs = {
fieldMetadataId: string;
objectMetadataId: string;
flatObjectMetadataMaps: FlatObjectMetadataMaps;
};
export const deleteFieldFromFlatObjectMetadataMapsOrThrow = ({
flatObjectMetadataMaps,
fieldMetadataId,
objectMetadataId,
}: DeleteFieldFromFlatObjectMetadataMapsArgs): FlatObjectMetadataMaps => {
const flatObjectMetadataWithFlatFieldMaps =
flatObjectMetadataMaps.byId[objectMetadataId];
if (!isDefined(flatObjectMetadataWithFlatFieldMaps)) {
throw new FlatObjectMetadataMapsException(
'deleteFieldFromFlatObjectMetadataMapsOrThrow: flat field metadata to delete parent flat object metadata not found',
FlatObjectMetadataMapsExceptionCode.OBJECT_METADATA_NOT_FOUND,
);
}
return {
byId: {
...flatObjectMetadataMaps.byId,
[objectMetadataId]:
deleteFieldFromFlatObjectMetadataWithFlatFieldMapsOrThrow({
fieldMetadataId,
flatObjectMetadataWithFlatFieldMaps,
}),
},
idByNameSingular: flatObjectMetadataMaps.idByNameSingular,
};
};
@@ -0,0 +1,50 @@
import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils';
import {
FlatObjectMetadataMapsException,
FlatObjectMetadataMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-object-metadata-maps/flat-object-metadata-maps.exception';
import { FlatObjectMetadataWithFlatFieldMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-with-flat-field-metadata-maps.type';
type DeleteFieldFromFlatObjectMetadataMapsArgs = {
fieldMetadataId: string;
flatObjectMetadataWithFlatFieldMaps: FlatObjectMetadataWithFlatFieldMaps;
};
export const deleteFieldFromFlatObjectMetadataWithFlatFieldMapsOrThrow = ({
flatObjectMetadataWithFlatFieldMaps,
fieldMetadataId: fieldMetadataIdToRemove,
}: DeleteFieldFromFlatObjectMetadataMapsArgs): FlatObjectMetadataWithFlatFieldMaps => {
const flatFieldMetadataToRemove =
flatObjectMetadataWithFlatFieldMaps.fieldsById[fieldMetadataIdToRemove];
if (!isDefined(flatFieldMetadataToRemove)) {
throw new FlatObjectMetadataMapsException(
'deleteFieldFromFlatObjectMetadataWithFlatFieldMapsOrThrow: field to delete not found',
FlatObjectMetadataMapsExceptionCode.FIELD_METADATA_NOT_FOUND,
);
}
const {
fieldIdByName,
fieldsById,
flatFieldMetadatas,
fieldIdByJoinColumnName,
} = flatObjectMetadataWithFlatFieldMaps;
const updatedFieldIdByJoinColumnName = Object.entries(
fieldIdByJoinColumnName,
).filter(([_joinColumnName, fieldId]) => fieldId !== fieldMetadataIdToRemove);
return {
...flatObjectMetadataWithFlatFieldMaps,
fieldIdByJoinColumnName: Object.fromEntries(updatedFieldIdByJoinColumnName),
fieldIdByName: removePropertiesFromRecord(fieldIdByName, [
flatFieldMetadataToRemove.name,
]),
fieldsById: removePropertiesFromRecord(fieldsById, [
fieldMetadataIdToRemove,
]),
flatFieldMetadatas: flatFieldMetadatas.filter(
(flatFieldMetadata) => flatFieldMetadata.id !== fieldMetadataIdToRemove,
),
};
};
@@ -0,0 +1,34 @@
import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils';
import {
FlatObjectMetadataMapsException,
FlatObjectMetadataMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-object-metadata-maps/flat-object-metadata-maps.exception';
import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
type DeleteObjectFromFlatObjectMetadataMapsOrThrowArgs = {
objectMetadataId: string;
flatObjectMetadataMaps: FlatObjectMetadataMaps;
};
export const deleteObjectFromFlatObjectMetadataMapsOrThrow = ({
flatObjectMetadataMaps,
objectMetadataId,
}: DeleteObjectFromFlatObjectMetadataMapsOrThrowArgs): FlatObjectMetadataMaps => {
if (!isDefined(flatObjectMetadataMaps.byId[objectMetadataId])) {
throw new FlatObjectMetadataMapsException(
'deleteObjectFromFlatObjectMetadataMapsOrThrow: object to delete not found',
FlatObjectMetadataMapsExceptionCode.OBJECT_METADATA_NOT_FOUND,
);
}
const updatedIdByNameSingularEntries = Object.entries(
flatObjectMetadataMaps.idByNameSingular,
).filter(([_nameSingular, id]) => id !== objectMetadataId);
return {
byId: removePropertiesFromRecord(flatObjectMetadataMaps.byId, [
objectMetadataId,
]),
idByNameSingular: Object.fromEntries(updatedIdByNameSingularEntries),
};
};
@@ -0,0 +1,30 @@
import { isDefined } from 'twenty-shared/utils';
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
export const findFlatFieldMetadataInFlatObjectMetadataMaps = ({
flatObjectMetadataMaps,
fieldMetadataId,
objectMetadataId,
}: {
objectMetadataId: string;
fieldMetadataId: string;
flatObjectMetadataMaps: FlatObjectMetadataMaps;
}): FlatFieldMetadata | undefined => {
const flatObjectMetadataWithFlatFieldMaps =
flatObjectMetadataMaps.byId[objectMetadataId];
if (!isDefined(flatObjectMetadataWithFlatFieldMaps)) {
return undefined;
}
const flatFieldMetadata =
flatObjectMetadataWithFlatFieldMaps.fieldsById[fieldMetadataId];
if (!isDefined(flatFieldMetadata)) {
return undefined;
}
return flatFieldMetadata;
};
@@ -0,0 +1,24 @@
import { isDefined } from 'twenty-shared/utils';
import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-with-flat-field-maps-to-flat-object-metadatas.util';
export const findFlatObjectdMetadataInFlatObjectMetadataMaps = ({
flatObjectMetadataMaps,
objectMetadataId,
}: {
objectMetadataId: string;
flatObjectMetadataMaps: FlatObjectMetadataMaps;
}): FlatObjectMetadata | undefined => {
const flatObjectMetadataWithFlatFieldMaps =
flatObjectMetadataMaps.byId[objectMetadataId];
if (!isDefined(flatObjectMetadataWithFlatFieldMaps)) {
return undefined;
}
return fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata(
flatObjectMetadataWithFlatFieldMaps,
);
};
@@ -0,0 +1,25 @@
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util';
import { deleteFieldFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-maps-or-throw.util';
export const replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow = ({
flatFieldMetadata,
flatObjectMetadataMaps,
}: {
flatFieldMetadata: FlatFieldMetadata;
flatObjectMetadataMaps: FlatObjectMetadataMaps;
}): FlatObjectMetadataMaps | undefined => {
const flatObjectMetadataMapsWithoutFlatFieldMetadataToReplace =
deleteFieldFromFlatObjectMetadataMapsOrThrow({
fieldMetadataId: flatFieldMetadata.id,
flatObjectMetadataMaps,
objectMetadataId: flatFieldMetadata.objectMetadataId,
});
return addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
flatFieldMetadata,
flatObjectMetadataMaps:
flatObjectMetadataMapsWithoutFlatFieldMetadataToReplace,
});
};
@@ -0,0 +1,24 @@
import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-to-flat-object-metadata-maps-or-throw.util';
import { deleteObjectFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-object-from-flat-object-metadata-maps-or-throw.util';
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
export const replaceFlatObjectMetadataInFlatObjectMetadataMaps = ({
flatObjectMetadata,
flatObjectMetadataMaps,
}: {
flatObjectMetadata: FlatObjectMetadata;
flatObjectMetadataMaps: FlatObjectMetadataMaps;
}) => {
const flatObjectMetadataMapsWithoutFlatObjectMetadataToReplace =
deleteObjectFromFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps,
objectMetadataId: flatObjectMetadata.id,
});
return addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow({
flatObjectMetadata,
flatObjectMetadataMaps:
flatObjectMetadataMapsWithoutFlatObjectMetadataToReplace,
});
};