Aggregated queries #1 (#8345)

First step of https://github.com/twentyhq/twenty/issues/6868

Adds min.., max.. queries for DATETIME fields
adds min.., max.., avg.., sum.. queries for NUMBER fields 

(count distinct operation and composite fields such as CURRENCY handling
will be dealt with in a future PR)

<img width="1422" alt="Capture d’écran 2024-11-06 à 15 48 46"
src="https://github.com/user-attachments/assets/4bcdece0-ad3e-4536-9720-fe4044a36719">

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
Marie
2024-11-14 18:05:05 +01:00
committed by GitHub
parent c966533f26
commit a799370483
93 changed files with 1590 additions and 1178 deletions

View File

@ -4,7 +4,7 @@ import graphqlFields from 'graphql-fields';
import { In, InsertResult } from 'typeorm';
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { CreateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
@ -19,35 +19,40 @@ import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
@Injectable()
export class GraphqlQueryCreateManyResolverService
implements ResolverService<CreateManyResolverArgs, IRecord[]>
implements ResolverService<CreateManyResolverArgs, ObjectRecord[]>
{
constructor(
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {}
async resolve<ObjectRecord extends IRecord = IRecord>(
async resolve<T extends ObjectRecord = ObjectRecord>(
args: CreateManyResolverArgs<Partial<ObjectRecord>>,
options: WorkspaceQueryRunnerOptions,
): Promise<ObjectRecord[]> {
const { authContext, info, objectMetadataMap, objectMetadataMapItem } =
options;
): Promise<T[]> {
const {
authContext,
info,
objectMetadataMaps,
objectMetadataItemWithFieldMaps,
} = options;
const dataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
authContext.workspace.id,
);
const repository = dataSource.getRepository(
objectMetadataMapItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
);
const graphqlQueryParser = new GraphqlQueryParser(
objectMetadataMapItem.fields,
objectMetadataMap,
objectMetadataItemWithFieldMaps.fieldsByName,
objectMetadataMaps,
);
const selectedFields = graphqlFields(info);
const { relations } = graphqlQueryParser.parseSelectedFields(
objectMetadataMapItem,
objectMetadataItemWithFieldMaps,
selectedFields,
);
@ -59,7 +64,7 @@ export class GraphqlQueryCreateManyResolverService
});
const queryBuilder = repository.createQueryBuilder(
objectMetadataMapItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
);
const nonFormattedUpsertedRecords = (await queryBuilder
@ -71,42 +76,42 @@ export class GraphqlQueryCreateManyResolverService
const upsertedRecords = formatResult(
nonFormattedUpsertedRecords,
objectMetadataMapItem,
objectMetadataMap,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
);
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
if (relations) {
await processNestedRelationsHelper.processNestedRelations(
objectMetadataMap,
objectMetadataMapItem,
upsertedRecords,
await processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords: upsertedRecords,
relations,
QUERY_MAX_RECORDS,
limit: QUERY_MAX_RECORDS,
authContext,
dataSource,
);
});
}
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
return upsertedRecords.map((record: ObjectRecord) =>
return upsertedRecords.map((record: T) =>
typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName: objectMetadataMapItem.nameSingular,
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
}),
);
}
async validate<ObjectRecord extends IRecord>(
args: CreateManyResolverArgs<Partial<ObjectRecord>>,
async validate<T extends ObjectRecord>(
args: CreateManyResolverArgs<Partial<T>>,
options: WorkspaceQueryRunnerOptions,
): Promise<void> {
assertMutationNotOnRemoteObject(options.objectMetadataItem);
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
args.data.forEach((record) => {
if (record?.id) {

View File

@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields';
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { DestroyManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
@ -16,46 +16,51 @@ import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
@Injectable()
export class GraphqlQueryDestroyManyResolverService
implements ResolverService<DestroyManyResolverArgs, IRecord[]>
implements ResolverService<DestroyManyResolverArgs, ObjectRecord[]>
{
constructor(
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {}
async resolve<ObjectRecord extends IRecord = IRecord>(
async resolve<T extends ObjectRecord = ObjectRecord>(
args: DestroyManyResolverArgs,
options: WorkspaceQueryRunnerOptions,
): Promise<ObjectRecord[]> {
const { authContext, objectMetadataMapItem, objectMetadataMap, info } =
options;
): Promise<T[]> {
const {
authContext,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
info,
} = options;
const dataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
authContext.workspace.id,
);
const repository = dataSource.getRepository(
objectMetadataMapItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
);
const graphqlQueryParser = new GraphqlQueryParser(
objectMetadataMapItem.fields,
objectMetadataMap,
objectMetadataItemWithFieldMaps.fieldsByName,
objectMetadataMaps,
);
const selectedFields = graphqlFields(info);
const { relations } = graphqlQueryParser.parseSelectedFields(
objectMetadataMapItem,
objectMetadataItemWithFieldMaps,
selectedFields,
);
const queryBuilder = repository.createQueryBuilder(
objectMetadataMapItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
);
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
queryBuilder,
objectMetadataMapItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
args.filter,
);
@ -66,31 +71,31 @@ export class GraphqlQueryDestroyManyResolverService
const deletedRecords = formatResult(
nonFormattedDeletedObjectRecords.raw,
objectMetadataMapItem,
objectMetadataMap,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
);
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
if (relations) {
await processNestedRelationsHelper.processNestedRelations(
objectMetadataMap,
objectMetadataMapItem,
deletedRecords,
await processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords: deletedRecords,
relations,
QUERY_MAX_RECORDS,
limit: QUERY_MAX_RECORDS,
authContext,
dataSource,
);
});
}
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
return deletedRecords.map((record: ObjectRecord) =>
return deletedRecords.map((record: T) =>
typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName: objectMetadataMapItem.nameSingular,
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
}),

View File

@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields';
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { DestroyOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
@ -20,45 +20,50 @@ import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
@Injectable()
export class GraphqlQueryDestroyOneResolverService
implements ResolverService<DestroyOneResolverArgs, IRecord>
implements ResolverService<DestroyOneResolverArgs, ObjectRecord>
{
constructor(
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {}
async resolve<ObjectRecord extends IRecord = IRecord>(
async resolve<T extends ObjectRecord = ObjectRecord>(
args: DestroyOneResolverArgs,
options: WorkspaceQueryRunnerOptions,
): Promise<ObjectRecord> {
const { authContext, objectMetadataMapItem, objectMetadataMap, info } =
options;
): Promise<T> {
const {
authContext,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
info,
} = options;
const dataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
authContext.workspace.id,
);
const repository = dataSource.getRepository(
objectMetadataMapItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
);
const graphqlQueryParser = new GraphqlQueryParser(
objectMetadataMapItem.fields,
objectMetadataMap,
objectMetadataItemWithFieldMaps.fieldsByName,
objectMetadataMaps,
);
const selectedFields = graphqlFields(info);
const { relations } = graphqlQueryParser.parseSelectedFields(
objectMetadataMapItem,
objectMetadataItemWithFieldMaps,
selectedFields,
);
const queryBuilder = repository.createQueryBuilder(
objectMetadataMapItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
);
const nonFormattedDeletedObjectRecords = await queryBuilder
.where(`"${objectMetadataMapItem.nameSingular}".id = :id`, {
.where(`"${objectMetadataItemWithFieldMaps.nameSingular}".id = :id`, {
id: args.id,
})
.take(1)
@ -75,30 +80,30 @@ export class GraphqlQueryDestroyOneResolverService
const recordBeforeDeletion = formatResult(
nonFormattedDeletedObjectRecords.raw,
objectMetadataMapItem,
objectMetadataMap,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
)[0];
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
if (relations) {
await processNestedRelationsHelper.processNestedRelations(
objectMetadataMap,
objectMetadataMapItem,
[recordBeforeDeletion],
await processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords: [recordBeforeDeletion],
relations,
QUERY_MAX_RECORDS,
limit: QUERY_MAX_RECORDS,
authContext,
dataSource,
);
});
}
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
return typeORMObjectRecordsParser.processRecord({
objectRecord: recordBeforeDeletion,
objectName: objectMetadataMapItem.nameSingular,
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
});

View File

@ -5,10 +5,10 @@ import { In } from 'typeorm';
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
import {
Record as IRecord,
ObjectRecord,
ObjectRecordFilter,
OrderByDirection,
RecordFilter,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-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 { FindDuplicatesResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
@ -21,7 +21,7 @@ import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { settings } from 'src/engine/constants/settings';
import { DUPLICATE_CRITERIA_COLLECTION } from 'src/engine/core-modules/duplicate/constants/duplicate-criteria.constants';
import { ObjectMetadataMapItem } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
@ -29,60 +29,63 @@ import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
@Injectable()
export class GraphqlQueryFindDuplicatesResolverService
implements
ResolverService<FindDuplicatesResolverArgs, IConnection<IRecord>[]>
ResolverService<FindDuplicatesResolverArgs, IConnection<ObjectRecord>[]>
{
constructor(
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {}
async resolve<ObjectRecord extends IRecord = IRecord>(
args: FindDuplicatesResolverArgs<Partial<ObjectRecord>>,
async resolve<T extends ObjectRecord = ObjectRecord>(
args: FindDuplicatesResolverArgs<Partial<T>>,
options: WorkspaceQueryRunnerOptions,
): Promise<IConnection<ObjectRecord>[]> {
const { authContext, objectMetadataMapItem, objectMetadataMap } = options;
): Promise<IConnection<T>[]> {
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
options;
const dataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
authContext.workspace.id,
);
const repository = dataSource.getRepository(
objectMetadataMapItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
);
const existingRecordsQueryBuilder = repository.createQueryBuilder(
objectMetadataMapItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
);
const duplicateRecordsQueryBuilder = repository.createQueryBuilder(
objectMetadataMapItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
);
const graphqlQueryParser = new GraphqlQueryParser(
objectMetadataMap[objectMetadataMapItem.nameSingular].fields,
objectMetadataMap,
objectMetadataMaps.byNameSingular[
objectMetadataItemWithFieldMaps.nameSingular
].fieldsByName,
objectMetadataMaps,
);
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
let objectRecords: Partial<ObjectRecord>[] = [];
let objectRecords: Partial<T>[] = [];
if (args.ids) {
const nonFormattedObjectRecords = (await existingRecordsQueryBuilder
.where({ id: In(args.ids) })
.getMany()) as ObjectRecord[];
.getMany()) as T[];
objectRecords = formatResult(
nonFormattedObjectRecords,
objectMetadataMapItem,
objectMetadataMap,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
);
} else if (args.data && !isEmpty(args.data)) {
objectRecords = formatData(args.data, objectMetadataMapItem);
objectRecords = formatData(args.data, objectMetadataItemWithFieldMaps);
}
const duplicateConnections: IConnection<ObjectRecord>[] = await Promise.all(
const duplicateConnections: IConnection<T>[] = await Promise.all(
objectRecords.map(async (record) => {
const duplicateConditions = this.buildDuplicateConditions(
objectMetadataMapItem,
objectMetadataItemWithFieldMaps,
[record],
record.id,
);
@ -90,7 +93,7 @@ export class GraphqlQueryFindDuplicatesResolverService
if (isEmpty(duplicateConditions)) {
return typeORMObjectRecordsParser.createConnection({
objectRecords: [],
objectName: objectMetadataMapItem.nameSingular,
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 0,
totalCount: 0,
order: [{ id: OrderByDirection.AscNullsFirst }],
@ -101,22 +104,22 @@ export class GraphqlQueryFindDuplicatesResolverService
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
duplicateRecordsQueryBuilder,
objectMetadataMapItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
duplicateConditions,
);
const nonFormattedDuplicates =
(await withFilterQueryBuilder.getMany()) as ObjectRecord[];
(await withFilterQueryBuilder.getMany()) as T[];
const duplicates = formatResult(
nonFormattedDuplicates,
objectMetadataMapItem,
objectMetadataMap,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
);
return typeORMObjectRecordsParser.createConnection({
objectRecords: duplicates,
objectName: objectMetadataMapItem.nameSingular,
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: duplicates.length,
totalCount: duplicates.length,
order: [{ id: OrderByDirection.AscNullsFirst }],
@ -130,16 +133,16 @@ export class GraphqlQueryFindDuplicatesResolverService
}
private buildDuplicateConditions(
objectMetadataMapItem: ObjectMetadataMapItem,
records?: Partial<IRecord>[] | undefined,
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
records?: Partial<ObjectRecord>[] | undefined,
filteringByExistingRecordId?: string,
): Partial<RecordFilter> {
): Partial<ObjectRecordFilter> {
if (!records || records.length === 0) {
return {};
}
const criteriaCollection = this.getApplicableDuplicateCriteriaCollection(
objectMetadataMapItem,
objectMetadataItemWithFieldMaps,
);
const conditions = records.flatMap((record) => {
@ -164,7 +167,7 @@ export class GraphqlQueryFindDuplicatesResolverService
});
});
const filter: Partial<RecordFilter> = {};
const filter: Partial<ObjectRecordFilter> = {};
if (conditions && !isEmpty(conditions)) {
filter.or = conditions;
@ -178,11 +181,12 @@ export class GraphqlQueryFindDuplicatesResolverService
}
private getApplicableDuplicateCriteriaCollection(
objectMetadataMapItem: ObjectMetadataMapItem,
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
) {
return DUPLICATE_CRITERIA_COLLECTION.filter(
(duplicateCriteria) =>
duplicateCriteria.objectName === objectMetadataMapItem.nameSingular,
duplicateCriteria.objectName ===
objectMetadataItemWithFieldMaps.nameSingular,
);
}

View File

@ -1,15 +1,14 @@
import { Injectable } from '@nestjs/common';
import { isDefined } from 'class-validator';
import graphqlFields from 'graphql-fields';
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
import {
Record as IRecord,
ObjectRecord,
ObjectRecordFilter,
ObjectRecordOrderBy,
OrderByDirection,
RecordFilter,
RecordOrderBy,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-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';
@ -19,35 +18,45 @@ import {
GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode,
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
import { GraphqlQuerySelectedFieldsResult } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { ProcessAggregateHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper';
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
import { computeCursorArgFilter } from 'src/engine/api/graphql/graphql-query-runner/utils/compute-cursor-arg-filter';
import {
getCursor,
getPaginationInfo,
} from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
import { isDefined } from 'src/utils/is-defined';
@Injectable()
export class GraphqlQueryFindManyResolverService
implements ResolverService<FindManyResolverArgs, IConnection<IRecord>>
implements ResolverService<FindManyResolverArgs, IConnection<ObjectRecord>>
{
constructor(
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly featureFlagService: FeatureFlagService,
) {}
async resolve<
ObjectRecord extends IRecord = IRecord,
Filter extends RecordFilter = RecordFilter,
OrderBy extends RecordOrderBy = RecordOrderBy,
T extends ObjectRecord = ObjectRecord,
Filter extends ObjectRecordFilter = ObjectRecordFilter,
OrderBy extends ObjectRecordOrderBy = ObjectRecordOrderBy,
>(
args: FindManyResolverArgs<Filter, OrderBy>,
options: WorkspaceQueryRunnerOptions,
): Promise<IConnection<ObjectRecord>> {
const { authContext, objectMetadataMapItem, info, objectMetadataMap } =
options;
): Promise<IConnection<T>> {
const {
authContext,
objectMetadataItemWithFieldMaps,
info,
objectMetadataMaps,
} = options;
const dataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
@ -55,47 +64,43 @@ export class GraphqlQueryFindManyResolverService
);
const repository = dataSource.getRepository(
objectMetadataMapItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
);
const queryBuilder = repository.createQueryBuilder(
objectMetadataMapItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
);
const countQueryBuilder = repository.createQueryBuilder(
objectMetadataMapItem.nameSingular,
const aggregateQueryBuilder = repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular,
);
const graphqlQueryParser = new GraphqlQueryParser(
objectMetadataMapItem.fields,
objectMetadataMap,
objectMetadataItemWithFieldMaps.fieldsByName,
objectMetadataMaps,
);
const withFilterCountQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
countQueryBuilder,
objectMetadataMapItem.nameSingular,
args.filter ?? ({} as Filter),
);
const selectedFields = graphqlFields(info);
const { relations } = graphqlQueryParser.parseSelectedFields(
objectMetadataMapItem,
selectedFields,
);
const isForwardPagination = !isDefined(args.before);
const limit = args.first ?? args.last ?? QUERY_MAX_RECORDS;
const withDeletedCountQueryBuilder =
graphqlQueryParser.applyDeletedAtToBuilder(
withFilterCountQueryBuilder,
const withFilterAggregateQueryBuilder =
graphqlQueryParser.applyFilterToBuilder(
aggregateQueryBuilder,
objectMetadataItemWithFieldMaps.nameSingular,
args.filter ?? ({} as Filter),
);
const totalCount = isDefined(selectedFields.totalCount)
? await withDeletedCountQueryBuilder.getCount()
: 0;
const selectedFields = graphqlFields(info);
const graphqlQuerySelectedFieldsResult: GraphqlQuerySelectedFieldsResult =
graphqlQueryParser.parseSelectedFields(
objectMetadataItemWithFieldMaps,
selectedFields,
);
const isForwardPagination = !isDefined(args.before);
const withDeletedAggregateQueryBuilder =
graphqlQueryParser.applyDeletedAtToBuilder(
withFilterAggregateQueryBuilder,
args.filter ?? ({} as Filter),
);
const cursor = getCursor(args);
@ -110,7 +115,7 @@ export class GraphqlQueryFindManyResolverService
const cursorArgFilter = computeCursorArgFilter(
cursor,
orderByWithIdCondition,
objectMetadataMapItem.fields,
objectMetadataItemWithFieldMaps.fieldsByName,
isForwardPagination,
);
@ -123,14 +128,14 @@ export class GraphqlQueryFindManyResolverService
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
queryBuilder,
objectMetadataMapItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
appliedFilters,
);
const withOrderByQueryBuilder = graphqlQueryParser.applyOrderToBuilder(
withFilterQueryBuilder,
orderByWithIdCondition,
objectMetadataMapItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
isForwardPagination,
);
@ -139,14 +144,36 @@ export class GraphqlQueryFindManyResolverService
args.filter ?? ({} as Filter),
);
const isAggregationsEnabled =
await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IsAggregateQueryEnabled,
authContext.workspace.id,
);
if (!isAggregationsEnabled) {
graphqlQuerySelectedFieldsResult.aggregate = {
totalCount: graphqlQuerySelectedFieldsResult.aggregate.totalCount,
};
}
const processAggregateHelper = new ProcessAggregateHelper();
processAggregateHelper.addSelectedAggregatedFieldsQueriesToQueryBuilder({
fieldMetadataMapByName: objectMetadataItemWithFieldMaps.fieldsByName,
selectedAggregatedFields: graphqlQuerySelectedFieldsResult.aggregate,
queryBuilder: withDeletedAggregateQueryBuilder,
});
const limit = args.first ?? args.last ?? QUERY_MAX_RECORDS;
const nonFormattedObjectRecords = await withDeletedQueryBuilder
.take(limit + 1)
.getMany();
const objectRecords = formatResult(
nonFormattedObjectRecords,
objectMetadataMapItem,
objectMetadataMap,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
);
const { hasNextPage, hasPreviousPage } = getPaginationInfo(
@ -159,37 +186,42 @@ export class GraphqlQueryFindManyResolverService
objectRecords.pop();
}
const parentObjectRecordsAggregatedValues =
await withDeletedAggregateQueryBuilder.getRawOne();
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
if (relations) {
await processNestedRelationsHelper.processNestedRelations(
objectMetadataMap,
objectMetadataMapItem,
objectRecords,
relations,
if (graphqlQuerySelectedFieldsResult.relations) {
await processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords: objectRecords,
parentObjectRecordsAggregatedValues,
relations: graphqlQuerySelectedFieldsResult.relations,
aggregate: graphqlQuerySelectedFieldsResult.aggregate,
limit,
authContext,
dataSource,
);
});
}
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
const result = typeORMObjectRecordsParser.createConnection({
return typeORMObjectRecordsParser.createConnection({
objectRecords,
objectName: objectMetadataMapItem.nameSingular,
objectRecordsAggregatedValues: parentObjectRecordsAggregatedValues,
selectedAggregatedFields: graphqlQuerySelectedFieldsResult.aggregate,
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: limit,
totalCount,
totalCount: parentObjectRecordsAggregatedValues.totalCount,
order: orderByWithIdCondition,
hasNextPage,
hasPreviousPage,
});
return result;
}
async validate<Filter extends RecordFilter>(
async validate<Filter extends ObjectRecordFilter>(
args: FindManyResolverArgs<Filter>,
_options: WorkspaceQueryRunnerOptions,
): Promise<void> {

View File

@ -4,9 +4,9 @@ import graphqlFields from 'graphql-fields';
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
import {
Record as IRecord,
RecordFilter,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
ObjectRecord,
ObjectRecordFilter,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { FindOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
@ -27,21 +27,25 @@ import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
@Injectable()
export class GraphqlQueryFindOneResolverService
implements ResolverService<FindOneResolverArgs, IRecord>
implements ResolverService<FindOneResolverArgs, ObjectRecord>
{
constructor(
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {}
async resolve<
ObjectRecord extends IRecord = IRecord,
Filter extends RecordFilter = RecordFilter,
T extends ObjectRecord = ObjectRecord,
Filter extends ObjectRecordFilter = ObjectRecordFilter,
>(
args: FindOneResolverArgs<Filter>,
options: WorkspaceQueryRunnerOptions,
): Promise<ObjectRecord> {
const { authContext, objectMetadataMapItem, info, objectMetadataMap } =
options;
): Promise<T> {
const {
authContext,
objectMetadataItemWithFieldMaps,
info,
objectMetadataMaps,
} = options;
const dataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
@ -49,28 +53,28 @@ export class GraphqlQueryFindOneResolverService
);
const repository = dataSource.getRepository(
objectMetadataMapItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
);
const queryBuilder = repository.createQueryBuilder(
objectMetadataMapItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
);
const graphqlQueryParser = new GraphqlQueryParser(
objectMetadataMapItem.fields,
objectMetadataMap,
objectMetadataItemWithFieldMaps.fieldsByName,
objectMetadataMaps,
);
const selectedFields = graphqlFields(info);
const { relations } = graphqlQueryParser.parseSelectedFields(
objectMetadataMapItem,
objectMetadataItemWithFieldMaps,
selectedFields,
);
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
queryBuilder,
objectMetadataMapItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
args.filter ?? ({} as Filter),
);
@ -83,8 +87,8 @@ export class GraphqlQueryFindOneResolverService
const objectRecord = formatResult(
nonFormattedObjectRecord,
objectMetadataMapItem,
objectMetadataMap,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
);
if (!objectRecord) {
@ -99,29 +103,29 @@ export class GraphqlQueryFindOneResolverService
const objectRecords = [objectRecord];
if (relations) {
await processNestedRelationsHelper.processNestedRelations(
objectMetadataMap,
objectMetadataMapItem,
objectRecords,
await processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords: objectRecords,
relations,
QUERY_MAX_RECORDS,
limit: QUERY_MAX_RECORDS,
authContext,
dataSource,
);
});
}
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
return typeORMObjectRecordsParser.processRecord({
objectRecord: objectRecords[0],
objectName: objectMetadataMapItem.nameSingular,
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
}) as ObjectRecord;
}) as T;
}
async validate<Filter extends RecordFilter>(
async validate<Filter extends ObjectRecordFilter>(
args: FindOneResolverArgs<Filter>,
_options: WorkspaceQueryRunnerOptions,
): Promise<void> {

View File

@ -5,10 +5,10 @@ import { Brackets } from 'typeorm';
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
import {
Record as IRecord,
ObjectRecord,
ObjectRecordFilter,
OrderByDirection,
RecordFilter,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-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 { SearchResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
@ -22,40 +22,39 @@ import { isDefined } from 'src/utils/is-defined';
@Injectable()
export class GraphqlQuerySearchResolverService
implements ResolverService<SearchResolverArgs, IConnection<IRecord>>
implements ResolverService<SearchResolverArgs, IConnection<ObjectRecord>>
{
constructor(
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {}
async resolve<
ObjectRecord extends IRecord = IRecord,
Filter extends RecordFilter = RecordFilter,
T extends ObjectRecord = ObjectRecord,
Filter extends ObjectRecordFilter = ObjectRecordFilter,
>(
args: SearchResolverArgs,
options: WorkspaceQueryRunnerOptions,
): Promise<IConnection<ObjectRecord>> {
): Promise<IConnection<T>> {
const {
authContext,
objectMetadataItem,
objectMetadataMapItem,
objectMetadataMap,
objectMetadataMaps,
objectMetadataItemWithFieldMaps,
info,
} = options;
const repository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
authContext.workspace.id,
objectMetadataItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
);
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
if (!isDefined(args.searchInput)) {
return typeORMObjectRecordsParser.createConnection({
objectRecords: [],
objectName: objectMetadataItem.nameSingular,
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 0,
totalCount: 0,
order: [{ id: OrderByDirection.AscNullsFirst }],
@ -69,16 +68,16 @@ export class GraphqlQuerySearchResolverService
const limit = args?.limit ?? QUERY_MAX_RECORDS;
const queryBuilder = repository.createQueryBuilder(
objectMetadataItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
);
const graphqlQueryParser = new GraphqlQueryParser(
objectMetadataMapItem.fields,
objectMetadataMap,
objectMetadataItemWithFieldMaps.fieldsByName,
objectMetadataMaps,
);
const queryBuilderWithFilter = graphqlQueryParser.applyFilterToBuilder(
queryBuilder,
objectMetadataMapItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
args.filter ?? ({} as Filter),
);
@ -109,7 +108,7 @@ export class GraphqlQuerySearchResolverService
.setParameter('searchTerms', searchTerms)
.setParameter('searchTermsOr', searchTermsOr)
.take(limit)
.getMany()) as ObjectRecord[];
.getMany()) as T[];
const objectRecords = await repository.formatResult(resultsWithTsVector);
@ -122,7 +121,7 @@ export class GraphqlQuerySearchResolverService
return typeORMObjectRecordsParser.createConnection({
objectRecords: objectRecords ?? [],
objectName: objectMetadataItem.nameSingular,
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: limit,
totalCount,
order,

View File

@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields';
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { UpdateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
@ -20,18 +20,22 @@ import { computeTableName } from 'src/engine/utils/compute-table-name.util';
@Injectable()
export class GraphqlQueryUpdateManyResolverService
implements ResolverService<UpdateManyResolverArgs, IRecord[]>
implements ResolverService<UpdateManyResolverArgs, ObjectRecord[]>
{
constructor(
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {}
async resolve<ObjectRecord extends IRecord = IRecord>(
args: UpdateManyResolverArgs<Partial<ObjectRecord>>,
async resolve<T extends ObjectRecord = ObjectRecord>(
args: UpdateManyResolverArgs<Partial<T>>,
options: WorkspaceQueryRunnerOptions,
): Promise<ObjectRecord[]> {
const { authContext, objectMetadataMapItem, objectMetadataMap, info } =
options;
): Promise<T[]> {
const {
authContext,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
info,
} = options;
const dataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
@ -39,28 +43,28 @@ export class GraphqlQueryUpdateManyResolverService
);
const repository = dataSource.getRepository(
objectMetadataMapItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
);
const graphqlQueryParser = new GraphqlQueryParser(
objectMetadataMapItem.fields,
objectMetadataMap,
objectMetadataItemWithFieldMaps.fieldsByName,
objectMetadataMaps,
);
const selectedFields = graphqlFields(info);
const { relations } = graphqlQueryParser.parseSelectedFields(
objectMetadataMapItem,
objectMetadataItemWithFieldMaps,
selectedFields,
);
const queryBuilder = repository.createQueryBuilder(
objectMetadataMapItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
);
const tableName = computeTableName(
objectMetadataMapItem.nameSingular,
objectMetadataMapItem.isCustom,
objectMetadataItemWithFieldMaps.nameSingular,
objectMetadataItemWithFieldMaps.isCustom,
);
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
@ -69,7 +73,7 @@ export class GraphqlQueryUpdateManyResolverService
args.filter,
);
const data = formatData(args.data, objectMetadataMapItem);
const data = formatData(args.data, objectMetadataItemWithFieldMaps);
const nonFormattedUpdatedObjectRecords = await withFilterQueryBuilder
.update(data)
@ -78,42 +82,42 @@ export class GraphqlQueryUpdateManyResolverService
const updatedRecords = formatResult(
nonFormattedUpdatedObjectRecords.raw,
objectMetadataMapItem,
objectMetadataMap,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
);
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
if (relations) {
await processNestedRelationsHelper.processNestedRelations(
objectMetadataMap,
objectMetadataMapItem,
updatedRecords,
await processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords: updatedRecords,
relations,
QUERY_MAX_RECORDS,
limit: QUERY_MAX_RECORDS,
authContext,
dataSource,
);
});
}
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
return updatedRecords.map((record: ObjectRecord) =>
return updatedRecords.map((record: T) =>
typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName: objectMetadataMapItem.nameSingular,
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
}),
);
}
async validate<ObjectRecord extends IRecord = IRecord>(
args: UpdateManyResolverArgs<Partial<ObjectRecord>>,
async validate<T extends ObjectRecord = ObjectRecord>(
args: UpdateManyResolverArgs<Partial<T>>,
options: WorkspaceQueryRunnerOptions,
): Promise<void> {
assertMutationNotOnRemoteObject(options.objectMetadataMapItem);
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
if (!args.filter) {
throw new Error('Filter is required');
}

View File

@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields';
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { UpdateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
@ -23,18 +23,22 @@ import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
@Injectable()
export class GraphqlQueryUpdateOneResolverService
implements ResolverService<UpdateOneResolverArgs, IRecord>
implements ResolverService<UpdateOneResolverArgs, ObjectRecord>
{
constructor(
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {}
async resolve<ObjectRecord extends IRecord = IRecord>(
args: UpdateOneResolverArgs<Partial<ObjectRecord>>,
async resolve<T extends ObjectRecord = ObjectRecord>(
args: UpdateOneResolverArgs<Partial<T>>,
options: WorkspaceQueryRunnerOptions,
): Promise<ObjectRecord> {
const { authContext, objectMetadataMapItem, objectMetadataMap, info } =
options;
): Promise<T> {
const {
authContext,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
info,
} = options;
const dataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
@ -42,26 +46,26 @@ export class GraphqlQueryUpdateOneResolverService
);
const repository = dataSource.getRepository(
objectMetadataMapItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
);
const graphqlQueryParser = new GraphqlQueryParser(
objectMetadataMapItem.fields,
objectMetadataMap,
objectMetadataItemWithFieldMaps.fieldsByName,
objectMetadataMaps,
);
const selectedFields = graphqlFields(info);
const { relations } = graphqlQueryParser.parseSelectedFields(
objectMetadataMapItem,
objectMetadataItemWithFieldMaps,
selectedFields,
);
const queryBuilder = repository.createQueryBuilder(
objectMetadataMapItem.nameSingular,
objectMetadataItemWithFieldMaps.nameSingular,
);
const data = formatData(args.data, objectMetadataMapItem);
const data = formatData(args.data, objectMetadataItemWithFieldMaps);
const result = await queryBuilder
.update(data)
@ -73,8 +77,8 @@ export class GraphqlQueryUpdateOneResolverService
const updatedRecords = formatResult(
nonFormattedUpdatedObjectRecords,
objectMetadataMapItem,
objectMetadataMap,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
);
if (updatedRecords.length === 0) {
@ -84,38 +88,38 @@ export class GraphqlQueryUpdateOneResolverService
);
}
const updatedRecord = updatedRecords[0] as ObjectRecord;
const updatedRecord = updatedRecords[0] as T;
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
if (relations) {
await processNestedRelationsHelper.processNestedRelations(
objectMetadataMap,
objectMetadataMapItem,
[updatedRecord],
await processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords: [updatedRecord],
relations,
QUERY_MAX_RECORDS,
limit: QUERY_MAX_RECORDS,
authContext,
dataSource,
);
});
}
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
return typeORMObjectRecordsParser.processRecord<ObjectRecord>({
return typeORMObjectRecordsParser.processRecord<T>({
objectRecord: updatedRecord,
objectName: objectMetadataMapItem.nameSingular,
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
});
}
async validate<ObjectRecord extends IRecord = IRecord>(
args: UpdateOneResolverArgs<Partial<ObjectRecord>>,
async validate<T extends ObjectRecord = ObjectRecord>(
args: UpdateOneResolverArgs<Partial<T>>,
options: WorkspaceQueryRunnerOptions,
): Promise<void> {
assertMutationNotOnRemoteObject(options.objectMetadataMapItem);
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
assertIsValidUuid(args.id);
}
}