Add Activepieces integration for workflow automation

- Add Activepieces fork with SmoothSchedule custom piece
- Create integrations app with Activepieces service layer
- Add embed token endpoint for iframe integration
- Create Automations page with embedded workflow builder
- Add sidebar visibility fix for embed mode
- Add list inactive customers endpoint to Public API
- Include SmoothSchedule triggers: event created/updated/cancelled
- Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,18 @@
{
"extends": [
"../server/api/.eslintrc.json"
],
"overrides": [
{
"files": [
"*.ts",
"*.js"
],
"parserOptions": {
"project": [
"packages/shared/tsconfig.*?.json"
]
}
}
]
}

View File

@@ -0,0 +1,7 @@
# shared
This library was generated with [Nx](https://nx.dev).
## Building
Run `nx build shared` to build the library.

View File

@@ -0,0 +1,17 @@
/* eslint-disable */
export default {
displayName: 'shared',
preset: '../../jest.preset.js',
globals: {},
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': [
'ts-jest',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
},
],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/packages/shared',
};

View File

@@ -0,0 +1,5 @@
{
"name": "@activepieces/shared",
"version": "0.30.2",
"type": "commonjs"
}

View File

@@ -0,0 +1,39 @@
{
"name": "shared",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/shared/src",
"projectType": "library",
"tags": [],
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/packages/shared",
"main": "packages/shared/src/index.ts",
"tsConfig": "packages/shared/tsconfig.lib.json",
"assets": ["packages/shared/*.md"],
"buildableProjectDepsInPackageJsonType": "dependencies",
"updateBuildableProjectDepsInPackageJson": true
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "packages/shared/jest.config.ts"
}
},
"publish": {
"executor": "nx:run-commands",
"options": {
"command": "node tools/scripts/publish.mjs shared {args.ver} {args.tag}"
},
"dependsOn": ["build"]
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
}
}
}

View File

@@ -0,0 +1,97 @@
export * from './lib/flows/actions/action'
export * from './lib/app-connection/app-connection'
export * from './lib/app-connection/dto/read-app-connection-request'
export * from './lib/app-connection/dto/upsert-app-connection-request'
export * from './lib/common'
export * from './lib/common/activepieces-error'
export * from './lib/common/telemetry'
export * from './lib/engine'
export * from './lib/workers/job-data'
export * from './lib/workers/queue-metrics'
export * from './lib/flag'
export * from './lib/flow-run/dto/list-flow-runs-request'
export * from './lib/flow-run/execution/execution-output'
export * from './lib/flow-run/execution/step-output'
export * from './lib/flows/operations'
export * from './lib/flow-run/execution/execution-output'
export * from './lib/flow-run/execution/step-output'
export * from './lib/pieces'
export * from './lib/store-entry/dto/store-entry-request'
export * from './lib/webhook'
export * from './lib/flows/dto/count-flows-request'
export * from './lib/authentication/dto/authentication-response'
export * from './lib/authentication/dto/sign-up-request'
export * from './lib/authentication/dto/sign-in-request'
export * from './lib/authentication/model/principal-type'
export * from './lib/authentication/model/principal'
export * from './lib/analytics'
export * from './lib/flows/actions/action'
export * from './lib/store-entry/store-entry'
export * from './lib/user'
export * from './lib/flow-run/test-flow-run-request'
export * from './lib/flows/triggers/trigger'
export * from './lib/flows/flow-version'
export * from './lib/flows/flow'
export * from './lib/file'
export * from './lib/flow-run/flow-run'
export * from './lib/flows/dto/create-flow-request'
export * from './lib/common/seek-page'
export * from './lib/common/id-generator'
export * from './lib/flows/triggers/trigger-events/trigger-events-dto'
export * from './lib/flows/triggers/trigger-events/trigger-event'
export * from './lib/flows/sample-data'
export * from './lib/common/base-model'
export * from './lib/flows/folders/folder'
export * from './lib/flows/folders/folder-requests'
export * from './lib/flows/dto/flow-mcp.requests'
export * from './lib/flows'
export * from './lib/flows/dto/list-flows-request'
export * from './lib/project'
export * from './lib/forms'
export * from './lib/platform'
export * from './lib/flow-run/execution/flow-execution'
export * from './lib/ai-providers'
export * from './lib/tag'
export * from './lib/websocket'
export * from './lib/flow-run/execution/flow-execution'
export * from './lib/flow-run/execution/flow-execution'
export * from './lib/flow-run/execution/flow-execution'
export * from './lib/flow-run/execution/flow-execution'
export * from './lib/federated-authn'
export * from './lib/store-entry/store-entry'
export * from './lib/flow-run/test-flow-run-request'
export * from './lib/support-url'
export * from './lib/feedback-url'
export * from './lib/license-keys'
export * from './lib/invitations'
export * from './lib/workers'
export * from './lib/flows/util/flow-structure-util'
export * from './lib/flows/operations'
export * from './lib/flows/util/flow-piece-util'
export * from './lib/property/markdown'
export * from './lib/project-role/project-role'
export * from './lib/project-role/project-role.request'
export * from './lib/flow-run/log-serializer'
export * from './lib/tables'
export * from './lib/project-release/project-release'
export * from './lib/project-release/project-release.request'
export * from './lib/project-release/project-state'
export * from './lib/authentication/user-identity'
export * from './lib/flows/operations/paste-operations'
export * from './lib/todos'
export * from './lib/todos/todos-request'
export * from './lib/mcp'
export * from './lib/agents'
export * from './lib/solutions'
export * from './lib/flows/triggers/trigger'
export * from './lib/trigger'
export * from './lib/flows/triggers/trigger-run'
export * from './lib/template'
// Look at https://github.com/sinclairzx81/typebox/issues/350
import { TypeSystemPolicy } from '@sinclair/typebox/system'
export * from './lib/license-keys'
export * from './lib/flow-run/execution/flow-execution'
TypeSystemPolicy.ExactOptionalPropertyTypes = false
export * from './lib/workers/queue-metrics'
export * from './lib/health'

View File

@@ -0,0 +1,97 @@
import { Static, Type } from '@sinclair/typebox'
import { DiscriminatedUnion, Nullable } from '../common'
export * from './tools'
export enum AgentOutputFieldType {
TEXT = 'text',
NUMBER = 'number',
BOOLEAN = 'boolean',
}
export enum AgentTaskStatus {
COMPLETED = 'COMPLETED',
FAILED = 'FAILED',
IN_PROGRESS = 'IN_PROGRESS',
}
export enum ContentBlockType {
MARKDOWN = 'MARKDOWN',
TOOL_CALL = 'TOOL_CALL',
}
export enum ToolCallStatus {
IN_PROGRESS = 'in-progress',
COMPLETED = 'completed',
}
export enum ExecutionToolStatus {
SUCCESS = 'SUCCESS',
FAILED = 'FAILED',
}
export enum ToolCallType {
PIECE = 'PIECE',
FLOW = 'FLOW',
}
export const AgentOutputField = Type.Object({
displayName: Type.String(),
description: Type.Optional(Type.String()),
type: Type.Enum(AgentOutputFieldType),
})
export type AgentOutputField = Static<typeof AgentOutputField>
export type AgentResult = {
prompt: string
steps: AgentStepBlock[]
status: AgentTaskStatus
structuredOutput?: unknown
}
export enum AgentPieceProps {
AGENT_TOOLS = 'agentTools',
STRUCTURED_OUTPUT = 'structuredOutput',
PROMPT = 'prompt',
MAX_STEPS = 'maxSteps',
AI_PROVIDER = 'provider',
AI_MODEL = 'model',
}
export const MarkdownContentBlock = Type.Object({
type: Type.Literal(ContentBlockType.MARKDOWN),
markdown: Type.String(),
})
export type MarkdownContentBlock = Static<typeof MarkdownContentBlock>
const ToolCallBaseSchema = Type.Object({
type: Type.Literal(ContentBlockType.TOOL_CALL),
input: Nullable(Type.Record(Type.String(), Type.Unknown())),
output: Type.Optional(Type.Unknown()),
toolName: Type.String(),
status: Type.Enum(ToolCallStatus),
toolCallId: Type.String(),
startTime: Type.String(),
endTime: Type.Optional(Type.String()),
})
export type ToolCallBase = Static<typeof ToolCallBaseSchema>
export const ToolCallContentBlock = DiscriminatedUnion('toolCallType', [
Type.Object({
...ToolCallBaseSchema.properties,
toolCallType: Type.Literal(ToolCallType.PIECE),
pieceName: Type.String(),
pieceVersion: Type.String(),
actionName: Type.String(),
}),
Type.Object({
...ToolCallBaseSchema.properties,
toolCallType: Type.Literal(ToolCallType.FLOW),
displayName: Type.String(),
flowId: Type.String(),
}),
])
export type ToolCallContentBlock = Static<typeof ToolCallContentBlock>
export const AgentStepBlock = Type.Union([MarkdownContentBlock, ToolCallContentBlock])
export type AgentStepBlock = Static<typeof AgentStepBlock>

View File

@@ -0,0 +1,42 @@
import { Static, Type } from '@sinclair/typebox'
import { DiscriminatedUnion } from '../common'
import { ApId } from '../common/id-generator'
export const TASK_COMPLETION_TOOL_NAME = 'updateTaskStatus'
export enum AgentToolType {
PIECE = 'PIECE',
FLOW = 'FLOW',
}
const AgentToolBase = {
toolName: Type.String(),
}
export const AgentPieceToolMetadata = Type.Object({
pieceName: Type.String(),
pieceVersion: Type.String(),
actionName: Type.String(),
predefinedInput: Type.Record(Type.String(), Type.Unknown()),
})
export type AgentPieceToolMetadata = Static<typeof AgentPieceToolMetadata>
export const AgentPieceTool = Type.Object({
type: Type.Literal(AgentToolType.PIECE),
...AgentToolBase,
pieceMetadata: AgentPieceToolMetadata,
})
export type AgentPieceTool = Static<typeof AgentPieceTool>
export const AgentFlowTool = Type.Object({
type: Type.Literal(AgentToolType.FLOW),
...AgentToolBase,
flowId: ApId,
})
export type AgentFlowTool = Static<typeof AgentFlowTool>
export const AgentTool = DiscriminatedUnion('type', [
AgentPieceTool,
AgentFlowTool,
])
export type AgentTool = Static<typeof AgentTool>

View File

@@ -0,0 +1,119 @@
import { Static, Type } from '@sinclair/typebox'
import { BaseModelSchema, DiscriminatedUnion } from '../common/base-model'
export const AnthropicProviderConfig = Type.Object({
apiKey: Type.String(),
})
export type AnthropicProviderConfig = Static<typeof AnthropicProviderConfig>
export const AzureProviderConfig = Type.Object({
apiKey: Type.String(),
resourceName: Type.String(),
})
export type AzureProviderConfig = Static<typeof AzureProviderConfig>
export const GoogleProviderConfig = Type.Object({
apiKey: Type.String(),
})
export type GoogleProviderConfig = Static<typeof GoogleProviderConfig>
export const OpenAIProviderConfig = Type.Object({
apiKey: Type.String(),
})
export type OpenAIProviderConfig = Static<typeof OpenAIProviderConfig>
export const OpenRouterProviderConfig = Type.Object({
apiKey: Type.String(),
})
export type OpenRouterProviderConfig = Static<typeof OpenRouterProviderConfig>
export const AIProviderConfig = Type.Union([
AnthropicProviderConfig,
AzureProviderConfig,
GoogleProviderConfig,
OpenAIProviderConfig,
OpenRouterProviderConfig,
])
export type AIProviderConfig = Static<typeof AIProviderConfig>
export enum
AIProviderName {
OPENAI = 'openai',
OPENROUTER = 'openrouter',
ANTHROPIC = 'anthropic',
AZURE = 'azure',
GOOGLE = 'google',
ACTIVEPIECES = 'activepieces',
}
const ProviderConfigUnion = DiscriminatedUnion('provider', [
Type.Object({
provider: Type.Literal(AIProviderName.OPENAI),
config: OpenAIProviderConfig,
}),
Type.Object({
provider: Type.Literal(AIProviderName.OPENROUTER),
config: OpenRouterProviderConfig,
}),
Type.Object({
provider: Type.Literal(AIProviderName.ANTHROPIC),
config: AnthropicProviderConfig,
}),
Type.Object({
provider: Type.Literal(AIProviderName.AZURE),
config: AzureProviderConfig,
}),
Type.Object({
provider: Type.Literal(AIProviderName.GOOGLE),
config: GoogleProviderConfig,
}),
Type.Object({
provider: Type.Literal(AIProviderName.ACTIVEPIECES),
config: OpenRouterProviderConfig,
}),
])
export const AIProvider = Type.Intersect([
Type.Object({ ...BaseModelSchema }),
ProviderConfigUnion,
Type.Object({
displayName: Type.String({ minLength: 1 }),
platformId: Type.String(),
}),
])
export type AIProvider = Static<typeof AIProvider>
export const AIProviderWithoutSensitiveData = Type.Object({
id: Type.String(),
name: Type.String(),
configured: Type.Boolean(),
})
export type AIProviderWithoutSensitiveData = Static<typeof AIProviderWithoutSensitiveData>
export enum AIProviderModelType {
IMAGE = 'image',
TEXT = 'text',
}
export const AIProviderModel = Type.Object({
id: Type.String(),
name: Type.String(),
type: Type.Enum(AIProviderModelType),
})
export type AIProviderModel = Static<typeof AIProviderModel>
export const CreateAIProviderRequest = ProviderConfigUnion
export type CreateAIProviderRequest = Static<typeof CreateAIProviderRequest>
export const AIErrorResponse = Type.Object({
error: Type.Object({
message: Type.String(),
type: Type.String(),
code: Type.String(),
}),
})
export type AIErrorResponse = Static<typeof AIErrorResponse>

View File

@@ -0,0 +1,85 @@
import { Static, Type } from '@sinclair/typebox'
import { BaseModelSchema, Nullable } from '../common/base-model'
export const DEFAULT_ESTIMATED_TIME_SAVED_PER_STEP = 2
export const UpdateTimeSavedPerRunRequest = Type.Object({
flowId: Type.String(),
timeSavedPerRun: Nullable(Type.Number()),
})
export type UpdateTimeSavedPerRunRequest = Static<typeof UpdateTimeSavedPerRunRequest>
export const UpdatePlatformReportRequest = Type.Object({
estimatedTimeSavedPerStep: Nullable(Type.Number()),
outdated: Type.Boolean(),
})
export type UpdatePlatformReportRequest = Static<typeof UpdatePlatformReportRequest>
export const AnalyticsPieceReportItem = Type.Object({
name: Type.String(),
displayName: Type.String(),
logoUrl: Type.String(),
usageCount: Type.Number(),
})
export type AnalyticsPieceReportItem = Static<typeof AnalyticsPieceReportItem>
export const AnalyticsPieceReport = Type.Array(AnalyticsPieceReportItem)
export type AnalyticsPieceReport = Static<typeof AnalyticsPieceReport>
export const AnalyticsProjectReportItem = Type.Object({
id: Type.String(),
displayName: Type.String(),
activeFlows: Type.Number(),
totalFlows: Type.Number(),
})
export type AnalyticsProjectReportItem = Static<typeof AnalyticsProjectReportItem>
export const AnalyticsProjectReport = Type.Array(AnalyticsProjectReportItem)
export type AnalyticsProjectReport = Static<typeof AnalyticsProjectReport>
export const AnalyticsRunsUsageItem = Type.Object({
day: Type.String(),
totalRuns: Type.Number(),
minutesSaved: Type.Number(),
})
export type AnalyticsRunsUsageItem = Static<typeof AnalyticsRunsUsageItem>
export const AnalyticsRunsUsage = Type.Array(AnalyticsRunsUsageItem)
export type AnalyticsRunsUsage = Static<typeof AnalyticsRunsUsage>
export const AnalyticsFlowReportItem = Type.Object({
flowId: Type.String(),
flowName: Type.String(),
projectId: Type.String(),
projectName: Type.String(),
runs: Type.Number(),
timeSavedPerRun: Type.Object({
value: Nullable(Type.Number()),
isEstimated: Type.Boolean(),
}),
minutesSaved: Type.Number(),
})
export type AnalyticsFlowReportItem = Static<typeof AnalyticsFlowReportItem>
export const AnalyticsFlowReport = Type.Array(AnalyticsFlowReportItem)
export type AnalyticsFlowReport = Static<typeof AnalyticsFlowReport>
export const PlatformAnalyticsReport = Type.Object({
...BaseModelSchema,
estimatedTimeSavedPerStep: Nullable(Type.Number()),
totalFlows: Type.Number(),
activeFlows: Type.Number(),
outdated: Type.Boolean(),
totalUsers: Type.Number(),
activeUsers: Type.Number(),
totalProjects: Type.Number(),
activeFlowsWithAI: Type.Number(),
totalFlowRuns: Type.Number(),
topPieces: AnalyticsPieceReport,
topProjects: AnalyticsProjectReport,
runsUsage: AnalyticsRunsUsage,
flowsDetails: AnalyticsFlowReport,
platformId: Type.String(),
})
export type PlatformAnalyticsReport = Static<typeof PlatformAnalyticsReport>

View File

@@ -0,0 +1,153 @@
import { Static, Type } from '@sinclair/typebox'
import { BaseModel, BaseModelSchema, Nullable } from '../common/base-model'
import { ApId } from '../common/id-generator'
import { Metadata } from '../common/metadata'
import { UserWithMetaInformation } from '../user'
import { OAuth2GrantType } from './dto/upsert-app-connection-request'
import { OAuth2AuthorizationMethod } from './oauth2-authorization-method'
export type AppConnectionId = string
export enum AppConnectionStatus {
ACTIVE = 'ACTIVE',
MISSING = 'MISSING',
ERROR = 'ERROR',
}
export enum AppConnectionScope {
PROJECT = 'PROJECT',
PLATFORM = 'PLATFORM',
}
export enum AppConnectionType {
OAUTH2 = 'OAUTH2',
PLATFORM_OAUTH2 = 'PLATFORM_OAUTH2',
CLOUD_OAUTH2 = 'CLOUD_OAUTH2',
SECRET_TEXT = 'SECRET_TEXT',
BASIC_AUTH = 'BASIC_AUTH',
CUSTOM_AUTH = 'CUSTOM_AUTH',
NO_AUTH = 'NO_AUTH',
}
export type SecretTextConnectionValue = {
type: AppConnectionType.SECRET_TEXT
secret_text: string
}
export type BasicAuthConnectionValue = {
username: string
password: string
type: AppConnectionType.BASIC_AUTH
}
export type BaseOAuth2ConnectionValue = {
expires_in?: number
client_id: string
token_type: string
access_token: string
claimed_at: number
refresh_token: string
scope: string
token_url: string
authorization_method?: OAuth2AuthorizationMethod
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: Record<string, any>
props?: Record<string, unknown>
grant_type?: OAuth2GrantType
}
export type CustomAuthConnectionValue<T extends Record<string, unknown> = Record<string, unknown>> = {
type: AppConnectionType.CUSTOM_AUTH
props: T
}
export type CloudOAuth2ConnectionValue = {
type: AppConnectionType.CLOUD_OAUTH2
} & BaseOAuth2ConnectionValue
export type PlatformOAuth2ConnectionValue = {
type: AppConnectionType.PLATFORM_OAUTH2
redirect_url: string
} & BaseOAuth2ConnectionValue
export type OAuth2ConnectionValueWithApp = {
type: AppConnectionType.OAUTH2
client_secret: string
redirect_url: string
} & BaseOAuth2ConnectionValue
export type NoAuthConnectionValue = {
type: AppConnectionType.NO_AUTH
}
export type AppConnectionValue<T extends AppConnectionType = AppConnectionType, PropsType extends Record<string, unknown> = Record<string, unknown>> =
T extends AppConnectionType.SECRET_TEXT ? SecretTextConnectionValue :
T extends AppConnectionType.BASIC_AUTH ? BasicAuthConnectionValue :
T extends AppConnectionType.CLOUD_OAUTH2 ? CloudOAuth2ConnectionValue :
T extends AppConnectionType.PLATFORM_OAUTH2 ? PlatformOAuth2ConnectionValue :
T extends AppConnectionType.OAUTH2 ? OAuth2ConnectionValueWithApp :
T extends AppConnectionType.CUSTOM_AUTH ? CustomAuthConnectionValue<PropsType> :
T extends AppConnectionType.NO_AUTH ? NoAuthConnectionValue :
never
export type AppConnection<Type extends AppConnectionType = AppConnectionType> = BaseModel<AppConnectionId> & {
externalId: string
type: Type
scope: AppConnectionScope
pieceName: string
displayName: string
projectIds: string[]
platformId: string
status: AppConnectionStatus
ownerId: string
owner: UserWithMetaInformation | null
value: AppConnectionValue<Type>
metadata: Metadata | null
pieceVersion: string
}
export type OAuth2AppConnection = AppConnection<AppConnectionType.OAUTH2>
export type SecretKeyAppConnection = AppConnection<AppConnectionType.SECRET_TEXT>
export type CloudAuth2Connection = AppConnection<AppConnectionType.CLOUD_OAUTH2>
export type PlatformOAuth2Connection = AppConnection<AppConnectionType.PLATFORM_OAUTH2>
export type BasicAuthConnection = AppConnection<AppConnectionType.BASIC_AUTH>
export type CustomAuthConnection = AppConnection<AppConnectionType.CUSTOM_AUTH>
export type NoAuthConnection = AppConnection<AppConnectionType.NO_AUTH>
export const AppConnectionWithoutSensitiveData = Type.Object({
...BaseModelSchema,
externalId: Type.String(),
displayName: Type.String(),
type: Type.Enum(AppConnectionType),
pieceName: Type.String(),
projectIds: Type.Array(ApId),
platformId: Nullable(Type.String()),
scope: Type.Enum(AppConnectionScope),
status: Type.Enum(AppConnectionStatus),
ownerId: Nullable(Type.String()),
owner: Nullable(UserWithMetaInformation),
metadata: Nullable(Metadata),
flowIds: Nullable(Type.Array(ApId)),
pieceVersion: Type.String(),
}, {
description: 'App connection is a connection to an external app.',
})
export type AppConnectionWithoutSensitiveData = Static<typeof AppConnectionWithoutSensitiveData> & { __brand: 'AppConnectionWithoutSensitiveData' }
export const AppConnectionOwners = Type.Object({
firstName: Type.String(),
lastName: Type.String(),
email: Type.String(),
})
export type AppConnectionOwners = Static<typeof AppConnectionOwners>
/**i.e props: {projectId: "123"} and value: "{{projectId}}" will return "123" */
export const resolveValueFromProps = (props: Record<string, unknown> | undefined, value: string)=>{
let resolvedScope = value
if (!props) {
return resolvedScope
}
Object.entries(props).forEach(([key, value]) => {
resolvedScope = resolvedScope.replace(`{${key}}`, String(value))
})
return resolvedScope
}

View File

@@ -0,0 +1,31 @@
import { Static, Type } from '@sinclair/typebox'
import { AppConnectionScope, AppConnectionStatus } from '../app-connection'
export const ListAppConnectionsRequestQuery = Type.Object({
cursor: Type.Optional(Type.String({})),
projectId: Type.String(),
scope: Type.Optional(Type.Enum(AppConnectionScope)),
pieceName: Type.Optional(Type.String({})),
displayName: Type.Optional(Type.String({})),
status: Type.Optional(Type.Array(Type.Enum(AppConnectionStatus))),
limit: Type.Optional(Type.Number({})),
})
export type ListAppConnectionsRequestQuery = Static<
typeof ListAppConnectionsRequestQuery
>
export const GetAppConnectionForWorkerRequestQuery = Type.Object({
externalId: Type.String(),
})
export type GetAppConnectionForWorkerRequestQuery = Static<
typeof GetAppConnectionForWorkerRequestQuery
>
export const ListGlobalConnectionsRequestQuery = Type.Omit(ListAppConnectionsRequestQuery, ['projectId'])
export type ListGlobalConnectionsRequestQuery = Static<typeof ListGlobalConnectionsRequestQuery>
export const ListAppConnectionOwnersRequestQuery = Type.Object({
projectId: Type.String(),
})
export type ListAppConnectionOwnersRequestQuery = Static<typeof ListAppConnectionOwnersRequestQuery>

View File

@@ -0,0 +1,207 @@
import { Static, Type } from '@sinclair/typebox'
import { Metadata } from '../../common/metadata'
import { AppConnectionScope, AppConnectionType } from '../app-connection'
import { OAuth2AuthorizationMethod } from '../oauth2-authorization-method'
const commonAuthProps = {
externalId: Type.String({}),
displayName: Type.String({}),
pieceName: Type.String({}),
projectId: Type.String({}),
metadata: Type.Optional(Metadata),
pieceVersion: Type.Optional(Type.String({})),
}
export const BOTH_CLIENT_CREDENTIALS_AND_AUTHORIZATION_CODE = 'both_client_credentials_and_authorization_code'
export enum OAuth2GrantType {
AUTHORIZATION_CODE = 'authorization_code',
CLIENT_CREDENTIALS = 'client_credentials',
}
const propsSchema = Type.Record(Type.String(), Type.Unknown())
export const UpsertCustomAuthRequest = Type.Object({
...commonAuthProps,
type: Type.Literal(AppConnectionType.CUSTOM_AUTH),
value: Type.Object({
type: Type.Literal(AppConnectionType.CUSTOM_AUTH),
props: propsSchema,
}),
}, {
title: 'Custom Auth',
description: 'Custom Auth',
})
export const UpsertNoAuthRequest = Type.Object({
...commonAuthProps,
type: Type.Literal(AppConnectionType.NO_AUTH),
value: Type.Object({
type: Type.Literal(AppConnectionType.NO_AUTH),
}),
}, {
title: 'No Auth',
description: 'No Auth',
})
const commonOAuth2ValueProps = {
client_id: Type.String({
minLength: 1,
}),
code: Type.String({
minLength: 1,
}),
code_challenge: Type.Optional(Type.String({})),
scope: Type.String(),
authorization_method: Type.Optional(Type.Enum(OAuth2AuthorizationMethod)),
}
export const UpsertPlatformOAuth2Request = Type.Object({
...commonAuthProps,
type: Type.Literal(AppConnectionType.PLATFORM_OAUTH2),
value: Type.Object({
...commonOAuth2ValueProps,
props: Type.Optional(propsSchema),
type: Type.Literal(AppConnectionType.PLATFORM_OAUTH2),
redirect_url: Type.String({
minLength: 1,
}),
}),
}, {
title: 'Platform OAuth2',
description: 'Platform OAuth2',
})
export const UpsertCloudOAuth2Request = Type.Object({
...commonAuthProps,
type: Type.Literal(AppConnectionType.CLOUD_OAUTH2),
value: Type.Object({
...commonOAuth2ValueProps,
props: Type.Optional(propsSchema),
scope: Type.String(),
type: Type.Literal(AppConnectionType.CLOUD_OAUTH2),
}),
}, {
title: 'Cloud OAuth2',
description: 'Cloud OAuth2',
})
export const UpsertSecretTextRequest = Type.Object({
...commonAuthProps,
type: Type.Literal(AppConnectionType.SECRET_TEXT),
value: Type.Object({
type: Type.Literal(AppConnectionType.SECRET_TEXT),
secret_text: Type.String({
minLength: 1,
}),
}),
}, {
title: 'Secret Text',
description: 'Secret Text',
})
export const UpsertOAuth2Request = Type.Object({
...commonAuthProps,
type: Type.Literal(AppConnectionType.OAUTH2),
value: Type.Object({
...commonOAuth2ValueProps,
client_secret: Type.String({
minLength: 1,
}),
grant_type: Type.Optional(Type.Enum(OAuth2GrantType)),
props: Type.Optional(Type.Record(Type.String(), Type.Any())),
authorization_method: Type.Optional(Type.Enum(OAuth2AuthorizationMethod)),
redirect_url: Type.String({
minLength: 1,
}),
type: Type.Literal(AppConnectionType.OAUTH2),
}),
}, {
title: 'OAuth2',
description: 'OAuth2',
})
export const UpsertBasicAuthRequest = Type.Object({
...commonAuthProps,
type: Type.Literal(AppConnectionType.BASIC_AUTH),
value: Type.Object({
username: Type.String({
minLength: 1,
}),
password: Type.String({
minLength: 1,
}),
type: Type.Literal(AppConnectionType.BASIC_AUTH),
}),
}, {
title: 'Basic Auth',
description: 'Basic Auth',
})
export const UpsertAppConnectionRequestBody = Type.Union([
UpsertSecretTextRequest,
UpsertOAuth2Request,
UpsertCloudOAuth2Request,
UpsertPlatformOAuth2Request,
UpsertBasicAuthRequest,
UpsertCustomAuthRequest,
UpsertNoAuthRequest,
])
export type UpsertCloudOAuth2Request = Static<typeof UpsertCloudOAuth2Request>
export type UpsertPlatformOAuth2Request = Static<typeof UpsertPlatformOAuth2Request>
export type UpsertOAuth2Request = Static<typeof UpsertOAuth2Request>
export type UpsertSecretTextRequest = Static<typeof UpsertSecretTextRequest>
export type UpsertBasicAuthRequest = Static<typeof UpsertBasicAuthRequest>
export type UpsertCustomAuthRequest = Static<typeof UpsertCustomAuthRequest>
export type UpsertNoAuthRequest = Static<typeof UpsertNoAuthRequest>
export type UpsertAppConnectionRequestBody = Static<typeof UpsertAppConnectionRequestBody>
export const UpdateConnectionValueRequestBody = Type.Object({
displayName: Type.String({
minLength: 1,
}),
metadata: Type.Optional(Metadata),
})
export const UpdateGlobalConnectionValueRequestBody = Type.Object({
displayName: Type.String({
minLength: 1,
}),
projectIds: Type.Optional(Type.Array(Type.String())),
metadata: Type.Optional(Metadata),
})
export type UpdateConnectionValueRequestBody = Static<typeof UpdateConnectionValueRequestBody>
export type UpdateGlobalConnectionValueRequestBody = Static<typeof UpdateGlobalConnectionValueRequestBody>
const GlobalConnectionExtras = Type.Object({
scope: Type.Literal(AppConnectionScope.PLATFORM),
projectIds: Type.Array(Type.String()),
externalId: Type.Optional(Type.String()),
metadata: Type.Optional(Metadata),
})
export const UpsertGlobalConnectionRequestBody =
Type.Union([
Type.Composite([Type.Omit(UpsertSecretTextRequest, ['projectId', 'externalId']), GlobalConnectionExtras]),
Type.Composite([Type.Omit(UpsertOAuth2Request, ['projectId', 'externalId']), GlobalConnectionExtras]),
Type.Composite([Type.Omit(UpsertCloudOAuth2Request, ['projectId', 'externalId']), GlobalConnectionExtras]),
Type.Composite([Type.Omit(UpsertPlatformOAuth2Request, ['projectId', 'externalId']), GlobalConnectionExtras]),
Type.Composite([Type.Omit(UpsertBasicAuthRequest, ['projectId', 'externalId']), GlobalConnectionExtras]),
Type.Composite([Type.Omit(UpsertCustomAuthRequest, ['projectId', 'externalId']), GlobalConnectionExtras]),
Type.Composite([Type.Omit(UpsertNoAuthRequest, ['projectId', 'externalId']), GlobalConnectionExtras]),
])
export type UpsertGlobalConnectionRequestBody = Static<typeof UpsertGlobalConnectionRequestBody>
export const ReplaceAppConnectionsRequestBody = Type.Object({
sourceAppConnectionId: Type.String(),
targetAppConnectionId: Type.String(),
projectId: Type.String(),
})
export type ReplaceAppConnectionsRequestBody = Static<typeof ReplaceAppConnectionsRequestBody>
export const ListFlowsFromAppConnectionRequestQuery = Type.Object({
sourceAppConnectionIds: Type.Array(Type.String()),
projectId: Type.String(),
})
export type ListFlowsFromAppConnectionRequestQuery = Static<typeof ListFlowsFromAppConnectionRequestQuery>

View File

@@ -0,0 +1,5 @@
// Todo remove it's duplicated in frameworkr as well, make sure it's not exported in shared package.
export enum OAuth2AuthorizationMethod {
HEADER = 'HEADER',
BODY = 'BODY',
}

View File

@@ -0,0 +1,17 @@
import { Static, Type } from '@sinclair/typebox'
import { User } from '../../user/user'
import { UserIdentity } from '../user-identity'
export const UserWithoutPassword = Type.Pick(User, ['id', 'platformRole', 'status', 'externalId', 'platformId'])
export type UserWithoutPassword = Static<typeof UserWithoutPassword>
export const AuthenticationResponse = Type.Composite([
UserWithoutPassword,
Type.Pick(UserIdentity, ['verified', 'firstName', 'lastName', 'email', 'trackEvents', 'newsLetter']),
Type.Object({
token: Type.String(),
projectId: Type.String(),
}),
])
export type AuthenticationResponse = Static<typeof AuthenticationResponse>

View File

@@ -0,0 +1,9 @@
import { Static, Type } from '@sinclair/typebox'
import { EmailType, PasswordType } from '../../user/user'
export const SignInRequest = Type.Object({
email: EmailType,
password: PasswordType,
})
export type SignInRequest = Static<typeof SignInRequest>

View File

@@ -0,0 +1,31 @@
import { Static, Type } from '@sinclair/typebox'
import { SAFE_STRING_PATTERN } from '../../common'
import { ApId } from '../../common/id-generator'
import { EmailType, PasswordType } from '../../user/user'
export const SignUpRequest = Type.Object({
email: EmailType,
password: PasswordType,
firstName: Type.String({
pattern: SAFE_STRING_PATTERN,
}),
lastName: Type.String({
pattern: SAFE_STRING_PATTERN,
}),
trackEvents: Type.Boolean(),
newsLetter: Type.Boolean(),
})
export type SignUpRequest = Static<typeof SignUpRequest>
export const SwitchPlatformRequest = Type.Object({
platformId: ApId,
})
export type SwitchPlatformRequest = Static<typeof SwitchPlatformRequest>
export const SwitchProjectRequest = Type.Object({
projectId: ApId,
})
export type SwitchProjectRequest = Static<typeof SwitchProjectRequest>

View File

@@ -0,0 +1,18 @@
export enum PrincipalType {
USER = 'USER',
ENGINE = 'ENGINE',
SERVICE = 'SERVICE',
WORKER = 'WORKER',
UNKNOWN = 'UNKNOWN',
}
export const ALL_PRINCIPAL_TYPES = Object.values(PrincipalType)
export const SERVICE_KEY_SECURITY_OPENAPI = {
apiKey: [],
}
export enum EndpointScope {
PLATFORM = 'PLATFORM',
PROJECT = 'PROJECT',
}

View File

@@ -0,0 +1,54 @@
import { ApId } from '../../common/id-generator'
import { PlatformId } from '../../platform'
import { ProjectId } from '../../project'
import { PrincipalType } from './principal-type'
export type WorkerPrincipal = {
id: ApId
type: PrincipalType.WORKER
}
export type AnnonymousPrincipal = {
id: ApId
type: PrincipalType.UNKNOWN
}
export type ServicePrincipal = {
id: ApId
type: PrincipalType.SERVICE
projectId: ProjectId
platform: {
id: ApId
}
}
export type UserPrincipal = {
id: ApId
type: PrincipalType.USER
projectId: ProjectId
platform: {
id: ApId
}
tokenVersion?: string
}
export type EnginePrincipal = {
id: ApId
type: PrincipalType.ENGINE
projectId: ProjectId
platform: {
id: PlatformId
}
}
export type PrincipalForType<T extends PrincipalType> = Extract<Principal, { type: T }>
export type PrincipalForTypes<R extends readonly PrincipalType[]> = PrincipalForType<R[number]>
export type Principal =
| WorkerPrincipal
| AnnonymousPrincipal
| ServicePrincipal
| UserPrincipal
| EnginePrincipal

View File

@@ -0,0 +1,24 @@
import { Static, Type } from '@sinclair/typebox'
import { BaseModelSchema } from '../common'
export enum UserIdentityProvider {
EMAIL = 'EMAIL',
GOOGLE = 'GOOGLE',
SAML = 'SAML',
JWT = 'JWT',
}
export const UserIdentity = Type.Object({
...BaseModelSchema,
firstName: Type.String(),
lastName: Type.String(),
email: Type.String(),
password: Type.String(),
trackEvents: Type.Boolean(),
newsLetter: Type.Boolean(),
verified: Type.Boolean(),
tokenVersion: Type.Optional(Type.String()),
provider: Type.Enum(UserIdentityProvider),
})
export type UserIdentity = Static<typeof UserIdentity>

View File

@@ -0,0 +1,572 @@
import { FileId } from '../file'
import { FlowRunId } from '../flow-run/flow-run'
import { FlowId } from '../flows/flow'
import { FlowVersionId } from '../flows/flow-version'
import { PlatformUsageMetric } from '../platform'
import { ProjectId } from '../project'
import { ProjectRole } from '../project-role/project-role'
import { UserId } from '../user'
import { ApId } from './id-generator'
import { Permission } from './security'
export class ActivepiecesError extends Error {
constructor(public error: ApErrorParams, message?: string) {
super(error.code + (message ? `: ${message}` : ''))
}
override toString(): string {
return JSON.stringify({
code: this.error.code,
message: this.message,
params: this.error.params,
})
}
}
export type ApErrorParams =
| AuthenticationParams
| AuthorizationErrorParams
| ConfigNotFoundErrorParams
| EmailIsNotVerifiedErrorParams
| EngineOperationFailureParams
| EntityNotFoundErrorParams
| ExecutionTimeoutErrorParams
| ExistingUserErrorParams
| FileNotFoundErrorParams
| FlowFormNotFoundError
| FlowNotFoundErrorParams
| FlowIsLockedErrorParams
| FlowOperationErrorParams
| FlowOperationInProgressErrorParams
| FlowRunNotFoundErrorParams
| InvalidApiKeyParams
| InvalidAppConnectionParams
| InvalidBearerTokenParams
| InvalidClaimParams
| InvalidCloudClaimParams
| InvalidCredentialsErrorParams
| InvalidJwtTokenErrorParams
| InvalidOtpParams
| InvalidSAMLResponseParams
| InvitationOnlySignUpParams
| JobRemovalFailureErrorParams
| OpenAiFailedErrorParams
| PauseMetadataMissingErrorParams
| PermissionDeniedErrorParams
| PieceNotFoundErrorParams
| PieceTriggerNotFoundErrorParams
| QuotaExceededParams
| FeatureDisabledErrorParams
| SignUpDisabledParams
| StepNotFoundErrorParams
| SystemInvalidErrorParams
| SystemPropNotDefinedErrorParams
| TestTriggerFailedErrorParams
| TriggerUpdateStatusErrorParams
| TriggerFailedErrorParams
| ValidationErrorParams
| InvitationOnlySignUpParams
| UserIsInActiveErrorParams
| DomainIsNotAllowedErrorParams
| EmailAuthIsDisabledParams
| ExistingAlertChannelErrorParams
| EmailAlreadyHasActivationKey
| ProviderProxyConfigNotFoundParams
| AIProviderModelNotSupportedParams
| AIProviderNotSupportedParams
| AIRequestNotSupportedParams
| AICreditLimitExceededParams
| SessionExpiredParams
| InvalidLicenseKeyParams
| NoChatResponseParams
| InvalidSmtpCredentialsErrorParams
| InvalidGitCredentialsParams
| InvalidReleaseTypeParams
| ProjectExternalIdAlreadyExistsParams
| MemoryIssueParams
| InvalidCustomDomainErrorParams
| McpPieceRequiresConnectionParams
| McpPieceConnectionMismatchParams
| ErrorUpdatingSubscriptionParams
| TriggerExecutionFailedParams
| SubflowFailedParams
| MachineNotAvailableParams
| MachineNotConnectedParams
| DoesNotMeetBusinessRequirementsParams
export type TriggerExecutionFailedParams = BaseErrorParams<ErrorCode.TRIGGER_EXECUTION_FAILED, {
flowId: FlowId
message?: string
pieceName: string
pieceVersion: string
}>
export type BaseErrorParams<T, V> = {
code: T
params: V
}
export type MemoryIssueParams = BaseErrorParams<ErrorCode.MEMORY_ISSUE, {
standardOutput: string
standardError: string
}>
export type InvitationOnlySignUpParams = BaseErrorParams<
ErrorCode.INVITATION_ONLY_SIGN_UP,
{
message?: string
}
>
export type InvalidClaimParams = BaseErrorParams<ErrorCode.INVALID_CLAIM, { redirectUrl: string, tokenUrl: string, clientId: string, message: string }>
export type InvalidCloudClaimParams = BaseErrorParams<ErrorCode.INVALID_CLOUD_CLAIM, { pieceName: string }>
export type InvalidBearerTokenParams = BaseErrorParams<ErrorCode.INVALID_BEARER_TOKEN, {
message?: string
}>
export type SessionExpiredParams = BaseErrorParams<ErrorCode.SESSION_EXPIRED, {
message?: string
}>
export type NoChatResponseParams = BaseErrorParams<ErrorCode.NO_CHAT_RESPONSE, Record<string, never>>
export type FileNotFoundErrorParams = BaseErrorParams<ErrorCode.FILE_NOT_FOUND, { id: FileId }>
export type EmailAuthIsDisabledParams = BaseErrorParams<ErrorCode.EMAIL_AUTH_DISABLED, Record<string, never>>
export type AuthorizationErrorParams = BaseErrorParams<
ErrorCode.AUTHORIZATION,
Record<string, string> &
{
message?: string
}
>
export type AICreditLimitExceededParams = BaseErrorParams<ErrorCode.AI_CREDIT_LIMIT_EXCEEDED, {
usage: number
limit: number
}>
export type PermissionDeniedErrorParams = BaseErrorParams<
ErrorCode.PERMISSION_DENIED,
{
userId: UserId
projectId: ProjectId
projectRole: ProjectRole | null
permission: Permission | undefined
}
>
export type SystemInvalidErrorParams = BaseErrorParams<
ErrorCode.SYSTEM_PROP_INVALID,
{
prop: string
}
>
export type FlowNotFoundErrorParams = BaseErrorParams<
ErrorCode.FLOW_NOT_FOUND,
{
id: FlowId
}
>
export type FlowRunNotFoundErrorParams = BaseErrorParams<
ErrorCode.FLOW_RUN_NOT_FOUND,
{
id: FlowRunId
}
>
export type InvalidCredentialsErrorParams = BaseErrorParams<
ErrorCode.INVALID_CREDENTIALS,
null
>
export type DomainIsNotAllowedErrorParams = BaseErrorParams<
ErrorCode.DOMAIN_NOT_ALLOWED,
{
domain: string
}
>
export type EmailIsNotVerifiedErrorParams = BaseErrorParams<
ErrorCode.EMAIL_IS_NOT_VERIFIED,
{
email: string
}
>
export type UserIsInActiveErrorParams = BaseErrorParams<
ErrorCode.USER_IS_INACTIVE,
{
email: string
}
>
export type ExistingUserErrorParams = BaseErrorParams<
ErrorCode.EXISTING_USER,
{
email: string
platformId: string | null
}
>
export type StepNotFoundErrorParams = BaseErrorParams<
ErrorCode.STEP_NOT_FOUND,
{
pieceName?: string
pieceVersion?: string
stepName: string
}
>
export type PieceNotFoundErrorParams = BaseErrorParams<
ErrorCode.PIECE_NOT_FOUND,
{
pieceName: string
pieceVersion: string | undefined
message: string
}
>
export type PieceTriggerNotFoundErrorParams = BaseErrorParams<
ErrorCode.PIECE_TRIGGER_NOT_FOUND,
{
pieceName: string
pieceVersion: string
triggerName: string | undefined
}
>
export type TriggerFailedErrorParams = BaseErrorParams<
ErrorCode.TRIGGER_FAILED,
{
pieceName: string
pieceVersion: string
triggerName: string
error: string | undefined
}
>
export type ConfigNotFoundErrorParams = BaseErrorParams<
ErrorCode.CONFIG_NOT_FOUND,
{
pieceName: string
pieceVersion: string
stepName: string
configName: string
}
>
export type JobRemovalFailureErrorParams = BaseErrorParams<
ErrorCode.JOB_REMOVAL_FAILURE,
{
flowVersionId: ApId
}
>
export type SystemPropNotDefinedErrorParams = BaseErrorParams<
ErrorCode.SYSTEM_PROP_NOT_DEFINED,
{
prop: string
}
>
export type OpenAiFailedErrorParams = BaseErrorParams<
ErrorCode.OPEN_AI_FAILED,
Record<string, never>
>
export type FlowOperationErrorParams = BaseErrorParams<
ErrorCode.FLOW_OPERATION_INVALID,
{
message: string
}
>
export type FlowOperationInProgressErrorParams = BaseErrorParams<
ErrorCode.FLOW_OPERATION_IN_PROGRESS, {
message: string
}>
export type FlowFormNotFoundError = BaseErrorParams<
ErrorCode.FLOW_FORM_NOT_FOUND,
{
flowId: FlowVersionId
message: string
}>
export type FlowIsLockedErrorParams = BaseErrorParams<
ErrorCode.FLOW_IN_USE,
{
flowVersionId: FlowVersionId
message: string
}>
export type InvalidJwtTokenErrorParams = BaseErrorParams<
ErrorCode.INVALID_OR_EXPIRED_JWT_TOKEN,
{
token: string
}
>
export type TestTriggerFailedErrorParams = BaseErrorParams<
ErrorCode.TEST_TRIGGER_FAILED,
{
message: string
}
>
export type EntityNotFoundErrorParams = BaseErrorParams<
ErrorCode.ENTITY_NOT_FOUND,
{
message?: string
entityType?: string
entityId?: string
}
>
export type InvalidCustomDomainErrorParams = BaseErrorParams<
ErrorCode.INVALID_CUSTOM_DOMAIN,
{
message: string
}
>
export type ExecutionTimeoutErrorParams = BaseErrorParams<
ErrorCode.EXECUTION_TIMEOUT,
{
standardOutput: string
standardError: string
}
>
export type ValidationErrorParams = BaseErrorParams<
ErrorCode.VALIDATION,
{
message: string
}
>
export type TriggerUpdateStatusErrorParams = BaseErrorParams<
ErrorCode.TRIGGER_UPDATE_STATUS,
{
flowId?: FlowId
flowVersionId?: FlowVersionId
message?: string
standardOutput?: string
standardError?: string
}
>
export type PauseMetadataMissingErrorParams = BaseErrorParams<
ErrorCode.PAUSE_METADATA_MISSING,
Record<string, never>
>
export type InvalidApiKeyParams = BaseErrorParams<
ErrorCode.INVALID_API_KEY,
Record<string, never>
>
export type EngineOperationFailureParams = BaseErrorParams<
ErrorCode.ENGINE_OPERATION_FAILURE,
{
message: string
context?: unknown
}
>
export type InvalidAppConnectionParams = BaseErrorParams<
ErrorCode.INVALID_APP_CONNECTION,
{
error: string
}
>
export type QuotaExceededParams = BaseErrorParams<
ErrorCode.QUOTA_EXCEEDED,
{
metric: PlatformUsageMetric
}
>
export type ErrorUpdatingSubscriptionParams = BaseErrorParams<
ErrorCode.ERROR_UPDATING_SUBSCRIPTION,
{
message: string
}>
export type ProviderProxyConfigNotFoundParams = BaseErrorParams<
ErrorCode.PROVIDER_PROXY_CONFIG_NOT_FOUND_FOR_PROVIDER,
{
provider: string
}>
export type AIProviderModelNotSupportedParams = BaseErrorParams<ErrorCode.AI_MODEL_NOT_SUPPORTED, {
provider: string
model: string
}>
export type AIProviderNotSupportedParams = BaseErrorParams<ErrorCode.AI_PROVIDER_NOT_SUPPORTED, {
provider: string
}>
export type AIRequestNotSupportedParams = BaseErrorParams<ErrorCode.AI_REQUEST_NOT_SUPPORTED, {
message: string
}>
export type FeatureDisabledErrorParams = BaseErrorParams<
ErrorCode.FEATURE_DISABLED,
{
message: string
}>
export type SignUpDisabledParams = BaseErrorParams<
ErrorCode.SIGN_UP_DISABLED,
Record<string, never>
>
export type AuthenticationParams = BaseErrorParams<
ErrorCode.AUTHENTICATION,
{
message: string
}>
export type InvalidSAMLResponseParams = BaseErrorParams<
ErrorCode.INVALID_SAML_RESPONSE,
{
message: string
}>
export type ExistingAlertChannelErrorParams = BaseErrorParams<
ErrorCode.EXISTING_ALERT_CHANNEL,
{
email: string
}>
export type InvalidOtpParams = BaseErrorParams<ErrorCode.INVALID_OTP, Record<string, never>>
export type InvalidLicenseKeyParams = BaseErrorParams<ErrorCode.INVALID_LICENSE_KEY, {
key: string
}>
export type EmailAlreadyHasActivationKey = BaseErrorParams<ErrorCode.EMAIL_ALREADY_HAS_ACTIVATION_KEY, {
email: string
}>
export type InvalidSmtpCredentialsErrorParams = BaseErrorParams<ErrorCode.INVALID_SMTP_CREDENTIALS, {
message: string
}>
export type InvalidGitCredentialsParams = BaseErrorParams<ErrorCode.INVALID_GIT_CREDENTIALS, {
message: string
}>
export type InvalidReleaseTypeParams = BaseErrorParams<ErrorCode.INVALID_RELEASE_TYPE, {
message: string
}>
export type ProjectExternalIdAlreadyExistsParams = BaseErrorParams<ErrorCode.PROJECT_EXTERNAL_ID_ALREADY_EXISTS, {
externalId: string
}>
export type McpPieceRequiresConnectionParams = BaseErrorParams<ErrorCode.MCP_PIECE_REQUIRES_CONNECTION, {
pieceName: string
}>
export type McpPieceConnectionMismatchParams = BaseErrorParams<ErrorCode.MCP_PIECE_CONNECTION_MISMATCH, {
pieceName: string
connectionPieceName: string
connectionId: string
}>
export type SubflowFailedParams = BaseErrorParams<ErrorCode.SUBFLOW_FAILED, {
message: string
}>
export type MachineNotAvailableParams = BaseErrorParams<ErrorCode.MACHINE_NOT_AVAILABLE, {
resourceType: string
}>
export type MachineNotConnectedParams = BaseErrorParams<ErrorCode.MACHINE_NOT_CONNECTED, {
message: string
}>
export type DoesNotMeetBusinessRequirementsParams = BaseErrorParams<ErrorCode.DOES_NOT_MEET_BUSINESS_REQUIREMENTS, {
message: string
}>
export enum ErrorCode {
MACHINE_NOT_CONNECTED = 'MACHINE_NOT_CONNECTED',
MACHINE_NOT_AVAILABLE = 'MACHINE_NOT_AVAILABLE',
INVALID_CUSTOM_DOMAIN = 'INVALID_CUSTOM_DOMAIN',
NO_CHAT_RESPONSE = 'NO_CHAT_RESPONSE',
ERROR_UPDATING_SUBSCRIPTION = 'ERROR_UPDATING_SUBSCRIPTION',
AUTHENTICATION = 'AUTHENTICATION',
AUTHORIZATION = 'AUTHORIZATION',
PROVIDER_PROXY_CONFIG_NOT_FOUND_FOR_PROVIDER = 'PROVIDER_PROXY_CONFIG_NOT_FOUND_FOR_PROVIDER',
AI_MODEL_NOT_SUPPORTED = 'AI_MODEL_NOT_SUPPORTED',
AI_PROVIDER_NOT_SUPPORTED = 'AI_PROVIDER_NOT_SUPPORTED',
AI_REQUEST_NOT_SUPPORTED = 'AI_REQUEST_NOT_SUPPORTED',
CONFIG_NOT_FOUND = 'CONFIG_NOT_FOUND',
DOMAIN_NOT_ALLOWED = 'DOMAIN_NOT_ALLOWED',
EMAIL_IS_NOT_VERIFIED = 'EMAIL_IS_NOT_VERIFIED',
ENGINE_OPERATION_FAILURE = 'ENGINE_OPERATION_FAILURE',
ENTITY_NOT_FOUND = 'ENTITY_NOT_FOUND',
EXECUTION_TIMEOUT = 'EXECUTION_TIMEOUT',
MEMORY_ISSUE = 'MEMORY_ISSUE',
TRIGGER_EXECUTION_FAILED = 'TRIGGER_EXECUTION_FAILED',
EMAIL_AUTH_DISABLED = 'EMAIL_AUTH_DISABLED',
EXISTING_USER = 'EXISTING_USER',
EXISTING_ALERT_CHANNEL = 'EXISTING_ALERT_CHANNEL',
PROJECT_EXTERNAL_ID_ALREADY_EXISTS = 'PROJECT_EXTERNAL_ID_ALREADY_EXISTS',
FLOW_FORM_NOT_FOUND = 'FLOW_FORM_NOT_FOUND',
FILE_NOT_FOUND = 'FILE_NOT_FOUND',
FLOW_INSTANCE_NOT_FOUND = 'INSTANCE_NOT_FOUND',
FLOW_NOT_FOUND = 'FLOW_NOT_FOUND',
FLOW_OPERATION_INVALID = 'FLOW_OPERATION_INVALID',
FLOW_OPERATION_IN_PROGRESS = 'FLOW_OPERATION_IN_PROGRESS',
FLOW_IN_USE = 'FLOW_IN_USE',
FLOW_RUN_NOT_FOUND = 'FLOW_RUN_NOT_FOUND',
INVALID_API_KEY = 'INVALID_API_KEY',
INVALID_APP_CONNECTION = 'INVALID_APP_CONNECTION',
INVALID_BEARER_TOKEN = 'INVALID_BEARER_TOKEN',
SESSION_EXPIRED = 'SESSION_EXPIRED',
INVALID_CLAIM = 'INVALID_CLAIM',
INVALID_CLOUD_CLAIM = 'INVALID_CLOUD_CLAIM',
INVALID_CREDENTIALS = 'INVALID_CREDENTIALS',
INVALID_OR_EXPIRED_JWT_TOKEN = 'INVALID_OR_EXPIRED_JWT_TOKEN',
INVALID_OTP = 'INVALID_OTP',
INVALID_SAML_RESPONSE = 'INVALID_SAML_RESPONSE',
INVITATION_ONLY_SIGN_UP = 'INVITATION_ONLY_SIGN_UP',
JOB_REMOVAL_FAILURE = 'JOB_REMOVAL_FAILURE',
OPEN_AI_FAILED = 'OPEN_AI_FAILED',
PAUSE_METADATA_MISSING = 'PAUSE_METADATA_MISSING',
PERMISSION_DENIED = 'PERMISSION_DENIED',
PIECE_NOT_FOUND = 'PIECE_NOT_FOUND',
PIECE_TRIGGER_NOT_FOUND = 'PIECE_TRIGGER_NOT_FOUND',
QUOTA_EXCEEDED = 'QUOTA_EXCEEDED',
FEATURE_DISABLED = 'FEATURE_DISABLED',
AI_CREDIT_LIMIT_EXCEEDED = 'AI_CREDIT_LIMIT_EXCEEDED',
SIGN_UP_DISABLED = 'SIGN_UP_DISABLED',
STEP_NOT_FOUND = 'STEP_NOT_FOUND',
SYSTEM_PROP_INVALID = 'SYSTEM_PROP_INVALID',
SYSTEM_PROP_NOT_DEFINED = 'SYSTEM_PROP_NOT_DEFINED',
TEST_TRIGGER_FAILED = 'TEST_TRIGGER_FAILED',
TRIGGER_UPDATE_STATUS = 'TRIGGER_UPDATE_STATUS',
TRIGGER_FAILED = 'TRIGGER_FAILED',
USER_IS_INACTIVE = 'USER_IS_INACTIVE',
VALIDATION = 'VALIDATION',
INVALID_LICENSE_KEY = 'INVALID_LICENSE_KEY',
EMAIL_ALREADY_HAS_ACTIVATION_KEY = 'EMAIL_ALREADY_HAS_ACTIVATION_KEY',
INVALID_SMTP_CREDENTIALS = 'INVALID_SMTP_CREDENTIALS',
INVALID_GIT_CREDENTIALS = 'INVALID_GIT_CREDENTIALS',
INVALID_RELEASE_TYPE = 'INVALID_RELEASE_TYPE',
MCP_PIECE_REQUIRES_CONNECTION = 'MCP_PIECE_REQUIRES_CONNECTION',
MCP_PIECE_CONNECTION_MISMATCH = 'MCP_PIECE_CONNECTION_MISMATCH',
SUBFLOW_FAILED = 'SUBFLOW_FAILED',
DOES_NOT_MEET_BUSINESS_REQUIREMENTS = 'DOES_NOT_MEET_BUSINESS_REQUIREMENTS',
}

View File

@@ -0,0 +1,65 @@
import { CreateType, Kind, SchemaOptions, Static, TEnum, TLiteral, TObject, TSchema, TUnion, Type } from '@sinclair/typebox'
export type BaseModel<T> = {
id: T
created: string
updated: string
}
export const BaseModelSchema = {
id: Type.String(),
created: Type.String(),
updated: Type.String(),
}
// Used to generate valid nullable in OpenAPI Schema
export const Nullable = <T extends TSchema>(schema: T) => Type.Optional(Type.Unsafe<Static<T> | null>({
...schema, nullable: true,
}))
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function NullableEnum<T extends TEnum<any>>(schema: T) {
const values = schema.anyOf.map(f => f.const)
return Type.Optional(Type.Unsafe<Static<T> | null>({ type: 'string', enum: values, nullable: true }))
}
// ------------------------------------------------------------------
// TDiscriminatedUnionObject
//
// Constructs a base TObject type requiring 1 discriminator property
// ------------------------------------------------------------------
// prettier-ignore
type TDiscriminatedUnionProperties<Discriminator extends string> = {
[_ in Discriminator]: TLiteral
}
// prettier-ignore
type TDiscriminatedUnionObject<Discriminator extends string> = TObject<TDiscriminatedUnionProperties<Discriminator>>
// ------------------------------------------------------------------
// DiscriminatedUnion
// ------------------------------------------------------------------
export type TDiscriminatedUnion<Types extends TObject[] = TObject[]> = {
[Kind]: 'DiscriminatedUnion'
static: Static<TUnion<Types>>
anyOf: Types
discriminator: {
propertyName: string
mapping?: Record<string, string>
}
} & TSchema
/** Creates a DiscriminatedUnion that works with OpenAPI. */
export function DiscriminatedUnion<Discriminator extends string, Types extends TDiscriminatedUnionObject<Discriminator>[]>(
discriminator: Discriminator,
types: [...Types],
options?: SchemaOptions,
): TDiscriminatedUnion<Types> {
return CreateType({
[Kind]: 'DiscriminatedUnion',
anyOf: types,
discriminator: {
propertyName: discriminator,
},
}, options) as never
}

View File

@@ -0,0 +1,6 @@
import { Static, Type } from '@sinclair/typebox'
export const ColorHex = Type.String({
pattern: '^#[0-9A-Fa-f]{6}$',
})
export type ColorHex = Static<typeof ColorHex>

View File

@@ -0,0 +1,15 @@
import { Static, Type } from '@sinclair/typebox'
import { customAlphabet } from 'nanoid'
const ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
const ID_LENGTH = 21
export const ApId = Type.String({
pattern: `^[0-9a-zA-Z]{${ID_LENGTH}}$`,
})
export type ApId = Static<typeof ApId>
export const apId = customAlphabet(ALPHABET, ID_LENGTH)
export const secureApId = (length: number) => customAlphabet(ALPHABET, length)()

View File

@@ -0,0 +1,8 @@
export * from './utils'
export * from './base-model'
export * from './locale'
export * from './security'
export * from './multipart-file'
export * from './metadata'
export * from './try-catch'
export * from './color'

View File

@@ -0,0 +1,12 @@
export enum LocalesEnum {
DUTCH = 'nl',
ENGLISH = 'en',
GERMAN = 'de',
FRENCH = 'fr',
SPANISH = 'es',
JAPANESE = 'ja',
CHINESE_SIMPLIFIED = 'zh',
PORTUGUESE = 'pt',
ARABIC = 'ar',
CHINESE_TRADITIONAL = 'zh-TW',
}

View File

@@ -0,0 +1,22 @@
import { Static, Type } from '@sinclair/typebox'
/**
* Flexible key-value record type for storing arbitrary data
*
* The Metadata type provides a flexible way to store additional information
* on various entities without requiring schema changes. It can be used for:
* - Custom categorization
* - Integration with external systems
* - Environment-specific configurations
* - Analytics and tracking information
*
* @example
* // Example metadata for a project
* const metadata = {
* department: "marketing",
* priority: 1,
* customField: "customValue"
* }
*/
export const Metadata = Type.Record(Type.String(), Type.Unknown())
export type Metadata = Static<typeof Metadata>

View File

@@ -0,0 +1,17 @@
import { Static, Type } from '@sinclair/typebox'
export const ApMultipartFile = Type.Object({
filename: Type.String(),
data: Type.Unknown(),
type: Type.Literal('file'),
mimetype: Type.Optional(Type.String()),
})
export type ApMultipartFile = Static<typeof ApMultipartFile> & {
data: Buffer
}
export const isMultipartFile = (value: unknown): value is ApMultipartFile => {
return typeof value === 'object' && value !== null && 'type' in value && value.type === 'file' && 'filename' in value && 'data' in value && value.data instanceof Buffer
}

View File

@@ -0,0 +1,2 @@
export * from './permission'
export const SAFE_STRING_PATTERN = '^[^./]+$'

View File

@@ -0,0 +1,33 @@
// Check access-control-list.ts for the list of permissions, you can add new permissions there, restart the main server to apply the changes
export enum Permission {
READ_APP_CONNECTION = 'READ_APP_CONNECTION',
WRITE_APP_CONNECTION = 'WRITE_APP_CONNECTION',
READ_FLOW = 'READ_FLOW',
WRITE_FLOW = 'WRITE_FLOW',
UPDATE_FLOW_STATUS = 'UPDATE_FLOW_STATUS',
WRITE_INVITATION = 'WRITE_INVITATION',
READ_INVITATION = 'READ_INVITATION',
READ_PROJECT_MEMBER = 'READ_PROJECT_MEMBER',
WRITE_PROJECT_MEMBER = 'WRITE_PROJECT_MEMBER',
WRITE_PROJECT_RELEASE = 'WRITE_PROJECT_RELEASE',
READ_PROJECT_RELEASE = 'READ_PROJECT_RELEASE',
READ_RUN = 'READ_RUN',
WRITE_RUN = 'WRITE_RUN',
READ_FOLDER = 'READ_FOLDER',
WRITE_FOLDER = 'WRITE_FOLDER',
WRITE_ALERT = 'WRITE_ALERT',
READ_ALERT = 'READ_ALERT',
READ_MCP = 'READ_MCP',
WRITE_MCP = 'WRITE_MCP',
WRITE_PROJECT = 'WRITE_PROJECT',
READ_PROJECT = 'READ_PROJECT',
READ_TODOS = 'READ_TODOS',
WRITE_TODOS = 'WRITE_TODOS',
READ_TABLE = 'READ_TABLE',
WRITE_TABLE = 'WRITE_TABLE',
}
export enum RoleType {
DEFAULT = 'DEFAULT',
CUSTOM = 'CUSTOM',
}

View File

@@ -0,0 +1,16 @@
import { TSchema, Type } from '@sinclair/typebox'
import { Nullable } from './base-model'
export type Cursor = string | null
export type SeekPage<T> = {
next: Cursor
previous: Cursor
data: T[]
}
export const SeekPage = (t: TSchema): TSchema => Type.Object({
data: Type.Array(t),
next: Nullable(Type.String({ description: 'Cursor to the next page' })),
previous: Nullable(Type.String({ description: 'Cursor to the previous page' })),
})

View File

@@ -0,0 +1,253 @@
import { RunEnvironment } from '../flow-run/flow-run'
import { FlowId } from '../flows/flow'
import { McpId } from '../mcp/mcp'
import { ProjectId } from '../project/project'
import { UserId } from '../user/user'
type FlowCreated = {
flowId: FlowId
}
type PiecesSearch = {
target: 'steps' | 'triggers'
search: string
}
type TemplateSearch = {
search: string
tags: string[]
pieces: string[]
}
type RunCreated = {
projectId: ProjectId
flowId: FlowId
environment: RunEnvironment
count: number
}
type FlowPublished = {
flowId: FlowId
}
type SignedUp = {
userId: UserId
email: string
firstName: string
lastName: string
projectId: ProjectId
}
type QuotaAlert = {
percentageUsed: number
}
type FlowImported = {
id: string
name: string
location:
| 'import flow view'
| 'inside the builder'
| 'import flow by uri encoded query param'
tab?: string
}
type FlowImportedUsingFile = {
location: 'inside dashboard' | 'inside the builder'
multiple: boolean
}
type FlowIssueClicked = {
flowId: string
}
type FlowIssueResolved = {
flowId: string
}
type RequestTrialSubmitted = {
fullName: string
email: string
numberOfEmployees: string
companyName: string
goal: string
}
type RequestTrialClicked = {
location: string
}
type KeyActivated = {
date: string
key: string
}
type UpgradeClicked = {
limitType?: 'team'
}
type UpgradePopup = {
limitType?: 'team'
}
type ReferralLinkCopied = {
userId: UserId
}
type RewardButtonClicked = {
source: 'note' | 'rewards-button'
}
type RewardInstructionsClicked = {
type: 'share-template' | 'linkedin' | 'referral' | 'contribute-piece'
}
type Referral = {
referredUserId: UserId
}
type FlowShared = {
flowId: FlowId
projectId: ProjectId
}
type OpenedFromDashboard = {
location: 'sidenav' | 'tasks-progress'
}
type FormsViewed = {
flowId: string
projectId: string
formProps: Record<string, unknown>
}
type UserInvited = {
platformId: string
projectId?: string
email: string
}
type TriggerFailuresExceeded = {
projectId: string
flowId: string
pieceName: string
pieceVersion: string
}
type AiProviderConfiguredOrUsed = {
provider: string
projectId: string
platformId: string
}
type McpToolCalled = {
mcpId: McpId
toolName: string
}
type PieceSelectorSearch = {
search: string
isTrigger: boolean
selectedActionOrTriggerName: string | null
}
export enum TelemetryEventName {
SIGNED_UP = 'signed.up',
QUOTA_ALERT = 'quota.alert',
REQUEST_TRIAL_CLICKED = 'request.trial.clicked',
REQUEST_TRIAL_SUBMITTED = 'request.trial.submitted',
KEY_ACTIVATED = 'key.activated',
FLOW_ISSUE_CLICKED = 'flow.issue.clicked',
FLOW_ISSUE_RESOLVED = 'flow.issue.resolved',
USER_INVITED = 'user.invited',
UPGRADE_POPUP = 'upgrade.popup',
CREATED_FLOW = 'flow.created',
DEMO_IMPORTED = 'demo.imported',
FLOW_RUN_CREATED = 'run.created',
FLOW_PUBLISHED = 'flow.published',
/**used with templates dialog + import flow component + flows imported by uri query param*/
FLOW_IMPORTED = 'flow.imported',
/**used only with import flow dialog*/
FLOW_IMPORTED_USING_FILE = 'flow.imported.using.file',
PIECES_SEARCH = 'pieces.search',
REFERRAL = 'referral',
REFERRAL_LINK_COPIED = 'referral.link.copied',
FLOW_SHARED = 'flow.shared',
TEMPLATE_SEARCH = 'template.search',
FORMS_VIEWED = 'forms.viewed',
FORMS_SUBMITTED = 'forms.submitted',
REWARDS_OPENED = 'rewards.opened',
REWARDS_INSTRUCTION_CLICKED = 'rewards.instructions.clicked',
TRIGGER_FAILURES_EXCEEDED = 'trigger.failures.exceeded',
AI_PROVIDER_USED = 'ai.provider.used',
AI_PROVIDER_CONFIGURED = 'ai.provider.configured',
MCP_TOOL_CALLED = 'mcp.tool.called',
UPGRADE_POPUP_OPENED = 'upgrade.popup.opened',
UPGRADE_CLICKED = 'upgrade.clicked',
OPENED_PRICING_FROM_DASHBOARD = 'opened.pricing.from.dashboard',
PIECE_SELECTOR_SEARCH = 'piece.selector.search',
}
type BaseTelemetryEvent<T, P> = {
name: T
payload: P
}
export type TelemetryEvent =
| BaseTelemetryEvent<TelemetryEventName.SIGNED_UP, SignedUp>
| BaseTelemetryEvent<TelemetryEventName.REFERRAL, Referral>
| BaseTelemetryEvent<
TelemetryEventName.REQUEST_TRIAL_CLICKED,
RequestTrialClicked
>
| BaseTelemetryEvent<TelemetryEventName.KEY_ACTIVATED, KeyActivated>
| BaseTelemetryEvent<
TelemetryEventName.REQUEST_TRIAL_SUBMITTED,
RequestTrialSubmitted
>
| BaseTelemetryEvent<TelemetryEventName.FLOW_ISSUE_CLICKED, FlowIssueClicked>
| BaseTelemetryEvent<
TelemetryEventName.FLOW_ISSUE_RESOLVED,
FlowIssueResolved
>
| BaseTelemetryEvent<TelemetryEventName.UPGRADE_CLICKED, UpgradeClicked>
| BaseTelemetryEvent<TelemetryEventName.UPGRADE_POPUP, UpgradePopup>
| BaseTelemetryEvent<TelemetryEventName.FLOW_RUN_CREATED, RunCreated>
| BaseTelemetryEvent<TelemetryEventName.FLOW_PUBLISHED, FlowPublished>
| BaseTelemetryEvent<TelemetryEventName.QUOTA_ALERT, QuotaAlert>
| BaseTelemetryEvent<TelemetryEventName.CREATED_FLOW, FlowCreated>
| BaseTelemetryEvent<TelemetryEventName.TEMPLATE_SEARCH, TemplateSearch>
| BaseTelemetryEvent<TelemetryEventName.PIECES_SEARCH, PiecesSearch>
| BaseTelemetryEvent<TelemetryEventName.FLOW_IMPORTED, FlowImported>
| BaseTelemetryEvent<
TelemetryEventName.FLOW_IMPORTED_USING_FILE,
FlowImportedUsingFile
>
| BaseTelemetryEvent<
TelemetryEventName.REFERRAL_LINK_COPIED,
ReferralLinkCopied
>
| BaseTelemetryEvent<TelemetryEventName.FLOW_SHARED, FlowShared>
| BaseTelemetryEvent<TelemetryEventName.DEMO_IMPORTED, Record<string, never>>
| BaseTelemetryEvent<
TelemetryEventName.OPENED_PRICING_FROM_DASHBOARD,
OpenedFromDashboard
>
| BaseTelemetryEvent<TelemetryEventName.FORMS_VIEWED, FormsViewed>
| BaseTelemetryEvent<TelemetryEventName.USER_INVITED, UserInvited>
| BaseTelemetryEvent<TelemetryEventName.FORMS_SUBMITTED, FormsViewed>
| BaseTelemetryEvent<TelemetryEventName.REWARDS_OPENED, RewardButtonClicked>
| BaseTelemetryEvent<
TelemetryEventName.REWARDS_INSTRUCTION_CLICKED,
RewardInstructionsClicked
>
| BaseTelemetryEvent<
TelemetryEventName.TRIGGER_FAILURES_EXCEEDED,
TriggerFailuresExceeded
>
| BaseTelemetryEvent<
TelemetryEventName.AI_PROVIDER_USED,
AiProviderConfiguredOrUsed
>
| BaseTelemetryEvent<
TelemetryEventName.AI_PROVIDER_CONFIGURED,
AiProviderConfiguredOrUsed
>
| BaseTelemetryEvent<TelemetryEventName.MCP_TOOL_CALLED, McpToolCalled>
| BaseTelemetryEvent<TelemetryEventName.PIECE_SELECTOR_SEARCH, PieceSelectorSearch>

View File

@@ -0,0 +1,37 @@
// Types for the result object with discriminated union
type Success<T> = {
data: T
error: null
}
type Failure<E> = {
data: null
error: E
}
export type Result<T, E = Error> = Success<T> | Failure<E>
// Main wrapper function
export async function tryCatch<T, E = Error>(
fn: () => Promise<T>,
): Promise<Result<T, E>> {
try {
const data = await fn()
return { data, error: null }
}
catch (error) {
return { data: null, error: error as E }
}
}
export function tryCatchSync<T, E = Error>(
fn: () => T,
): Result<T, E> {
try {
const data = fn()
return { data, error: null }
}
catch (error) {
return { data: null, error: error as E }
}
}

View File

@@ -0,0 +1,55 @@
export function assertEqual<T>(
actual: T,
expected: T,
fieldName1: string,
fieldName2: string,
): asserts actual is T {
if (actual !== expected) {
throw new Error(`${fieldName1} and ${fieldName2} should be equal`)
}
}
export function assertNotNullOrUndefined<T>(
value: T | null | undefined,
fieldName: string,
): asserts value is T {
if (value === null || value === undefined) {
throw new Error(`${fieldName} is null or undefined`)
}
}
export function assertNotEqual<T>(
value1: T,
value2: T,
fieldName1: string,
fieldName2: string,
): void {
if (value1 === value2) {
throw new Error(`${fieldName1} and ${fieldName2} should not be equal`)
}
}
export const isNotUndefined = <T>(value: T | undefined): value is T => {
return value !== undefined
}
export function assertNull<T>(
value: T | null,
fieldName: string,
): asserts value is T {
if (value !== null) {
throw new Error(`${fieldName} should be null`)
}
}
export function asserNotEmpty<T>(
value: T[] | null | undefined,
fieldName: string,
): asserts value is T[] {
assertNotNullOrUndefined(value, fieldName)
if (value.length === 0) {
throw new Error(`${fieldName} should be not empty`)
}
}

View File

@@ -0,0 +1,3 @@
export * from './object-utils'
export * from './utils'
export * from './assertions'

View File

@@ -0,0 +1,125 @@
import { isNil, isString } from './utils'
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function deleteProperties(obj: Record<string, unknown>, props: string[]) {
const copy = { ...obj }
for (const prop of props) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete copy[prop]
}
return copy
}
export function omit<T extends object, K extends keyof T>(obj: T, keysToOmit: K[]): Omit<T, K> {
return Object.fromEntries(
Object.entries(obj).filter(([key]) => !keysToOmit.includes(key as K)),
) as Omit<T, K>
}
export const spreadIfNotUndefined = <T>(key: string, value: T | undefined): Record<string, T> => {
if (value === undefined) {
return {}
}
return {
[key]: value,
}
}
export const spreadIfDefined = <T>(key: string, value: T | undefined | null): Record<string, T> => {
if (isNil(value)) {
return {}
}
return {
[key]: value,
}
}
export function deleteProps<T extends Record<string, unknown>, K extends keyof T>(
obj: T,
prop: K[],
): Omit<T, K> {
const newObj = { ...obj }
for (const p of prop) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete newObj[p]
}
return newObj
}
export function sanitizeObjectForPostgresql<T>(input: T): T {
return applyFunctionToValuesSync<T>(input, (str) => {
if (isString(str)) {
// eslint-disable-next-line no-control-regex
const controlCharsRegex = /\u0000/g
return str.replace(controlCharsRegex, '')
}
return str
})
}
export function applyFunctionToValuesSync<T>(obj: unknown, apply: (str: string) => unknown): T {
if (isNil(obj)) {
return obj as T
}
else if (isString(obj)) {
return apply(obj) as T
}
else if (Array.isArray(obj)) {
return obj.map(item => applyFunctionToValuesSync(item, apply)) as unknown as T
}
else if (isObject(obj)) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [key, applyFunctionToValuesSync(value, apply)]),
) as T
}
return obj as T
}
export async function applyFunctionToValues<T>(obj: unknown, apply: (str: string) => Promise<unknown>): Promise<T> {
if (isNil(obj)) {
return obj as T
}
else if (isString(obj)) {
return (await apply(obj)) as T
}
else if (Array.isArray(obj)) {
// Create a new array and map over it with Promise.all
const newArray = await Promise.all(obj.map(item => applyFunctionToValues(item, apply)))
return newArray as unknown as T
}
else if (isObject(obj)) {
// Use Object.fromEntries and map entries asynchronously
const newEntries = await Promise.all(
Object.entries(obj).map(async ([key, value]) => [key, await applyFunctionToValues(value, apply)]),
)
return Object.fromEntries(newEntries) as T
}
return obj as T
}
export const isObject = (obj: unknown): obj is Record<string, unknown> => {
return typeof obj === 'object' && obj !== null && !Array.isArray(obj)
}
export type MakeKeyNonNullableAndRequired<T extends object, K extends keyof T> = T & { [P in K]-?: NonNullable<T[P]> }
export function groupBy<T, K extends string | number | symbol>(
items: T[],
keySelector: (item: T) => K,
): Record<K, T[]> {
const result = {} as Record<K, T[]>
for (const item of items) {
const key = keySelector(item)
if (!result[key]) {
result[key] = []
}
result[key].push(item)
}
return result
}

View File

@@ -0,0 +1,147 @@
import { deepmerge } from 'deepmerge-ts'
export function isString(str: unknown): str is string {
return str != null && typeof str === 'string'
}
export function isNil<T>(value: T | null | undefined): value is null | undefined {
return value === null || value === undefined
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function setAtPath<T, K extends keyof any>(obj: T, path: K | K[], value: any): void {
const pathArray = Array.isArray(path) ? path : (path as string).match(/([^[.\]])+/g) as unknown as K[]
// eslint-disable-next-line @typescript-eslint/no-explicit-any
pathArray.reduce((acc: any, key: K, i: number) => {
if (acc[key] === undefined) acc[key] = {}
if (i === pathArray.length - 1) acc[key] = value
return acc[key]
}, obj)
}
export function insertAt<T>(array: T[], index: number, item: T): T[] {
return [...array.slice(0, index), item, ...array.slice(index)]
}
export function debounce<T>(func: (...args: T[]) => void, wait: number): (key?: string, ...args: T[]) => void {
let timeout: NodeJS.Timeout
let currentKey: string | undefined
return function (key?: string, ...args: T[]) {
const later = () => {
func(...args)
}
if (currentKey === key) {
clearTimeout(timeout)
}
currentKey = key
timeout = setTimeout(later, wait)
}
}
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends Record<string, unknown> ? DeepPartial<T[P]> : T[P];
}
/**
* This function also merges arrays, x = [1, 2], y = [3, 4], z = deepMergeAndCast(x, y) -> [1, 2, 3, 4]
**/
export function deepMergeAndCast<T>(target: DeepPartial<T>, source: DeepPartial<T>): T {
return deepmerge(target as Partial<T>, source as Partial<T>) as T
}
export function kebabCase(str: string): string {
return str
.replace(/([a-z])([A-Z])/g, '$1-$2') // Handle camelCase by adding hyphen between lowercase and uppercase letters
.replace(/\s+/g, '-') // Replace spaces with hyphens
.replace(/_/g, '-') // Replace underscores with hyphens
.toLowerCase() // Convert to lowercase
.replace(/^-+|-+$/g, '') // Remove leading and trailing hyphens
}
export function isEmpty<T>(value: T | null | undefined): boolean {
if (value == null) {
return true
}
if (typeof value === 'string' || Array.isArray(value)) {
return value.length === 0
}
if (typeof value === 'object') {
return Object.keys(value).length === 0
}
return false
}
export function startCase(str: string): string {
return str
.replace(/([a-z])([A-Z])/g, '$1 $2')
.replace(/[_-]+/g, ' ')
.replace(/\s+/g, ' ')
.replace(/^[a-z]/, match => match.toUpperCase())
.replace(/\b[a-z]/g, match => match.toUpperCase())
}
export function camelCase(str: string): string {
return str
.replace(/([-_][a-z])/g, group => group.toUpperCase()
.replace('-', '')
.replace('_', ''))
}
export function parseToJsonIfPossible(str: unknown): unknown {
try {
return JSON.parse(str as string)
}
catch (e) {
return str
}
}
export function pickBy<T extends Record<string, unknown>>(
object: T,
predicate: (value: T[keyof T], key: keyof T) => boolean,
): Partial<T> {
return Object.keys(object).reduce((result: Partial<T>, key: keyof T) => {
if (predicate(object[key], key)) {
result[key] = object[key]
}
return result
}, {})
}
export function chunk<T>(records: T[], size: number) {
const chunks: T[][] = []
for (let i = 0; i < records.length; i += size) {
chunks.push(records.slice(i, i + size))
}
return chunks
}
export function partition<T>(array: T[], predicate: (item: T, index: number, arr: T[]) => boolean): [T[], T[]] {
const truthy: T[] = []
const falsy: T[] = []
array.forEach((item, idx) => {
if (predicate(item, idx, array)) {
truthy.push(item)
}
else {
falsy.push(item)
}
})
return [truthy, falsy]
}
export function unique<T>(array: T[]): T[] {
return array.filter((item, index, self) => index === self.findIndex(other => JSON.stringify(other) === JSON.stringify(item)))
}

View File

@@ -0,0 +1,13 @@
import { FlowVersionState } from '../flows/flow-version'
export const DEFAULT_MCP_DATA = {
flowId: 'mcp-flow-id',
flowVersionId: 'mcp-flow-version-id',
flowVersionState: FlowVersionState.LOCKED,
flowRunId: 'mcp-flow-run-id',
triggerPieceName: 'mcp-trigger-piece-name',
}
export const ERROR_MESSAGES_TO_REDACT = [
'HttpClient#sendRequest',
]

View File

@@ -0,0 +1,255 @@
import { Static, Type } from '@sinclair/typebox'
import { ExecutionToolStatus } from '../agents'
import { AppConnectionValue } from '../app-connection/app-connection'
import { ExecutionState, ExecutionType, ResumePayload } from '../flow-run/execution/execution-output'
import { FlowRunId, RunEnvironment } from '../flow-run/flow-run'
import { FlowVersion } from '../flows/flow-version'
import { PiecePackage } from '../pieces'
import { PlatformId } from '../platform'
import { ProjectId } from '../project/project'
import { ScheduleOptions } from '../trigger'
export enum EngineOperationType {
EXTRACT_PIECE_METADATA = 'EXTRACT_PIECE_METADATA',
EXECUTE_FLOW = 'EXECUTE_FLOW',
EXECUTE_PROPERTY = 'EXECUTE_PROPERTY',
EXECUTE_TRIGGER_HOOK = 'EXECUTE_TRIGGER_HOOK',
EXECUTE_VALIDATE_AUTH = 'EXECUTE_VALIDATE_AUTH',
}
export enum TriggerHookType {
ON_ENABLE = 'ON_ENABLE',
ON_DISABLE = 'ON_DISABLE',
HANDSHAKE = 'HANDSHAKE',
RENEW = 'RENEW',
RUN = 'RUN',
TEST = 'TEST',
}
export type EngineOperation =
| ExecuteToolOperation
| ExecuteFlowOperation
| ExecutePropsOptions
| ExecuteTriggerOperation<TriggerHookType>
| ExecuteExtractPieceMetadataOperation
| ExecuteValidateAuthOperation
export const enum EngineSocketEvent {
ENGINE_RESPONSE = 'engine-response',
ENGINE_STDOUT = 'engine-stdout',
ENGINE_STDERR = 'engine-stderr',
ENGINE_READY = 'engine-ready',
ENGINE_OPERATION = 'engine-operation',
UPDATE_RUN_PROGRESS = 'update-run-progress',
SEND_FLOW_RESPONSE = 'send-flow-response',
UPDATE_STEP_PROGRESS = 'update-step-progress',
}
export const EngineStdout = Type.Object({
message: Type.String(),
})
export const EngineStderr = Type.Object({
message: Type.String(),
})
export type EngineStdout = Static<typeof EngineStdout>
export type EngineStderr = Static<typeof EngineStderr>
export type BaseEngineOperation = {
projectId: ProjectId
engineToken: string
internalApiUrl: string
publicApiUrl: string
timeoutInSeconds: number
platformId: PlatformId
}
export type ExecuteValidateAuthOperation = Omit<BaseEngineOperation, 'projectId'> & {
piece: PiecePackage
auth: AppConnectionValue
}
export type ExecuteExtractPieceMetadata = PiecePackage & { platformId: PlatformId }
export type ExecuteExtractPieceMetadataOperation = ExecuteExtractPieceMetadata & { timeoutInSeconds: number, platformId: PlatformId }
export type ExecuteToolOperation = BaseEngineOperation & {
actionName: string
pieceName: string
pieceVersion: string
predefinedInput: Record<string, unknown>
instruction: string
}
export type ExecutePropsOptions = BaseEngineOperation & {
piece: PiecePackage
propertyName: string
actionOrTriggerName: string
flowVersion?: FlowVersion
input: Record<string, unknown>
sampleData: Record<string, unknown>
searchValue?: string
}
type BaseExecuteFlowOperation<T extends ExecutionType> = BaseEngineOperation & {
flowVersion: FlowVersion
flowRunId: FlowRunId
executionType: T
runEnvironment: RunEnvironment
executionState: ExecutionState
serverHandlerId: string | null
httpRequestId: string | null
progressUpdateType: ProgressUpdateType
stepNameToTest: string | null
sampleData?: Record<string, unknown>
logsUploadUrl?: string
logsFileId?: string
}
export enum ProgressUpdateType {
WEBHOOK_RESPONSE = 'WEBHOOK_RESPONSE',
TEST_FLOW = 'TEST_FLOW',
NONE = 'NONE',
}
export type BeginExecuteFlowOperation = BaseExecuteFlowOperation<ExecutionType.BEGIN> & {
triggerPayload: unknown
executeTrigger: boolean
}
export type ResumeExecuteFlowOperation = BaseExecuteFlowOperation<ExecutionType.RESUME> & {
resumePayload: ResumePayload
}
export type ExecuteFlowOperation = BeginExecuteFlowOperation | ResumeExecuteFlowOperation
export type ExecuteTriggerOperation<HT extends TriggerHookType> = BaseEngineOperation & {
hookType: HT
test: boolean
flowVersion: FlowVersion
webhookUrl: string
triggerPayload?: TriggerPayload
appWebhookUrl?: string
webhookSecret?: string | Record<string, string>
}
export const TriggerPayload = Type.Object({
body: Type.Unknown(),
rawBody: Type.Optional(Type.Unknown()),
headers: Type.Record(Type.String(), Type.String()),
queryParams: Type.Record(Type.String(), Type.String()),
})
export type TriggerPayload<T = unknown> = {
body: T
rawBody?: unknown
headers: Record<string, string>
queryParams: Record<string, string>
}
export type EventPayload<B = unknown> = {
body: B
rawBody?: unknown
method: string
headers: Record<string, string>
queryParams: Record<string, string>
}
export type ParseEventResponse = {
event?: string
identifierValue?: string
reply?: {
headers: Record<string, string>
body: unknown
}
}
export type AppEventListener = {
events: string[]
identifierValue: string
}
type ExecuteTestOrRunTriggerResponse = {
success: boolean
message?: string
output: unknown[]
}
type ExecuteHandshakeTriggerResponse = {
success: boolean
message?: string
response?: {
status: number
body?: unknown
headers?: Record<string, string>
}
}
type ExecuteOnEnableTriggerResponse = {
listeners: AppEventListener[]
scheduleOptions?: ScheduleOptions
}
export const EngineHttpResponse = Type.Object({
status: Type.Number(),
body: Type.Unknown(),
headers: Type.Record(Type.String(), Type.String()),
})
export type EngineHttpResponse = Static<typeof EngineHttpResponse>
export type ExecuteTriggerResponse<H extends TriggerHookType> = H extends TriggerHookType.RUN ? ExecuteTestOrRunTriggerResponse :
H extends TriggerHookType.HANDSHAKE ? ExecuteHandshakeTriggerResponse :
H extends TriggerHookType.TEST ? ExecuteTestOrRunTriggerResponse :
H extends TriggerHookType.RENEW ? Record<string, never> :
H extends TriggerHookType.ON_DISABLE ? Record<string, never> :
ExecuteOnEnableTriggerResponse
export type ExecuteToolResponse = {
status: ExecutionToolStatus
output?: unknown
resolvedInput: Record<string, unknown>
errorMessage?: unknown
}
export type ExecuteActionResponse = {
success: boolean
input: unknown
output: unknown
message?: string
}
type BaseExecuteValidateAuthResponseOutput<Valid extends boolean> = {
valid: Valid
}
type ValidExecuteValidateAuthResponseOutput = BaseExecuteValidateAuthResponseOutput<true>
type InvalidExecuteValidateAuthResponseOutput = BaseExecuteValidateAuthResponseOutput<false> & {
error: string
}
export type ExecuteValidateAuthResponse =
| ValidExecuteValidateAuthResponseOutput
| InvalidExecuteValidateAuthResponseOutput
export type EngineResponse<T = unknown> = {
status: EngineResponseStatus
response: T
delayInSeconds?: number
error?: string
}
export enum EngineResponseStatus {
OK = 'OK',
INTERNAL_ERROR = 'INTERNAL_ERROR',
TIMEOUT = 'TIMEOUT',
MEMORY_ISSUE = 'MEMORY_ISSUE',
}

View File

@@ -0,0 +1,104 @@
import { STORE_KEY_MAX_LENGTH } from '../store-entry/store-entry'
export enum ExecutionErrorType {
ENGINE = 'ENGINE',
USER = 'USER',
}
export class ExecutionError extends Error {
public type: ExecutionErrorType
constructor(name: string, message: string, type: ExecutionErrorType, public cause?: unknown) {
super(message)
this.name = name
this.type = type
}
}
function formatMessage(message: string) {
return JSON.stringify({
message,
}, null, 2)
}
export class ConnectionNotFoundError extends ExecutionError {
constructor(connectionName: string, cause?: unknown) {
super('ConnectionNotFound', formatMessage(`connection (${connectionName}) not found`), ExecutionErrorType.USER, cause)
}
}
export class ConnectionLoadingError extends ExecutionError {
constructor(connectionName: string, cause?: unknown) {
super('ConnectionLoadingFailure', formatMessage(`Failed to load connection (${connectionName})`), ExecutionErrorType.ENGINE, cause)
}
}
export class ConnectionExpiredError extends ExecutionError {
constructor(connectionName: string, cause?: unknown) {
super('ConnectionExpired', formatMessage(`connection (${connectionName}) expired, reconnect again`), ExecutionErrorType.USER, cause)
}
}
export class StorageLimitError extends ExecutionError {
public maxStorageSizeInBytes: number
constructor(key: string, maxStorageSizeInBytes: number, cause?: unknown) {
super('StorageLimitError', formatMessage(`Failed to read/write key "${key}", the value you are trying to read/write is larger than ${Math.floor(maxStorageSizeInBytes / 1024)} KB`), ExecutionErrorType.USER, cause)
this.maxStorageSizeInBytes = maxStorageSizeInBytes
}
}
export class StorageInvalidKeyError extends ExecutionError {
constructor(key: string, cause?: unknown) {
super('StorageInvalidKeyError', formatMessage(`Failed to read/write key "${key}", the key is empty or longer than ${STORE_KEY_MAX_LENGTH} characters`), ExecutionErrorType.USER, cause)
}
}
export class StorageError extends ExecutionError {
constructor(key: string, cause?: unknown) {
super('StorageError', formatMessage(`Failed to read/write key "${key}" due to ${JSON.stringify(cause)}`), ExecutionErrorType.ENGINE, cause)
}
}
export class FileStoreError extends ExecutionError {
constructor(cause?: unknown) {
super('FileStoreError', formatMessage(`Failed to store file due to ${JSON.stringify(cause)}`), ExecutionErrorType.ENGINE, cause)
}
}
export class PausedFlowTimeoutError extends ExecutionError {
constructor(cause?: unknown, maximumPauseDurationDays?: number) {
super('PausedFlowTimeoutError', `The flow cannot be paused for more than ${maximumPauseDurationDays} days`, ExecutionErrorType.USER, cause)
}
}
export class FileSizeError extends ExecutionError {
constructor(currentFileSize: number, maximumSupportSize: number, cause?: unknown) {
super('FileSizeError', JSON.stringify({
message: 'File size is larger than maximum supported size',
currentFileSize: `${currentFileSize} MB`,
maximumSupportSize: `${maximumSupportSize} MB`,
}), ExecutionErrorType.USER, cause)
}
}
export class FetchError extends ExecutionError {
constructor(url: string, cause?: unknown) {
super('FetchError', formatMessage(`Failed to fetch from ${url}`), ExecutionErrorType.ENGINE, cause)
}
}
export class InvalidCronExpressionError extends ExecutionError {
constructor(cronExpression: string, cause?: unknown) {
super('InvalidCronExpressionError', formatMessage(`Invalid cron expression: ${cronExpression}`), ExecutionErrorType.USER, cause)
}
}
export class EngineGenericError extends ExecutionError {
constructor(name: string, message: string, cause?: unknown) {
super(name, formatMessage(message), ExecutionErrorType.ENGINE, cause)
}
}

View File

@@ -0,0 +1,11 @@
export * from './engine-operation'
export * from './requests'
export * from './engine-constants'
export * from './execution-errors'
export enum ExecutionMode {
SANDBOX_PROCESS = 'SANDBOX_PROCESS',
SANDBOX_CODE_ONLY = 'SANDBOX_CODE_ONLY',
UNSANDBOXED = 'UNSANDBOXED',
SANDBOX_CODE_AND_PROCESS = 'SANDBOX_CODE_AND_PROCESS',
}

View File

@@ -0,0 +1,70 @@
import { Static, Type } from '@sinclair/typebox'
import { Nullable } from '../common'
import { FlowRunStatus, PauseMetadata } from '../flow-run/execution/flow-execution'
import { FailedStep } from '../flow-run/flow-run'
import { StepRunResponse } from '../flows/sample-data'
import { ProgressUpdateType } from './engine-operation'
export const UpdateRunProgressRequest = Type.Object({
runId: Type.String(),
tags: Type.Optional(Type.Array(Type.String())),
status: Type.Enum(FlowRunStatus),
projectId: Type.String(),
progressUpdateType: Type.Optional(Type.Enum(ProgressUpdateType)),
workerHandlerId: Nullable(Type.String()),
httpRequestId: Nullable(Type.String()),
logsFileId: Type.Optional(Type.String()),
stepNameToTest: Type.Optional(Type.String()),
failedStep: Type.Optional(FailedStep),
startTime: Type.Optional(Type.String()),
finishTime: Type.Optional(Type.String()),
stepResponse: Type.Optional(StepRunResponse),
pauseMetadata: Type.Optional(PauseMetadata),
stepsCount: Type.Optional(Type.Number()),
})
export type UpdateRunProgressRequest = Static<typeof UpdateRunProgressRequest>
export const UpdateStepProgressRequest = Type.Object({
projectId: Type.String(),
stepResponse: StepRunResponse,
})
export type UpdateStepProgressRequest = Static<typeof UpdateStepProgressRequest>
export const UploadLogsQueryParams = Type.Object({
token: Type.String(),
})
export type UploadLogsQueryParams = Static<typeof UploadLogsQueryParams>
export enum UploadLogsBehavior {
UPLOAD_DIRECTLY = 'UPLOAD_DIRECTLY',
REDIRECT_TO_S3 = 'REDIRECT_TO_S3',
}
export const UploadLogsToken = Type.Object({
logsFileId: Type.String(),
projectId: Type.String(),
flowRunId: Type.String(),
behavior: Type.Enum(UploadLogsBehavior),
})
export type UploadLogsToken = Static<typeof UploadLogsToken>
export const SendFlowResponseRequest = Type.Object({
workerHandlerId: Type.String(),
httpRequestId: Type.String(),
runResponse: Type.Object({
status: Type.Number(),
body: Type.Any(),
headers: Type.Record(Type.String(), Type.String()),
}),
})
export type SendFlowResponseRequest = Static<typeof SendFlowResponseRequest>
export const GetFlowVersionForWorkerRequest = Type.Object({
versionId: Type.String(),
})
export type GetFlowVersionForWorkerRequest = Static<typeof GetFlowVersionForWorkerRequest>

View File

@@ -0,0 +1,8 @@
export enum ThirdPartyAuthnProviderEnum {
GOOGLE = 'google',
SAML = 'saml',
}
export type ThirdPartyAuthnProvidersToShowMap = {
[k in ThirdPartyAuthnProviderEnum]: boolean;
}

View File

@@ -0,0 +1,51 @@
import { Static, Type } from '@sinclair/typebox'
import { Nullable } from '../common'
import { ThirdPartyAuthnProviderEnum } from './authn-provider-name'
export * from './authn-provider-name'
export const federatedAuthnLoginResponse = Type.Object({
loginUrl: Type.String(),
})
export type FederatedAuthnLoginResponse = Static<typeof federatedAuthnLoginResponse>
export const ClaimTokenRequest = Type.Object({
providerName: Type.Enum(ThirdPartyAuthnProviderEnum),
code: Type.String(),
})
export type ClaimTokenRequest = Static<typeof ClaimTokenRequest>
export const GoogleAuthnProviderConfig = Type.Object({
clientId: Type.String(),
clientSecret: Type.String(),
})
export type GoogleAuthnProviderConfig = Static<typeof GoogleAuthnProviderConfig>
export const GithubAuthnProviderConfig = Type.Object({
clientId: Type.String(),
clientSecret: Type.String(),
})
export type GithubAuthnProviderConfig = Static<typeof GithubAuthnProviderConfig>
export const SAMLAuthnProviderConfig = Type.Object({
idpMetadata: Type.String(),
idpCertificate: Type.String(),
})
export type SAMLAuthnProviderConfig = Static<typeof SAMLAuthnProviderConfig>
export const FederatedAuthnProviderConfig = Type.Object({
google: Nullable(GoogleAuthnProviderConfig),
github: Nullable(GithubAuthnProviderConfig),
saml: Nullable(SAMLAuthnProviderConfig),
})
export type FederatedAuthnProviderConfig = Static<typeof FederatedAuthnProviderConfig>
export const FederatedAuthnProviderConfigWithoutSensitiveData = Type.Object({
google: Nullable(Type.Pick(GoogleAuthnProviderConfig, ['clientId'])),
github: Nullable(Type.Pick(GithubAuthnProviderConfig, ['clientId'])),
saml: Nullable(Type.Object({})),
})
export type FederatedAuthnProviderConfigWithoutSensitiveData = Static<typeof FederatedAuthnProviderConfigWithoutSensitiveData>

View File

@@ -0,0 +1 @@
export const feedbackUrl = 'https://feedback.activepieces.com'

View File

@@ -0,0 +1,50 @@
import { Static, Type } from '@sinclair/typebox'
import { BaseModelSchema } from '../common/base-model'
import { ApId } from '../common/id-generator'
export type FileId = ApId
export enum FileType {
UNKNOWN = 'UNKNOWN',
FLOW_RUN_LOG = 'FLOW_RUN_LOG',
PACKAGE_ARCHIVE = 'PACKAGE_ARCHIVE',
FLOW_STEP_FILE = 'FLOW_STEP_FILE',
SAMPLE_DATA = 'SAMPLE_DATA',
/*
@deprecated activepieces no longer stores trigger payload
*/
TRIGGER_PAYLOAD = 'TRIGGER_PAYLOAD',
SAMPLE_DATA_INPUT = 'SAMPLE_DATA_INPUT',
TRIGGER_EVENT_FILE = 'TRIGGER_EVENT_FILE',
PROJECT_RELEASE = 'PROJECT_RELEASE',
FLOW_VERSION_BACKUP = 'FLOW_VERSION_BACKUP',
PLATFORM_ASSET = 'PLATFORM_ASSET',
}
export enum FileCompression {
NONE = 'NONE',
GZIP = 'GZIP',
}
export enum FileLocation {
S3 = 'S3',
DB = 'DB',
}
export const File = Type.Object({
...BaseModelSchema,
projectId: Type.Optional(Type.String()),
platformId: Type.Optional(Type.String()),
type: Type.Enum(FileType),
compression: Type.Enum(FileCompression),
data: Type.Optional(Type.Unknown()),
location: Type.Enum(FileLocation),
size: Type.Optional(Type.Number()),
fileName: Type.Optional(Type.String()),
s3Key: Type.Optional(Type.String()),
metadata: Type.Optional(Type.Record(Type.String(), Type.String())),
})
export type File = Static<typeof File> & {
data: Buffer
}

View File

@@ -0,0 +1,7 @@
import { Static, Type } from '@sinclair/typebox'
import { TemplateCategory } from '../template/template'
export const UpdateTemplatesCategoriesFlagRequestBody = Type.Object({
value: Type.Array(Type.Enum(TemplateCategory)),
})
export type UpdateTemplatesCategoriesFlagRequestBody = Static<typeof UpdateTemplatesCategoriesFlagRequestBody>

View File

@@ -0,0 +1,62 @@
import { BaseModel } from '../common/base-model'
import { ApId } from '../common/id-generator'
export type FlagId = ApId
export type Flag = {
value: unknown
} & BaseModel<FlagId>
export enum ApEnvironment {
PRODUCTION = 'prod',
DEVELOPMENT = 'dev',
TESTING = 'test',
}
export enum ApEdition {
COMMUNITY = 'ce',
ENTERPRISE = 'ee',
CLOUD = 'cloud',
}
export enum ApFlagId {
SHOW_POWERED_BY_IN_FORM = 'SHOW_POWERED_BY_IN_FORM',
CLOUD_AUTH_ENABLED = 'CLOUD_AUTH_ENABLED',
CAN_CONFIGURE_AI_PROVIDER = 'CAN_CONFIGURE_AI_PROVIDER',
AGENTS_CONFIGURED = 'AGENTS_CONFIGURED',
CURRENT_VERSION = 'CURRENT_VERSION',
EDITION = 'EDITION',
EMAIL_AUTH_ENABLED = 'EMAIL_AUTH_ENABLED',
EXECUTION_DATA_RETENTION_DAYS = 'EXECUTION_DATA_RETENTION_DAYS',
ENVIRONMENT = 'ENVIRONMENT',
PUBLIC_URL = 'PUBLIC_URL',
LATEST_VERSION = 'LATEST_VERSION',
PRIVACY_POLICY_URL = 'PRIVACY_POLICY_URL',
PIECES_SYNC_MODE = 'PIECES_SYNC_MODE',
PRIVATE_PIECES_ENABLED = 'PRIVATE_PIECES_ENABLED',
FLOW_RUN_MEMORY_LIMIT_KB = 'FLOW_RUN_MEMORY_LIMIT_KB',
FLOW_RUN_TIME_SECONDS = 'FLOW_RUN_TIME_SECONDS',
SHOW_BILLING = 'SHOW_BILLING',
SHOW_COMMUNITY = 'SHOW_COMMUNITY',
SUPPORTED_APP_WEBHOOKS = 'SUPPORTED_APP_WEBHOOKS',
TELEMETRY_ENABLED = 'TELEMETRY_ENABLED',
TEMPLATES_PROJECT_ID = 'TEMPLATES_PROJECT_ID',
TERMS_OF_SERVICE_URL = 'TERMS_OF_SERVICE_URL',
THEME = 'THEME',
THIRD_PARTY_AUTH_PROVIDER_REDIRECT_URL = 'THIRD_PARTY_AUTH_PROVIDER_REDIRECT_URL',
THIRD_PARTY_AUTH_PROVIDERS_TO_SHOW_MAP = 'THIRD_PARTY_AUTH_PROVIDERS_TO_SHOW_MAP',
SAML_AUTH_ACS_URL = 'SAML_AUTH_ACS_URL',
USER_CREATED = 'USER_CREATED',
WEBHOOK_URL_PREFIX = 'WEBHOOK_URL_PREFIX',
ALLOW_NPM_PACKAGES_IN_CODE_STEP = 'ALLOW_NPM_PACKAGES_IN_CODE_STEP',
PAUSED_FLOW_TIMEOUT_DAYS = 'PAUSED_FLOW_TIMEOUT_DAYS',
WEBHOOK_TIMEOUT_SECONDS = 'WEBHOOK_TIMEOUT_SECONDS',
MAX_RECORDS_PER_TABLE = 'MAX_RECORDS_PER_TABLE',
MAX_FIELDS_PER_TABLE = 'MAX_FIELDS_PER_TABLE',
MAX_FILE_SIZE_MB = 'MAX_FILE_SIZE_MB',
MAX_MCPS_PER_PROJECT = 'MAX_MCPS_PER_PROJECT',
ENABLE_FLOW_ON_PUBLISH = 'ENABLE_FLOW_ON_PUBLISH',
SHOW_ALERTS = 'SHOW_ALERTS',
SHOW_PROJECT_MEMBERS = 'SHOW_PROJECT_MEMBERS',
TEMPLATES_CATEGORIES = 'TEMPLATES_CATEGORIES',
}

View File

@@ -0,0 +1,2 @@
export * from './flag'
export * from './flag.requests'

View File

@@ -0,0 +1,19 @@
import { Static, Type } from '@sinclair/typebox'
import { ApId } from '../../common/id-generator'
import { FlowRunStatus } from '../execution/flow-execution'
export const ListFlowRunsRequestQuery = Type.Object({
flowId: Type.Optional(Type.Array(ApId)),
tags: Type.Optional(Type.Array(Type.String({}))),
status: Type.Optional(Type.Array(Type.Enum(FlowRunStatus))),
limit: Type.Optional(Type.Number({})),
cursor: Type.Optional(Type.String({})),
createdAfter: Type.Optional(Type.String({})),
createdBefore: Type.Optional(Type.String({})),
projectId: ApId,
failedStepName: Type.Optional(Type.String({})),
flowRunIds: Type.Optional(Type.Array(ApId)),
archived: Type.Optional(Type.Boolean({})),
})
export type ListFlowRunsRequestQuery = Static<typeof ListFlowRunsRequestQuery>

View File

@@ -0,0 +1,22 @@
import { Type } from '@sinclair/typebox'
import { TriggerPayload } from '../../engine'
import { StepOutput } from './step-output'
export enum ExecutionType {
BEGIN = 'BEGIN',
RESUME = 'RESUME',
}
export type ExecutionState = {
steps: Record<string, StepOutput>
}
export const ExecutionState = Type.Object({
steps: Type.Record(Type.String(), Type.Unknown()),
})
export type ExecutioOutputFile = {
executionState: ExecutionState
}
export type ResumePayload = TriggerPayload

View File

@@ -0,0 +1,91 @@
import { Static, Type } from '@sinclair/typebox'
import { ProgressUpdateType } from '../../engine'
export enum FlowRunStatus {
FAILED = 'FAILED',
QUOTA_EXCEEDED = 'QUOTA_EXCEEDED',
INTERNAL_ERROR = 'INTERNAL_ERROR',
PAUSED = 'PAUSED',
QUEUED = 'QUEUED',
RUNNING = 'RUNNING',
SUCCEEDED = 'SUCCEEDED',
MEMORY_LIMIT_EXCEEDED = 'MEMORY_LIMIT_EXCEEDED',
TIMEOUT = 'TIMEOUT',
CANCELED = 'CANCELED',
}
export enum PauseType {
DELAY = 'DELAY',
WEBHOOK = 'WEBHOOK',
}
export const DelayPauseMetadata = Type.Object({
type: Type.Literal(PauseType.DELAY),
resumeDateTime: Type.String(),
requestIdToReply: Type.Optional(Type.String()),
handlerId: Type.Optional(Type.String({})),
progressUpdateType: Type.Optional(Type.Enum(ProgressUpdateType)),
})
export type DelayPauseMetadata = Static<typeof DelayPauseMetadata>
export const RespondResponse = Type.Object({
status: Type.Optional(Type.Number()),
body: Type.Optional(Type.Unknown()),
headers: Type.Optional(Type.Record(Type.String(), Type.String())),
})
export type RespondResponse = Static<typeof RespondResponse>
export const StopResponse = Type.Object({
status: Type.Optional(Type.Number()),
body: Type.Optional(Type.Unknown()),
headers: Type.Optional(Type.Record(Type.String(), Type.String())),
})
export type StopResponse = Static<typeof StopResponse>
export const WebhookPauseMetadata = Type.Object({
type: Type.Literal(PauseType.WEBHOOK),
requestId: Type.String(),
requestIdToReply: Type.Optional(Type.String()),
response: RespondResponse,
handlerId: Type.Optional(Type.String({})),
progressUpdateType: Type.Optional(Type.Enum(ProgressUpdateType)),
})
export type WebhookPauseMetadata = Static<typeof WebhookPauseMetadata>
export const PauseMetadata = Type.Union([DelayPauseMetadata, WebhookPauseMetadata])
export type PauseMetadata = Static<typeof PauseMetadata>
export const isFlowRunStateTerminal = ({ status, ignoreInternalError }: { status: FlowRunStatus, ignoreInternalError: boolean }): boolean => {
switch (status) {
case FlowRunStatus.SUCCEEDED:
case FlowRunStatus.TIMEOUT:
case FlowRunStatus.FAILED:
case FlowRunStatus.QUOTA_EXCEEDED:
case FlowRunStatus.MEMORY_LIMIT_EXCEEDED:
case FlowRunStatus.CANCELED:
return true
case FlowRunStatus.INTERNAL_ERROR:
return !ignoreInternalError
case FlowRunStatus.QUEUED:
case FlowRunStatus.RUNNING:
return false
case FlowRunStatus.PAUSED:
return false
}
}
export const FAILED_STATES = [
FlowRunStatus.FAILED,
FlowRunStatus.INTERNAL_ERROR,
FlowRunStatus.QUOTA_EXCEEDED,
FlowRunStatus.TIMEOUT,
FlowRunStatus.MEMORY_LIMIT_EXCEEDED,
]
export const isFailedState = (status: FlowRunStatus): boolean => {
return FAILED_STATES.includes(status)
}

View File

@@ -0,0 +1,189 @@
import { isNil } from '../../common'
import { FlowActionType } from '../../flows/actions/action'
import { FlowTriggerType } from '../../flows/triggers/trigger'
export enum StepOutputStatus {
FAILED = 'FAILED',
PAUSED = 'PAUSED',
RUNNING = 'RUNNING',
STOPPED = 'STOPPED',
SUCCEEDED = 'SUCCEEDED',
}
type BaseStepOutputParams<T extends FlowActionType | FlowTriggerType, OUTPUT> = {
type: T
status: StepOutputStatus
input: unknown
output?: OUTPUT
duration?: number
errorMessage?: unknown
}
export class GenericStepOutput<T extends FlowActionType | FlowTriggerType, OUTPUT> {
type: T
status: StepOutputStatus
input: unknown
output?: OUTPUT
duration?: number
errorMessage?: unknown
constructor(step: BaseStepOutputParams<T, OUTPUT>) {
this.type = step.type
this.status = step.status
this.input = step.input
this.output = step.output
this.duration = step.duration
this.errorMessage = step.errorMessage
}
setOutput(output: OUTPUT): GenericStepOutput<T, OUTPUT> {
return new GenericStepOutput<T, OUTPUT>({
...this,
output,
})
}
setStatus(status: StepOutputStatus): GenericStepOutput<T, OUTPUT> {
return new GenericStepOutput<T, OUTPUT>({
...this,
status,
})
}
setErrorMessage(errorMessage: unknown): GenericStepOutput<T, OUTPUT> {
return new GenericStepOutput<T, OUTPUT>({
...this,
errorMessage,
})
}
setDuration(duration: number): GenericStepOutput<T, OUTPUT> {
return new GenericStepOutput<T, OUTPUT>({
...this,
duration,
})
}
static create<T extends FlowActionType | FlowTriggerType, OUTPUT>({
input,
type,
status,
output,
}: {
input: unknown
type: T
status: StepOutputStatus
output?: OUTPUT
}): GenericStepOutput<T, OUTPUT> {
return new GenericStepOutput<T, OUTPUT>({
input,
type,
status,
output,
})
}
}
export type StepOutput =
| GenericStepOutput<FlowActionType.LOOP_ON_ITEMS, LoopStepResult>
| GenericStepOutput<FlowActionType.ROUTER, unknown>
| GenericStepOutput<
| Exclude<FlowActionType, FlowActionType.LOOP_ON_ITEMS | FlowActionType.ROUTER>
| FlowTriggerType,
unknown
>
type BranchResult = {
branchName: string
branchIndex: number
evaluation: boolean
}
type RouterStepResult = {
branches: BranchResult[]
}
export class RouterStepOutput extends GenericStepOutput<
FlowActionType.ROUTER,
RouterStepResult
> {
static init({ input }: { input: unknown }): RouterStepOutput {
return new RouterStepOutput({
type: FlowActionType.ROUTER,
input,
status: StepOutputStatus.SUCCEEDED,
})
}
}
export type LoopStepResult = {
item: unknown
index: number
iterations: Record<string, StepOutput>[]
}
export class LoopStepOutput extends GenericStepOutput<
FlowActionType.LOOP_ON_ITEMS,
LoopStepResult
> {
constructor(
step: BaseStepOutputParams<FlowActionType.LOOP_ON_ITEMS, LoopStepResult>,
) {
super(step)
this.output = step.output ?? {
item: undefined,
index: 0,
iterations: [],
}
}
static init({ input }: { input: unknown }): LoopStepOutput {
return new LoopStepOutput({
type: FlowActionType.LOOP_ON_ITEMS,
input,
status: StepOutputStatus.SUCCEEDED,
})
}
setIterations(iterations: Record<string, StepOutput>[]): LoopStepOutput {
return new LoopStepOutput({
...this,
output: {
...this.output,
iterations,
},
})
}
hasIteration(iteration: number): boolean {
return !isNil(this.output?.iterations[iteration])
}
setItemAndIndex({
item,
index,
}: {
item: unknown
index: number
}): LoopStepOutput {
return new LoopStepOutput({
...this,
output: {
item,
index,
iterations: this.output?.iterations ?? [],
},
})
}
addIteration(): LoopStepOutput {
return new LoopStepOutput({
...this,
output: {
item: this.output?.item,
index: this.output?.index,
iterations: [...(this.output?.iterations ?? []), {}],
},
})
}
}

View File

@@ -0,0 +1,62 @@
import { Static, Type } from '@sinclair/typebox'
import { BaseModelSchema, Nullable } from '../common/base-model'
import { ApId } from '../common/id-generator'
import { ExecutionState } from './execution/execution-output'
import { FlowRunStatus, PauseMetadata } from './execution/flow-execution'
export const PARENT_RUN_ID_HEADER = 'ap-parent-run-id'
export const FAIL_PARENT_ON_FAILURE_HEADER = 'ap-fail-parent-on-failure'
export type FlowRunId = ApId
export enum RunEnvironment {
PRODUCTION = 'PRODUCTION',
TESTING = 'TESTING',
}
export enum FlowRetryStrategy {
ON_LATEST_VERSION = 'ON_LATEST_VERSION',
FROM_FAILED_STEP = 'FROM_FAILED_STEP',
}
export type FlowRetryPayload = {
strategy: FlowRetryStrategy
}
export const FlowRun = Type.Object({
...BaseModelSchema,
projectId: Type.String(),
flowId: Type.String(),
parentRunId: Type.Optional(Type.String()),
failParentOnFailure: Type.Boolean(),
tags: Type.Optional(Type.Array(Type.String())),
flowVersionId: Type.String(),
flowVersion: Type.Optional(Type.Object({
displayName: Type.Optional(Type.String()),
})),
logsFileId: Nullable(Type.String()),
status: Type.Enum(FlowRunStatus),
startTime: Type.Optional(Type.String()),
finishTime: Type.Optional(Type.String()),
environment: Type.Enum(RunEnvironment),
pauseMetadata: Type.Optional(PauseMetadata),
// The steps data may be missing if the flow has not started yet,
// or if the run is older than AP_EXECUTION_DATA_RETENTION_DAYS and its execution data has been purged.
steps: Nullable(Type.Record(Type.String(), Type.Unknown())),
failedStep: Type.Optional(Type.Object({
name: Type.String(),
displayName: Type.String(),
})),
stepNameToTest: Type.Optional(Type.String()),
archivedAt: Nullable(Type.String({ default: null })),
stepsCount: Type.Optional(Type.Number()),
})
export const FailedStep = Type.Object({
name: Type.String(),
displayName: Type.String(),
message: Type.String(),
})
export type FailedStep = Static<typeof FailedStep>
export type FlowRun = Static<typeof FlowRun> & ExecutionState

View File

@@ -0,0 +1,7 @@
import { ExecutioOutputFile } from './execution/execution-output'
export const logSerializer = {
async serialize(log: ExecutioOutputFile): Promise<Buffer> {
return Buffer.from(JSON.stringify(log, null))
},
}

View File

@@ -0,0 +1,60 @@
import { Static, Type } from '@sinclair/typebox'
import { ApId } from '../common/id-generator'
import { FlowRunStatus } from './execution/flow-execution'
import { FlowRetryStrategy } from './flow-run'
export const TestFlowRunRequestBody = Type.Object({
flowVersionId: ApId,
})
export type TestFlowRunRequestBody = Static<typeof TestFlowRunRequestBody>
export const RetryFlowRequestBody = Type.Object({
strategy: Type.Enum(FlowRetryStrategy),
projectId: ApId,
})
export type RetryFlowRequestBody = Static<typeof RetryFlowRequestBody>
export const BulkActionOnRunsRequestBody = Type.Object({
projectId: ApId,
flowRunIds: Type.Optional(Type.Array(ApId)),
excludeFlowRunIds: Type.Optional(Type.Array(ApId)),
strategy: Type.Enum(FlowRetryStrategy),
status: Type.Optional(Type.Array(Type.Enum(FlowRunStatus))),
flowId: Type.Optional(Type.Array(ApId)),
createdAfter: Type.Optional(Type.String()),
createdBefore: Type.Optional(Type.String()),
failedStepName: Type.Optional(Type.String()),
})
export type BulkActionOnRunsRequestBody = Static<typeof BulkActionOnRunsRequestBody>
export const BulkCancelFlowRequestBody = Type.Object({
projectId: ApId,
flowRunIds: Type.Optional(Type.Array(ApId)),
excludeFlowRunIds: Type.Optional(Type.Array(ApId)),
status: Type.Optional(Type.Array(Type.Union([
Type.Literal(FlowRunStatus.PAUSED),
Type.Literal(FlowRunStatus.QUEUED),
]))),
flowId: Type.Optional(Type.Array(ApId)),
createdAfter: Type.Optional(Type.String()),
createdBefore: Type.Optional(Type.String()),
})
export type BulkCancelFlowRequestBody = Static<typeof BulkCancelFlowRequestBody>
export const BulkArchiveActionOnRunsRequestBody = Type.Object({
projectId: ApId,
flowRunIds: Type.Optional(Type.Array(ApId)),
excludeFlowRunIds: Type.Optional(Type.Array(ApId)),
status: Type.Optional(Type.Array(Type.Enum(FlowRunStatus))),
flowId: Type.Optional(Type.Array(ApId)),
createdAfter: Type.Optional(Type.String()),
createdBefore: Type.Optional(Type.String()),
failedStepName: Type.Optional(Type.String()),
})
export type BulkArchiveActionOnRunsRequestBody = Static<typeof BulkArchiveActionOnRunsRequestBody>

View File

@@ -0,0 +1,350 @@
import { Static, Type } from '@sinclair/typebox'
import { VersionType } from '../../pieces'
import { PropertySettings } from '../properties'
import { SampleDataSetting } from '../sample-data'
export enum FlowActionType {
CODE = 'CODE',
PIECE = 'PIECE',
LOOP_ON_ITEMS = 'LOOP_ON_ITEMS',
ROUTER = 'ROUTER',
}
export enum RouterExecutionType {
EXECUTE_ALL_MATCH = 'EXECUTE_ALL_MATCH',
EXECUTE_FIRST_MATCH = 'EXECUTE_FIRST_MATCH',
}
export enum BranchExecutionType {
FALLBACK = 'FALLBACK',
CONDITION = 'CONDITION',
}
const commonActionProps = {
name: Type.String({}),
valid: Type.Boolean({}),
displayName: Type.String({}),
skip: Type.Optional(Type.Boolean({})),
}
const commonActionSettings = {
sampleData: Type.Optional(SampleDataSetting),
customLogoUrl: Type.Optional(Type.String()),
}
export const ActionErrorHandlingOptions = Type.Optional(
Type.Object({
continueOnFailure: Type.Optional(
Type.Object({
value: Type.Optional(Type.Boolean()),
}),
),
retryOnFailure: Type.Optional(
Type.Object({
value: Type.Optional(Type.Boolean()),
}),
),
}),
)
export type ActionErrorHandlingOptions = Static<
typeof ActionErrorHandlingOptions
>
export const SourceCode = Type.Object({
packageJson: Type.String({}),
code: Type.String({}),
})
export type SourceCode = Static<typeof SourceCode>
export const CodeActionSettings = Type.Object({
...commonActionSettings,
sourceCode: SourceCode,
input: Type.Record(Type.String({}), Type.Any()),
errorHandlingOptions: ActionErrorHandlingOptions,
})
export type CodeActionSettings = Static<typeof CodeActionSettings>
export const CodeActionSchema = Type.Object({
...commonActionProps,
type: Type.Literal(FlowActionType.CODE),
settings: CodeActionSettings,
})
export const PieceActionSettings = Type.Object({
...commonActionSettings,
propertySettings: Type.Record(Type.String(), PropertySettings),
pieceName: Type.String({}),
pieceVersion: VersionType,
actionName: Type.Optional(Type.String({})),
input: Type.Record(Type.String({}), Type.Unknown()),
errorHandlingOptions: ActionErrorHandlingOptions,
})
export type PieceActionSettings = Static<typeof PieceActionSettings>
export const PieceActionSchema = Type.Object({
...commonActionProps,
type: Type.Literal(FlowActionType.PIECE),
settings: PieceActionSettings,
})
// Loop Items
export const LoopOnItemsActionSettings = Type.Object({
...commonActionSettings,
items: Type.String(),
})
export type LoopOnItemsActionSettings = Static<
typeof LoopOnItemsActionSettings
>
export const LoopOnItemsActionSchema = Type.Object({
...commonActionProps,
type: Type.Literal(FlowActionType.LOOP_ON_ITEMS),
settings: LoopOnItemsActionSettings,
})
export enum BranchOperator {
TEXT_CONTAINS = 'TEXT_CONTAINS',
TEXT_DOES_NOT_CONTAIN = 'TEXT_DOES_NOT_CONTAIN',
TEXT_EXACTLY_MATCHES = 'TEXT_EXACTLY_MATCHES',
TEXT_DOES_NOT_EXACTLY_MATCH = 'TEXT_DOES_NOT_EXACTLY_MATCH',
TEXT_STARTS_WITH = 'TEXT_START_WITH',
TEXT_DOES_NOT_START_WITH = 'TEXT_DOES_NOT_START_WITH',
TEXT_ENDS_WITH = 'TEXT_ENDS_WITH',
TEXT_DOES_NOT_END_WITH = 'TEXT_DOES_NOT_END_WITH',
NUMBER_IS_GREATER_THAN = 'NUMBER_IS_GREATER_THAN',
NUMBER_IS_LESS_THAN = 'NUMBER_IS_LESS_THAN',
NUMBER_IS_EQUAL_TO = 'NUMBER_IS_EQUAL_TO',
BOOLEAN_IS_TRUE = 'BOOLEAN_IS_TRUE',
BOOLEAN_IS_FALSE = 'BOOLEAN_IS_FALSE',
DATE_IS_BEFORE = 'DATE_IS_BEFORE',
DATE_IS_EQUAL = 'DATE_IS_EQUAL',
DATE_IS_AFTER = 'DATE_IS_AFTER',
LIST_CONTAINS = 'LIST_CONTAINS',
LIST_DOES_NOT_CONTAIN = 'LIST_DOES_NOT_CONTAIN',
LIST_IS_EMPTY = 'LIST_IS_EMPTY',
LIST_IS_NOT_EMPTY = 'LIST_IS_NOT_EMPTY',
EXISTS = 'EXISTS',
DOES_NOT_EXIST = 'DOES_NOT_EXIST',
}
export const singleValueConditions = [
BranchOperator.EXISTS,
BranchOperator.DOES_NOT_EXIST,
BranchOperator.BOOLEAN_IS_TRUE,
BranchOperator.BOOLEAN_IS_FALSE,
BranchOperator.LIST_IS_EMPTY,
BranchOperator.LIST_IS_NOT_EMPTY,
]
export const textConditions = [
BranchOperator.TEXT_CONTAINS,
BranchOperator.TEXT_DOES_NOT_CONTAIN,
BranchOperator.TEXT_EXACTLY_MATCHES,
BranchOperator.TEXT_DOES_NOT_EXACTLY_MATCH,
BranchOperator.TEXT_STARTS_WITH,
BranchOperator.TEXT_DOES_NOT_START_WITH,
BranchOperator.TEXT_ENDS_WITH,
BranchOperator.TEXT_DOES_NOT_END_WITH,
BranchOperator.LIST_CONTAINS,
BranchOperator.LIST_DOES_NOT_CONTAIN,
]
const BranchOperatorTextLiterals = [
Type.Literal(BranchOperator.TEXT_CONTAINS),
Type.Literal(BranchOperator.TEXT_DOES_NOT_CONTAIN),
Type.Literal(BranchOperator.TEXT_EXACTLY_MATCHES),
Type.Literal(BranchOperator.TEXT_DOES_NOT_EXACTLY_MATCH),
Type.Literal(BranchOperator.TEXT_STARTS_WITH),
Type.Literal(BranchOperator.TEXT_DOES_NOT_START_WITH),
Type.Literal(BranchOperator.TEXT_ENDS_WITH),
Type.Literal(BranchOperator.TEXT_DOES_NOT_END_WITH),
Type.Literal(BranchOperator.LIST_CONTAINS),
Type.Literal(BranchOperator.LIST_DOES_NOT_CONTAIN),
]
const BranchOperatorNumberLiterals = [
Type.Literal(BranchOperator.NUMBER_IS_GREATER_THAN),
Type.Literal(BranchOperator.NUMBER_IS_LESS_THAN),
Type.Literal(BranchOperator.NUMBER_IS_EQUAL_TO),
]
const BranchOperatorDateLiterals = [
Type.Literal(BranchOperator.DATE_IS_BEFORE),
Type.Literal(BranchOperator.DATE_IS_EQUAL),
Type.Literal(BranchOperator.DATE_IS_AFTER),
]
const BranchOperatorSingleValueLiterals = [
Type.Literal(BranchOperator.EXISTS),
Type.Literal(BranchOperator.DOES_NOT_EXIST),
Type.Literal(BranchOperator.BOOLEAN_IS_TRUE),
Type.Literal(BranchOperator.BOOLEAN_IS_FALSE),
Type.Literal(BranchOperator.LIST_IS_EMPTY),
Type.Literal(BranchOperator.LIST_IS_NOT_EMPTY),
]
const BranchTextConditionValid = (addMinLength: boolean) =>
Type.Object({
firstValue: Type.String(addMinLength ? { minLength: 1 } : {}),
secondValue: Type.String(addMinLength ? { minLength: 1 } : {}),
caseSensitive: Type.Optional(Type.Boolean()),
operator: Type.Optional(Type.Union(BranchOperatorTextLiterals)),
})
const BranchNumberConditionValid = (addMinLength: boolean) =>
Type.Object({
firstValue: Type.String(addMinLength ? { minLength: 1 } : {}),
secondValue: Type.String(addMinLength ? { minLength: 1 } : {}),
operator: Type.Optional(Type.Union(BranchOperatorNumberLiterals)),
})
const BranchDateConditionValid = (addMinLength: boolean) =>
Type.Object({
firstValue: Type.String(addMinLength ? { minLength: 1 } : {}),
secondValue: Type.String(addMinLength ? { minLength: 1 } : {}),
operator: Type.Optional(Type.Union(BranchOperatorDateLiterals)),
})
const BranchSingleValueConditionValid = (addMinLength: boolean) =>
Type.Object({
firstValue: Type.String(addMinLength ? { minLength: 1 } : {}),
operator: Type.Optional(Type.Union(BranchOperatorSingleValueLiterals)),
})
const BranchConditionValid = (addMinLength: boolean) =>
Type.Union([
BranchTextConditionValid(addMinLength),
BranchNumberConditionValid(addMinLength),
BranchDateConditionValid(addMinLength),
BranchSingleValueConditionValid(addMinLength),
])
export const ValidBranchCondition = BranchConditionValid(true)
export type ValidBranchCondition = Static<typeof ValidBranchCondition>
// TODO remove this and use ValidBranchCondition everywhere
export const BranchCondition = BranchConditionValid(false)
export type BranchCondition = Static<typeof BranchCondition>
export const BranchTextCondition = BranchTextConditionValid(false)
export type BranchTextCondition = Static<typeof BranchTextCondition>
export const BranchNumberCondition = BranchNumberConditionValid(false)
export type BranchNumberCondition = Static<typeof BranchNumberCondition>
export const BranchDateCondition = BranchDateConditionValid(false)
export type BranchDateCondition = Static<typeof BranchDateCondition>
export const BranchSingleValueCondition =
BranchSingleValueConditionValid(false)
export type BranchSingleValueCondition = Static<
typeof BranchSingleValueCondition
>
export const RouterBranchesSchema = (addMinLength: boolean) =>
Type.Array(
Type.Union([
Type.Object({
conditions: Type.Array(Type.Array(BranchConditionValid(addMinLength))),
branchType: Type.Literal(BranchExecutionType.CONDITION),
branchName: Type.String(),
}),
Type.Object({
branchType: Type.Literal(BranchExecutionType.FALLBACK),
branchName: Type.String(),
}),
]),
)
export const RouterActionSettings = Type.Object({
...commonActionSettings,
branches: RouterBranchesSchema(false),
executionType: Type.Enum(RouterExecutionType),
})
export const RouterActionSettingsWithValidation = Type.Object({
branches: RouterBranchesSchema(true),
executionType: Type.Enum(RouterExecutionType),
})
export type RouterActionSettings = Static<typeof RouterActionSettings>
// Union of all actions
export const FlowAction = Type.Recursive((action) =>
Type.Union([
Type.Intersect([
CodeActionSchema,
Type.Object({
nextAction: Type.Optional(action),
}),
]),
Type.Intersect([
PieceActionSchema,
Type.Object({
nextAction: Type.Optional(action),
}),
]),
Type.Intersect([
LoopOnItemsActionSchema,
Type.Object({
nextAction: Type.Optional(action),
firstLoopAction: Type.Optional(action),
}),
]),
Type.Intersect([
Type.Object({
...commonActionProps,
type: Type.Literal(FlowActionType.ROUTER),
settings: RouterActionSettings,
}),
Type.Object({
nextAction: Type.Optional(action),
children: Type.Array(Type.Union([action, Type.Null()])),
}),
]),
]),
)
export const RouterActionSchema = Type.Object({
...commonActionProps,
type: Type.Literal(FlowActionType.ROUTER),
settings: RouterActionSettings,
})
export const SingleActionSchema = Type.Union([
CodeActionSchema,
PieceActionSchema,
LoopOnItemsActionSchema,
RouterActionSchema,
])
export type FlowAction = Static<typeof FlowAction>
export type RouterAction = Static<typeof RouterActionSchema> & {
nextAction?: FlowAction
children: (FlowAction | null)[]
}
export type LoopOnItemsAction = Static<typeof LoopOnItemsActionSchema> & {
nextAction?: FlowAction
firstLoopAction?: FlowAction
}
export type PieceAction = Static<typeof PieceActionSchema> & {
nextAction?: FlowAction
}
export type CodeAction = Static<typeof CodeActionSchema> & {
nextAction?: FlowAction
}
export const emptyCondition: ValidBranchCondition = {
firstValue: '',
secondValue: '',
operator: BranchOperator.TEXT_CONTAINS,
caseSensitive: false,
}

View File

@@ -0,0 +1,7 @@
import { Static, Type } from '@sinclair/typebox'
export const CountFlowsRequest = Type.Object({
folderId: Type.Optional(Type.String()),
})
export type CountFlowsRequest = Static<typeof CountFlowsRequest>

View File

@@ -0,0 +1,13 @@
import { Static, Type } from '@sinclair/typebox'
import { Metadata } from '../../common/metadata'
export const CreateFlowRequest = Type.Object({
displayName: Type.String({}),
/**If folderId is provided, folderName is ignored */
folderId: Type.Optional(Type.String({})),
folderName: Type.Optional(Type.String({})),
projectId: Type.String({}),
metadata: Type.Optional(Metadata),
})
export type CreateFlowRequest = Static<typeof CreateFlowRequest>

View File

@@ -0,0 +1,8 @@
import { Static, Type } from '@sinclair/typebox'
export const CreateMCPServerFromStepParams = Type.Object({
flowId: Type.String(),
flowVersionId: Type.String(),
stepName: Type.String(),
})
export type CreateMCPServerFromStepParams = Static<typeof CreateMCPServerFromStepParams>

View File

@@ -0,0 +1,32 @@
import { Static, Type } from '@sinclair/typebox'
import { Cursor } from '../../common/seek-page'
import { FlowStatus } from '../flow'
import { FlowVersionState } from '../flow-version'
export const ListFlowsRequest = Type.Object({
folderId: Type.Optional(Type.String()),
limit: Type.Optional(Type.Number({})),
cursor: Type.Optional(Type.String({})),
status: Type.Optional(Type.Array(Type.Enum(FlowStatus))),
projectId: Type.String({}),
name: Type.Optional(Type.String({})),
agentExternalIds: Type.Optional(Type.Array(Type.String({}))),
versionState: Type.Optional(Type.Enum(FlowVersionState)),
connectionExternalIds: Type.Optional(Type.Array(Type.String({}))),
externalIds: Type.Optional(Type.Array(Type.String({}))),
})
export type ListFlowsRequest = Omit<Static<typeof ListFlowsRequest>, 'cursor'> & { cursor: Cursor | undefined }
export const GetFlowQueryParamsRequest = Type.Object({
versionId: Type.Optional(Type.String({})),
})
export type GetFlowQueryParamsRequest = Static<typeof GetFlowQueryParamsRequest>
export const ListFlowVersionRequest = Type.Object({
limit: Type.Optional(Type.Number({})),
cursor: Type.Optional(Type.String({})),
})
export type ListFlowVersionRequest = Omit<Static<typeof ListFlowVersionRequest>, 'cursor'> & { cursor: Cursor | undefined }

View File

@@ -0,0 +1,44 @@
import { Static, Type } from '@sinclair/typebox'
import { BaseModelSchema, Nullable } from '../common/base-model'
import { ApId } from '../common/id-generator'
import { UserWithMetaInformation } from '../user'
import { FlowTrigger } from './triggers/trigger'
export type FlowVersionId = ApId
export const LATEST_FLOW_SCHEMA_VERSION = '10'
export enum FlowVersionState {
LOCKED = 'LOCKED',
DRAFT = 'DRAFT',
}
export const FlowVersion = Type.Object({
...BaseModelSchema,
flowId: Type.String(),
displayName: Type.String(),
trigger: FlowTrigger,
updatedBy: Nullable(Type.String()),
valid: Type.Boolean(),
schemaVersion: Nullable(Type.String()),
agentIds: Type.Array(Type.String()),
state: Type.Enum(FlowVersionState),
connectionIds: Type.Array(Type.String()),
backupFiles: Nullable(Type.Record(Type.String(), Type.String())),
})
export type FlowVersion = Static<typeof FlowVersion>
export const FlowVersionMetadata = Type.Object({
...BaseModelSchema,
flowId: Type.String(),
displayName: Type.String(),
valid: Type.Boolean(),
state: Type.Enum(FlowVersionState),
updatedBy: Nullable(Type.String()),
schemaVersion: Nullable(Type.String()),
updatedByUser: Nullable(UserWithMetaInformation),
})
export type FlowVersionMetadata = Static<typeof FlowVersionMetadata>

View File

@@ -0,0 +1,61 @@
import { Static, Type } from '@sinclair/typebox'
import { BaseModelSchema, Nullable } from '../common/base-model'
import { ApId } from '../common/id-generator'
import { Metadata } from '../common/metadata'
import { TriggerSource, WebhookHandshakeConfiguration } from '../trigger'
import { FlowVersion } from './flow-version'
export type FlowId = ApId
export enum FlowStatus {
ENABLED = 'ENABLED',
DISABLED = 'DISABLED',
}
export enum FlowOperationStatus {
NONE = 'NONE',
DELETING = 'DELETING',
ENABLING = 'ENABLING',
DISABLING = 'DISABLING',
}
export const flowExecutionStateKey = (flowId: FlowId) => `flow-execution-state:${flowId}`
export type FlowExecutionState = {
exists: false
} | {
exists: true
handshakeConfiguration: WebhookHandshakeConfiguration | undefined
flow: Flow
platformId: string
}
export const Flow = Type.Object({
...BaseModelSchema,
projectId: Type.String(),
externalId: Type.String(),
folderId: Nullable(Type.String()),
status: Type.Enum(FlowStatus),
publishedVersionId: Nullable(Type.String()),
metadata: Nullable(Metadata),
operationStatus: Type.Enum(FlowOperationStatus),
timeSavedPerRun: Nullable(Type.Number()),
})
export type Flow = Static<typeof Flow>
export const PopulatedFlow = Type.Composite([
Flow,
Type.Object({
version: FlowVersion,
triggerSource: Type.Optional(Type.Pick(TriggerSource, ['schedule'])),
}),
])
export type PopulatedFlow = Static<typeof PopulatedFlow>
export const PopulatedTriggerSource = Type.Composite([
TriggerSource,
Type.Object({
flow: Flow,
}),
])
export type PopulatedTriggerSource = Static<typeof PopulatedTriggerSource>

View File

@@ -0,0 +1,30 @@
import { Static, Type } from '@sinclair/typebox'
import { Cursor } from '../../common/seek-page'
export const CreateFolderRequest = Type.Object({
displayName: Type.String(),
projectId: Type.String(),
})
export type CreateFolderRequest = Static<typeof CreateFolderRequest>
export const UpdateFolderRequest = Type.Object({
displayName: Type.String(),
})
export type UpdateFolderRequest = Static<typeof UpdateFolderRequest>
export const DeleteFolderRequest = Type.Object({
id: Type.String(),
})
export type DeleteFlowRequest = Static<typeof DeleteFolderRequest>
export const ListFolderRequest = Type.Object({
limit: Type.Optional(Type.Number({})),
cursor: Type.Optional(Type.String({})),
projectId: Type.String(),
})
export type ListFolderRequest = Omit<Static<typeof ListFolderRequest>, 'cursor'> & { cursor: Cursor | undefined }

View File

@@ -0,0 +1,18 @@
import { Static, Type } from '@sinclair/typebox'
import { BaseModelSchema } from '../../common'
export type FolderId = string
export const Folder = Type.Object({
...BaseModelSchema,
id: Type.String(),
projectId: Type.String(),
displayName: Type.String(),
displayOrder: Type.Number(),
})
export const UncategorizedFolderId = 'NULL'
export type Folder = Static<typeof Folder>
export type FolderDto = Folder & { numberOfFlows: number }

View File

@@ -0,0 +1,3 @@
import { Folder } from './folder'
export type FolderDto = Folder & { numberOfFlows: number }

View File

@@ -0,0 +1,54 @@
import { Static, Type } from '@sinclair/typebox'
export enum FormInputType {
TEXT = 'text',
FILE = 'file',
TEXT_AREA = 'text_area',
TOGGLE = 'toggle',
}
export const FormInput = Type.Object({
displayName: Type.String(),
required: Type.Boolean(),
description: Type.String(),
type: Type.Enum(FormInputType),
})
export type FormInput = Static<typeof FormInput>
export const FormProps = Type.Object({
inputs: Type.Array(FormInput),
waitForResponse: Type.Boolean(),
})
export type FormProps = Static<typeof FormProps>
export const FormResponse = Type.Object({
id: Type.String(),
title: Type.String(),
props: FormProps,
projectId: Type.String(),
version: Type.String(),
})
export type FormResponse = Static<typeof FormResponse>
export const ChatUIProps = Type.Object({
botName: Type.String(),
})
export type ChatUIProps = Static<typeof ChatUIProps>
export const ChatUIResponse = Type.Object({
id: Type.String(),
title: Type.String(),
props: ChatUIProps,
projectId: Type.String(),
platformLogoUrl: Type.String(),
platformName: Type.String(),
})
export type ChatUIResponse = Static<typeof ChatUIResponse>
export const USE_DRAFT_QUERY_PARAM_NAME = 'useDraft'

View File

@@ -0,0 +1,6 @@
export * from './form'
export * from './step-file/step-file'
export * from './sample-data'
export * from './flow'
export * from './test-trigger'
export * from './properties'

View File

@@ -0,0 +1,82 @@
import { applyFunctionToValuesSync, isString } from '../../common'
import { FlowAction } from '../actions/action'
import { FlowVersion } from '../flow-version'
import { flowStructureUtil } from '../util/flow-structure-util'
function mapToNewNames(flowVersion: FlowVersion, clonedActions: FlowAction[]): Record<string, string> {
const existingNames = flowStructureUtil.getAllSteps(flowVersion.trigger)
.map(step => step.name)
const oldStepNames = clonedActions.flatMap(clonedAction => flowStructureUtil.getAllSteps(clonedAction).map(step => step.name))
return oldStepNames.reduce((nameMap, oldName) => {
const newName = flowStructureUtil.findUnusedName(existingNames)
existingNames.push(newName)
return { ...nameMap, [oldName]: newName }
}, {} as Record<string, string>)
}
type ReplaceOldStepNameWithNewOneProps = {
input: string
oldStepName: string
newStepName: string
}
function replaceOldStepNameWithNewOne({
input,
oldStepName,
newStepName,
}: ReplaceOldStepNameWithNewOneProps): string {
const regex = /{{(.*?)}}/g // Regular expression to match strings inside {{ }}
return input.replace(regex, (match, content) => {
// Replace the content inside {{ }} using the provided function
const replacedContent = content.replaceAll(
new RegExp(`\\b${oldStepName}\\b`, 'g'),
`${newStepName}`,
)
// Reconstruct the {{ }} with the replaced content
return `{{${replacedContent}}}`
})
}
function clone(step: FlowAction, oldNameToNewName: Record<string, string>): FlowAction {
step.displayName = `${step.displayName} Copy`
step.name = oldNameToNewName[step.name]
if ('input' in step.settings) {
Object.keys(oldNameToNewName).forEach((oldName) => {
const settings = step.settings as { input: unknown }
settings.input = applyFunctionToValuesSync(
settings.input,
(value: unknown) => {
if (isString(value)) {
return replaceOldStepNameWithNewOne({
input: value,
oldStepName: oldName,
newStepName: oldNameToNewName[oldName],
})
}
return value
},
)
})
}
if (step.settings.sampleData) {
step.settings = {
...step.settings,
sampleData: {
...step.settings.sampleData,
sampleDataFileId: undefined,
sampleDataInputFileId: undefined,
lastTestDate: undefined,
},
}
}
return step
}
export const addActionUtils = {
mapToNewNames,
clone,
}

View File

@@ -0,0 +1,134 @@
import { TypeCompiler } from '@sinclair/typebox/compiler'
import { isNil } from '../../common'
import { ActivepiecesError, ErrorCode } from '../../common/activepieces-error'
import { FlowAction, FlowActionType, LoopOnItemsAction, RouterAction, SingleActionSchema } from '../actions/action'
import { FlowVersion } from '../flow-version'
import { flowStructureUtil, Step } from '../util/flow-structure-util'
import { AddActionRequest, StepLocationRelativeToParent, UpdateActionRequest } from './index'
const actionSchemaValidator = TypeCompiler.Compile(SingleActionSchema)
type ActionCreationProps = {
nextAction?: FlowAction
}
function createAction(request: UpdateActionRequest, {
nextAction,
}: ActionCreationProps): FlowAction {
const baseProperties = {
displayName: request.displayName,
name: request.name,
valid: false,
skip: request.skip,
settings: {
...request.settings,
customLogoUrl: request.settings.customLogoUrl,
},
nextAction,
}
let action: FlowAction
switch (request.type) {
case FlowActionType.ROUTER:
action = {
...baseProperties,
type: FlowActionType.ROUTER,
settings: request.settings,
children: request.settings.branches.map(() => null),
}
break
case FlowActionType.LOOP_ON_ITEMS:
action = {
...baseProperties,
type: FlowActionType.LOOP_ON_ITEMS,
settings: request.settings,
}
break
case FlowActionType.PIECE:
action = {
...baseProperties,
type: FlowActionType.PIECE,
settings: request.settings,
}
break
case FlowActionType.CODE:
action = {
...baseProperties,
type: FlowActionType.CODE,
settings: request.settings,
}
break
}
const valid = (isNil(request.valid) ? true : request.valid) && actionSchemaValidator.Check(action)
return {
...action,
valid,
}
}
function handleLoopOnItems(parentStep: LoopOnItemsAction, request: AddActionRequest): Step {
if (request.stepLocationRelativeToParent === StepLocationRelativeToParent.INSIDE_LOOP) {
parentStep.firstLoopAction = createAction(request.action, {
nextAction: parentStep.firstLoopAction,
})
}
else if (request.stepLocationRelativeToParent === StepLocationRelativeToParent.AFTER) {
parentStep.nextAction = createAction(request.action, {
nextAction: parentStep.nextAction,
})
}
else {
throw new ActivepiecesError(
{
code: ErrorCode.FLOW_OPERATION_INVALID,
params: {
message: `Loop step parent ${request.stepLocationRelativeToParent} not found`,
},
})
}
return parentStep
}
function handleRouter(parentStep: RouterAction, request: AddActionRequest): Step {
if (request.stepLocationRelativeToParent === StepLocationRelativeToParent.INSIDE_BRANCH && !isNil(request.branchIndex)) {
parentStep.children[request.branchIndex] = createAction(request.action, {
nextAction: parentStep.children[request.branchIndex] ?? undefined,
})
}
else if (request.stepLocationRelativeToParent === StepLocationRelativeToParent.AFTER) {
parentStep.nextAction = createAction(request.action, {
nextAction: parentStep.nextAction,
})
}
else {
throw new ActivepiecesError({
code: ErrorCode.FLOW_OPERATION_INVALID,
params: {
message: `Router step parent ${request.stepLocationRelativeToParent} not found`,
},
})
}
return parentStep
}
function _addAction(flowVersion: FlowVersion, request: AddActionRequest): FlowVersion {
return flowStructureUtil.transferFlow(flowVersion, (parentStep: Step) => {
if (parentStep.name !== request.parentStep) {
return parentStep
}
switch (parentStep.type) {
case FlowActionType.LOOP_ON_ITEMS:
return handleLoopOnItems(parentStep, request)
case FlowActionType.ROUTER:
return handleRouter(parentStep, request)
default: {
parentStep.nextAction = createAction(request.action, {
nextAction: parentStep.nextAction,
})
return parentStep
}
}
})
}
export { _addAction }

View File

@@ -0,0 +1,26 @@
import { insertAt } from '../../common'
import { FlowActionType, RouterAction } from '../actions/action'
import { FlowVersion } from '../flow-version'
import { flowStructureUtil } from '../util/flow-structure-util'
import { AddBranchRequest } from '.'
function _addBranch(flowVersion: FlowVersion, request: AddBranchRequest): FlowVersion {
return flowStructureUtil.transferFlow(flowVersion, (parentStep) => {
if (parentStep.name !== request.stepName || parentStep.type !== FlowActionType.ROUTER) {
return parentStep
}
const routerAction = parentStep as RouterAction
return {
...routerAction,
settings: {
...routerAction.settings,
branches: insertAt(routerAction.settings.branches, request.branchIndex, flowStructureUtil.createBranch(request.branchName, request.conditions)),
},
children: insertAt(routerAction.children, request.branchIndex, null),
}
})
}
export { _addBranch }

View File

@@ -0,0 +1,19 @@
import { FlowAction } from '../actions/action'
import { FlowVersion } from '../flow-version'
import { flowStructureUtil } from '../util/flow-structure-util'
import { _getImportOperations } from './import-flow'
export function _getActionsForCopy(selectedSteps: string[], flowVersion: FlowVersion): FlowAction[] {
const allSteps = flowStructureUtil.getAllSteps(flowVersion.trigger)
const actionsToCopy = selectedSteps
.map((stepName) => flowStructureUtil.getStepOrThrow(stepName, flowVersion.trigger))
.filter((step) => flowStructureUtil.isAction(step.type))
return actionsToCopy
.filter(step => !actionsToCopy.filter(parent => parent.name !== step.name).some(parent => flowStructureUtil.isChildOf(parent, step.name)))
.map(step => {
const clonedAction = JSON.parse(JSON.stringify(step))
clonedAction.nextAction = undefined
return clonedAction
})
.sort((a, b) => allSteps.indexOf(a) - allSteps.indexOf(b)) as FlowAction[]
}

View File

@@ -0,0 +1,46 @@
import { FlowAction, FlowActionType } from '../actions/action'
import { FlowVersion } from '../flow-version'
import { flowStructureUtil } from '../util/flow-structure-util'
import { DeleteActionRequest } from './index'
function _deleteAction(
flowVersion: FlowVersion,
request: DeleteActionRequest,
): FlowVersion {
let clonedVersion: FlowVersion = flowVersion
for (const name of request.names) {
clonedVersion = flowStructureUtil.transferFlow(clonedVersion, (parentStep) => {
if (parentStep.nextAction && parentStep.nextAction.name === name) {
const stepToUpdate: FlowAction = parentStep.nextAction
parentStep.nextAction = stepToUpdate.nextAction
}
switch (parentStep.type) {
case FlowActionType.LOOP_ON_ITEMS: {
if (
parentStep.firstLoopAction &&
parentStep.firstLoopAction.name === name
) {
const stepToUpdate: FlowAction = parentStep.firstLoopAction
parentStep.firstLoopAction = stepToUpdate.nextAction
}
break
}
case FlowActionType.ROUTER: {
parentStep.children = parentStep.children.map((child) => {
if (child && child.name === name) {
return child.nextAction ?? null
}
return child
})
break
}
default:
break
}
return parentStep
})
}
return clonedVersion
}
export { _deleteAction }

View File

@@ -0,0 +1,23 @@
import { FlowActionType, RouterAction } from '../actions/action'
import { FlowVersion } from '../flow-version'
import { flowStructureUtil } from '../util/flow-structure-util'
import { DeleteBranchRequest } from '.'
function _deleteBranch(flowVersion: FlowVersion, request: DeleteBranchRequest): FlowVersion {
return flowStructureUtil.transferFlow(flowVersion, (parentStep) => {
if (parentStep.name !== request.stepName || parentStep.type !== FlowActionType.ROUTER) {
return parentStep
}
const routerAction = parentStep as RouterAction
return {
...routerAction,
settings: {
...routerAction.settings,
branches: routerAction.settings.branches.filter((_, index) => index !== request.branchIndex),
},
children: routerAction.children.filter((_, index) => index !== request.branchIndex),
}
})
}
export { _deleteBranch }

View File

@@ -0,0 +1,74 @@
import { isNil } from '../../common'
import { BranchExecutionType, FlowAction, RouterAction } from '../actions/action'
import { FlowVersion } from '../flow-version'
import { flowStructureUtil } from '../util/flow-structure-util'
import { addActionUtils } from './add-action-util'
import { _getImportOperations } from './import-flow'
import { FlowOperationRequest, FlowOperationType, StepLocationRelativeToParent } from '.'
function _duplicateStep(stepName: string, flowVersion: FlowVersion): FlowOperationRequest[] {
const clonedAction: FlowAction = JSON.parse(JSON.stringify(flowStructureUtil.getActionOrThrow(stepName, flowVersion.trigger)))
const clonedActionWithoutNextAction = {
...clonedAction,
nextAction: undefined,
}
const oldNameToNewName = addActionUtils.mapToNewNames(flowVersion, [clonedActionWithoutNextAction])
const clonedSubflow = flowStructureUtil.transferStep(clonedActionWithoutNextAction, (step: FlowAction) => {
return addActionUtils.clone(step, oldNameToNewName)
})
const importOperations = _getImportOperations(clonedSubflow)
return [
{
type: FlowOperationType.ADD_ACTION,
request: {
action: clonedSubflow as FlowAction,
parentStep: stepName,
stepLocationRelativeToParent: StepLocationRelativeToParent.AFTER,
},
},
...importOperations,
]
}
function _duplicateBranch(
routerName: string,
childIndex: number,
flowVersion: FlowVersion,
): FlowOperationRequest[] {
const router = flowStructureUtil.getActionOrThrow(routerName, flowVersion.trigger)
const clonedRouter: RouterAction = JSON.parse(JSON.stringify(router))
const operations: FlowOperationRequest[] = [{
type: FlowOperationType.ADD_BRANCH,
request: {
branchName: `${clonedRouter.settings.branches[childIndex].branchName} Copy`,
branchIndex: childIndex + 1,
stepName: routerName,
conditions: clonedRouter.settings.branches[childIndex].branchType === BranchExecutionType.CONDITION ? clonedRouter.settings.branches[childIndex].conditions : undefined,
},
}]
const childRouter = clonedRouter.children[childIndex]
if (!isNil(childRouter)) {
const oldNameToNewName = addActionUtils.mapToNewNames(flowVersion, [childRouter])
const clonedSubflow = flowStructureUtil.transferStep(childRouter, (step: FlowAction) => {
return addActionUtils.clone(step, oldNameToNewName)
})
const importOperations = _getImportOperations(clonedSubflow)
operations.push({
type: FlowOperationType.ADD_ACTION,
request: {
stepLocationRelativeToParent: StepLocationRelativeToParent.INSIDE_BRANCH,
action: clonedSubflow as FlowAction,
parentStep: routerName,
branchIndex: childIndex + 1,
},
})
operations.push(...importOperations)
}
return operations
}
export { _duplicateStep, _duplicateBranch }

View File

@@ -0,0 +1,130 @@
import { isNil } from '../../common'
import { FlowAction, FlowActionType } from '../actions/action'
import { FlowVersion } from '../flow-version'
import { FlowTrigger, FlowTriggerType } from '../triggers/trigger'
import { flowStructureUtil } from '../util/flow-structure-util'
import { FlowOperationRequest, FlowOperationType, ImportFlowRequest, StepLocationRelativeToParent } from './index'
function createDeleteActionOperation(actionName: string): FlowOperationRequest {
return {
type: FlowOperationType.DELETE_ACTION,
request: { names: [actionName] },
}
}
function createUpdateTriggerOperation(trigger: FlowTrigger): FlowOperationRequest {
return {
type: FlowOperationType.UPDATE_TRIGGER,
request: trigger,
}
}
function createChangeNameOperation(displayName: string): FlowOperationRequest {
return {
type: FlowOperationType.CHANGE_NAME,
request: { displayName },
}
}
function _getImportOperations(step: FlowAction | FlowTrigger | undefined): FlowOperationRequest[] {
const steps: FlowOperationRequest[] = []
while (step) {
if (step.nextAction) {
steps.push({
type: FlowOperationType.ADD_ACTION,
request: {
parentStep: step?.name ?? '',
stepLocationRelativeToParent: StepLocationRelativeToParent.AFTER,
action: removeAnySubsequentAction(step.nextAction),
},
})
}
switch (step.type) {
case FlowActionType.LOOP_ON_ITEMS: {
if (step.firstLoopAction) {
steps.push({
type: FlowOperationType.ADD_ACTION,
request: {
parentStep: step.name,
stepLocationRelativeToParent: StepLocationRelativeToParent.INSIDE_LOOP,
action: removeAnySubsequentAction(step.firstLoopAction),
},
})
steps.push(..._getImportOperations(step.firstLoopAction))
}
break
}
case FlowActionType.ROUTER: {
if (step.children) {
for (const [index, child] of step.children.entries()) {
if (!isNil(child)) {
steps.push({
type: FlowOperationType.ADD_ACTION,
request: {
parentStep: step.name,
stepLocationRelativeToParent: StepLocationRelativeToParent.INSIDE_BRANCH,
branchIndex: index,
action: removeAnySubsequentAction(child),
},
})
steps.push(..._getImportOperations(child))
}
}
}
break
}
case FlowActionType.CODE:
case FlowActionType.PIECE:
case FlowTriggerType.PIECE:
case FlowTriggerType.EMPTY: {
break
}
}
step = step.nextAction
}
return steps
}
function removeAnySubsequentAction(action: FlowAction): FlowAction {
const clonedAction: FlowAction = JSON.parse(JSON.stringify(action))
switch (clonedAction.type) {
case FlowActionType.ROUTER: {
clonedAction.children = clonedAction.children.map((child: FlowAction | null) => {
if (isNil(child)) {
return null
}
return removeAnySubsequentAction(child)
})
break
}
case FlowActionType.LOOP_ON_ITEMS: {
delete clonedAction.firstLoopAction
break
}
case FlowActionType.PIECE:
case FlowActionType.CODE:
break
}
delete clonedAction.nextAction
return clonedAction
}
function _importFlow(flowVersion: FlowVersion, request: ImportFlowRequest): FlowOperationRequest[] {
const existingActions = flowStructureUtil.getAllNextActionsWithoutChildren(flowVersion.trigger)
const deleteOperations = existingActions.map(action =>
createDeleteActionOperation(action.name),
)
const importOperations = _getImportOperations(request.trigger)
return [
createChangeNameOperation(request.displayName),
...deleteOperations,
createUpdateTriggerOperation(request.trigger),
...importOperations,
]
}
export { _importFlow, _getImportOperations }

View File

@@ -0,0 +1,462 @@
import { Static, Type } from '@sinclair/typebox'
import { Nullable } from '../../common'
import { Metadata } from '../../common/metadata'
import { BranchCondition, CodeActionSchema, LoopOnItemsActionSchema, PieceActionSchema, RouterActionSchema } from '../actions/action'
import { FlowStatus } from '../flow'
import { FlowVersion, FlowVersionState } from '../flow-version'
import { SaveSampleDataRequest } from '../sample-data'
import { EmptyTrigger, FlowTrigger, FlowTriggerType, PieceTrigger } from '../triggers/trigger'
import { flowPieceUtil } from '../util/flow-piece-util'
import { flowStructureUtil } from '../util/flow-structure-util'
import { _addAction } from './add-action'
import { _addBranch } from './add-branch'
import { _getActionsForCopy } from './copy-action-operations'
import { _deleteAction } from './delete-action'
import { _deleteBranch } from './delete-branch'
import { _duplicateBranch, _duplicateStep } from './duplicate-step'
import { _importFlow } from './import-flow'
import { _moveAction } from './move-action'
import { _moveBranch } from './move-branch'
import { _getOperationsForPaste } from './paste-operations'
import { _skipAction } from './skip-action'
import { _updateAction } from './update-action'
import { _updateTrigger } from './update-trigger'
export enum FlowOperationType {
LOCK_AND_PUBLISH = 'LOCK_AND_PUBLISH',
CHANGE_STATUS = 'CHANGE_STATUS',
LOCK_FLOW = 'LOCK_FLOW',
CHANGE_FOLDER = 'CHANGE_FOLDER',
CHANGE_NAME = 'CHANGE_NAME',
MOVE_ACTION = 'MOVE_ACTION',
IMPORT_FLOW = 'IMPORT_FLOW',
UPDATE_TRIGGER = 'UPDATE_TRIGGER',
ADD_ACTION = 'ADD_ACTION',
UPDATE_ACTION = 'UPDATE_ACTION',
DELETE_ACTION = 'DELETE_ACTION',
DUPLICATE_ACTION = 'DUPLICATE_ACTION',
USE_AS_DRAFT = 'USE_AS_DRAFT',
DELETE_BRANCH = 'DELETE_BRANCH',
ADD_BRANCH = 'ADD_BRANCH',
DUPLICATE_BRANCH = 'DUPLICATE_BRANCH',
SET_SKIP_ACTION = 'SET_SKIP_ACTION',
UPDATE_METADATA = 'UPDATE_METADATA',
MOVE_BRANCH = 'MOVE_BRANCH',
SAVE_SAMPLE_DATA = 'SAVE_SAMPLE_DATA',
UPDATE_MINUTES_SAVED = 'UPDATE_MINUTES_SAVED',
}
export const DeleteBranchRequest = Type.Object({
branchIndex: Type.Number(),
stepName: Type.String(),
})
export const AddBranchRequest = Type.Object({
branchIndex: Type.Number(),
stepName: Type.String(),
conditions: Type.Optional(Type.Array(Type.Array(BranchCondition))),
branchName: Type.String(),
})
export const MoveBranchRequest = Type.Object({
sourceBranchIndex: Type.Number(),
targetBranchIndex: Type.Number(),
stepName: Type.String(),
})
export type MoveBranchRequest = Static<typeof MoveBranchRequest>
export const SkipActionRequest = Type.Object({
names: Type.Array(Type.String()),
skip: Type.Boolean(),
})
export type SkipActionRequest = Static<typeof SkipActionRequest>
export const DuplicateBranchRequest = Type.Object({
branchIndex: Type.Number(),
stepName: Type.String(),
})
export type DeleteBranchRequest = Static<typeof DeleteBranchRequest>
export type AddBranchRequest = Static<typeof AddBranchRequest>
export type DuplicateBranchRequest = Static<typeof DuplicateBranchRequest>
export enum StepLocationRelativeToParent {
AFTER = 'AFTER',
INSIDE_LOOP = 'INSIDE_LOOP',
INSIDE_BRANCH = 'INSIDE_BRANCH',
}
export const UseAsDraftRequest = Type.Object({
versionId: Type.String(),
})
export type UseAsDraftRequest = Static<typeof UseAsDraftRequest>
export const LockFlowRequest = Type.Object({})
export type LockFlowRequest = Static<typeof LockFlowRequest>
export const ImportFlowRequest = Type.Object({
displayName: Type.String({}),
trigger: FlowTrigger,
schemaVersion: Nullable(Type.String()),
})
export type ImportFlowRequest = Static<typeof ImportFlowRequest>
export const ChangeFolderRequest = Type.Object({
folderId: Nullable(Type.String({})),
})
export type ChangeFolderRequest = Static<typeof ChangeFolderRequest>
export const ChangeNameRequest = Type.Object({
displayName: Type.String({}),
})
export type ChangeNameRequest = Static<typeof ChangeNameRequest>
export const DeleteActionRequest = Type.Object({
names: Type.Array(Type.String()),
})
export type DeleteActionRequest = Static<typeof DeleteActionRequest>
export const UpdateActionRequest = Type.Union([
CodeActionSchema,
LoopOnItemsActionSchema,
PieceActionSchema,
RouterActionSchema,
])
export type UpdateActionRequest = Static<typeof UpdateActionRequest>
export const DuplicateStepRequest = Type.Object({
stepName: Type.String(),
})
export type DuplicateStepRequest = Static<typeof DuplicateStepRequest>
export const MoveActionRequest = Type.Object({
name: Type.String(),
newParentStep: Type.String(),
stepLocationRelativeToNewParent: Type.Optional(
Type.Enum(StepLocationRelativeToParent),
),
branchIndex: Type.Optional(Type.Number()),
})
export type MoveActionRequest = Static<typeof MoveActionRequest>
export const AddActionRequest = Type.Object({
parentStep: Type.String(),
stepLocationRelativeToParent: Type.Optional(
Type.Enum(StepLocationRelativeToParent),
),
branchIndex: Type.Optional(Type.Number()),
action: UpdateActionRequest,
})
export type AddActionRequest = Static<typeof AddActionRequest>
export const UpdateTriggerRequest = Type.Union([EmptyTrigger, PieceTrigger])
export type UpdateTriggerRequest = Static<typeof UpdateTriggerRequest>
export const UpdateFlowStatusRequest = Type.Object({
status: Type.Enum(FlowStatus),
})
export type UpdateFlowStatusRequest = Static<typeof UpdateFlowStatusRequest>
export const ChangePublishedVersionIdRequest = Type.Object({
status: Type.Optional(Type.Enum(FlowStatus)),
})
export type ChangePublishedVersionIdRequest = Static<
typeof ChangePublishedVersionIdRequest
>
export const UpdateMetadataRequest = Type.Object({
metadata: Nullable(Metadata),
})
export type UpdateMetadataRequest = Static<typeof UpdateMetadataRequest>
export const UpdateMinutesSavedRequest = Type.Object({
timeSavedPerRun: Nullable(Type.Number()),
})
export type UpdateMinutesSavedRequest = Static<typeof UpdateMinutesSavedRequest>
export const FlowOperationRequest = Type.Union([
Type.Object(
{
type: Type.Literal(FlowOperationType.MOVE_ACTION),
request: MoveActionRequest,
},
{
title: 'Move Action',
},
),
Type.Object(
{
type: Type.Literal(FlowOperationType.CHANGE_STATUS),
request: UpdateFlowStatusRequest,
},
{
title: 'Change Status',
},
),
Type.Object(
{
type: Type.Literal(FlowOperationType.LOCK_AND_PUBLISH),
request: ChangePublishedVersionIdRequest,
},
{
title: 'Lock and Publish',
},
),
Type.Object(
{
type: Type.Literal(FlowOperationType.USE_AS_DRAFT),
request: UseAsDraftRequest,
},
{
title: 'Copy as Draft',
},
),
Type.Object(
{
type: Type.Literal(FlowOperationType.LOCK_FLOW),
request: LockFlowRequest,
},
{
title: 'Lock Flow',
},
),
Type.Object(
{
type: Type.Literal(FlowOperationType.IMPORT_FLOW),
request: ImportFlowRequest,
},
{
title: 'Import Flow',
},
),
Type.Object(
{
type: Type.Literal(FlowOperationType.CHANGE_NAME),
request: ChangeNameRequest,
},
{
title: 'Change Name',
},
),
Type.Object(
{
type: Type.Literal(FlowOperationType.DELETE_ACTION),
request: DeleteActionRequest,
},
{
title: 'Delete Action',
},
),
Type.Object(
{
type: Type.Literal(FlowOperationType.UPDATE_ACTION),
request: UpdateActionRequest,
},
{
title: 'Update Action',
},
),
Type.Object(
{
type: Type.Literal(FlowOperationType.ADD_ACTION),
request: AddActionRequest,
},
{
title: 'Add Action',
},
),
Type.Object(
{
type: Type.Literal(FlowOperationType.UPDATE_TRIGGER),
request: UpdateTriggerRequest,
},
{
title: 'Update Trigger',
},
),
Type.Object(
{
type: Type.Literal(FlowOperationType.CHANGE_FOLDER),
request: ChangeFolderRequest,
},
{
title: 'Change Folder',
},
),
Type.Object(
{
type: Type.Literal(FlowOperationType.DUPLICATE_ACTION),
request: DuplicateStepRequest,
},
{
title: 'Duplicate Action',
},
),
Type.Object(
{
type: Type.Literal(FlowOperationType.DELETE_BRANCH),
request: DeleteBranchRequest,
},
{
title: 'Delete Branch',
},
),
Type.Object(
{
type: Type.Literal(FlowOperationType.ADD_BRANCH),
request: AddBranchRequest,
},
{
title: 'Add Branch',
},
),
Type.Object(
{
type: Type.Literal(FlowOperationType.DUPLICATE_BRANCH),
request: DuplicateBranchRequest,
},
{
title: 'Duplicate Branch',
},
),
Type.Object(
{
type: Type.Literal(FlowOperationType.SET_SKIP_ACTION),
request: SkipActionRequest,
},
{
title: 'Skip Action',
},
),
Type.Object(
{
type: Type.Literal(FlowOperationType.UPDATE_METADATA),
request: UpdateMetadataRequest,
},
{
title: 'Update Metadata',
},
),
Type.Object(
{
type: Type.Literal(FlowOperationType.MOVE_BRANCH),
request: MoveBranchRequest,
},
),
Type.Object(
{
type: Type.Literal(FlowOperationType.SAVE_SAMPLE_DATA),
request: SaveSampleDataRequest,
},
),
Type.Object(
{
type: Type.Literal(FlowOperationType.UPDATE_MINUTES_SAVED),
request: UpdateMinutesSavedRequest,
},
{
title: 'Update Minutes Saved',
},
),
])
export type FlowOperationRequest = Static<typeof FlowOperationRequest>
export const flowOperations = {
getActionsForCopy: _getActionsForCopy,
getOperationsForPaste: _getOperationsForPaste,
apply(flowVersion: FlowVersion, operation: FlowOperationRequest): FlowVersion {
let clonedVersion: FlowVersion = JSON.parse(JSON.stringify(flowVersion))
switch (operation.type) {
case FlowOperationType.MOVE_ACTION: {
const operations: FlowOperationRequest[] = _moveAction(clonedVersion, operation.request)
operations.forEach((operation) => {
clonedVersion = flowOperations.apply(clonedVersion, operation)
})
clonedVersion = flowPieceUtil.makeFlowAutoUpgradable(clonedVersion)
break
}
case FlowOperationType.CHANGE_NAME:
clonedVersion.displayName = operation.request.displayName
break
case FlowOperationType.DUPLICATE_BRANCH: {
const operations = _duplicateBranch(operation.request.stepName, operation.request.branchIndex, clonedVersion)
operations.forEach((operation) => {
clonedVersion = flowOperations.apply(clonedVersion, operation)
})
break
}
case FlowOperationType.DUPLICATE_ACTION: {
const operations = _duplicateStep(operation.request.stepName, clonedVersion)
operations.forEach((operation) => {
clonedVersion = flowOperations.apply(clonedVersion, operation)
})
break
}
case FlowOperationType.LOCK_FLOW:
clonedVersion.state = FlowVersionState.LOCKED
break
case FlowOperationType.ADD_ACTION: {
clonedVersion = _addAction(clonedVersion, operation.request)
clonedVersion = flowPieceUtil.makeFlowAutoUpgradable(clonedVersion)
break
}
case FlowOperationType.DELETE_ACTION: {
clonedVersion = _deleteAction(clonedVersion, operation.request)
clonedVersion = flowPieceUtil.makeFlowAutoUpgradable(clonedVersion)
break
}
case FlowOperationType.UPDATE_TRIGGER: {
clonedVersion = _updateTrigger(clonedVersion, operation.request)
clonedVersion = flowPieceUtil.makeFlowAutoUpgradable(clonedVersion)
break
}
case FlowOperationType.ADD_BRANCH: {
clonedVersion = _addBranch(clonedVersion, operation.request)
clonedVersion = flowPieceUtil.makeFlowAutoUpgradable(clonedVersion)
break
}
case FlowOperationType.DELETE_BRANCH: {
clonedVersion = _deleteBranch(clonedVersion, operation.request)
clonedVersion = flowPieceUtil.makeFlowAutoUpgradable(clonedVersion)
break
}
case FlowOperationType.UPDATE_ACTION: {
clonedVersion = _updateAction(clonedVersion, operation.request)
clonedVersion = flowPieceUtil.makeFlowAutoUpgradable(clonedVersion)
break
}
case FlowOperationType.IMPORT_FLOW: {
const operations = _importFlow(clonedVersion, operation.request)
operations.forEach((operation) => {
clonedVersion = flowOperations.apply(clonedVersion, operation)
})
break
}
case FlowOperationType.SET_SKIP_ACTION: {
clonedVersion = _skipAction(clonedVersion, operation.request)
clonedVersion = flowPieceUtil.makeFlowAutoUpgradable(clonedVersion)
break
}
case FlowOperationType.MOVE_BRANCH: {
clonedVersion = _moveBranch(clonedVersion, operation.request)
clonedVersion = flowPieceUtil.makeFlowAutoUpgradable(clonedVersion)
break
}
default:
break
}
clonedVersion.valid = flowStructureUtil.getAllSteps(clonedVersion.trigger).every((step) => {
const isSkipped = step.type != FlowTriggerType.EMPTY && step.type != FlowTriggerType.PIECE && step.skip
return step.valid || isSkipped
})
return clonedVersion
},
}

View File

@@ -0,0 +1,41 @@
import { FlowVersion } from '../flow-version'
import { flowStructureUtil } from '../util/flow-structure-util'
import { _addAction } from './add-action'
import { _deleteAction } from './delete-action'
import { _getImportOperations } from './import-flow'
import { _updateAction } from './update-action'
import { FlowOperationRequest, FlowOperationType, MoveActionRequest } from './index'
export function _moveAction(flowVersion: FlowVersion, request: MoveActionRequest): FlowOperationRequest[] {
const sourceStep = flowStructureUtil.getActionOrThrow(request.name, flowVersion.trigger)
flowStructureUtil.getStepOrThrow(request.newParentStep, flowVersion.trigger)
const sourceStepWithoutNextAction = {
...sourceStep,
nextAction: undefined,
}
const deleteOperations: FlowOperationRequest[] = [
{
type: FlowOperationType.DELETE_ACTION,
request: {
names: [request.name],
},
},
]
const addOperations: FlowOperationRequest[] = [
{
type: FlowOperationType.ADD_ACTION,
request: {
action: sourceStepWithoutNextAction,
parentStep: request.newParentStep,
stepLocationRelativeToParent: request.stepLocationRelativeToNewParent,
branchIndex: request.branchIndex,
},
},
..._getImportOperations(sourceStepWithoutNextAction),
]
return [
...deleteOperations,
...addOperations,
]
}

View File

@@ -0,0 +1,29 @@
import { BranchExecutionType, FlowActionType } from '../actions/action'
import { FlowVersion } from '../flow-version'
import { flowStructureUtil } from '../util/flow-structure-util'
import { MoveBranchRequest } from '.'
const isIndexWithinBounds = (index: number, arrayLength: number) => index >= 0 && index < arrayLength
export function _moveBranch(flowVersion: FlowVersion, request: MoveBranchRequest): FlowVersion {
return flowStructureUtil.transferFlow(flowVersion, (stepToUpdate) => {
if (stepToUpdate.name !== request.stepName || stepToUpdate.type !== FlowActionType.ROUTER) {
return stepToUpdate
}
const routerStep = stepToUpdate
if (!isIndexWithinBounds(request.sourceBranchIndex, routerStep.settings.branches.length) || !isIndexWithinBounds(request.targetBranchIndex, routerStep.settings.branches.length) || request.sourceBranchIndex === request.targetBranchIndex) {
return stepToUpdate
}
if (routerStep.settings.branches[request.sourceBranchIndex].branchType === BranchExecutionType.FALLBACK || routerStep.settings.branches[request.targetBranchIndex].branchType === BranchExecutionType.FALLBACK) {
return stepToUpdate
}
const sourceBranch = routerStep.settings.branches[request.sourceBranchIndex]
routerStep.settings.branches.splice(request.sourceBranchIndex, 1)
routerStep.settings.branches.splice(request.targetBranchIndex, 0, sourceBranch)
const sourceBranchChildren = routerStep.children[request.sourceBranchIndex]
routerStep.children.splice(request.sourceBranchIndex, 1)
routerStep.children.splice(request.targetBranchIndex, 0, sourceBranchChildren)
return routerStep
})
}

View File

@@ -0,0 +1,60 @@
import { FlowAction } from '../actions/action'
import { FlowVersion } from '../flow-version'
import { flowStructureUtil } from '../util/flow-structure-util'
import { addActionUtils } from './add-action-util'
import { _getImportOperations } from './import-flow'
import { FlowOperationRequest, FlowOperationType, StepLocationRelativeToParent } from './index'
export type InsideBranchPasteLocation = {
branchIndex: number
stepLocationRelativeToParent: StepLocationRelativeToParent.INSIDE_BRANCH
parentStepName: string
}
export type OutsideBranchPasteLocation = {
parentStepName: string
stepLocationRelativeToParent:
| StepLocationRelativeToParent.AFTER
| StepLocationRelativeToParent.INSIDE_LOOP
}
export type PasteLocation = InsideBranchPasteLocation | OutsideBranchPasteLocation
export const _getOperationsForPaste = (
actions: FlowAction[],
flowVersion: FlowVersion,
pastingDetails: PasteLocation,
) => {
const newNamesMap = addActionUtils.mapToNewNames(flowVersion, actions)
const clonedActions: FlowAction[] = actions.map(action => flowStructureUtil.transferStep(action, (step: FlowAction) => {
return addActionUtils.clone(step, newNamesMap)
}) as FlowAction)
const operations: FlowOperationRequest[] = []
for (let i = 0; i < clonedActions.length; i++) {
if (i === 0) {
operations.push({
type: FlowOperationType.ADD_ACTION,
request: {
action: clonedActions[i],
parentStep: pastingDetails.parentStepName,
stepLocationRelativeToParent: pastingDetails.stepLocationRelativeToParent,
branchIndex: pastingDetails.stepLocationRelativeToParent === StepLocationRelativeToParent.INSIDE_BRANCH ? pastingDetails.branchIndex : undefined,
},
})
}
else {
operations.push({
type: FlowOperationType.ADD_ACTION,
request: {
action: clonedActions[i],
parentStep: clonedActions[i - 1].name,
stepLocationRelativeToParent: StepLocationRelativeToParent.AFTER,
},
})
}
const importOperations = _getImportOperations(clonedActions[i])
operations.push(...importOperations)
}
return operations
}

View File

@@ -0,0 +1,15 @@
import { FlowVersion } from '../flow-version'
import { flowStructureUtil } from '../util/flow-structure-util'
import { SkipActionRequest } from '.'
export function _skipAction(flowVersion: FlowVersion, request: SkipActionRequest): FlowVersion {
return flowStructureUtil.transferFlow(flowVersion, (stepToUpdate) => {
if (!request.names.includes(stepToUpdate.name)) {
return stepToUpdate
}
return {
...stepToUpdate,
skip: request.skip,
}
})
}

View File

@@ -0,0 +1,77 @@
import { TypeCompiler } from '@sinclair/typebox/compiler'
import { isNil } from '../../common'
import { FlowAction, FlowActionType, SingleActionSchema } from '../actions/action'
import { FlowVersion } from '../flow-version'
import { flowStructureUtil } from '../util/flow-structure-util'
import { UpdateActionRequest } from './index'
const actionSchemaValidator = TypeCompiler.Compile(SingleActionSchema)
function _updateAction(flowVersion: FlowVersion, request: UpdateActionRequest): FlowVersion {
return flowStructureUtil.transferFlow(flowVersion, (stepToUpdate) => {
if (stepToUpdate.name !== request.name) {
return stepToUpdate
}
const baseProps: Omit<FlowAction, 'type'> = {
displayName: request.displayName,
name: request.name,
valid: false,
skip: request.skip,
settings: {
...stepToUpdate.settings,
customLogoUrl: request.settings.customLogoUrl,
},
}
let updatedAction: FlowAction
switch (request.type) {
case FlowActionType.CODE: {
updatedAction = {
...baseProps,
settings: request.settings,
type: FlowActionType.CODE,
nextAction: stepToUpdate.nextAction,
}
break
}
case FlowActionType.PIECE: {
updatedAction = {
...baseProps,
settings: request.settings,
type: FlowActionType.PIECE,
nextAction: stepToUpdate.nextAction,
}
break
}
case FlowActionType.LOOP_ON_ITEMS: {
updatedAction = {
...baseProps,
settings: request.settings,
type: FlowActionType.LOOP_ON_ITEMS,
firstLoopAction: 'firstLoopAction' in stepToUpdate ? stepToUpdate.firstLoopAction : undefined,
nextAction: stepToUpdate.nextAction,
}
break
}
case FlowActionType.ROUTER: {
updatedAction = {
...baseProps,
settings: request.settings,
type: FlowActionType.ROUTER,
nextAction: stepToUpdate.nextAction,
children: 'children' in stepToUpdate ? stepToUpdate.children : [null, null],
}
break
}
}
const valid = (isNil(request.valid) ? true : request.valid) && actionSchemaValidator.Check(updatedAction)
return {
...updatedAction,
valid,
}
})
}
export { _updateAction }

View File

@@ -0,0 +1,53 @@
import { TypeCompiler } from '@sinclair/typebox/compiler'
import { isNil } from '../../common'
import { FlowAction } from '../actions/action'
import { FlowVersion } from '../flow-version'
import { FlowTrigger, FlowTriggerType } from '../triggers/trigger'
import { flowStructureUtil } from '../util/flow-structure-util'
import { UpdateTriggerRequest } from '.'
const triggerSchemaValidation = TypeCompiler.Compile(FlowTrigger)
function createTrigger(name: string, request: UpdateTriggerRequest, nextAction: FlowAction | undefined): FlowTrigger {
const baseProperties = {
displayName: request.displayName,
name,
valid: false,
nextAction,
}
let trigger: FlowTrigger
switch (request.type) {
case FlowTriggerType.EMPTY:
trigger = {
...baseProperties,
type: FlowTriggerType.EMPTY,
settings: request.settings,
}
break
case FlowTriggerType.PIECE:
trigger = {
...baseProperties,
type: FlowTriggerType.PIECE,
settings: request.settings,
}
break
}
const valid = (isNil(request.valid) ? true : request.valid) && triggerSchemaValidation.Check(trigger)
return {
...trigger,
valid,
}
}
function _updateTrigger(flowVersion: FlowVersion, request: UpdateTriggerRequest): FlowVersion {
const trigger = flowStructureUtil.getStepOrThrow(request.name, flowVersion.trigger)
const updatedTrigger = createTrigger(request.name, request, trigger.nextAction)
return flowStructureUtil.transferFlow(flowVersion, (parentStep) => {
if (parentStep.name === request.name) {
return updatedTrigger
}
return parentStep
})
}
export { _updateTrigger }

View File

@@ -0,0 +1 @@
export * from './property'

View File

@@ -0,0 +1,12 @@
import { Static, Type } from '@sinclair/typebox'
export enum PropertyExecutionType {
MANUAL = 'MANUAL',
DYNAMIC = 'DYNAMIC',
}
export const PropertySettings = Type.Object({
type: Type.Enum(PropertyExecutionType),
schema: Type.Optional(Type.Any()),
})
export type PropertySettings = Static<typeof PropertySettings>

View File

@@ -0,0 +1,75 @@
import { Pick, Static, Type } from '@sinclair/typebox'
import { File } from '../../file'
export enum SampleDataFileType {
INPUT = 'INPUT',
OUTPUT = 'OUTPUT',
}
export const DATA_TYPE_KEY_IN_FILE_METADATA = 'dataType'
export enum SampleDataDataType {
JSON = 'JSON',
STRING = 'STRING',
}
export const SaveSampleDataRequest = Type.Object({
stepName: Type.String(),
payload: Type.Unknown(),
type: Type.Enum(SampleDataFileType),
dataType: Type.Enum(SampleDataDataType),
})
export type SaveSampleDataRequest = Static<typeof SaveSampleDataRequest>
export const GetSampleDataRequest = Type.Object({
flowId: Type.String(),
flowVersionId: Type.String(),
stepName: Type.String(),
projectId: Type.String(),
type: Type.Enum(SampleDataFileType),
})
export type GetSampleDataRequest = Static<typeof GetSampleDataRequest>
export const CreateStepRunRequestBody = Type.Object({
flowVersionId: Type.String(),
stepName: Type.String(),
})
export type CreateStepRunRequestBody = Static<typeof CreateStepRunRequestBody>
export const StepRunResponse = Type.Object({
runId: Type.String(),
success: Type.Boolean(),
input: Type.Unknown(),
output: Type.Unknown(),
sampleDataFileId: Type.Optional(Type.String()),
sampleDataInputFileId: Type.Optional(Type.String()),
standardError: Type.String(),
standardOutput: Type.String(),
})
export type StepRunResponse = Static<typeof StepRunResponse>
export const StepExecutionPath = Type.Array(Type.Tuple([Type.String(), Type.Number()]))
export type StepExecutionPath = Static<typeof StepExecutionPath>
export const SampleDataSetting = Type.Object(
{
sampleDataFileId: Type.Optional(Type.String()),
sampleDataInputFileId: Type.Optional(Type.String()),
lastTestDate: Type.Optional(Type.String()),
},
{
additionalProperties: true,
},
)
export type SampleDataSettings = Static<typeof SampleDataSetting>
export const DEFAULT_SAMPLE_DATA_SETTINGS: SampleDataSettings = {
sampleDataFileId: undefined,
sampleDataInputFileId: undefined,
}
export const SaveSampleDataResponse = Pick(File, ['id', 'size', 'type'])
export type SaveSampleDataResponse = Static<typeof SaveSampleDataResponse>

View File

@@ -0,0 +1,19 @@
import { Static, Type } from '@sinclair/typebox'
import { ApMultipartFile } from '../../common'
export const StepFileUpsertRequest = Type.Object({
flowId: Type.String(),
stepName: Type.String(),
file: Type.Optional(Type.Pick(ApMultipartFile, ['data'])),
contentLength: Type.Number(),
fileName: Type.String(),
})
export type StepFileUpsert = Static<typeof StepFileUpsertRequest>
export const StepFileUpsertResponse = Type.Object({
uploadUrl: Type.Optional(Type.String()),
url: Type.String(),
})
export type StepFileUpsertResponse = Static<typeof StepFileUpsertResponse>

View File

@@ -0,0 +1,22 @@
import { Static, Type } from '@sinclair/typebox'
import { ApId } from '../common/id-generator'
export enum TriggerTestStrategy {
SIMULATION = 'SIMULATION',
TEST_FUNCTION = 'TEST_FUNCTION',
}
export const TestTriggerRequestBody = Type.Object({
flowId: ApId,
flowVersionId: ApId,
testStrategy: Type.Enum(TriggerTestStrategy),
})
export type TestTriggerRequestBody = Static<typeof TestTriggerRequestBody>
export const CancelTestTriggerRequestBody = Type.Object({
flowId: ApId,
})
export type CancelTestTriggerRequestBody = Static<typeof CancelTestTriggerRequestBody>

View File

@@ -0,0 +1,23 @@
import { Static, Type } from '@sinclair/typebox'
import { BaseModel } from '../../../common'
export type TriggerEventId = string
export const TriggerEvent = Type.Object({
id: Type.String(),
projectId: Type.String(),
flowId: Type.String(),
sourceName: Type.String(),
fileId: Type.String(),
})
export type TriggerEvent = Static<typeof TriggerEvent> & BaseModel<TriggerEventId>
export const TriggerEventWithPayload = Type.Composite([
TriggerEvent,
Type.Object({
payload: Type.Unknown(),
}),
])
export type TriggerEventWithPayload = Static<typeof TriggerEventWithPayload>

View File

@@ -0,0 +1,21 @@
import { Static, Type } from '@sinclair/typebox'
import { Cursor } from '../../../common/seek-page'
import { FlowId } from '../../flow'
export const ListTriggerEventsRequest = Type.Object({
flowId: Type.String({}),
limit: Type.Optional(Type.Number({})),
cursor: Type.Optional(Type.String({})),
})
export type ListTriggerEventsRequest = Omit<Static<typeof ListTriggerEventsRequest>, 'flowId' | 'cursor'> & {
flowId: FlowId
cursor: Cursor | undefined
}
export const SaveTriggerEventRequest = Type.Object({
flowId: Type.String({}),
mockData: Type.Unknown(),
})
export type SaveTriggerEventRequest = Static<typeof SaveTriggerEventRequest>

View File

@@ -0,0 +1,21 @@
import { Static, Type } from '@sinclair/typebox'
export enum TriggerRunStatus {
COMPLETED = 'COMPLETED',
FAILED = 'FAILED',
INTERNAL_ERROR = 'INTERNAL_ERROR',
TIMED_OUT = 'TIMED_OUT',
}
export const TriggerStatusReport = Type.Object({
pieces: Type.Record(Type.String(), Type.Object({
dailyStats: Type.Record(Type.String(), Type.Object({
success: Type.Number(),
failure: Type.Number(),
})),
totalRuns: Type.Number(),
})),
})
export type TriggerStatusReport = Static<typeof TriggerStatusReport>

View File

@@ -0,0 +1,66 @@
import { Static, Type } from '@sinclair/typebox'
import { VersionType } from '../../pieces'
import { CodeActionSettings, LoopOnItemsActionSettings, PieceActionSettings, RouterActionSettings } from '../actions/action'
import { PropertySettings } from '../properties'
import { SampleDataSetting } from '../sample-data'
export const AUTHENTICATION_PROPERTY_NAME = 'auth'
export const PieceTriggerSettings = Type.Object({
sampleData: Type.Optional(SampleDataSetting),
propertySettings: Type.Record(Type.String(), PropertySettings),
customLogoUrl: Type.Optional(Type.String()),
pieceName: Type.String({}),
pieceVersion: VersionType,
triggerName: Type.Optional(Type.String({})),
input: Type.Record(Type.String({}), Type.Any()),
})
export type PieceTriggerSettings = Static<typeof PieceTriggerSettings>
export enum FlowTriggerType {
EMPTY = 'EMPTY',
PIECE = 'PIECE_TRIGGER',
}
const commonProps = {
name: Type.String({}),
valid: Type.Boolean({}),
displayName: Type.String({}),
nextAction: Type.Optional(Type.Any()),
}
export const EmptyTrigger = Type.Object({
...commonProps,
type: Type.Literal(FlowTriggerType.EMPTY),
settings: Type.Any(),
})
export type EmptyTrigger = Static<typeof EmptyTrigger>
export const PieceTrigger = Type.Object({
...commonProps,
type: Type.Literal(FlowTriggerType.PIECE),
settings: PieceTriggerSettings,
})
export type PieceTrigger = Static<typeof PieceTrigger>
export const FlowTrigger = Type.Union([
PieceTrigger,
EmptyTrigger,
])
export type FlowTrigger = Static<typeof FlowTrigger>
export type StepSettings =
| CodeActionSettings
| PieceActionSettings
| PieceTriggerSettings
| RouterActionSettings
| LoopOnItemsActionSettings

View File

@@ -0,0 +1,47 @@
import semver from 'semver'
import { FlowActionType } from '../actions/action'
import { FlowVersion } from '../flow-version'
import { FlowTrigger, FlowTriggerType } from '../triggers/trigger'
import { flowStructureUtil, Step } from '../util/flow-structure-util'
export const flowPieceUtil = {
makeFlowAutoUpgradable(flowVersion: FlowVersion): FlowVersion {
return flowStructureUtil.transferFlow(flowVersion, (step) => {
if (step.name !== step.name) {
return step
}
const clonedStep: Step = JSON.parse(JSON.stringify(step))
switch (step.type) {
case FlowActionType.PIECE:
case FlowTriggerType.PIECE: {
const { pieceVersion } = step.settings
clonedStep.settings.pieceVersion = flowPieceUtil.getMostRecentPatchVersion(pieceVersion)
break
}
default:
break
}
return clonedStep
})
},
getExactVersion(pieceVersion: string): string {
if (pieceVersion.startsWith('^') || pieceVersion.startsWith('~')) {
return pieceVersion.slice(1)
}
return pieceVersion
},
getUsedPieces(trigger: FlowTrigger): string[] {
return flowStructureUtil.getAllSteps(trigger)
.filter((step) => step.type === FlowActionType.PIECE || step.type === FlowTriggerType.PIECE)
.map((step) => step.settings.pieceName)
},
getMostRecentPatchVersion(pieceVersion: string): string {
if (pieceVersion.startsWith('^') || pieceVersion.startsWith('~')) {
return pieceVersion
}
if (semver.valid(pieceVersion) && semver.lt(pieceVersion, '1.0.0')) {
return `~${pieceVersion}`
}
return `^${pieceVersion}`
},
}

View File

@@ -0,0 +1,267 @@
import { isNil } from '../../common'
import { ActivepiecesError, ErrorCode } from '../../common/activepieces-error'
import { BranchCondition, BranchExecutionType, emptyCondition, FlowAction, FlowActionType, LoopOnItemsAction, RouterAction } from '../actions/action'
import { FlowVersion } from '../flow-version'
import { FlowTrigger, FlowTriggerType } from '../triggers/trigger'
export const AI_PIECE_NAME = '@activepieces/piece-ai'
export type Step = FlowAction | FlowTrigger
type StepWithIndex = Step & {
dfsIndex: number
}
function isAction(type: FlowActionType | FlowTriggerType | undefined): type is FlowActionType {
return Object.entries(FlowActionType).some(([, value]) => value === type)
}
function isTrigger(type: FlowActionType | FlowTriggerType | undefined): type is FlowTriggerType {
return Object.entries(FlowTriggerType).some(([, value]) => value === type)
}
function getActionOrThrow(name: string, flowRoot: Step): FlowAction {
const step = getStepOrThrow(name, flowRoot)
if (!isAction(step.type)) {
throw new ActivepiecesError({
code: ErrorCode.STEP_NOT_FOUND,
params: {
stepName: name,
},
})
}
return step as FlowAction
}
function getTriggerOrThrow(name: string, flowRoot: Step): FlowTrigger {
const step = getStepOrThrow(name, flowRoot)
if (!isTrigger(step.type)) {
throw new ActivepiecesError({
code: ErrorCode.STEP_NOT_FOUND,
params: {
stepName: name,
},
})
}
return step as FlowTrigger
}
function getStep(name: string, flowRoot: Step): Step | undefined {
return getAllSteps(flowRoot).find((step) => step.name === name)
}
function getStepOrThrow(name: string, flowRoot: Step): Step {
const step = getStep(name, flowRoot)
if (isNil(step)) {
throw new ActivepiecesError({
code: ErrorCode.STEP_NOT_FOUND,
params: {
stepName: name,
},
})
}
return step
}
function transferStep<T extends Step>(
step: Step,
transferFunction: (step: T) => T,
): Step {
const updatedStep = transferFunction(step as T)
switch (updatedStep.type) {
case FlowActionType.LOOP_ON_ITEMS: {
const { firstLoopAction } = updatedStep
if (firstLoopAction) {
updatedStep.firstLoopAction = transferStep(
firstLoopAction,
transferFunction,
) as FlowAction
}
break
}
case FlowActionType.ROUTER: {
const { children } = updatedStep
if (children) {
updatedStep.children = children.map((child) =>
child ? (transferStep(child, transferFunction) as FlowAction) : null,
)
}
break
}
default:
break
}
if (updatedStep.nextAction) {
updatedStep.nextAction = transferStep(
updatedStep.nextAction,
transferFunction,
) as FlowAction
}
return updatedStep
}
function transferFlow<T extends Step>(
flowVersion: FlowVersion,
transferFunction: (step: T) => T,
): FlowVersion {
const clonedFlow = JSON.parse(JSON.stringify(flowVersion))
clonedFlow.trigger = transferStep(
clonedFlow.trigger,
transferFunction,
) as FlowTrigger
return clonedFlow
}
function getAllSteps(step: Step): Step[] {
const steps: Step[] = []
transferStep(step, (currentStep) => {
steps.push(currentStep)
return currentStep
})
return steps
}
const createBranch = (branchName: string, conditions: BranchCondition[][] | undefined) => {
return {
conditions: conditions ?? [[emptyCondition]],
branchType: BranchExecutionType.CONDITION,
branchName,
}
}
function findPathToStep(trigger: FlowTrigger, targetStepName: string): StepWithIndex[] {
const steps = flowStructureUtil.getAllSteps(trigger).map((step, dfsIndex) => ({
...step,
dfsIndex,
}))
return steps
.filter((step) => {
const steps = flowStructureUtil.getAllSteps(step)
return steps.some((s) => s.name === targetStepName)
})
.filter((step) => step.name !== targetStepName)
}
function getAllChildSteps(action: LoopOnItemsAction | RouterAction): Step[] {
return getAllSteps({
...action,
nextAction: undefined,
})
}
function isChildOf(parent: Step, childStepName: string): boolean {
switch (parent.type) {
case FlowActionType.ROUTER:
case FlowActionType.LOOP_ON_ITEMS: {
const children = getAllChildSteps(parent)
return children.findIndex((c) => c.name === childStepName) > -1
}
default:
break
}
return false
}
const findUnusedNames = (source: FlowTrigger | string[], count = 1) => {
const names = Array.isArray(source) ? source : flowStructureUtil.getAllSteps(source).map((f) => f.name)
const unusedNames = []
for (let i = 1; i <= count; i++) {
const name = findUnusedName(names)
unusedNames.push(name)
names.push(name)
}
return unusedNames
}
const findUnusedName = (source: FlowTrigger | string[]) => {
const names = Array.isArray(source) ? source : flowStructureUtil.getAllSteps(source).map((f) => f.name)
let index = 1
let name = 'step_1'
while (names.includes(name)) {
index++
name = 'step_' + index
}
return name
}
function getAllNextActionsWithoutChildren(start: Step): Step[] {
const actions: Step[] = []
let currentAction = start.nextAction
while (!isNil(currentAction)) {
actions.push(currentAction)
currentAction = currentAction.nextAction
}
return actions
}
function extractConnectionIdsFromAuth(auth: string): string[] {
const match = auth.match(/{{connections\['([^']*(?:'\s*,\s*'[^']*)*)'\]}}/)
if (!match || !match[1]) {
return []
}
return match[1].split(/'\s*,\s*'/).map(id => id.trim())
}
function extractAgentIds(flowVersion: FlowVersion): string[] {
const getExternalAgentId = (action: Step) => {
if (isAgentPiece(action) && 'agentId' in action.settings.input) {
return action.settings.input.agentId
}
return null
}
return flowStructureUtil.getAllSteps(flowVersion.trigger).map(step => getExternalAgentId(step)).filter(step => step !== null && step !== '')
}
function isAgentPiece(action: Step) {
return (
action.type === FlowActionType.PIECE && action.settings.pieceName === AI_PIECE_NAME
)
}
function extractConnectionIds(flowVersion: FlowVersion): string[] {
const triggerAuthIds = flowVersion.trigger.settings?.input?.auth
? extractConnectionIdsFromAuth(flowVersion.trigger.settings.input.auth)
: []
const stepAuthIds = flowStructureUtil
.getAllSteps(flowVersion.trigger)
.flatMap(step =>
step.settings?.input?.auth
? extractConnectionIdsFromAuth(step.settings.input.auth)
: [],
)
return Array.from(new Set([...triggerAuthIds, ...stepAuthIds]))
}
export const flowStructureUtil = {
isTrigger,
isAction,
getAllSteps,
transferStep,
transferFlow,
getStepOrThrow,
getActionOrThrow,
getTriggerOrThrow,
getStep,
createBranch,
findPathToStep,
isChildOf,
findUnusedName,
findUnusedNames,
getAllNextActionsWithoutChildren,
getAllChildSteps,
extractConnectionIds,
isAgentPiece,
extractAgentIds,
}

View File

@@ -0,0 +1,58 @@
import { Static, Type } from '@sinclair/typebox'
const FileResponseInterfaceV1 = Type.Object({
base64Url: Type.String(),
fileName: Type.String(),
extension: Type.Optional(Type.String()),
})
const FileResponseInterfaceV2 = Type.Object({
mimeType: Type.String(),
url: Type.String(),
fileName: Type.Optional(Type.String()),
})
export const FileResponseInterface = Type.Union([FileResponseInterfaceV1, FileResponseInterfaceV2])
export type FileResponseInterface = Static<typeof FileResponseInterface>
export enum HumanInputFormResultTypes {
FILE = 'file',
MARKDOWN = 'markdown',
}
export function createKeyForFormInput(displayName: string) {
const inputKey = displayName
.toLowerCase()
.replace(/\s+(\w)/g, (_, letter) => letter.toUpperCase())
.replace(/^(.)/, letter => letter.toLowerCase())
/**We do this because react form inputs must not contain quotes */
return inputKey.replaceAll(/[\\"''\n\r\t]/g, '')
}
export const HumanInputFormResult = Type.Union([
Type.Object({
type: Type.Literal(HumanInputFormResultTypes.FILE),
value: FileResponseInterface,
}),
Type.Object({
type: Type.Literal(HumanInputFormResultTypes.MARKDOWN),
value: Type.String(),
files: Type.Optional(Type.Array(FileResponseInterface)),
}),
])
export type HumanInputFormResult = Static<typeof HumanInputFormResult>
export const ChatFormResponse = Type.Object({
sessionId: Type.String(),
message: Type.String(),
files: Type.Optional(Type.Array(Type.String())),
})
export type ChatFormResponse = Static<typeof ChatFormResponse>

View File

@@ -0,0 +1,9 @@
import { Static, Type } from '@sinclair/typebox'
export const GetSystemHealthChecksResponse = Type.Object({
cpu: Type.Boolean(),
disk: Type.Boolean(),
ram: Type.Boolean(),
})
export type GetSystemHealthChecksResponse = Static<typeof GetSystemHealthChecksResponse>

View File

@@ -0,0 +1,67 @@
import { Static, Type } from '@sinclair/typebox'
import { BaseModelSchema, Nullable, NullableEnum } from '../common'
import { ProjectRole } from '../project-role/project-role'
import { PlatformRole } from '../user/index'
export enum InvitationType {
PLATFORM = 'PLATFORM',
PROJECT = 'PROJECT',
}
export enum InvitationStatus {
PENDING = 'PENDING',
ACCEPTED = 'ACCEPTED',
}
export const UserInvitation = Type.Object({
...BaseModelSchema,
email: Type.String(),
status: Type.Enum(InvitationStatus),
type: Type.Enum(InvitationType),
platformId: Type.String(),
platformRole: NullableEnum(Type.Enum(PlatformRole)),
projectId: Nullable(Type.String()),
projectRoleId: Nullable(Type.String()),
projectRole: Nullable(ProjectRole),
})
export type UserInvitation = Static<typeof UserInvitation>
export const UserInvitationWithLink = Type.Composite([UserInvitation, Type.Object({
link: Type.Optional(Type.String()),
})])
export type UserInvitationWithLink = Static<typeof UserInvitationWithLink>
export const SendUserInvitationRequest = Type.Union([
Type.Object({
type: Type.Literal(InvitationType.PROJECT),
email: Type.String(),
projectId: Type.String(),
projectRole: Type.String(),
}),
Type.Object({
type: Type.Literal(InvitationType.PLATFORM),
email: Type.String(),
platformRole: Type.Enum(PlatformRole),
}),
])
export type SendUserInvitationRequest = Static<typeof SendUserInvitationRequest>
export const AcceptUserInvitationRequest = Type.Object({
invitationToken: Type.String(),
})
export type AcceptUserInvitationRequest = Static<typeof AcceptUserInvitationRequest>
export const ListUserInvitationsRequest = Type.Object({
limit: Type.Optional(Type.Number()),
cursor: Type.Optional(Type.String()),
type: Type.Enum(InvitationType),
projectId: Nullable(Type.String()),
status: Type.Optional(Type.Enum(InvitationStatus)),
})
export type ListUserInvitationsRequest = Static<typeof ListUserInvitationsRequest>

View File

@@ -0,0 +1,49 @@
import { Static, Type } from '@sinclair/typebox'
export const VerifyLicenseKeyRequestBody = Type.Object({
licenseKey: Type.String(),
platformId: Type.String(),
})
export type VerifyLicenseKeyRequestBody = Static<typeof VerifyLicenseKeyRequestBody>
export const LicenseKeyEntity = Type.Object({
id: Type.String(),
email: Type.String(),
expiresAt: Type.String(),
activatedAt: Type.String(),
createdAt: Type.String(),
key: Type.String(),
ssoEnabled: Type.Boolean(),
environmentsEnabled: Type.Boolean(),
showPoweredBy: Type.Boolean(),
embeddingEnabled: Type.Boolean(),
auditLogEnabled: Type.Boolean(),
customAppearanceEnabled: Type.Boolean(),
manageProjectsEnabled: Type.Boolean(),
managePiecesEnabled: Type.Boolean(),
manageTemplatesEnabled: Type.Boolean(),
apiKeysEnabled: Type.Boolean(),
customDomainsEnabled: Type.Boolean(),
projectRolesEnabled: Type.Boolean(),
analyticsEnabled: Type.Boolean(),
globalConnectionsEnabled: Type.Boolean(),
customRolesEnabled: Type.Boolean(),
agentsEnabled: Type.Boolean(),
tablesEnabled: Type.Boolean(),
todosEnabled: Type.Boolean(),
mcpsEnabled: Type.Boolean(),
})
export const CreateTrialLicenseKeyRequestBody = Type.Composite([Type.Object({
email: Type.String(),
companyName: Type.String(),
goal: Type.String(),
keyType: Type.Optional(Type.String()),
}), Type.Omit(LicenseKeyEntity, ['id', 'email', 'expiresAt', 'activatedAt', 'key', 'createdAt'])])
export type CreateTrialLicenseKeyRequestBody = Static<typeof CreateTrialLicenseKeyRequestBody>
export type LicenseKeyEntity = Static<typeof LicenseKeyEntity>

View File

@@ -0,0 +1,2 @@
export * from './mcp'
export * from './pieces/mcp-piece'

View File

@@ -0,0 +1,34 @@
import { Static, Type } from '@sinclair/typebox'
import { BaseModelSchema } from '../common'
import { ApId } from '../common/id-generator'
import { PopulatedFlow } from '../flows/flow'
export type McpId = ApId
export const MCP_TRIGGER_PIECE_NAME = '@activepieces/piece-mcp'
export enum McpServerStatus {
ENABLED = 'ENABLED',
DISABLED = 'DISABLED',
}
export const McpServer = Type.Object({
...BaseModelSchema,
projectId: ApId,
status: Type.Enum(McpServerStatus),
token: ApId,
})
export const PopulatedMcpServer = Type.Composite([McpServer, Type.Object({
flows: Type.Array(PopulatedFlow),
})])
export type PopulatedMcpServer = Static<typeof PopulatedMcpServer>
export type McpServer = Static<typeof McpServer>
export const UpdateMcpServerRequest = Type.Object({
status: Type.Enum(McpServerStatus),
})
export type UpdateMcpServerRequest = Static<typeof UpdateMcpServerRequest>

Some files were not shown because too many files have changed in this diff Show More