diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2.ts index fbdcc745b48..663d8595f54 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2.ts @@ -13,10 +13,12 @@ import { FieldMetadataExceptionCode, } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; import { FlatFieldMetadataValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service'; -import { fromCreateFieldInputToFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-metadata.util'; +import { FailedFlatFieldMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-field-metadata/types/failed-flat-field-metadata-validation.type'; +import { fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-and-its-flat-object-metadata.util'; import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { fromObjectMetadataMapsToFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-maps-to-flat-object-metadatas.util'; import { mergeTwoFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/merge-two-flat-object-metadatas.util'; +import { getFieldMetadataEntityFromCachedObjectMetadataMaps } from 'src/engine/metadata-modules/utils/get-field-metadata-entity-from-cached-object-metadata-maps.util'; import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service'; import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service'; import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service'; @@ -66,29 +68,34 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService + await fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata({ + existingFlatObjectMetadatas, + rawCreateFieldInput: fieldMetadataInput, + }), + ), + ); + let flatObjectMetadatasWithNewFields: FlatObjectMetadata[] = []; - for (const fieldMetadataInput of fieldMetadataInputs) { - const createdFlatFieldsMetadataAndParentFlatObjectMetadata = - await fromCreateFieldInputToFlatFieldMetadata({ - existingFlatObjectMetadatas, - rawCreateFieldInput: fieldMetadataInput, - }); - + for (const flatFieldMetadataAndParentPair of flatFieldToCreateAndItsFlatObjectMetadataArray) { + const flatFieldMetadataValidationPromises = + flatFieldMetadataAndParentPair.flatMap< + Promise + >(({ flatFieldMetadata: flatFieldMetadataToValidate }) => + this.flatFieldMetadataValidatorService.validateOneFlatFieldMetadata({ + existingFlatObjectMetadatas, + flatFieldMetadataToValidate, + workspaceId, + }), + ); const createdFlatFieldMetadataValidationResult = ( - await Promise.all( - createdFlatFieldsMetadataAndParentFlatObjectMetadata.map( - ({ flatFieldMetadata: flatFieldMetadataToValidate }) => - this.flatFieldMetadataValidatorService.validateOneFlatFieldMetadata( - { - existingFlatObjectMetadatas, - flatFieldMetadataToValidate, - workspaceId, - }, - ), - ), - ) - ).filter(isDefined); + await Promise.all(flatFieldMetadataValidationPromises) + ) + .flat() + .filter(isDefined); if (createdFlatFieldMetadataValidationResult.length > 0) { const errors = createdFlatFieldMetadataValidationResult.flat(); @@ -100,7 +107,7 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService( + flatFieldMetadataAndParentPair.map( ({ flatFieldMetadata, parentFlatObjectMetadata }) => { return { ...parentFlatObjectMetadata, @@ -129,11 +136,27 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService( + (createdFlatFieldAndItsParentFlatObject) => { + return createdFlatFieldAndItsParentFlatObject + .map( + ({ + flatFieldMetadata: { id: fieldMetadataId }, + parentFlatObjectMetadata: { id: objectMetadataId }, + }) => + getFieldMetadataEntityFromCachedObjectMetadataMaps({ + fieldMetadataId, + objectMetadataId, + objectMetadataMaps: recomputedCache.objectMetadataMaps, + }), + ) + .filter(isDefined); + }, + ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-relation-target-field-metadata.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-relation-target-field-metadata.type.ts new file mode 100644 index 00000000000..47e9e618c27 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-relation-target-field-metadata.type.ts @@ -0,0 +1,7 @@ +import { FieldMetadataType } from 'twenty-shared/types'; + +import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; + +export type FlatRelationTargetFieldMetadata = FlatFieldMetadata< + FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION +>['flatRelationTargetFieldMetadata']; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-and-its-flat-object-metadata.util.ts similarity index 97% rename from packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-metadata.util.ts rename to packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-and-its-flat-object-metadata.util.ts index ab2389746ec..96a4a1d8698 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-and-its-flat-object-metadata.util.ts @@ -24,18 +24,18 @@ type FromCreateFieldInputToFlatObjectMetadata = { rawCreateFieldInput: CreateFieldInput; existingFlatObjectMetadatas: FlatObjectMetadata[]; }; -export type FlatFieldMetadataAndParentFlatObjectMetadata< +export type FlatFieldAndItsFlatObjectMetadata< T extends FieldMetadataType = FieldMetadataType, > = { flatFieldMetadata: FlatFieldMetadata; parentFlatObjectMetadata: FlatObjectMetadata; }; -export const fromCreateFieldInputToFlatFieldMetadata = async ({ +export const fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata = async ({ existingFlatObjectMetadatas, rawCreateFieldInput, }: FromCreateFieldInputToFlatObjectMetadata): Promise< - FlatFieldMetadataAndParentFlatObjectMetadata[] + FlatFieldAndItsFlatObjectMetadata[] > => { if (rawCreateFieldInput.isRemoteCreation) { throw new FieldMetadataException( diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-field-metadata-entity-to-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-field-metadata-entity-to-flat-field-metadata.util.ts index b3c322c72d0..27b5946be38 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-field-metadata-entity-to-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-field-metadata-entity-to-flat-field-metadata.util.ts @@ -1,32 +1,42 @@ -import { FieldMetadataType } from 'twenty-shared/types'; -import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils'; +import { + FieldMetadataType, + RelationAndMorphRelationFieldMetadataType, +} from 'twenty-shared/types'; +import { removePropertiesFromRecord } from 'twenty-shared/utils'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { + FieldMetadataException, + FieldMetadataExceptionCode, +} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; import { FieldMetadataEntityRelationProperties, FlatFieldMetadata, fieldMetadataRelationProperties, } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; -import { fromObjectMetadataEntityToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-entity-to-flat-object-metadata.util'; +import { FlatRelationTargetFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-relation-target-field-metadata.type'; import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.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 { fromObjectMetadataEntityToFlatObjectMetadataWithoutFields } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-object-metadata-entity-to-flat-object-metadata-without-fields.util'; +export const fromFieldMetadataEntityToFlatRelationTargetFieldMetadata = ( + fieldMetadataEntity: FieldMetadataEntity, +): FlatRelationTargetFieldMetadata => { + return { + uniqueIdentifier: fieldMetadataEntity.standardId ?? fieldMetadataEntity.id, + ...removePropertiesFromRecord(fieldMetadataEntity, [ + 'relationTargetObjectMetadata', + 'relationTargetFieldMetadata', + ]), + }; +}; + +// TODO refactor this method should not be recursive but depth 1 export const fromFieldMetadataEntityToFlatFieldMetadata = < T extends FieldMetadataType, >( fieldMetadataEntity: FieldMetadataEntity, - /** - * private depth bottleneck - */ - _depth?: number, // This is intended to be abstract ): FlatFieldMetadata => { - if (isDefined(_depth) && _depth > 1) { - throw new Error( - 'fromFieldMetadataEntityToFlatFieldMetadata entering a possible infinite loop', - ); - } - if ( isFieldMetadataEntityOfType( fieldMetadataEntity, @@ -44,20 +54,26 @@ export const fromFieldMetadataEntityToFlatFieldMetadata = < FieldMetadataEntityRelationProperties >(fieldMetadataEntity, fieldMetadataRelationProperties); - const newDepth = isDefined(_depth) ? _depth + 1 : 1; + if ( + !isFieldMetadataEntityOfType( + fieldMetadataEntity.relationTargetFieldMetadata, + FieldMetadataType.RELATION, + ) + ) { + throw new FieldMetadataException( + 'Relation target field is not a field metadata type relation', + FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED, + ); + } + const flatRelationTargetFieldMetadata = - fromFieldMetadataEntityToFlatFieldMetadata( + fromFieldMetadataEntityToFlatRelationTargetFieldMetadata( fieldMetadataEntity.relationTargetFieldMetadata, - newDepth, ); - const flatObjectTargetFieldMetadata = - fromObjectMetadataEntityToFlatObjectMetadata( - fieldMetadataEntity.relationTargetObjectMetadata, - ); const flatRelationTargetObjectMetadata = - fromFlatObjectMetadataToFlatObjectMetadataWithoutFields( - flatObjectTargetFieldMetadata, + fromObjectMetadataEntityToFlatObjectMetadataWithoutFields( + fieldMetadataEntity.relationTargetObjectMetadata, ); return { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-metadata.util.ts index b3927e53ab5..9eacf9d6f13 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-metadata.util.ts @@ -9,7 +9,7 @@ import { } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; import { validateRelationCreationPayloadOrThrow } from 'src/engine/metadata-modules/field-metadata/utils/validate-relation-creation-payload.util'; import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; -import { FlatFieldMetadataAndParentFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-metadata.util'; +import { FlatFieldAndItsFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-and-its-flat-object-metadata.util'; import { getDefaultFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util'; import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { computeMetadataNameFromLabel } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util'; @@ -24,7 +24,7 @@ export const fromRelationCreateFieldInputToFlatFieldMetadata = async ({ sourceParentFlatObjectMetadata, createFieldInput, }: FromRelationCreateFieldInputToFlatFieldMetadataArgs): Promise< - FlatFieldMetadataAndParentFlatObjectMetadata[] + FlatFieldAndItsFlatObjectMetadata[] > => { const { relationCreationPayload } = createFieldInput; @@ -104,5 +104,5 @@ export const fromRelationCreateFieldInputToFlatFieldMetadata = async ({ flatFieldMetadata: targetFlatFieldMetadata, parentFlatObjectMetadata: targetParentFlatObjectMetadata, }, - ] satisfies FlatFieldMetadataAndParentFlatObjectMetadata[]; + ] satisfies FlatFieldAndItsFlatObjectMetadata[]; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-cached-field-metadata-entity-to-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-cached-field-metadata-entity-to-flat-field-metadata.util.ts new file mode 100644 index 00000000000..69013d12b25 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-cached-field-metadata-entity-to-flat-field-metadata.util.ts @@ -0,0 +1,118 @@ +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 { FlatRelationTargetFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-relation-target-field-metadata.type'; +import { fromObjectMetadataItemWithFieldMapsToFlatObjectMetadataWithoutFields } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-item-with-field-maps-to-flat-object-metadata-without-fields.util'; +import { CachedFieldMetadataEntity } from 'src/engine/metadata-modules/types/cached-field-metadata-entity'; +import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; +import { + WorkspaceMetadataCacheException, + WorkspaceMetadataCacheExceptionCode, +} from 'src/engine/metadata-modules/workspace-metadata-cache/exceptions/workspace-metadata-cache.exception'; +import { isCachedFieldMetadataEntityOfType } from 'src/engine/utils/is-cached-field-metadata-of-type.util'; + +const fromCachedRelationFieldMetadataEntityToFlatRelationTargetFieldMetadata = < + T extends FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION, +>( + cachedFieldMetadataEntity: CachedFieldMetadataEntity, +): FlatRelationTargetFieldMetadata => { + return { + ...cachedFieldMetadataEntity, + uniqueIdentifier: + cachedFieldMetadataEntity.standardId ?? cachedFieldMetadataEntity.id, + type: cachedFieldMetadataEntity.type, + }; +}; + +type CachedFieldMetadataEntityAndObjectMetadataMap< + T extends FieldMetadataType, +> = { + objectMetadataMaps: ObjectMetadataMaps; + cachedFieldMetadataEntity: CachedFieldMetadataEntity; +}; + +export const fromCachedFieldMetadataEntityToFlatFieldMetadata = < + T extends FieldMetadataType, +>( + { + cachedFieldMetadataEntity, + objectMetadataMaps, + }: CachedFieldMetadataEntityAndObjectMetadataMap, + // This is intended to be abstract return type +): FlatFieldMetadata => { + if ( + isCachedFieldMetadataEntityOfType( + cachedFieldMetadataEntity, + FieldMetadataType.RELATION, + ) || + isCachedFieldMetadataEntityOfType( + cachedFieldMetadataEntity, + FieldMetadataType.MORPH_RELATION, + ) + ) { + const relationTargetObjectMetadataItemWithFieldsMaps = + objectMetadataMaps.byId[ + cachedFieldMetadataEntity.relationTargetObjectMetadataId + ]; + + if (!isDefined(relationTargetObjectMetadataItemWithFieldsMaps)) { + throw new WorkspaceMetadataCacheException( + 'Cache object is not up to date', + WorkspaceMetadataCacheExceptionCode.OBJECT_METADATA_MAP_NOT_FOUND, + ); + } + + const flatRelationTargetObjectMetadata = + fromObjectMetadataItemWithFieldMapsToFlatObjectMetadataWithoutFields( + relationTargetObjectMetadataItemWithFieldsMaps, + ); + + const relationTargetCachedFieldMetadata = + relationTargetObjectMetadataItemWithFieldsMaps.fieldsById[ + cachedFieldMetadataEntity.relationTargetFieldMetadataId + ]; + + if (!isDefined(relationTargetCachedFieldMetadata)) { + throw new WorkspaceMetadataCacheException( + 'Cache field is not up to date', + WorkspaceMetadataCacheExceptionCode.FIELD_METADATA_NOT_FOUND, + ); + } + if ( + !isCachedFieldMetadataEntityOfType( + relationTargetCachedFieldMetadata, + FieldMetadataType.RELATION, + ) + ) { + throw new WorkspaceMetadataCacheException( + 'Cache field is invalid', + WorkspaceMetadataCacheExceptionCode.FIELD_METADATA_INVALID, + ); + } + + const flatRelationTargetFieldMetadata = + fromCachedRelationFieldMetadataEntityToFlatRelationTargetFieldMetadata( + relationTargetCachedFieldMetadata, + ); + + return { + ...cachedFieldMetadataEntity, + uniqueIdentifier: + cachedFieldMetadataEntity.standardId ?? cachedFieldMetadataEntity.id, + flatRelationTargetFieldMetadata, + flatRelationTargetObjectMetadata, + type: cachedFieldMetadataEntity.type, + } satisfies FlatFieldMetadata< + FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION + >; + } + + return { + ...cachedFieldMetadataEntity, + uniqueIdentifier: + cachedFieldMetadataEntity.standardId ?? cachedFieldMetadataEntity.id, + flatRelationTargetFieldMetadata: null, + flatRelationTargetObjectMetadata: null, + }; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-item-with-field-maps-to-flat-object-metadata-without-fields.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-item-with-field-maps-to-flat-object-metadata-without-fields.util.ts new file mode 100644 index 00000000000..cd1a19a9988 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-item-with-field-maps-to-flat-object-metadata-without-fields.util.ts @@ -0,0 +1,22 @@ +import { FlatObjectMetadataWithoutFields } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; +import { fromObjectMetadataEntityToFlatObjectMetadataWithoutFields } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-object-metadata-entity-to-flat-object-metadata-without-fields.util'; + +export const fromObjectMetadataItemWithFieldMapsToFlatObjectMetadataWithoutFields = + ( + objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, + ): FlatObjectMetadataWithoutFields => { + const { + fieldsById: _fieldsById, + fieldIdByJoinColumnName: _fieldIdByJoinColumnName, + fieldIdByName: _fieldIdByName, + indexMetadatas, + ...rest + } = objectMetadataItemWithFieldMaps; + + return fromObjectMetadataEntityToFlatObjectMetadataWithoutFields({ + ...rest, + fields: [], + indexMetadatas, + }); + }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-item-with-field-maps-to-flat-object-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-item-with-field-maps-to-flat-object-metadata.util.ts index 2a48f979532..b39a7c992a0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-item-with-field-maps-to-flat-object-metadata.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-item-with-field-maps-to-flat-object-metadata.util.ts @@ -1,21 +1,40 @@ import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; -import { fromObjectMetadataEntityToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-entity-to-flat-object-metadata.util'; +import { fromCachedFieldMetadataEntityToFlatFieldMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-cached-field-metadata-entity-to-flat-field-metadata.util'; +import { CachedFieldMetadataEntity } from 'src/engine/metadata-modules/types/cached-field-metadata-entity'; import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; +import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; -export const fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata = ( - objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, -): FlatObjectMetadata => { +type FromObjectMetadataItemWithFieldMapsToFlatObjectMetadataArgs = { + objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps; + objectMetadataMaps: ObjectMetadataMaps; +}; +export const fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata = ({ + objectMetadataItemWithFieldMaps, + objectMetadataMaps, +}: FromObjectMetadataItemWithFieldMapsToFlatObjectMetadataArgs): FlatObjectMetadata => { const { fieldsById, fieldIdByJoinColumnName: _fieldIdByJoinColumnName, fieldIdByName: _fieldIdByName, - indexMetadatas, + indexMetadatas: _indexMetadatas, ...rest } = objectMetadataItemWithFieldMaps; - return fromObjectMetadataEntityToFlatObjectMetadata({ + const cachedFieldMetadataEntities = + Object.values(fieldsById); + + const flatFieldMetadatas = cachedFieldMetadataEntities.map( + (cachedFieldMetadataEntity) => + fromCachedFieldMetadataEntityToFlatFieldMetadata({ + cachedFieldMetadataEntity, + objectMetadataMaps, + }), + ); + + return { ...rest, - fields: Object.values(fieldsById), - indexMetadatas, - }); + flatFieldMetadatas, + uniqueIdentifier: rest.standardId ?? rest.id, + flatIndexMetadatas: [], // prastoin TODO convert from indexMetadatas to flatIndexMetadatas + }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-maps-to-flat-object-metadatas.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-maps-to-flat-object-metadatas.util.ts index 7a7ea4030a3..e296a0831f5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-maps-to-flat-object-metadatas.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-maps-to-flat-object-metadatas.util.ts @@ -3,6 +3,10 @@ import { isDefined } from 'twenty-shared/utils'; import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-item-with-field-maps-to-flat-object-metadata.util'; import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; +import { + WorkspaceMetadataCacheException, + WorkspaceMetadataCacheExceptionCode, +} from 'src/engine/metadata-modules/workspace-metadata-cache/exceptions/workspace-metadata-cache.exception'; export const fromObjectMetadataMapsToFlatObjectMetadatas = ( objectMetadataMaps: ObjectMetadataMaps, @@ -10,12 +14,19 @@ export const fromObjectMetadataMapsToFlatObjectMetadatas = ( const objectMetadataIds = Object.keys(objectMetadataMaps.byId); return objectMetadataIds.flatMap((objectMetadataId) => { - const occurrence = objectMetadataMaps.byId[objectMetadataId]; + const objectMetadataItemWithFieldMaps = + objectMetadataMaps.byId[objectMetadataId]; - if (!isDefined(occurrence)) { - return []; + if (!isDefined(objectMetadataItemWithFieldMaps)) { + throw new WorkspaceMetadataCacheException( + 'Object metadata not found in cache', + WorkspaceMetadataCacheExceptionCode.OBJECT_METADATA_MAP_NOT_FOUND, + ); } - return fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata(occurrence); + return fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata({ + objectMetadataItemWithFieldMaps, + objectMetadataMaps, + }); }); }; diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata-v2.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata-v2.service.ts index 990c5861f7b..406f2039075 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata-v2.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata-v2.service.ts @@ -41,8 +41,8 @@ export class ObjectMetadataServiceV2 extends TypeOrmQueryService = Omit, FieldMetadataEntityRelationProperties>; diff --git a/packages/twenty-server/src/engine/metadata-modules/types/field-metadata-map.ts b/packages/twenty-server/src/engine/metadata-modules/types/field-metadata-map.ts index b12b46f0af4..80cf3af673c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/types/field-metadata-map.ts +++ b/packages/twenty-server/src/engine/metadata-modules/types/field-metadata-map.ts @@ -1,3 +1,3 @@ import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -export type FieldMetadataMap = Record; +export type FieldMetadataMap = Record; // TODO refactor Should be CachedFieldMetadataEntity or best FlatFieldMetadata diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/get-field-metadata-entity-from-cached-object-metadata-maps.util.ts b/packages/twenty-server/src/engine/metadata-modules/utils/get-field-metadata-entity-from-cached-object-metadata-maps.util.ts new file mode 100644 index 00000000000..0791de38afd --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/utils/get-field-metadata-entity-from-cached-object-metadata-maps.util.ts @@ -0,0 +1,42 @@ +import { isDefined } from 'twenty-shared/utils'; + +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { CachedFieldMetadataEntity } from 'src/engine/metadata-modules/types/cached-field-metadata-entity'; +import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; + +export const getFieldMetadataEntityFromCachedObjectMetadataMaps = ({ + fieldMetadataId, + objectMetadataId, + objectMetadataMaps, +}: { + objectMetadataMaps: ObjectMetadataMaps; + objectMetadataId: string; + fieldMetadataId: string; +}): FieldMetadataEntity | undefined => { + const objectMetadataFromCache = objectMetadataMaps.byId[objectMetadataId]; + + if (!isDefined(objectMetadataFromCache)) { + return undefined; + } + + const fieldMetadataFromCache: CachedFieldMetadataEntity = + objectMetadataFromCache.fieldsById[fieldMetadataId]; + + if (!isDefined(fieldMetadataFromCache)) { + return undefined; + } + + // We need to determine if graphql will be able to handle relations crafting byitself + return { + ...fieldMetadataFromCache, + object: {} as ObjectMetadataEntity, + relationTargetFieldMetadata: null, + relationTargetObjectMetadata: null, + indexFieldMetadatas: {} as IndexFieldMetadataEntity, + fieldPermissions: [], + createdAt: new Date(fieldMetadataFromCache.createdAt), + updatedAt: new Date(fieldMetadataFromCache.updatedAt), + }; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-cache/exceptions/workspace-metadata-cache.exception.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-cache/exceptions/workspace-metadata-cache.exception.ts index 6e2ef1fdb74..ce35e87be12 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-cache/exceptions/workspace-metadata-cache.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-cache/exceptions/workspace-metadata-cache.exception.ts @@ -4,5 +4,7 @@ export class WorkspaceMetadataCacheException extends CustomException { + }): Promise { const currentCacheVersion = await this.getMetadataVersionFromCache(workspaceId); @@ -84,7 +84,7 @@ export class WorkspaceMetadataCacheService { workspaceId, }: { workspaceId: string; - }): Promise { + }): Promise { const currentDatabaseVersion = await this.getMetadataVersionFromDatabase(workspaceId); diff --git a/packages/twenty-server/src/engine/utils/is-cached-field-metadata-of-type.util.ts b/packages/twenty-server/src/engine/utils/is-cached-field-metadata-of-type.util.ts new file mode 100644 index 00000000000..37a201ae449 --- /dev/null +++ b/packages/twenty-server/src/engine/utils/is-cached-field-metadata-of-type.util.ts @@ -0,0 +1,14 @@ +import { FieldMetadataType } from 'twenty-shared/types'; + +import { CachedFieldMetadataEntity } from 'src/engine/metadata-modules/types/cached-field-metadata-entity'; + +export function isCachedFieldMetadataEntityOfType< + Field extends CachedFieldMetadataEntity, + Type extends FieldMetadataType, +>( + fieldMetadata: Pick, + type: Type, +): fieldMetadata is CachedFieldMetadataEntity & + CachedFieldMetadataEntity { + return fieldMetadata.type === type; +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-object-metadata-entity-to-flat-object-metadata-without-fields.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-object-metadata-entity-to-flat-object-metadata-without-fields.util.ts new file mode 100644 index 00000000000..5ce501b8bb8 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-object-metadata-entity-to-flat-object-metadata-without-fields.util.ts @@ -0,0 +1,12 @@ +import { FlatObjectMetadataWithoutFields } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { fromObjectMetadataEntityToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-entity-to-flat-object-metadata.util'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; + +export const fromObjectMetadataEntityToFlatObjectMetadataWithoutFields = ( + objectMetadataEntity: ObjectMetadataEntity, +): FlatObjectMetadataWithoutFields => { + return fromObjectMetadataEntityToFlatObjectMetadata({ + ...objectMetadataEntity, + fields: [], + }); +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service.ts index 1183155770b..cd3eff767cc 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service.ts @@ -36,6 +36,7 @@ export class WorkspaceMigrationBuilderV2Service { createdObjectMetadata, deletedObjectMetadata, updatedObjectMetadata, + inferDeletionFromMissingObjectFieldIndex, }); const createdObjectMetadataCreateIndexActions = 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 f0e461ccca4..d1ca12f187e 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 @@ -13,11 +13,14 @@ import { } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-object-actions'; export type CreatedDeletedUpdatedObjectMetadataInputMatrix = - CustomDeletedCreatedUpdatedMatrix<'objectMetadata', FlatObjectMetadata>; + CustomDeletedCreatedUpdatedMatrix<'objectMetadata', FlatObjectMetadata> & { + inferDeletionFromMissingObjectFieldIndex: boolean; + }; export const buildWorkspaceMigrationV2ObjectActions = ({ createdObjectMetadata, deletedObjectMetadata, updatedObjectMetadata, + inferDeletionFromMissingObjectFieldIndex, }: CreatedDeletedUpdatedObjectMetadataInputMatrix): WorkspaceMigrationObjectActionV2[] => { const createdObjectActions = createdObjectMetadata.map( (flatObjectMetadata) => { @@ -36,9 +39,9 @@ export const buildWorkspaceMigrationV2ObjectActions = ({ }, ); - const deletedObjectActions = deletedObjectMetadata.map( - getWorkspaceMigrationV2ObjectDeleteAction, - ); + const deletedObjectActions = inferDeletionFromMissingObjectFieldIndex + ? deletedObjectMetadata.map(getWorkspaceMigrationV2ObjectDeleteAction) + : []; const updatedObjectActions = updatedObjectMetadata.flatMap(({ from, to }) => { diff --git a/packages/twenty-shared/src/types/RelationAndMorphRelationFieldMetadataType.ts b/packages/twenty-shared/src/types/RelationAndMorphRelationFieldMetadataType.ts new file mode 100644 index 00000000000..624cd3bd0d6 --- /dev/null +++ b/packages/twenty-shared/src/types/RelationAndMorphRelationFieldMetadataType.ts @@ -0,0 +1,5 @@ +import { FieldMetadataType } from './FieldMetadataType'; + +export type RelationAndMorphRelationFieldMetadataType = + | FieldMetadataType.RELATION + | FieldMetadataType.MORPH_RELATION; diff --git a/packages/twenty-shared/src/types/index.ts b/packages/twenty-shared/src/types/index.ts index ae7c777897a..df9b0429b31 100644 --- a/packages/twenty-shared/src/types/index.ts +++ b/packages/twenty-shared/src/types/index.ts @@ -17,6 +17,7 @@ export type { NonNullableRequired } from './NonNullableRequired'; export type { NullablePartial } from './NullablePartial'; export type { ObjectRecordsPermissions } from './ObjectRecordsPermissions'; export type { ObjectRecordsPermissionsByRoleId } from './ObjectRecordsPermissionsByRoleId'; +export type { RelationAndMorphRelationFieldMetadataType } from './RelationAndMorphRelationFieldMetadataType'; export type { RestrictedField } from './RestrictedField'; export type { RestrictedFields } from './RestrictedFields'; export type { StepFilterGroup, StepFilter } from './StepFilters';