@flowlab/all
Version:
A cool library focusing on handling various flows
147 lines (131 loc) • 6.29 kB
text/typescript
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
}
}