From ca3383cd86c3a96f703bd0dfb55aaad5fdaa4ea2 Mon Sep 17 00:00:00 2001 From: Paul Rastoin <45004772+prastoin@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:10:14 +0200 Subject: [PATCH] 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 --- .../merge-two-flat-field-metadatas.util.ts | 2 +- .../types/flat-index-metadata.type.ts | 2 + .../flat-object-metadata-maps.exception.ts | 15 + ...flat-object-metadata-maps-or-throw.util.ts | 39 ++ ...data-with-flat-field-maps-or-throw.util.ts | 70 +++ ...flat-object-metadata-maps-or-throw.util.ts | 39 ++ ...flat-object-metadata-maps-or-throw.util.ts | 41 ++ ...data-with-flat-field-maps-or-throw.util.ts | 50 +++ ...flat-object-metadata-maps-or-throw.util.ts | 34 ++ ...adata-in-flat-object-metadata-maps.util.ts | 30 ++ ...adata-in-flat-object-metadata-maps.util.ts | 24 ++ ...flat-object-metadata-maps-or-throw.util.ts | 25 ++ ...flat-object-metadata-maps-or-throw.util.ts | 24 ++ ...tadata-from-flat-object-metadatas.util.ts} | 2 +- ...ject-metadata-with-flat-field-maps.util.ts | 24 ++ .../merge-two-flat-object-metadatas.util.ts | 5 +- .../workspace-migration-builder.spec.ts.snap | 404 +----------------- .../types/to-merge.type.ts | 4 +- .../workspace-migration-field-action-v2.ts | 10 +- .../workspace-migration-index-action-v2.ts | 2 +- .../workspace-migration-object-action-v2.ts | 4 +- ...et-workspace-migration-v2-field-actions.ts | 9 +- ...et-workspace-migration-v2-index-actions.ts | 2 +- ...t-workspace-migration-v2-object-actions.ts | 3 +- ...pace-migration-v2-field-actions-builder.ts | 8 +- ...ace-migration-v2-object-actions-builder.ts | 4 +- ...ion-action-on-flat-object-metadata-maps.ts | 147 +++++++ .../workspace-migration-runner-args.type.ts | 6 +- ...-to-partial-field-or-object-entity.util.ts | 15 + ...ce-metadata-field-action-runner.service.ts | 23 +- ...space-metadata-migration-runner-service.ts | 112 +++-- ...e-metadata-object-action-runner.service.ts | 44 +- .../workspace-migration-runner-v2.module.ts | 2 + .../workspace-migration-runner-v2.service.ts | 44 +- .../workspace-migration-runner.exception.ts | 13 + ...rkspace-schema-migration-runner.service.ts | 112 +++-- .../twenty-shared/src/types/FromToType.ts | 5 +- ...om-array-to-unique-key-record.util.test.ts | 90 ++++ 38 files changed, 897 insertions(+), 592 deletions(-) create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/flat-object-metadata-maps.exception.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-to-flat-object-metadata-with-flat-field-maps-or-throw.util.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-to-flat-object-metadata-maps-or-throw.util.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-maps-or-throw.util.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-with-flat-field-maps-or-throw.util.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-object-from-flat-object-metadata-maps-or-throw.util.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps.util.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps.util.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util.ts rename packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/{remove-flat-field-metadata-from-flat-object-metadatas.util.ts => delete-flat-field-metadata-from-flat-object-metadatas.util.ts} (93%) create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-to-flat-object-metadata-with-flat-field-maps.util.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/apply-workspace-migration-action-on-flat-object-metadata-maps.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner.exception.ts create mode 100644 packages/twenty-shared/src/utils/__tests__/from-array-to-unique-key-record.util.test.ts diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/merge-two-flat-field-metadatas.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/merge-two-flat-field-metadatas.util.ts index 51d8415765..0130380079 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/merge-two-flat-field-metadatas.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/merge-two-flat-field-metadatas.util.ts @@ -10,7 +10,7 @@ import { ToMerge } from 'src/engine/workspace-manager/workspace-migration-v2/wor export const mergeTwoFlatFieldMetadatas = ({ destFlatFieldMetadatas, toMergeFlatFieldMetadatas, -}: ToMerge): FlatFieldMetadata[] => { +}: ToMerge): FlatFieldMetadata[] => { const initialRecordAccumulator = fromArrayToUniqueKeyRecord({ array: destFlatFieldMetadatas, uniqueKey: 'uniqueIdentifier', diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type.ts index 7ca51797c3..1154141da0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type.ts @@ -8,9 +8,11 @@ type IndexMetadataRelationProperties = ExtractRecordTypeOrmRelationProperties< MetadataEntitiesRelationTarget >; +// TODO prastoin refactor FlatIndexMetadata to not be a Partial extension of IndexMetadataEntity export type FlatIndexMetadata = Partial< Omit > & { + id: string; flatIndexFieldMetadatas: FlatIndexFieldMetadata[]; uniqueIdentifier: string; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/flat-object-metadata-maps.exception.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/flat-object-metadata-maps.exception.ts new file mode 100644 index 0000000000..6808d287b0 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/flat-object-metadata-maps.exception.ts @@ -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); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util.ts new file mode 100644 index 0000000000..4359590a4b --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util.ts @@ -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, + }; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-to-flat-object-metadata-with-flat-field-maps-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-to-flat-object-metadata-with-flat-field-maps-or-throw.util.ts new file mode 100644 index 0000000000..e76aafa52c --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-to-flat-object-metadata-with-flat-field-maps-or-throw.util.ts @@ -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, + }, + }; + }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-to-flat-object-metadata-maps-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-to-flat-object-metadata-maps-or-throw.util.ts new file mode 100644 index 0000000000..01448216cb --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-to-flat-object-metadata-maps-or-throw.util.ts @@ -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, + }, + }; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-maps-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-maps-or-throw.util.ts new file mode 100644 index 0000000000..67666d7593 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-maps-or-throw.util.ts @@ -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, + }; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-with-flat-field-maps-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-with-flat-field-maps-or-throw.util.ts new file mode 100644 index 0000000000..5db86f4dd1 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-with-flat-field-maps-or-throw.util.ts @@ -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, + ), + }; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-object-from-flat-object-metadata-maps-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-object-from-flat-object-metadata-maps-or-throw.util.ts new file mode 100644 index 0000000000..153d522f32 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-object-from-flat-object-metadata-maps-or-throw.util.ts @@ -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), + }; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps.util.ts new file mode 100644 index 0000000000..abe3f13da4 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps.util.ts @@ -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; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps.util.ts new file mode 100644 index 0000000000..7ace3afd98 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps.util.ts @@ -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, + ); +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util.ts new file mode 100644 index 0000000000..e1c0523b0f --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util.ts @@ -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, + }); +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util.ts new file mode 100644 index 0000000000..1c5fdae91d --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util.ts @@ -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, + }); +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/remove-flat-field-metadata-from-flat-object-metadatas.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/delete-flat-field-metadata-from-flat-object-metadatas.util.ts similarity index 93% rename from packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/remove-flat-field-metadata-from-flat-object-metadatas.util.ts rename to packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/delete-flat-field-metadata-from-flat-object-metadatas.util.ts index 7a1c8ac1c6..77a0c67f04 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/remove-flat-field-metadata-from-flat-object-metadatas.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/delete-flat-field-metadata-from-flat-object-metadatas.util.ts @@ -1,7 +1,7 @@ import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; -export const removeFlatFieldMetadataFromFlatObjectMetadatas = ({ +export const deleteFlatFieldMetadataFromFlatObjectMetadatas = ({ flatFieldMetadata: flatFieldMetadataToRemove, flatObjectMetadatas, }: { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-to-flat-object-metadata-with-flat-field-maps.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-to-flat-object-metadata-with-flat-field-maps.util.ts new file mode 100644 index 0000000000..d7d720c57e --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-to-flat-object-metadata-with-flat-field-maps.util.ts @@ -0,0 +1,24 @@ +import { FlatObjectMetadataWithFlatFieldMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-with-flat-field-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'; +import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; + +export const fromFlatObjectMetadataToFlatObjectMetadataWithFlatFieldMaps = ( + flatObjectMetadata: FlatObjectMetadata, +): FlatObjectMetadataWithFlatFieldMaps => { + const emptyFlatObjectMetadataWithFlatFieldMaps: FlatObjectMetadataWithFlatFieldMaps = + { + ...flatObjectMetadata, + fieldIdByJoinColumnName: {}, + fieldIdByName: {}, + fieldsById: {}, + }; + + return flatObjectMetadata.flatFieldMetadatas.reduce( + (flatObjectMetadataWithFlatFieldMaps, flatFieldMetadata) => + addFlatFieldMetadataToFlatObjectMetadataWithFlatFieldMapsOrThrow({ + flatFieldMetadata, + flatObjectMetadataWithFlatFieldMaps, + }), + emptyFlatObjectMetadataWithFlatFieldMaps, + ); +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/merge-two-flat-object-metadatas.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/merge-two-flat-object-metadatas.util.ts index 64ec8b38c1..0104c2617d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/merge-two-flat-object-metadatas.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/merge-two-flat-object-metadatas.util.ts @@ -11,7 +11,10 @@ import { ToMerge } from 'src/engine/workspace-manager/workspace-migration-v2/wor export const mergeTwoFlatObjectMetadatas = ({ destFlatObjectMetadatas, toMergeFlatObjectMetadatas, -}: ToMerge): FlatObjectMetadata[] => { +}: ToMerge< + FlatObjectMetadata[], + 'FlatObjectMetadatas' +>): FlatObjectMetadata[] => { const initialObjectAccumulator = fromArrayToUniqueKeyRecord({ array: destFlatObjectMetadatas, uniqueKey: 'uniqueIdentifier', diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/__snapshots__/workspace-migration-builder.spec.ts.snap b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/__snapshots__/workspace-migration-builder.spec.ts.snap index c302e40863..012961b22e 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/__snapshots__/workspace-migration-builder.spec.ts.snap +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/__snapshots__/workspace-migration-builder.spec.ts.snap @@ -5,56 +5,8 @@ exports[`Workspace migration builder field actions test suite It should NOT buil exports[`Workspace migration builder field actions test suite It should build a delete_field action 1`] = ` [ { - "flatFieldMetadata": { - "defaultValue": null, - "description": "default flat field metadata description", - "flatRelationTargetFieldMetadata": null, - "flatRelationTargetObjectMetadata": null, - "icon": "icon", - "id": Any, - "isActive": true, - "isCustom": true, - "isLabelSyncedWithName": false, - "isNullable": true, - "isSystem": false, - "isUnique": false, - "label": "flat field metadata label", - "name": "flatFieldMetadataName", - "objectMetadataId": Any, - "options": null, - "relationTargetFieldMetadataId": null, - "relationTargetObjectMetadataId": null, - "settings": null, - "standardId": null, - "standardOverrides": null, - "type": "TEXT", - "uniqueIdentifier": "field-metadata-unique-identifier-1", - "workspaceId": Any, - }, - "flatObjectMetadataWithoutFields": { - "description": "default flat object metadata description", - "icon": "icon", - "id": Any, - "imageIdentifierFieldMetadataId": Any, - "isActive": true, - "isAuditLogged": true, - "isCustom": true, - "isLabelSyncedWithName": false, - "isRemote": false, - "isSearchable": true, - "isSystem": false, - "labelIdentifierFieldMetadataId": Any, - "labelPlural": "default flat object metadata label plural", - "labelSingular": "default flat object metadata label singular", - "namePlural": "defaultflatObjectMetadataNamePlural", - "nameSingular": "defaultflatObjectMetadataNameSingular", - "shortcut": "shortcut", - "standardId": null, - "standardOverrides": null, - "targetTableName": "", - "uniqueIdentifier": "object-metadata-unique-identifier-1", - "workspaceId": Any, - }, + "fieldMetadataId": Any, + "objectMetadataId": Any, "type": "delete_field", }, ] @@ -89,30 +41,6 @@ exports[`Workspace migration builder field actions test suite It should build an "uniqueIdentifier": "field-metadata-unique-identifier-1", "workspaceId": Any, }, - "flatObjectMetadataWithoutFields": { - "description": "default flat object metadata description", - "icon": "icon", - "id": Any, - "imageIdentifierFieldMetadataId": Any, - "isActive": true, - "isAuditLogged": true, - "isCustom": true, - "isLabelSyncedWithName": true, - "isRemote": false, - "isSearchable": true, - "isSystem": false, - "labelIdentifierFieldMetadataId": Any, - "labelPlural": "default flat object metadata label plural", - "labelSingular": "default flat object metadata label singular", - "namePlural": "defaultflatObjectMetadataNamePlural", - "nameSingular": "defaultflatObjectMetadataNameSingular", - "shortcut": "shortcut", - "standardId": null, - "standardOverrides": null, - "targetTableName": "", - "uniqueIdentifier": "object-metadata-unique-identifier-1", - "workspaceId": Any, - }, "type": "create_field", }, ] @@ -197,30 +125,6 @@ exports[`Workspace migration builder field actions test suite It should build an "uniqueIdentifier": "field-metadata-unique-identifier-1", "workspaceId": Any, }, - "flatObjectMetadataWithoutFields": { - "description": "default flat object metadata description", - "icon": "icon", - "id": Any, - "imageIdentifierFieldMetadataId": Any, - "isActive": true, - "isAuditLogged": true, - "isCustom": true, - "isLabelSyncedWithName": true, - "isRemote": false, - "isSearchable": true, - "isSystem": false, - "labelIdentifierFieldMetadataId": Any, - "labelPlural": "default flat object metadata label plural", - "labelSingular": "default flat object metadata label singular", - "namePlural": "defaultflatObjectMetadataNamePlural", - "nameSingular": "defaultflatObjectMetadataNameSingular", - "shortcut": "shortcut", - "standardId": null, - "standardOverrides": null, - "targetTableName": "", - "uniqueIdentifier": "object-metadata-unique-identifier-1", - "workspaceId": Any, - }, "type": "create_field", }, ] @@ -229,56 +133,8 @@ exports[`Workspace migration builder field actions test suite It should build an exports[`Workspace migration builder field actions test suite It should build an update_field action 1`] = ` [ { - "flatFieldMetadata": { - "defaultValue": null, - "description": "new description", - "flatRelationTargetFieldMetadata": null, - "flatRelationTargetObjectMetadata": null, - "icon": "new icon", - "id": Any, - "isActive": false, - "isCustom": true, - "isLabelSyncedWithName": false, - "isNullable": true, - "isSystem": false, - "isUnique": false, - "label": "flat field metadata label", - "name": "new name", - "objectMetadataId": Any, - "options": null, - "relationTargetFieldMetadataId": null, - "relationTargetObjectMetadataId": null, - "settings": null, - "standardId": null, - "standardOverrides": null, - "type": "TEXT", - "uniqueIdentifier": "field-metadata-unique-identifier-1", - "workspaceId": Any, - }, - "flatObjectMetadataWithoutFields": { - "description": "default flat object metadata description", - "icon": "icon", - "id": Any, - "imageIdentifierFieldMetadataId": Any, - "isActive": true, - "isAuditLogged": true, - "isCustom": true, - "isLabelSyncedWithName": false, - "isRemote": false, - "isSearchable": true, - "isSystem": false, - "labelIdentifierFieldMetadataId": Any, - "labelPlural": "default flat object metadata label plural", - "labelSingular": "default flat object metadata label singular", - "namePlural": "defaultflatObjectMetadataNamePlural", - "nameSingular": "defaultflatObjectMetadataNameSingular", - "shortcut": "shortcut", - "standardId": null, - "standardOverrides": null, - "targetTableName": "", - "uniqueIdentifier": "object-metadata-unique-identifier-1", - "workspaceId": Any, - }, + "fieldMetadataId": Any, + "objectMetadataId": Any, "type": "update_field", "updates": [ { @@ -310,56 +166,8 @@ exports[`Workspace migration builder field actions test suite It should build an exports[`Workspace migration builder field actions test suite It should build an update_field action for a RELATION field 1`] = ` [ { - "flatFieldMetadata": { - "defaultValue": null, - "description": "new description", - "flatRelationTargetFieldMetadata": null, - "flatRelationTargetObjectMetadata": null, - "icon": "icon", - "id": Any, - "isActive": false, - "isCustom": true, - "isLabelSyncedWithName": false, - "isNullable": true, - "isSystem": false, - "isUnique": false, - "label": "new label", - "name": "flatFieldMetadataName", - "objectMetadataId": Any, - "options": null, - "relationTargetFieldMetadataId": Any, - "relationTargetObjectMetadataId": Any, - "settings": null, - "standardId": null, - "standardOverrides": null, - "type": "RELATION", - "uniqueIdentifier": "field-metadata-unique-identifier-1", - "workspaceId": Any, - }, - "flatObjectMetadataWithoutFields": { - "description": "default flat object metadata description", - "icon": "icon", - "id": Any, - "imageIdentifierFieldMetadataId": Any, - "isActive": true, - "isAuditLogged": true, - "isCustom": true, - "isLabelSyncedWithName": true, - "isRemote": false, - "isSearchable": true, - "isSystem": false, - "labelIdentifierFieldMetadataId": Any, - "labelPlural": "default flat object metadata label plural", - "labelSingular": "default flat object metadata label singular", - "namePlural": "defaultflatObjectMetadataNamePlural", - "nameSingular": "defaultflatObjectMetadataNameSingular", - "shortcut": "shortcut", - "standardId": null, - "standardOverrides": null, - "targetTableName": "", - "uniqueIdentifier": "object-metadata-unique-identifier-1", - "workspaceId": Any, - }, + "fieldMetadataId": Any, + "objectMetadataId": Any, "type": "update_field", "updates": [ { @@ -388,20 +196,7 @@ exports[`Workspace migration builder field actions test suite It should not infe exports[`Workspace migration builder index actions test suite It should build a delete_index action 1`] = ` [ { - "flatIndexMetadata": { - "createdAt": Any, - "flatIndexFieldMetadatas": [], - "id": Any, - "indexType": "BTREE", - "indexWhereClause": undefined, - "isCustom": false, - "isUnique": false, - "name": "defaultFlatIndexMetadataName", - "objectMetadataId": Any, - "uniqueIdentifier": "field-metadata-unique-identifier-1", - "updatedAt": Any, - "workspaceId": Any, - }, + "flatIndexMetadataId": Any, "type": "delete_index", }, ] @@ -432,20 +227,7 @@ exports[`Workspace migration builder index actions test suite It should build an exports[`Workspace migration builder index actions test suite It should build an delete_index and a create_index action ( the way we handle update ) 1`] = ` [ { - "flatIndexMetadata": { - "createdAt": Any, - "flatIndexFieldMetadatas": [], - "id": Any, - "indexType": "BTREE", - "indexWhereClause": undefined, - "isCustom": false, - "isUnique": false, - "name": "defaultFlatIndexMetadataName", - "objectMetadataId": Any, - "uniqueIdentifier": "field-metadata-unique-identifier-1", - "updatedAt": Any, - "workspaceId": Any, - }, + "flatIndexMetadataId": Any, "type": "delete_index", }, { @@ -534,30 +316,6 @@ exports[`Workspace migration builder object actions test suite It should build a "uniqueIdentifier": "field_0", "workspaceId": Any, }, - "flatObjectMetadataWithoutFields": { - "description": "default flat object metadata description", - "icon": "icon", - "id": Any, - "imageIdentifierFieldMetadataId": Any, - "isActive": true, - "isAuditLogged": true, - "isCustom": true, - "isLabelSyncedWithName": true, - "isRemote": false, - "isSearchable": true, - "isSystem": false, - "labelIdentifierFieldMetadataId": Any, - "labelPlural": "default flat object metadata label plural", - "labelSingular": "default flat object metadata label singular", - "namePlural": "totos", - "nameSingular": "toto", - "shortcut": "shortcut", - "standardId": null, - "standardOverrides": null, - "targetTableName": "", - "uniqueIdentifier": "pomme", - "workspaceId": Any, - }, "type": "create_field", }, { @@ -587,30 +345,6 @@ exports[`Workspace migration builder object actions test suite It should build a "uniqueIdentifier": "field_1", "workspaceId": Any, }, - "flatObjectMetadataWithoutFields": { - "description": "default flat object metadata description", - "icon": "icon", - "id": Any, - "imageIdentifierFieldMetadataId": Any, - "isActive": true, - "isAuditLogged": true, - "isCustom": true, - "isLabelSyncedWithName": true, - "isRemote": false, - "isSearchable": true, - "isSystem": false, - "labelIdentifierFieldMetadataId": Any, - "labelPlural": "default flat object metadata label plural", - "labelSingular": "default flat object metadata label singular", - "namePlural": "totos", - "nameSingular": "toto", - "shortcut": "shortcut", - "standardId": null, - "standardOverrides": null, - "targetTableName": "", - "uniqueIdentifier": "pomme", - "workspaceId": Any, - }, "type": "create_field", }, { @@ -640,30 +374,6 @@ exports[`Workspace migration builder object actions test suite It should build a "uniqueIdentifier": "field_2", "workspaceId": Any, }, - "flatObjectMetadataWithoutFields": { - "description": "default flat object metadata description", - "icon": "icon", - "id": Any, - "imageIdentifierFieldMetadataId": Any, - "isActive": true, - "isAuditLogged": true, - "isCustom": true, - "isLabelSyncedWithName": true, - "isRemote": false, - "isSearchable": true, - "isSystem": false, - "labelIdentifierFieldMetadataId": Any, - "labelPlural": "default flat object metadata label plural", - "labelSingular": "default flat object metadata label singular", - "namePlural": "totos", - "nameSingular": "toto", - "shortcut": "shortcut", - "standardId": null, - "standardOverrides": null, - "targetTableName": "", - "uniqueIdentifier": "pomme", - "workspaceId": Any, - }, "type": "create_field", }, { @@ -693,30 +403,6 @@ exports[`Workspace migration builder object actions test suite It should build a "uniqueIdentifier": "field_3", "workspaceId": Any, }, - "flatObjectMetadataWithoutFields": { - "description": "default flat object metadata description", - "icon": "icon", - "id": Any, - "imageIdentifierFieldMetadataId": Any, - "isActive": true, - "isAuditLogged": true, - "isCustom": true, - "isLabelSyncedWithName": true, - "isRemote": false, - "isSearchable": true, - "isSystem": false, - "labelIdentifierFieldMetadataId": Any, - "labelPlural": "default flat object metadata label plural", - "labelSingular": "default flat object metadata label singular", - "namePlural": "totos", - "nameSingular": "toto", - "shortcut": "shortcut", - "standardId": null, - "standardOverrides": null, - "targetTableName": "", - "uniqueIdentifier": "pomme", - "workspaceId": Any, - }, "type": "create_field", }, { @@ -746,30 +432,6 @@ exports[`Workspace migration builder object actions test suite It should build a "uniqueIdentifier": "field_4", "workspaceId": Any, }, - "flatObjectMetadataWithoutFields": { - "description": "default flat object metadata description", - "icon": "icon", - "id": Any, - "imageIdentifierFieldMetadataId": Any, - "isActive": true, - "isAuditLogged": true, - "isCustom": true, - "isLabelSyncedWithName": true, - "isRemote": false, - "isSearchable": true, - "isSystem": false, - "labelIdentifierFieldMetadataId": Any, - "labelPlural": "default flat object metadata label plural", - "labelSingular": "default flat object metadata label singular", - "namePlural": "totos", - "nameSingular": "toto", - "shortcut": "shortcut", - "standardId": null, - "standardOverrides": null, - "targetTableName": "", - "uniqueIdentifier": "pomme", - "workspaceId": Any, - }, "type": "create_field", }, ], @@ -822,30 +484,7 @@ exports[`Workspace migration builder object actions test suite It should build a exports[`Workspace migration builder object actions test suite It should build a delete_object action 1`] = ` [ { - "flatObjectMetadataWithoutFields": { - "description": "default flat object metadata description", - "icon": "icon", - "id": Any, - "imageIdentifierFieldMetadataId": Any, - "isActive": true, - "isAuditLogged": true, - "isCustom": true, - "isLabelSyncedWithName": true, - "isRemote": false, - "isSearchable": true, - "isSystem": false, - "labelIdentifierFieldMetadataId": Any, - "labelPlural": "default flat object metadata label plural", - "labelSingular": "default flat object metadata label singular", - "namePlural": "totos", - "nameSingular": "toto", - "shortcut": "shortcut", - "standardId": null, - "standardOverrides": null, - "targetTableName": "", - "uniqueIdentifier": "pomme", - "workspaceId": Any, - }, + "objectMetadataId": Any, "type": "delete_object", }, ] @@ -854,30 +493,7 @@ exports[`Workspace migration builder object actions test suite It should build a exports[`Workspace migration builder object actions test suite It should build an update_object action with all object updated fields 1`] = ` [ { - "flatObjectMetadataWithoutFields": { - "description": "default flat object metadata description", - "icon": "icon", - "id": Any, - "imageIdentifierFieldMetadataId": Any, - "isActive": true, - "isAuditLogged": true, - "isCustom": true, - "isLabelSyncedWithName": false, - "isRemote": false, - "isSearchable": true, - "isSystem": false, - "labelIdentifierFieldMetadataId": Any, - "labelPlural": "default flat object metadata label plural", - "labelSingular": "default flat object metadata label singular", - "namePlural": "prastoins", - "nameSingular": "prastouin", - "shortcut": "shortcut", - "standardId": null, - "standardOverrides": null, - "targetTableName": "", - "uniqueIdentifier": "pomme", - "workspaceId": Any, - }, + "objectMetadataId": Any, "type": "update_object", "updates": [ { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/to-merge.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/to-merge.type.ts index 97669040a0..877a647675 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/to-merge.type.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/to-merge.type.ts @@ -1,3 +1,3 @@ -export type ToMerge = { - [P in ['toMerge', 'dest'][number] as `${P}Flat${Capitalize}Metadatas`]: T; +export type ToMerge = { + [P in 'toMerge' | 'dest' as `${P}${Capitalize}`]: T; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2.ts index d7beb70e1f..08bd52cd7b 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2.ts @@ -3,19 +3,17 @@ import { FromTo } from 'twenty-shared/types'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { FlatFieldMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata.util'; -import { FlatObjectMetadataWithoutFields } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; export type CreateFieldAction = { type: 'create_field'; flatFieldMetadata: FlatFieldMetadata; - flatObjectMetadataWithoutFields: FlatObjectMetadataWithoutFields; }; export type UpdateFieldAction = { type: 'update_field'; workspaceId: string; - flatFieldMetadata: FlatFieldMetadata; - flatObjectMetadataWithoutFields: FlatObjectMetadataWithoutFields; + fieldMetadataId: string; + objectMetadataId: string; updates: Array< { [P in FlatFieldMetadataPropertiesToCompare]: { @@ -27,8 +25,8 @@ export type UpdateFieldAction = { export type DeleteFieldAction = { type: 'delete_field'; - flatFieldMetadata: FlatFieldMetadata; - flatObjectMetadataWithoutFields: FlatObjectMetadataWithoutFields; + fieldMetadataId: string; + objectMetadataId: string; }; export type WorkspaceMigrationFieldActionV2 = diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2.ts index 5162ad460a..07aee3452d 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2.ts @@ -7,7 +7,7 @@ export type CreateIndexAction = { export type DeleteIndexAction = { type: 'delete_index'; - flatIndexMetadata: FlatIndexMetadata; + flatIndexMetadataId: string; }; export type WorkspaceMigrationIndexActionV2 = diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2.ts index b7169863a9..9ec7eb8dc7 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2.ts @@ -13,7 +13,7 @@ export type CreateObjectAction = { export type UpdateObjectAction = { type: 'update_object'; - flatObjectMetadataWithoutFields: FlatObjectMetadataWithoutFields; + objectMetadataId: string; updates: Array< { [P in FlatObjectMetadataPropertiesToCompare]: { @@ -25,7 +25,7 @@ export type UpdateObjectAction = { export type DeleteObjectAction = { type: 'delete_object'; - flatObjectMetadataWithoutFields: FlatObjectMetadataWithoutFields; + objectMetadataId: string; }; export type WorkspaceMigrationObjectActionV2 = diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-field-actions.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-field-actions.ts index 9e08947b27..df89cee433 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-field-actions.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-field-actions.ts @@ -4,7 +4,6 @@ import { CreateFieldAction, DeleteFieldAction, } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2'; -import { fromFlatObjectMetadataToFlatObjectMetadataWithoutFields } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-flat-object-metadata-to-flat-object-metadata-without-fields.util'; type FlatFieldMetadataAndFlatObjectMetadata = { flatFieldMetadata: FlatFieldMetadata; @@ -13,12 +12,9 @@ type FlatFieldMetadataAndFlatObjectMetadata = { export const getWorkspaceMigrationV2FieldCreateAction = ({ flatFieldMetadata, - flatObjectMetadata, }: FlatFieldMetadataAndFlatObjectMetadata): CreateFieldAction => ({ type: 'create_field', flatFieldMetadata, - flatObjectMetadataWithoutFields: - fromFlatObjectMetadataToFlatObjectMetadataWithoutFields(flatObjectMetadata), }); export const getWorkspaceMigrationV2FieldDeleteAction = ({ @@ -26,7 +22,6 @@ export const getWorkspaceMigrationV2FieldDeleteAction = ({ flatObjectMetadata, }: FlatFieldMetadataAndFlatObjectMetadata): DeleteFieldAction => ({ type: 'delete_field', - flatFieldMetadata, - flatObjectMetadataWithoutFields: - fromFlatObjectMetadataToFlatObjectMetadataWithoutFields(flatObjectMetadata), + fieldMetadataId: flatFieldMetadata.id, + objectMetadataId: flatObjectMetadata.id, }); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-index-actions.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-index-actions.ts index bf44b333be..efe61f4574 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-index-actions.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-index-actions.ts @@ -15,5 +15,5 @@ export const getWorkspaceMigrationV2DeleteIndexAction = ( flatIndexMetadata: FlatIndexMetadata, ): DeleteIndexAction => ({ type: 'delete_index', - flatIndexMetadata, + flatIndexMetadataId: flatIndexMetadata.id, }); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-object-actions.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-object-actions.ts index f6c64aa4a3..82d8611590 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-object-actions.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-object-actions.ts @@ -23,6 +23,5 @@ export const getWorkspaceMigrationV2ObjectDeleteAction = ( flatObjectMetadata: FlatObjectMetadata, ): DeleteObjectAction => ({ type: 'delete_object', - flatObjectMetadataWithoutFields: - fromFlatObjectMetadataToFlatObjectMetadataWithoutFields(flatObjectMetadata), + objectMetadataId: flatObjectMetadata.id, }); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-field-actions-builder.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-field-actions-builder.ts index 79750271d5..8cc05a2d95 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-field-actions-builder.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-field-actions-builder.ts @@ -4,7 +4,6 @@ import { WorkspaceMigrationFieldActionV2, } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2'; import { UpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-field-matrix.util'; -import { fromFlatObjectMetadataToFlatObjectMetadataWithoutFields } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-flat-object-metadata-to-flat-object-metadata-without-fields.util'; import { getWorkspaceMigrationV2FieldCreateAction, getWorkspaceMigrationV2FieldDeleteAction, @@ -40,11 +39,8 @@ export const buildWorkspaceMigrationV2FieldActions = ({ return { type: 'update_field', - flatFieldMetadata: to, - flatObjectMetadataWithoutFields: - fromFlatObjectMetadataToFlatObjectMetadataWithoutFields( - flatObjectMetadata, - ), + fieldMetadataId: to.id, + objectMetadataId: to.objectMetadataId, workspaceId: to.workspaceId, updates, }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder.ts index d1ca12f187..1ff563d165 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder.ts @@ -5,7 +5,6 @@ import { UpdateObjectAction, WorkspaceMigrationObjectActionV2, } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2'; -import { fromFlatObjectMetadataToFlatObjectMetadataWithoutFields } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-flat-object-metadata-to-flat-object-metadata-without-fields.util'; import { getWorkspaceMigrationV2FieldCreateAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-field-actions'; import { getWorkspaceMigrationV2ObjectCreateAction, @@ -56,8 +55,7 @@ export const buildWorkspaceMigrationV2ObjectActions = ({ return { type: 'update_object', - flatObjectMetadataWithoutFields: - fromFlatObjectMetadataToFlatObjectMetadataWithoutFields(to), + objectMetadataId: to.id, updates: objectUpdatedProperties, }; }); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/apply-workspace-migration-action-on-flat-object-metadata-maps.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/apply-workspace-migration-action-on-flat-object-metadata-maps.ts new file mode 100644 index 0000000000..488c709a95 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/apply-workspace-migration-action-on-flat-object-metadata-maps.ts @@ -0,0 +1,147 @@ +import { assertUnreachable, isDefined } from 'twenty-shared/utils'; + +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 { addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-to-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'; +import { deleteObjectFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-object-from-flat-object-metadata-maps-or-throw.util'; +import { findFlatFieldMetadataInFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps.util'; +import { findFlatObjectdMetadataInFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps.util'; +import { replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util'; +import { replaceFlatObjectMetadataInFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util'; +import { WorkspaceMigrationRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-runner-args.type'; +import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util'; +import { + WorkspaceMigrationRunnerException, + WorkspaceMigrationRunnerExceptionCode, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner.exception'; + +export const applyWorkspaceMigrationActionOnFlatObjectMetadataMaps = ({ + action, + flatObjectMetadataMaps, +}: Omit< + WorkspaceMigrationRunnerArgs, + 'queryRunner' +>): FlatObjectMetadataMaps => { + switch (action.type) { + case 'delete_object': { + return deleteObjectFromFlatObjectMetadataMapsOrThrow({ + flatObjectMetadataMaps, + objectMetadataId: action.objectMetadataId, + }); + } + case 'create_object': { + const flatObjectMetadataWithoutFields = + action.flatObjectMetadataWithoutFields; + const flatFieldMetadatas = action.createFieldActions.map( + (createFieldAction) => createFieldAction.flatFieldMetadata, + ); + + return addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow({ + flatObjectMetadata: { + ...flatObjectMetadataWithoutFields, + flatIndexMetadatas: [], + flatFieldMetadatas, + }, + flatObjectMetadataMaps, + }); + } + case 'update_object': { + const { objectMetadataId } = action; + const existingFlatObjectMetadata = + findFlatObjectdMetadataInFlatObjectMetadataMaps({ + objectMetadataId, + flatObjectMetadataMaps, + }); + + if (!isDefined(existingFlatObjectMetadata)) { + throw new WorkspaceMigrationRunnerException( + `Workspace migration failed: Object metadata not found in cache`, + WorkspaceMigrationRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND, + ); + } + const updatedFlatObjectMetadata = { + ...existingFlatObjectMetadata, + ...fromWorkspaceMigrationUpdateActionToPartialEntity(action), + }; + + return replaceFlatObjectMetadataInFlatObjectMetadataMaps({ + flatObjectMetadata: updatedFlatObjectMetadata, + flatObjectMetadataMaps, + }); + } + case 'create_field': { + const updatedFlatObjectMetadataMaps = + addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({ + flatFieldMetadata: action.flatFieldMetadata, + flatObjectMetadataMaps, + }); + + if (!isDefined(updatedFlatObjectMetadataMaps)) { + throw new WorkspaceMigrationRunnerException( + `Workspace migration failed: dispatchAndAddFlatFieldMetadataInFlatObjectMetadataMaps failed`, + WorkspaceMigrationRunnerExceptionCode.FIELD_METADATA_NOT_FOUND, + ); + } + + return updatedFlatObjectMetadataMaps; + } + case 'update_field': { + const { fieldMetadataId, objectMetadataId } = action; + const existingFlatFieldMetadata = + findFlatFieldMetadataInFlatObjectMetadataMaps({ + fieldMetadataId, + objectMetadataId, + flatObjectMetadataMaps, + }); + + if (!isDefined(existingFlatFieldMetadata)) { + throw new WorkspaceMigrationRunnerException( + `Workspace migration failed: Field metadata not found in cache`, + WorkspaceMigrationRunnerExceptionCode.FIELD_METADATA_NOT_FOUND, + ); + } + + const updatedFlatFieldMetadata = { + ...existingFlatFieldMetadata, + ...fromWorkspaceMigrationUpdateActionToPartialEntity(action), + }; + + const updatedFlatObjectMetadataMaps = + replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({ + flatFieldMetadata: updatedFlatFieldMetadata, + flatObjectMetadataMaps, + }); + + if (!isDefined(updatedFlatObjectMetadataMaps)) { + throw new WorkspaceMigrationRunnerException( + `Workspace migration failed: dispatchAndReplaceFlatFieldMetadataInFlatObjectMetadataMaps failed`, + WorkspaceMigrationRunnerExceptionCode.FIELD_METADATA_NOT_FOUND, + ); + } + + return updatedFlatObjectMetadataMaps; + } + case 'delete_field': { + return deleteFieldFromFlatObjectMetadataMapsOrThrow({ + fieldMetadataId: action.fieldMetadataId, + flatObjectMetadataMaps, + objectMetadataId: action.objectMetadataId, + }); + } + case 'create_index': { + // TODO prastoin handle indexes + return flatObjectMetadataMaps; + } + case 'delete_index': { + // TODO prastoin handle indexes + return flatObjectMetadataMaps; + } + default: { + assertUnreachable( + action, + 'Should never occur, encountered an unsupported workspace migration action type', + ); + } + } +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-runner-args.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-runner-args.type.ts index 8b9ec58b16..c7e2d1652d 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-runner-args.type.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-runner-args.type.ts @@ -1,8 +1,10 @@ import { QueryRunner } from 'typeorm'; -import { WorkspaceMigrationV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-v2'; +import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; +import { WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2'; export type WorkspaceMigrationRunnerArgs = { - workspaceMigration: WorkspaceMigrationV2; + action: WorkspaceMigrationActionV2; queryRunner: QueryRunner; + flatObjectMetadataMaps: FlatObjectMetadataMaps; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util.ts new file mode 100644 index 0000000000..cf29e50b65 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util.ts @@ -0,0 +1,15 @@ +import { UpdateFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2'; +import { UpdateObjectAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2'; + +export const fromWorkspaceMigrationUpdateActionToPartialEntity = < + T extends UpdateFieldAction | UpdateObjectAction, +>( + action: T, +) => { + return action.updates.reduce((acc, { to, property }) => { + return { + ...acc, + [property]: to, + }; + }, {}); +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-field-action-runner.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-field-action-runner.service.ts index 2c4d922639..e4e2425393 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-field-action-runner.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-field-action-runner.service.ts @@ -1,8 +1,6 @@ import { In } from 'typeorm'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; -import { FlatFieldMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata.util'; import { CreateFieldAction, DeleteFieldAction, @@ -11,6 +9,7 @@ import { } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2'; import { RunnerMethodForActionType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/runner-method-for-action-type'; import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; +import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util'; export class WorkspaceMetadataFieldActionRunnerService implements @@ -25,10 +24,10 @@ export class WorkspaceMetadataFieldActionRunnerService FieldMetadataEntity, ); - const { flatFieldMetadata } = action; + const { fieldMetadataId } = action; await fieldMetadataRepository.delete({ - id: In([flatFieldMetadata.id]), + id: In([fieldMetadataId]), }); }; @@ -43,7 +42,6 @@ export class WorkspaceMetadataFieldActionRunnerService const { flatFieldMetadata } = action; - // We need to defer here in case we create a relation the relationTargetFieldMetadataId might not already be created here await fieldMetadataRepository.save(flatFieldMetadata); }; runUpdateFieldMetadataMigration = async ({ @@ -55,16 +53,11 @@ export class WorkspaceMetadataFieldActionRunnerService FieldMetadataEntity, ); - const { flatFieldMetadata, updates } = action; - const update = updates.reduce< - Partial> - >((acc, { property, to }) => { - return { - ...acc, - [property]: to, - }; - }, {}); + const { fieldMetadataId } = action; - await fieldMetadataRepository.update(flatFieldMetadata.id, update); + await fieldMetadataRepository.update( + fieldMetadataId, + fromWorkspaceMigrationUpdateActionToPartialEntity(action), + ); }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-migration-runner-service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-migration-runner-service.ts index 4f8fab17ab..e81b58ec86 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-migration-runner-service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-migration-runner-service.ts @@ -16,65 +16,63 @@ export class WorkspaceMetadataMigrationRunnerService { ) {} runWorkspaceMetadataMigration = async ({ - workspaceMigration, + action, queryRunner, }: WorkspaceMigrationRunnerArgs) => { - for (const action of workspaceMigration.actions) { - switch (action.type) { - case 'delete_object': { - await this.workspaceMetadataObjectMigrationRunnerService.runDeleteObjectMetadataMigration( - { action, queryRunner }, - ); - break; - } - case 'create_object': { - await this.workspaceMetadataObjectMigrationRunnerService.runCreateObjectMetadataMigration( - { action, queryRunner }, - ); - break; - } - case 'update_object': { - await this.workspaceMetadataObjectMigrationRunnerService.runUpdateObjectMetadataMigration( - { action, queryRunner }, - ); - break; - } - case 'create_field': { - await this.workspaceMetadataFieldMigrationRunnerService.runCreateFieldMetadataMigration( - { action, queryRunner }, - ); - break; - } - case 'update_field': { - await this.workspaceMetadataFieldMigrationRunnerService.runUpdateFieldMetadataMigration( - { action, queryRunner }, - ); - break; - } - case 'delete_field': { - await this.workspaceMetadataFieldMigrationRunnerService.runDeleteFieldMetadataMigration( - { action, queryRunner }, - ); - break; - } - case 'create_index': { - await this.workspaceMetadataIndexMigrationRunnerService.runCreateIndexMetadataMigration( - { action, queryRunner }, - ); - break; - } - case 'delete_index': { - await this.workspaceMetadataIndexMigrationRunnerService.runDeleteIndexMetadataMigration( - { action, queryRunner }, - ); - break; - } - default: { - assertUnreachable( - action, - 'Should never occur, encountered an unsupported workspace migration action type', - ); - } + switch (action.type) { + case 'delete_object': { + await this.workspaceMetadataObjectMigrationRunnerService.runDeleteObjectMetadataMigration( + { action, queryRunner }, + ); + break; + } + case 'create_object': { + await this.workspaceMetadataObjectMigrationRunnerService.runCreateObjectMetadataMigration( + { action, queryRunner }, + ); + break; + } + case 'update_object': { + await this.workspaceMetadataObjectMigrationRunnerService.runUpdateObjectMetadataMigration( + { action, queryRunner }, + ); + break; + } + case 'create_field': { + await this.workspaceMetadataFieldMigrationRunnerService.runCreateFieldMetadataMigration( + { action, queryRunner }, + ); + break; + } + case 'update_field': { + await this.workspaceMetadataFieldMigrationRunnerService.runUpdateFieldMetadataMigration( + { action, queryRunner }, + ); + break; + } + case 'delete_field': { + await this.workspaceMetadataFieldMigrationRunnerService.runDeleteFieldMetadataMigration( + { action, queryRunner }, + ); + break; + } + case 'create_index': { + await this.workspaceMetadataIndexMigrationRunnerService.runCreateIndexMetadataMigration( + { action, queryRunner }, + ); + break; + } + case 'delete_index': { + await this.workspaceMetadataIndexMigrationRunnerService.runDeleteIndexMetadataMigration( + { action, queryRunner }, + ); + break; + } + default: { + assertUnreachable( + action, + 'Should never occur, encountered an unsupported workspace migration action type', + ); } } }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-object-action-runner.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-object-action-runner.service.ts index 402e4152fa..2cfa0279bf 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-object-action-runner.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-object-action-runner.service.ts @@ -1,8 +1,6 @@ import { Injectable } from '@nestjs/common'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; -import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; -import { FlatObjectMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-object-metadata/utils/compare-two-flat-object-metadata.util'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { CreateObjectAction, @@ -12,16 +10,21 @@ import { } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2'; import { RunnerMethodForActionType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/runner-method-for-action-type'; import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; +import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util'; +import { WorkspaceMetadataFieldActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-field-action-runner.service'; @Injectable() export class WorkspaceMetadataObjectActionRunnerService implements RunnerMethodForActionType { - constructor(private readonly dataSourceService: DataSourceService) {} + constructor( + private readonly dataSourceService: DataSourceService, + private readonly workspaceMetadataFieldActionRunnerService: WorkspaceMetadataFieldActionRunnerService, + ) {} runDeleteObjectMetadataMigration = async ({ - action: { flatObjectMetadataWithoutFields }, + action: { objectMetadataId }, queryRunner, }: WorkspaceMigrationActionRunnerArgs) => { const objectMetadataRepository = @@ -29,11 +32,11 @@ export class WorkspaceMetadataObjectActionRunnerService ObjectMetadataEntity, ); - await objectMetadataRepository.delete(flatObjectMetadataWithoutFields.id); + await objectMetadataRepository.delete(objectMetadataId); }; runCreateObjectMetadataMigration = async ({ - action: { flatObjectMetadataWithoutFields }, + action: { flatObjectMetadataWithoutFields, createFieldActions }, queryRunner, }: WorkspaceMigrationActionRunnerArgs) => { const objectMetadataRepository = @@ -49,12 +52,20 @@ export class WorkspaceMetadataObjectActionRunnerService ...flatObjectMetadataWithoutFields, dataSourceId: lastDataSourceMetadata.id, targetTableName: 'DEPRECATED', - // TODO call for each provided field too or pass fields here }); + + for (const createFieldAction of createFieldActions) { + await this.workspaceMetadataFieldActionRunnerService.runCreateFieldMetadataMigration( + { + action: createFieldAction, + queryRunner, + }, + ); + } }; runUpdateObjectMetadataMigration = async ({ - action: { flatObjectMetadataWithoutFields, updates }, + action, queryRunner, }: WorkspaceMigrationActionRunnerArgs) => { const objectMetadataRepository = @@ -62,18 +73,9 @@ export class WorkspaceMetadataObjectActionRunnerService ObjectMetadataEntity, ); - const update = updates.reduce< - Partial> - >((acc, { property, to }) => { - return { - ...acc, - [property]: to, - }; - }, {}); - - await objectMetadataRepository.save({ - ...flatObjectMetadataWithoutFields, // could be stricter - ...update, - }); + await objectMetadataRepository.update( + action.objectMetadataId, + fromWorkspaceMigrationUpdateActionToPartialEntity(action), + ); }; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module.ts index fd76dc13c3..63cb04064e 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module.ts @@ -3,6 +3,7 @@ import { Module } from '@nestjs/common'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; +import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module'; import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module'; import { WorkspaceMetadataFieldActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-field-action-runner.service'; @@ -22,6 +23,7 @@ import { WorkspaceSchemaObjectActionRunnerService } from 'src/engine/workspace-m DataSourceModule, WorkspaceMetadataVersionModule, WorkspacePermissionsCacheModule, + WorkspaceMetadataCacheModule, ], providers: [ WorkspaceMetadataObjectActionRunnerService, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service.ts index c82c3c06e2..f22543f212 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service.ts @@ -3,9 +3,11 @@ import { InjectDataSource } from '@nestjs/typeorm'; import { DataSource } from 'typeorm'; +import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service'; import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service'; import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service'; import { WorkspaceMigrationV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-v2'; +import { applyWorkspaceMigrationActionOnFlatObjectMetadataMaps } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/apply-workspace-migration-action-on-flat-object-metadata-maps'; import { WorkspaceMetadataMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-migration-runner-service'; import { WorkspaceSchemaMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-migration-runner.service'; @@ -16,6 +18,7 @@ export class WorkspaceMigrationRunnerV2Service { private readonly workspaceSchemaMigrationRunner: WorkspaceSchemaMigrationRunnerService, private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService, private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService, + private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService, @InjectDataSource('core') private readonly coreDataSource: DataSource, ) {} @@ -23,19 +26,40 @@ export class WorkspaceMigrationRunnerV2Service { run = async (workspaceMigration: WorkspaceMigrationV2) => { const queryRunner = this.coreDataSource.createQueryRunner(); + const { flatObjectMetadataMaps } = + await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps( + { + workspaceId: workspaceMigration.workspaceId, + }, + ); + await queryRunner.connect(); await queryRunner.startTransaction(); + let optimisticFlatObjectMetadataMaps = structuredClone( + flatObjectMetadataMaps, + ); + try { - await Promise.all([ - this.workspaceMetadataMigrationRunner.runWorkspaceMetadataMigration({ - queryRunner, - workspaceMigration, - }), - this.workspaceSchemaMigrationRunner.runWorkspaceSchemaMigration({ - queryRunner, - workspaceMigration, - }), - ]); + for (const action of workspaceMigration.actions) { + await Promise.all([ + this.workspaceMetadataMigrationRunner.runWorkspaceMetadataMigration({ + flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps, + queryRunner, + action, + }), + this.workspaceSchemaMigrationRunner.runWorkspaceSchemaMigration({ + queryRunner, + flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps, + action, + }), + ]); + + optimisticFlatObjectMetadataMaps = + applyWorkspaceMigrationActionOnFlatObjectMetadataMaps({ + action, + flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps, + }); + } await queryRunner.commitTransaction(); const { workspaceId } = workspaceMigration; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner.exception.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner.exception.ts new file mode 100644 index 0000000000..e86f59cb6b --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner.exception.ts @@ -0,0 +1,13 @@ +import { + appendCommonExceptionCode, + CustomException, +} from 'src/utils/custom-exception'; + +export class WorkspaceMigrationRunnerException extends CustomException< + keyof typeof WorkspaceMigrationRunnerExceptionCode +> {} + +export const WorkspaceMigrationRunnerExceptionCode = appendCommonExceptionCode({ + FIELD_METADATA_NOT_FOUND: 'FIELD_METADATA_NOT_FOUND', + OBJECT_METADATA_NOT_FOUND: 'OBJECT_METADATA_NOT_FOUND', +} as const); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-migration-runner.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-migration-runner.service.ts index 61901a0067..9ef711457c 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-migration-runner.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-migration-runner.service.ts @@ -16,65 +16,63 @@ export class WorkspaceSchemaMigrationRunnerService { ) {} runWorkspaceSchemaMigration = async ({ - workspaceMigration, + action, queryRunner, }: WorkspaceMigrationRunnerArgs) => { - for (const action of workspaceMigration.actions) { - switch (action.type) { - case 'delete_object': { - await this.workspaceSchemaObjectMigrationRunnerService.runDeleteObjectSchemaMigration( - { action, queryRunner }, - ); - break; - } - case 'create_object': { - await this.workspaceSchemaObjectMigrationRunnerService.runCreateObjectSchemaMigration( - { action, queryRunner }, - ); - break; - } - case 'update_object': { - await this.workspaceSchemaObjectMigrationRunnerService.runUpdateObjectSchemaMigration( - { action, queryRunner }, - ); - break; - } - case 'create_field': { - await this.workspaceSchemaFieldMigrationRunnerService.runCreateFieldSchemaMigration( - { action, queryRunner }, - ); - break; - } - case 'update_field': { - await this.workspaceSchemaFieldMigrationRunnerService.runUpdateFieldSchemaMigration( - { action, queryRunner }, - ); - break; - } - case 'delete_field': { - await this.workspaceSchemaFieldMigrationRunnerService.runDeleteFieldSchemaMigration( - { action, queryRunner }, - ); - break; - } - case 'create_index': { - await this.workspaceSchemaIndexMigrationRunnerService.runCreateIndexSchemaMigration( - { action, queryRunner }, - ); - break; - } - case 'delete_index': { - await this.workspaceSchemaIndexMigrationRunnerService.runDeleteIndexSchemaMigration( - { action, queryRunner }, - ); - break; - } - default: { - assertUnreachable( - action, - 'Should never occur, encountered an unsupported workspace migration action type', - ); - } + switch (action.type) { + case 'delete_object': { + await this.workspaceSchemaObjectMigrationRunnerService.runDeleteObjectSchemaMigration( + { action, queryRunner }, + ); + break; + } + case 'create_object': { + await this.workspaceSchemaObjectMigrationRunnerService.runCreateObjectSchemaMigration( + { action, queryRunner }, + ); + break; + } + case 'update_object': { + await this.workspaceSchemaObjectMigrationRunnerService.runUpdateObjectSchemaMigration( + { action, queryRunner }, + ); + break; + } + case 'create_field': { + await this.workspaceSchemaFieldMigrationRunnerService.runCreateFieldSchemaMigration( + { action, queryRunner }, + ); + break; + } + case 'update_field': { + await this.workspaceSchemaFieldMigrationRunnerService.runUpdateFieldSchemaMigration( + { action, queryRunner }, + ); + break; + } + case 'delete_field': { + await this.workspaceSchemaFieldMigrationRunnerService.runDeleteFieldSchemaMigration( + { action, queryRunner }, + ); + break; + } + case 'create_index': { + await this.workspaceSchemaIndexMigrationRunnerService.runCreateIndexSchemaMigration( + { action, queryRunner }, + ); + break; + } + case 'delete_index': { + await this.workspaceSchemaIndexMigrationRunnerService.runDeleteIndexSchemaMigration( + { action, queryRunner }, + ); + break; + } + default: { + assertUnreachable( + action, + 'Should never occur, encountered an unsupported workspace migration action type', + ); } } }; diff --git a/packages/twenty-shared/src/types/FromToType.ts b/packages/twenty-shared/src/types/FromToType.ts index a4890702bf..a174f3bf00 100644 --- a/packages/twenty-shared/src/types/FromToType.ts +++ b/packages/twenty-shared/src/types/FromToType.ts @@ -1,4 +1,3 @@ -export type FromTo = { - from: T; - to: T; +export type FromTo = { + [P in 'from' | 'to' as `${P}${Capitalize}`]: T; }; diff --git a/packages/twenty-shared/src/utils/__tests__/from-array-to-unique-key-record.util.test.ts b/packages/twenty-shared/src/utils/__tests__/from-array-to-unique-key-record.util.test.ts new file mode 100644 index 0000000000..54b99a5eb4 --- /dev/null +++ b/packages/twenty-shared/src/utils/__tests__/from-array-to-unique-key-record.util.test.ts @@ -0,0 +1,90 @@ +import { EachTestingContext } from '@/testing/types/EachTestingContext.type'; +import { fromArrayToUniqueKeyRecord } from '@/utils/from-array-to-unique-key-record.util'; + +type FromArrayToUniqueKeyRecordTestCase = EachTestingContext<{ + input: { + array: any[]; + uniqueKey: string; + }; + expected: Record | Error; +}>; + +describe('fromArrayToUniqueKeyRecord', () => { + const testCases: FromArrayToUniqueKeyRecordTestCase[] = [ + { + title: 'should convert array to record using id as unique key', + context: { + input: { + array: [ + { id: '1', name: 'John' }, + { id: '2', name: 'Jane' }, + ], + uniqueKey: 'id', + }, + expected: { + '1': { id: '1', name: 'John' }, + '2': { id: '2', name: 'Jane' }, + }, + }, + }, + { + title: 'should convert array to record using email as unique key', + context: { + input: { + array: [ + { email: 'john@test.com', name: 'John' }, + { email: 'jane@test.com', name: 'Jane' }, + ], + uniqueKey: 'email', + }, + expected: { + 'john@test.com': { email: 'john@test.com', name: 'John' }, + 'jane@test.com': { email: 'jane@test.com', name: 'Jane' }, + }, + }, + }, + { + title: 'should handle empty array', + context: { + input: { + array: [], + uniqueKey: 'id', + }, + expected: {}, + }, + }, + { + title: 'should throw error when array contains duplicate unique keys', + context: { + input: { + array: [ + { id: '1', name: 'John' }, + { id: '1', name: 'Jane' }, + ], + uniqueKey: 'id', + }, + expected: new Error( + 'Should never occur, flat array contains twice the same unique key 1', + ), + }, + }, + ]; + + test.each(testCases)('$title', ({ context: { input, expected } }) => { + if (expected instanceof Error) { + expect(() => + fromArrayToUniqueKeyRecord({ + array: input.array, + uniqueKey: input.uniqueKey, + }), + ).toThrow(expected.message); + } else { + const result = fromArrayToUniqueKeyRecord({ + array: input.array, + uniqueKey: input.uniqueKey, + }); + + expect(result).toEqual(expected); + } + }); +});