Serverless function UI (#6388)
https://www.figma.com/design/xt8O9mFeLl46C5InWwoMrN/Twenty?node-id=36235-120877 Did not do the file manager part. A Function is defined using one unique file at the moment Feature protected by featureFlag `IS_FUNCTION_SETTINGS_ENABLED` ## Demo https://github.com/user-attachments/assets/0acb8291-47b4-4521-a6fa-a88b9198609b
This commit is contained in:
@ -0,0 +1,16 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
@InputType()
|
||||
export class CreateServerlessFunctionFromFileInput {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Field()
|
||||
name: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
description?: string;
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
import { CreateServerlessFunctionFromFileInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function-from-file.input';
|
||||
|
||||
@InputType()
|
||||
export class CreateServerlessFunctionInput extends CreateServerlessFunctionFromFileInput {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Field()
|
||||
code: string;
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import { ID, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
@InputType()
|
||||
export class DeleteServerlessFunctionInput {
|
||||
@IDField(() => ID, { description: 'The id of the function.' })
|
||||
id!: string;
|
||||
}
|
||||
@ -1,14 +1,18 @@
|
||||
import { ArgsType, Field } from '@nestjs/graphql';
|
||||
|
||||
import { IsNotEmpty, IsObject, IsOptional, IsString } from 'class-validator';
|
||||
import { IsNotEmpty, IsObject, IsOptional, IsUUID } from 'class-validator';
|
||||
import graphqlTypeJson from 'graphql-type-json';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@ArgsType()
|
||||
export class ExecuteServerlessFunctionInput {
|
||||
@Field({ description: 'Name of the serverless function to execute' })
|
||||
@Field(() => UUIDScalarType, {
|
||||
description: 'Id of the serverless function to execute',
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
name: string;
|
||||
@IsUUID()
|
||||
id: string;
|
||||
|
||||
@Field(() => graphqlTypeJson, {
|
||||
description: 'Payload in JSON format',
|
||||
|
||||
@ -4,7 +4,7 @@ import { IsObject } from 'class-validator';
|
||||
import graphqlTypeJson from 'graphql-type-json';
|
||||
|
||||
@ObjectType('ServerlessFunctionExecutionResult')
|
||||
export class ServerlessFunctionExecutionResultDTO {
|
||||
export class ServerlessFunctionExecutionResultDto {
|
||||
@IsObject()
|
||||
@Field(() => graphqlTypeJson, {
|
||||
description: 'Execution result in JSON format',
|
||||
@ -26,7 +26,7 @@ registerEnumType(ServerlessFunctionSyncStatus, {
|
||||
description: 'SyncStatus of the serverlessFunction',
|
||||
});
|
||||
|
||||
@ObjectType('serverlessFunction')
|
||||
@ObjectType('ServerlessFunction')
|
||||
@Authorize({
|
||||
authorize: (context: any) => ({
|
||||
workspaceId: { eq: context?.req?.user?.workspace?.id },
|
||||
@ -47,11 +47,25 @@ export class ServerlessFunctionDto {
|
||||
@Field()
|
||||
name: string;
|
||||
|
||||
@IsString()
|
||||
@Field()
|
||||
description: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Field()
|
||||
sourceCodeHash: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Field()
|
||||
sourceCodeFullPath: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Field()
|
||||
runtime: string;
|
||||
|
||||
@IsEnum(ServerlessFunctionSyncStatus)
|
||||
@IsNotEmpty()
|
||||
@Field(() => ServerlessFunctionSyncStatus)
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsNotEmpty, IsString, IsUUID } from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@InputType()
|
||||
export class UpdateServerlessFunctionInput {
|
||||
@Field(() => UUIDScalarType, {
|
||||
description: 'Id of the serverless function to execute',
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsUUID()
|
||||
id: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Field()
|
||||
name: string;
|
||||
|
||||
@IsString()
|
||||
@Field({ nullable: true })
|
||||
description?: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Field()
|
||||
code: string;
|
||||
}
|
||||
@ -12,6 +12,10 @@ export enum ServerlessFunctionSyncStatus {
|
||||
READY = 'READY',
|
||||
}
|
||||
|
||||
export enum ServerlessFunctionRuntime {
|
||||
NODE18 = 'nodejs18.x',
|
||||
}
|
||||
|
||||
@Entity('serverlessFunction')
|
||||
@Unique('IndexOnNameAndWorkspaceIdUnique', ['name', 'workspaceId'])
|
||||
export class ServerlessFunctionEntity {
|
||||
@ -21,9 +25,18 @@ export class ServerlessFunctionEntity {
|
||||
@Column({ nullable: false })
|
||||
name: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
description: string;
|
||||
|
||||
@Column({ nullable: false })
|
||||
sourceCodeHash: string;
|
||||
|
||||
@Column({ nullable: false })
|
||||
sourceCodeFullPath: string;
|
||||
|
||||
@Column({ nullable: false, default: ServerlessFunctionRuntime.NODE18 })
|
||||
runtime: ServerlessFunctionRuntime;
|
||||
|
||||
@Column({
|
||||
nullable: false,
|
||||
default: ServerlessFunctionSyncStatus.NOT_READY,
|
||||
|
||||
@ -9,6 +9,7 @@ export class ServerlessFunctionException extends CustomException {
|
||||
|
||||
export enum ServerlessFunctionExceptionCode {
|
||||
SERVERLESS_FUNCTION_NOT_FOUND = 'SERVERLESS_FUNCTION_NOT_FOUND',
|
||||
FEATURE_FLAG_INVALID = 'FEATURE_FLAG_INVALID',
|
||||
SERVERLESS_FUNCTION_ALREADY_EXIST = 'SERVERLESS_FUNCTION_ALREADY_EXIST',
|
||||
SERVERLESS_FUNCTION_NOT_READY = 'SERVERLESS_FUNCTION_NOT_READY',
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import {
|
||||
NestjsQueryGraphQLModule,
|
||||
@ -14,6 +15,7 @@ import { ServerlessFunctionResolver } from 'src/engine/metadata-modules/serverle
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { ServerlessFunctionDto } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function.dto';
|
||||
import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module';
|
||||
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -24,6 +26,7 @@ import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-
|
||||
[ServerlessFunctionEntity],
|
||||
'metadata',
|
||||
),
|
||||
TypeOrmModule.forFeature([FeatureFlagEntity], 'core'),
|
||||
],
|
||||
services: [ServerlessFunctionService],
|
||||
resolvers: [
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { FileUpload, GraphQLUpload } from 'graphql-upload';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
||||
@ -9,45 +11,136 @@ import { ExecuteServerlessFunctionInput } from 'src/engine/metadata-modules/serv
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { ServerlessFunctionDto } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function.dto';
|
||||
import { ServerlessFunctionExecutionResultDTO } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function-execution-result-d-t.o';
|
||||
import { ServerlessFunctionExecutionResultDto } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function-execution-result.dto';
|
||||
import { serverlessFunctionGraphQLApiExceptionHandler } from 'src/engine/metadata-modules/serverless-function/utils/serverless-function-graphql-api-exception-handler.utils';
|
||||
import { CreateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function.input';
|
||||
import { CreateServerlessFunctionFromFileInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function-from-file.input';
|
||||
import { UpdateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/update-serverless-function.input';
|
||||
import { DeleteServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/delete-serverless-function.input';
|
||||
import {
|
||||
FeatureFlagEntity,
|
||||
FeatureFlagKeys,
|
||||
} from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import {
|
||||
ServerlessFunctionException,
|
||||
ServerlessFunctionExceptionCode,
|
||||
} from 'src/engine/metadata-modules/serverless-function/serverless-function.exception';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Resolver()
|
||||
export class ServerlessFunctionResolver {
|
||||
constructor(
|
||||
private readonly serverlessFunctionService: ServerlessFunctionService,
|
||||
@InjectRepository(FeatureFlagEntity, 'core')
|
||||
private readonly featureFlagRepository: Repository<FeatureFlagEntity>,
|
||||
) {}
|
||||
|
||||
async checkFeatureFlag(workspaceId: string) {
|
||||
const isFunctionSettingsEnabled =
|
||||
await this.featureFlagRepository.findOneBy({
|
||||
workspaceId,
|
||||
key: FeatureFlagKeys.IsFunctionSettingsEnabled,
|
||||
value: true,
|
||||
});
|
||||
|
||||
if (!isFunctionSettingsEnabled) {
|
||||
throw new ServerlessFunctionException(
|
||||
`IS_FUNCTION_SETTINGS_ENABLED feature flag is not set to true for this workspace`,
|
||||
ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Mutation(() => ServerlessFunctionDto)
|
||||
async createOneServerlessFunction(
|
||||
@Args({ name: 'file', type: () => GraphQLUpload })
|
||||
file: FileUpload,
|
||||
@Args('name', { type: () => String }) name: string,
|
||||
async deleteOneServerlessFunction(
|
||||
@Args('input') input: DeleteServerlessFunctionInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
try {
|
||||
return await this.serverlessFunctionService.createOne(
|
||||
name,
|
||||
await this.checkFeatureFlag(workspaceId);
|
||||
|
||||
return await this.serverlessFunctionService.deleteOneServerlessFunction(
|
||||
input.id,
|
||||
workspaceId,
|
||||
file,
|
||||
);
|
||||
} catch (error) {
|
||||
serverlessFunctionGraphQLApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
@Mutation(() => ServerlessFunctionExecutionResultDTO)
|
||||
@Mutation(() => ServerlessFunctionDto)
|
||||
async updateOneServerlessFunction(
|
||||
@Args('input')
|
||||
input: UpdateServerlessFunctionInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
try {
|
||||
await this.checkFeatureFlag(workspaceId);
|
||||
|
||||
return await this.serverlessFunctionService.updateOneServerlessFunction(
|
||||
input,
|
||||
workspaceId,
|
||||
);
|
||||
} catch (error) {
|
||||
serverlessFunctionGraphQLApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
@Mutation(() => ServerlessFunctionDto)
|
||||
async createOneServerlessFunction(
|
||||
@Args('input')
|
||||
input: CreateServerlessFunctionInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
try {
|
||||
await this.checkFeatureFlag(workspaceId);
|
||||
|
||||
return await this.serverlessFunctionService.createOneServerlessFunction(
|
||||
{
|
||||
name: input.name,
|
||||
description: input.description,
|
||||
},
|
||||
input.code,
|
||||
workspaceId,
|
||||
);
|
||||
} catch (error) {
|
||||
serverlessFunctionGraphQLApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
@Mutation(() => ServerlessFunctionDto)
|
||||
async createOneServerlessFunctionFromFile(
|
||||
@Args({ name: 'file', type: () => GraphQLUpload })
|
||||
file: FileUpload,
|
||||
@Args('input')
|
||||
input: CreateServerlessFunctionFromFileInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
try {
|
||||
await this.checkFeatureFlag(workspaceId);
|
||||
|
||||
return await this.serverlessFunctionService.createOneServerlessFunction(
|
||||
input,
|
||||
file,
|
||||
workspaceId,
|
||||
);
|
||||
} catch (error) {
|
||||
serverlessFunctionGraphQLApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
@Mutation(() => ServerlessFunctionExecutionResultDto)
|
||||
async executeOneServerlessFunction(
|
||||
@Args() executeServerlessFunctionInput: ExecuteServerlessFunctionInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
try {
|
||||
const { name, payload } = executeServerlessFunctionInput;
|
||||
await this.checkFeatureFlag(workspaceId);
|
||||
const { id, payload } = executeServerlessFunctionInput;
|
||||
|
||||
return {
|
||||
result: await this.serverlessFunctionService.executeOne(
|
||||
name,
|
||||
id,
|
||||
workspaceId,
|
||||
payload,
|
||||
),
|
||||
|
||||
@ -5,6 +5,8 @@ import { join } from 'path';
|
||||
|
||||
import { FileUpload } from 'graphql-upload';
|
||||
import { Repository } from 'typeorm';
|
||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
|
||||
@ -21,24 +23,28 @@ import { readFileContent } from 'src/engine/integrations/file-storage/utils/read
|
||||
import { FileStorageService } from 'src/engine/integrations/file-storage/file-storage.service';
|
||||
import { SOURCE_FILE_NAME } from 'src/engine/integrations/serverless/drivers/constants/source-file-name';
|
||||
import { serverlessFunctionCreateHash } from 'src/engine/metadata-modules/serverless-function/utils/serverless-function-create-hash.utils';
|
||||
import { CreateServerlessFunctionFromFileInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function-from-file.input';
|
||||
import { UpdateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/update-serverless-function.input';
|
||||
|
||||
@Injectable()
|
||||
export class ServerlessFunctionService {
|
||||
export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFunctionEntity> {
|
||||
constructor(
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
private readonly serverlessService: ServerlessService,
|
||||
@InjectRepository(ServerlessFunctionEntity, 'metadata')
|
||||
private readonly serverlessFunctionRepository: Repository<ServerlessFunctionEntity>,
|
||||
) {}
|
||||
) {
|
||||
super(serverlessFunctionRepository);
|
||||
}
|
||||
|
||||
async executeOne(
|
||||
name: string,
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
payload: object | undefined = undefined,
|
||||
) {
|
||||
const functionToExecute = await this.serverlessFunctionRepository.findOne({
|
||||
where: {
|
||||
name,
|
||||
id,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
@ -62,14 +68,82 @@ export class ServerlessFunctionService {
|
||||
return this.serverlessService.execute(functionToExecute, payload);
|
||||
}
|
||||
|
||||
async createOne(
|
||||
name: string,
|
||||
async deleteOneServerlessFunction(id: string, workspaceId: string) {
|
||||
const existingServerlessFunction =
|
||||
await this.serverlessFunctionRepository.findOne({
|
||||
where: { id, workspaceId },
|
||||
});
|
||||
|
||||
if (!existingServerlessFunction) {
|
||||
throw new ServerlessFunctionException(
|
||||
`Function does not exist`,
|
||||
ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
await super.deleteOne(id);
|
||||
|
||||
await this.serverlessService.delete(existingServerlessFunction);
|
||||
|
||||
return existingServerlessFunction;
|
||||
}
|
||||
|
||||
async updateOneServerlessFunction(
|
||||
serverlessFunctionInput: UpdateServerlessFunctionInput,
|
||||
workspaceId: string,
|
||||
{ createReadStream, mimetype }: FileUpload,
|
||||
) {
|
||||
const existingServerlessFunction =
|
||||
await this.serverlessFunctionRepository.findOne({
|
||||
where: { name, workspaceId },
|
||||
where: { id: serverlessFunctionInput.id, workspaceId },
|
||||
});
|
||||
|
||||
if (!existingServerlessFunction) {
|
||||
throw new ServerlessFunctionException(
|
||||
`Function does not exist`,
|
||||
ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const codeHasChanged =
|
||||
serverlessFunctionCreateHash(serverlessFunctionInput.code) !==
|
||||
existingServerlessFunction.sourceCodeHash;
|
||||
|
||||
await super.updateOne(existingServerlessFunction.id, {
|
||||
name: serverlessFunctionInput.name,
|
||||
description: serverlessFunctionInput.description,
|
||||
sourceCodeHash: serverlessFunctionCreateHash(
|
||||
serverlessFunctionInput.code,
|
||||
),
|
||||
});
|
||||
|
||||
if (codeHasChanged) {
|
||||
const fileFolder = join(
|
||||
FileFolder.ServerlessFunction,
|
||||
workspaceId,
|
||||
existingServerlessFunction.id,
|
||||
);
|
||||
|
||||
await this.fileStorageService.write({
|
||||
file: serverlessFunctionInput.code,
|
||||
name: SOURCE_FILE_NAME,
|
||||
mimeType: undefined,
|
||||
folder: fileFolder,
|
||||
});
|
||||
|
||||
await this.serverlessService.build(existingServerlessFunction);
|
||||
}
|
||||
|
||||
return await this.findById(existingServerlessFunction.id);
|
||||
}
|
||||
|
||||
async createOneServerlessFunction(
|
||||
serverlessFunctionInput: CreateServerlessFunctionFromFileInput,
|
||||
code: FileUpload | string,
|
||||
workspaceId: string,
|
||||
) {
|
||||
const existingServerlessFunction =
|
||||
await this.serverlessFunctionRepository.findOne({
|
||||
where: { name: serverlessFunctionInput.name, workspaceId },
|
||||
});
|
||||
|
||||
if (existingServerlessFunction) {
|
||||
@ -79,34 +153,44 @@ export class ServerlessFunctionService {
|
||||
);
|
||||
}
|
||||
|
||||
const typescriptCode = await readFileContent(createReadStream());
|
||||
let typescriptCode: string;
|
||||
|
||||
const serverlessFunction = await this.serverlessFunctionRepository.save({
|
||||
name,
|
||||
workspaceId,
|
||||
sourceCodeHash: serverlessFunctionCreateHash(typescriptCode),
|
||||
});
|
||||
if (typeof code === 'string') {
|
||||
typescriptCode = code;
|
||||
} else {
|
||||
typescriptCode = await readFileContent(code.createReadStream());
|
||||
}
|
||||
|
||||
const serverlessFunctionId = v4();
|
||||
|
||||
const fileFolder = join(
|
||||
FileFolder.ServerlessFunction,
|
||||
workspaceId,
|
||||
serverlessFunction.id,
|
||||
serverlessFunctionId,
|
||||
);
|
||||
|
||||
const sourceCodeFullPath = fileFolder + '/' + SOURCE_FILE_NAME;
|
||||
|
||||
const serverlessFunction = await super.createOne({
|
||||
...serverlessFunctionInput,
|
||||
id: serverlessFunctionId,
|
||||
workspaceId,
|
||||
sourceCodeHash: serverlessFunctionCreateHash(typescriptCode),
|
||||
sourceCodeFullPath,
|
||||
});
|
||||
|
||||
await this.fileStorageService.write({
|
||||
file: typescriptCode,
|
||||
name: SOURCE_FILE_NAME,
|
||||
mimeType: mimetype,
|
||||
mimeType: undefined,
|
||||
folder: fileFolder,
|
||||
});
|
||||
|
||||
await this.serverlessService.build(serverlessFunction);
|
||||
await this.serverlessFunctionRepository.update(serverlessFunction.id, {
|
||||
await super.updateOne(serverlessFunctionId, {
|
||||
syncStatus: ServerlessFunctionSyncStatus.READY,
|
||||
});
|
||||
|
||||
return await this.serverlessFunctionRepository.findOneByOrFail({
|
||||
id: serverlessFunction.id,
|
||||
});
|
||||
return await this.findById(serverlessFunctionId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ export const serverlessFunctionGraphQLApiExceptionHandler = (error: any) => {
|
||||
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_ALREADY_EXIST:
|
||||
throw new ConflictError(error.message);
|
||||
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_READY:
|
||||
case ServerlessFunctionExceptionCode.FEATURE_FLAG_INVALID:
|
||||
throw new ForbiddenError(error.message);
|
||||
default:
|
||||
throw new InternalServerError(error.message);
|
||||
|
||||
Reference in New Issue
Block a user