feat: add missing updateMany and deleteMany resolvers on flexible backend (#2758)

* feat: add missing updateMany and deleteMany resolvers on flexible backend

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Refactor according to review

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Update return types for `createMany`, `updateMany` and `deleteMany`

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

---------

Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com>
Co-authored-by: v1b3m <vibenjamin6@gmail.com>
This commit is contained in:
gitstart-twenty
2023-11-30 17:58:08 +05:45
committed by GitHub
parent 1ba062f40c
commit 1822370389
13 changed files with 273 additions and 2 deletions

View File

@@ -21,6 +21,10 @@ export const getResolverName = (
return `update${pascalCase(objectMetadata.nameSingular)}`;
case 'deleteOne':
return `delete${pascalCase(objectMetadata.nameSingular)}`;
case 'updateMany':
return `update${pascalCase(objectMetadata.namePlural)}`;
case 'deleteMany':
return `delete${pascalCase(objectMetadata.namePlural)}`;
default:
throw new Error(`Unknown resolver type: ${type}`);
}

View File

@@ -0,0 +1,33 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceQueryBuilderOptions } from 'src/workspace/workspace-query-builder/interfaces/workspace-query-builder-options.interface';
import { DeleteManyResolverArgs } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { stringifyWithoutKeyQuote } from 'src/workspace/workspace-query-builder/utils/stringify-without-key-quote.util';
import { FieldsStringFactory } from './fields-string.factory';
@Injectable()
export class DeleteManyQueryFactory {
constructor(private readonly fieldsStringFactory: FieldsStringFactory) {}
create(args: DeleteManyResolverArgs, options: WorkspaceQueryBuilderOptions) {
const fieldsString = this.fieldsStringFactory.create(
options.info,
options.fieldMetadataCollection,
);
return `
mutation {
deleteFrom${
options.targetTableName
}Collection(filter: ${stringifyWithoutKeyQuote(args.filter)}) {
affectedCount
records {
${fieldsString}
}
}
}
`;
}
}

View File

@@ -8,6 +8,8 @@ import { FieldsStringFactory } from './fields-string.factory';
import { FindManyQueryFactory } from './find-many-query.factory';
import { FindOneQueryFactory } from './find-one-query.factory';
import { UpdateOneQueryFactory } from './update-one-query.factory';
import { UpdateManyQueryFactory } from './update-many-query.factory';
import { DeleteManyQueryFactory } from './delete-many-query.factory';
export const workspaceQueryBuilderFactories = [
ArgsAliasFactory,
@@ -20,4 +22,6 @@ export const workspaceQueryBuilderFactories = [
FindManyQueryFactory,
FindOneQueryFactory,
UpdateOneQueryFactory,
UpdateManyQueryFactory,
DeleteManyQueryFactory,
];

View File

@@ -0,0 +1,51 @@
import { Injectable } from '@nestjs/common';
import {
Record as IRecord,
RecordFilter,
} from 'src/workspace/workspace-query-builder/interfaces/record.interface';
import { WorkspaceQueryBuilderOptions } from 'src/workspace/workspace-query-builder/interfaces/workspace-query-builder-options.interface';
import { UpdateManyResolverArgs } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { stringifyWithoutKeyQuote } from 'src/workspace/workspace-query-builder/utils/stringify-without-key-quote.util';
import { FieldsStringFactory } from 'src/workspace/workspace-query-builder/factories/fields-string.factory';
import { ArgsAliasFactory } from 'src/workspace/workspace-query-builder/factories/args-alias.factory';
@Injectable()
export class UpdateManyQueryFactory {
constructor(
private readonly fieldsStringFactory: FieldsStringFactory,
private readonly argsAliasFactory: ArgsAliasFactory,
) {}
create<
Record extends IRecord = IRecord,
Filter extends RecordFilter = RecordFilter,
>(
args: UpdateManyResolverArgs<Record, Filter>,
options: WorkspaceQueryBuilderOptions,
) {
const fieldsString = this.fieldsStringFactory.create(
options.info,
options.fieldMetadataCollection,
);
const computedArgs = this.argsAliasFactory.create(
args,
options.fieldMetadataCollection,
);
return `
mutation {
update${options.targetTableName}Collection(
set: ${stringifyWithoutKeyQuote(computedArgs.data)},
filter: ${stringifyWithoutKeyQuote(args.filter)},
) {
affectedCount
records {
${fieldsString}
}
}
}`;
}
}

View File

@@ -12,6 +12,8 @@ import {
CreateManyResolverArgs,
UpdateOneResolverArgs,
DeleteOneResolverArgs,
UpdateManyResolverArgs,
DeleteManyResolverArgs,
} from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { FindManyQueryFactory } from './factories/find-many-query.factory';
@@ -19,6 +21,8 @@ import { FindOneQueryFactory } from './factories/find-one-query.factory';
import { CreateManyQueryFactory } from './factories/create-many-query.factory';
import { UpdateOneQueryFactory } from './factories/update-one-query.factory';
import { DeleteOneQueryFactory } from './factories/delete-one-query.factory';
import { UpdateManyQueryFactory } from './factories/update-many-query.factory';
import { DeleteManyQueryFactory } from './factories/delete-many-query.factory';
@Injectable()
export class WorkspaceQueryBuilderFactory {
@@ -30,6 +34,8 @@ export class WorkspaceQueryBuilderFactory {
private readonly createManyQueryFactory: CreateManyQueryFactory,
private readonly updateOneQueryFactory: UpdateOneQueryFactory,
private readonly deleteOneQueryFactory: DeleteOneQueryFactory,
private readonly updateManyQueryFactory: UpdateManyQueryFactory,
private readonly deleteManyQueryFactory: DeleteManyQueryFactory,
) {}
findMany<
@@ -69,4 +75,21 @@ export class WorkspaceQueryBuilderFactory {
): string {
return this.deleteOneQueryFactory.create(args, options);
}
updateMany<
Record extends IRecord = IRecord,
Filter extends RecordFilter = RecordFilter,
>(
args: UpdateManyResolverArgs<Record, Filter>,
options: WorkspaceQueryBuilderOptions,
): string {
return this.updateManyQueryFactory.create(args, options);
}
deleteMany<Filter extends RecordFilter = RecordFilter>(
args: DeleteManyResolverArgs<Filter>,
options: WorkspaceQueryBuilderOptions,
): string {
return this.deleteManyQueryFactory.create(args, options);
}
}

View File

@@ -9,9 +9,11 @@ import {
import {
CreateManyResolverArgs,
CreateOneResolverArgs,
DeleteManyResolverArgs,
DeleteOneResolverArgs,
FindManyResolverArgs,
FindOneResolverArgs,
UpdateManyResolverArgs,
UpdateOneResolverArgs,
} from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
@@ -127,6 +129,43 @@ export class WorkspaceQueryRunnerService {
)?.records?.[0];
}
async updateMany<Record extends IRecord = IRecord>(
args: UpdateManyResolverArgs<Record>,
options: WorkspaceQueryRunnerOptions,
): Promise<Record[] | undefined> {
const { workspaceId, targetTableName } = options;
const query = this.workspaceQueryBuilderFactory.updateMany(args, options);
const result = await this.execute(query, workspaceId);
return this.parseResult<PGGraphQLMutation<Record>>(
result,
targetTableName,
'update',
)?.records;
}
async deleteMany<
Record extends IRecord = IRecord,
Filter extends RecordFilter = RecordFilter,
>(
args: DeleteManyResolverArgs<Filter>,
options: WorkspaceQueryRunnerOptions,
): Promise<Record[] | undefined> {
const { workspaceId, targetTableName } = options;
const query = this.workspaceQueryBuilderFactory.deleteMany(args, options);
const result = await this.execute(query, workspaceId);
return this.parseResult<PGGraphQLMutation<Record>>(
result,
targetTableName,
'deleteFrom',
)?.records;
}
private async execute(
query: string,
workspaceId: string,

View File

@@ -0,0 +1,36 @@
import { Injectable } from '@nestjs/common';
import {
DeleteManyResolverArgs,
Resolver,
} from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { WorkspaceSchemaBuilderContext } from 'src/workspace/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { WorkspaceResolverBuilderFactoryInterface } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
import { WorkspaceQueryRunnerService } from 'src/workspace/workspace-query-runner/workspace-query-runner.service';
@Injectable()
export class DeleteManyResolverFactory
implements WorkspaceResolverBuilderFactoryInterface
{
public static methodName = 'deleteMany' as const;
constructor(
private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService,
) {}
create(
context: WorkspaceSchemaBuilderContext,
): Resolver<DeleteManyResolverArgs> {
const internalContext = context;
return (_source, args, context, info) => {
return this.workspaceQueryRunnerService.deleteMany(args, {
targetTableName: internalContext.targetTableName,
workspaceId: internalContext.workspaceId,
info,
fieldMetadataCollection: internalContext.fieldMetadataCollection,
});
};
}
}

View File

@@ -1,9 +1,12 @@
import { UpdateManyResolverFactory } from 'src/workspace/workspace-resolver-builder/factories/update-many-resolver.factory';
import { FindManyResolverFactory } from './find-many-resolver.factory';
import { FindOneResolverFactory } from './find-one-resolver.factory';
import { CreateManyResolverFactory } from './create-many-resolver.factory';
import { CreateOneResolverFactory } from './create-one-resolver.factory';
import { UpdateOneResolverFactory } from './update-one-resolver.factory';
import { DeleteOneResolverFactory } from './delete-one-resolver.factory';
import { DeleteManyResolverFactory } from './delete-many-resolver.factory';
export const workspaceResolverBuilderFactories = [
FindManyResolverFactory,
@@ -12,6 +15,8 @@ export const workspaceResolverBuilderFactories = [
CreateOneResolverFactory,
UpdateOneResolverFactory,
DeleteOneResolverFactory,
UpdateManyResolverFactory,
DeleteManyResolverFactory,
];
export const workspaceResolverBuilderMethodNames = {
@@ -24,5 +29,7 @@ export const workspaceResolverBuilderMethodNames = {
CreateOneResolverFactory.methodName,
UpdateOneResolverFactory.methodName,
DeleteOneResolverFactory.methodName,
UpdateManyResolverFactory.methodName,
DeleteManyResolverFactory.methodName,
],
} as const;

View File

@@ -0,0 +1,36 @@
import { Injectable } from '@nestjs/common';
import {
Resolver,
UpdateManyResolverArgs,
} from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { WorkspaceSchemaBuilderContext } from 'src/workspace/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { WorkspaceResolverBuilderFactoryInterface } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
import { WorkspaceQueryRunnerService } from 'src/workspace/workspace-query-runner/workspace-query-runner.service';
@Injectable()
export class UpdateManyResolverFactory
implements WorkspaceResolverBuilderFactoryInterface
{
public static methodName = 'updateMany' as const;
constructor(
private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService,
) {}
create(
context: WorkspaceSchemaBuilderContext,
): Resolver<UpdateManyResolverArgs> {
const internalContext = context;
return (_source, args, context, info) => {
return this.workspaceQueryRunnerService.updateMany(args, {
targetTableName: internalContext.targetTableName,
workspaceId: internalContext.workspaceId,
info,
fieldMetadataCollection: internalContext.fieldMetadataCollection,
});
};
}
}

View File

@@ -39,10 +39,22 @@ export interface UpdateOneResolverArgs<Data extends Record = Record> {
data: Data;
}
export interface UpdateManyResolverArgs<
Data extends Record = Record,
Filter = any,
> {
filter: Filter;
data: Data;
}
export interface DeleteOneResolverArgs {
id: string;
}
export interface DeleteManyResolverArgs<Filter = any> {
filter: Filter;
}
export type WorkspaceResolverBuilderQueryMethodNames =
(typeof workspaceResolverBuilderMethodNames.queries)[number];

View File

@@ -5,6 +5,8 @@ import { IResolvers } from '@graphql-tools/utils';
import { ObjectMetadataInterface } from 'src/workspace/workspace-schema-builder/interfaces/object-metadata.interface';
import { getResolverName } from 'src/workspace/utils/get-resolver-name.util';
import { UpdateManyResolverFactory } from 'src/workspace/workspace-resolver-builder/factories/update-many-resolver.factory';
import { DeleteManyResolverFactory } from 'src/workspace/workspace-resolver-builder/factories/delete-many-resolver.factory';
import { FindManyResolverFactory } from './factories/find-many-resolver.factory';
import { FindOneResolverFactory } from './factories/find-one-resolver.factory';
@@ -29,6 +31,8 @@ export class WorkspaceResolverFactory {
private readonly createOneResolverFactory: CreateOneResolverFactory,
private readonly updateOneResolverFactory: UpdateOneResolverFactory,
private readonly deleteOneResolverFactory: DeleteOneResolverFactory,
private readonly updateManyResolverFactory: UpdateManyResolverFactory,
private readonly deleteManyResolverFactory: DeleteManyResolverFactory,
) {}
async create(
@@ -46,6 +50,8 @@ export class WorkspaceResolverFactory {
['createOne', this.createOneResolverFactory],
['updateOne', this.updateOneResolverFactory],
['deleteOne', this.deleteOneResolverFactory],
['updateMany', this.updateManyResolverFactory],
['deleteMany', this.deleteManyResolverFactory],
]);
const resolvers: IResolvers = {
Query: {},

View File

@@ -9,6 +9,7 @@ import { ObjectMetadataInterface } from 'src/workspace/workspace-schema-builder/
import { TypeDefinitionsStorage } from 'src/workspace/workspace-schema-builder/storages/type-definitions.storage';
import { getResolverName } from 'src/workspace/utils/get-resolver-name.util';
import { getResolverArgs } from 'src/workspace/workspace-schema-builder/utils/get-resolver-args.util';
import { TypeMapperService } from 'src/workspace/workspace-schema-builder/services/type-mapper.service';
import { ArgsFactory } from './args.factory';
import { ObjectTypeDefinitionKind } from './object-type-definition.factory';
@@ -25,6 +26,7 @@ export class RootTypeFactory {
constructor(
private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
private readonly typeMapperService: TypeMapperService,
private readonly argsFactory: ArgsFactory,
) {}
@@ -70,7 +72,7 @@ export class RootTypeFactory {
for (const methodName of workspaceResolverMethodNames) {
const name = getResolverName(objectMetadata, methodName);
const args = getResolverArgs(methodName);
const outputType = this.typeDefinitionsStorage.getObjectTypeByKey(
const objectType = this.typeDefinitionsStorage.getObjectTypeByKey(
objectMetadata.id,
methodName === 'findMany'
? ObjectTypeDefinitionKind.Connection
@@ -84,7 +86,7 @@ export class RootTypeFactory {
options,
);
if (!outputType) {
if (!objectType) {
this.logger.error(
`Could not find a GraphQL type for ${objectMetadata.id} for method ${methodName}`,
{
@@ -99,6 +101,12 @@ export class RootTypeFactory {
);
}
const outputType = this.typeMapperService.mapToGqlType(objectType, {
isArray: ['updateMany', 'deleteMany', 'createMany'].includes(
methodName,
),
});
fieldConfigMap[name] = {
type: outputType,
args: argsType,

View File

@@ -36,6 +36,7 @@ export const getResolverArgs = (
},
};
case 'findOne':
case 'deleteMany':
return {
filter: {
kind: InputTypeDefinitionKind.Filter,
@@ -75,6 +76,17 @@ export const getResolverArgs = (
isNullable: false,
},
};
case 'updateMany':
return {
data: {
kind: InputTypeDefinitionKind.Update,
isNullable: false,
},
filter: {
kind: InputTypeDefinitionKind.Filter,
isNullable: false,
},
};
default:
throw new Error(`Unknown resolver type: ${type}`);
}