2114 extensibility manage serverless execution from built code (#17317)
Summary - Serverless functions are now built when created/updated instead of at execution time - Added builtHandlerPath field to track pre-built function artifacts - Renamed base-typescript-project to seed-project with pre-built ESM output included - Renamed file folder enums for consistency (Functions → BuiltFunction, SourceCode → Source) - supports backwards compatibility with existing serverless functions Tested with all combinations - storage : local driver and S3 driver - serverless : local driver and lambda driver
This commit is contained in:
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Default tool input schema matching the base-typescript-project template
|
||||
// Default tool input schema matching the seed-project template
|
||||
// Template params: { a: string; b: number; }
|
||||
export const DEFAULT_TOOL_INPUT_SCHEMA = {
|
||||
type: 'object',
|
||||
|
||||
+5
@@ -56,6 +56,11 @@ export class CreateServerlessFunctionInput {
|
||||
@IsOptional()
|
||||
handlerPath?: string;
|
||||
|
||||
@IsString()
|
||||
@Field({ nullable: true })
|
||||
@IsOptional()
|
||||
builtHandlerPath?: string;
|
||||
|
||||
@Field(() => graphqlTypeJson, { nullable: true })
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
|
||||
+4
@@ -65,6 +65,10 @@ export class ServerlessFunctionDTO {
|
||||
@Field()
|
||||
handlerPath: string;
|
||||
|
||||
@IsString()
|
||||
@Field()
|
||||
builtHandlerPath: string;
|
||||
|
||||
@IsString()
|
||||
@Field()
|
||||
handlerName: string;
|
||||
|
||||
+4
@@ -27,6 +27,7 @@ export enum ServerlessFunctionRuntime {
|
||||
}
|
||||
|
||||
export const DEFAULT_HANDLER_PATH = 'src/index.ts';
|
||||
export const DEFAULT_BUILT_HANDLER_PATH = 'index.mjs';
|
||||
export const DEFAULT_HANDLER_NAME = 'main';
|
||||
|
||||
@Entity('serverlessFunction')
|
||||
@@ -45,6 +46,9 @@ export class ServerlessFunctionEntity
|
||||
@Column({ nullable: false, default: DEFAULT_HANDLER_PATH })
|
||||
handlerPath: string;
|
||||
|
||||
@Column({ nullable: false, default: DEFAULT_BUILT_HANDLER_PATH })
|
||||
builtHandlerPath: string;
|
||||
|
||||
@Column({ nullable: false, default: DEFAULT_HANDLER_NAME })
|
||||
handlerName: string;
|
||||
|
||||
|
||||
+42
-5
@@ -8,6 +8,7 @@ import {
|
||||
} from 'twenty-shared/application';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception';
|
||||
import { type ServerlessExecuteResult } from 'src/engine/core-modules/serverless/drivers/interfaces/serverless-driver.interface';
|
||||
@@ -19,7 +20,7 @@ import { ApplicationTokenService } from 'src/engine/core-modules/auth/token/serv
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { buildEnvVar } from 'src/engine/core-modules/serverless/drivers/utils/build-env-var';
|
||||
import { ServerlessService } from 'src/engine/core-modules/serverless/serverless.service';
|
||||
import { getServerlessFolderOrThrow } from 'src/engine/core-modules/serverless/utils/serverless-get-folder.utils';
|
||||
import { getServerlessFolderOrThrow } from 'src/engine/core-modules/serverless/utils/get-serverless-folder-or-throw.utils';
|
||||
import { ThrottlerService } from 'src/engine/core-modules/throttler/throttler.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
@@ -47,6 +48,8 @@ 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';
|
||||
|
||||
const MIN_TOKEN_EXPIRATION_IN_SECONDS = 5;
|
||||
|
||||
@@ -205,6 +208,21 @@ export class ServerlessFunctionService {
|
||||
...buildEnvVar(flatApplicationVariables),
|
||||
};
|
||||
|
||||
// We keep that check to build functions
|
||||
if (
|
||||
!(await isServerlessFunctionBuilt({
|
||||
flatServerlessFunction,
|
||||
version,
|
||||
fileStorageService: this.fileStorageService,
|
||||
}))
|
||||
) {
|
||||
await buildAndUploadServerlessFunction({
|
||||
flatServerlessFunction,
|
||||
version,
|
||||
fileStorageService: this.fileStorageService,
|
||||
});
|
||||
}
|
||||
|
||||
const resultServerlessFunction = await this.callWithTimeout({
|
||||
callback: () =>
|
||||
this.serverlessService.execute({
|
||||
@@ -299,19 +317,38 @@ export class ServerlessFunctionService {
|
||||
? `${parseInt(existingFlatServerlessFunction.latestVersion, 10) + 1}`
|
||||
: '1';
|
||||
|
||||
const draftFolderPath = getServerlessFolderOrThrow({
|
||||
const draftSourceFolderPath = getServerlessFolderOrThrow({
|
||||
flatServerlessFunction: existingFlatServerlessFunction,
|
||||
version: 'draft',
|
||||
fileFolder: FileFolder.ServerlessFunction,
|
||||
});
|
||||
|
||||
const newFolderPath = getServerlessFolderOrThrow({
|
||||
const newSourceFolderPath = getServerlessFolderOrThrow({
|
||||
flatServerlessFunction: existingFlatServerlessFunction,
|
||||
version: newVersion,
|
||||
fileFolder: FileFolder.ServerlessFunction,
|
||||
});
|
||||
|
||||
await this.fileStorageService.copy({
|
||||
from: { folderPath: draftFolderPath },
|
||||
to: { folderPath: newFolderPath },
|
||||
from: { folderPath: draftSourceFolderPath },
|
||||
to: { folderPath: newSourceFolderPath },
|
||||
});
|
||||
|
||||
const draftBuiltFolderPath = getServerlessFolderOrThrow({
|
||||
flatServerlessFunction: existingFlatServerlessFunction,
|
||||
version: 'draft',
|
||||
fileFolder: FileFolder.BuiltFunction,
|
||||
});
|
||||
|
||||
const newBuiltFolderPath = getServerlessFolderOrThrow({
|
||||
flatServerlessFunction: existingFlatServerlessFunction,
|
||||
version: newVersion,
|
||||
fileFolder: FileFolder.BuiltFunction,
|
||||
});
|
||||
|
||||
await this.fileStorageService.copy({
|
||||
from: { folderPath: draftBuiltFolderPath },
|
||||
to: { folderPath: newBuiltFolderPath },
|
||||
});
|
||||
|
||||
const newPublishedVersions = [
|
||||
|
||||
+4
@@ -4,6 +4,7 @@ import { v4 } from 'uuid';
|
||||
import { DEFAULT_TOOL_INPUT_SCHEMA } from 'src/engine/metadata-modules/serverless-function/constants/default-tool-input-schema.constant';
|
||||
import { type CreateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function.input';
|
||||
import {
|
||||
DEFAULT_BUILT_HANDLER_PATH,
|
||||
DEFAULT_HANDLER_NAME,
|
||||
DEFAULT_HANDLER_PATH,
|
||||
ServerlessFunctionRuntime,
|
||||
@@ -38,6 +39,9 @@ export const fromCreateServerlessFunctionInputToFlatServerlessFunction = ({
|
||||
rawCreateServerlessFunctionInput.handlerPath ?? DEFAULT_HANDLER_PATH,
|
||||
handlerName:
|
||||
rawCreateServerlessFunctionInput.handlerName ?? DEFAULT_HANDLER_NAME,
|
||||
builtHandlerPath:
|
||||
rawCreateServerlessFunctionInput.builtHandlerPath ??
|
||||
DEFAULT_BUILT_HANDLER_PATH,
|
||||
universalIdentifier:
|
||||
rawCreateServerlessFunctionInput.universalIdentifier ?? v4(),
|
||||
createdAt: currentDate.toISOString(),
|
||||
|
||||
+1
@@ -44,6 +44,7 @@ export const fromFlatServerlessFunctionToServerlessFunctionDto = ({
|
||||
timeoutSeconds: flatServerlessFunction.timeoutSeconds,
|
||||
latestVersion: flatServerlessFunction.latestVersion ?? undefined,
|
||||
handlerPath: flatServerlessFunction.handlerPath,
|
||||
builtHandlerPath: flatServerlessFunction.builtHandlerPath,
|
||||
handlerName: flatServerlessFunction.handlerName,
|
||||
publishedVersions: flatServerlessFunction.publishedVersions,
|
||||
toolInputSchema: flatServerlessFunction.toolInputSchema ?? undefined,
|
||||
|
||||
Reference in New Issue
Block a user