@flowlab/all
Version:
A cool library focusing on handling various flows
110 lines (98 loc) • 4.89 kB
text/typescript
// src/config/loader.ts
import * as fs from 'fs/promises';
import * as path from 'path';
import * as yaml from 'js-yaml';
import { DataPipeline } from '../core/pipeline';
import { PipelineConfig, PipelineStepConfig } from '../core/interfaces';
import { createExtractor, createLoader, createPipelineStep } from '../core/registry';
import { ConfigurationError } from '../core/errors';
import { Logger } from 'pino';
import { createLogger } from '../utils/logger';
/**
* Loads pipeline configuration from a JSON or YAML file.
* @param configPath Path to the configuration file.
* @param logger Optional logger instance.
* @returns A configured DataPipeline instance.
*/
export async function loadPipelineFromConfig(
configPath: string,
logger: Logger = createLogger()
): Promise<DataPipeline<any, any>> { // Returns pipeline with 'any' types, specific types are internal
logger.info(`Loading pipeline configuration from: ${configPath}`);
let rawConfig: string;
try {
rawConfig = await fs.readFile(configPath, 'utf-8');
} catch (error: any) {
throw new ConfigurationError(`Failed to read configuration file: ${configPath}`, configPath, error.message);
}
let config: PipelineConfig;
try {
const ext = path.extname(configPath).toLowerCase();
if (ext === '.yaml' || ext === '.yml') {
config = yaml.load(rawConfig) as PipelineConfig;
} else if (ext === '.json') {
config = JSON.parse(rawConfig);
} else {
throw new ConfigurationError(`Unsupported configuration file format: ${ext}. Use .json or .yaml/.yml.`);
}
logger.debug({ config }, 'Configuration loaded successfully.');
// TODO: Add validation using Zod or similar schema validator here
} catch (error: any) {
throw new ConfigurationError(`Failed to parse configuration file: ${configPath}`, configPath, error.message);
}
if (!config || !config.id || !config.source || !config.steps || !config.target) {
throw new ConfigurationError(`Invalid configuration structure in ${configPath}. Missing required fields (id, source, steps, target).`);
}
const pipeline = new DataPipeline<any, any>(config.id, { logger });
// Configure pipeline options
if (config.options) {
pipeline.configure({
batchSize: config.options.batchSize,
retries: config.options.errorHandling?.retries,
retryDelay: config.options.errorHandling?.delay,
// Pass other options if DataPipeline supports them
});
logger.info({ options: config.options }, 'Pipeline options configured.');
}
// Instantiate and set extractor
try {
const extractor = createExtractor(config.source);
pipeline.extract(extractor);
logger.info(`Extractor '${config.source.type}' configured.`);
} catch (error) {
if (error instanceof ConfigurationError || error instanceof ComponentError) throw error;
throw new ComponentError(`Error configuring extractor from config`, 'Extractor', error as Error);
}
// Instantiate and add steps
try {
config.steps.forEach((stepConfig, index) => {
const stepComponent = createPipelineStep(stepConfig);
// The pipeline's chain methods don't retain specific types well here,
// so we cast internally. The actual execution relies on the component interfaces.
if ('clean' in stepComponent) {
pipeline.clean(stepComponent as any);
logger.info(`Step ${index + 1}: Cleaner '${stepConfig.cleaner || stepConfig.type}' configured.`);
} else if ('transform' in stepComponent) {
pipeline.transform(stepComponent as any);
logger.info(`Step ${index + 1}: Transformer '${stepConfig.transformer || stepConfig.type}' configured.`);
} else {
// Should not happen if createPipelineStep is correct
throw new ConfigurationError(`Invalid component returned for step ${index + 1}`);
}
});
} catch (error) {
if (error instanceof ConfigurationError || error instanceof ComponentError) throw error;
throw new ComponentError(`Error configuring pipeline steps from config`, 'Steps', error as Error);
}
// Instantiate and set loader
try {
const loader = createLoader(config.target);
pipeline.load(loader);
logger.info(`Loader '${config.target.type}' configured.`);
} catch (error) {
if (error instanceof ConfigurationError || error instanceof ComponentError) throw error;
throw new ComponentError(`Error configuring loader from config`, 'Loader', error as Error);
}
logger.info(`Pipeline '${config.id}' successfully created from configuration.`);
return pipeline;
}