[POC] add graphql query runner (#6747)
## Context The goal is to replace pg_graphql with our own ORM wrapper (TwentyORM). This PR tries to add some parsing logic to convert graphql requests to send to the ORM to replace pg_graphql implementation. --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -0,0 +1,130 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import graphqlFields from 'graphql-fields';
|
||||
import { FindManyOptions, ObjectLiteral } from 'typeorm';
|
||||
|
||||
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 { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||
import { FindManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { QUERY_MAX_RECORDS } from 'src/engine/api/graphql/graphql-query-runner/constants/query-max-records.constant';
|
||||
import {
|
||||
GraphqlQueryRunnerException,
|
||||
GraphqlQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/parsers/graphql-query.parser';
|
||||
import { applyRangeFilter } from 'src/engine/api/graphql/graphql-query-runner/utils/apply-range-filter.util';
|
||||
import {
|
||||
createConnection,
|
||||
decodeCursor,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/utils/connection.util';
|
||||
import { convertObjectMetadataToMap } from 'src/engine/api/graphql/graphql-query-runner/utils/convert-object-metadata-to-map.util';
|
||||
import { LogExecutionTime } from 'src/engine/decorators/observability/log-execution-time.decorator';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQueryRunnerService {
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
@LogExecutionTime()
|
||||
async findManyWithTwentyOrm<
|
||||
ObjectRecord extends IRecord = IRecord,
|
||||
Filter extends RecordFilter = RecordFilter,
|
||||
OrderBy extends RecordOrderBy = RecordOrderBy,
|
||||
>(
|
||||
args: FindManyResolverArgs<Filter, OrderBy>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<IConnection<ObjectRecord>> {
|
||||
const { authContext, objectMetadataItem, info, objectMetadataCollection } =
|
||||
options;
|
||||
|
||||
const repository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||
authContext.workspace.id,
|
||||
objectMetadataItem.nameSingular,
|
||||
);
|
||||
|
||||
const selectedFields = graphqlFields(info);
|
||||
|
||||
const objectMetadataMap = convertObjectMetadataToMap(
|
||||
objectMetadataCollection,
|
||||
);
|
||||
|
||||
const objectMetadata = objectMetadataMap[objectMetadataItem.nameSingular];
|
||||
|
||||
if (!objectMetadata) {
|
||||
throw new Error(
|
||||
`Object metadata for ${objectMetadataItem.nameSingular} not found`,
|
||||
);
|
||||
}
|
||||
|
||||
const fieldMetadataMap = objectMetadata.fields;
|
||||
|
||||
const graphqlQueryParser = new GraphqlQueryParser(
|
||||
fieldMetadataMap,
|
||||
objectMetadataMap,
|
||||
);
|
||||
|
||||
const { select, relations } = graphqlQueryParser.parseSelectedFields(
|
||||
objectMetadataItem,
|
||||
selectedFields,
|
||||
);
|
||||
|
||||
const order = args.orderBy
|
||||
? graphqlQueryParser.parseOrder(args.orderBy)
|
||||
: undefined;
|
||||
|
||||
const where = args.filter
|
||||
? graphqlQueryParser.parseFilter(args.filter)
|
||||
: {};
|
||||
|
||||
let cursor: Record<string, any> | undefined;
|
||||
|
||||
if (args.after) {
|
||||
cursor = decodeCursor(args.after);
|
||||
} else if (args.before) {
|
||||
cursor = decodeCursor(args.before);
|
||||
}
|
||||
|
||||
if (args.first && args.last) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
'Cannot provide both first and last',
|
||||
GraphqlQueryRunnerExceptionCode.ARGS_CONFLICT,
|
||||
);
|
||||
}
|
||||
|
||||
const take = args.first ?? args.last ?? QUERY_MAX_RECORDS;
|
||||
|
||||
const findOptions: FindManyOptions<ObjectLiteral> = {
|
||||
where,
|
||||
order,
|
||||
select,
|
||||
relations,
|
||||
take,
|
||||
};
|
||||
|
||||
const totalCount = await repository.count({
|
||||
where,
|
||||
});
|
||||
|
||||
if (cursor) {
|
||||
applyRangeFilter(where, order, cursor);
|
||||
}
|
||||
|
||||
const objectRecords = await repository.find(findOptions);
|
||||
|
||||
return createConnection(
|
||||
(objectRecords as ObjectRecord[]) ?? [],
|
||||
take,
|
||||
totalCount,
|
||||
order,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user