Implemented dataloader for relation metadata (#4891)

- Implemented dataloader package on metadata graphql server
- Implemented a dataloader for relation metadata module

---------

Co-authored-by: Jérémy M <jeremy.magrin@gmail.com>
This commit is contained in:
Lucas Bordeau
2024-04-09 17:09:02 +02:00
committed by GitHub
parent b724c5e610
commit ee5aaae796
8 changed files with 132 additions and 40 deletions

View File

@ -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<YogaDriverConfig>({
driver: YogaDriver,
useFactory: metadataModuleFactory,
imports: [GraphQLConfigModule],
inject: [EnvironmentService, ExceptionHandlerService],
imports: [GraphQLConfigModule, DataloaderModule],
inject: [EnvironmentService, ExceptionHandlerService, DataloaderService],
}),
MetadataEngineModule,
WorkspaceMigrationRunnerModule,

View File

@ -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<YogaDriverConfig> => {
const config: YogaDriverConfig = {
autoSchemaFile: true,
@ -32,6 +34,9 @@ export const metadataModuleFactory = async (
}),
],
path: '/metadata',
context: () => ({
loaders: dataloaderService.createLoaders(),
}),
};
if (environmentService.get('DEBUG_MODE')) {

View File

@ -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<string, RelationMetadataEntity>;
}

View File

@ -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 {}

View File

@ -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,
};
}
}

View File

@ -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<RelationDefinitionDTO | null> {
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;
}
}

View File

@ -485,68 +485,48 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
return fieldMetadataInputOverrided as UpdateFieldInput;
}
public async getRelationDefinition(
fieldMetadata: FieldMetadataDTO,
public async getRelationDefinitionFromRelationMetadata(
fieldMetadataDTO: FieldMetadataDTO,
relationMetadata: RelationMetadataEntity,
): Promise<RelationDefinitionDTO | null> {
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,
};
}

View File

@ -317,4 +317,43 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
return deletedRelationMetadata;
}
async findManyRelationMetadataByFieldMetadataIds(
fieldMetadataIds: string[],
): Promise<(RelationMetadataEntity | NotFoundException)[]> {
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;
}
}