FlatObjectMetadataMaps and non FP style refactor (#13590)

# Introduction
- Retrieveing flat-object-metadata-maps for input transpilation, we
decided not to introduce map merge for remaining validation operation
for the moment
- Refactored the field metadata service v2 to sequentially
optimistically render on each iteration
This commit is contained in:
Paul Rastoin
2025-08-05 10:46:28 +02:00
committed by GitHub
parent ca9464e19d
commit 6af99ab1ea
14 changed files with 316 additions and 94 deletions
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
@@ -13,10 +14,13 @@ 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 { 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 { isFlatFieldMetadataEntityOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
import { mergeFlatFieldMetadatasInFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/merge-flat-field-metadatas-in-flat-object-metadata.util';
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { dispatchAndMergeFlatFieldMetadatasInFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/dispatch-and-merge-flat-field-metadatas-in-flat-object-metadatas.util';
import { fromObjectMetadataMapsToFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-maps-to-flat-object-metadatas.util';
import { removeFlatFieldMetadataFromFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/remove-flat-field-metadata-from-flat-object-metadatas.util';
import { fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-with-flat-field-maps-to-flat-object-metadatas.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';
@@ -58,62 +62,132 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService<FieldMetadataEnt
const workspaceId = fieldMetadataInputs[0].workspaceId;
const { objectMetadataMaps } =
await this.workspaceMetadataCacheService.getExistingOrRecomputeMetadataMaps(
const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } =
await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps(
{ workspaceId },
);
const existingFlatObjectMetadatas =
fromObjectMetadataMapsToFlatObjectMetadatas(objectMetadataMaps);
const flatFieldMetadatasToCreate = (
await Promise.all(
fieldMetadataInputs.map(
async (fieldMetadataInput) =>
await fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata({
existingFlatObjectMetadatas,
existingFlatObjectMetadataMaps,
rawCreateFieldInput: fieldMetadataInput,
}),
),
)
).flat();
const optimisticRenderedFlatObjectMetadatas =
dispatchAndMergeFlatFieldMetadatasInFlatObjectMetadatas({
flatObjectMetadatas: existingFlatObjectMetadatas,
flatFieldMetadatas: flatFieldMetadatasToCreate,
});
const existingFlatObjectMetadatas = Object.values(
existingFlatObjectMetadataMaps.byId,
).map(fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata);
const flatFieldMetadataValidationPromises = flatFieldMetadatasToCreate.map(
(flatFieldMetadataToValidate) =>
this.flatFieldMetadataValidatorService.validateOneFlatFieldMetadata({
existingFlatObjectMetadatas:
removeFlatFieldMetadataFromFlatObjectMetadatas({
flatFieldMetadata: flatFieldMetadataToValidate,
flatObjectMetadatas: optimisticRenderedFlatObjectMetadatas,
}),
flatFieldMetadataToValidate,
workspaceId,
}),
const impactedObjectMetadataIds = Array.from(
new Set(
flatFieldMetadatasToCreate.map(
(flatFieldMetadata) => flatFieldMetadata.objectMetadataId,
),
),
);
const filterFlatObjectMetadatasByImpactedIds = (
flatObjectMetadatas: FlatObjectMetadata[],
) =>
flatObjectMetadatas.filter((flatObjectMetadata) =>
impactedObjectMetadataIds.includes(flatObjectMetadata.id),
);
const impactedExistingFlatObjectMetadatas =
filterFlatObjectMetadatasByImpactedIds(existingFlatObjectMetadatas);
const allValidationErrors: FailedFlatFieldMetadataValidationExceptions[] =
[];
let sequentiallyOptimisticallyRenderedFlatObjectMetadatas = structuredClone(
existingFlatObjectMetadatas,
);
const createdFlatFieldMetadataValidationResult = (
await Promise.all(flatFieldMetadataValidationPromises)
)
.flat()
.filter(isDefined);
for (const flatFieldMetadataToCreate of flatFieldMetadatasToCreate) {
let otherFlatObjectMetadataToValidate: FlatObjectMetadata | undefined =
undefined;
if (createdFlatFieldMetadataValidationResult.length > 0) {
if (
isFlatFieldMetadataEntityOfType(
flatFieldMetadataToCreate,
FieldMetadataType.RELATION,
) ||
isFlatFieldMetadataEntityOfType(
flatFieldMetadataToCreate,
FieldMetadataType.MORPH_RELATION,
)
) {
const relatedFlatFieldMetadataToCreate =
flatFieldMetadatasToCreate.find(
(relatedFlatFieldMetadata) =>
isFlatFieldMetadataEntityOfType(
relatedFlatFieldMetadata,
FieldMetadataType.RELATION,
) &&
relatedFlatFieldMetadata.id ===
flatFieldMetadataToCreate.relationTargetFieldMetadataId,
);
const relatedFlatObjectMetadata = isDefined(
relatedFlatFieldMetadataToCreate,
)
? existingFlatObjectMetadataMaps.byId[
relatedFlatFieldMetadataToCreate.objectMetadataId
]
: undefined;
otherFlatObjectMetadataToValidate =
isDefined(relatedFlatObjectMetadata) &&
isDefined(relatedFlatFieldMetadataToCreate)
? mergeFlatFieldMetadatasInFlatObjectMetadata({
flatFieldMetadatas: [relatedFlatFieldMetadataToCreate],
flatObjectMetadata: relatedFlatObjectMetadata,
})
: undefined;
}
const validationErrors =
await this.flatFieldMetadataValidatorService.validateOneFlatFieldMetadata(
{
existingFlatObjectMetadatas:
sequentiallyOptimisticallyRenderedFlatObjectMetadatas,
flatFieldMetadataToValidate: flatFieldMetadataToCreate,
workspaceId,
othersFlatObjectMetadataToValidate: isDefined(
otherFlatObjectMetadataToValidate,
)
? [otherFlatObjectMetadataToValidate]
: undefined,
},
);
if (validationErrors.length > 0) {
allValidationErrors.push(...validationErrors);
continue;
}
sequentiallyOptimisticallyRenderedFlatObjectMetadatas =
dispatchAndMergeFlatFieldMetadatasInFlatObjectMetadatas({
flatFieldMetadatas: [flatFieldMetadataToCreate],
flatObjectMetadatas:
sequentiallyOptimisticallyRenderedFlatObjectMetadatas,
});
}
if (allValidationErrors.length > 0) {
throw new MultipleMetadataValidationErrors(
createdFlatFieldMetadataValidationResult,
allValidationErrors,
'Multiple validation errors occurred while creating field',
);
}
const workspaceMigration = this.workspaceMigrationBuilderV2.build({
objectMetadataFromToInputs: {
from: existingFlatObjectMetadatas,
to: optimisticRenderedFlatObjectMetadatas,
from: impactedExistingFlatObjectMetadatas,
to: filterFlatObjectMetadatasByImpactedIds(
sequentiallyOptimisticallyRenderedFlatObjectMetadatas,
),
},
inferDeletionFromMissingObjectFieldIndex: false,
workspaceId,