UNPKG

chrono-forge

Version:

A comprehensive framework for building resilient Temporal workflows, advanced state management, and real-time streaming activities in TypeScript. Designed for a seamless developer experience with powerful abstractions, dynamic orchestration, and full cont

214 lines (213 loc) 8.45 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DSLInterpreter = DSLInterpreter; exports.convertStepsToDSL = convertStepsToDSL; const workflow_1 = require("@temporalio/workflow"); const eventemitter3_graphology_1 = require("eventemitter3-graphology"); const graphology_dag_1 = require("graphology-dag"); async function DSLInterpreter(dsl, injectedActivities, injectedSteps) { const acts = injectedActivities || (0, workflow_1.proxyActivities)({ startToCloseTimeout: '1 minute' }); const steps = injectedSteps || {}; const bindings = dsl.variables; const graph = buildDependencyGraph(dsl.plan, bindings); return await executeGraphByGenerations(graph, bindings, acts, steps); } function buildDependencyGraph(plan, bindings) { const graph = new eventemitter3_graphology_1.DirectedGraph(); const createExecuteFunction = (type, name, args, result) => { return async ({ activities, steps }) => { const executorMap = type === 'activity' ? activities : steps; if (!executorMap?.[name]) { throw new Error(`${type} function '${name}' not found`); } let resolvedArgs = args.map((arg) => { try { if (graph.hasNodeAttribute(arg, 'result')) { return graph.getNodeAttribute(arg, 'result'); } } catch (e) { if (bindings[arg] !== undefined) { return bindings[arg]; } return arg; } }); const output = await executorMap[name](...resolvedArgs); if (result && output !== undefined) { bindings[result] = output; graph.setNodeAttribute(result, 'result', output); } return output; }; }; const addNodeAndDependencies = (type, name, args = [], result) => { const nodeId = result ?? name; if (!graph.hasNode(nodeId)) { graph.addNode(nodeId, { type, execute: createExecuteFunction(type, name, args, result) }); } for (const arg of args) { if ((graph.hasNode(arg) || bindings[arg] !== undefined) && !graph.hasEdge(arg, nodeId)) { try { graph.addDirectedEdge(arg, nodeId); } catch (e) { } } } }; const processStatement = (statement) => { if ('execute' in statement) { if (statement.execute.activity) { const { name, arguments: args = [], result } = statement.execute.activity; addNodeAndDependencies('activity', name, args, result); } else if (statement.execute.step) { const { name, arguments: args = [], result } = statement.execute.step; addNodeAndDependencies('step', name, args, result); } else if (statement.execute.code) { console.warn('Code execution not implemented'); } } else if ('sequence' in statement) { statement.sequence.elements.forEach(processStatement); } else if ('parallel' in statement) { statement.parallel.branches.forEach(processStatement); } }; processStatement(plan); if ((0, graphology_dag_1.hasCycle)(graph)) { throw new Error('Circular dependency detected in workflow graph'); } console.log(visualizeWorkflowGenerations(graph)); return graph; } async function executeGraphByGenerations(graph, bindings, activities, steps) { const generations = (0, graphology_dag_1.topologicalGenerations)(graph); if (generations.length === 0) { console.warn('No generations found in the graph. Skipping execution.'); return; } for (let genIndex = 0; genIndex < generations.length; genIndex++) { const generation = generations[genIndex]; console.log(`Executing generation ${genIndex} with ${generation.length} nodes: ${generation.join(', ')}`); await executeGenerationWithErrorHandling(generation, graph, activities, steps); } console.log('Workflow completed successfully'); } function visualizeWorkflowGenerations(graph) { const generations = (0, graphology_dag_1.topologicalGenerations)(graph); let visualization = 'Workflow Execution Plan:\n'; generations.forEach((generation, index) => { visualization += `\nGeneration ${index}:\n`; generation.forEach((nodeId) => { const dependencies = graph.inNeighbors(nodeId); const nodeType = graph.hasNodeAttribute(nodeId, 'type') ? graph.getNodeAttribute(nodeId, 'type') : 'unknown'; visualization += ` - ${nodeId} [${nodeType}]${dependencies.length > 0 ? ` (depends on: ${dependencies.join(', ')})` : ''}\n`; }); }); return visualization; } async function executeGenerationWithErrorHandling(generation, graph, activities, steps) { const results = await Promise.allSettled(generation.map(async (nodeId) => { try { if (graph.hasNodeAttribute(nodeId, 'execute')) { const execute = graph.getNodeAttribute(nodeId, 'execute'); await execute({ activities, steps }); } } catch (error) { console.error(`Error executing node ${nodeId}:`, error); throw error; } })); const failures = results.filter((r) => r.status === 'rejected'); if (failures.length > 0) { throw new Error(`${failures.length} operations failed in this generation`); } } function convertStepsToDSL(steps, initialVariables = {}) { if (!steps || steps.length === 0) { return { variables: initialVariables, plan: { sequence: { elements: [] } } }; } const graph = new eventemitter3_graphology_1.DirectedGraph(); for (const step of steps) { graph.addNode(step.name, { metadata: step }); } for (const step of steps) { if (step.before) { const beforeSteps = Array.isArray(step.before) ? step.before : [step.before]; for (const beforeStep of beforeSteps) { if (graph.hasNode(beforeStep)) { graph.addDirectedEdge(step.name, beforeStep); } } } if (step.after) { const afterSteps = Array.isArray(step.after) ? step.after : [step.after]; for (const afterStep of afterSteps) { if (graph.hasNode(afterStep)) { graph.addDirectedEdge(afterStep, step.name); } } } } if ((0, graphology_dag_1.hasCycle)(graph)) { throw new Error('Circular dependency detected in workflow steps'); } const generations = (0, graphology_dag_1.topologicalGenerations)(graph); const dslElements = []; for (const generation of generations) { if (generation.length === 1) { const stepName = generation[0]; const stepMeta = steps.find((s) => s.name === stepName); if (stepMeta) { dslElements.push({ execute: { step: { name: stepMeta.method, result: stepName } } }); } } else if (generation.length > 1) { const parallelBranches = []; for (const stepName of generation) { const stepMeta = steps.find((s) => s.name === stepName); if (stepMeta) { parallelBranches.push({ execute: { step: { name: stepMeta.method, result: stepName } } }); } } dslElements.push({ parallel: { branches: parallelBranches } }); } } return { variables: initialVariables, plan: { sequence: { elements: dslElements } } }; }