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