Serverless built updates (#17351)
follow up of https://github.com/twentyhq/twenty/pull/17317
This commit is contained in:
+2
-2
@@ -947,7 +947,7 @@ export class ApplicationSyncService {
|
||||
name,
|
||||
code,
|
||||
timeoutSeconds: serverlessFunctionToSync.timeoutSeconds,
|
||||
handlerPath: serverlessFunctionToSync.sourceHandlerPath,
|
||||
sourceHandlerPath: serverlessFunctionToSync.sourceHandlerPath,
|
||||
handlerName: serverlessFunctionToSync.handlerName,
|
||||
toolInputSchema: serverlessFunctionToSync.toolInputSchema,
|
||||
isTool: serverlessFunctionToSync.isTool,
|
||||
@@ -991,7 +991,7 @@ export class ApplicationSyncService {
|
||||
code,
|
||||
universalIdentifier: serverlessFunctionToCreate.universalIdentifier,
|
||||
timeoutSeconds: serverlessFunctionToCreate.timeoutSeconds,
|
||||
handlerPath: serverlessFunctionToCreate.sourceHandlerPath,
|
||||
sourceHandlerPath: serverlessFunctionToCreate.sourceHandlerPath,
|
||||
handlerName: serverlessFunctionToCreate.handlerName,
|
||||
applicationId,
|
||||
serverlessFunctionLayerId,
|
||||
|
||||
-86
@@ -1,86 +0,0 @@
|
||||
import { join } from 'path';
|
||||
import fs from 'fs/promises';
|
||||
|
||||
import { build } from 'esbuild';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import { getServerlessFolderOrThrow } from 'src/engine/core-modules/serverless/utils/get-serverless-folder-or-throw.utils';
|
||||
import { LambdaBuildDirectoryManager } from 'src/engine/core-modules/serverless/drivers/utils/lambda-build-directory-manager';
|
||||
import { type FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
|
||||
import { type FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
|
||||
const buildServerlessFunctionInMemory = async ({
|
||||
sourceTemporaryDir,
|
||||
handlerPath,
|
||||
builtHandlerPath,
|
||||
}: {
|
||||
sourceTemporaryDir: string;
|
||||
handlerPath: string;
|
||||
builtHandlerPath: string;
|
||||
}): Promise<string> => {
|
||||
const entryFilePath = join(sourceTemporaryDir, handlerPath);
|
||||
const builtBundleFilePath = join(sourceTemporaryDir, builtHandlerPath);
|
||||
|
||||
await build({
|
||||
entryPoints: [entryFilePath],
|
||||
outfile: builtBundleFilePath,
|
||||
platform: 'node',
|
||||
format: 'esm',
|
||||
target: 'es2017',
|
||||
bundle: true,
|
||||
sourcemap: true,
|
||||
packages: 'external',
|
||||
});
|
||||
|
||||
return builtBundleFilePath;
|
||||
};
|
||||
|
||||
export const buildAndUploadServerlessFunction = async ({
|
||||
flatServerlessFunction,
|
||||
version,
|
||||
fileStorageService,
|
||||
}: {
|
||||
flatServerlessFunction: FlatServerlessFunction;
|
||||
version: string;
|
||||
fileStorageService: FileStorageService;
|
||||
}): Promise<void> => {
|
||||
const sourceFolderPath = getServerlessFolderOrThrow({
|
||||
flatServerlessFunction,
|
||||
version,
|
||||
fileFolder: FileFolder.ServerlessFunction,
|
||||
});
|
||||
|
||||
const builtFolderPath = getServerlessFolderOrThrow({
|
||||
flatServerlessFunction,
|
||||
version,
|
||||
fileFolder: FileFolder.BuiltFunction,
|
||||
});
|
||||
|
||||
const lambdaBuildDirectoryManager = new LambdaBuildDirectoryManager();
|
||||
|
||||
try {
|
||||
const { sourceTemporaryDir } = await lambdaBuildDirectoryManager.init();
|
||||
|
||||
await fileStorageService.download({
|
||||
from: { folderPath: sourceFolderPath },
|
||||
to: { folderPath: sourceTemporaryDir },
|
||||
});
|
||||
|
||||
const builtBundleFilePath = await buildServerlessFunctionInMemory({
|
||||
sourceTemporaryDir,
|
||||
handlerPath: flatServerlessFunction.handlerPath,
|
||||
builtHandlerPath: flatServerlessFunction.builtHandlerPath,
|
||||
});
|
||||
|
||||
const builtFile = await fs.readFile(builtBundleFilePath, 'utf-8');
|
||||
|
||||
await fileStorageService.write({
|
||||
file: builtFile,
|
||||
name: flatServerlessFunction.builtHandlerPath,
|
||||
mimeType: 'application/javascript',
|
||||
folder: builtFolderPath,
|
||||
});
|
||||
} finally {
|
||||
await lambdaBuildDirectoryManager.clean();
|
||||
}
|
||||
};
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
import type { FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
|
||||
import type { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { getServerlessFolderOrThrow } from 'src/engine/core-modules/serverless/utils/get-serverless-folder-or-throw.utils';
|
||||
|
||||
export const isServerlessFunctionBuilt = async ({
|
||||
flatServerlessFunction,
|
||||
version,
|
||||
fileStorageService,
|
||||
}: {
|
||||
flatServerlessFunction: FlatServerlessFunction;
|
||||
version: string;
|
||||
fileStorageService: FileStorageService;
|
||||
}) => {
|
||||
const folderPath = getServerlessFolderOrThrow({
|
||||
flatServerlessFunction,
|
||||
version,
|
||||
});
|
||||
|
||||
return await fileStorageService.checkFileExists({
|
||||
folderPath,
|
||||
filename: flatServerlessFunction.builtHandlerPath,
|
||||
});
|
||||
};
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { FunctionBuildService } from 'src/engine/metadata-modules/function-build/function-build.service';
|
||||
|
||||
@Module({
|
||||
providers: [FunctionBuildService],
|
||||
exports: [FunctionBuildService],
|
||||
})
|
||||
export class FunctionBuildModule {}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { join } from 'path';
|
||||
import fs from 'fs/promises';
|
||||
|
||||
import { build } from 'esbuild';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { LambdaBuildDirectoryManager } from 'src/engine/core-modules/serverless/drivers/utils/lambda-build-directory-manager';
|
||||
import { getServerlessFolderOrThrow } from 'src/engine/core-modules/serverless/utils/get-serverless-folder-or-throw.utils';
|
||||
import { type FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
|
||||
|
||||
@Injectable()
|
||||
export class FunctionBuildService {
|
||||
constructor(private readonly fileStorageService: FileStorageService) {}
|
||||
|
||||
async isBuilt({
|
||||
flatServerlessFunction,
|
||||
version,
|
||||
}: {
|
||||
flatServerlessFunction: FlatServerlessFunction;
|
||||
version: string;
|
||||
}): Promise<boolean> {
|
||||
const folderPath = getServerlessFolderOrThrow({
|
||||
flatServerlessFunction,
|
||||
version,
|
||||
});
|
||||
|
||||
return await this.fileStorageService.checkFileExists({
|
||||
folderPath,
|
||||
filename: flatServerlessFunction.builtHandlerPath,
|
||||
});
|
||||
}
|
||||
|
||||
async buildAndUpload({
|
||||
flatServerlessFunction,
|
||||
version,
|
||||
}: {
|
||||
flatServerlessFunction: FlatServerlessFunction;
|
||||
version: string;
|
||||
}): Promise<void> {
|
||||
const sourceFolderPath = getServerlessFolderOrThrow({
|
||||
flatServerlessFunction,
|
||||
version,
|
||||
fileFolder: FileFolder.ServerlessFunction,
|
||||
});
|
||||
|
||||
const builtFolderPath = getServerlessFolderOrThrow({
|
||||
flatServerlessFunction,
|
||||
version,
|
||||
fileFolder: FileFolder.BuiltFunction,
|
||||
});
|
||||
|
||||
const lambdaBuildDirectoryManager = new LambdaBuildDirectoryManager();
|
||||
|
||||
try {
|
||||
const { sourceTemporaryDir } = await lambdaBuildDirectoryManager.init();
|
||||
|
||||
await this.fileStorageService.download({
|
||||
from: { folderPath: sourceFolderPath },
|
||||
to: { folderPath: sourceTemporaryDir },
|
||||
});
|
||||
|
||||
const builtBundleFilePath = await this.buildInMemory({
|
||||
sourceTemporaryDir,
|
||||
sourceHandlerPath: flatServerlessFunction.sourceHandlerPath,
|
||||
builtHandlerPath: flatServerlessFunction.builtHandlerPath,
|
||||
});
|
||||
|
||||
const builtFile = await fs.readFile(builtBundleFilePath, 'utf-8');
|
||||
|
||||
await this.fileStorageService.write({
|
||||
file: builtFile,
|
||||
name: flatServerlessFunction.builtHandlerPath,
|
||||
mimeType: 'application/javascript',
|
||||
folder: builtFolderPath,
|
||||
});
|
||||
} finally {
|
||||
await lambdaBuildDirectoryManager.clean();
|
||||
}
|
||||
}
|
||||
|
||||
private async buildInMemory({
|
||||
sourceTemporaryDir,
|
||||
sourceHandlerPath,
|
||||
builtHandlerPath,
|
||||
}: {
|
||||
sourceTemporaryDir: string;
|
||||
sourceHandlerPath: string;
|
||||
builtHandlerPath: string;
|
||||
}): Promise<string> {
|
||||
const entryFilePath = join(sourceTemporaryDir, sourceHandlerPath);
|
||||
const builtBundleFilePath = join(sourceTemporaryDir, builtHandlerPath);
|
||||
|
||||
await build({
|
||||
entryPoints: [entryFilePath],
|
||||
outfile: builtBundleFilePath,
|
||||
platform: 'node',
|
||||
format: 'esm',
|
||||
target: 'es2017',
|
||||
bundle: true,
|
||||
sourcemap: true,
|
||||
packages: 'external',
|
||||
});
|
||||
|
||||
return builtBundleFilePath;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -6,7 +6,7 @@ export const FLAT_SERVERLESS_FUNCTION_EDITABLE_PROPERTIES = [
|
||||
'timeoutSeconds',
|
||||
'checksum',
|
||||
'code',
|
||||
'handlerPath',
|
||||
'sourceHandlerPath',
|
||||
'handlerName',
|
||||
'toolInputSchema',
|
||||
'isTool',
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ export class CreateServerlessFunctionInput {
|
||||
@IsString()
|
||||
@Field({ nullable: true })
|
||||
@IsOptional()
|
||||
handlerPath?: string;
|
||||
sourceHandlerPath?: string;
|
||||
|
||||
@IsString()
|
||||
@Field({ nullable: true })
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ export class ServerlessFunctionDTO {
|
||||
|
||||
@IsString()
|
||||
@Field()
|
||||
handlerPath: string;
|
||||
sourceHandlerPath: string;
|
||||
|
||||
@IsString()
|
||||
@Field()
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ class UpdateServerlessFunctionInputUpdates {
|
||||
@IsString()
|
||||
@Field({ nullable: true })
|
||||
@IsOptional()
|
||||
handlerPath?: string;
|
||||
sourceHandlerPath?: string;
|
||||
|
||||
@Field(() => graphqlTypeJson, { nullable: true })
|
||||
@IsObject()
|
||||
|
||||
+3
-3
@@ -26,7 +26,7 @@ export enum ServerlessFunctionRuntime {
|
||||
NODE22 = 'nodejs22.x',
|
||||
}
|
||||
|
||||
export const DEFAULT_HANDLER_PATH = 'src/index.ts';
|
||||
export const DEFAULT_SOURCE_HANDLER_PATH = 'src/index.ts';
|
||||
export const DEFAULT_BUILT_HANDLER_PATH = 'index.mjs';
|
||||
export const DEFAULT_HANDLER_NAME = 'main';
|
||||
|
||||
@@ -43,8 +43,8 @@ export class ServerlessFunctionEntity
|
||||
@Column({ nullable: false })
|
||||
name: string;
|
||||
|
||||
@Column({ nullable: false, default: DEFAULT_HANDLER_PATH })
|
||||
handlerPath: string;
|
||||
@Column({ nullable: false, default: DEFAULT_SOURCE_HANDLER_PATH })
|
||||
sourceHandlerPath: string;
|
||||
|
||||
@Column({ nullable: false, default: DEFAULT_BUILT_HANDLER_PATH })
|
||||
builtHandlerPath: string;
|
||||
|
||||
+2
@@ -26,6 +26,7 @@ import { WorkspaceFlatServerlessFunctionMapCacheService } from 'src/engine/metad
|
||||
import { SubscriptionsModule } from 'src/engine/subscriptions/subscriptions.module';
|
||||
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
||||
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
|
||||
import { FunctionBuildModule } from 'src/engine/metadata-modules/function-build/function-build.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -45,6 +46,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
PermissionsModule,
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
WorkspaceMigrationModule,
|
||||
FunctionBuildModule,
|
||||
ServerlessFunctionLayerModule,
|
||||
SubscriptionsModule,
|
||||
WorkspaceCacheModule,
|
||||
|
||||
+4
-6
@@ -48,8 +48,7 @@ import {
|
||||
WorkflowVersionStepExceptionCode,
|
||||
} from 'src/modules/workflow/common/exceptions/workflow-version-step.exception';
|
||||
import { cleanServerUrl } from 'src/utils/clean-server-url';
|
||||
import { isServerlessFunctionBuilt } from 'src/engine/core-modules/serverless/drivers/utils/is-serverless-function-built';
|
||||
import { buildAndUploadServerlessFunction } from 'src/engine/core-modules/serverless/drivers/utils/build-and-upload-serverless-function';
|
||||
import { FunctionBuildService } from 'src/engine/metadata-modules/function-build/function-build.service';
|
||||
|
||||
const MIN_TOKEN_EXPIRATION_IN_SECONDS = 5;
|
||||
|
||||
@@ -58,6 +57,7 @@ export class ServerlessFunctionService {
|
||||
constructor(
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
private readonly serverlessService: ServerlessService,
|
||||
private readonly functionBuildService: FunctionBuildService,
|
||||
private readonly serverlessFunctionLayerService: ServerlessFunctionLayerService,
|
||||
@InjectRepository(ServerlessFunctionEntity)
|
||||
private readonly serverlessFunctionRepository: Repository<ServerlessFunctionEntity>,
|
||||
@@ -210,16 +210,14 @@ export class ServerlessFunctionService {
|
||||
|
||||
// We keep that check to build functions
|
||||
if (
|
||||
!(await isServerlessFunctionBuilt({
|
||||
!(await this.functionBuildService.isBuilt({
|
||||
flatServerlessFunction,
|
||||
version,
|
||||
fileStorageService: this.fileStorageService,
|
||||
}))
|
||||
) {
|
||||
await buildAndUploadServerlessFunction({
|
||||
await this.functionBuildService.buildAndUpload({
|
||||
flatServerlessFunction,
|
||||
version,
|
||||
fileStorageService: this.fileStorageService,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -6,7 +6,7 @@ import { type CreateServerlessFunctionInput } from 'src/engine/metadata-modules/
|
||||
import {
|
||||
DEFAULT_BUILT_HANDLER_PATH,
|
||||
DEFAULT_HANDLER_NAME,
|
||||
DEFAULT_HANDLER_PATH,
|
||||
DEFAULT_SOURCE_HANDLER_PATH,
|
||||
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';
|
||||
@@ -35,8 +35,9 @@ export const fromCreateServerlessFunctionInputToFlatServerlessFunction = ({
|
||||
id,
|
||||
name: rawCreateServerlessFunctionInput.name,
|
||||
description: rawCreateServerlessFunctionInput.description ?? null,
|
||||
handlerPath:
|
||||
rawCreateServerlessFunctionInput.handlerPath ?? DEFAULT_HANDLER_PATH,
|
||||
sourceHandlerPath:
|
||||
rawCreateServerlessFunctionInput.sourceHandlerPath ??
|
||||
DEFAULT_SOURCE_HANDLER_PATH,
|
||||
handlerName:
|
||||
rawCreateServerlessFunctionInput.handlerName ?? DEFAULT_HANDLER_NAME,
|
||||
builtHandlerPath:
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ export const fromFlatServerlessFunctionToServerlessFunctionDto = ({
|
||||
runtime: flatServerlessFunction.runtime,
|
||||
timeoutSeconds: flatServerlessFunction.timeoutSeconds,
|
||||
latestVersion: flatServerlessFunction.latestVersion ?? undefined,
|
||||
handlerPath: flatServerlessFunction.handlerPath,
|
||||
sourceHandlerPath: flatServerlessFunction.sourceHandlerPath,
|
||||
builtHandlerPath: flatServerlessFunction.builtHandlerPath,
|
||||
handlerName: flatServerlessFunction.handlerName,
|
||||
publishedVersions: flatServerlessFunction.publishedVersions,
|
||||
|
||||
+6
-4
@@ -13,14 +13,17 @@ import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless
|
||||
import { CreateServerlessFunctionAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/serverless-function/types/workspace-migration-serverless-function-action.type';
|
||||
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
|
||||
import { FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
|
||||
import { buildAndUploadServerlessFunction } from 'src/engine/core-modules/serverless/drivers/utils/build-and-upload-serverless-function';
|
||||
import { FunctionBuildService } from 'src/engine/metadata-modules/function-build/function-build.service';
|
||||
|
||||
@Injectable()
|
||||
export class CreateServerlessFunctionActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||
'create',
|
||||
'serverlessFunction',
|
||||
) {
|
||||
constructor(private readonly fileStorageService: FileStorageService) {
|
||||
constructor(
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
private readonly functionBuildService: FunctionBuildService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -66,10 +69,9 @@ export class CreateServerlessFunctionActionHandlerService extends WorkspaceMigra
|
||||
});
|
||||
}
|
||||
}
|
||||
await buildAndUploadServerlessFunction({
|
||||
await this.functionBuildService.buildAndUpload({
|
||||
flatServerlessFunction: serverlessFunction,
|
||||
version: 'draft',
|
||||
fileStorageService: this.fileStorageService,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -14,7 +14,7 @@ import { FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-f
|
||||
import { UpdateServerlessFunctionAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/serverless-function/types/workspace-migration-serverless-function-action.type';
|
||||
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
|
||||
import { fromFlatEntityPropertiesUpdatesToPartialFlatEntity } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/from-flat-entity-properties-updates-to-partial-flat-entity';
|
||||
import { buildAndUploadServerlessFunction } from 'src/engine/core-modules/serverless/drivers/utils/build-and-upload-serverless-function';
|
||||
import { FunctionBuildService } from 'src/engine/metadata-modules/function-build/function-build.service';
|
||||
|
||||
@Injectable()
|
||||
export class UpdateServerlessFunctionActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||
@@ -24,6 +24,7 @@ export class UpdateServerlessFunctionActionHandlerService extends WorkspaceMigra
|
||||
constructor(
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
private readonly serverlessService: ServerlessService,
|
||||
private readonly functionBuildService: FunctionBuildService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -86,10 +87,9 @@ export class UpdateServerlessFunctionActionHandlerService extends WorkspaceMigra
|
||||
|
||||
await this.fileStorageService.writeFolder(code, fileFolder);
|
||||
|
||||
await buildAndUploadServerlessFunction({
|
||||
await this.functionBuildService.buildAndUpload({
|
||||
flatServerlessFunction,
|
||||
version: 'draft',
|
||||
fileStorageService: this.fileStorageService,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -73,9 +73,10 @@ import { UpdateViewGroupActionHandlerService } from 'src/engine/workspace-manage
|
||||
import { CreateViewActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view/services/create-view-action-handler.service';
|
||||
import { DeleteViewActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view/services/delete-view-action-handler.service';
|
||||
import { UpdateViewActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/view/services/update-view-action-handler.service';
|
||||
import { FunctionBuildModule } from 'src/engine/metadata-modules/function-build/function-build.module';
|
||||
|
||||
@Module({
|
||||
imports: [WorkspaceSchemaManagerModule],
|
||||
imports: [WorkspaceSchemaManagerModule, FunctionBuildModule],
|
||||
providers: [
|
||||
CreateFieldActionHandlerService,
|
||||
UpdateFieldActionHandlerService,
|
||||
|
||||
Reference in New Issue
Block a user