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