diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.module.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.module.ts index db4dd0ff37b..a05a99f6091 100644 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.module.ts @@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; +import { ApplicationLayerModule } from 'src/engine/core-modules/application-layer/application-layer.module'; import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; import { ApplicationModule } from 'src/engine/core-modules/application/application.module'; import { AuditModule } from 'src/engine/core-modules/audit/audit.module'; @@ -18,11 +19,11 @@ import { LogicFunctionLayerModule } from 'src/engine/metadata-modules/logic-func import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity'; import { LogicFunctionResolver } from 'src/engine/metadata-modules/logic-function/logic-function.resolver'; import { LogicFunctionMetadataService } from 'src/engine/metadata-modules/logic-function/services/logic-function-metadata.service'; +import { LogicFunctionService } from 'src/engine/metadata-modules/logic-function/services/logic-function.service'; import { WorkspaceFlatLogicFunctionMapCacheService } from 'src/engine/metadata-modules/logic-function/services/workspace-flat-logic-function-map-cache.service'; import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; import { SubscriptionsModule } from 'src/engine/subscriptions/subscriptions.module'; import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module'; -import { ApplicationLayerModule } from 'src/engine/core-modules/application-layer/application-layer.module'; @Module({ imports: [ @@ -45,9 +46,10 @@ import { ApplicationLayerModule } from 'src/engine/core-modules/application-laye ], providers: [ LogicFunctionMetadataService, + LogicFunctionService, LogicFunctionResolver, WorkspaceFlatLogicFunctionMapCacheService, ], - exports: [LogicFunctionMetadataService], + exports: [LogicFunctionMetadataService, LogicFunctionService], }) export class LogicFunctionModule {} diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.resolver.ts index 1f0b2bbfe4e..77515bd341a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.resolver.ts @@ -4,18 +4,15 @@ import { Args, Mutation, Query, Resolver, Subscription } from '@nestjs/graphql'; import graphqlTypeJson from 'graphql-type-json'; import { PermissionFlagType } from 'twenty-shared/constants'; import { isDefined } from 'twenty-shared/utils'; -import { v4 } from 'uuid'; -import { ApplicationService } from 'src/engine/core-modules/application/services/application.service'; import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter'; import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe'; -import { LogicFunctionResourceService } from 'src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; import { FeatureFlagGuard } from 'src/engine/guards/feature-flag.guard'; import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; -import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; +import { CreateDefaultLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/create-default-logic-function.input'; import { ExecuteOneLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/execute-logic-function.input'; import { LogicFunctionExecutionResultDTO } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-execution-result.dto'; import { LogicFunctionIdInput } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-id.input'; @@ -23,16 +20,10 @@ import { LogicFunctionLogsDTO } from 'src/engine/metadata-modules/logic-function import { LogicFunctionLogsInput } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-logs.input'; import { LogicFunctionDTO } from 'src/engine/metadata-modules/logic-function/dtos/logic-function.dto'; import { UpdateLogicFunctionSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/update-logic-function-source.input'; -import { LogicFunctionMetadataService } from 'src/engine/metadata-modules/logic-function/services/logic-function-metadata.service'; -import { FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type'; -import { findFlatLogicFunctionOrThrow } from 'src/engine/metadata-modules/logic-function/utils/find-flat-logic-function-or-throw.util'; -import { fromFlatLogicFunctionToLogicFunctionDto } from 'src/engine/metadata-modules/logic-function/utils/from-flat-logic-function-to-logic-function-dto.util'; +import { LogicFunctionService } from 'src/engine/metadata-modules/logic-function/services/logic-function.service'; import { logicFunctionGraphQLApiExceptionHandler } from 'src/engine/metadata-modules/logic-function/utils/logic-function-graphql-api-exception-handler.utils'; import { SubscriptionChannel } from 'src/engine/subscriptions/enums/subscription-channel.enum'; import { SubscriptionService } from 'src/engine/subscriptions/subscription.service'; -import { LogicFunctionExecutorService } from 'src/engine/core-modules/logic-function/logic-function-executor/logic-function-executor.service'; -import { ApplicationLayerService } from 'src/engine/core-modules/application-layer/application-layer.service'; -import { CreateDefaultLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/create-default-logic-function.input'; @UseGuards( WorkspaceAuthGuard, @@ -44,13 +35,8 @@ import { CreateDefaultLogicFunctionInput } from 'src/engine/metadata-modules/log @UseFilters(PreventNestToAutoLogGraphqlErrorsFilter) export class LogicFunctionResolver { constructor( - private readonly logicFunctionExecutorService: LogicFunctionExecutorService, - private readonly logicFunctionMetadataService: LogicFunctionMetadataService, - private readonly logicFunctionResourceService: LogicFunctionResourceService, - private readonly applicationService: ApplicationService, - private readonly applicationLayerService: ApplicationLayerService, + private readonly logicFunctionService: LogicFunctionService, private readonly subscriptionService: SubscriptionService, - private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, ) {} @Query(() => LogicFunctionDTO) @@ -59,22 +45,7 @@ export class LogicFunctionResolver { @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, ): Promise { try { - const { flatLogicFunctionMaps } = - await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: ['flatLogicFunctionMaps'], - }, - ); - - const flatLogicFunction = findFlatLogicFunctionOrThrow({ - id, - flatLogicFunctionMaps, - }); - - return fromFlatLogicFunctionToLogicFunctionDto({ - flatLogicFunction, - }); + return await this.logicFunctionService.findOne({ id, workspaceId }); } catch (error) { return logicFunctionGraphQLApiExceptionHandler(error); } @@ -85,25 +56,7 @@ export class LogicFunctionResolver { @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, ): Promise { try { - const { flatLogicFunctionMaps } = - await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: ['flatLogicFunctionMaps'], - }, - ); - - return Object.values(flatLogicFunctionMaps.byUniversalIdentifier) - .filter( - (flatLogicFunction): flatLogicFunction is FlatLogicFunction => - isDefined(flatLogicFunction) && - !isDefined(flatLogicFunction.deletedAt), - ) - .map((flatLogicFunction) => - fromFlatLogicFunctionToLogicFunctionDto({ - flatLogicFunction, - }), - ); + return await this.logicFunctionService.findMany({ workspaceId }); } catch (error) { return logicFunctionGraphQLApiExceptionHandler(error); } @@ -115,8 +68,8 @@ export class LogicFunctionResolver { @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, ) { try { - return await this.applicationLayerService.getAvailablePackages({ - logicFunctionId: id, + return await this.logicFunctionService.getAvailablePackages({ + id, workspaceId, }); } catch (error) { @@ -127,19 +80,11 @@ export class LogicFunctionResolver { @Mutation(() => LogicFunctionDTO) @UseGuards(SettingsPermissionGuard(PermissionFlagType.WORKFLOWS)) async deleteOneLogicFunction( - @Args('input') input: LogicFunctionIdInput, + @Args('input') { id }: LogicFunctionIdInput, @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, ): Promise { try { - const flatLogicFunction = - await this.logicFunctionMetadataService.destroyOne({ - id: input.id, - workspaceId, - }); - - return fromFlatLogicFunctionToLogicFunctionDto({ - flatLogicFunction, - }); + return await this.logicFunctionService.deleteOne({ id, workspaceId }); } catch (error) { return logicFunctionGraphQLApiExceptionHandler(error); } @@ -148,44 +93,13 @@ export class LogicFunctionResolver { @Mutation(() => LogicFunctionDTO) @UseGuards(SettingsPermissionGuard(PermissionFlagType.WORKFLOWS)) async createDefaultLogicFunction( - @Args('input') - input: CreateDefaultLogicFunctionInput, + @Args('input') input: CreateDefaultLogicFunctionInput, @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, ): Promise { try { - const { workspaceCustomFlatApplication } = - await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( - { - workspaceId, - }, - ); - - const logicFunctionId = input.id ?? v4(); - - const { sourceHandlerPath, builtHandlerPath, handlerName, checksum } = - await this.logicFunctionResourceService.seedSourceFiles({ - sourceSubfolder: logicFunctionId, - workspaceId, - applicationUniversalIdentifier: - workspaceCustomFlatApplication.universalIdentifier, - }); - - const flatLogicFunction = - await this.logicFunctionMetadataService.createOne({ - input: { - ...input, - id: logicFunctionId, - sourceHandlerPath, - builtHandlerPath, - handlerName, - checksum, - }, - workspaceId, - ownerFlatApplication: workspaceCustomFlatApplication, - }); - - return fromFlatLogicFunctionToLogicFunctionDto({ - flatLogicFunction, + return await this.logicFunctionService.createDefault({ + input, + workspaceId, }); } catch (error) { return logicFunctionGraphQLApiExceptionHandler(error); @@ -198,48 +112,13 @@ export class LogicFunctionResolver { @Args('input') { id, payload, forceRebuild }: ExecuteOneLogicFunctionInput, @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, ): Promise { - // TODO: remove forceRebuild parameter and add a column called shouldRebuild or isBuiltUpToDate try { - if (forceRebuild) { - const { flatLogicFunction, applicationUniversalIdentifier } = - await this.getLogicFunctionContext({ id, workspaceId }); - - const { checksum } = - await this.logicFunctionResourceService.buildFromSource({ - workspaceId, - applicationUniversalIdentifier, - sourceHandlerPath: flatLogicFunction.sourceHandlerPath, - builtHandlerPath: flatLogicFunction.builtHandlerPath, - }); - - await this.logicFunctionMetadataService.updateChecksum({ - id, - checksum, - workspaceId, - }); - } - - const result = await this.logicFunctionExecutorService.execute({ - logicFunctionId: id, - workspaceId, + return await this.logicFunctionService.executeOne({ + id, payload, + forceRebuild, + workspaceId, }); - - return { - data: result.data as LogicFunctionExecutionResultDTO['data'], - logs: result.logs, - duration: result.duration, - status: result.status, - error: result.error - ? { - errorType: result.error.errorType, - errorMessage: result.error.errorMessage, - stackTrace: Array.isArray(result.error.stackTrace) - ? result.error.stackTrace.join('\n') - : result.error.stackTrace, - } - : undefined, - }; } catch (error) { return logicFunctionGraphQLApiExceptionHandler(error); } @@ -251,13 +130,9 @@ export class LogicFunctionResolver { @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, ) { try { - const { flatLogicFunction, applicationUniversalIdentifier } = - await this.getLogicFunctionContext({ id, workspaceId }); - - return this.logicFunctionResourceService.getSourceCode({ + return await this.logicFunctionService.getSourceCode({ + id, workspaceId, - applicationUniversalIdentifier, - sourceHandlerPath: flatLogicFunction.sourceHandlerPath, }); } catch (error) { return logicFunctionGraphQLApiExceptionHandler(error); @@ -271,14 +146,10 @@ export class LogicFunctionResolver { @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, ): Promise { try { - const { flatLogicFunction, applicationUniversalIdentifier } = - await this.getLogicFunctionContext({ id, workspaceId }); - - await this.logicFunctionResourceService.updateSourceFiles({ - workspaceId, - applicationUniversalIdentifier, - sourceHandlerPath: flatLogicFunction.sourceHandlerPath, + await this.logicFunctionService.updateLogicFunctionSource({ + id, code, + workspaceId, }); return true; @@ -330,34 +201,4 @@ export class LogicFunctionResolver { workspaceId: workspace.id, }); } - - private async getLogicFunctionContext({ - id, - workspaceId, - }: { - id: string; - workspaceId: string; - }) { - const [{ flatLogicFunctionMaps }, { workspaceCustomFlatApplication }] = - await Promise.all([ - this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps({ - workspaceId, - flatMapsKeys: ['flatLogicFunctionMaps'], - }), - this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( - { workspaceId }, - ), - ]); - - const flatLogicFunction = findFlatLogicFunctionOrThrow({ - id, - flatLogicFunctionMaps, - }); - - return { - flatLogicFunction, - applicationUniversalIdentifier: - workspaceCustomFlatApplication.universalIdentifier, - }; - } } diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function.service.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function.service.ts new file mode 100644 index 00000000000..24cc6e07bb3 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function.service.ts @@ -0,0 +1,267 @@ +import { Injectable } from '@nestjs/common'; + +import { Sources } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; +import { v4 } from 'uuid'; + +import { ApplicationLayerService } from 'src/engine/core-modules/application-layer/application-layer.service'; +import { ApplicationService } from 'src/engine/core-modules/application/services/application.service'; +import { LogicFunctionExecutorService } from 'src/engine/core-modules/logic-function/logic-function-executor/logic-function-executor.service'; +import { LogicFunctionResourceService } from 'src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service'; +import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; +import { CreateDefaultLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/create-default-logic-function.input'; +import { LogicFunctionExecutionResultDTO } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-execution-result.dto'; +import { LogicFunctionDTO } from 'src/engine/metadata-modules/logic-function/dtos/logic-function.dto'; +import { LogicFunctionMetadataService } from 'src/engine/metadata-modules/logic-function/services/logic-function-metadata.service'; +import { FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type'; +import { findFlatLogicFunctionOrThrow } from 'src/engine/metadata-modules/logic-function/utils/find-flat-logic-function-or-throw.util'; +import { fromFlatLogicFunctionToLogicFunctionDto } from 'src/engine/metadata-modules/logic-function/utils/from-flat-logic-function-to-logic-function-dto.util'; + +@Injectable() +export class LogicFunctionService { + constructor( + private readonly logicFunctionExecutorService: LogicFunctionExecutorService, + private readonly logicFunctionMetadataService: LogicFunctionMetadataService, + private readonly logicFunctionResourceService: LogicFunctionResourceService, + private readonly applicationService: ApplicationService, + private readonly applicationLayerService: ApplicationLayerService, + private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, + ) {} + + async findOne({ + id, + workspaceId, + }: { + id: string; + workspaceId: string; + }): Promise { + const { flatLogicFunctionMaps } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatLogicFunctionMaps'], + }, + ); + + const flatLogicFunction = findFlatLogicFunctionOrThrow({ + id, + flatLogicFunctionMaps, + }); + + return fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction }); + } + + async findMany({ + workspaceId, + }: { + workspaceId: string; + }): Promise { + const { flatLogicFunctionMaps } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatLogicFunctionMaps'], + }, + ); + + return Object.values(flatLogicFunctionMaps.byUniversalIdentifier) + .filter( + (flatLogicFunction): flatLogicFunction is FlatLogicFunction => + isDefined(flatLogicFunction) && + !isDefined(flatLogicFunction.deletedAt), + ) + .map((flatLogicFunction) => + fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction }), + ); + } + + async getAvailablePackages({ + id, + workspaceId, + }: { + id: string; + workspaceId: string; + }) { + return this.applicationLayerService.getAvailablePackages({ + logicFunctionId: id, + workspaceId, + }); + } + + async deleteOne({ + id, + workspaceId, + }: { + id: string; + workspaceId: string; + }): Promise { + const flatLogicFunction = + await this.logicFunctionMetadataService.destroyOne({ + id, + workspaceId, + }); + + return fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction }); + } + + async createDefault({ + input, + workspaceId, + }: { + input: CreateDefaultLogicFunctionInput; + workspaceId: string; + }): Promise { + const { workspaceCustomFlatApplication } = + await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( + { workspaceId }, + ); + + const logicFunctionId = input.id ?? v4(); + + const { sourceHandlerPath, builtHandlerPath, handlerName, checksum } = + await this.logicFunctionResourceService.seedSourceFiles({ + sourceSubfolder: logicFunctionId, + workspaceId, + applicationUniversalIdentifier: + workspaceCustomFlatApplication.universalIdentifier, + }); + + const flatLogicFunction = await this.logicFunctionMetadataService.createOne( + { + input: { + ...input, + id: logicFunctionId, + sourceHandlerPath, + builtHandlerPath, + handlerName, + checksum, + }, + workspaceId, + ownerFlatApplication: workspaceCustomFlatApplication, + }, + ); + + return fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction }); + } + + // TODO: remove forceRebuild parameter and add a column called shouldRebuild or isBuiltUpToDate + async executeOne({ + id, + payload, + forceRebuild, + workspaceId, + }: { + id: string; + payload: object; + forceRebuild?: boolean; + workspaceId: string; + }): Promise { + if (forceRebuild) { + const { flatLogicFunction, applicationUniversalIdentifier } = + await this.getLogicFunctionContext({ id, workspaceId }); + + const { checksum } = + await this.logicFunctionResourceService.buildFromSource({ + workspaceId, + applicationUniversalIdentifier, + sourceHandlerPath: flatLogicFunction.sourceHandlerPath, + builtHandlerPath: flatLogicFunction.builtHandlerPath, + }); + + await this.logicFunctionMetadataService.updateChecksum({ + id, + checksum, + workspaceId, + }); + } + + const result = await this.logicFunctionExecutorService.execute({ + logicFunctionId: id, + workspaceId, + payload, + }); + + return { + data: result.data as LogicFunctionExecutionResultDTO['data'], + logs: result.logs, + duration: result.duration, + status: result.status, + error: result.error + ? { + errorType: result.error.errorType, + errorMessage: result.error.errorMessage, + stackTrace: Array.isArray(result.error.stackTrace) + ? result.error.stackTrace.join('\n') + : result.error.stackTrace, + } + : undefined, + }; + } + + async getSourceCode({ + id, + workspaceId, + }: { + id: string; + workspaceId: string; + }): Promise { + const { flatLogicFunction, applicationUniversalIdentifier } = + await this.getLogicFunctionContext({ id, workspaceId }); + + return this.logicFunctionResourceService.getSourceCode({ + workspaceId, + applicationUniversalIdentifier, + sourceHandlerPath: flatLogicFunction.sourceHandlerPath, + }); + } + + async updateLogicFunctionSource({ + id, + code, + workspaceId, + }: { + id: string; + code: Sources; + workspaceId: string; + }): Promise { + const { flatLogicFunction, applicationUniversalIdentifier } = + await this.getLogicFunctionContext({ id, workspaceId }); + + await this.logicFunctionResourceService.updateSourceFiles({ + workspaceId, + applicationUniversalIdentifier, + sourceHandlerPath: flatLogicFunction.sourceHandlerPath, + code, + }); + } + + private async getLogicFunctionContext({ + id, + workspaceId, + }: { + id: string; + workspaceId: string; + }) { + const [{ flatLogicFunctionMaps }, { workspaceCustomFlatApplication }] = + await Promise.all([ + this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps({ + workspaceId, + flatMapsKeys: ['flatLogicFunctionMaps'], + }), + this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( + { workspaceId }, + ), + ]); + + const flatLogicFunction = findFlatLogicFunctionOrThrow({ + id, + flatLogicFunctionMaps, + }); + + return { + flatLogicFunction, + applicationUniversalIdentifier: + workspaceCustomFlatApplication.universalIdentifier, + }; + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/skill-metadata/create-standard-flat-skill-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/skill-metadata/create-standard-flat-skill-metadata.util.ts index ab6c1672026..8787f67fe4e 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/skill-metadata/create-standard-flat-skill-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/skill-metadata/create-standard-flat-skill-metadata.util.ts @@ -54,6 +54,10 @@ For CRON triggers, settings.type must be one of these exact values: - Requires: pattern: string (cron expression) - Example: { type: "CUSTOM", pattern: "0 * * * *", outputSchema: {} } +## CODE Steps + +CODE steps run custom TypeScript code. Load the \`update_logic_function_source\` tool to modify the source code. + ## Critical Notes Always rely on tool schema definitions: diff --git a/packages/twenty-server/src/modules/workflow/common/workflow-common.module.ts b/packages/twenty-server/src/modules/workflow/common/workflow-common.module.ts index 1f3bd0a646c..526d202532d 100644 --- a/packages/twenty-server/src/modules/workflow/common/workflow-common.module.ts +++ b/packages/twenty-server/src/modules/workflow/common/workflow-common.module.ts @@ -2,7 +2,6 @@ import { Module } from '@nestjs/common'; import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module'; import { LogicFunctionModule } from 'src/engine/metadata-modules/logic-function/logic-function.module'; -import { CodeStepBuildModule } from 'src/modules/workflow/workflow-builder/workflow-version-step/code-step/code-step-build.module'; import { WorkflowQueryHookModule } from 'src/modules/workflow/common/query-hooks/workflow-query-hook.module'; import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; @@ -11,7 +10,6 @@ import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/work WorkflowQueryHookModule, LogicFunctionModule, WorkspaceManyOrAllFlatEntityMapsCacheModule, - CodeStepBuildModule, ], providers: [WorkflowCommonWorkspaceService], exports: [WorkflowCommonWorkspaceService], diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.module.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.module.ts index 45c72050c37..09ff162de37 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.module.ts @@ -6,13 +6,13 @@ import { AiAgentRoleModule } from 'src/engine/metadata-modules/ai/ai-agent-role/ import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity'; import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module'; import { LogicFunctionModule } from 'src/engine/metadata-modules/logic-function/logic-function.module'; -import { CodeStepBuildModule } from 'src/modules/workflow/workflow-builder/workflow-version-step/code-step/code-step-build.module'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity'; import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module'; import { WorkflowSchemaModule } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.module'; +import { CodeStepBuildModule } from 'src/modules/workflow/workflow-builder/workflow-version-step/code-step/code-step-build.module'; import { WorkflowVersionStepCreationWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-creation.workspace-service'; import { WorkflowVersionStepDeletionWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-deletion.workspace-service'; import { WorkflowVersionStepHelpersWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-helpers.workspace-service'; @@ -48,7 +48,6 @@ import { WorkflowVersionStepWorkspaceService } from 'src/modules/workflow/workfl WorkflowVersionStepWorkspaceService, WorkflowVersionStepOperationsWorkspaceService, WorkflowVersionStepHelpersWorkspaceService, - CodeStepBuildModule, ], }) export class WorkflowVersionStepModule {} diff --git a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-runner.module.ts b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-runner.module.ts index f75fe8df06a..96ecf92875d 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-runner.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-runner.module.ts @@ -3,6 +3,7 @@ import { Module } from '@nestjs/common'; import { BillingModule } from 'src/engine/core-modules/billing/billing.module'; import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module'; import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module'; +import { CodeStepBuildModule } from 'src/modules/workflow/workflow-builder/workflow-version-step/code-step/code-step-build.module'; import { WorkflowVersionStepModule } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.module'; import { WorkflowExecutorModule } from 'src/modules/workflow/workflow-executor/workflow-executor.module'; import { RunWorkflowJob } from 'src/modules/workflow/workflow-runner/jobs/run-workflow.job'; @@ -19,6 +20,7 @@ import { WorkflowRunnerWorkspaceService } from 'src/modules/workflow/workflow-ru MetricsModule, WorkflowRunQueueModule, WorkflowVersionStepModule, + CodeStepBuildModule, ], providers: [WorkflowRunnerWorkspaceService, RunWorkflowJob], exports: [WorkflowRunnerWorkspaceService], diff --git a/packages/twenty-server/src/modules/workflow/workflow-tools/services/workflow-tool.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-tools/services/workflow-tool.workspace-service.ts index 0e0e307a171..ba7f0ba6123 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-tools/services/workflow-tool.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-tools/services/workflow-tool.workspace-service.ts @@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common'; import { type ToolSet } from 'ai'; import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service'; +import { LogicFunctionService } from 'src/engine/metadata-modules/logic-function/services/logic-function.service'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config'; import { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service'; @@ -20,6 +21,7 @@ import { createDeactivateWorkflowVersionTool } from 'src/modules/workflow/workfl import { createDeleteWorkflowVersionEdgeTool } from 'src/modules/workflow/workflow-tools/tools/delete-workflow-version-edge.tool'; import { createDeleteWorkflowVersionStepTool } from 'src/modules/workflow/workflow-tools/tools/delete-workflow-version-step.tool'; import { createGetWorkflowCurrentVersionTool } from 'src/modules/workflow/workflow-tools/tools/get-workflow-current-version.tool'; +import { createUpdateLogicFunctionSourceTool } from 'src/modules/workflow/workflow-tools/tools/update-logic-function-source.tool'; import { createUpdateWorkflowVersionPositionsTool } from 'src/modules/workflow/workflow-tools/tools/update-workflow-version-positions.tool'; import { createUpdateWorkflowVersionStepTool } from 'src/modules/workflow/workflow-tools/tools/update-workflow-version-step.tool'; import { createUpdateWorkflowVersionTriggerTool } from 'src/modules/workflow/workflow-tools/tools/update-workflow-version-trigger.tool'; @@ -39,6 +41,7 @@ export class WorkflowToolWorkspaceService { workflowSchemaService: WorkflowSchemaWorkspaceService, globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, recordPositionService: RecordPositionService, + logicFunctionService: LogicFunctionService, ) { this.deps = { workflowVersionStepService, @@ -49,6 +52,7 @@ export class WorkflowToolWorkspaceService { workflowSchemaService, globalWorkspaceOrmManager, recordPositionService, + logicFunctionService, }; } @@ -108,6 +112,10 @@ export class WorkflowToolWorkspaceService { this.deps, context, ); + const updateLogicFunctionSource = createUpdateLogicFunctionSourceTool( + this.deps, + context, + ); return { [createCompleteWorkflow.name]: createCompleteWorkflow, @@ -123,6 +131,7 @@ export class WorkflowToolWorkspaceService { [deactivateWorkflowVersion.name]: deactivateWorkflowVersion, [computeStepOutputSchema.name]: computeStepOutputSchema, [getWorkflowCurrentVersion.name]: getWorkflowCurrentVersion, + [updateLogicFunctionSource.name]: updateLogicFunctionSource, }; } } diff --git a/packages/twenty-server/src/modules/workflow/workflow-tools/tools/update-logic-function-source.tool.ts b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/update-logic-function-source.tool.ts new file mode 100644 index 00000000000..558f345e35b --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/update-logic-function-source.tool.ts @@ -0,0 +1,85 @@ +import { z } from 'zod'; + +import { + type WorkflowToolContext, + type WorkflowToolDependencies, +} from 'src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type'; + +const updateLogicFunctionSourceSchema = z.object({ + logicFunctionId: z + .string() + .uuid() + .describe( + 'The ID of the logic function to update (from the code step settings.input.logicFunctionId)', + ), + code: z + .object({ + src: z + .object({ + 'index.ts': z + .string() + .describe( + 'The TypeScript source code for the logic function. Must export a main function.', + ), + }) + .describe('Source folder containing the index.ts file'), + }) + .describe( + 'The source code structure. Use { src: { "index.ts": "your code here" } }', + ), +}); + +export const createUpdateLogicFunctionSourceTool = ( + deps: Pick, + context: WorkflowToolContext, +) => ({ + name: 'update_logic_function_source' as const, + description: `Update the TypeScript source code of a logic function used in a workflow code step. + +Use this tool to modify the actual code that runs when a CODE step executes. + +The code must: +- Export a 'main' function as the entry point +- Use TypeScript syntax +- Return an object with the result +- Use native APIs only (fetch, etc.) - external packages cannot be imported + +Example code using fetch for HTTP requests: +\`\`\`typescript +export const main = async (params: { url: string }) => { + const response = await fetch(params.url); + const data = await response.json(); + return { data }; +}; +\`\`\` + +To find the logicFunctionId, look at the code step's settings.input.logicFunctionId field.`, + inputSchema: updateLogicFunctionSourceSchema, + execute: async (parameters: { + logicFunctionId: string; + code: { src: { 'index.ts': string } }; + }) => { + try { + const { logicFunctionId, code } = parameters; + const { workspaceId } = context; + + await deps.logicFunctionService.updateLogicFunctionSource({ + id: logicFunctionId, + code, + workspaceId, + }); + + return { + success: true, + message: `Successfully updated source code for logic function ${logicFunctionId}`, + logicFunctionId, + }; + } catch (error) { + return { + success: false, + error: error.message, + message: `Failed to update logic function source: ${error.message}`, + }; + } + }, +}); diff --git a/packages/twenty-server/src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type.ts b/packages/twenty-server/src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type.ts index adb710c13fb..ca65be3d06e 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type.ts @@ -1,4 +1,5 @@ import type { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service'; +import type { LogicFunctionService } from 'src/engine/metadata-modules/logic-function/services/logic-function.service'; import type { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import type { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service'; import type { WorkflowVersionEdgeWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.workspace-service'; @@ -16,6 +17,7 @@ export type WorkflowToolDependencies = { workflowSchemaService: WorkflowSchemaWorkspaceService; globalWorkspaceOrmManager: GlobalWorkspaceOrmManager; recordPositionService: RecordPositionService; + logicFunctionService: LogicFunctionService; }; export type WorkflowToolContext = { diff --git a/packages/twenty-server/src/modules/workflow/workflow-tools/workflow-tools.module.ts b/packages/twenty-server/src/modules/workflow/workflow-tools/workflow-tools.module.ts index d4124847d42..176b349fc8f 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-tools/workflow-tools.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-tools/workflow-tools.module.ts @@ -2,6 +2,7 @@ import { Global, Module } from '@nestjs/common'; import { RecordPositionModule } from 'src/engine/core-modules/record-position/record-position.module'; import { WORKFLOW_TOOL_SERVICE_TOKEN } from 'src/engine/core-modules/tool-provider/constants/workflow-tool-service.token'; +import { LogicFunctionModule } from 'src/engine/metadata-modules/logic-function/logic-function.module'; import { WorkflowSchemaModule } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.module'; import { WorkflowVersionEdgeModule } from 'src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.module'; import { WorkflowVersionStepModule } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.module'; @@ -21,6 +22,7 @@ import { WorkflowToolWorkspaceService } from './services/workflow-tool.workspace WorkflowTriggerModule, WorkflowSchemaModule, RecordPositionModule, + LogicFunctionModule, ], providers: [ WorkflowToolWorkspaceService,