UNPKG

@flowlab/all

Version:

A cool library focusing on handling various flows

65 lines (56 loc) 3.29 kB
// src/transformers/customFunctionTransformer.ts import * as path from 'path'; import { ITransformer, PipelineContext, PipelineStepConfig } from '../core/interfaces'; import { ComponentError, ConfigurationError } from '../core/errors'; interface CustomFunctionConfig extends PipelineStepConfig { type: 'custom'; customFunctionPath: string; // Path relative to project root or absolute // Optionally pass other config from the step to the function functionConfig?: any; } type TransformFunction<TInput, TOutput> = (data: TInput, context: PipelineContext, config?: any) => Promise<TOutput> | TOutput; export class CustomFunctionTransformer<TInput, TOutput> implements ITransformer<TInput, TOutput> { private transformFn: TransformFunction<TInput, TOutput> | null = null; private config: CustomFunctionConfig; private functionPath: string; constructor(config: CustomFunctionConfig) { if (!config.customFunctionPath) { throw new ConfigurationError(`CustomFunctionTransformer requires 'customFunctionPath' in step config.`); } this.config = config; // Resolve path - IMPORTANT: Consider security implications of loading arbitrary code. // This assumes path is relative to CWD or absolute. Might need better resolution logic. this.functionPath = path.resolve(process.cwd(), this.config.customFunctionPath); } private async loadFunction(context: PipelineContext): Promise<void> { if (this.transformFn) return; // Already loaded context.logger.info(`Loading custom transform function from: ${this.functionPath}`); try { // Use dynamic import() const module = await import(this.functionPath); if (typeof module.default !== 'function') { throw new ConfigurationError(`Module ${this.functionPath} does not have a default export function.`); } this.transformFn = module.default; context.logger.debug(`Custom transform function loaded successfully.`); } catch (error: any) { context.logger.error({ err: error, path: this.functionPath }, `Failed to load custom transform function.`); throw new ComponentError(`Failed to load custom transform function from ${this.functionPath}`, 'CustomFunctionTransformer', error); } } async transform(data: TInput, context: PipelineContext): Promise<TOutput> { await this.loadFunction(context); // Load function on first call if (!this.transformFn) { // Should not happen if loadFunction succeeded or threw throw new ComponentError('Custom transform function not loaded.', 'CustomFunctionTransformer'); } try { // Pass data, context, and optional step config to the function return await Promise.resolve(this.transformFn(data, context, this.config.functionConfig)); } catch (error: any) { context.logger.error({ err: error, functionPath: this.functionPath, item: data }, `Error executing custom transform function`); throw new ComponentError(`Error during custom transformation from ${this.functionPath}`, 'CustomFunctionTransformer', error); } } }