UNPKG

temporal-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

279 lines (278 loc) 10.8 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); const generations = (0, graphology_dag_1.topologicalGenerations)(graph); if (generations.length === 0) { console.warn('No generations found in the graph. Skipping execution.'); return; } console.log(visualizeWorkflowGenerations(graph)); for (const generation of generations) { for (const nodeId of generation) { const node = graph.getNodeAttributes(nodeId); if (!node?.execute) continue; if (node.condition) { const conditionMet = await node.condition(dsl); if (!conditionMet) continue; } yield { nodeId, graph, bindings: dsl.variables, acts, steps, nodeIds: generation, execute: async () => await node.execute({ activities: acts, steps }) }; } } console.log('Workflow completed successfully'); } async function executeNode(nodeId, graph, activities, steps) { try { const node = graph.getNodeAttributes(nodeId); if (!node.execute) { throw new Error(`No execute function found for node ${nodeId}`); } return await node.execute({ activities, steps }); } catch (error) { console.error(`Error executing node ${nodeId}:`, error); throw error; } } function buildDependencyGraph(plan, bindings) { const graph = new eventemitter3_graphology_1.DirectedGraph(); let autoIncrementId = 0; const createExecuteFunction = (type, nodeId, name, args, result, condition) => { 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) => { if (bindings[arg] !== undefined) { return bindings[arg]; } if (graph.hasNode(arg)) { const nodeResult = graph.getNodeAttribute(arg, 'result'); if (nodeResult !== undefined) { return nodeResult; } } return arg; }); const output = await executorMap[name](...resolvedArgs); if (output !== undefined) { if (result) { bindings[result] = output; } graph.setNodeAttribute(nodeId, 'result', output); } return output; }; }; const addNodeAndDependencies = (type, name, args, result, condition) => { const nodeId = `${type}_${name}_${autoIncrementId++}`; graph.addNode(nodeId, { type, name, args, result, condition, execute: createExecuteFunction(type, nodeId, name, args, result, condition) }); for (const arg of args) { const dependencyNodes = Array.from(graph.nodes()).filter((n) => { const nodeResult = graph.getNodeAttribute(n, 'result'); return nodeResult === arg; }); for (const depNode of dependencyNodes) { try { graph.addDirectedEdge(depNode, nodeId); } catch (e) { } } } return nodeId; }; const processStatement = (statement, previousNodeId) => { if ('execute' in statement) { let nodeId; if (statement.execute.activity) { const { name, arguments: args = [], result } = statement.execute.activity; nodeId = addNodeAndDependencies('activity', name, args, result, statement.condition); } else if (statement.execute.step) { const { name, arguments: args = [], result } = statement.execute.step; nodeId = addNodeAndDependencies('step', name, args, result, statement.condition); } if (nodeId && previousNodeId) { try { graph.addDirectedEdge(previousNodeId, nodeId); } catch (e) { } } return nodeId; } else if ('sequence' in statement) { let lastNodeId; for (const element of statement.sequence.elements) { lastNodeId = processStatement(element, lastNodeId); } return lastNodeId; } else if ('parallel' in statement) { const startNodeId = previousNodeId; const nodeIds = statement.parallel.branches .map((branch) => { return processStatement(branch, startNodeId); }) .filter((id) => id !== undefined); return nodeIds.length > 0 ? nodeIds[nodeIds.length - 1] : undefined; } return undefined; }; processStatement(plan); if ((0, graphology_dag_1.hasCycle)(graph)) { throw new Error('Circular dependency detected in workflow 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 } } }; }