Extract field metadata build out of object metadata build (#14763)

# Introduction

Extracting the legacy fields build and dispatch out of the object one to
follow the generic flat entity build, also update caches entries for
object

## Main tasks
- flat field map cache
- flat field builder
- refactored the dispatch matrix to return flat entity maps instead of
flat entity arrays
- orchestrator aggregator
- removing legacy code
- making universal identifier aka standardId of standard field for
custom object deterministically dynamic
- perfs debug logs for v2

## TODO
- [x] Refactor the generic entity builder to be dependency flat maps in
order to main foreign keys list in flat parent
- [x] Refactor the flat object metadata to contain the array of related
fields and avoid costy find object fields
- [ ] Improve the create field handler to handle multiple field at once
- [ ] Refactor the dispatch to embbed the comparison
- [ ] Improve perf by extracting from elements out of existing
- [ ] Fix the labelIdentifierId validators on object before field
creation ( integ tests are in failing mode )

## Debug logs snippet

```ts
[EntityBuilder fieldMetadata] matrix computation: 0.027ms
[EntityBuilder fieldMetadata] creation validation: 0.001ms
[EntityBuilder fieldMetadata] deletion validation: 0.293ms
[EntityBuilder fieldMetadata] update validation: 0.006ms
[EntityBuilder fieldMetadata] entity processing: 0.363ms
[EntityBuilder fieldMetadata] validateAndBuild: 0.455ms
[EntityBuilder index] matrix computation: 0.005ms
[EntityBuilder index] creation validation: 0.001ms
[EntityBuilder index] deletion validation: 0.146ms
[EntityBuilder index] update validation: 0.004ms
[EntityBuilder index] entity processing: 0.199ms
[EntityBuilder index] validateAndBuild: 0.228ms
[Runner] Initial cache retrieval: 0.549ms
[BaseWorkspaceMigrationRunnerActionHandlerService] delete_index executeForWorkspaceSchema: 11.665ms
[BaseWorkspaceMigrationRunnerActionHandlerService] delete_index executeForMetadata: 12.864ms
[BaseWorkspaceMigrationRunnerActionHandlerService] delete_field executeForWorkspaceSchema: 1.476ms
[BaseWorkspaceMigrationRunnerActionHandlerService] delete_field executeForMetadata: 6.816ms
[BaseWorkspaceMigrationRunnerActionHandlerService] delete_field executeForWorkspaceSchema: 0.062ms
[BaseWorkspaceMigrationRunnerActionHandlerService] delete_field executeForMetadata: 0.889ms
[Runner] Transaction execution: 23.434ms
[Runner] Cache invalidation: 316.662ms
[Runner] Total execution: 340.767ms
```
As you can see cache invalidation is way to long, we could replace the
cache by the optimistic in the end
This commit is contained in:
Paul Rastoin
2025-10-02 18:07:34 +02:00
committed by GitHub
parent d4b83df3f5
commit 170f6d27c5
240 changed files with 11385 additions and 17744 deletions
@@ -1,5 +1,6 @@
import { FieldMetadataType, RelationType } from 'twenty-shared/types';
import { ROCKET_CUSTOM_OBJECT_SEED } from 'src/engine/workspace-manager/dev-seeder/metadata/custom-objects/constants/rocket-custom-object-seed.constant';
import { type FieldMetadataSeed } from 'src/engine/workspace-manager/dev-seeder/metadata/types/field-metadata-seed.type';
export const PET_CUSTOM_RELATION_FIELD_SEEDS: (FieldMetadataSeed & {
@@ -18,7 +19,10 @@ export const PET_CUSTOM_RELATION_FIELD_SEEDS: (FieldMetadataSeed & {
targetFieldIcon: 'IconRelationOneToMany',
},
],
targetObjectMetadataNames: ['surveyResult', 'rocket'],
targetObjectMetadataNames: [
'surveyResult',
ROCKET_CUSTOM_OBJECT_SEED.nameSingular,
],
},
{
type: FieldMetadataType.MORPH_RELATION,
@@ -33,6 +37,9 @@ export const PET_CUSTOM_RELATION_FIELD_SEEDS: (FieldMetadataSeed & {
targetFieldIcon: 'IconRelationOneToMany',
},
],
targetObjectMetadataNames: ['surveyResult', 'rocket'],
targetObjectMetadataNames: [
'surveyResult',
ROCKET_CUSTOM_OBJECT_SEED.nameSingular,
],
},
];
@@ -6,7 +6,8 @@ import { DataSource } from 'typeorm';
import { type DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata.service';
import { FieldMetadataServiceV2 } from 'src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2';
import { ObjectMetadataServiceV2 } from 'src/engine/metadata-modules/object-metadata/object-metadata-v2.service';
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
@@ -30,7 +31,8 @@ import { prefillCoreViews } from 'src/engine/workspace-manager/standard-objects-
export class DevSeederMetadataService {
constructor(
private readonly objectMetadataService: ObjectMetadataService,
private readonly fieldMetadataService: FieldMetadataService,
private readonly objectMetadataServiceV2: ObjectMetadataServiceV2,
private readonly fieldMetadataServiceV2: FieldMetadataServiceV2,
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
@InjectDataSource()
private readonly coreDataSource: DataSource,
@@ -61,7 +63,10 @@ export class DevSeederMetadataService {
{ objectName: 'person', seeds: PERSON_CUSTOM_FIELD_SEEDS },
],
relations: [
{ objectName: 'pet', seeds: PET_CUSTOM_RELATION_FIELD_SEEDS },
{
objectName: PET_CUSTOM_OBJECT_SEED.nameSingular,
seeds: PET_CUSTOM_RELATION_FIELD_SEEDS,
},
],
},
[SEED_YCOMBINATOR_WORKSPACE_ID]: {
@@ -131,9 +136,11 @@ export class DevSeederMetadataService {
workspaceId: string;
objectMetadataSeed: ObjectMetadataSeed;
}): Promise<void> {
await this.objectMetadataService.createOne({
...objectMetadataSeed,
dataSourceId,
await this.objectMetadataServiceV2.createOne({
createObjectInput: {
...objectMetadataSeed,
dataSourceId,
},
workspaceId,
});
}
@@ -152,19 +159,20 @@ export class DevSeederMetadataService {
where: { nameSingular: objectMetadataNameSingular },
});
if (!objectMetadata) {
if (!isDefined(objectMetadata)) {
throw new Error(
`Object metadata not found for: ${objectMetadataNameSingular}`,
);
}
const createFieldInputs = fieldMetadataSeeds.map((fieldMetadataSeed) => ({
...fieldMetadataSeed,
objectMetadataId: objectMetadata.id,
}));
await this.fieldMetadataService.createMany(
fieldMetadataSeeds.map((fieldMetadataSeed) => ({
...fieldMetadataSeed,
objectMetadataId: objectMetadata.id,
workspaceId,
})),
);
await this.fieldMetadataServiceV2.createMany({
createFieldInputs,
workspaceId,
});
}
private async seedCoreViews({
@@ -226,27 +234,27 @@ export class DevSeederMetadataService {
},
);
const relationFieldInputs = this.createFieldInputs({
const createFieldInputs = this.createFieldInputs({
relation,
objectMetadataMaps,
workspaceId,
});
await this.fieldMetadataService.createMany(relationFieldInputs);
await this.fieldMetadataServiceV2.createMany({
createFieldInputs,
workspaceId,
});
}
private createFieldInputs({
relation,
objectMetadataMaps,
workspaceId,
}: {
relation: {
objectName: string;
seeds: (FieldMetadataSeed & { targetObjectMetadataNames: string[] })[];
};
objectMetadataMaps: ObjectMetadataMaps;
workspaceId: string;
}): CreateFieldInput[] {
}): Omit<CreateFieldInput, 'workspaceId'>[] {
const objectMetadataId =
objectMetadataMaps.idByNameSingular[relation.objectName];
@@ -261,7 +269,6 @@ export class DevSeederMetadataService {
label: seed.label,
name: seed.name,
objectMetadataId,
workspaceId,
morphRelationsCreationPayload: seed.targetObjectMetadataNames.map(
(targetObjectMetadataName) => {
const targetObjectMetadataId =