Add serverless function in twenty-cli (#14819)
- add serverlessFunction schema in twenty-cli - add trigger schema in twenty-cli - update serverless function code save and get - sync serverless function
This commit is contained in:
+12
@@ -3,11 +3,15 @@ import { Field, HideField, InputType } from '@nestjs/graphql';
|
||||
import {
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
Max,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
import graphqlTypeJson from 'graphql-type-json';
|
||||
|
||||
import { ServerlessFunctionCode } from 'src/engine/metadata-modules/serverless-function/types/serverless-function-code.type';
|
||||
|
||||
@InputType()
|
||||
export class CreateServerlessFunctionInput {
|
||||
@@ -31,6 +35,14 @@ export class CreateServerlessFunctionInput {
|
||||
@HideField()
|
||||
applicationId?: string;
|
||||
|
||||
@HideField()
|
||||
universalIdentifier?: string;
|
||||
|
||||
@HideField()
|
||||
serverlessFunctionLayerId?: string;
|
||||
|
||||
@Field(() => graphqlTypeJson, { nullable: true })
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
code?: ServerlessFunctionCode;
|
||||
}
|
||||
|
||||
+2
-1
@@ -13,6 +13,7 @@ import {
|
||||
import graphqlTypeJson from 'graphql-type-json';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { ServerlessFunctionCode } from 'src/engine/metadata-modules/serverless-function/types/serverless-function-code.type';
|
||||
|
||||
@InputType()
|
||||
export class UpdateServerlessFunctionInput {
|
||||
@@ -41,5 +42,5 @@ export class UpdateServerlessFunctionInput {
|
||||
|
||||
@Field(() => graphqlTypeJson)
|
||||
@IsObject()
|
||||
code: JSON;
|
||||
code: ServerlessFunctionCode;
|
||||
}
|
||||
|
||||
+1
-1
@@ -39,6 +39,6 @@ import { ServerlessFunctionLayerModule } from 'src/engine/metadata-modules/serve
|
||||
ServerlessFunctionResolver,
|
||||
WorkspaceFlatServerlessFunctionMapCacheService,
|
||||
],
|
||||
exports: [ServerlessFunctionService],
|
||||
exports: [ServerlessFunctionService, ServerlessFunctionV2Service],
|
||||
})
|
||||
export class ServerlessFunctionModule {}
|
||||
|
||||
+8
-28
@@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { basename, dirname, join } from 'path';
|
||||
import { join } from 'path';
|
||||
|
||||
import deepEqual from 'deep-equal';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@@ -13,9 +13,6 @@ import { type ServerlessExecuteResult } from 'src/engine/core-modules/serverless
|
||||
import { AuditService } from 'src/engine/core-modules/audit/services/audit.service';
|
||||
import { SERVERLESS_FUNCTION_EXECUTED_EVENT } from 'src/engine/core-modules/audit/utils/events/workspace-event/serverless-function/serverless-function-executed';
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { readFileContent } from 'src/engine/core-modules/file-storage/utils/read-file-content';
|
||||
import { ENV_FILE_NAME } from 'src/engine/core-modules/serverless/drivers/constants/env-file-name';
|
||||
import { INDEX_FILE_NAME } from 'src/engine/core-modules/serverless/drivers/constants/index-file-name';
|
||||
import { getBaseTypescriptProjectFiles } from 'src/engine/core-modules/serverless/drivers/utils/get-base-typescript-project-files';
|
||||
import { getLayerDependencies } from 'src/engine/core-modules/serverless/drivers/utils/get-last-layer-dependencies';
|
||||
import { ServerlessService } from 'src/engine/core-modules/serverless/serverless.service';
|
||||
@@ -34,6 +31,7 @@ import {
|
||||
WorkflowVersionStepExceptionCode,
|
||||
} from 'src/modules/workflow/common/exceptions/workflow-version-step.exception';
|
||||
import { ServerlessFunctionLayerService } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.service';
|
||||
import { Sources } from 'src/engine/core-modules/file-storage/types/source.type';
|
||||
|
||||
@Injectable()
|
||||
export class ServerlessFunctionService {
|
||||
@@ -66,7 +64,7 @@ export class ServerlessFunctionService {
|
||||
workspaceId: string,
|
||||
id: string,
|
||||
version: string,
|
||||
): Promise<{ [filePath: string]: string } | undefined> {
|
||||
): Promise<Sources | undefined> {
|
||||
const serverlessFunction =
|
||||
await this.serverlessFunctionRepository.findOneOrFail({
|
||||
where: {
|
||||
@@ -81,20 +79,7 @@ export class ServerlessFunctionService {
|
||||
version,
|
||||
});
|
||||
|
||||
const indexFileStream = await this.fileStorageService.read({
|
||||
folderPath: join(folderPath, 'src'),
|
||||
filename: INDEX_FILE_NAME,
|
||||
});
|
||||
|
||||
const envFileStream = await this.fileStorageService.read({
|
||||
folderPath: folderPath,
|
||||
filename: ENV_FILE_NAME,
|
||||
});
|
||||
|
||||
return {
|
||||
'.env': await readFileContent(envFileStream),
|
||||
'src/index.ts': await readFileContent(indexFileStream),
|
||||
};
|
||||
return await this.fileStorageService.readFolder(folderPath);
|
||||
} catch (error) {
|
||||
if (error.code === FileStorageExceptionCode.FILE_NOT_FOUND) {
|
||||
return;
|
||||
@@ -289,15 +274,10 @@ export class ServerlessFunctionService {
|
||||
version: 'draft',
|
||||
});
|
||||
|
||||
for (const key of Object.keys(serverlessFunctionInput.code)) {
|
||||
await this.fileStorageService.write({
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
file: serverlessFunctionInput.code[key],
|
||||
name: basename(key),
|
||||
mimeType: undefined,
|
||||
folder: join(fileFolder, dirname(key)),
|
||||
});
|
||||
}
|
||||
await this.fileStorageService.writeFolder(
|
||||
serverlessFunctionInput.code,
|
||||
fileFolder,
|
||||
);
|
||||
|
||||
return this.serverlessFunctionRepository.findOneBy({
|
||||
id: existingServerlessFunction.id,
|
||||
|
||||
+6
-2
@@ -168,9 +168,11 @@ export class ServerlessFunctionV2Service {
|
||||
async deleteOne({
|
||||
deleteServerlessFunctionInput,
|
||||
workspaceId,
|
||||
isSystemBuild = false,
|
||||
}: {
|
||||
deleteServerlessFunctionInput: ServerlessFunctionIdInput;
|
||||
workspaceId: string;
|
||||
isSystemBuild?: boolean;
|
||||
}): Promise<FlatServerlessFunction> {
|
||||
const { flatServerlessFunctionMaps: existingFlatServerlessFunctionMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
@@ -211,8 +213,8 @@ export class ServerlessFunctionV2Service {
|
||||
},
|
||||
},
|
||||
buildOptions: {
|
||||
isSystemBuild: false,
|
||||
inferDeletionFromMissingEntities: false,
|
||||
isSystemBuild,
|
||||
},
|
||||
workspaceId,
|
||||
},
|
||||
@@ -244,9 +246,11 @@ export class ServerlessFunctionV2Service {
|
||||
async destroyOne({
|
||||
destroyServerlessFunctionInput,
|
||||
workspaceId,
|
||||
isSystemBuild = false,
|
||||
}: {
|
||||
destroyServerlessFunctionInput: ServerlessFunctionIdInput;
|
||||
workspaceId: string;
|
||||
isSystemBuild?: boolean;
|
||||
}): Promise<FlatServerlessFunction> {
|
||||
const { flatServerlessFunctionMaps: existingFlatServerlessFunctionMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
@@ -288,7 +292,7 @@ export class ServerlessFunctionV2Service {
|
||||
},
|
||||
},
|
||||
buildOptions: {
|
||||
isSystemBuild: false,
|
||||
isSystemBuild,
|
||||
inferDeletionFromMissingEntities: true,
|
||||
},
|
||||
workspaceId,
|
||||
|
||||
+2
-1
@@ -6,6 +6,7 @@ import { type ServerlessFunctionEntity } from 'src/engine/metadata-modules/serve
|
||||
import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type';
|
||||
import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { type ServerlessFunctionLayerEntity } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.entity';
|
||||
import { type ServerlessFunctionCode } from 'src/engine/metadata-modules/serverless-function/types/serverless-function-code.type';
|
||||
|
||||
export type ServerlessFunctionEntityRelationProperties =
|
||||
ExtractRecordTypeOrmRelationProperties<
|
||||
@@ -23,5 +24,5 @@ export type FlatServerlessFunction = Omit<
|
||||
ServerlessFunctionEntityRelationProperties
|
||||
> & {
|
||||
universalIdentifier: string;
|
||||
code?: JSON;
|
||||
code?: ServerlessFunctionCode;
|
||||
};
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import { type Sources } from 'src/engine/core-modules/file-storage/types/source.type';
|
||||
|
||||
export type ServerlessFunctionCode = {
|
||||
src: {
|
||||
'index.ts': string;
|
||||
} & Sources;
|
||||
'.env'?: string;
|
||||
};
|
||||
+9
-3
@@ -4,6 +4,7 @@ import { LAST_LAYER_VERSION } from 'src/engine/core-modules/serverless/drivers/l
|
||||
import { type CreateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function.input';
|
||||
import { ServerlessFunctionRuntime } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
import { type FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
|
||||
import { serverlessFunctionCreateHash } from 'src/engine/metadata-modules/serverless-function/utils/serverless-function-create-hash.utils';
|
||||
|
||||
export const fromCreateServerlessFunctionInputToFlatServerlessFunction = ({
|
||||
createServerlessFunctionInput,
|
||||
@@ -13,14 +14,14 @@ export const fromCreateServerlessFunctionInputToFlatServerlessFunction = ({
|
||||
workspaceId: string;
|
||||
}): FlatServerlessFunction => {
|
||||
const id = v4();
|
||||
const universalIdentifier = v4();
|
||||
const currentDate = new Date();
|
||||
|
||||
return {
|
||||
id,
|
||||
name: createServerlessFunctionInput.name,
|
||||
description: createServerlessFunctionInput.description ?? null,
|
||||
universalIdentifier,
|
||||
universalIdentifier:
|
||||
createServerlessFunctionInput.universalIdentifier ?? v4(),
|
||||
createdAt: currentDate,
|
||||
updatedAt: currentDate,
|
||||
deletedAt: null,
|
||||
@@ -34,6 +35,11 @@ export const fromCreateServerlessFunctionInputToFlatServerlessFunction = ({
|
||||
serverlessFunctionLayerId:
|
||||
createServerlessFunctionInput.serverlessFunctionLayerId ?? null,
|
||||
workspaceId,
|
||||
checksum: null,
|
||||
code: createServerlessFunctionInput?.code,
|
||||
checksum: createServerlessFunctionInput?.code
|
||||
? serverlessFunctionCreateHash(
|
||||
JSON.stringify(createServerlessFunctionInput.code),
|
||||
)
|
||||
: null,
|
||||
};
|
||||
};
|
||||
|
||||
+3
-3
@@ -13,8 +13,8 @@ import {
|
||||
ServerlessFunctionExceptionCode,
|
||||
} from 'src/engine/metadata-modules/serverless-function/serverless-function.exception';
|
||||
import { type FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
|
||||
import { serverlessFunctionCreateCodeChecksum } from 'src/engine/metadata-modules/serverless-function/utils/serverless-function-create-code-checksum.utils';
|
||||
import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util';
|
||||
import { serverlessFunctionCreateHash } from 'src/engine/metadata-modules/serverless-function/utils/serverless-function-create-hash.utils';
|
||||
|
||||
export const fromUpdateServerlessFunctionInputToFlatServerlessFunctionToUpdateOrThrow =
|
||||
({
|
||||
@@ -42,8 +42,8 @@ export const fromUpdateServerlessFunctionInputToFlatServerlessFunctionToUpdateOr
|
||||
const updatedEditableFieldProperties = extractAndSanitizeObjectStringFields(
|
||||
{
|
||||
...rawUpdateServerlessFunctionInput,
|
||||
checksum: serverlessFunctionCreateCodeChecksum(
|
||||
rawUpdateServerlessFunctionInput.code,
|
||||
checksum: serverlessFunctionCreateHash(
|
||||
JSON.stringify(rawUpdateServerlessFunctionInput.code),
|
||||
),
|
||||
},
|
||||
FLAT_SERVERLESS_FUNCTION_EDITABLE_PROPERTIES,
|
||||
|
||||
+4
-1
@@ -1,8 +1,11 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { serverlessFunctionCreateHash } from 'src/engine/metadata-modules/serverless-function/utils/serverless-function-create-hash.utils';
|
||||
import { type ServerlessFunctionCode } from 'src/engine/metadata-modules/serverless-function/types/serverless-function-code.type';
|
||||
|
||||
export const serverlessFunctionCreateCodeChecksum = (code: JSON): string => {
|
||||
export const serverlessFunctionCreateCodeChecksum = (
|
||||
code: ServerlessFunctionCode,
|
||||
): string => {
|
||||
if (!isDefined(code) || typeof code !== 'object') {
|
||||
return serverlessFunctionCreateHash('');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user