@flowlab/all
Version:
A cool library focusing on handling various flows
1,476 lines (1,336 loc) • 92.3 kB
Markdown
``` Typescript
// --- src/types/runtime.ts ---
/**
* @module types/runtime
* 定义工作流运行时相关的类型,如状态、上下文、历史记录等。
*/
/**
* 节点或工作流的执行状态枚举。
*/
export enum nodeStatus {
PENDING = 'PENDING', // 待处理:步骤或工作流已创建但尚未开始执行。
RUNNING = 'RUNNING', // 运行中:步骤或工作流当前正在执行。
COMPLETED = 'COMPLETED', // 已完成:步骤或工作流成功执行完毕。
FAILED = 'FAILED', // 已失败:步骤或工作流执行过程中遇到错误导致失败。
TIMEOUT = 'TIMEOUT', // 已超时:步骤执行时间超过了配置的超时限制。
SKIPPED = 'SKIPPED', // 已跳过:由于条件不满足或其他原因,步骤被跳过执行。
CANCELLED = 'CANCELLED', // 已取消:工作流或步骤被人为或通过 API 取消。
COMPENSATING = 'COMPENSATING', // 补偿中:正在执行补偿逻辑(用于回滚操作)。(来自 Root 设计)
COMPENSATED = 'COMPENSATED', // 已补偿:补偿逻辑已成功执行。(来自 Root 设计)
}
/**
* 工作流实例的运行时上下文接口。
* 包含工作流执行期间的所有状态和信息。
*/
export interface IWorkflowContext {
readonly workflowId: string; // 工作流实例的唯一标识符 (运行时生成)。
readonly definitionId: string; // 所使用的工流定义的 ID。
readonly definitionName?: string; // 工作流定义的名称 (可选)。
readonly input: Readonly<Record<string, any>>; // 工作流的初始输入数据 (只读)。
status: NodeStatus; // 工作流当前的整体状态。
variables: Record<string, any>; // 工作流范围内的变量,用于在步骤间传递和存储状态 (可变)。
output?: Record<string, any>; // 工作流最终的输出结果。
startTime: Date; // 工作流实例开始执行的时间。
endTime?: Date; // 工作流实例结束执行的时间。
history: ReadonlyArray<IExecutionRecord>; // 工作流执行历史记录 (只读数组,但内容可追加)。
logs: string[]; // 工作流范围内的简单日志记录 (可追加)。
// --- 可选的上下文信息 (来自 Root/Combined 设计) ---
tenantId?: string; // 租户 ID,用于多租户场景。
userId?: string; // 执行用户的 ID。
userRole?: string; // 执行用户的角色,用于权限控制。
traceId?: string; // 分布式追踪 ID。
// --- 允许扩展 ---
[key: string]: any; // 允许附加其他自定义上下文信息。
}
/**
* 节点执行时的上下文接口。
* 提供节点执行所需的特定信息和操作。
*/
export interface INodeContext {
readonly workflowContext: IWorkflowContext; // 对父工作流上下文的只读引用。
readonly stepId: string; // 当前执行的步骤在其工作流定义中的 ID。
readonly nodeId: string; // 当前步骤所关联的已注册节点的 ID/名称。
readonly input: Readonly<Record<string, any>>; // 经过 inputMapping 解析后,传递给当前节点的输入数据 (只读)。
status: NodeStatus; // 当前节点本次执行的状态。
output?: Record<string, any>; // 节点执行产生的输出数据 (由节点逻辑设置)。
startTime: Date; // 节点本次执行的开始时间。
endTime?: Date; // 节点本次执行的结束时间。
retries: number; // 当前节点的重试次数 (从 0 开始)。
error?: Error; // 如果节点执行失败,存储错误对象。
logs: { timestamp: Date; message: string }[]; // 节点范围内的结构化日志记录 (可追加)。
// --- 便捷辅助方法 ---
/**
* 获取工作流变量的值。
* @param name 变量名称。
* @returns 变量的值,如果不存在则返回 undefined。
*/
getVariable<T = any>(name: string): T | undefined;
/**
* 设置工作流变量的值。
* @param name 变量名称。
* @param value 要设置的值。
*/
setVariable(name:string, value: any): void;
/**
* 记录一条日志到当前节点和工作流的日志中。
* @param message 日志消息。
*/
log(message: string): void;
}
/**
* 单个步骤的执行记录接口。
* 用于构建工作流的执行历史 (history)。
*/
export interface IExecutionRecord {
stepId: string; // 执行的步骤 ID。
nodeId?: string; // 执行的节点 ID (非任务步骤可能没有)。
status: NodeStatus; // 该步骤执行的最终状态。
startTime: Date; // 步骤开始执行的时间。
endTime?: Date; // 步骤结束执行的时间。
input?: Record<string, any>; // (可选) 记录传递给该步骤的输入。
output?: Record<string, any>; // (可选) 记录该步骤产生的输出。
error?: string; // 如果执行失败,记录错误消息。
retries?: number; // 执行时的重试次数。
}
// --- src/types/config.ts ---
/**
* @module types/config
* 定义工作流和节点配置相关的类型。
*/
import { IWorkflowContext, INodeContext } from './runtime'; // 引入运行时类型
// --- 节点定义相关类型 ---
/**
* 节点执行函数的类型定义。
* 这是注册简单功能节点的方式。
* @param context 当前节点的执行上下文,包含工作流变量、输入、日志方法等。
* @param input (可选便利参数) 解析后的节点输入数据。推荐优先使用 context.input。
* @returns Promise,可以返回节点输出 (会设置到 context.output),或返回 void (如果直接操作 context.output)。
*/
export type NodeFunction<Input = any, Output = any> = (
context: INodeContext,
input?: Input // 为简单函数提供便利,但建议主要通过 context.input 访问
) => Promise<Output | void>;
/**
* 节点元数据接口。
* 用于描述一个已注册的节点。
*/
export interface NodeMetadata {
id: string; // 节点的唯一标识符/名称,注册和引用时使用。
description?: string; // 节点功能的可选描述。
inputSchema?: object; // (可选) 用于验证节点输入的 JSON Schema。
outputSchema?: object;// (可选) 用于验证节点输出的 JSON Schema。
}
// --- 步骤配置相关类型 ---
/**
* 工作流中步骤的类型枚举。
*/
export enum StepType {
TASK = 'TASK', // 任务步骤:执行一个已注册的节点。
CONDITION = 'CONDITION', // 条件步骤:根据条件选择下一个执行分支。
PARALLEL = 'PARALLEL', // 并行步骤:同时执行多个子步骤或分支。
SUB_WORKFLOW = 'SUB_WORKFLOW', // 子工作流步骤:调用并执行另一个已定义的工作流。
EVENT_TRIGGER = 'EVENT_TRIGGER', // 事件触发步骤:执行时触发一个外部事件。(来自 Root)
EVENT_LISTENER = 'EVENT_LISTENER', // 事件监听步骤:作为工作流的起点,等待特定事件发生。(来自 Root)
// 可以根据需要扩展其他类型,如 WAIT (等待特定时间或条件)、MANUAL_APPROVAL (人工审批) 等。
}
/**
* 步骤重试配置接口。
*/
export interface RetryOptions {
maxRetries: number; // 最大重试次数。
delayMs: number; // 每次重试之间的基础延迟时间(毫秒)。
backoffFactor?: number; // (可选) 指数退避因子,每次重试延迟 = delayMs * (backoffFactor ^ retryCount)。
}
/**
* 所有步骤类型共有的基础配置接口。
*/
export interface BaseStepConfig {
id: string; // 步骤在工作流定义内的唯一标识符。
description?: string; // 步骤的可选描述。
nextStepId?: string; // 默认的下一个步骤 ID (通常用于 TASK, SUB_WORKFLOW 等线性步骤)。
// --- 来自 Root 设计的高级特性 ---
timeoutMs?: number; // 步骤执行的超时时间(毫秒)。
retryOptions?: RetryOptions; // 步骤失败时的自动重试配置。
compensateOnFailure?: boolean | string; // 步骤失败时是否触发补偿,或指定补偿步骤的 ID。
requiredRoles?: string[]; // (可选) 执行此步骤所需的用户角色列表。
// --- 输入/输出映射 (来自 Root 设计,非常关键) ---
/**
* 输入映射:定义如何将工作流上下文中的数据映射到当前步骤(节点)的输入。
* 键 (key) 是节点期望的输入字段名。
* 值 (value) 是数据来源路径 (点表示法),例如:
* - 'variables.userId' (来自工作流变量)
* - 'input.orderId' (来自工作流初始输入)
* - 'steps.previousStepId.output.fieldName' (来自先前步骤的输出 - 需要引擎支持缓存或解析)
* 映射后的值会覆盖 config 中定义的静态 input。
* @example { nodeInputField: 'variables.someData', user: 'input.requestUser' }
*/
inputMapping?: Record<string, string>;
/**
* 输出映射:定义如何将当前步骤(节点)的输出数据映射回工作流变量。
* 键 (key) 是目标工作流变量的路径 (点表示法),目前主要支持 'variables.xxx'。
* 值 (value) 是当前节点输出对象 (context.output) 中的字段名。
* @example { 'variables.resultData': 'output.processedValue', 'variables.user.name': 'output.customerName' }
*/
outputMapping?: Record<string, string>;
}
// --- 具体的步骤配置类型 ---
/**
* 任务步骤 (Task Step) 的配置接口。
* 用于执行一个已注册的节点。
*/
export interface TaskStepConfig extends BaseStepConfig {
type: StepType.TASK;
nodeId: string; // 要执行的已注册节点的 ID。
input?: Record<string, any>; // (可选) 传递给节点的静态输入值 (会被 inputMapping 覆盖)。
}
/**
* 条件函数的类型定义。
* 可以是同步或异步函数,返回布尔值或分支名称字符串。
*/
export type ConditionFunction = (context: IWorkflowContext) => Promise<string | boolean> | string | boolean;
/**
* 条件步骤 (Condition Step) 的配置接口。
* 根据条件函数的结果,决定下一个要执行的步骤。
*/
export interface ConditionStepConfig extends BaseStepConfig {
type: StepType.CONDITION;
condition: ConditionFunction; // 用于评估条件的函数。
branches: Record<string, string>; // 映射表:将 condition 函数的返回值 (true/false/分支名) 映射到下一个步骤的 ID。
// `nextStepId` 在此类型中通常不直接使用。
}
/**
* 并行步骤 (Parallel Step) 的配置接口。
* 允许同时执行多个步骤。
*/
export interface ParallelStepConfig extends BaseStepConfig {
type: StepType.PARALLEL;
// 定义要并行执行的步骤列表。
parallelSteps: StepConfig[]; // 每个元素都是一个完整的步骤配置对象。
// `nextStepId` 指向所有并行分支都成功完成后要执行的步骤。
}
/**
* 子工作流步骤 (Sub-Workflow Step) 的配置接口。
* 用于调用并执行另一个已定义的工作流。
*/
export interface SubWorkflowStepConfig extends BaseStepConfig {
type: StepType.SUB_WORKFLOW;
subWorkflowId: string; // 要调用的子工作流的定义 ID。
input?: Record<string, any>; // (可选) 传递给子工作流的静态输入 (会被 inputMapping 覆盖)。
waitForCompletion?: boolean; // 是否等待子工作流执行完成,默认为 true。
}
// --- 事件相关步骤配置 (来自 Root 设计) ---
/**
* 事件触发步骤 (Event Trigger Step) 的配置接口。
* 用于在工作流执行到此步骤时,向外部系统或事件总线发出一个事件。
*/
export interface EventTriggerStepConfig extends BaseStepConfig {
type: StepType.EVENT_TRIGGER;
eventName: string; // 要触发的事件的名称。
eventDataMapping?: Record<string, string>; // (可选) 定义如何将工作流上下文数据映射到事件负载 (payload)。
}
/**
* 事件监听步骤 (Event Listener Step) 的配置接口。
* 通常用作工作流的起点,等待特定的外部事件来触发工作流实例的创建和执行。
*/
export interface EventListenerStepConfig extends BaseStepConfig {
type: StepType.EVENT_LISTENER;
eventName: string; // 要监听的事件的名称。
// outputMapping 通常用于将接收到的事件负载数据映射到工作流的初始变量中。
// 作为起点时,通常没有 nextStepId,流程的下一步由 definition.setStartStep 指向。
}
/**
* 所有可能步骤配置的联合类型。
* 工作流定义中的步骤列表会包含这些类型的对象。
*/
export type StepConfig =
| TaskStepConfig
| ConditionStepConfig
| ParallelStepConfig
| SubWorkflowStepConfig
| EventTriggerStepConfig
| EventListenerStepConfig;
// --- 工作流定义的数据结构 ---
/**
* 工作流定义的核心数据结构接口。
* 用于存储、持久化和加载工作流定义。
*/
export interface WorkflowDefinitionData {
id: string; // 工作流定义的唯一标识符。
name?: string; // 工作流定义的可读名称 (可选)。
description?: string; // 工作流定义的详细描述 (可选)。
steps: Record<string, StepConfig>; // 包含所有步骤配置的映射表 (键是步骤 ID)。
startStepId?: string; // 工作流的起始步骤 ID。
version?: number; // (可选) 工作流定义的版本号,用于版本控制。
// 可以添加其他元数据,如创建者、标签等。
}
// --- src/services/logger/interface.ts ---
/**
* @module services/logger
* 定义日志记录器服务的接口。
*/
/**
* 日志记录器接口。
* 定义了不同日志级别的记录方法。
*/
export interface ILogger {
/**
* 记录调试级别的日志。通常用于详细的开发调试信息。
* @param message 日志消息。
* @param context (可选) 附加的上下文信息对象。
*/
debug(message: string, context?: Record<string, any>): void;
/**
* 记录信息级别的日志。用于记录常规的操作信息或状态变化。
* @param message 日志消息。
* @param context (可选) 附加的上下文信息对象。
*/
info(message: string, context?: Record<string, any>): void;
/**
* 记录警告级别的日志。用于表示潜在的问题或非致命错误。
* @param message 日志消息。
* @param context (可选) 附加的上下文信息对象。
*/
warn(message: string, context?: Record<string, any>): void;
/**
* 记录错误级别的日志。用于记录执行失败、异常等严重问题。
* @param message 日志消息。
* @param error (可选) 关联的 Error 对象。
* @param context (可选) 附加的上下文信息对象。
*/
error(message: string, error?: Error, context?: Record<string, any>): void;
}
// --- src/services/persistence/interface.ts ---
/**
* @module services/persistence
* 定义持久化服务的接口,用于保存和加载工作流定义及实例状态。
*/
import { WorkflowDefinitionData } from '../../types/config'; // 引入配置类型
import { IWorkflowContext } from '../../types/runtime'; // 引入运行时类型
/**
* 持久化服务接口。
* 定义了保存和加载工作流定义及实例状态所需的方法。
* 这是实现工作流状态持久化、长时间运行和故障恢复的关键。
*/
export interface IPersistence {
// --- 工作流定义持久化 (可选但推荐) ---
/**
* 保存工作流定义数据。
* @param definition 要保存的工作流定义数据对象。
*/
saveDefinition(definition: WorkflowDefinitionData): Promise<void>;
/**
* 加载指定 ID 和版本 (可选) 的工作流定义数据。
* @param definitionId 工作流定义的 ID。
* @param version (可选) 要加载的版本号。
* @returns 返回工作流定义数据对象,如果找不到则返回 null。
*/
loadDefinition(definitionId: string, version?: number): Promise<WorkflowDefinitionData | null>;
/**
* (可选) 列出所有已保存的工作流定义。
* @returns 返回包含所有工作流定义数据对象的数组。
*/
listDefinitions?(): Promise<WorkflowDefinitionData[]>;
// --- 工作流实例状态持久化 ---
/**
* 保存工作流实例的当前状态 (上下文)。
* 这个方法会在工作流执行的关键点(如步骤完成后)被调用。
* @param context 要保存的工作流实例上下文对象。
*/
saveState(context: IWorkflowContext): Promise<void>;
/**
* 加载指定实例 ID 的工作流状态 (上下文)。
* 用于在服务重启或需要恢复执行时加载之前的状态。
* @param workflowId 工作流实例的 ID。
* @returns 返回工作流实例的上下文对象,如果找不到则返回 null。
*/
loadState(workflowId: string): Promise<IWorkflowContext | null>;
/**
* (可选) 删除指定实例 ID 的工作流状态。
* @param workflowId 要删除的工作流实例的 ID。
*/
deleteState?(workflowId: string): Promise<void>;
/**
* (可选) 根据状态查询工作流实例。
* @param status 要查询的状态。
* @returns 返回符合条件的工作流实例上下文对象的数组。
*/
// findStatesByStatus?(status: NodeStatus): Promise<IWorkflowContext[]>;
}
// --- src/services/scheduler/interface.ts ---
/**
* @module services/scheduler
* 定义调度器服务的接口,用于延迟或异步执行任务。
*/
import { INodeContext } from '../../types/runtime'; // 引入运行时类型
import { StepConfig } from '../../types/config'; // 引入配置类型
/**
* 调度选项接口。
*/
export interface ScheduleOptions {
delayMs?: number; // 延迟多少毫秒后执行。
runAt?: Date; // 指定一个未来的时间点执行。
// 可以根据需要添加其他选项,如任务优先级、目标队列名称等。
}
/**
* 调度器服务接口。
* 用于将工作流中的某个步骤的执行推迟到未来或交给后台工作者处理。
* 这对于实现长时间运行的任务、定时任务、或与外部异步系统交互非常有用。
*/
export interface IScheduler {
/**
* 调度一个步骤(通常是节点)的执行。
* 引擎在遇到需要调度的步骤时会调用此方法。
* @param step 要调度的步骤的配置对象。
* @param context 当前节点的执行上下文 (需要包含足够的信息供调度任务恢复执行)。
* @param options 调度选项,如延迟时间等。
* @returns 返回一个唯一的调度任务 ID,可用于后续取消等操作。
*/
scheduleStep(step: StepConfig, context: INodeContext, options?: ScheduleOptions): Promise<string>;
/**
* 取消一个之前已调度的任务。
* @param taskId 要取消的任务的 ID (由 scheduleStep 返回)。
* @returns 返回是否成功取消。
*/
cancel(taskId: string): Promise<boolean>;
}
// --- src/services/eventManager/interface.ts ---
/**
* @module services/eventManager
* 定义事件管理器服务的接口,用于在工作流内外发布和订阅事件。
*/
import { IWorkflowContext } from '../../types/runtime'; // 引入运行时类型
/**
* 事件负载 (Payload) 接口。
* 定义了在事件管理器中传递的事件数据的基本结构。
*/
export interface IEventPayload {
eventName: string; // 事件的名称。
data?: Record<string, any>; // 事件关联的数据。
sourceWorkflowId?: string; // (可选) 触发此事件的工作流实例的 ID。
timestamp: Date; // 事件发生的时间戳。
}
/**
* 事件管理器服务接口。
* 用于实现事件驱动的工作流,允许工作流触发外部事件,或被外部事件触发。
*/
export interface IEventManager {
/**
* 发出一个事件。
* 工作流引擎或自定义节点可以在执行过程中调用此方法来通知外部系统。
* @param eventName 要发出的事件的名称。
* @param data (可选) 附加到事件的数据负载。
* @param sourceContext (可选) 触发事件的源工作流上下文。
*/
emit(eventName: string, data?: Record<string, any>, sourceContext?: IWorkflowContext): Promise<void>;
/**
* (可选的设计) 将工作流定义绑定到特定事件。
* 当监听到指定事件时,自动创建并启动对应工作流的新实例。
* 事件监听和绑定的具体实现可能依赖于外部消息队列或事件总线。
* @param eventName 要监听的事件名称。
* @param definitionId 监听到事件时要启动的工作流定义 ID。
*/
// bindWorkflowToEvent?(eventName: string, definitionId: string): Promise<void>;
}
// --- src/nodes/baseNode.ts ---
/**
* @module nodes/baseNode
* 定义所有自定义节点实现必须继承的基础抽象类。
*/
import { INodeContext, NodeStatus, IExecutionRecord } from '../types/runtime'; // 引入运行时类型
import { NodeMetadata } from '../types/config'; // 引入配置类型
/**
* 自定义节点的抽象基类。
* 开发者通过继承此类并实现 `execute` 方法来创建自定义的任务逻辑。
*/
export abstract class BaseNode {
/**
* 节点的元数据。必须由子类实现。
* 包含节点的唯一 ID 和可选描述。
*/
abstract readonly metadata: NodeMetadata;
/**
* 节点的核心执行逻辑。必须由子类实现。
* 在此方法中实现节点的具体业务任务。
* 通过 `context` 参数访问输入数据 (`context.input`)、
* 设置输出数据 (`context.output = ...`)、
* 读写工作流变量 (`context.getVariable`/`context.setVariable`)、
* 以及记录日志 (`context.log(...)`)。
* **注意:** 此方法应返回 `Promise<void>`,执行结果通过修改 `context.output` 来传递。
* @param context 当前节点的执行上下文。
*/
abstract execute(context: INodeContext): Promise<void>;
/**
* (可选) 校验节点输入。在 `execute` 之前被引擎调用。
* 如果输入无效,应抛出 `ConfigurationError` 或其他适当的错误。
* @param context 当前节点的执行上下文,可以访问 `context.input`。
*/
validate?(context: INodeContext): Promise<void> | void;
/**
* (可选) 节点的补偿逻辑。
* 当包含此节点的工作流步骤失败且配置了 `compensateOnFailure` 时,
* 工作流引擎会尝试调用此方法来撤销 `execute` 方法所做的操作(如果可能)。
* 例如,删除已创建的资源、恢复数据库状态等。
* @param context 当前节点的执行上下文。
*/
compensate?(context: INodeContext): Promise<void>;
/**
* (可选) 获取执行此节点所需的用户角色列表。
* 引擎会在执行前检查当前用户的角色是否满足要求。
* @returns 返回一个字符串数组,表示所需角色;如果不需要特定角色,返回 `undefined`。
*/
get requiredRoles(): string[] | undefined {
// 默认不需要特定角色
return undefined;
}
}
// --- src/core/nodeRegistry.ts ---
/**
* @module core/nodeRegistry
* 负责注册和管理所有可供工作流使用的节点实现。
*/
import { NodeFunction, NodeMetadata } from '../types/config'; // 引入配置类型
import { BaseNode } from '../nodes/baseNode'; // 引入基类
import { ConfigurationError } from '../errors'; // 引入错误类型
/**
* 内部存储的节点实现类型。可以是函数或 BaseNode 实例。
*/
type NodeImplementation = NodeFunction | BaseNode;
/**
* 内部表示已注册节点的信息。
*/
interface RegisteredNode {
id: string; // 节点的唯一 ID。
implementation: NodeImplementation;// 节点的实际执行逻辑(函数或类实例)。
isClassInstance: boolean; // 标记实现是否为 BaseNode 类实例。
description?: string; // 节点描述。
}
/**
* 节点注册表类。
* 由 FlowLabEngine 内部管理。
*/
export class NodeRegistry {
// 使用 Map 存储已注册的节点,键为节点 ID。
private nodes = new Map<string, RegisteredNode>();
/**
* 注册一个基于 BaseNode 子类实例的节点。
* @param node BaseNode 的实例。节点的 ID 和描述将从实例的 metadata 中获取。
*/
register(node: BaseNode): void;
/**
* 注册一个基于简单函数的节点。
* @param id 节点的唯一标识符。
* @param func 节点的执行函数 (NodeFunction 类型)。
* @param description (可选) 节点的描述。
*/
register(id: string, func: NodeFunction, description?: string): void;
/**
* 注册节点方法的重载实现。
*/
register(idOrNode: string | BaseNode, func?: NodeFunction, description?: string): void {
let id: string;
let implementation: NodeImplementation;
let isClassInstance = false;
let nodeDescription = description;
if (typeof idOrNode === 'string') {
// 处理函数注册方式
id = idOrNode;
if (!func) {
// 如果第一个参数是字符串,第二个参数(函数)必须提供
throw new ConfigurationError(`注册节点 '${id}' 时必须提供节点执行函数。`);
}
implementation = func;
nodeDescription = description; // 使用传入的描述
isClassInstance = false;
} else if (idOrNode instanceof BaseNode) {
// 处理类实例注册方式
id = idOrNode.metadata.id; // 从元数据获取 ID
implementation = idOrNode;
isClassInstance = true;
// 优先使用元数据中的描述,如果元数据没有则使用传入的描述
nodeDescription = idOrNode.metadata.description || description;
} else {
// 无效的参数组合
throw new ConfigurationError('注册节点的参数无效。请提供 (BaseNode实例) 或 (ID, 函数, [描述])。');
}
// 检查 ID 是否已存在
if (this.nodes.has(id)) {
// 可以选择抛出错误或发出警告并覆盖
console.warn(`[NodeRegistry] 节点 ID '${id}' 已被注册,将进行覆盖。`);
}
// 存储注册信息
this.nodes.set(id, { id, implementation, isClassInstance, description: nodeDescription });
console.log(`[NodeRegistry] 节点 '${id}' 已注册。`);
}
/**
* 获取指定 ID 的已注册节点信息。
* @param id 节点 ID。
* @returns 返回包含节点实现等信息的对象,如果未找到则返回 undefined。
*/
get(id: string): RegisteredNode | undefined {
return this.nodes.get(id);
}
/**
* (内部使用) 获取指定 ID 的节点实现逻辑 (函数或实例)。
* @param id 节点 ID。
* @returns 返回节点实现,如果未找到则返回 undefined。
*/
getNodeImplementation(id: string): NodeImplementation | undefined {
return this.nodes.get(id)?.implementation;
}
/**
* 获取所有已注册节点的 ID 列表。
* @returns 返回包含所有节点 ID 的字符串数组。
*/
listNodeIds(): string[] {
return Array.from(this.nodes.keys());
}
}
// --- src/core/definition.ts ---
/**
* @module core/definition
* 提供 WorkflowDefinition 类,用于以编程方式构建工作流的结构。
*/
import { StepConfig, WorkflowDefinitionData, StepType, TaskStepConfig, ConditionStepConfig, ParallelStepConfig, SubWorkflowStepConfig, ConditionFunction } from '../types/config'; // 引入配置类型
import { ConfigurationError } from '../errors'; // 引入错误类型
/**
* 工作流定义类 (构建器)。
* 提供链式 API 来添加各种类型的步骤,并设置工作流的起始点。
*/
export class WorkflowDefinition {
// 存储工作流定义的核心数据
private data: WorkflowDefinitionData;
/**
* 创建一个新的工作流定义。
* @param id 工作流定义的唯一标识符。
* @param name (可选) 工作流的可读名称。
* @param description (可选) 工作流的描述。
*/
constructor(id: string, name?: string, description?: string) {
if (!id) throw new ConfigurationError('创建工作流定义时必须提供 ID。');
this.data = {
id,
name,
description,
steps: {}, // 初始化步骤映射表
};
}
// --- 用于构建工作流的链式方法 ---
/**
* 添加一个任务步骤 (执行节点)。
* @param config 任务步骤的配置对象 (省略了 type 属性)。
* @returns 返回当前 WorkflowDefinition 实例,支持链式调用。
*/
addStep(config: Omit<TaskStepConfig, 'type'>): this {
this._addStepInternal({ ...config, type: StepType.TASK });
return this;
}
/**
* 添加一个条件步骤。
* @param config 条件步骤的配置对象 (省略了 type 属性)。
* @returns 返回当前 WorkflowDefinition 实例,支持链式调用。
*/
addCondition(config: Omit<ConditionStepConfig, 'type'>): this {
// 添加验证:确保 branches 对象存在且不为空
if (!config.branches || Object.keys(config.branches).length === 0) {
throw new ConfigurationError(`条件步骤 '${config.id}' 必须包含 'branches' 映射。`);
}
this._addStepInternal({ ...config, type: StepType.CONDITION });
return this;
}
/**
* 添加一个并行步骤。
* @param config 并行步骤的配置对象 (省略了 type 属性)。
* @returns 返回当前 WorkflowDefinition 实例,支持链式调用。
*/
addParallel(config: Omit<ParallelStepConfig, 'type'>): this {
// 添加验证:确保 parallelSteps 数组存在且不为空
if (!config.parallelSteps || config.parallelSteps.length === 0) {
throw new ConfigurationError(`并行步骤 '${config.id}' 必须包含 'parallelSteps' 数组。`);
}
// TODO: 可以添加更深入的验证,例如检查 parallelSteps 内部结构的合法性
this._addStepInternal({ ...config, type: StepType.PARALLEL });
return this;
}
/**
* 添加一个子工作流步骤。
* @param config 子工作流步骤的配置对象 (省略了 type 属性)。
* @returns 返回当前 WorkflowDefinition 实例,支持链式调用。
*/
addSubWorkflow(config: Omit<SubWorkflowStepConfig, 'type'>): this {
if (!config.subWorkflowId) {
throw new ConfigurationError(`子工作流步骤 '${config.id}' 必须包含 'subWorkflowId'。`);
}
this._addStepInternal({ ...config, type: StepType.SUB_WORKFLOW });
return this;
}
// TODO: 根据需要添加 addEventTrigger, addEventListener 等方法
/**
* 设置工作流的起始步骤。
* @param stepId 要设置为起点的步骤的 ID。
* @returns 返回当前 WorkflowDefinition 实例,支持链式调用。
* @throws {ConfigurationError} 如果提供的 stepId 在当前定义中不存在。
*/
setStartStep(stepId: string): this {
if (!this.data.steps[stepId]) {
// 检查要设置的起始步骤是否已添加到定义中
throw new ConfigurationError(`无法设置起始步骤:在工作流定义 '${this.data.id}' 中找不到 ID 为 '${stepId}' 的步骤。`);
}
this.data.startStepId = stepId;
return this;
}
// --- 内部辅助方法 ---
/**
* 内部方法,用于添加步骤到 `steps` 映射表,并进行基本校验。
* @param config 完整的步骤配置对象。
* @throws {ConfigurationError} 如果步骤 ID 缺失或重复。
*/
private _addStepInternal(config: StepConfig): void {
if (!config.id) throw new ConfigurationError('添加步骤时必须提供步骤 ID。');
// 检查步骤 ID 是否已存在
if (this.data.steps[config.id]) throw new ConfigurationError(`步骤 ID '${config.id}' 在工作流定义 '${this.data.id}' 中已存在。`);
// TODO: 根据步骤类型添加更详细的验证逻辑,例如:
// - TASK 步骤必须有 nodeId。
// - CONDITION 步骤必须有 condition 和 branches。
// - PARALLEL 步骤必须有 parallelSteps。
// - SUB_WORKFLOW 步骤必须有 subWorkflowId。
// - 检查 nextStepId 是否指向存在的步骤 (可以在 validate() 方法中做)。
this.data.steps[config.id] = config;
}
// --- 数据访问器 ---
/** 获取工作流定义的 ID。 */
get id(): string { return this.data.id; }
/** 获取工作流定义的名称 (可选)。 */
get name(): string | undefined { return this.data.name; }
/** 获取工作流定义的描述 (可选)。 */
get description(): string | undefined { return this.data.description; }
/** 获取工作流定义的起始步骤 ID (可选)。 */
get startStepId(): string | undefined { return this.data.startStepId; }
/**
* 获取指定 ID 的步骤配置。
* @param stepId 步骤 ID。
* @returns 返回步骤配置对象,如果未找到则返回 undefined。
*/
getStep(stepId: string): StepConfig | undefined {
return this.data.steps[stepId];
}
/**
* 获取此工作流定义中所有步骤的配置。
* @returns 返回一个只读的步骤映射表 (键是步骤 ID)。
*/
getSteps(): Readonly<Record<string, StepConfig>> {
return this.data.steps;
}
/**
* 获取用于存储或检查的工作流定义的原始数据对象。
* @returns 返回一个只读的工作流定义数据对象。
*/
getData(): Readonly<WorkflowDefinitionData> {
// 返回数据的深拷贝可能更安全,防止外部修改,但会牺牲性能
// return JSON.parse(JSON.stringify(this.data));
// 或者返回只读引用
return this.data;
}
/**
* 验证工作流定义的完整性和基本逻辑。
* @returns 如果定义有效则返回 true,否则返回 false 并打印错误日志。
*/
validate(): boolean {
let isValid = true;
if (!this.data.startStepId) {
console.error(`[验证错误] 工作流 '${this.id}': 未设置起始步骤 (startStepId)。`);
isValid = false;
}
// TODO: 实现更复杂的验证逻辑:
// 1. 检查所有 nextStepId, branches 中的 stepId 是否都指向已定义的步骤。
// 2. 检测是否存在无法到达的步骤。
// 3. 检测是否存在明确的循环(如果业务逻辑不允许)。
// 4. 验证特定步骤类型的配置是否完整 (如 TASK 有 nodeId)。
for (const stepId in this.data.steps) {
const step = this.data.steps[stepId];
// 简单示例:检查 nextStepId 是否有效
if (step.nextStepId && !this.data.steps[step.nextStepId]) {
console.error(`[验证错误] 工作流 '${this.id}', 步骤 '${stepId}': nextStepId 指向了不存在的步骤 '${step.nextStepId}'。`);
isValid = false;
}
// TODO: 添加更多验证规则...
}
return isValid;
}
}
// --- src/core/engine.ts ---
/**
* @module core/engine
* 提供 FlowLabEngine 类,作为与 FlowLab 交互的主要入口点。
* 管理配置、注册表、服务和执行器的创建。
*/
import { NodeRegistry } from './nodeRegistry'; // 引入节点注册表
import { WorkflowDefinition } from './definition'; // 引入工作流定义类
import { WorkflowExecutor } from './executor'; // 引入执行器
import { ILogger, ConsoleLogger, IPersistence, IScheduler, IEventManager } from '../services'; // 引入服务接口和默认实现
import { BaseNode } from '../nodes/baseNode'; // 引入节点基类
import { NodeFunction, WorkflowDefinitionData } from '../types/config'; // 引入配置类型
import { IWorkflowContext } from '../types/runtime'; // 引入运行时类型
import { ConfigurationError } from '../errors'; // 引入错误类型
/**
* FlowLabEngine 的配置选项接口。
*/
export interface FlowLabEngineOptions {
logger?: ILogger; // (可选) 提供自定义的日志记录器实现。
persistence?: IPersistence; // (可选) 提供持久化服务实现,用于保存/加载工作流状态和定义。
scheduler?: IScheduler; // (可选) 提供调度器服务实现,用于延迟或异步执行。
eventManager?: IEventManager; // (可选) 提供事件管理器实现,用于事件驱动流程。
// --- 全局引擎设置 ---
maxLoopIterations?: number; // (可选) 防止工作流中意外无限循环的最大迭代次数 (默认 1000)。
}
/**
* FlowLab 引擎主类。
* 负责初始化、配置管理、节点注册、工作流定义和执行。
*/
export class FlowLabEngine {
// 只读属性,存储引擎的核心组件和配置
readonly nodeRegistry: NodeRegistry; // 节点注册表实例
readonly logger: ILogger; // 日志记录器实例
readonly persistence?: IPersistence; // 持久化服务实例 (可选)
readonly scheduler?: IScheduler; // 调度器服务实例 (可选)
readonly eventManager?: IEventManager; // 事件管理器实例 (可选)
readonly options: Readonly<FlowLabEngineOptions>; // 引擎配置选项 (只读)
// 用于在内存中存储已注册或加载的工作流定义
private workflowDefinitions = new Map<string, WorkflowDefinition>();
/**
* 创建 FlowLabEngine 实例。
* @param options (可选) 引擎的配置选项,用于注入自定义服务或设置参数。
*/
constructor(options: FlowLabEngineOptions = {}) {
// 合并默认选项和传入选项
this.options = {
maxLoopIterations: 1000, // 设置默认的最大循环迭代次数保护
...options
};
// 初始化核心组件
this.nodeRegistry = new NodeRegistry();
// 如果未提供 logger,则使用默认的 ConsoleLogger
this.logger = options.logger ?? new ConsoleLogger('[FlowLab]');
// 存储可选的服务实例
this.persistence = options.persistence;
this.scheduler = options.scheduler;
this.eventManager = options.eventManager;
this.logger.info('FlowLabEngine 已初始化。');
// 打印已启用的可选服务信息
if (this.persistence) this.logger.info(`持久化服务已启用: ${this.persistence.constructor.name}`);
if (this.scheduler) this.logger.info(`调度器服务已启用: ${this.scheduler.constructor.name}`);
if (this.eventManager) this.logger.info(`事件管理器已启用: ${this.eventManager.constructor.name}`);
}
// --- 节点注册 (委托给 NodeRegistry) ---
/**
* 注册一个基于 BaseNode 子类实例的节点。
* @param node BaseNode 的实例。
*/
registerNode(node: BaseNode): void;
/**
* 注册一个基于简单函数的节点。
* @param id 节点的唯一标识符。
* @param func 节点的执行函数 (NodeFunction 类型)。
* @param description (可选) 节点的描述。
*/
registerNode(id: string, func: NodeFunction, description?: string): void;
/**
* 注册节点方法的重载实现。
*/
registerNode(idOrNode: string | BaseNode, func?: NodeFunction, description?: string): void {
// 直接调用 nodeRegistry 的 register 方法
// 类型断言 as any 用于简化重载调用,实际类型安全由 register 内部保证
this.nodeRegistry.register(idOrNode as any, func as any, description);
}
// --- 工作流定义管理 ---
/**
* 开始定义一个新的工作流。返回一个构建器实例用于链式添加步骤。
* 定义会暂时存储在内存中。如需持久化或按 ID 执行,请调用 `registerDefinition`。
* @param id 工作流定义的唯一标识符。
* @param name (可选) 工作流的可读名称。
* @param description (可选) 工作流的描述。
* @returns 返回 WorkflowDefinition 构建器实例。
*/
defineWorkflow(id: string, name?: string, description?: string): WorkflowDefinition {
const definition = new WorkflowDefinition(id, name, description);
// 将新创建的定义实例存入内存 Map,方便后续查找
this.workflowDefinitions.set(id, definition);
this.logger.info(`工作流定义 '${id}' 已创建。`);
return definition;
}
/**
* 显式注册一个工作流定义。
* 可以传入 `WorkflowDefinition` 实例或从持久化加载的 `WorkflowDefinitionData` 对象。
* 注册后,工作流可以通过其 ID 被执行器查找和执行。
* 如果配置了持久化服务,此方法也会尝试将定义保存到持久化存储中。
* @param definition 工作流定义实例或数据对象。
*/
registerDefinition(definition: WorkflowDefinition | WorkflowDefinitionData): void {
// 如果传入的是数据对象,先将其转换为 WorkflowDefinition 实例
const def = definition instanceof WorkflowDefinition ? definition : this._hydrateDefinition(definition);
// 检查是否覆盖已存在的定义
if (this.workflowDefinitions.has(def.id)) {
this.logger.warn(`将覆盖已存在的工作流定义,ID: '${def.id}'。`);
}
// 存入内存 Map
this.workflowDefinitions.set(def.id, def);
// 如果配置了持久化,并且持久化服务支持 saveDefinition 方法
if (this.persistence?.saveDefinition) {
this.persistence.saveDefinition(def.getData())
.then(() => this.logger.debug(`定义 '${def.id}' 已保存到持久化存储。`))
.catch(err => this.logger.error(`保存定义 '${def.id}' 到持久化存储失败。`, err));
}
this.logger.info(`工作流定义 '${def.id}' 已注册。`);
}
/**
* 根据 ID 获取工作流定义。
* 首先尝试从内存缓存中获取,如果找不到且配置了持久化服务,则尝试从持久化存储加载。
* @param id 要获取的工作流定义的 ID。
* @returns 返回 WorkflowDefinition 实例的 Promise,如果找不到则返回 undefined。
*/
async getDefinition(id: string): Promise<WorkflowDefinition | undefined> {
// 1. 尝试从内存获取
let definition = this.workflowDefinitions.get(id);
// 2. 如果内存没有,且配置了持久化,并且持久化支持加载定义
if (!definition && this.persistence?.loadDefinition) {
this.logger.debug(`定义 '${id}' 不在内存中,尝试从持久化存储加载...`);
// 3. 从持久化加载数据
const data = await this.persistence.loadDefinition(id);
if (data) {
// 4. 如果加载成功,将数据转换为实例,并存入内存缓存
definition = this._hydrateDefinition(data);
this.workflowDefinitions.set(id, definition); // 缓存到内存
this.logger.info(`定义 '${id}' 已从持久化存储加载。`);
} else {
// 5. 持久化存储中也未找到
this.logger.warn(`在持久化存储中未找到定义 '${id}'。`);
}
}
// 6. 返回找到的定义实例或 undefined
return definition;
}
/**
* (内部方法) 从 WorkflowDefinitionData 对象重新构建 WorkflowDefinition 实例。
* 用于从持久化存储加载数据后恢复对象。
* @param data 从持久化存储加载的工作流定义数据。
* @returns 返回一个 WorkflowDefinition 实例。
*/
private _hydrateDefinition(data: WorkflowDefinitionData): WorkflowDefinition {
// 创建实例
const definition = new WorkflowDefinition(data.id, data.name, data.description);
// 重新添加步骤到实例中
for (const stepId in data.steps) {
// 调用内部的添加步骤方法 (假设存在且接受 StepConfig)
// 需要确保 WorkflowDefinition 类有此内部方法或提供公共方法
(definition as any)._addStepInternal(data.steps[stepId]);
}
// 设置起始步骤
if (data.startStepId) {
definition.setStartStep(data.startStepId);
}
// TODO: 处理版本号等其他元数据
return definition;
}
// --- 工作流执行 ---
/**
* 创建一个工作流执行器实例。
* 执行器会持有对引擎核心组件(注册表、服务等)的引用。
* @returns 返回一个新的 WorkflowExecutor 实例。
*/
createExecutor(): WorkflowExecutor {
// 将引擎自身管理的依赖项传递给执行器
return new WorkflowExecutor({
nodeRegistry: this.nodeRegistry,
logger: this.logger,
persistence: this.persistence,
scheduler: this.scheduler,
eventManager: this.eventManager,
// 传递一个函数给执行器,让它可以按需查找工作流定义
getWorkflowDefinition: this.getDefinition.bind(this),
maxLoopIterations: this.options.maxLoopIterations,
});
}
/**
* (便捷方法) 快速定义并立即执行一个简单的工作流。
* 适用于测试、脚本或不需要持久化定义的场景。
* 工作流定义在此调用结束后通常不会被保留(除非在回调中手动注册)。
* @param definitionBuilder 一个回调函数,接收 WorkflowDefinition 构建器实例,用于在函数内部定义工作流步骤。
* @param input 工作流的初始输入数据。
* @param contextExtras (可选) 附加的上下文信息,如 tenantId, userId 等。
* @returns 返回工作流执行结果的 Promise (IWorkflowContext)。
* @throws {ConfigurationError} 如果内联定义的工作流验证失败。
*/
async runWorkflow(
definitionBuilder: (builder: WorkflowDefinition) => void,
input: Record<string, any>,
contextExtras: Partial<IWorkflowContext> = {}
): Promise<IWorkflowContext> {
// 1. 创建一个临时的、唯一的 ID 用于本次定义
const tempId = `temp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
// 2. 创建 WorkflowDefinition 构建器实例
const builder = new WorkflowDefinition(tempId, `Temporary Workflow ${tempId}`);
// 3. 调用用户提供的回调函数,让用户使用构建器定义步骤
definitionBuilder(builder);
// 4. 验证内联定义的工作流是否有效
if (!builder.validate()) {
throw new ConfigurationError(`临时创建的工作流定义验证失败。`);
}
// 5. 创建执行器
const executor = this.createExecutor();
// 6. 直接使用构建好的 WorkflowDefinition 实例执行工作流
return executor.run(builder, input, contextExtras);
}
}
// --- src/core/executor.ts ---
/**
* @module core/executor
* 提供 WorkflowExecutor 类,负责实际执行工作流定义的逻辑。
*/
import { WorkflowDefinition } from './definition'; // 引入定义类
import { NodeRegistry } from './nodeRegistry'; // 引入节点注册表
import { ILogger, IPersistence, IScheduler, IEventManager } from '../services'; // 引入服务接口
import { IWorkflowContext, INodeContext, NodeStatus, IExecutionRecord } from '../types/runtime'; // 引入运行时类型
import { StepConfig, StepType, TaskStepConfig, ConditionStepConfig, ParallelStepConfig, SubWorkflowStepConfig, RetryOptions } from '../types/config'; // 引入配置类型
import { createWorkflowContext, createNodeContext } from './context'; // 引入上下文创建函数
import { WorkflowError, NodeExecutionError, TimeoutError, ConfigurationError, AuthorizationError } from '../errors'; // 引入错误类型
import { BaseNode } from '../nodes/baseNode'; // 引入节点基类
import { NodeFunction } from '../types/config'; // 引入节点函数类型
/**
* WorkflowExecutor 的构造函数选项接口。
* 用于注入执行器所需的依赖项。
*/
export interface WorkflowExecutorOptions {
nodeRegistry: NodeRegistry; // 节点注册表实例。
logger: ILogger; // 日志记录器实例。
persistence?: IPersistence; // (可选) 持久化服务实例。
scheduler?: IScheduler; // (可选) 调度器服务实例。
eventManager?: IEventManager; // (可选) 事件管理器实例。
// 一个函数,用于根据 ID 异步查找工作流定义 (可能从内存或持久化加载)。
getWorkflowDefinition: (id: string) => Promise<WorkflowDefinition | undefined>;
maxLoopIterations?: number; // (可选) 最大循环迭代次数。
}
/**
* 工作流执行器类。
* 负责接收工作流定义和输入,然后按照定义驱动整个流程的执行。
* 管理运行时状态、步骤跳转、节点调用、错误处理、重试、超时等。
*/
export class WorkflowExecutor {
// 存储从构造函数传入的依赖项
private readonly nodeRegistry: NodeRegistry;
private readonly logger: ILogger;
private readonly persistence?: IPersistence;
private readonly scheduler?: IScheduler;
private readonly eventManager?: IEventManager;
private readonly getWorkflowDefinition: (id: string) => Promise<WorkflowDefinition | undefined>;
private readonly maxLoopIterations: number;
/**
* 创建 WorkflowExecutor 实例。通常由 FlowLabEngine 创建。
* @param options 包含所需依赖项的配置对象。
*/
constructor(options: WorkflowExecutorOptions) {
this.nodeRegistry = options.nodeRegistry;
this.logger = options.logger;
this.persistence = options.persistence;
this.scheduler = options.scheduler;
this.eventManager = options.eventManager;
this.getWorkflowDefinition = options.getWorkflowDefinition;
this.maxLoopIterations = options.maxLoopIterations ?? 1000; // 使用默认值
}
/**
* 执行一个工作流实例。
* @param definitionOrId 要执行的工作流定义实例,或其注册 ID。
* @param input 工作流的初始输入数据。
* @param contextExtras (可选) 附加的初始上下文信息 (如 userId, tenantId)。
* @returns 返回包含最终状态、输出、变量和历史记录的工作流上下文对象 (IWorkflowContext) 的 Promise。
* @throws {ConfigurationError} 如果工作流定义未找到、无效或没有设置起始步骤。
*/
async run(
definitionOrId: WorkflowDefinition | string,
input: Record<string, any>,
contextExtras: Partial<IWorkflowContext> = {}
): Promise<IWorkflowContext> {
// 1. 获取工作流定义实例
const definition = typeof definitionOrId === 'string'
? await this.getWorkflowDefinition(definitionOrId) // 如果是 ID,则异步查找
: definitionOrId; // 如果是实例,则直接使用
// 检查定义是否存在
if (!definition) {
throw new ConfigurationError(`无法执行工作流:未找到 ID 为 '${definitionOrId}' 的定义。`);
}
// 检查是否有起始步骤
if (!definition.startStepId) {
throw new ConfigurationError(`工作流定义 '${definition.id}' 没有设置起始步骤。`);
}
// 验证定义是否有效 (可选但推荐)
if (!definition.validate()) {
throw new ConfigurationError(`工作流定义 '${definition.id}' 未通过验证。`);
}
// 2. 初始化本次执行的工作流上下文 (Context)
const context = createWorkflowContext(definition.id, definition.name, input, contextExtras);
this.logger.info(`开始执行工作流实例: ${context.workflowId} (定义: ${definition.id})`);
// 触发 'workflow.started' 事件 (如果配置了 EventManager)
await this.emitEvent('workflow.started', context);
// 3. 开始执行循环
let currentStepId: string | undefined = definition.startStepId; // 从起始步骤开始
let loopGuard = 0; // 防止无限循环的计数器
try {
// 循环条件:当前步骤 ID 存在,且未超过最大循环次数
while (currentStepId && loopGuard++ < this.maxLoopIterations) {
// 获取当前步骤的配置
const stepConfig = definition.getStep(currentStepId);
if (!stepConfig) {
// 理论上 validate() 应该能捕获此错误,但作为运行时检查
throw new WorkflowError(`在工作流 '${definition.id}' 中找不到步骤 '${currentStepId}'。`, context.workflowId);
}
context.logs.push(`准备执行步骤: ${currentStepId} (类型: ${stepConfig.type})`);
// 调用内部方法执行单个步骤
const stepResult = await this.executeStep(stepConfig, context);
// 更新执行历史
this.updateHistory(context, stepResult.record);
// 如果配置了持久化,保存当前状态 (允许从中断处恢复)
if (this.persistence) {
try {
await this.persistence.saveState(context);
this.logger.debug(`工作流 ${context.workflowId} 状态已保存 (步骤 ${currentStepId} 完成)。`);
} catch (persistError) {
this.logger.error(`保存工作流 ${context.workflowId} 状态失败。`, persistError as Error);
// TODO: 是否应该因为持久化失败而停止工作流?取决于业务需求。
}
}
// 检查步骤执行结果状态
if (stepResult.status === NodeStatus.FAILED || stepResult.status === NodeStatus.CANCELLED) {
// 如果步骤失败或被取消,则整个工作流失败/取消
context.status = stepResult.status;
context.output = stepResult.output; // 记录失败前的最后输出
this.logger.error(`工作流实例 ${context.workflowId} 在步骤 ${currentStepId} 失败。`, stepResult.error);
// 触发 'workflow.failed' 或 'workflow.cancelled' 事件
await this.emitEvent(stepResult.status === NodeStatus.FAILED ? 'workflow.failed' : 'workflow.cancelled', context, { stepId: currentStepId, error: stepResult.error?.message });
break; // 终止工作流执行循环
}
if (stepResult.status === NodeStatus.COMPENSATING) {
// 如果步骤请求补偿,则停止正常流程
// TODO: 实现触发补偿流程的逻辑
this.logger.warn(`步骤 ${currentStepId} 请求补偿,停止正常流程。补偿逻辑待实现。`);
context.status = NodeStatus.FAILED; // 或许用一个专门的 'COMPENSATION_NEEDED' 状态?暂时标记为失败
await this.emitEvent('workflow.compensation.requested', context, { stepId: currentStepId });
break; // 终止正常执行循环
}
// 确定下一个要执行的步骤 ID
currentStepId = stepResult.nextStepId;
// 如果没有下一个步骤 ID,说明工作流正常结束
if (!currentStepId) {
context.status = NodeStatus.COMPLETED; // 设置最终状态为完成
context.output = stepResult.output; // 记录最后一个成功步骤的输出作为工作流输出
this.logger.info(`工作流实例 ${context.workflowId} 已成功完成。`);
// 触发 'workflow.completed' 事件
await this.emitEvent('workflow.completed', context);
break; // 退出执行循环
}
} // 结束 while 循环
// 检查是否因为达到最大循环次数而退出
if (loopGuard >= this.maxLoopIterations) {
throw new WorkflowError(`已达到最大循环迭代次数 (${this.maxLoopIterations})。检测到潜在的无限循环。`, context.workflowId);
}
} catch (error: any) {
// 捕获执行循环中未处理的异常
context.status = NodeStatus.FAILED; // 设置工作流状态为失败
context.error = error; // 在工作流上下文中记录错误
this.logger.error(`工作流实例 ${context.workflowId} 遇到未处理错误。`, error);
// 触发 'workflow.failed' 事件
await this.emitEvent('workflow.failed', context, { error: error.message });
// 不再向上抛出异常,而是返回包含失败状态的上下文
} finally {
// 无论成功或失败,记录结束时间
context.endTime = new Date();
// 最后一次保存状态 (如果配置了持久化)
if (this.persistence) {
try {
await this.persistence.saveState(context);
this.logger.debug(`工作流 ${context.workflowId} 最终状态已保存。`);
} catch (saveError) {
// 记录保存最终状态失败的错误,但不影响返回结果
this.logger.error(`保存工作流 ${context.workflowId} 的最终状态失败。`, saveError as Error);
}
}
}
// 4. 返回包含最终结果的工作流上下文
return context;
}
// --- 单个步骤执行逻辑 ---
/**
* (内部核心方法) 执行单个步骤。根据步骤类型调用不同的处理逻辑。
* @param stepConfig 要执行的步骤的配置。
* @param workflowContext 当前工作流的上下文。
* @returns 返回一个包含步骤执行结果(状态、下一个步骤ID、输出、错误、记录)的对象 Promise。
*/
private async executeStep(
stepConfig: StepConfig,
workflowContext: IWorkflowContext
): Promise<{ status: NodeStatus; nextStepId?: string; output?: any; error?: Error; record: IExecutionRecord }> {
const stepStartTime = new Date(); // 记录步骤开始时间
let stepStatus: NodeStatus = NodeStatus.PENDING; // 初始化步骤状态
let stepOutput: any = undefined; // 初始化步骤输出
let stepError: Error | undefined = undefined; // 初始化步骤错误
let nextStepId: string | undefined = stepConfig.nextStepId; // 获取默认的下一个步骤 ID
// 初始化本次步骤的执行记录对象
const record: Partial<IExecutionRecord> = {
stepId: stepConfig.id,
startTime: stepStartTime,
status: NodeStatus.PENDING, // 初始状态
};
try {
// 触发 'step.started' 事件
await this.emitEvent('step.started', workflowContext, { stepId: stepConfig.id, type: stepConfig.type });
stepStatus = NodeStatus.RUNNING; // 更新状态为运行中
record.status = stepStatus; // 更新记录中的状态
// --- 根据步骤类型分发执行逻辑 ---
switch (stepConfig.type) {
case StepType.TASK:
// 执行任务步骤 (调用节点)
const taskResult = await this.executeTaskStep(stepConfig, workflowContext);
stepStatus = taskResult.status; // 获取任务执行结果状态
stepOutput = taskResult.output; // 获取任务输出
stepError = taskResult.error; // 获取任务错误
record.nodeId = stepConfig.nodeId; // 记录执行的节点 ID
// 任务步骤通常使用默认的 nextStepId
break;
case StepType.CONDITION:
// 执行条件步骤
const conditionResult = await this.executeConditionStep(stepConfig, workflowContext);
stepStatus = NodeStatus.COMPLETED; // 条件步骤本身总是“完成”
// 条件步骤的 *结果* 决定了下一个步骤的 ID
nextStepId = conditionResult.nextStepId;
record.output = { branchTaken: conditionResult.branchKey }; // 在记录中存储选择的分支
if (!nextStepId && conditionResult.branchKey) {
// 如果条件评估有结果,但没有找到对应的分支目标
this.logger.warn(`条件步骤 '${stepConfig.id}' 的结果 '${conditionResult.branchKey}' 没有找到匹配的分支目标。流程将在此路径结束。`);
}
break;
case StepType.PARALLEL:
// TODO: 实现并行步骤执行逻辑
// - 需要并发执行 parallelSteps 中的所有步骤。
// - 等待所有并行分支完成。
// - 处理部分分支失败的情况(例如,是否继续、是否补偿)。
// - 收集所有分支的输出(如何合并?)。
this.logger.warn(`并行步骤 (PARALLEL) 的执行逻辑尚未完全实现,步骤: ${stepConfig.id}`);
stepStatus = NodeStatus.SKIPPED; // 暂时标记为跳过
break;
case StepType.SUB_WORKFLOW:
// TODO: 实现子工作流步骤执行逻辑
// - 需要获取子工作流的定义 (使用 getWorkflowDefinition)。
// - 创建一个新的执行器或递归调用 run 方法。
// - 处理输入映射 (将父流程数据映射到子流程输入)。
// - 处理输出映射 (将子流程输出映射回父流程变量)。
// - 处理子流程失败的情况。
this.logger.warn(`子工作流步骤 (SUB_WORKFLOW) 的执行逻辑尚未完全实现,步骤: ${stepConfig.id}`);
stepStatus = NodeStatus.SKIPPED; // 暂时标记为跳过
break;
case StepType.EVENT_TRIGGER:
// TODO: 实现事件触发逻辑
// - 解析 eventDataMapping 从上下文中提取数据。
// - 调用 this.eventManager.emit 发出事件。
this.logger.warn(`事件触发步骤 (EVENT_TRIGGER) 的执行逻辑尚未完全实现,步骤: ${stepConfig.id}`);
stepStatus = NodeStatus.COMPLETED; // 触发事件本身通常是成功的
break;
case StepType.EVENT_LISTENER:
// 事件监听步骤通常作为起点,其执行逻辑由引擎在接收到外部事件时处理,
// 在正常的步骤执行循环中可能不需要做特别的操作,或者标记为完成。
this.logger.debug(`事件监听步骤 (EVENT_LISTENER) '${stepConfig.id}' 在执行流中遇到,通常由外部事件触发启动,标记为完成。`);
stepStatus = NodeStatus.COMPLETED;
break;
default:
// 如果遇到未知的步骤类型,抛出配置错误
throw new ConfigurationError(`不支持的步骤类型: ${(stepConfig as any).type}`);
}
// 检查是否需要在失败时触发补偿
if (stepStatus === NodeStatus.FAILED && stepConfig.compensateOnFailure) {
// TODO: 实现触发补偿的逻辑
// - 可能需要查找补偿步骤 (如果 compensateOnFailure 是 stepId)。
// - 将工作流状态置为需要补偿,并启动补偿流程。
this.logger.warn(`步骤 '${stepConfig.id}' 失败,请求补偿。补偿逻辑待实现。`);
stepStatus = NodeStatus.COMPENSATING; // 改变状态以表示需要补偿
}
} catch (err: any) {
// 捕获步骤执行过程中的任何未处理异常
stepStatus = NodeStatus.FAILED; // 标记步骤为失败
// 包装错误,确保是 Error 类型
stepError = err instanceof Error ? err : new WorkflowError(String(err), workflowContext.workflowId);
this.logger.error(`执行步骤 ${stepConfig.id} 时发生错误: ${stepError.message}`, stepError);
} finally {
// 无论成功或失败,更新执行记录的最终状态和结束时间
record.status = stepStatus;
record.endTime = new Date();
record.output = stepOutput; // 记录步骤的输出
record.error = stepError?.message; // 记录错误消息
// 触发 'step.[status]' 事件,例如 'step.completed', 'step.failed'
await this.emitEvent(`step.${stepStatus.toLowerCase()}`, workflowContext, {
stepId: stepConfig.id,
type: stepConfig.type,
error: stepError?.message,
output: stepOutput // (可选) 在事件中包含输出
});
}
// 返回包含执行结果的对象
return { status: stepStatus, nextStepId, output: stepOutput, error: stepError, record: record as IExecutionRecord };
}
// --- 任务步骤执行 (处理节点调用、重试、超时) ---
/**
* (内部方法) 执行单个任务步骤,包括调用节点、处理重试和超时。
* @param config 任务步骤的配置。
* @param workflowContext 当前工作流上下文。
* @returns 返回包含状态、输出和错误的执行结果对象 Promise。
*/
private async executeTaskStep(
config: TaskStepConfig,
workflowContext: IWorkflowContext
): Promise<{ status: NodeStatus; output?: any; error?: Error }> {
// 获取重试和超时配置,提供默认值
const retryOptions = config.retryOptions;
const maxRetries = retryOptions?.maxRetries ?? 0;
const initialDelayMs = retryOptions?.delayMs ?? 0;
const backoffFactor = retryOptions?.backoffFactor ?? 1; // 默认为 1 (无指数退避)
const timeoutMs = config.timeoutMs; // 超时配置 (可选)
let nodeOutput: any = undefined; // 存储节点最终的输出
let nodeError: Error | undefined = undefined; // 存储最终的错误
let nodeStatus: NodeStatus = NodeStatus.PENDING; // 初始化节点状态
// --- 重试循环 ---
for (let attempt = 0; attempt <= maxRetries; attempt++) {
// 为本次尝试创建节点上下文
const nodeContext = createNodeContext(workflowContext, config.id, config.nodeId, attempt);
// 查找注册的节点信息
const nodeInfo = this.nodeRegistry.get(config.nodeId);
// 检查节点是否已注册
if (!nodeInfo) {
throw new ConfigurationError(`节点 '${config.nodeId}' 未被注册。`);
}
// 1. 解析输入映射:将工作流数据映射到节点输入
this.resolveInputMapping(config, workflowContext, nodeContext);
let executionPromise: Promise<void>; // 用于执行节点逻辑的 Promise
nodeStatus = NodeStatus.RUNNING; // 设置状态为运行中
try {
// 2. 获取节点实现 (函数或 BaseNode 实例)
const implementation = nodeInfo.implementation;
let nodeExecuteFn: (ctx: INodeContext) => Promise<void>; // 标准化节点执行函数签名
// --- 根据节点类型准备执行函数 ---
if (nodeInfo.isClassInstance) {
// 如果是 BaseNode 实例
const nodeInstance = implementation as BaseNode;
// 2a. (可选) 检查角色权限
if (nodeInstance.requiredRoles && !this.checkRoles(workflowContext.userRole, nodeInstance.requiredRoles)) {
// 如果角色不满足,抛出授权错误
throw new AuthorizationError(`用户角色 '${workflowContext.userRole || '未提供'}' 无权执行节点 '${config.nodeId}'。需要角色: ${nodeInstance.requiredRoles.join(', ')}`);
}
// 2b. (可选) 调用节点的输入验证方法
if (nodeInstance.validate) {
this.logger.debug(`为节点 '${config.nodeId}' 执行输入验证...`);
await nodeInstance.validate(nodeContext); // 如果验证失败会抛出错误
}
// 绑定实例上下文,获取执行函数
nodeExecuteFn = nodeInstance.execute.bind(nodeInstance);
} else {
// 如果是简单函数
const simpleFunc = implementation as NodeFunction;
// 将简单函数包装成接受 INodeContext 的标准签名
// 注意:这里假设 NodeFunction 现在接收 INodeContext 作为第一个参数
nodeExecuteFn = async (ctx: INodeContext) => {
const result = await (simpleFunc as any)(ctx, ctx.input); // 调用原始函数
if (result !== undefined) {