import { StepType, resolveProfile } from './config.js';
import { Wasm4pmError, ErrorCode, ErrorRecovery } from './errors.js';
const STEP_TYPE_TO_WASM = {
[StepType.DFG]: 'discover_dfg',
[StepType.ALPHA_PLUS_PLUS]: 'discover_alpha_plus_plus',
[StepType.HEURISTIC_MINER]: 'discover_heuristic_miner',
[StepType.INDUCTIVE_MINER]: 'discover_inductive_miner',
[StepType.GENETIC]: 'discover_genetic',
[StepType.PSO]: 'discover_pso',
[StepType.A_STAR]: 'discover_astar',
[StepType.ILP]: 'discover_ilp',
[StepType.ACO]: 'discover_ant_colony',
[StepType.SIMULATED_ANNEALING]: 'discover_simulated_annealing',
[StepType.STATISTICS]: 'analyze_statistics',
[StepType.CONFORMANCE]: 'check_conformance',
[StepType.VARIANTS]: 'analyze_variants',
[StepType.PERFORMANCE]: 'analyze_performance',
[StepType.CLUSTERING]: 'analyze_clustering',
[StepType.FILTER]: 'filter_log',
[StepType.TRANSFORM]: 'transform_log',
[StepType.VALIDATE]: 'validate_log',
};
export class PipelineResolver {
constructor() {
this.stepTypeToWasm = { ...STEP_TYPE_TO_WASM };
}
resolve(config) {
let configSteps;
if (config.pipeline && config.pipeline.length > 0) {
configSteps = config.pipeline;
} else {
configSteps = resolveProfile(config.execution.profile);
}
const executableSteps = [];
for (const configStep of configSteps) {
const wasmFunction = this.stepTypeToWasm[configStep.type];
if (!wasmFunction) {
throw new Wasm4pmError(
`No WASM binding for step type: ${configStep.type}`,
ErrorCode.CONFIG_INVALID,
{
nextAction: ErrorRecovery.CONTACT_SUPPORT,
context: {
stepType: configStep.type,
availableTypes: Object.keys(this.stepTypeToWasm),
},
}
);
}
const executableStep = {
stepId: configStep.id,
type: configStep.type,
wasmFunction,
params: configStep.parameters || {},
dependencies: configStep.dependsOn || [],
timeout: config.execution.timeoutMs,
retryable: !configStep.required, required: configStep.required,
};
executableSteps.push(executableStep);
}
this.validateDependencies(executableSteps);
return topologicalSort(executableSteps);
}
validateDependencies(steps) {
const stepIds = new Set(steps.map((s) => s.stepId));
for (const step of steps) {
for (const dep of step.dependencies) {
if (!stepIds.has(dep)) {
throw new Wasm4pmError(
`Step "${step.stepId}" depends on non-existent step "${dep}"`,
ErrorCode.CONFIG_INVALID,
{
nextAction: ErrorRecovery.RECONFIGURE,
context: {
step: step.stepId,
missingDependency: dep,
availableSteps: Array.from(stepIds),
},
}
);
}
}
}
}
getAvailableStepTypes() {
return Object.keys(this.stepTypeToWasm);
}
getWasmFunction(stepType) {
return this.stepTypeToWasm[stepType];
}
}
export function topologicalSort(steps) {
const stepMap = new Map(steps.map((s) => [s.stepId, s]));
const visited = new Set();
const sorted = [];
const recursionStack = new Set();
function visit(stepId) {
if (visited.has(stepId)) {
return;
}
if (recursionStack.has(stepId)) {
throw new Wasm4pmError(
`Circular dependency detected in pipeline: ${stepId}`,
ErrorCode.EXECUTION_FAILED,
{
nextAction: ErrorRecovery.RECONFIGURE,
context: { step: stepId },
}
);
}
recursionStack.add(stepId);
const step = stepMap.get(stepId);
if (step) {
for (const dep of step.dependencies) {
visit(dep);
}
}
recursionStack.delete(stepId);
visited.add(stepId);
if (step) {
sorted.push(step);
}
}
for (const step of steps) {
visit(step.stepId);
}
return sorted;
}
export function getTransitiveDependencies(stepId, steps) {
const stepMap = new Map(steps.map((s) => [s.stepId, s]));
const dependencies = new Set();
function collectDeps(id) {
const step = stepMap.get(id);
if (!step) return;
for (const dep of step.dependencies) {
if (!dependencies.has(dep)) {
dependencies.add(dep);
collectDeps(dep);
}
}
}
collectDeps(stepId);
return dependencies;
}