diff --git a/packages/twenty-server/src/engine/api/graphql/metadata-graphql-api.module.ts b/packages/twenty-server/src/engine/api/graphql/metadata-graphql-api.module.ts index 9bcf02bef..cc305f99c 100644 --- a/packages/twenty-server/src/engine/api/graphql/metadata-graphql-api.module.ts +++ b/packages/twenty-server/src/engine/api/graphql/metadata-graphql-api.module.ts @@ -10,14 +10,16 @@ import { GraphQLConfigModule } from 'src/engine/api/graphql/graphql-config/graph import { metadataModuleFactory } from 'src/engine/api/graphql/metadata.module-factory'; import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; import { ExceptionHandlerService } from 'src/engine/integrations/exception-handler/exception-handler.service'; +import { DataloaderModule } from 'src/engine/dataloaders/dataloader.module'; +import { DataloaderService } from 'src/engine/dataloaders/dataloader.service'; @Module({ imports: [ GraphQLModule.forRootAsync({ driver: YogaDriver, useFactory: metadataModuleFactory, - imports: [GraphQLConfigModule], - inject: [EnvironmentService, ExceptionHandlerService], + imports: [GraphQLConfigModule, DataloaderModule], + inject: [EnvironmentService, ExceptionHandlerService, DataloaderService], }), MetadataEngineModule, WorkspaceMigrationRunnerModule, diff --git a/packages/twenty-server/src/engine/api/graphql/metadata.module-factory.ts b/packages/twenty-server/src/engine/api/graphql/metadata.module-factory.ts index 97c880477..b58c3ac3b 100644 --- a/packages/twenty-server/src/engine/api/graphql/metadata.module-factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/metadata.module-factory.ts @@ -7,10 +7,12 @@ import { useExceptionHandler } from 'src/engine/integrations/exception-handler/h import { useThrottler } from 'src/engine/api/graphql/graphql-config/hooks/use-throttler'; import { MetadataGraphQLApiModule } from 'src/engine/api/graphql/metadata-graphql-api.module'; import { renderApolloPlayground } from 'src/engine/utils/render-apollo-playground.util'; +import { DataloaderService } from 'src/engine/dataloaders/dataloader.service'; export const metadataModuleFactory = async ( environmentService: EnvironmentService, exceptionHandlerService: ExceptionHandlerService, + dataloaderService: DataloaderService, ): Promise => { const config: YogaDriverConfig = { autoSchemaFile: true, @@ -32,6 +34,9 @@ export const metadataModuleFactory = async ( }), ], path: '/metadata', + context: () => ({ + loaders: dataloaderService.createLoaders(), + }), }; if (environmentService.get('DEBUG_MODE')) { diff --git a/packages/twenty-server/src/engine/dataloaders/dataloader.interface.ts b/packages/twenty-server/src/engine/dataloaders/dataloader.interface.ts new file mode 100644 index 000000000..defdba211 --- /dev/null +++ b/packages/twenty-server/src/engine/dataloaders/dataloader.interface.ts @@ -0,0 +1,7 @@ +import DataLoader from 'dataloader'; + +import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; + +export interface IDataloaders { + relationMetadataLoader: DataLoader; +} diff --git a/packages/twenty-server/src/engine/dataloaders/dataloader.module.ts b/packages/twenty-server/src/engine/dataloaders/dataloader.module.ts new file mode 100644 index 000000000..a669e0e05 --- /dev/null +++ b/packages/twenty-server/src/engine/dataloaders/dataloader.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; + +import { DataloaderService } from 'src/engine/dataloaders/dataloader.service'; +import { RelationMetadataModule } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.module'; + +@Module({ + providers: [DataloaderService], + imports: [RelationMetadataModule], + exports: [DataloaderService], +}) +export class DataloaderModule {} diff --git a/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts b/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts new file mode 100644 index 000000000..4a12186c5 --- /dev/null +++ b/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@nestjs/common'; + +import DataLoader from 'dataloader'; + +import { IDataloaders } from 'src/engine/dataloaders/dataloader.interface'; +import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; +import { RelationMetadataService } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.service'; + +@Injectable() +export class DataloaderService { + constructor( + private readonly relationMetadataService: RelationMetadataService, + ) {} + + createLoaders(): IDataloaders { + const relationMetadataLoader = new DataLoader< + string, + RelationMetadataEntity + >(async (fieldMetadataIds: string[]) => { + const relationsMetadataCollection = + await this.relationMetadataService.findManyRelationMetadataByFieldMetadataIds( + fieldMetadataIds, + ); + + return relationsMetadataCollection; + }); + + return { + relationMetadataLoader, + }; + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts index 77deab4ef..b4bc92e00 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts @@ -5,6 +5,7 @@ import { } from '@nestjs/common'; import { Args, + Context, Mutation, Parent, ResolveField, @@ -12,6 +13,7 @@ import { } from '@nestjs/graphql'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { IDataloaders } from 'src/engine/dataloaders/dataloader.interface'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard'; import { CreateOneFieldMetadataInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input'; @@ -89,7 +91,21 @@ export class FieldMetadataResolver { @ResolveField(() => RelationDefinitionDTO, { nullable: true }) async relationDefinition( @Parent() fieldMetadata: FieldMetadataDTO, + @Context() context: { loaders: IDataloaders }, ): Promise { - return await this.fieldMetadataService.getRelationDefinition(fieldMetadata); + if (fieldMetadata.type !== FieldMetadataType.RELATION) { + return null; + } + + const relationMetadataItem = + await context.loaders.relationMetadataLoader.load(fieldMetadata.id); + + const relationDefinition = + await this.fieldMetadataService.getRelationDefinitionFromRelationMetadata( + fieldMetadata, + relationMetadataItem, + ); + + return relationDefinition; } } diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts index 67ec645e5..cab253bfb 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts @@ -485,68 +485,48 @@ export class FieldMetadataService extends TypeOrmQueryService { - if (fieldMetadata.type !== FieldMetadataType.RELATION) { + if (fieldMetadataDTO.type !== FieldMetadataType.RELATION) { return null; } - const foundRelationMetadata = await this.relationMetadataRepository.findOne( - { - where: [ - { fromFieldMetadataId: fieldMetadata.id }, - { toFieldMetadataId: fieldMetadata.id }, - ], - relations: [ - 'fromObjectMetadata', - 'toObjectMetadata', - 'fromFieldMetadata', - 'toFieldMetadata', - ], - }, - ); - - if (!foundRelationMetadata) { - throw new Error('RelationMetadata not found'); - } - const isRelationFromSource = - foundRelationMetadata.fromFieldMetadata.id === fieldMetadata.id; + relationMetadata.fromFieldMetadata.id === fieldMetadataDTO.id; // TODO: implement MANY_TO_MANY - if ( - foundRelationMetadata.relationType === RelationMetadataType.MANY_TO_MANY - ) { + if (relationMetadata.relationType === RelationMetadataType.MANY_TO_MANY) { throw new Error(` - Relation type ${foundRelationMetadata.relationType} not supported + Relation type ${relationMetadata.relationType} not supported `); } if (isRelationFromSource) { const direction = - foundRelationMetadata.relationType === RelationMetadataType.ONE_TO_ONE + relationMetadata.relationType === RelationMetadataType.ONE_TO_ONE ? RelationDefinitionType.ONE_TO_ONE : RelationDefinitionType.ONE_TO_MANY; return { - sourceObjectMetadata: foundRelationMetadata.fromObjectMetadata, - sourceFieldMetadata: foundRelationMetadata.fromFieldMetadata, - targetObjectMetadata: foundRelationMetadata.toObjectMetadata, - targetFieldMetadata: foundRelationMetadata.toFieldMetadata, + sourceObjectMetadata: relationMetadata.fromObjectMetadata, + sourceFieldMetadata: relationMetadata.fromFieldMetadata, + targetObjectMetadata: relationMetadata.toObjectMetadata, + targetFieldMetadata: relationMetadata.toFieldMetadata, direction, }; } else { const direction = - foundRelationMetadata.relationType === RelationMetadataType.ONE_TO_ONE + relationMetadata.relationType === RelationMetadataType.ONE_TO_ONE ? RelationDefinitionType.ONE_TO_ONE : RelationDefinitionType.MANY_TO_ONE; return { - sourceObjectMetadata: foundRelationMetadata.toObjectMetadata, - sourceFieldMetadata: foundRelationMetadata.toFieldMetadata, - targetObjectMetadata: foundRelationMetadata.fromObjectMetadata, - targetFieldMetadata: foundRelationMetadata.fromFieldMetadata, + sourceObjectMetadata: relationMetadata.toObjectMetadata, + sourceFieldMetadata: relationMetadata.toFieldMetadata, + targetObjectMetadata: relationMetadata.fromObjectMetadata, + targetFieldMetadata: relationMetadata.fromFieldMetadata, direction, }; } diff --git a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts index a5a755ad0..1180e0dc9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts @@ -317,4 +317,43 @@ export class RelationMetadataService extends TypeOrmQueryService { + const relationMetadataCollection = + await this.relationMetadataRepository.find({ + where: [ + { + fromFieldMetadataId: In(fieldMetadataIds), + }, + { + toFieldMetadataId: In(fieldMetadataIds), + }, + ], + relations: [ + 'fromObjectMetadata', + 'toObjectMetadata', + 'fromFieldMetadata', + 'toFieldMetadata', + ], + }); + + const mappedResult = fieldMetadataIds.map((fieldMetadataId) => { + const foundRelationMetadataItem = relationMetadataCollection.find( + (relationMetadataItem) => + relationMetadataItem.fromFieldMetadataId === fieldMetadataId || + relationMetadataItem.toFieldMetadataId === fieldMetadataId, + ); + + return ( + foundRelationMetadataItem ?? + new NotFoundException( + `RelationMetadata with fieldMetadataId ${fieldMetadataId} not found`, + ) + ); + }); + + return mappedResult; + } }