UNPKG

@flowlab/all

Version:

A cool library focusing on handling various flows

147 lines (131 loc) 6.29 kB
import { NodeRegistry } from './nodeRegistry'; import { WorkflowDefinition } from './definition'; import { WorkflowExecutor } from './executor'; import { ILogger, IPersistence, IScheduler, IEventManager } from '../services'; // Assuming index exports import { BaseNode } from '../nodes/baseNode'; import { NodeFunction, WorkflowDefinitionData } from '../types/config'; import { IWorkflowContext } from '../types/runtime'; import { ConfigurationError } from '../errors'; export interface FlowLabEngineOptions { logger?: ILogger; persistence?: IPersistence; scheduler?: IScheduler; eventManager?: IEventManager; // Global engine settings maxLoopIterations?: number; } export class FlowLabEngine { readonly nodeRegistry: NodeRegistry; readonly logger: ILogger; readonly persistence?: IPersistence; readonly scheduler?: IScheduler; readonly eventManager?: IEventManager; readonly options: Readonly<FlowLabEngineOptions>; // Allow storing definitions in memory or loading from persistence private workflowDefinitions = new Map<string, WorkflowDefinition>(); constructor(options: FlowLabEngineOptions = {}) { this.options = { maxLoopIterations: 1000, // Default loop protection ...options }; this.nodeRegistry = new NodeRegistry(); this.persistence = options.persistence; this.scheduler = options.scheduler; this.eventManager = options.eventManager; // this.logger.info('FlowLabEngine initialized.'); if (this.persistence) this.logger.info(`Persistence enabled: ${this.persistence.constructor.name}`); if (this.scheduler) this.logger.info(`Scheduler enabled: ${this.scheduler.constructor.name}`); if (this.eventManager) this.logger.info(`EventManager enabled: ${this.eventManager.constructor.name}`); } // --- Node Registration (Delegated) --- registerNode(node: BaseNode): void; registerNode(id: string, func: NodeFunction, description?: string): void; registerNode(idOrNode: string | BaseNode, func?: NodeFunction, description?: string): void { this.nodeRegistry.register(idOrNode as any, func as any, description); } // --- Workflow Definition --- defineWorkflow(id: string, name?: string, description?: string): WorkflowDefinition { const definition = new WorkflowDefinition(id, name, description); // Store definition in memory for execution by ID // TODO: Decide if registration should be explicit via `registerDefinition` this.workflowDefinitions.set(id, definition); this.logger.info(`Workflow definition '${id}' created.`); return definition; } // Explicitly register a definition (e.g., after building or loading) registerDefinition(definition: WorkflowDefinition | WorkflowDefinitionData): void { const def = definition instanceof WorkflowDefinition ? definition : this._hydrateDefinition(definition); if (this.workflowDefinitions.has(def.id)) { this.logger.warn(`Overwriting existing definition for workflow ID '${def.id}'.`); } this.workflowDefinitions.set(def.id, def); // Optionally save to persistence if configured if (this.persistence?.saveDefinition) { this.persistence.saveDefinition(def.getData()) .catch(err => this.logger.error(`Failed to save definition '${def.id}' to persistence`, err)); } this.logger.info(`Workflow definition '${def.id}' registered.`); } async getDefinition(id: string): Promise<WorkflowDefinition | undefined> { let definition = this.workflowDefinitions.get(id); if (!definition && this.persistence?.loadDefinition) { this.logger.debug(`Definition '${id}' not in memory, attempting to load from persistence...`); const data = await this.persistence.loadDefinition(id); if (data) { definition = this._hydrateDefinition(data); this.workflowDefinitions.set(id, definition); // Cache in memory this.logger.info(`Definition '${id}' loaded from persistence.`); } else { this.logger.warn(`Definition '${id}' not found in persistence.`); } } return definition; } // Re-create WorkflowDefinition instance from data (e.g., loaded from DB) private _hydrateDefinition(data: WorkflowDefinitionData): WorkflowDefinition { const definition = new WorkflowDefinition(data.id, data.name, data.description); // Re-add steps from data for (const stepId in data.steps) { // This assumes _addStepInternal exists and accepts StepConfig (definition as any)._addStepInternal(data.steps[stepId]); } if (data.startStepId) { definition.setStartStep(data.startStepId); } // TODO: Handle versioning? return definition; } // --- Workflow Execution --- createExecutor(): WorkflowExecutor { // Pass engine dependencies to the executor return new WorkflowExecutor({ nodeRegistry: this.nodeRegistry, logger: this.logger, persistence: this.persistence, scheduler: this.scheduler, eventManager: this.eventManager, getWorkflowDefinition: this.getDefinition.bind(this), // Provide lookup function maxLoopIterations: this.options.maxLoopIterations, }); } /** * Convenience method to define AND run a simple workflow immediately. * Combines definition and execution for simple cases (closer to Core API). * Warning: Definition is ephemeral unless explicitly registered/saved. */ async runWorkflow( definitionBuilder: (builder: WorkflowDefinition) => void, input: Record<string, any>, contextExtras: Partial<IWorkflowContext> = {} // Allow passing tenantId, userId etc. ): Promise<IWorkflowContext> { // Create a temporary definition ID const tempId = `temp-${Date.now()}-${Math.random().toString(16).slice(2)}`; const builder = new WorkflowDefinition(tempId); definitionBuilder(builder); // Allow user to define steps via callback if (!builder.validate()) { throw new ConfigurationError(`Temporary workflow definition validation failed.`); } const executor = this.createExecutor(); return executor.run(builder, input, contextExtras); // Execute directly } }