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
JavaScript
;
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
}
}
};
}