import { Injectable } from '@nestjs/common'; import { Record as IRecord, RecordFilter, RecordOrderBy, } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface'; import { IEdge } from 'src/engine/api/graphql/workspace-query-runner/interfaces/edge.interface'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { CreateManyResolverArgs, CreateOneResolverArgs, DeleteManyResolverArgs, DeleteOneResolverArgs, DestroyManyResolverArgs, DestroyOneResolverArgs, FindDuplicatesResolverArgs, FindManyResolverArgs, FindOneResolverArgs, ResolverArgs, ResolverArgsType, RestoreManyResolverArgs, SearchResolverArgs, UpdateManyResolverArgs, UpdateOneResolverArgs, WorkspaceResolverBuilderMethodNames, } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { GraphqlQueryResolverFactory } from 'src/engine/api/graphql/graphql-query-runner/factories/graphql-query-resolver.factory'; import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service'; import { QueryResultGettersFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/query-result-getters.factory'; import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory'; import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service'; import { LogExecutionTime } from 'src/engine/decorators/observability/log-execution-time.decorator'; import { capitalize } from 'src/utils/capitalize'; @Injectable() export class GraphqlQueryRunnerService { constructor( private readonly workspaceQueryHookService: WorkspaceQueryHookService, private readonly queryRunnerArgsFactory: QueryRunnerArgsFactory, private readonly queryResultGettersFactory: QueryResultGettersFactory, private readonly graphqlQueryResolverFactory: GraphqlQueryResolverFactory, private readonly apiEventEmitterService: ApiEventEmitterService, ) {} /** QUERIES */ @LogExecutionTime() async findOne( args: FindOneResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise { return this.executeQuery, ObjectRecord>( 'findOne', args, options, ); } @LogExecutionTime() async findMany< ObjectRecord extends IRecord, Filter extends RecordFilter, OrderBy extends RecordOrderBy, >( args: FindManyResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise>> { return this.executeQuery< FindManyResolverArgs, IConnection> >('findMany', args, options); } @LogExecutionTime() async findDuplicates( args: FindDuplicatesResolverArgs>, options: WorkspaceQueryRunnerOptions, ): Promise[]> { return this.executeQuery< FindDuplicatesResolverArgs>, IConnection[] >('findDuplicates', args, options); } @LogExecutionTime() async search( args: SearchResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise> { return this.executeQuery>( 'search', args, options, ); } /** MUTATIONS */ @LogExecutionTime() async createOne( args: CreateOneResolverArgs>, options: WorkspaceQueryRunnerOptions, ): Promise { const results = await this.executeQuery< CreateManyResolverArgs>, ObjectRecord[] >('createMany', { data: [args.data], upsert: args.upsert }, options); // TODO: emitCreateEvents should be moved to the ORM layer if (results) { this.apiEventEmitterService.emitCreateEvents( results, options.authContext, options.objectMetadataItem, ); } return results[0]; } @LogExecutionTime() async createMany( args: CreateManyResolverArgs>, options: WorkspaceQueryRunnerOptions, ): Promise { const results = await this.executeQuery< CreateManyResolverArgs>, ObjectRecord[] >('createMany', args, options); if (results) { this.apiEventEmitterService.emitCreateEvents( results, options.authContext, options.objectMetadataItem, ); } return results; } @LogExecutionTime() public async updateOne( args: UpdateOneResolverArgs>, options: WorkspaceQueryRunnerOptions, ): Promise { const existingRecord = await this.executeQuery< FindOneResolverArgs, ObjectRecord >( 'findOne', { filter: { id: { eq: args.id } }, }, options, ); const result = await this.executeQuery< UpdateOneResolverArgs>, ObjectRecord >('updateOne', args, options); this.apiEventEmitterService.emitUpdateEvents( [existingRecord], [result], Object.keys(args.data), options.authContext, options.objectMetadataItem, ); return result; } @LogExecutionTime() public async updateMany( args: UpdateManyResolverArgs>, options: WorkspaceQueryRunnerOptions, ): Promise { const existingRecords = await this.executeQuery< FindManyResolverArgs, IConnection> >( 'findMany', { filter: args.filter, }, options, ); const result = await this.executeQuery< UpdateManyResolverArgs>, ObjectRecord[] >('updateMany', args, options); this.apiEventEmitterService.emitUpdateEvents( existingRecords.edges.map((edge) => edge.node), result, Object.keys(args.data), options.authContext, options.objectMetadataItem, ); return result; } @LogExecutionTime() public async deleteOne( args: DeleteOneResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise { const result = await this.executeQuery< UpdateOneResolverArgs>, ObjectRecord >( 'deleteOne', { id: args.id, data: { deletedAt: new Date() } as Partial, }, options, ); this.apiEventEmitterService.emitDeletedEvents( [result], options.authContext, options.objectMetadataItem, ); return result; } @LogExecutionTime() public async deleteMany( args: DeleteManyResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise { const result = await this.executeQuery< UpdateManyResolverArgs>, ObjectRecord[] >( 'deleteMany', { filter: args.filter, data: { deletedAt: new Date() } as Partial, }, options, ); this.apiEventEmitterService.emitDeletedEvents( result, options.authContext, options.objectMetadataItem, ); return result; } @LogExecutionTime() async destroyOne( args: DestroyOneResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise { const result = await this.executeQuery< DestroyOneResolverArgs, ObjectRecord >('destroyOne', args, options); this.apiEventEmitterService.emitDestroyEvents( [result], options.authContext, options.objectMetadataItem, ); return result; } @LogExecutionTime() async destroyMany( args: DestroyManyResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise { const result = await this.executeQuery< DestroyManyResolverArgs, ObjectRecord[] >('destroyMany', args, options); this.apiEventEmitterService.emitDestroyEvents( result, options.authContext, options.objectMetadataItem, ); return result; } @LogExecutionTime() public async restoreMany( args: RestoreManyResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise { return await this.executeQuery< UpdateManyResolverArgs>, ObjectRecord >( 'restoreMany', { filter: args.filter, data: { deletedAt: null } as Partial, }, options, ); } private async executeQuery( operationName: WorkspaceResolverBuilderMethodNames, args: Input, options: WorkspaceQueryRunnerOptions, ): Promise { const { authContext, objectMetadataItem } = options; const resolver = this.graphqlQueryResolverFactory.getResolver(operationName); await resolver.validate(args, options); const hookedArgs = await this.workspaceQueryHookService.executePreQueryHooks( authContext, objectMetadataItem.nameSingular, operationName, args, ); const computedArgs = await this.queryRunnerArgsFactory.create( hookedArgs, options, ResolverArgsType[capitalize(operationName)], ); const results = await resolver.resolve(computedArgs as Input, options); const resultWithGetters = await this.queryResultGettersFactory.create( results, objectMetadataItem, authContext.workspace.id, ); const resultWithGettersArray = Array.isArray(resultWithGetters) ? resultWithGetters : [resultWithGetters]; await this.workspaceQueryHookService.executePostQueryHooks( authContext, objectMetadataItem.nameSingular, operationName, resultWithGettersArray, ); return resultWithGetters; } }