Rename serverlessFunction to logicFunction (#17494)
## Summary Rename "Serverless Function" to "Logic Function" across the codebase for clearer naming. ### Environment Variable Changes | Old | New | |-----|-----| | `SERVERLESS_TYPE` | `LOGIC_FUNCTION_TYPE` | | `SERVERLESS_LAMBDA_REGION` | `LOGIC_FUNCTION_LAMBDA_REGION` | | `SERVERLESS_LAMBDA_ROLE` | `LOGIC_FUNCTION_LAMBDA_ROLE` | | `SERVERLESS_LAMBDA_SUBHOSTING_URL` | `LOGIC_FUNCTION_LAMBDA_SUBHOSTING_URL` | | `SERVERLESS_LAMBDA_ACCESS_KEY_ID` | `LOGIC_FUNCTION_LAMBDA_ACCESS_KEY_ID` | | `SERVERLESS_LAMBDA_SECRET_ACCESS_KEY` | `LOGIC_FUNCTION_LAMBDA_SECRET_ACCESS_KEY` | ### Breaking Changes - Environment variables must be updated in production deployments - Database migration renames `serverlessFunction` → `logicFunction` tables
This commit is contained in:
+1
-1
@@ -19,7 +19,7 @@ export class ApplicationExceptionFilter implements ExceptionFilter {
|
||||
case ApplicationExceptionCode.FIELD_NOT_FOUND:
|
||||
case ApplicationExceptionCode.ENTITY_NOT_FOUND:
|
||||
case ApplicationExceptionCode.APPLICATION_NOT_FOUND:
|
||||
case ApplicationExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND:
|
||||
case ApplicationExceptionCode.LOGIC_FUNCTION_NOT_FOUND:
|
||||
throw new NotFoundError(exception);
|
||||
case ApplicationExceptionCode.FORBIDDEN:
|
||||
case ApplicationExceptionCode.INVALID_INPUT:
|
||||
|
||||
+4
-4
@@ -15,8 +15,8 @@ import { ObjectPermissionModule } from 'src/engine/metadata-modules/object-permi
|
||||
import { PermissionFlagModule } from 'src/engine/metadata-modules/permission-flag/permission-flag.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { RoleModule } from 'src/engine/metadata-modules/role/role.module';
|
||||
import { ServerlessFunctionLayerModule } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.module';
|
||||
import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module';
|
||||
import { LogicFunctionLayerModule } from 'src/engine/metadata-modules/logic-function-layer/logic-function-layer.module';
|
||||
import { LogicFunctionModule } from 'src/engine/metadata-modules/logic-function/logic-function.module';
|
||||
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
||||
import { WorkspaceMigrationGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-graphql-api-exception.interceptor';
|
||||
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/workspace-migration-runner.module';
|
||||
@@ -32,8 +32,8 @@ import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-commo
|
||||
ObjectMetadataModule,
|
||||
FieldMetadataModule,
|
||||
DataSourceModule,
|
||||
ServerlessFunctionLayerModule,
|
||||
ServerlessFunctionModule,
|
||||
LogicFunctionLayerModule,
|
||||
LogicFunctionModule,
|
||||
WorkspaceMigrationModule,
|
||||
PermissionsModule,
|
||||
RoleModule,
|
||||
|
||||
+100
-102
@@ -9,8 +9,8 @@ import {
|
||||
ObjectManifest,
|
||||
RelationFieldManifest,
|
||||
RoleManifest,
|
||||
ServerlessFunctionManifest,
|
||||
ServerlessFunctionTriggerManifest,
|
||||
LogicFunctionManifest,
|
||||
LogicFunctionTriggerManifest,
|
||||
} from 'twenty-shared/application';
|
||||
import { FieldMetadataType, HTTPMethod, Sources } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@@ -35,16 +35,16 @@ import { FieldPermissionService } from 'src/engine/metadata-modules/object-permi
|
||||
import { ObjectPermissionService } from 'src/engine/metadata-modules/object-permission/object-permission.service';
|
||||
import { PermissionFlagService } from 'src/engine/metadata-modules/permission-flag/permission-flag.service';
|
||||
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
|
||||
import { ServerlessFunctionLayerService } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.service';
|
||||
import { ServerlessFunctionV2Service } from 'src/engine/metadata-modules/serverless-function/services/serverless-function-v2.service';
|
||||
import { FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
|
||||
import { LogicFunctionLayerService } from 'src/engine/metadata-modules/logic-function-layer/logic-function-layer.service';
|
||||
import { LogicFunctionV2Service } from 'src/engine/metadata-modules/logic-function/services/logic-function-v2.service';
|
||||
import { FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
|
||||
import { computeMetadataNameFromLabelOrThrow } from 'src/engine/metadata-modules/utils/compute-metadata-name-from-label-or-throw.util';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
|
||||
import {
|
||||
CronTriggerSettings,
|
||||
DatabaseEventTriggerSettings,
|
||||
HttpRouteTriggerSettings,
|
||||
} from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
} from 'src/engine/metadata-modules/logic-function/logic-function.entity';
|
||||
|
||||
@Injectable()
|
||||
export class ApplicationSyncService {
|
||||
@@ -53,10 +53,10 @@ export class ApplicationSyncService {
|
||||
constructor(
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly applicationVariableService: ApplicationVariableEntityService,
|
||||
private readonly serverlessFunctionLayerService: ServerlessFunctionLayerService,
|
||||
private readonly logicFunctionLayerService: LogicFunctionLayerService,
|
||||
private readonly objectMetadataService: ObjectMetadataService,
|
||||
private readonly fieldMetadataService: FieldMetadataService,
|
||||
private readonly serverlessFunctionV2Service: ServerlessFunctionV2Service,
|
||||
private readonly logicFunctionV2Service: LogicFunctionV2Service,
|
||||
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly dataSourceService: DataSourceService,
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
@@ -102,19 +102,19 @@ export class ApplicationSyncService {
|
||||
}
|
||||
|
||||
if (manifest.functions.length > 0) {
|
||||
if (!isDefined(application.serverlessFunctionLayerId)) {
|
||||
if (!isDefined(application.logicFunctionLayerId)) {
|
||||
throw new ApplicationException(
|
||||
`Failed to sync serverless function, could not find a serverless function layer.`,
|
||||
`Failed to sync logic function, could not find a logic function layer.`,
|
||||
ApplicationExceptionCode.FIELD_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
await this.syncServerlessFunctions({
|
||||
serverlessFunctionsToSync: manifest.functions,
|
||||
await this.syncLogicFunctions({
|
||||
logicFunctionsToSync: manifest.functions,
|
||||
code: manifest.sources,
|
||||
workspaceId,
|
||||
applicationId: application.id,
|
||||
serverlessFunctionLayerId: application.serverlessFunctionLayerId,
|
||||
logicFunctionLayerId: application.logicFunctionLayerId,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -147,17 +147,17 @@ export class ApplicationSyncService {
|
||||
description: manifest.application.description,
|
||||
version: packageJson.version,
|
||||
sourcePath: 'cli-sync', // Placeholder for CLI-synced apps
|
||||
serverlessFunctionLayerId: null,
|
||||
defaultServerlessFunctionRoleId: null,
|
||||
logicFunctionLayerId: null,
|
||||
defaultLogicFunctionRoleId: null,
|
||||
workspaceId,
|
||||
}));
|
||||
|
||||
let serverlessFunctionLayerId = application.serverlessFunctionLayerId;
|
||||
let logicFunctionLayerId = application.logicFunctionLayerId;
|
||||
|
||||
if (manifest.functions.length > 0) {
|
||||
if (!isDefined(serverlessFunctionLayerId)) {
|
||||
serverlessFunctionLayerId = (
|
||||
await this.serverlessFunctionLayerService.create(
|
||||
if (!isDefined(logicFunctionLayerId)) {
|
||||
logicFunctionLayerId = (
|
||||
await this.logicFunctionLayerService.create(
|
||||
{
|
||||
packageJson,
|
||||
yarnLock,
|
||||
@@ -167,8 +167,8 @@ export class ApplicationSyncService {
|
||||
).id;
|
||||
}
|
||||
|
||||
await this.serverlessFunctionLayerService.update(
|
||||
serverlessFunctionLayerId,
|
||||
await this.logicFunctionLayerService.update(
|
||||
logicFunctionLayerId,
|
||||
{
|
||||
packageJson,
|
||||
yarnLock,
|
||||
@@ -189,8 +189,8 @@ export class ApplicationSyncService {
|
||||
name,
|
||||
description: manifest.application.description,
|
||||
version: packageJson.version,
|
||||
serverlessFunctionLayerId,
|
||||
defaultServerlessFunctionRoleId: null,
|
||||
logicFunctionLayerId,
|
||||
defaultLogicFunctionRoleId: null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ export class ApplicationSyncService {
|
||||
workspaceId: string;
|
||||
applicationId: string;
|
||||
}) {
|
||||
let defaultServerlessFunctionRoleId: string | null = null;
|
||||
let defaultLogicFunctionRoleId: string | null = null;
|
||||
|
||||
for (const role of manifest.roles ?? []) {
|
||||
let existingRole = await this.roleService.getRoleByUniversalIdentifier({
|
||||
@@ -237,13 +237,13 @@ export class ApplicationSyncService {
|
||||
existingRole.universalIdentifier ===
|
||||
manifest.application.functionRoleUniversalIdentifier
|
||||
) {
|
||||
defaultServerlessFunctionRoleId = existingRole.id;
|
||||
defaultLogicFunctionRoleId = existingRole.id;
|
||||
}
|
||||
}
|
||||
|
||||
if (isDefined(defaultServerlessFunctionRoleId)) {
|
||||
if (isDefined(defaultLogicFunctionRoleId)) {
|
||||
await this.applicationService.update(applicationId, {
|
||||
defaultServerlessFunctionRoleId: defaultServerlessFunctionRoleId,
|
||||
defaultLogicFunctionRoleId: defaultLogicFunctionRoleId,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -849,149 +849,147 @@ export class ApplicationSyncService {
|
||||
}
|
||||
}
|
||||
|
||||
private async syncServerlessFunctions({
|
||||
serverlessFunctionsToSync,
|
||||
private async syncLogicFunctions({
|
||||
logicFunctionsToSync,
|
||||
code,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
serverlessFunctionLayerId,
|
||||
logicFunctionLayerId,
|
||||
}: {
|
||||
serverlessFunctionsToSync: ServerlessFunctionManifest[];
|
||||
logicFunctionsToSync: LogicFunctionManifest[];
|
||||
workspaceId: string;
|
||||
code: Sources;
|
||||
applicationId: string;
|
||||
serverlessFunctionLayerId: string;
|
||||
logicFunctionLayerId: string;
|
||||
}) {
|
||||
const { flatServerlessFunctionMaps } =
|
||||
const { flatLogicFunctionMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatServerlessFunctionMaps'],
|
||||
flatMapsKeys: ['flatLogicFunctionMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const applicationServerlessFunctions = Object.values(
|
||||
flatServerlessFunctionMaps.byId,
|
||||
const applicationLogicFunctions = Object.values(
|
||||
flatLogicFunctionMaps.byId,
|
||||
).filter(
|
||||
(serverlessFunction) =>
|
||||
isDefined(serverlessFunction) &&
|
||||
serverlessFunction.applicationId === applicationId,
|
||||
) as FlatServerlessFunction[];
|
||||
(logicFunction) =>
|
||||
isDefined(logicFunction) &&
|
||||
logicFunction.applicationId === applicationId,
|
||||
) as FlatLogicFunction[];
|
||||
|
||||
const serverlessFunctionsToSyncUniversalIdentifiers =
|
||||
serverlessFunctionsToSync.map(
|
||||
(serverlessFunction) => serverlessFunction.universalIdentifier,
|
||||
const logicFunctionsToSyncUniversalIdentifiers = logicFunctionsToSync.map(
|
||||
(logicFunction) => logicFunction.universalIdentifier,
|
||||
);
|
||||
|
||||
const applicationLogicFunctionsUniversalIdentifiers =
|
||||
applicationLogicFunctions.map(
|
||||
(logicFunction) => logicFunction.universalIdentifier,
|
||||
);
|
||||
|
||||
const applicationServerlessFunctionsUniversalIdentifiers =
|
||||
applicationServerlessFunctions.map(
|
||||
(serverlessFunction) => serverlessFunction.universalIdentifier,
|
||||
);
|
||||
|
||||
const serverlessFunctionsToDelete = applicationServerlessFunctions.filter(
|
||||
(serverlessFunction) =>
|
||||
isDefined(serverlessFunction.universalIdentifier) &&
|
||||
!serverlessFunctionsToSyncUniversalIdentifiers.includes(
|
||||
serverlessFunction.universalIdentifier,
|
||||
const logicFunctionsToDelete = applicationLogicFunctions.filter(
|
||||
(logicFunction) =>
|
||||
isDefined(logicFunction.universalIdentifier) &&
|
||||
!logicFunctionsToSyncUniversalIdentifiers.includes(
|
||||
logicFunction.universalIdentifier,
|
||||
),
|
||||
);
|
||||
|
||||
const serverlessFunctionsToUpdate = applicationServerlessFunctions.filter(
|
||||
(serverlessFunction) =>
|
||||
isDefined(serverlessFunction.universalIdentifier) &&
|
||||
serverlessFunctionsToSyncUniversalIdentifiers.includes(
|
||||
serverlessFunction.universalIdentifier,
|
||||
const logicFunctionsToUpdate = applicationLogicFunctions.filter(
|
||||
(logicFunction) =>
|
||||
isDefined(logicFunction.universalIdentifier) &&
|
||||
logicFunctionsToSyncUniversalIdentifiers.includes(
|
||||
logicFunction.universalIdentifier,
|
||||
),
|
||||
);
|
||||
|
||||
const serverlessFunctionsToCreate = serverlessFunctionsToSync.filter(
|
||||
(serverlessFunctionToSync) =>
|
||||
!applicationServerlessFunctionsUniversalIdentifiers.includes(
|
||||
serverlessFunctionToSync.universalIdentifier,
|
||||
const logicFunctionsToCreate = logicFunctionsToSync.filter(
|
||||
(logicFunctionToSync) =>
|
||||
!applicationLogicFunctionsUniversalIdentifiers.includes(
|
||||
logicFunctionToSync.universalIdentifier,
|
||||
),
|
||||
);
|
||||
|
||||
for (const serverlessFunctionToDelete of serverlessFunctionsToDelete) {
|
||||
await this.serverlessFunctionV2Service.destroyOne({
|
||||
destroyServerlessFunctionInput: { id: serverlessFunctionToDelete.id },
|
||||
for (const logicFunctionToDelete of logicFunctionsToDelete) {
|
||||
await this.logicFunctionV2Service.destroyOne({
|
||||
destroyLogicFunctionInput: { id: logicFunctionToDelete.id },
|
||||
workspaceId,
|
||||
isSystemBuild: true,
|
||||
});
|
||||
}
|
||||
|
||||
for (const serverlessFunctionToUpdate of serverlessFunctionsToUpdate) {
|
||||
const serverlessFunctionToSync = serverlessFunctionsToSync.find(
|
||||
(serverlessFunction) =>
|
||||
serverlessFunction.universalIdentifier ===
|
||||
serverlessFunctionToUpdate.universalIdentifier,
|
||||
for (const logicFunctionToUpdate of logicFunctionsToUpdate) {
|
||||
const logicFunctionToSync = logicFunctionsToSync.find(
|
||||
(logicFunction) =>
|
||||
logicFunction.universalIdentifier ===
|
||||
logicFunctionToUpdate.universalIdentifier,
|
||||
);
|
||||
|
||||
if (!serverlessFunctionToSync) {
|
||||
if (!logicFunctionToSync) {
|
||||
throw new ApplicationException(
|
||||
`Failed to find serverlessFunction to sync with universalIdentifier ${serverlessFunctionToUpdate.universalIdentifier}`,
|
||||
ApplicationExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND,
|
||||
`Failed to find logicFunction to sync with universalIdentifier ${logicFunctionToUpdate.universalIdentifier}`,
|
||||
ApplicationExceptionCode.LOGIC_FUNCTION_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const name =
|
||||
serverlessFunctionToSync.name ??
|
||||
parse(serverlessFunctionToSync.handlerName).name;
|
||||
logicFunctionToSync.name ?? parse(logicFunctionToSync.handlerName).name;
|
||||
|
||||
const updateServerlessFunctionInput = {
|
||||
id: serverlessFunctionToUpdate.id,
|
||||
const updateLogicFunctionInput = {
|
||||
id: logicFunctionToUpdate.id,
|
||||
update: {
|
||||
name,
|
||||
code,
|
||||
timeoutSeconds: serverlessFunctionToSync.timeoutSeconds,
|
||||
sourceHandlerPath: serverlessFunctionToSync.sourceHandlerPath,
|
||||
builtHandlerPath: serverlessFunctionToSync.builtHandlerPath,
|
||||
handlerName: serverlessFunctionToSync.handlerName,
|
||||
toolInputSchema: serverlessFunctionToSync.toolInputSchema,
|
||||
isTool: serverlessFunctionToSync.isTool,
|
||||
timeoutSeconds: logicFunctionToSync.timeoutSeconds,
|
||||
sourceHandlerPath: logicFunctionToSync.sourceHandlerPath,
|
||||
builtHandlerPath: logicFunctionToSync.builtHandlerPath,
|
||||
handlerName: logicFunctionToSync.handlerName,
|
||||
toolInputSchema: logicFunctionToSync.toolInputSchema,
|
||||
isTool: logicFunctionToSync.isTool,
|
||||
},
|
||||
};
|
||||
|
||||
await this.serverlessFunctionV2Service.updateOne(
|
||||
updateServerlessFunctionInput,
|
||||
await this.logicFunctionV2Service.updateOne(
|
||||
updateLogicFunctionInput,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
// Trigger settings are now embedded in the serverless function entity
|
||||
// Trigger settings are now embedded in the logic function entity
|
||||
// They are handled through the update input
|
||||
}
|
||||
|
||||
for (const serverlessFunctionToCreate of serverlessFunctionsToCreate) {
|
||||
for (const logicFunctionToCreate of logicFunctionsToCreate) {
|
||||
const name =
|
||||
serverlessFunctionToCreate.name ??
|
||||
parse(serverlessFunctionToCreate.handlerName).name;
|
||||
logicFunctionToCreate.name ??
|
||||
parse(logicFunctionToCreate.handlerName).name;
|
||||
|
||||
const createServerlessFunctionInput = {
|
||||
const createLogicFunctionInput = {
|
||||
name,
|
||||
code,
|
||||
universalIdentifier: serverlessFunctionToCreate.universalIdentifier,
|
||||
timeoutSeconds: serverlessFunctionToCreate.timeoutSeconds,
|
||||
sourceHandlerPath: serverlessFunctionToCreate.sourceHandlerPath,
|
||||
handlerName: serverlessFunctionToCreate.handlerName,
|
||||
builtHandlerPath: serverlessFunctionToCreate.builtHandlerPath,
|
||||
universalIdentifier: logicFunctionToCreate.universalIdentifier,
|
||||
timeoutSeconds: logicFunctionToCreate.timeoutSeconds,
|
||||
sourceHandlerPath: logicFunctionToCreate.sourceHandlerPath,
|
||||
handlerName: logicFunctionToCreate.handlerName,
|
||||
builtHandlerPath: logicFunctionToCreate.builtHandlerPath,
|
||||
applicationId,
|
||||
serverlessFunctionLayerId,
|
||||
toolInputSchema: serverlessFunctionToCreate.toolInputSchema,
|
||||
isTool: serverlessFunctionToCreate.isTool,
|
||||
logicFunctionLayerId,
|
||||
toolInputSchema: logicFunctionToCreate.toolInputSchema,
|
||||
isTool: logicFunctionToCreate.isTool,
|
||||
};
|
||||
|
||||
await this.serverlessFunctionV2Service.createOne({
|
||||
createServerlessFunctionInput,
|
||||
await this.logicFunctionV2Service.createOne({
|
||||
createLogicFunctionInput,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
});
|
||||
|
||||
// Trigger settings are now embedded in the serverless function entity
|
||||
// Trigger settings are now embedded in the logic function entity
|
||||
// They are handled through the create input
|
||||
}
|
||||
}
|
||||
|
||||
private extractTriggerSettingsFromManifest(
|
||||
triggers: ServerlessFunctionTriggerManifest[] = [],
|
||||
triggers: LogicFunctionTriggerManifest[] = [],
|
||||
): {
|
||||
cronTriggerSettings: CronTriggerSettings | null;
|
||||
databaseEventTriggerSettings: DatabaseEventTriggerSettings | null;
|
||||
|
||||
@@ -16,7 +16,7 @@ import { ApplicationVariableEntity } from 'src/engine/core-modules/applicationVa
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
|
||||
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
|
||||
import { WorkspaceRelatedEntity } from 'src/engine/workspace-manager/types/workspace-related-entity';
|
||||
|
||||
@Entity({ name: 'application', schema: 'core' })
|
||||
@@ -54,13 +54,13 @@ export class ApplicationEntity extends WorkspaceRelatedEntity {
|
||||
sourcePath: string;
|
||||
|
||||
@Column({ nullable: true, type: 'uuid' })
|
||||
serverlessFunctionLayerId: string | null;
|
||||
logicFunctionLayerId: string | null;
|
||||
|
||||
@Column({ nullable: true, type: 'uuid' })
|
||||
defaultServerlessFunctionRoleId: string | null;
|
||||
defaultLogicFunctionRoleId: string | null;
|
||||
|
||||
@Field(() => RoleDTO, { nullable: true })
|
||||
defaultServerlessFunctionRole: RoleDTO | null;
|
||||
defaultLogicFunctionRole: RoleDTO | null;
|
||||
|
||||
@Column({ nullable: false, type: 'boolean', default: true })
|
||||
canBeUninstalled: boolean;
|
||||
@@ -71,13 +71,13 @@ export class ApplicationEntity extends WorkspaceRelatedEntity {
|
||||
agents: Relation<AgentEntity[]>;
|
||||
|
||||
@OneToMany(
|
||||
() => ServerlessFunctionEntity,
|
||||
(serverlessFunction) => serverlessFunction.application,
|
||||
() => LogicFunctionEntity,
|
||||
(logicFunction) => logicFunction.application,
|
||||
{
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
)
|
||||
serverlessFunctions: Relation<ServerlessFunctionEntity[]>;
|
||||
logicFunctions: Relation<LogicFunctionEntity[]>;
|
||||
|
||||
@OneToMany(() => ObjectMetadataEntity, (object) => object.application, {
|
||||
onDelete: 'CASCADE',
|
||||
|
||||
@@ -7,7 +7,7 @@ import { CustomException } from 'src/utils/custom-exception';
|
||||
export enum ApplicationExceptionCode {
|
||||
OBJECT_NOT_FOUND = 'OBJECT_NOT_FOUND',
|
||||
FIELD_NOT_FOUND = 'FIELD_NOT_FOUND',
|
||||
SERVERLESS_FUNCTION_NOT_FOUND = 'SERVERLESS_FUNCTION_NOT_FOUND',
|
||||
LOGIC_FUNCTION_NOT_FOUND = 'LOGIC_FUNCTION_NOT_FOUND',
|
||||
ENTITY_NOT_FOUND = 'ENTITY_NOT_FOUND',
|
||||
APPLICATION_NOT_FOUND = 'APPLICATION_NOT_FOUND',
|
||||
FORBIDDEN = 'FORBIDDEN',
|
||||
@@ -22,8 +22,8 @@ const getApplicationExceptionUserFriendlyMessage = (
|
||||
return msg`Object not found.`;
|
||||
case ApplicationExceptionCode.FIELD_NOT_FOUND:
|
||||
return msg`Field not found.`;
|
||||
case ApplicationExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND:
|
||||
return msg`Serverless function not found.`;
|
||||
case ApplicationExceptionCode.LOGIC_FUNCTION_NOT_FOUND:
|
||||
return msg`Logic function not found.`;
|
||||
case ApplicationExceptionCode.ENTITY_NOT_FOUND:
|
||||
return msg`Entity not found.`;
|
||||
case ApplicationExceptionCode.APPLICATION_NOT_FOUND:
|
||||
|
||||
@@ -35,7 +35,7 @@ export class ApplicationService {
|
||||
|
||||
if (
|
||||
!isDefined(application) ||
|
||||
!isDefined(application.defaultServerlessFunctionRoleId)
|
||||
!isDefined(application.defaultLogicFunctionRoleId)
|
||||
) {
|
||||
throw new ApplicationException(
|
||||
`Could not find application ${applicationId}`,
|
||||
@@ -43,7 +43,7 @@ export class ApplicationService {
|
||||
);
|
||||
}
|
||||
|
||||
return application.defaultServerlessFunctionRoleId;
|
||||
return application.defaultLogicFunctionRoleId;
|
||||
}
|
||||
|
||||
async findWorkspaceTwentyStandardAndCustomApplicationOrThrow({
|
||||
@@ -118,7 +118,7 @@ export class ApplicationService {
|
||||
return this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
relations: [
|
||||
'serverlessFunctions',
|
||||
'logicFunctions',
|
||||
'agents',
|
||||
'objects',
|
||||
'applicationVariables',
|
||||
@@ -133,7 +133,7 @@ export class ApplicationService {
|
||||
const application = await this.applicationRepository.findOne({
|
||||
where: { workspaceId, id: applicationId },
|
||||
relations: [
|
||||
'serverlessFunctions',
|
||||
'logicFunctions',
|
||||
'agents',
|
||||
'objects',
|
||||
'applicationVariables',
|
||||
@@ -210,7 +210,7 @@ export class ApplicationService {
|
||||
const twentyStandardApplication = await this.create(
|
||||
{
|
||||
...TWENTY_STANDARD_APPLICATION,
|
||||
serverlessFunctionLayerId: null,
|
||||
logicFunctionLayerId: null,
|
||||
workspaceId,
|
||||
canBeUninstalled: false,
|
||||
},
|
||||
@@ -246,7 +246,7 @@ export class ApplicationService {
|
||||
universalIdentifier: applicationId,
|
||||
workspaceId: workspaceId,
|
||||
id: applicationId,
|
||||
serverlessFunctionLayerId: null,
|
||||
logicFunctionLayerId: null,
|
||||
canBeUninstalled: false,
|
||||
},
|
||||
queryRunner,
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { type ApplicationEntity } from 'src/engine/core-modules/application/appl
|
||||
export const APPLICATION_ENTITY_RELATION_PROPERTIES = [
|
||||
'workspace',
|
||||
'agents',
|
||||
'serverlessFunctions',
|
||||
'logicFunctions',
|
||||
'objects',
|
||||
'applicationVariables',
|
||||
] as const satisfies (keyof ApplicationEntity)[];
|
||||
|
||||
@@ -12,7 +12,7 @@ import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/
|
||||
import { ApplicationVariableEntityDTO } from 'src/engine/core-modules/applicationVariable/dtos/application-variable.dto';
|
||||
import { AgentDTO } from 'src/engine/metadata-modules/ai/ai-agent/dtos/agent.dto';
|
||||
import { ObjectMetadataDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto';
|
||||
import { ServerlessFunctionDTO } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function.dto';
|
||||
import { LogicFunctionDTO } from 'src/engine/metadata-modules/logic-function/dtos/logic-function.dto';
|
||||
import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
|
||||
|
||||
@ObjectType('Application')
|
||||
@@ -47,17 +47,17 @@ export class ApplicationDTO {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@Field({ nullable: true })
|
||||
defaultServerlessFunctionRoleId?: string;
|
||||
defaultLogicFunctionRoleId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Field(() => RoleDTO, { nullable: true })
|
||||
defaultServerlessFunctionRole?: RoleDTO;
|
||||
defaultLogicFunctionRole?: RoleDTO;
|
||||
|
||||
@Field(() => [AgentDTO])
|
||||
agents?: AgentDTO[];
|
||||
|
||||
@Field(() => [ServerlessFunctionDTO])
|
||||
serverlessFunctions?: ServerlessFunctionDTO[];
|
||||
@Field(() => [LogicFunctionDTO])
|
||||
logicFunctions?: LogicFunctionDTO[];
|
||||
|
||||
@Field(() => [ObjectMetadataDTO])
|
||||
objects?: ObjectMetadataDTO[];
|
||||
|
||||
@@ -27,9 +27,9 @@ import {
|
||||
type MonitoringTrackEvent,
|
||||
} from 'src/engine/core-modules/audit/utils/events/workspace-event/monitoring/monitoring';
|
||||
import {
|
||||
type SERVERLESS_FUNCTION_EXECUTED_EVENT,
|
||||
type ServerlessFunctionExecutedTrackEvent,
|
||||
} from 'src/engine/core-modules/audit/utils/events/workspace-event/serverless-function/serverless-function-executed';
|
||||
type LOGIC_FUNCTION_EXECUTED_EVENT,
|
||||
type LogicFunctionExecutedTrackEvent,
|
||||
} from 'src/engine/core-modules/audit/utils/events/workspace-event/logic-function/logic-function-executed';
|
||||
import {
|
||||
type USER_SIGNUP_EVENT,
|
||||
type UserSignupTrackEvent,
|
||||
@@ -47,7 +47,7 @@ import {
|
||||
export type TrackEventName =
|
||||
| typeof CUSTOM_DOMAIN_ACTIVATED_EVENT
|
||||
| typeof CUSTOM_DOMAIN_DEACTIVATED_EVENT
|
||||
| typeof SERVERLESS_FUNCTION_EXECUTED_EVENT
|
||||
| typeof LOGIC_FUNCTION_EXECUTED_EVENT
|
||||
| typeof WEBHOOK_RESPONSE_EVENT
|
||||
| typeof WORKSPACE_ENTITY_CREATED_EVENT
|
||||
| typeof MONITORING_EVENT
|
||||
@@ -61,7 +61,7 @@ export type TrackEventName =
|
||||
export interface TrackEvents {
|
||||
[CUSTOM_DOMAIN_ACTIVATED_EVENT]: CustomDomainActivatedTrackEvent;
|
||||
[CUSTOM_DOMAIN_DEACTIVATED_EVENT]: CustomDomainDeactivatedTrackEvent;
|
||||
[SERVERLESS_FUNCTION_EXECUTED_EVENT]: ServerlessFunctionExecutedTrackEvent;
|
||||
[LOGIC_FUNCTION_EXECUTED_EVENT]: LogicFunctionExecutedTrackEvent;
|
||||
[WEBHOOK_RESPONSE_EVENT]: WebhookResponseTrackEvent;
|
||||
[WORKSPACE_ENTITY_CREATED_EVENT]: WorkspaceEntityCreatedTrackEvent;
|
||||
[USER_SIGNUP_EVENT]: UserSignupTrackEvent;
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { registerEvent } from 'src/engine/core-modules/audit/utils/events/workspace-event/track';
|
||||
|
||||
export const LOGIC_FUNCTION_EXECUTED_EVENT = 'Logic Function Executed' as const;
|
||||
export const logicFunctionExecutedSchema = z.strictObject({
|
||||
event: z.literal(LOGIC_FUNCTION_EXECUTED_EVENT),
|
||||
properties: z.strictObject({
|
||||
duration: z.number(),
|
||||
status: z.enum(['IDLE', 'SUCCESS', 'ERROR']),
|
||||
errorType: z.string().optional(),
|
||||
functionId: z.string(),
|
||||
functionName: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export type LogicFunctionExecutedTrackEvent = z.infer<
|
||||
typeof logicFunctionExecutedSchema
|
||||
>;
|
||||
|
||||
registerEvent(LOGIC_FUNCTION_EXECUTED_EVENT, logicFunctionExecutedSchema);
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { registerEvent } from 'src/engine/core-modules/audit/utils/events/workspace-event/track';
|
||||
|
||||
export const SERVERLESS_FUNCTION_EXECUTED_EVENT =
|
||||
'Serverless Function Executed' as const;
|
||||
export const serverlessFunctionExecutedSchema = z.strictObject({
|
||||
event: z.literal(SERVERLESS_FUNCTION_EXECUTED_EVENT),
|
||||
properties: z.strictObject({
|
||||
duration: z.number(),
|
||||
status: z.enum(['IDLE', 'SUCCESS', 'ERROR']),
|
||||
errorType: z.string().optional(),
|
||||
functionId: z.string(),
|
||||
functionName: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export type ServerlessFunctionExecutedTrackEvent = z.infer<
|
||||
typeof serverlessFunctionExecutedSchema
|
||||
>;
|
||||
|
||||
registerEvent(
|
||||
SERVERLESS_FUNCTION_EXECUTED_EVENT,
|
||||
serverlessFunctionExecutedSchema,
|
||||
);
|
||||
@@ -47,8 +47,8 @@ import { PublicDomainModule } from 'src/engine/core-modules/public-domain/public
|
||||
import { RedisClientModule } from 'src/engine/core-modules/redis-client/redis-client.module';
|
||||
import { RedisClientService } from 'src/engine/core-modules/redis-client/redis-client.service';
|
||||
import { SearchModule } from 'src/engine/core-modules/search/search.module';
|
||||
import { serverlessModuleFactory } from 'src/engine/core-modules/serverless/serverless-module.factory';
|
||||
import { ServerlessModule } from 'src/engine/core-modules/serverless/serverless.module';
|
||||
import { logicFunctionExecutorModuleFactory } from 'src/engine/core-modules/logic-function-executor/logic-function-executor-module.factory';
|
||||
import { LogicFunctionExecutorModule } from 'src/engine/core-modules/logic-function-executor/logic-function-executor.module';
|
||||
import { WorkspaceSSOModule } from 'src/engine/core-modules/sso/sso.module';
|
||||
import { TelemetryModule } from 'src/engine/core-modules/telemetry/telemetry.module';
|
||||
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
|
||||
@@ -141,8 +141,8 @@ import { FileModule } from './file/file.module';
|
||||
CacheStorageModule,
|
||||
AiModelsModule,
|
||||
AiBillingModule,
|
||||
ServerlessModule.forRootAsync({
|
||||
useFactory: serverlessModuleFactory,
|
||||
LogicFunctionExecutorModule.forRootAsync({
|
||||
useFactory: logicFunctionExecutorModuleFactory,
|
||||
inject: [TwentyConfigService, FileStorageService],
|
||||
}),
|
||||
CodeInterpreterModule.forRootAsync({
|
||||
|
||||
+2
-2
@@ -24,10 +24,10 @@ export const fileFolderConfigs: Record<FileFolder, FileFolderConfig> = {
|
||||
[FileFolder.PersonPicture]: {
|
||||
ignoreExpirationToken: false,
|
||||
},
|
||||
[FileFolder.ServerlessFunction]: {
|
||||
[FileFolder.LogicFunction]: {
|
||||
ignoreExpirationToken: false,
|
||||
},
|
||||
[FileFolder.ServerlessFunctionToDelete]: {
|
||||
[FileFolder.LogicFunctionToDelete]: {
|
||||
ignoreExpirationToken: false,
|
||||
},
|
||||
[FileFolder.File]: {
|
||||
|
||||
@@ -20,7 +20,7 @@ export const checkFilePath = (filePath: string): string => {
|
||||
}
|
||||
|
||||
if (
|
||||
folder !== kebabCase(FileFolder.ServerlessFunction) &&
|
||||
folder !== kebabCase(FileFolder.LogicFunction) &&
|
||||
size &&
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
!settings.storage.imageCropSizes[folder]?.includes(size)
|
||||
|
||||
+5
-5
@@ -10,9 +10,9 @@ import { Command, CommandRunner, Option } from 'nest-commander';
|
||||
const execFilePromise = promisify(execFile);
|
||||
|
||||
@Command({
|
||||
name: 'serverless:add-packages',
|
||||
name: 'logic-function-executor:add-packages',
|
||||
description:
|
||||
'Create a new serverless layer version and install packages in it',
|
||||
'Create a new logic function executor layer version and install packages in it',
|
||||
})
|
||||
export class AddPackagesCommand extends CommandRunner {
|
||||
private readonly logger = new Logger(AddPackagesCommand.name);
|
||||
@@ -36,7 +36,7 @@ export class AddPackagesCommand extends CommandRunner {
|
||||
this.logger.log('');
|
||||
|
||||
const layersFolder = this.getAbsoluteFilePath(
|
||||
`src/engine/core-modules/serverless/drivers/layers`,
|
||||
`src/engine/core-modules/logic-function-executor/drivers/layers`,
|
||||
);
|
||||
|
||||
const currentVersion = await this.getLastLayerVersion();
|
||||
@@ -108,7 +108,7 @@ export class AddPackagesCommand extends CommandRunner {
|
||||
|
||||
private async getLastLayerVersion() {
|
||||
const filePath = this.getAbsoluteFilePath(
|
||||
'src/engine/core-modules/serverless/drivers/layers/last-layer-version.ts',
|
||||
'src/engine/core-modules/logic-function-executor/drivers/layers/last-layer-version.ts',
|
||||
);
|
||||
|
||||
const content = await fs.readFile(filePath, 'utf8');
|
||||
@@ -123,7 +123,7 @@ export class AddPackagesCommand extends CommandRunner {
|
||||
|
||||
private async updateLastLayerVersion(newVersion: number) {
|
||||
const filePath = this.getAbsoluteFilePath(
|
||||
'src/engine/core-modules/serverless/drivers/layers/last-layer-version.ts',
|
||||
'src/engine/core-modules/logic-function-executor/drivers/layers/last-layer-version.ts',
|
||||
);
|
||||
|
||||
await fs.writeFile(
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { join } from 'path';
|
||||
import { tmpdir } from 'os';
|
||||
|
||||
export const LOGIC_FUNCTION_EXECUTOR_TMPDIR_FOLDER = join(
|
||||
tmpdir(),
|
||||
'logic-function-executor-tmpdir',
|
||||
);
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import {
|
||||
type LogicFunctionExecutorDriver,
|
||||
type LogicFunctionExecuteResult,
|
||||
} from 'src/engine/core-modules/logic-function-executor/drivers/interfaces/logic-function-executor-driver.interface';
|
||||
|
||||
import {
|
||||
LogicFunctionException,
|
||||
LogicFunctionExceptionCode,
|
||||
} from 'src/engine/metadata-modules/logic-function/logic-function.exception';
|
||||
|
||||
export class DisabledDriver implements LogicFunctionExecutorDriver {
|
||||
async delete(): Promise<void> {
|
||||
// No-op when disabled
|
||||
}
|
||||
|
||||
async execute(): Promise<LogicFunctionExecuteResult> {
|
||||
throw new LogicFunctionException(
|
||||
'Logic function execution is disabled. Set LOGIC_FUNCTION_TYPE to LOCAL or LAMBDA to enable.',
|
||||
LogicFunctionExceptionCode.LOGIC_FUNCTION_DISABLED,
|
||||
);
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import { type FlatLogicFunctionLayer } from 'src/engine/metadata-modules/logic-function-layer/types/flat-logic-function-layer.type';
|
||||
import { type LogicFunctionExecutionStatus } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-execution-result.dto';
|
||||
import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
|
||||
|
||||
export type LogicFunctionExecuteError = {
|
||||
errorType: string;
|
||||
errorMessage: string;
|
||||
stackTrace: string | string[];
|
||||
};
|
||||
|
||||
export type LogicFunctionExecuteResult = {
|
||||
data: object | null;
|
||||
duration: number;
|
||||
logs: string;
|
||||
status: LogicFunctionExecutionStatus;
|
||||
error?: LogicFunctionExecuteError;
|
||||
};
|
||||
|
||||
export interface LogicFunctionExecutorDriver {
|
||||
delete(flatLogicFunction: FlatLogicFunction): Promise<void>;
|
||||
execute({
|
||||
flatLogicFunction,
|
||||
flatLogicFunctionLayer,
|
||||
payload,
|
||||
version,
|
||||
env,
|
||||
}: {
|
||||
flatLogicFunction: FlatLogicFunction;
|
||||
flatLogicFunctionLayer: FlatLogicFunctionLayer;
|
||||
payload: object;
|
||||
version: string;
|
||||
env?: Record<string, string>;
|
||||
}): Promise<LogicFunctionExecuteResult>;
|
||||
}
|
||||
+59
-70
@@ -23,27 +23,27 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import {
|
||||
type ServerlessDriver,
|
||||
type ServerlessExecuteResult,
|
||||
} from 'src/engine/core-modules/serverless/drivers/interfaces/serverless-driver.interface';
|
||||
type LogicFunctionExecutorDriver,
|
||||
type LogicFunctionExecuteResult,
|
||||
} from 'src/engine/core-modules/logic-function-executor/drivers/interfaces/logic-function-executor-driver.interface';
|
||||
|
||||
import { type FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { copyAndBuildDependencies } from 'src/engine/core-modules/serverless/drivers/utils/copy-and-build-dependencies';
|
||||
import { copyExecutor } from 'src/engine/core-modules/serverless/drivers/utils/copy-executor';
|
||||
import { createZipFile } from 'src/engine/core-modules/serverless/drivers/utils/create-zip-file';
|
||||
import { copyAndBuildDependencies } from 'src/engine/core-modules/logic-function-executor/drivers/utils/copy-and-build-dependencies';
|
||||
import { copyExecutor } from 'src/engine/core-modules/logic-function-executor/drivers/utils/copy-executor';
|
||||
import { createZipFile } from 'src/engine/core-modules/logic-function-executor/drivers/utils/create-zip-file';
|
||||
import {
|
||||
LambdaBuildDirectoryManager,
|
||||
NODE_LAYER_SUBFOLDER,
|
||||
} 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 FlatServerlessFunctionLayer } from 'src/engine/metadata-modules/serverless-function-layer/types/flat-serverless-function-layer.type';
|
||||
import { ServerlessFunctionExecutionStatus } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function-execution-result.dto';
|
||||
import { ServerlessFunctionRuntime } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
} from 'src/engine/core-modules/logic-function-executor/drivers/utils/lambda-build-directory-manager';
|
||||
import { getLogicFunctionFolderOrThrow } from 'src/engine/core-modules/logic-function-executor/utils/get-logic-function-folder-or-throw.utils';
|
||||
import { type FlatLogicFunctionLayer } from 'src/engine/metadata-modules/logic-function-layer/types/flat-logic-function-layer.type';
|
||||
import { LogicFunctionExecutionStatus } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-execution-result.dto';
|
||||
import { LogicFunctionRuntime } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
|
||||
import {
|
||||
ServerlessFunctionException,
|
||||
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';
|
||||
LogicFunctionException,
|
||||
LogicFunctionExceptionCode,
|
||||
} from 'src/engine/metadata-modules/logic-function/logic-function.exception';
|
||||
import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
|
||||
const UPDATE_FUNCTION_DURATION_TIMEOUT_IN_SECONDS = 60;
|
||||
@@ -63,7 +63,7 @@ export interface LambdaDriverOptions extends LambdaClientConfig {
|
||||
subhostingRole?: string;
|
||||
}
|
||||
|
||||
export class LambdaDriver implements ServerlessDriver {
|
||||
export class LambdaDriver implements LogicFunctionExecutorDriver {
|
||||
private lambdaClient: Lambda | undefined;
|
||||
private credentialsExpiry: Date | null = null;
|
||||
private readonly options: LambdaDriverOptions;
|
||||
@@ -125,11 +125,11 @@ export class LambdaDriver implements ServerlessDriver {
|
||||
}
|
||||
|
||||
private async waitFunctionUpdates(
|
||||
flatServerlessFunction: FlatServerlessFunction,
|
||||
flatLogicFunction: FlatLogicFunction,
|
||||
maxWaitTime: number = UPDATE_FUNCTION_DURATION_TIMEOUT_IN_SECONDS,
|
||||
) {
|
||||
const waitParams = {
|
||||
FunctionName: flatServerlessFunction.id,
|
||||
FunctionName: flatLogicFunction.id,
|
||||
};
|
||||
|
||||
await waitUntilFunctionUpdatedV2(
|
||||
@@ -138,16 +138,14 @@ export class LambdaDriver implements ServerlessDriver {
|
||||
);
|
||||
}
|
||||
|
||||
private getLayerName(
|
||||
flatServerlessFunctionLayer: FlatServerlessFunctionLayer,
|
||||
) {
|
||||
return flatServerlessFunctionLayer.checksum;
|
||||
private getLayerName(flatLogicFunctionLayer: FlatLogicFunctionLayer) {
|
||||
return flatLogicFunctionLayer.checksum;
|
||||
}
|
||||
|
||||
private async createLayerIfNotExists(
|
||||
flatServerlessFunctionLayer: FlatServerlessFunctionLayer,
|
||||
flatLogicFunctionLayer: FlatLogicFunctionLayer,
|
||||
): Promise<string> {
|
||||
const layerName = this.getLayerName(flatServerlessFunctionLayer);
|
||||
const layerName = this.getLayerName(flatLogicFunctionLayer);
|
||||
|
||||
const listLayerParams: ListLayerVersionsCommandInput = {
|
||||
LayerName: layerName,
|
||||
@@ -175,7 +173,7 @@ export class LambdaDriver implements ServerlessDriver {
|
||||
|
||||
await copyAndBuildDependencies(
|
||||
nodeDependenciesFolder,
|
||||
flatServerlessFunctionLayer,
|
||||
flatLogicFunctionLayer,
|
||||
);
|
||||
|
||||
await createZipFile(sourceTemporaryDir, lambdaZipPath);
|
||||
@@ -186,8 +184,8 @@ export class LambdaDriver implements ServerlessDriver {
|
||||
ZipFile: await fs.readFile(lambdaZipPath),
|
||||
},
|
||||
CompatibleRuntimes: [
|
||||
ServerlessFunctionRuntime.NODE18,
|
||||
ServerlessFunctionRuntime.NODE22,
|
||||
LogicFunctionRuntime.NODE18,
|
||||
LogicFunctionRuntime.NODE22,
|
||||
],
|
||||
};
|
||||
|
||||
@@ -204,12 +202,10 @@ export class LambdaDriver implements ServerlessDriver {
|
||||
return result.LayerVersionArn;
|
||||
}
|
||||
|
||||
private async getLambdaExecutor(
|
||||
flatServerlessFunction: FlatServerlessFunction,
|
||||
) {
|
||||
private async getLambdaExecutor(flatLogicFunction: FlatLogicFunction) {
|
||||
try {
|
||||
const getFunctionCommand: GetFunctionCommand = new GetFunctionCommand({
|
||||
FunctionName: flatServerlessFunction.id,
|
||||
FunctionName: flatLogicFunction.id,
|
||||
});
|
||||
|
||||
return await (await this.getLambdaClient()).send(getFunctionCommand);
|
||||
@@ -220,12 +216,12 @@ export class LambdaDriver implements ServerlessDriver {
|
||||
}
|
||||
}
|
||||
|
||||
async delete(flatServerlessFunction: FlatServerlessFunction) {
|
||||
const lambdaExecutor = await this.getLambdaExecutor(flatServerlessFunction);
|
||||
async delete(flatLogicFunction: FlatLogicFunction) {
|
||||
const lambdaExecutor = await this.getLambdaExecutor(flatLogicFunction);
|
||||
|
||||
if (isDefined(lambdaExecutor)) {
|
||||
const deleteFunctionCommand = new DeleteFunctionCommand({
|
||||
FunctionName: flatServerlessFunction.id,
|
||||
FunctionName: flatLogicFunction.id,
|
||||
});
|
||||
|
||||
await (await this.getLambdaClient()).send(deleteFunctionCommand);
|
||||
@@ -233,10 +229,10 @@ export class LambdaDriver implements ServerlessDriver {
|
||||
}
|
||||
|
||||
private async isAlreadyBuilt(
|
||||
flatServerlessFunction: FlatServerlessFunction,
|
||||
flatServerlessFunctionLayer: FlatServerlessFunctionLayer,
|
||||
flatLogicFunction: FlatLogicFunction,
|
||||
flatLogicFunctionLayer: FlatLogicFunctionLayer,
|
||||
) {
|
||||
const lambdaExecutor = await this.getLambdaExecutor(flatServerlessFunction);
|
||||
const lambdaExecutor = await this.getLambdaExecutor(flatLogicFunction);
|
||||
|
||||
if (!isDefined(lambdaExecutor)) {
|
||||
return false;
|
||||
@@ -245,38 +241,31 @@ export class LambdaDriver implements ServerlessDriver {
|
||||
const layers = lambdaExecutor.Configuration?.Layers;
|
||||
|
||||
if (!isDefined(layers) || layers.length !== 1) {
|
||||
await this.delete(flatServerlessFunction);
|
||||
await this.delete(flatLogicFunction);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const layerName = this.getLayerName(flatServerlessFunctionLayer);
|
||||
const layerName = this.getLayerName(flatLogicFunctionLayer);
|
||||
|
||||
if (layers[0].Arn?.includes(layerName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
await this.delete(flatServerlessFunction);
|
||||
await this.delete(flatLogicFunction);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async build(
|
||||
flatServerlessFunction: FlatServerlessFunction,
|
||||
flatServerlessFunctionLayer: FlatServerlessFunctionLayer,
|
||||
flatLogicFunction: FlatLogicFunction,
|
||||
flatLogicFunctionLayer: FlatLogicFunctionLayer,
|
||||
) {
|
||||
if (
|
||||
await this.isAlreadyBuilt(
|
||||
flatServerlessFunction,
|
||||
flatServerlessFunctionLayer,
|
||||
)
|
||||
) {
|
||||
if (await this.isAlreadyBuilt(flatLogicFunction, flatLogicFunctionLayer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const layerArn = await this.createLayerIfNotExists(
|
||||
flatServerlessFunctionLayer,
|
||||
);
|
||||
const layerArn = await this.createLayerIfNotExists(flatLogicFunctionLayer);
|
||||
|
||||
const lambdaBuildDirectoryManager = new LambdaBuildDirectoryManager();
|
||||
|
||||
@@ -291,12 +280,12 @@ export class LambdaDriver implements ServerlessDriver {
|
||||
Code: {
|
||||
ZipFile: await fs.readFile(lambdaZipPath),
|
||||
},
|
||||
FunctionName: flatServerlessFunction.id,
|
||||
FunctionName: flatLogicFunction.id,
|
||||
Layers: [layerArn],
|
||||
Handler: 'index.handler',
|
||||
Role: this.options.lambdaRole,
|
||||
Runtime: flatServerlessFunction.runtime,
|
||||
Timeout: 900, // timeout is handled by the serverless function service
|
||||
Runtime: flatLogicFunction.runtime,
|
||||
Timeout: 900, // timeout is handled by the logic function service
|
||||
};
|
||||
|
||||
const command = new CreateFunctionCommand(params);
|
||||
@@ -322,26 +311,26 @@ export class LambdaDriver implements ServerlessDriver {
|
||||
}
|
||||
|
||||
async execute({
|
||||
flatServerlessFunction,
|
||||
flatServerlessFunctionLayer,
|
||||
flatLogicFunction,
|
||||
flatLogicFunctionLayer,
|
||||
payload,
|
||||
version,
|
||||
env,
|
||||
}: {
|
||||
flatServerlessFunction: FlatServerlessFunction;
|
||||
flatServerlessFunctionLayer: FlatServerlessFunctionLayer;
|
||||
flatLogicFunction: FlatLogicFunction;
|
||||
flatLogicFunctionLayer: FlatLogicFunctionLayer;
|
||||
payload: object;
|
||||
version: string;
|
||||
env?: Record<string, string>;
|
||||
}): Promise<ServerlessExecuteResult> {
|
||||
await this.build(flatServerlessFunction, flatServerlessFunctionLayer);
|
||||
}): Promise<LogicFunctionExecuteResult> {
|
||||
await this.build(flatLogicFunction, flatLogicFunctionLayer);
|
||||
|
||||
await this.waitFunctionUpdates(flatServerlessFunction);
|
||||
await this.waitFunctionUpdates(flatLogicFunction);
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
const builtHandlerFolderPath = getServerlessFolderOrThrow({
|
||||
flatServerlessFunction,
|
||||
const builtHandlerFolderPath = getLogicFunctionFolderOrThrow({
|
||||
flatLogicFunction,
|
||||
version,
|
||||
fileFolder: FileFolder.BuiltFunction,
|
||||
});
|
||||
@@ -350,7 +339,7 @@ export class LambdaDriver implements ServerlessDriver {
|
||||
await streamToBuffer(
|
||||
await this.fileStorageService.read({
|
||||
folderPath: builtHandlerFolderPath,
|
||||
filename: flatServerlessFunction.builtHandlerPath,
|
||||
filename: flatLogicFunction.builtHandlerPath,
|
||||
}),
|
||||
)
|
||||
).toString('utf-8');
|
||||
@@ -359,11 +348,11 @@ export class LambdaDriver implements ServerlessDriver {
|
||||
params: payload,
|
||||
code: compiledCode,
|
||||
env: env ?? {},
|
||||
handlerName: flatServerlessFunction.handlerName,
|
||||
handlerName: flatLogicFunction.handlerName,
|
||||
};
|
||||
|
||||
const params: InvokeCommandInput = {
|
||||
FunctionName: flatServerlessFunction.id,
|
||||
FunctionName: flatLogicFunction.id,
|
||||
Payload: JSON.stringify(executorPayload),
|
||||
LogType: LogType.Tail,
|
||||
};
|
||||
@@ -385,7 +374,7 @@ export class LambdaDriver implements ServerlessDriver {
|
||||
return {
|
||||
data: null,
|
||||
duration,
|
||||
status: ServerlessFunctionExecutionStatus.ERROR,
|
||||
status: LogicFunctionExecutionStatus.ERROR,
|
||||
error: parsedResult,
|
||||
logs,
|
||||
};
|
||||
@@ -395,13 +384,13 @@ export class LambdaDriver implements ServerlessDriver {
|
||||
data: parsedResult,
|
||||
logs,
|
||||
duration,
|
||||
status: ServerlessFunctionExecutionStatus.SUCCESS,
|
||||
status: LogicFunctionExecutionStatus.SUCCESS,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ResourceNotFoundException) {
|
||||
throw new ServerlessFunctionException(
|
||||
throw new LogicFunctionException(
|
||||
`Function Version '${version}' does not exist`,
|
||||
ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND,
|
||||
LogicFunctionExceptionCode.LOGIC_FUNCTION_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
+40
-39
@@ -5,25 +5,25 @@ import { join } from 'path';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import {
|
||||
type ServerlessDriver,
|
||||
type ServerlessExecuteResult,
|
||||
} from 'src/engine/core-modules/serverless/drivers/interfaces/serverless-driver.interface';
|
||||
type LogicFunctionExecutorDriver,
|
||||
type LogicFunctionExecuteResult,
|
||||
} from 'src/engine/core-modules/logic-function-executor/drivers/interfaces/logic-function-executor-driver.interface';
|
||||
|
||||
import { type FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { SERVERLESS_TMPDIR_FOLDER } from 'src/engine/core-modules/serverless/drivers/constants/serverless-tmpdir-folder';
|
||||
import { copyAndBuildDependencies } from 'src/engine/core-modules/serverless/drivers/utils/copy-and-build-dependencies';
|
||||
import { ConsoleListener } from 'src/engine/core-modules/serverless/drivers/utils/intercept-console';
|
||||
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 FlatServerlessFunctionLayer } from 'src/engine/metadata-modules/serverless-function-layer/types/flat-serverless-function-layer.type';
|
||||
import { ServerlessFunctionExecutionStatus } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function-execution-result.dto';
|
||||
import { type FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
|
||||
import { LOGIC_FUNCTION_EXECUTOR_TMPDIR_FOLDER } from 'src/engine/core-modules/logic-function-executor/drivers/constants/logic-function-executor-tmpdir-folder';
|
||||
import { copyAndBuildDependencies } from 'src/engine/core-modules/logic-function-executor/drivers/utils/copy-and-build-dependencies';
|
||||
import { ConsoleListener } from 'src/engine/core-modules/logic-function-executor/drivers/utils/intercept-console';
|
||||
import { LambdaBuildDirectoryManager } from 'src/engine/core-modules/logic-function-executor/drivers/utils/lambda-build-directory-manager';
|
||||
import { getLogicFunctionFolderOrThrow } from 'src/engine/core-modules/logic-function-executor/utils/get-logic-function-folder-or-throw.utils';
|
||||
import { type FlatLogicFunctionLayer } from 'src/engine/metadata-modules/logic-function-layer/types/flat-logic-function-layer.type';
|
||||
import { LogicFunctionExecutionStatus } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-execution-result.dto';
|
||||
import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
|
||||
|
||||
export interface LocalDriverOptions {
|
||||
fileStorageService: FileStorageService;
|
||||
}
|
||||
|
||||
export class LocalDriver implements ServerlessDriver {
|
||||
export class LocalDriver implements LogicFunctionExecutorDriver {
|
||||
private readonly fileStorageService: FileStorageService;
|
||||
|
||||
constructor(options: LocalDriverOptions) {
|
||||
@@ -31,16 +31,19 @@ export class LocalDriver implements ServerlessDriver {
|
||||
}
|
||||
|
||||
private getInMemoryLayerFolderPath = (
|
||||
flatServerlessFunctionLayer: FlatServerlessFunctionLayer,
|
||||
flatLogicFunctionLayer: FlatLogicFunctionLayer,
|
||||
) => {
|
||||
return join(SERVERLESS_TMPDIR_FOLDER, flatServerlessFunctionLayer.checksum);
|
||||
return join(
|
||||
LOGIC_FUNCTION_EXECUTOR_TMPDIR_FOLDER,
|
||||
flatLogicFunctionLayer.checksum,
|
||||
);
|
||||
};
|
||||
|
||||
private async createLayerIfNotExists(
|
||||
flatServerlessFunctionLayer: FlatServerlessFunctionLayer,
|
||||
flatLogicFunctionLayer: FlatLogicFunctionLayer,
|
||||
) {
|
||||
const inMemoryLayerFolderPath = this.getInMemoryLayerFolderPath(
|
||||
flatServerlessFunctionLayer,
|
||||
flatLogicFunctionLayer,
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -48,38 +51,36 @@ export class LocalDriver implements ServerlessDriver {
|
||||
} catch {
|
||||
await copyAndBuildDependencies(
|
||||
inMemoryLayerFolderPath,
|
||||
flatServerlessFunctionLayer,
|
||||
flatLogicFunctionLayer,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async delete() {}
|
||||
|
||||
private async build(
|
||||
flatServerlessFunctionLayer: FlatServerlessFunctionLayer,
|
||||
) {
|
||||
await this.createLayerIfNotExists(flatServerlessFunctionLayer);
|
||||
private async build(flatLogicFunctionLayer: FlatLogicFunctionLayer) {
|
||||
await this.createLayerIfNotExists(flatLogicFunctionLayer);
|
||||
}
|
||||
|
||||
async execute({
|
||||
flatServerlessFunction,
|
||||
flatServerlessFunctionLayer,
|
||||
flatLogicFunction,
|
||||
flatLogicFunctionLayer,
|
||||
payload,
|
||||
version,
|
||||
env,
|
||||
}: {
|
||||
flatServerlessFunction: FlatServerlessFunction;
|
||||
flatServerlessFunctionLayer: FlatServerlessFunctionLayer;
|
||||
flatLogicFunction: FlatLogicFunction;
|
||||
flatLogicFunctionLayer: FlatLogicFunctionLayer;
|
||||
payload: object;
|
||||
version: string;
|
||||
env?: Record<string, string>;
|
||||
}): Promise<ServerlessExecuteResult> {
|
||||
await this.build(flatServerlessFunctionLayer);
|
||||
}): Promise<LogicFunctionExecuteResult> {
|
||||
await this.build(flatLogicFunctionLayer);
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
const builtHandlerFolderPath = getServerlessFolderOrThrow({
|
||||
flatServerlessFunction,
|
||||
const builtHandlerFolderPath = getLogicFunctionFolderOrThrow({
|
||||
flatLogicFunction,
|
||||
version,
|
||||
fileFolder: FileFolder.BuiltFunction,
|
||||
});
|
||||
@@ -92,18 +93,18 @@ export class LocalDriver implements ServerlessDriver {
|
||||
await this.fileStorageService.download({
|
||||
from: {
|
||||
folderPath: builtHandlerFolderPath,
|
||||
filename: flatServerlessFunction.builtHandlerPath,
|
||||
filename: flatLogicFunction.builtHandlerPath,
|
||||
},
|
||||
to: {
|
||||
folderPath: sourceTemporaryDir,
|
||||
filename: flatServerlessFunction.builtHandlerPath,
|
||||
filename: flatLogicFunction.builtHandlerPath,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await fs.symlink(
|
||||
join(
|
||||
this.getInMemoryLayerFolderPath(flatServerlessFunctionLayer),
|
||||
this.getInMemoryLayerFolderPath(flatLogicFunctionLayer),
|
||||
'node_modules',
|
||||
),
|
||||
join(sourceTemporaryDir, 'node_modules'),
|
||||
@@ -151,13 +152,13 @@ export class LocalDriver implements ServerlessDriver {
|
||||
try {
|
||||
const builtBundleFilePath = join(
|
||||
sourceTemporaryDir,
|
||||
flatServerlessFunction.builtHandlerPath,
|
||||
flatLogicFunction.builtHandlerPath,
|
||||
);
|
||||
|
||||
const runnerPath = await this.writeBootstrapRunner({
|
||||
dir: sourceTemporaryDir,
|
||||
builtFileAbsPath: builtBundleFilePath,
|
||||
handlerName: flatServerlessFunction.handlerName,
|
||||
handlerName: flatLogicFunction.handlerName,
|
||||
});
|
||||
|
||||
const { ok, result, error, stack, stdout, stderr } =
|
||||
@@ -165,7 +166,7 @@ export class LocalDriver implements ServerlessDriver {
|
||||
runnerPath,
|
||||
env: env ?? {},
|
||||
payload,
|
||||
timeoutMs: 900_000, // timeout is handled by the serverless function service
|
||||
timeoutMs: 900_000, // timeout is handled by the logic function service
|
||||
});
|
||||
|
||||
if (stdout)
|
||||
@@ -190,7 +191,7 @@ export class LocalDriver implements ServerlessDriver {
|
||||
data: (result ?? null) as object | null,
|
||||
logs,
|
||||
duration,
|
||||
status: ServerlessFunctionExecutionStatus.SUCCESS,
|
||||
status: LogicFunctionExecutionStatus.SUCCESS,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -203,7 +204,7 @@ export class LocalDriver implements ServerlessDriver {
|
||||
errorMessage: error || 'Unknown error',
|
||||
stackTrace: stack ? String(stack).split('\n') : [],
|
||||
},
|
||||
status: ServerlessFunctionExecutionStatus.ERROR,
|
||||
status: LogicFunctionExecutionStatus.ERROR,
|
||||
};
|
||||
} finally {
|
||||
consoleListener.release();
|
||||
@@ -232,7 +233,7 @@ export class LocalDriver implements ServerlessDriver {
|
||||
const builtUrl = pathToFileURL(${JSON.stringify(builtFileAbsPath)});
|
||||
const mod = await import(builtUrl.href);
|
||||
if (typeof mod.${handlerName} !== 'function') {
|
||||
throw new Error('Export "${handlerName}" not found in serverless bundle');
|
||||
throw new Error('Export "${handlerName}" not found in function bundle');
|
||||
}
|
||||
|
||||
let payload = undefined;
|
||||
@@ -333,7 +334,7 @@ export class LocalDriver implements ServerlessDriver {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
if (code === 0) {
|
||||
// Fallback path if no IPC (shouldn’t happen with our stdio)
|
||||
// Fallback path if no IPC (shouldn't happen with our stdio)
|
||||
resolve({ ok: true, stdout, stderr });
|
||||
} else {
|
||||
resolve({
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { type FlatApplicationVariable } from 'src/engine/core-modules/applicationVariable/types/flat-application-variable.type';
|
||||
import { type SecretEncryptionService } from 'src/engine/core-modules/secret-encryption/secret-encryption.service';
|
||||
import { buildEnvVar } from 'src/engine/core-modules/serverless/drivers/utils/build-env-var';
|
||||
import { buildEnvVar } from 'src/engine/core-modules/logic-function-executor/drivers/utils/build-env-var';
|
||||
|
||||
describe('buildEnvVar', () => {
|
||||
const mockSecretEncryptionService = {
|
||||
+6
-6
@@ -3,22 +3,22 @@ import { promises as fs, statSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { promisify } from 'util';
|
||||
|
||||
import { getLayerDependenciesDirName } from 'src/engine/core-modules/serverless/drivers/utils/get-layer-dependencies-dir-name';
|
||||
import { type FlatServerlessFunctionLayer } from 'src/engine/metadata-modules/serverless-function-layer/types/flat-serverless-function-layer.type';
|
||||
import { getLayerDependenciesDirName } from 'src/engine/core-modules/logic-function-executor/drivers/utils/get-layer-dependencies-dir-name';
|
||||
import { type FlatLogicFunctionLayer } from 'src/engine/metadata-modules/logic-function-layer/types/flat-logic-function-layer.type';
|
||||
|
||||
const execFilePromise = promisify(execFile);
|
||||
|
||||
export const copyAndBuildDependencies = async (
|
||||
buildDirectory: string,
|
||||
flatServerlessFunctionLayer: FlatServerlessFunctionLayer,
|
||||
flatLogicFunctionLayer: FlatLogicFunctionLayer,
|
||||
) => {
|
||||
await fs.mkdir(buildDirectory, {
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
const packageJson = flatServerlessFunctionLayer.packageJson;
|
||||
const packageJson = flatLogicFunctionLayer.packageJson;
|
||||
|
||||
const yarnLock = flatServerlessFunctionLayer.yarnLock;
|
||||
const yarnLock = flatLogicFunctionLayer.yarnLock;
|
||||
|
||||
await fs.writeFile(
|
||||
join(buildDirectory, 'package.json'),
|
||||
@@ -46,7 +46,7 @@ export const copyAndBuildDependencies = async (
|
||||
} catch (error: any) {
|
||||
const errorMessage =
|
||||
[error?.stdout, error?.stderr].filter(Boolean).join('\n') ||
|
||||
'Failed to install serverless dependencies';
|
||||
'Failed to install logic function executor dependencies';
|
||||
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
import { getExecutorFilePath } from 'src/engine/core-modules/serverless/drivers/utils/get-executor-file-path';
|
||||
import { getExecutorFilePath } from 'src/engine/core-modules/logic-function-executor/drivers/utils/get-executor-file-path';
|
||||
|
||||
export const copyExecutor = async (buildDirectory: string) => {
|
||||
await fs.mkdir(buildDirectory, {
|
||||
+1
-1
@@ -5,7 +5,7 @@ import { ASSET_PATH } from 'src/constants/assets-path';
|
||||
export const getExecutorFilePath = (): string => {
|
||||
const baseTypescriptProjectPath = path.join(
|
||||
ASSET_PATH,
|
||||
`engine/core-modules/serverless/drivers/constants/executor`,
|
||||
`engine/core-modules/logic-function-executor/drivers/constants/executor`,
|
||||
);
|
||||
|
||||
return path.resolve(__dirname, baseTypescriptProjectPath);
|
||||
+2
-2
@@ -3,8 +3,8 @@ import { join } from 'path';
|
||||
|
||||
import { type PackageJson } from 'twenty-shared/application';
|
||||
|
||||
import { getLayerDependenciesDirName } from 'src/engine/core-modules/serverless/drivers/utils/get-layer-dependencies-dir-name';
|
||||
import { LAST_LAYER_VERSION } from 'src/engine/core-modules/serverless/drivers/layers/last-layer-version';
|
||||
import { getLayerDependenciesDirName } from 'src/engine/core-modules/logic-function-executor/drivers/utils/get-layer-dependencies-dir-name';
|
||||
import { LAST_LAYER_VERSION } from 'src/engine/core-modules/logic-function-executor/drivers/layers/last-layer-version';
|
||||
|
||||
export type LayerDependencies = {
|
||||
packageJson: PackageJson;
|
||||
+1
-1
@@ -7,7 +7,7 @@ export const getLayerDependenciesDirName = (
|
||||
): string => {
|
||||
const baseTypescriptProjectPath = path.join(
|
||||
ASSET_PATH,
|
||||
`engine/core-modules/serverless/drivers/layers/${version}`,
|
||||
`engine/core-modules/logic-function-executor/drivers/layers/${version}`,
|
||||
);
|
||||
|
||||
return path.resolve(__dirname, baseTypescriptProjectPath);
|
||||
+1
-1
@@ -32,7 +32,7 @@ const getAllFiles = async (
|
||||
export const getSeedProjectFiles = (async () => {
|
||||
const seedProjectPath = join(
|
||||
ASSET_PATH,
|
||||
`engine/core-modules/serverless/drivers/constants/seed-project`,
|
||||
`engine/core-modules/logic-function-executor/drivers/constants/seed-project`,
|
||||
);
|
||||
|
||||
return await getAllFiles(seedProjectPath);
|
||||
+2
-2
@@ -3,7 +3,7 @@ import * as fs from 'fs/promises';
|
||||
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { SERVERLESS_TMPDIR_FOLDER } from 'src/engine/core-modules/serverless/drivers/constants/serverless-tmpdir-folder';
|
||||
import { LOGIC_FUNCTION_EXECUTOR_TMPDIR_FOLDER } from 'src/engine/core-modules/logic-function-executor/drivers/constants/logic-function-executor-tmpdir-folder';
|
||||
|
||||
export const NODE_LAYER_SUBFOLDER = 'nodejs';
|
||||
|
||||
@@ -12,7 +12,7 @@ const LAMBDA_ZIP_FILE_NAME = 'lambda.zip';
|
||||
|
||||
export class LambdaBuildDirectoryManager {
|
||||
private temporaryDir = join(
|
||||
SERVERLESS_TMPDIR_FOLDER,
|
||||
LOGIC_FUNCTION_EXECUTOR_TMPDIR_FOLDER,
|
||||
`${TEMPORARY_LAMBDA_FOLDER}-${v4()}`,
|
||||
);
|
||||
|
||||
+18
-18
@@ -2,46 +2,46 @@ import { fromNodeProviderChain } from '@aws-sdk/credential-providers';
|
||||
|
||||
import { type FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import {
|
||||
ServerlessDriverType,
|
||||
type ServerlessModuleOptions,
|
||||
} from 'src/engine/core-modules/serverless/serverless.interface';
|
||||
LogicFunctionExecutorDriverType,
|
||||
type LogicFunctionExecutorModuleOptions,
|
||||
} from 'src/engine/core-modules/logic-function-executor/logic-function-executor.interface';
|
||||
import { type TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
|
||||
export const serverlessModuleFactory = async (
|
||||
export const logicFunctionExecutorModuleFactory = async (
|
||||
twentyConfigService: TwentyConfigService,
|
||||
fileStorageService: FileStorageService,
|
||||
): Promise<ServerlessModuleOptions> => {
|
||||
const driverType = twentyConfigService.get('SERVERLESS_TYPE');
|
||||
): Promise<LogicFunctionExecutorModuleOptions> => {
|
||||
const driverType = twentyConfigService.get('LOGIC_FUNCTION_TYPE');
|
||||
const options = { fileStorageService };
|
||||
|
||||
switch (driverType) {
|
||||
case ServerlessDriverType.DISABLED: {
|
||||
case LogicFunctionExecutorDriverType.DISABLED: {
|
||||
return {
|
||||
type: ServerlessDriverType.DISABLED,
|
||||
type: LogicFunctionExecutorDriverType.DISABLED,
|
||||
};
|
||||
}
|
||||
case ServerlessDriverType.LOCAL: {
|
||||
case LogicFunctionExecutorDriverType.LOCAL: {
|
||||
return {
|
||||
type: ServerlessDriverType.LOCAL,
|
||||
type: LogicFunctionExecutorDriverType.LOCAL,
|
||||
options,
|
||||
};
|
||||
}
|
||||
case ServerlessDriverType.LAMBDA: {
|
||||
const region = twentyConfigService.get('SERVERLESS_LAMBDA_REGION');
|
||||
case LogicFunctionExecutorDriverType.LAMBDA: {
|
||||
const region = twentyConfigService.get('LOGIC_FUNCTION_LAMBDA_REGION');
|
||||
const accessKeyId = twentyConfigService.get(
|
||||
'SERVERLESS_LAMBDA_ACCESS_KEY_ID',
|
||||
'LOGIC_FUNCTION_LAMBDA_ACCESS_KEY_ID',
|
||||
);
|
||||
const secretAccessKey = twentyConfigService.get(
|
||||
'SERVERLESS_LAMBDA_SECRET_ACCESS_KEY',
|
||||
'LOGIC_FUNCTION_LAMBDA_SECRET_ACCESS_KEY',
|
||||
);
|
||||
const lambdaRole = twentyConfigService.get('SERVERLESS_LAMBDA_ROLE');
|
||||
const lambdaRole = twentyConfigService.get('LOGIC_FUNCTION_LAMBDA_ROLE');
|
||||
|
||||
const subhostingRole = twentyConfigService.get(
|
||||
'SERVERLESS_LAMBDA_SUBHOSTING_ROLE',
|
||||
'LOGIC_FUNCTION_LAMBDA_SUBHOSTING_ROLE',
|
||||
);
|
||||
|
||||
return {
|
||||
type: ServerlessDriverType.LAMBDA,
|
||||
type: LogicFunctionExecutorDriverType.LAMBDA,
|
||||
options: {
|
||||
...options,
|
||||
credentials: accessKeyId
|
||||
@@ -60,7 +60,7 @@ export const serverlessModuleFactory = async (
|
||||
}
|
||||
default:
|
||||
throw new Error(
|
||||
`Invalid serverless driver type (${driverType}), check your .env file`,
|
||||
`Invalid logic function executor driver type (${driverType}), check your .env file`,
|
||||
);
|
||||
}
|
||||
};
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export const LOGIC_FUNCTION_EXECUTOR_DRIVER = Symbol(
|
||||
'LOGIC_FUNCTION_EXECUTOR_DRIVER',
|
||||
);
|
||||
+11
-9
@@ -1,37 +1,39 @@
|
||||
import { type FactoryProvider, type ModuleMetadata } from '@nestjs/common';
|
||||
|
||||
import { type LambdaDriverOptions } from 'src/engine/core-modules/serverless/drivers/lambda.driver';
|
||||
import { type LocalDriverOptions } from 'src/engine/core-modules/serverless/drivers/local.driver';
|
||||
import { type LambdaDriverOptions } from 'src/engine/core-modules/logic-function-executor/drivers/lambda.driver';
|
||||
import { type LocalDriverOptions } from 'src/engine/core-modules/logic-function-executor/drivers/local.driver';
|
||||
|
||||
export enum ServerlessDriverType {
|
||||
export enum LogicFunctionExecutorDriverType {
|
||||
DISABLED = 'DISABLED',
|
||||
LAMBDA = 'LAMBDA',
|
||||
LOCAL = 'LOCAL',
|
||||
}
|
||||
|
||||
export interface DisabledDriverFactoryOptions {
|
||||
type: ServerlessDriverType.DISABLED;
|
||||
type: LogicFunctionExecutorDriverType.DISABLED;
|
||||
}
|
||||
|
||||
export interface LocalDriverFactoryOptions {
|
||||
type: ServerlessDriverType.LOCAL;
|
||||
type: LogicFunctionExecutorDriverType.LOCAL;
|
||||
options: LocalDriverOptions;
|
||||
}
|
||||
|
||||
export interface LambdaDriverFactoryOptions {
|
||||
type: ServerlessDriverType.LAMBDA;
|
||||
type: LogicFunctionExecutorDriverType.LAMBDA;
|
||||
options: LambdaDriverOptions;
|
||||
}
|
||||
|
||||
export type ServerlessModuleOptions =
|
||||
export type LogicFunctionExecutorModuleOptions =
|
||||
| DisabledDriverFactoryOptions
|
||||
| LocalDriverFactoryOptions
|
||||
| LambdaDriverFactoryOptions;
|
||||
|
||||
export type ServerlessModuleAsyncOptions = {
|
||||
export type LogicFunctionExecutorModuleAsyncOptions = {
|
||||
useFactory: (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
...args: any[]
|
||||
) => ServerlessModuleOptions | Promise<ServerlessModuleOptions>;
|
||||
) =>
|
||||
| LogicFunctionExecutorModuleOptions
|
||||
| Promise<LogicFunctionExecutorModuleOptions>;
|
||||
} & Pick<ModuleMetadata, 'imports'> &
|
||||
Pick<FactoryProvider, 'inject'>;
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
import { type DynamicModule, Global } from '@nestjs/common';
|
||||
|
||||
import { AddPackagesCommand } from 'src/engine/core-modules/logic-function-executor/commands/add-packages.command';
|
||||
import { DisabledDriver } from 'src/engine/core-modules/logic-function-executor/drivers/disabled.driver';
|
||||
import { LambdaDriver } from 'src/engine/core-modules/logic-function-executor/drivers/lambda.driver';
|
||||
import { LocalDriver } from 'src/engine/core-modules/logic-function-executor/drivers/local.driver';
|
||||
import { LOGIC_FUNCTION_EXECUTOR_DRIVER } from 'src/engine/core-modules/logic-function-executor/logic-function-executor.constants';
|
||||
import {
|
||||
LogicFunctionExecutorDriverType,
|
||||
type LogicFunctionExecutorModuleAsyncOptions,
|
||||
} from 'src/engine/core-modules/logic-function-executor/logic-function-executor.interface';
|
||||
import { LogicFunctionExecutorService } from 'src/engine/core-modules/logic-function-executor/logic-function-executor.service';
|
||||
|
||||
@Global()
|
||||
export class LogicFunctionExecutorModule {
|
||||
static forRootAsync(
|
||||
options: LogicFunctionExecutorModuleAsyncOptions,
|
||||
): DynamicModule {
|
||||
const provider = {
|
||||
provide: LOGIC_FUNCTION_EXECUTOR_DRIVER,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
useFactory: async (...args: any[]) => {
|
||||
const config = await options.useFactory(...args);
|
||||
|
||||
switch (config?.type) {
|
||||
case LogicFunctionExecutorDriverType.DISABLED:
|
||||
return new DisabledDriver();
|
||||
case LogicFunctionExecutorDriverType.LOCAL:
|
||||
return new LocalDriver(config.options);
|
||||
case LogicFunctionExecutorDriverType.LAMBDA:
|
||||
return new LambdaDriver(config.options);
|
||||
default: {
|
||||
const unknownConfig = config as { type?: string };
|
||||
|
||||
throw new Error(
|
||||
`Unknown logic function executor driver type: ${unknownConfig?.type}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
inject: options.inject || [],
|
||||
};
|
||||
|
||||
return {
|
||||
module: LogicFunctionExecutorModule,
|
||||
imports: options.imports || [],
|
||||
providers: [LogicFunctionExecutorService, provider, AddPackagesCommand],
|
||||
exports: [LogicFunctionExecutorService],
|
||||
};
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
LogicFunctionExecutorDriver,
|
||||
type LogicFunctionExecuteResult,
|
||||
} from 'src/engine/core-modules/logic-function-executor/drivers/interfaces/logic-function-executor-driver.interface';
|
||||
|
||||
import { LOGIC_FUNCTION_EXECUTOR_DRIVER } from 'src/engine/core-modules/logic-function-executor/logic-function-executor.constants';
|
||||
import { type FlatLogicFunctionLayer } from 'src/engine/metadata-modules/logic-function-layer/types/flat-logic-function-layer.type';
|
||||
import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
|
||||
|
||||
@Injectable()
|
||||
export class LogicFunctionExecutorService
|
||||
implements LogicFunctionExecutorDriver
|
||||
{
|
||||
constructor(
|
||||
@Inject(LOGIC_FUNCTION_EXECUTOR_DRIVER)
|
||||
private driver: LogicFunctionExecutorDriver,
|
||||
) {}
|
||||
|
||||
async delete(flatLogicFunction: FlatLogicFunction): Promise<void> {
|
||||
return this.driver.delete(flatLogicFunction);
|
||||
}
|
||||
|
||||
async execute({
|
||||
flatLogicFunction,
|
||||
flatLogicFunctionLayer,
|
||||
payload,
|
||||
version,
|
||||
env,
|
||||
}: {
|
||||
flatLogicFunction: FlatLogicFunction;
|
||||
flatLogicFunctionLayer: FlatLogicFunctionLayer;
|
||||
payload: object;
|
||||
version: string;
|
||||
env?: Record<string, string>;
|
||||
}): Promise<LogicFunctionExecuteResult> {
|
||||
return this.driver.execute({
|
||||
flatLogicFunction,
|
||||
flatLogicFunctionLayer,
|
||||
payload,
|
||||
version,
|
||||
env,
|
||||
});
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
import { join } from 'path';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import {
|
||||
LogicFunctionException,
|
||||
LogicFunctionExceptionCode,
|
||||
} from 'src/engine/metadata-modules/logic-function/logic-function.exception';
|
||||
import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
|
||||
|
||||
export const getLogicFunctionFolderOrThrow = ({
|
||||
flatLogicFunction,
|
||||
version,
|
||||
fileFolder = FileFolder.LogicFunction,
|
||||
}: {
|
||||
flatLogicFunction: FlatLogicFunction;
|
||||
version?: 'draft' | 'latest' | (string & NonNullable<unknown>);
|
||||
fileFolder?:
|
||||
| FileFolder.LogicFunction
|
||||
| FileFolder.LogicFunctionToDelete
|
||||
| FileFolder.BuiltFunction;
|
||||
}) => {
|
||||
if (version === 'latest' && !isDefined(flatLogicFunction.latestVersion)) {
|
||||
throw new LogicFunctionException(
|
||||
"Can't get 'latest' version when logicFunction 'latestVersion' is undefined",
|
||||
LogicFunctionExceptionCode.LOGIC_FUNCTION_VERSION_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const computedVersion =
|
||||
version === 'latest' ? flatLogicFunction.latestVersion : version;
|
||||
|
||||
return join(
|
||||
'workspace-' + flatLogicFunction.workspaceId,
|
||||
fileFolder,
|
||||
flatLogicFunction.id,
|
||||
computedVersion || '',
|
||||
);
|
||||
};
|
||||
@@ -24,7 +24,7 @@ import { CronTriggerModule } from 'src/engine/metadata-modules/cron-trigger/cron
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { DatabaseEventTriggerModule } from 'src/engine/metadata-modules/database-event-trigger/database-event-trigger.module';
|
||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||
import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module';
|
||||
import { LogicFunctionModule } from 'src/engine/metadata-modules/logic-function/logic-function.module';
|
||||
import { SubscriptionsModule } from 'src/engine/subscriptions/subscriptions.module';
|
||||
import { CleanOnboardingWorkspacesJob } from 'src/engine/workspace-manager/workspace-cleaner/crons/clean-onboarding-workspaces.job';
|
||||
import { CleanSuspendedWorkspacesJob } from 'src/engine/workspace-manager/workspace-cleaner/crons/clean-suspended-workspaces.job';
|
||||
@@ -70,7 +70,7 @@ import { WorkflowModule } from 'src/modules/workflow/workflow.module';
|
||||
AiAgentMonitorModule,
|
||||
CronTriggerModule,
|
||||
DatabaseEventTriggerModule,
|
||||
ServerlessFunctionModule,
|
||||
LogicFunctionModule,
|
||||
],
|
||||
providers: [
|
||||
CleanSuspendedWorkspacesJob,
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ export const MESSAGE_QUEUE_PRIORITY = {
|
||||
[MessageQueue.calendarQueue]: 4,
|
||||
[MessageQueue.contactCreationQueue]: 4,
|
||||
[MessageQueue.taskAssignedQueue]: 4,
|
||||
[MessageQueue.serverlessFunctionQueue]: 4,
|
||||
[MessageQueue.logicFunctionQueue]: 4,
|
||||
[MessageQueue.workspaceQueue]: 5,
|
||||
[MessageQueue.triggerQueue]: 5,
|
||||
[MessageQueue.deleteCascadeQueue]: 6,
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ export enum MessageQueue {
|
||||
workflowQueue = 'workflow-queue',
|
||||
delayedJobsQueue = 'delayed-jobs-queue',
|
||||
deleteCascadeQueue = 'delete-cascade-queue',
|
||||
serverlessFunctionQueue = 'serverless-function-queue',
|
||||
logicFunctionQueue = 'logic-function-queue',
|
||||
triggerQueue = 'trigger-queue',
|
||||
aiQueue = 'ai-queue',
|
||||
}
|
||||
|
||||
+2
-4
@@ -122,10 +122,8 @@ export class CommonApiContextBuilderService {
|
||||
authContext.apiKey.id,
|
||||
workspaceId,
|
||||
);
|
||||
} else if (
|
||||
isDefined(authContext.application?.defaultServerlessFunctionRoleId)
|
||||
) {
|
||||
roleId = authContext.application.defaultServerlessFunctionRoleId;
|
||||
} else if (isDefined(authContext.application?.defaultLogicFunctionRoleId)) {
|
||||
roleId = authContext.application.defaultLogicFunctionRoleId;
|
||||
} else if (isDefined(authContext.userWorkspaceId)) {
|
||||
const userWorkspaceRoleId =
|
||||
await this.userRoleService.getRoleIdForUserWorkspace({
|
||||
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
import { join } from 'path';
|
||||
import { tmpdir } from 'os';
|
||||
|
||||
export const SERVERLESS_TMPDIR_FOLDER = join(tmpdir(), 'serverless-tmpdir');
|
||||
@@ -1,22 +0,0 @@
|
||||
import {
|
||||
type ServerlessDriver,
|
||||
type ServerlessExecuteResult,
|
||||
} from 'src/engine/core-modules/serverless/drivers/interfaces/serverless-driver.interface';
|
||||
|
||||
import {
|
||||
ServerlessFunctionException,
|
||||
ServerlessFunctionExceptionCode,
|
||||
} from 'src/engine/metadata-modules/serverless-function/serverless-function.exception';
|
||||
|
||||
export class DisabledDriver implements ServerlessDriver {
|
||||
async delete(): Promise<void> {
|
||||
// No-op when disabled
|
||||
}
|
||||
|
||||
async execute(): Promise<ServerlessExecuteResult> {
|
||||
throw new ServerlessFunctionException(
|
||||
'Serverless function execution is disabled. Set SERVERLESS_TYPE to LOCAL or LAMBDA to enable.',
|
||||
ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_DISABLED,
|
||||
);
|
||||
}
|
||||
}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
import { type FlatServerlessFunctionLayer } from 'src/engine/metadata-modules/serverless-function-layer/types/flat-serverless-function-layer.type';
|
||||
import { type ServerlessFunctionExecutionStatus } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function-execution-result.dto';
|
||||
import { type FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
|
||||
|
||||
export type ServerlessExecuteError = {
|
||||
errorType: string;
|
||||
errorMessage: string;
|
||||
stackTrace: string | string[];
|
||||
};
|
||||
|
||||
export type ServerlessExecuteResult = {
|
||||
data: object | null;
|
||||
duration: number;
|
||||
logs: string;
|
||||
status: ServerlessFunctionExecutionStatus;
|
||||
error?: ServerlessExecuteError;
|
||||
};
|
||||
|
||||
export interface ServerlessDriver {
|
||||
delete(flatServerlessFunction: FlatServerlessFunction): Promise<void>;
|
||||
execute({
|
||||
flatServerlessFunction,
|
||||
flatServerlessFunctionLayer,
|
||||
payload,
|
||||
version,
|
||||
env,
|
||||
}: {
|
||||
flatServerlessFunction: FlatServerlessFunction;
|
||||
flatServerlessFunctionLayer: FlatServerlessFunctionLayer;
|
||||
payload: object;
|
||||
version: string;
|
||||
env?: Record<string, string>;
|
||||
}): Promise<ServerlessExecuteResult>;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export const SERVERLESS_DRIVER = Symbol('SERVERLESS_DRIVER');
|
||||
@@ -1,49 +0,0 @@
|
||||
import { type DynamicModule, Global } from '@nestjs/common';
|
||||
|
||||
import { AddPackagesCommand } from 'src/engine/core-modules/serverless/commands/add-packages.command';
|
||||
import { DisabledDriver } from 'src/engine/core-modules/serverless/drivers/disabled.driver';
|
||||
import { LambdaDriver } from 'src/engine/core-modules/serverless/drivers/lambda.driver';
|
||||
import { LocalDriver } from 'src/engine/core-modules/serverless/drivers/local.driver';
|
||||
import { SERVERLESS_DRIVER } from 'src/engine/core-modules/serverless/serverless.constants';
|
||||
import {
|
||||
ServerlessDriverType,
|
||||
type ServerlessModuleAsyncOptions,
|
||||
} from 'src/engine/core-modules/serverless/serverless.interface';
|
||||
import { ServerlessService } from 'src/engine/core-modules/serverless/serverless.service';
|
||||
|
||||
@Global()
|
||||
export class ServerlessModule {
|
||||
static forRootAsync(options: ServerlessModuleAsyncOptions): DynamicModule {
|
||||
const provider = {
|
||||
provide: SERVERLESS_DRIVER,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
useFactory: async (...args: any[]) => {
|
||||
const config = await options.useFactory(...args);
|
||||
|
||||
switch (config?.type) {
|
||||
case ServerlessDriverType.DISABLED:
|
||||
return new DisabledDriver();
|
||||
case ServerlessDriverType.LOCAL:
|
||||
return new LocalDriver(config.options);
|
||||
case ServerlessDriverType.LAMBDA:
|
||||
return new LambdaDriver(config.options);
|
||||
default: {
|
||||
const unknownConfig = config as { type?: string };
|
||||
|
||||
throw new Error(
|
||||
`Unknown serverless driver type: ${unknownConfig?.type}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
inject: options.inject || [],
|
||||
};
|
||||
|
||||
return {
|
||||
module: ServerlessModule,
|
||||
imports: options.imports || [],
|
||||
providers: [ServerlessService, provider, AddPackagesCommand],
|
||||
exports: [ServerlessService],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
ServerlessDriver,
|
||||
type ServerlessExecuteResult,
|
||||
} from 'src/engine/core-modules/serverless/drivers/interfaces/serverless-driver.interface';
|
||||
|
||||
import { SERVERLESS_DRIVER } from 'src/engine/core-modules/serverless/serverless.constants';
|
||||
import { type FlatServerlessFunctionLayer } from 'src/engine/metadata-modules/serverless-function-layer/types/flat-serverless-function-layer.type';
|
||||
import { type FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
|
||||
|
||||
@Injectable()
|
||||
export class ServerlessService implements ServerlessDriver {
|
||||
constructor(@Inject(SERVERLESS_DRIVER) private driver: ServerlessDriver) {}
|
||||
|
||||
async delete(flatServerlessFunction: FlatServerlessFunction): Promise<void> {
|
||||
return this.driver.delete(flatServerlessFunction);
|
||||
}
|
||||
|
||||
async execute({
|
||||
flatServerlessFunction,
|
||||
flatServerlessFunctionLayer,
|
||||
payload,
|
||||
version,
|
||||
env,
|
||||
}: {
|
||||
flatServerlessFunction: FlatServerlessFunction;
|
||||
flatServerlessFunctionLayer: FlatServerlessFunctionLayer;
|
||||
payload: object;
|
||||
version: string;
|
||||
env?: Record<string, string>;
|
||||
}): Promise<ServerlessExecuteResult> {
|
||||
return this.driver.execute({
|
||||
flatServerlessFunction,
|
||||
flatServerlessFunctionLayer,
|
||||
payload,
|
||||
version,
|
||||
env,
|
||||
});
|
||||
}
|
||||
}
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
import { join } from 'path';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import {
|
||||
ServerlessFunctionException,
|
||||
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';
|
||||
|
||||
export const getServerlessFolderOrThrow = ({
|
||||
flatServerlessFunction,
|
||||
version,
|
||||
fileFolder = FileFolder.ServerlessFunction,
|
||||
}: {
|
||||
flatServerlessFunction: FlatServerlessFunction;
|
||||
version?: 'draft' | 'latest' | (string & NonNullable<unknown>);
|
||||
fileFolder?:
|
||||
| FileFolder.ServerlessFunction
|
||||
| FileFolder.ServerlessFunctionToDelete
|
||||
| FileFolder.BuiltFunction;
|
||||
}) => {
|
||||
if (
|
||||
version === 'latest' &&
|
||||
!isDefined(flatServerlessFunction.latestVersion)
|
||||
) {
|
||||
throw new ServerlessFunctionException(
|
||||
"Can't get 'latest' version when serverlessFunction 'latestVersion' is undefined",
|
||||
ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_VERSION_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const computedVersion =
|
||||
version === 'latest' ? flatServerlessFunction.latestVersion : version;
|
||||
|
||||
return join(
|
||||
'workspace-' + flatServerlessFunction.workspaceId,
|
||||
fileFolder,
|
||||
flatServerlessFunction.id,
|
||||
computedVersion || '',
|
||||
);
|
||||
};
|
||||
+1
-1
@@ -6,5 +6,5 @@ export enum ToolCategory {
|
||||
NATIVE_MODEL = 'NATIVE_MODEL',
|
||||
VIEW = 'VIEW',
|
||||
DASHBOARD = 'DASHBOARD',
|
||||
SERVERLESS_FUNCTION = 'SERVERLESS_FUNCTION',
|
||||
LOGIC_FUNCTION = 'LOGIC_FUNCTION',
|
||||
}
|
||||
|
||||
+22
-24
@@ -11,65 +11,63 @@ import {
|
||||
import { ToolCategory } from 'src/engine/core-modules/tool-provider/enums/tool-category.enum';
|
||||
import { wrapJsonSchemaForExecution } from 'src/engine/core-modules/tool/utils/wrap-tool-for-execution.util';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
||||
import { type FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
|
||||
import { LogicFunctionService } from 'src/engine/metadata-modules/logic-function/logic-function.service';
|
||||
import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
|
||||
|
||||
@Injectable()
|
||||
export class ServerlessFunctionToolProvider implements ToolProvider {
|
||||
readonly category = ToolCategory.SERVERLESS_FUNCTION;
|
||||
export class LogicFunctionToolProvider implements ToolProvider {
|
||||
readonly category = ToolCategory.LOGIC_FUNCTION;
|
||||
|
||||
constructor(
|
||||
private readonly serverlessFunctionService: ServerlessFunctionService,
|
||||
private readonly logicFunctionService: LogicFunctionService,
|
||||
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
) {}
|
||||
|
||||
async isAvailable(_context: ToolProviderContext): Promise<boolean> {
|
||||
// Serverless function tools are available if there are any functions marked as tools
|
||||
// Logic function tools are available if there are any functions marked as tools
|
||||
return true;
|
||||
}
|
||||
|
||||
async generateTools(context: ToolProviderContext): Promise<ToolSet> {
|
||||
const { flatServerlessFunctionMaps } =
|
||||
const { flatLogicFunctionMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId: context.workspaceId,
|
||||
flatMapsKeys: ['flatServerlessFunctionMaps'],
|
||||
flatMapsKeys: ['flatLogicFunctionMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
// Filter serverless functions that are marked as tools
|
||||
const serverlessFunctionsWithSchema = Object.values(
|
||||
flatServerlessFunctionMaps.byId,
|
||||
// Filter logic functions that are marked as tools
|
||||
const logicFunctionsWithSchema = Object.values(
|
||||
flatLogicFunctionMaps.byId,
|
||||
).filter(
|
||||
(fn): fn is FlatServerlessFunction =>
|
||||
(fn): fn is FlatLogicFunction =>
|
||||
isDefined(fn) && fn.isTool === true && fn.deletedAt === null,
|
||||
);
|
||||
|
||||
const tools: ToolSet = {};
|
||||
|
||||
for (const serverlessFunction of serverlessFunctionsWithSchema) {
|
||||
const toolName = this.buildServerlessFunctionToolName(
|
||||
serverlessFunction.name,
|
||||
);
|
||||
for (const logicFunction of logicFunctionsWithSchema) {
|
||||
const toolName = this.buildLogicFunctionToolName(logicFunction.name);
|
||||
|
||||
const wrappedSchema = wrapJsonSchemaForExecution(
|
||||
serverlessFunction.toolInputSchema as Record<string, unknown>,
|
||||
logicFunction.toolInputSchema as Record<string, unknown>,
|
||||
);
|
||||
|
||||
tools[toolName] = {
|
||||
description:
|
||||
serverlessFunction.description ||
|
||||
`Execute the ${serverlessFunction.name} serverless function`,
|
||||
logicFunction.description ||
|
||||
`Execute the ${logicFunction.name} logic function`,
|
||||
inputSchema: jsonSchema(wrappedSchema),
|
||||
execute: async (parameters: Record<string, unknown>) => {
|
||||
const { loadingMessage: _, ...actualParams } = parameters;
|
||||
|
||||
const result =
|
||||
await this.serverlessFunctionService.executeOneServerlessFunction({
|
||||
id: serverlessFunction.id,
|
||||
await this.logicFunctionService.executeOneLogicFunction({
|
||||
id: logicFunction.id,
|
||||
workspaceId: context.workspaceId,
|
||||
payload: actualParams,
|
||||
version: serverlessFunction.latestVersion ?? 'draft',
|
||||
version: logicFunction.latestVersion ?? 'draft',
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
@@ -90,9 +88,9 @@ export class ServerlessFunctionToolProvider implements ToolProvider {
|
||||
return tools;
|
||||
}
|
||||
|
||||
private buildServerlessFunctionToolName(functionName: string): string {
|
||||
private buildLogicFunctionToolName(functionName: string): string {
|
||||
// Convert function name to a valid tool name (lowercase, underscores)
|
||||
return `serverless_${functionName
|
||||
return `logic_function_${functionName
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '_')
|
||||
.replace(/^_+|_+$/g, '')}`;
|
||||
+3
-3
@@ -25,7 +25,7 @@ export type ToolIndexEntry = {
|
||||
| 'METADATA'
|
||||
| 'VIEW'
|
||||
| 'DASHBOARD'
|
||||
| 'SERVERLESS_FUNCTION';
|
||||
| 'LOGIC_FUNCTION';
|
||||
objectName?: string;
|
||||
operation?: string;
|
||||
inputSchema?: object;
|
||||
@@ -40,7 +40,7 @@ export type ToolSearchOptions = {
|
||||
| 'METADATA'
|
||||
| 'VIEW'
|
||||
| 'DASHBOARD'
|
||||
| 'SERVERLESS_FUNCTION';
|
||||
| 'LOGIC_FUNCTION';
|
||||
};
|
||||
|
||||
export type ToolContext = {
|
||||
@@ -259,7 +259,7 @@ export class ToolRegistryService {
|
||||
NATIVE_MODEL: 'ACTION',
|
||||
VIEW: 'VIEW',
|
||||
DASHBOARD: 'DASHBOARD',
|
||||
SERVERLESS_FUNCTION: 'SERVERLESS_FUNCTION',
|
||||
LOGIC_FUNCTION: 'LOGIC_FUNCTION',
|
||||
};
|
||||
|
||||
return Object.entries(tools).map(([name, tool]) => {
|
||||
|
||||
+7
-7
@@ -8,7 +8,7 @@ import { DashboardToolProvider } from 'src/engine/core-modules/tool-provider/pro
|
||||
import { DatabaseToolProvider } from 'src/engine/core-modules/tool-provider/providers/database-tool.provider';
|
||||
import { MetadataToolProvider } from 'src/engine/core-modules/tool-provider/providers/metadata-tool.provider';
|
||||
import { NativeModelToolProvider } from 'src/engine/core-modules/tool-provider/providers/native-model-tool.provider';
|
||||
import { ServerlessFunctionToolProvider } from 'src/engine/core-modules/tool-provider/providers/serverless-function-tool.provider';
|
||||
import { LogicFunctionToolProvider } from 'src/engine/core-modules/tool-provider/providers/logic-function-tool.provider';
|
||||
import { ViewToolProvider } from 'src/engine/core-modules/tool-provider/providers/view-tool.provider';
|
||||
import { WorkflowToolProvider } from 'src/engine/core-modules/tool-provider/providers/workflow-tool.provider';
|
||||
import { ToolModule } from 'src/engine/core-modules/tool/tool.module';
|
||||
@@ -18,7 +18,7 @@ import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module';
|
||||
import { LogicFunctionModule } from 'src/engine/metadata-modules/logic-function/logic-function.module';
|
||||
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
|
||||
import { ViewModule } from 'src/engine/metadata-modules/view/view.module';
|
||||
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
||||
@@ -44,7 +44,7 @@ import { ToolRegistryService } from './services/tool-registry.service';
|
||||
ViewModule,
|
||||
WorkspaceCacheModule,
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
ServerlessFunctionModule,
|
||||
LogicFunctionModule,
|
||||
UserRoleModule,
|
||||
],
|
||||
providers: [
|
||||
@@ -54,7 +54,7 @@ import { ToolRegistryService } from './services/tool-registry.service';
|
||||
DatabaseToolProvider,
|
||||
MetadataToolProvider,
|
||||
NativeModelToolProvider,
|
||||
ServerlessFunctionToolProvider,
|
||||
LogicFunctionToolProvider,
|
||||
ViewToolProvider,
|
||||
WorkflowToolProvider,
|
||||
{
|
||||
@@ -65,7 +65,7 @@ import { ToolRegistryService } from './services/tool-registry.service';
|
||||
databaseProvider: DatabaseToolProvider,
|
||||
metadataProvider: MetadataToolProvider,
|
||||
nativeModelProvider: NativeModelToolProvider,
|
||||
serverlessFunctionProvider: ServerlessFunctionToolProvider,
|
||||
logicFunctionProvider: LogicFunctionToolProvider,
|
||||
viewProvider: ViewToolProvider,
|
||||
workflowProvider: WorkflowToolProvider,
|
||||
) => [
|
||||
@@ -74,7 +74,7 @@ import { ToolRegistryService } from './services/tool-registry.service';
|
||||
databaseProvider,
|
||||
metadataProvider,
|
||||
nativeModelProvider,
|
||||
serverlessFunctionProvider,
|
||||
logicFunctionProvider,
|
||||
viewProvider,
|
||||
workflowProvider,
|
||||
],
|
||||
@@ -84,7 +84,7 @@ import { ToolRegistryService } from './services/tool-registry.service';
|
||||
DatabaseToolProvider,
|
||||
MetadataToolProvider,
|
||||
NativeModelToolProvider,
|
||||
ServerlessFunctionToolProvider,
|
||||
LogicFunctionToolProvider,
|
||||
ViewToolProvider,
|
||||
WorkflowToolProvider,
|
||||
],
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ export const wrapSchemaForExecution = <T extends z.ZodRawShape>(
|
||||
}) as z.ZodObject<T & { loadingMessage: z.ZodString }>;
|
||||
};
|
||||
|
||||
// For non-Zod schemas (serverless functions with JSON Schema)
|
||||
// For non-Zod schemas (logic functions with JSON Schema)
|
||||
export const wrapJsonSchemaForExecution = (
|
||||
schema: Record<string, unknown>,
|
||||
): Record<string, unknown> => {
|
||||
|
||||
@@ -23,7 +23,7 @@ import { ExceptionHandlerDriver } from 'src/engine/core-modules/exception-handle
|
||||
import { StorageDriverType } from 'src/engine/core-modules/file-storage/interfaces';
|
||||
import { LoggerDriverType } from 'src/engine/core-modules/logger/interfaces';
|
||||
import { type MeterDriver } from 'src/engine/core-modules/metrics/types/meter-driver.type';
|
||||
import { ServerlessDriverType } from 'src/engine/core-modules/serverless/serverless.interface';
|
||||
import { LogicFunctionExecutorDriverType } from 'src/engine/core-modules/logic-function-executor/logic-function-executor.interface';
|
||||
import { CastToLogLevelArray } from 'src/engine/core-modules/twenty-config/decorators/cast-to-log-level-array.decorator';
|
||||
import { CastToTypeORMLogLevelArray } from 'src/engine/core-modules/twenty-config/decorators/cast-to-typeorm-log-level-array.decorator';
|
||||
import { CastToMeterDriverArray } from 'src/engine/core-modules/twenty-config/decorators/cast-to-meter-driver.decorator';
|
||||
@@ -446,87 +446,98 @@ export class ConfigVariables {
|
||||
STORAGE_S3_SECRET_ACCESS_KEY: string;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.SERVERLESS_CONFIG,
|
||||
description: 'Type of serverless execution (local or Lambda)',
|
||||
group: ConfigVariablesGroup.LOGIC_FUNCTION_CONFIG,
|
||||
description: 'Type of function execution (local or Lambda)',
|
||||
type: ConfigVariableType.ENUM,
|
||||
options: Object.values(ServerlessDriverType),
|
||||
options: Object.values(LogicFunctionExecutorDriverType),
|
||||
isEnvOnly: true,
|
||||
})
|
||||
@IsOptional()
|
||||
@CastToUpperSnakeCase()
|
||||
SERVERLESS_TYPE: ServerlessDriverType = ServerlessDriverType.LOCAL;
|
||||
LOGIC_FUNCTION_TYPE: LogicFunctionExecutorDriverType =
|
||||
LogicFunctionExecutorDriverType.LOCAL;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.SERVERLESS_CONFIG,
|
||||
group: ConfigVariablesGroup.LOGIC_FUNCTION_CONFIG,
|
||||
description:
|
||||
'Configure whether console logs from serverless functions are displayed in the terminal',
|
||||
'Configure whether console logs from logic functions are displayed in the terminal',
|
||||
type: ConfigVariableType.BOOLEAN,
|
||||
})
|
||||
@IsOptional()
|
||||
SERVERLESS_LOGS_ENABLED: false;
|
||||
LOGIC_FUNCTION_LOGS_ENABLED: false;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.SERVERLESS_CONFIG,
|
||||
description: 'Throttle limit for serverless function execution',
|
||||
group: ConfigVariablesGroup.LOGIC_FUNCTION_CONFIG,
|
||||
description: 'Throttle limit for logic function execution',
|
||||
type: ConfigVariableType.NUMBER,
|
||||
})
|
||||
@CastToPositiveNumber()
|
||||
SERVERLESS_FUNCTION_EXEC_THROTTLE_LIMIT = 1000;
|
||||
LOGIC_FUNCTION_EXEC_THROTTLE_LIMIT = 1000;
|
||||
|
||||
// milliseconds
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.SERVERLESS_CONFIG,
|
||||
description: 'Time-to-live for serverless function execution throttle',
|
||||
group: ConfigVariablesGroup.LOGIC_FUNCTION_CONFIG,
|
||||
description: 'Time-to-live for logic function execution throttle',
|
||||
type: ConfigVariableType.NUMBER,
|
||||
})
|
||||
@CastToPositiveNumber()
|
||||
SERVERLESS_FUNCTION_EXEC_THROTTLE_TTL = 60_000;
|
||||
LOGIC_FUNCTION_EXEC_THROTTLE_TTL = 60_000;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.SERVERLESS_CONFIG,
|
||||
group: ConfigVariablesGroup.LOGIC_FUNCTION_CONFIG,
|
||||
description: 'Region for AWS Lambda functions',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.LAMBDA)
|
||||
@ValidateIf(
|
||||
(env) => env.LOGIC_FUNCTION_TYPE === LogicFunctionExecutorDriverType.LAMBDA,
|
||||
)
|
||||
@IsAWSRegion()
|
||||
SERVERLESS_LAMBDA_REGION: AwsRegion;
|
||||
LOGIC_FUNCTION_LAMBDA_REGION: AwsRegion;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.SERVERLESS_CONFIG,
|
||||
group: ConfigVariablesGroup.LOGIC_FUNCTION_CONFIG,
|
||||
description: 'IAM role for AWS Lambda functions',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.LAMBDA)
|
||||
SERVERLESS_LAMBDA_ROLE: string;
|
||||
@ValidateIf(
|
||||
(env) => env.LOGIC_FUNCTION_TYPE === LogicFunctionExecutorDriverType.LAMBDA,
|
||||
)
|
||||
LOGIC_FUNCTION_LAMBDA_ROLE: string;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.SERVERLESS_CONFIG,
|
||||
group: ConfigVariablesGroup.LOGIC_FUNCTION_CONFIG,
|
||||
description: 'Role to assume when hosting lambdas in dedicated AWS account',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.LAMBDA)
|
||||
@ValidateIf(
|
||||
(env) => env.LOGIC_FUNCTION_TYPE === LogicFunctionExecutorDriverType.LAMBDA,
|
||||
)
|
||||
@IsOptional()
|
||||
SERVERLESS_LAMBDA_SUBHOSTING_ROLE?: string;
|
||||
LOGIC_FUNCTION_LAMBDA_SUBHOSTING_ROLE?: string;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.SERVERLESS_CONFIG,
|
||||
group: ConfigVariablesGroup.LOGIC_FUNCTION_CONFIG,
|
||||
isSensitive: true,
|
||||
description: 'Access key ID for AWS Lambda functions',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.LAMBDA)
|
||||
@ValidateIf(
|
||||
(env) => env.LOGIC_FUNCTION_TYPE === LogicFunctionExecutorDriverType.LAMBDA,
|
||||
)
|
||||
@IsOptional()
|
||||
SERVERLESS_LAMBDA_ACCESS_KEY_ID: string;
|
||||
LOGIC_FUNCTION_LAMBDA_ACCESS_KEY_ID: string;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.SERVERLESS_CONFIG,
|
||||
group: ConfigVariablesGroup.LOGIC_FUNCTION_CONFIG,
|
||||
isSensitive: true,
|
||||
description: 'Secret access key for AWS Lambda functions',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.LAMBDA)
|
||||
@ValidateIf(
|
||||
(env) => env.LOGIC_FUNCTION_TYPE === LogicFunctionExecutorDriverType.LAMBDA,
|
||||
)
|
||||
@IsOptional()
|
||||
SERVERLESS_LAMBDA_SECRET_ACCESS_KEY: string;
|
||||
LOGIC_FUNCTION_LAMBDA_SECRET_ACCESS_KEY: string;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.CODE_INTERPRETER_CONFIG,
|
||||
|
||||
+2
-2
@@ -89,10 +89,10 @@ export const CONFIG_VARIABLES_GROUP_METADATA: Record<
|
||||
'Configure the LLM provider and model to use for the app. This is experimental and not linked to any public feature.',
|
||||
isHiddenOnLoad: true,
|
||||
},
|
||||
[ConfigVariablesGroup.SERVERLESS_CONFIG]: {
|
||||
[ConfigVariablesGroup.LOGIC_FUNCTION_CONFIG]: {
|
||||
position: 1500,
|
||||
description:
|
||||
'In our multi-tenant cloud app, we offload untrusted custom code from workflows to a serverless system (Lambda) for enhanced security and scalability. Self-hosters with a single tenant can typically ignore this configuration.',
|
||||
'In our multi-tenant cloud app, we offload untrusted custom code from workflows to a function execution system (Lambda) for enhanced security and scalability. Self-hosters with a single tenant can typically ignore this configuration.',
|
||||
isHiddenOnLoad: true,
|
||||
},
|
||||
[ConfigVariablesGroup.CODE_INTERPRETER_CONFIG]: {
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ export enum ConfigVariablesGroup {
|
||||
CAPTCHA_CONFIG = 'CAPTCHA_CONFIG',
|
||||
CLOUDFLARE_CONFIG = 'CLOUDFLARE_CONFIG',
|
||||
LLM = 'LLM',
|
||||
SERVERLESS_CONFIG = 'SERVERLESS_CONFIG',
|
||||
LOGIC_FUNCTION_CONFIG = 'LOGIC_FUNCTION_CONFIG',
|
||||
CODE_INTERPRETER_CONFIG = 'CODE_INTERPRETER_CONFIG',
|
||||
SSL = 'SSL',
|
||||
SUPPORT_CHAT_CONFIG = 'SUPPORT_CHAT_CONFIG',
|
||||
|
||||
Reference in New Issue
Block a user