@allternit/workflow-engine 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,532 +0,0 @@
1
- /**
2
- * Workflow Engine
3
- *
4
- * Core engine for executing workflows with DAG support.
5
- */
6
- import { v4 as uuidv4 } from 'uuid';
7
- /**
8
- * Create workflow engine
9
- */
10
- export function createWorkflowEngine(config = {}) {
11
- const workflows = new Map();
12
- const executions = new Map();
13
- const nodeTypes = new Map();
14
- const activeExecutions = new Set();
15
- const { maxConcurrentExecutions = 10, defaultTimeout = 300000, // 5 minutes
16
- enableHistory = true, hooks = {}, } = config;
17
- /**
18
- * Register built-in node types
19
- */
20
- function registerBuiltInNodeTypes() {
21
- // Input nodes
22
- registerNodeType({
23
- type: 'trigger:manual',
24
- category: 'input',
25
- displayName: 'Manual Trigger',
26
- description: 'Triggered manually or via API',
27
- outputs: [{ name: 'data', type: 'any' }],
28
- executor: async (_, __, inputs) => inputs,
29
- });
30
- registerNodeType({
31
- type: 'trigger:schedule',
32
- category: 'input',
33
- displayName: 'Schedule Trigger',
34
- description: 'Triggered on a schedule',
35
- outputs: [{ name: 'timestamp', type: 'string' }],
36
- executor: async (_, __, inputs) => inputs,
37
- });
38
- registerNodeType({
39
- type: 'trigger:webhook',
40
- category: 'input',
41
- displayName: 'Webhook Trigger',
42
- description: 'Triggered by HTTP webhook',
43
- outputs: [{ name: 'body', type: 'any' }, { name: 'headers', type: 'object' }],
44
- executor: async (_, __, inputs) => inputs,
45
- });
46
- // Transform nodes
47
- registerNodeType({
48
- type: 'transform:map',
49
- category: 'transform',
50
- displayName: 'Map',
51
- description: 'Transform data using expression',
52
- inputs: [{ name: 'data', type: 'any', required: true }],
53
- outputs: [{ name: 'result', type: 'any' }],
54
- configSchema: [
55
- { name: 'expression', type: 'string', required: true, description: 'JavaScript expression' },
56
- ],
57
- executor: async (node, context, inputs) => {
58
- const expression = node.config?.expression;
59
- if (!expression)
60
- throw new Error('Expression required');
61
- // Simple expression evaluation
62
- const data = inputs.data;
63
- const result = new Function('data', 'context', `return ${expression}`)(data, context);
64
- return { result };
65
- },
66
- });
67
- registerNodeType({
68
- type: 'transform:filter',
69
- category: 'transform',
70
- displayName: 'Filter',
71
- description: 'Filter array items',
72
- inputs: [{ name: 'array', type: 'array', required: true }],
73
- outputs: [{ name: 'filtered', type: 'array' }],
74
- configSchema: [
75
- { name: 'condition', type: 'string', required: true, description: 'Filter condition (item, index) => boolean' },
76
- ],
77
- executor: async (node, _, inputs) => {
78
- const array = inputs.array;
79
- const condition = node.config?.condition;
80
- if (!condition)
81
- return { filtered: array };
82
- const filterFn = new Function('item', 'index', `return ${condition}`);
83
- const filtered = array.filter((item, index) => filterFn(item, index));
84
- return { filtered };
85
- },
86
- });
87
- // Condition nodes
88
- registerNodeType({
89
- type: 'condition:if',
90
- category: 'condition',
91
- displayName: 'If Condition',
92
- description: 'Branch based on condition',
93
- inputs: [{ name: 'data', type: 'any' }],
94
- outputs: [
95
- { name: 'true', type: 'any' },
96
- { name: 'false', type: 'any' },
97
- ],
98
- configSchema: [
99
- { name: 'condition', type: 'string', required: true, description: 'Boolean expression' },
100
- ],
101
- executor: async (node, context, inputs) => {
102
- const condition = node.config?.condition;
103
- if (!condition)
104
- throw new Error('Condition required');
105
- const result = new Function('data', 'context', `return ${condition}`)(inputs.data, context);
106
- return result ? { true: inputs.data } : { false: inputs.data };
107
- },
108
- });
109
- // Loop nodes
110
- registerNodeType({
111
- type: 'loop:for-each',
112
- category: 'loop',
113
- displayName: 'For Each',
114
- description: 'Iterate over array items',
115
- inputs: [{ name: 'array', type: 'array', required: true }],
116
- outputs: [{ name: 'results', type: 'array' }],
117
- configSchema: [
118
- { name: 'parallel', type: 'boolean', default: false, description: 'Execute in parallel' },
119
- { name: 'maxConcurrency', type: 'number', default: 5, description: 'Max parallel executions' },
120
- ],
121
- executor: async (node, context, inputs) => {
122
- // For-each logic handled by scheduler
123
- return { items: inputs.array };
124
- },
125
- });
126
- // Delay nodes
127
- registerNodeType({
128
- type: 'delay:wait',
129
- category: 'delay',
130
- displayName: 'Wait',
131
- description: 'Pause execution',
132
- inputs: [{ name: 'data', type: 'any' }],
133
- outputs: [{ name: 'data', type: 'any' }],
134
- configSchema: [
135
- { name: 'delay', type: 'number', required: true, description: 'Delay in milliseconds' },
136
- ],
137
- executor: async (node, _, inputs) => {
138
- const delay = node.config?.delay || 1000;
139
- await new Promise(resolve => setTimeout(resolve, delay));
140
- return { data: inputs.data };
141
- },
142
- });
143
- // HTTP nodes
144
- registerNodeType({
145
- type: 'http:request',
146
- category: 'http',
147
- displayName: 'HTTP Request',
148
- description: 'Make HTTP request',
149
- inputs: [
150
- { name: 'url', type: 'string', required: true },
151
- { name: 'body', type: 'any' },
152
- ],
153
- outputs: [
154
- { name: 'response', type: 'object' },
155
- { name: 'status', type: 'number' },
156
- ],
157
- configSchema: [
158
- { name: 'method', type: 'string', default: 'GET', description: 'HTTP method' },
159
- { name: 'headers', type: 'object', description: 'Request headers' },
160
- { name: 'timeout', type: 'number', default: 30000, description: 'Timeout in ms' },
161
- ],
162
- executor: async (node, _, inputs) => {
163
- const method = node.config?.method || 'GET';
164
- const url = inputs.url;
165
- const headers = node.config?.headers || {};
166
- const timeout = node.config?.timeout || 30000;
167
- const controller = new AbortController();
168
- const timeoutId = setTimeout(() => controller.abort(), timeout);
169
- try {
170
- const response = await fetch(url, {
171
- method,
172
- headers,
173
- body: inputs.body ? JSON.stringify(inputs.body) : undefined,
174
- signal: controller.signal,
175
- });
176
- clearTimeout(timeoutId);
177
- const data = await response.json().catch(() => null);
178
- const responseHeaders = {};
179
- response.headers.forEach((value, key) => {
180
- responseHeaders[key] = value;
181
- });
182
- return {
183
- response: data,
184
- status: response.status,
185
- headers: responseHeaders,
186
- };
187
- }
188
- catch (error) {
189
- clearTimeout(timeoutId);
190
- throw error;
191
- }
192
- },
193
- });
194
- // Output nodes
195
- registerNodeType({
196
- type: 'output:result',
197
- category: 'output',
198
- displayName: 'Set Result',
199
- description: 'Set workflow output',
200
- inputs: [{ name: 'data', type: 'any' }],
201
- executor: async (_, __, inputs) => inputs,
202
- });
203
- registerNodeType({
204
- type: 'output:log',
205
- category: 'output',
206
- displayName: 'Log',
207
- description: 'Log message to console',
208
- inputs: [{ name: 'message', type: 'any' }],
209
- configSchema: [
210
- { name: 'level', type: 'string', default: 'info', description: 'Log level' },
211
- ],
212
- executor: async (node, _, inputs) => {
213
- const level = node.config?.level || 'info';
214
- console[level]?.('Workflow log:', inputs.message);
215
- return { message: inputs.message };
216
- },
217
- });
218
- }
219
- /**
220
- * Register workflow
221
- */
222
- function registerWorkflow(workflow) {
223
- workflows.set(workflow.id, workflow);
224
- }
225
- /**
226
- * Get workflow
227
- */
228
- function getWorkflow(id) {
229
- return workflows.get(id);
230
- }
231
- /**
232
- * List workflows
233
- */
234
- function listWorkflows() {
235
- return Array.from(workflows.values());
236
- }
237
- /**
238
- * Delete workflow
239
- */
240
- function deleteWorkflow(id) {
241
- return workflows.delete(id);
242
- }
243
- /**
244
- * Execute workflow
245
- */
246
- async function execute(workflowId, inputs = {}) {
247
- const workflow = workflows.get(workflowId);
248
- if (!workflow) {
249
- throw new Error(`Workflow not found: ${workflowId}`);
250
- }
251
- // Check concurrent execution limit
252
- if (activeExecutions.size >= maxConcurrentExecutions) {
253
- throw new Error('Max concurrent executions reached');
254
- }
255
- const executionId = uuidv4();
256
- // Build DAG
257
- const dag = buildDAG(workflow);
258
- // Initialize execution
259
- const execution = {
260
- id: executionId,
261
- workflowId,
262
- status: 'pending',
263
- inputs,
264
- context: {
265
- variables: { ...inputs },
266
- state: {
267
- activeNodes: [],
268
- completedNodes: [],
269
- failedNodes: [],
270
- nodeResults: {},
271
- executionPath: [],
272
- },
273
- },
274
- nodeExecutions: [],
275
- startedAt: new Date().toISOString(),
276
- };
277
- executions.set(executionId, execution);
278
- activeExecutions.add(executionId);
279
- // Trigger before execute hook
280
- await hooks.beforeExecute?.(execution);
281
- // Start execution
282
- execution.status = 'running';
283
- try {
284
- // Execute starting nodes (no incoming connections)
285
- const startNodes = dag.nodes.filter(n => !dag.connections.some(c => c.target === n.id));
286
- await Promise.all(startNodes.map(node => executeNode(node, execution, dag)));
287
- execution.status = 'completed';
288
- execution.completedAt = new Date().toISOString();
289
- // Collect outputs
290
- const outputNodes = workflow.nodes.filter(n => n.type === 'output:result');
291
- execution.outputs = {};
292
- for (const node of outputNodes) {
293
- const result = execution.context?.state.nodeResults[node.id];
294
- if (result) {
295
- Object.assign(execution.outputs, result);
296
- }
297
- }
298
- }
299
- catch (error) {
300
- execution.status = 'failed';
301
- execution.error = {
302
- code: 'EXECUTION_ERROR',
303
- message: error instanceof Error ? error.message : String(error),
304
- };
305
- await hooks.onError?.(execution.error, execution.context);
306
- }
307
- finally {
308
- activeExecutions.delete(executionId);
309
- await hooks.afterExecute?.(execution);
310
- }
311
- return execution;
312
- }
313
- /**
314
- * Build DAG from workflow
315
- */
316
- function buildDAG(workflow) {
317
- return {
318
- nodes: workflow.nodes,
319
- connections: workflow.connections,
320
- };
321
- }
322
- /**
323
- * Execute a node
324
- */
325
- async function executeNode(node, execution, dag) {
326
- if (!execution.context)
327
- return;
328
- const nodeType = nodeTypes.get(node.type);
329
- if (!nodeType) {
330
- throw new Error(`Unknown node type: ${node.type}`);
331
- }
332
- // Check if already executed
333
- if (execution.context.state.completedNodes.includes(node.id)) {
334
- return;
335
- }
336
- // Check dependencies
337
- const incomingConnections = dag.connections.filter(c => c.target === node.id);
338
- const dependenciesMet = incomingConnections.every(c => execution.context.state.completedNodes.includes(c.source) ||
339
- execution.context.state.nodeResults[c.source] !== undefined);
340
- if (!dependenciesMet) {
341
- return; // Will be executed when dependencies complete
342
- }
343
- // Mark as active
344
- execution.context.state.activeNodes.push(node.id);
345
- execution.context.state.executionPath.push(node.id);
346
- // Prepare inputs
347
- const inputs = {};
348
- // Get inputs from connections
349
- incomingConnections.forEach(conn => {
350
- const sourceResult = execution.context.state.nodeResults[conn.source];
351
- if (sourceResult) {
352
- const portName = conn.sourcePort || 'data';
353
- inputs[portName] = sourceResult;
354
- }
355
- });
356
- // Apply input mappings
357
- node.inputs?.forEach(mapping => {
358
- const value = evaluateMapping(mapping.source, execution.context);
359
- if (value !== undefined || mapping.default !== undefined) {
360
- inputs[mapping.target] = value ?? mapping.default;
361
- }
362
- });
363
- // Create node execution
364
- const nodeExecution = {
365
- nodeId: node.id,
366
- status: 'running',
367
- inputs,
368
- startedAt: new Date().toISOString(),
369
- };
370
- execution.nodeExecutions = execution.nodeExecutions || [];
371
- execution.nodeExecutions.push(nodeExecution);
372
- // Trigger before node execute hook
373
- await hooks.beforeNodeExecute?.(node, execution.context);
374
- try {
375
- // Execute node
376
- const executor = nodeType.executor;
377
- if (!executor) {
378
- throw new Error(`No executor for node type: ${node.type}`);
379
- }
380
- const result = await executor(node, execution.context, inputs);
381
- // Store result
382
- execution.context.state.nodeResults[node.id] = result;
383
- execution.context.state.completedNodes.push(node.id);
384
- // Update node execution
385
- nodeExecution.status = 'completed';
386
- nodeExecution.outputs = result;
387
- nodeExecution.completedAt = new Date().toISOString();
388
- // Trigger after node execute hook
389
- await hooks.afterNodeExecute?.(node, execution.context, result);
390
- // Execute next nodes
391
- const outgoingConnections = dag.connections.filter(c => c.source === node.id);
392
- for (const conn of outgoingConnections) {
393
- // Check condition
394
- if (conn.condition) {
395
- const conditionMet = evaluateCondition(conn.condition, result, execution.context);
396
- if (!conditionMet)
397
- continue;
398
- }
399
- const nextNode = dag.nodes.find(n => n.id === conn.target);
400
- if (nextNode) {
401
- await executeNode(nextNode, execution, dag);
402
- }
403
- }
404
- }
405
- catch (error) {
406
- nodeExecution.status = 'failed';
407
- nodeExecution.error = {
408
- code: 'NODE_EXECUTION_ERROR',
409
- message: error instanceof Error ? error.message : String(error),
410
- nodeId: node.id,
411
- };
412
- execution.context.state.failedNodes.push(node.id);
413
- throw error;
414
- }
415
- finally {
416
- // Remove from active
417
- execution.context.state.activeNodes = execution.context.state.activeNodes.filter(id => id !== node.id);
418
- }
419
- }
420
- /**
421
- * Evaluate mapping expression
422
- */
423
- function evaluateMapping(expression, context) {
424
- try {
425
- // Simple variable substitution
426
- if (expression.startsWith('${') && expression.endsWith('}')) {
427
- const path = expression.slice(2, -1);
428
- return getValueByPath(context.variables, path);
429
- }
430
- return expression;
431
- }
432
- catch {
433
- return undefined;
434
- }
435
- }
436
- /**
437
- * Evaluate condition
438
- */
439
- function evaluateCondition(condition, result, context) {
440
- try {
441
- return new Function('result', 'context', `return ${condition}`)(result, context);
442
- }
443
- catch {
444
- return false;
445
- }
446
- }
447
- /**
448
- * Get value by path
449
- */
450
- function getValueByPath(obj, path) {
451
- return path.split('.').reduce((acc, key) => {
452
- if (acc && typeof acc === 'object') {
453
- return acc[key];
454
- }
455
- return undefined;
456
- }, obj);
457
- }
458
- /**
459
- * Get execution
460
- */
461
- function getExecution(executionId) {
462
- return executions.get(executionId);
463
- }
464
- /**
465
- * Cancel execution
466
- */
467
- function cancelExecution(executionId) {
468
- const execution = executions.get(executionId);
469
- if (execution && execution.status === 'running') {
470
- execution.status = 'cancelled';
471
- execution.completedAt = new Date().toISOString();
472
- activeExecutions.delete(executionId);
473
- return true;
474
- }
475
- return false;
476
- }
477
- /**
478
- * Pause execution
479
- */
480
- function pauseExecution(executionId) {
481
- const execution = executions.get(executionId);
482
- if (execution && execution.status === 'running') {
483
- execution.status = 'paused';
484
- return true;
485
- }
486
- return false;
487
- }
488
- /**
489
- * Resume execution
490
- */
491
- function resumeExecution(executionId) {
492
- const execution = executions.get(executionId);
493
- if (execution && execution.status === 'paused') {
494
- execution.status = 'running';
495
- // TODO: Resume execution logic
496
- return true;
497
- }
498
- return false;
499
- }
500
- /**
501
- * Register node type
502
- */
503
- function registerNodeType(nodeType) {
504
- nodeTypes.set(nodeType.type, nodeType);
505
- }
506
- /**
507
- * Get node type
508
- */
509
- function getNodeType(type) {
510
- return nodeTypes.get(type);
511
- }
512
- // Initialize
513
- registerBuiltInNodeTypes();
514
- return {
515
- registerWorkflow,
516
- getWorkflow,
517
- listWorkflows,
518
- deleteWorkflow,
519
- execute,
520
- getExecution,
521
- cancelExecution,
522
- pauseExecution,
523
- resumeExecution,
524
- registerNodeType,
525
- getNodeType,
526
- };
527
- }
528
- /**
529
- * Global workflow engine instance
530
- */
531
- export const globalWorkflowEngine = createWorkflowEngine();
532
- //# sourceMappingURL=workflow-engine.js.map
@@ -1,38 +0,0 @@
1
- /**
2
- * A2R Workflow Engine
3
- *
4
- * Visual workflow orchestration with DAG support.
5
- *
6
- * @example
7
- * ```typescript
8
- * import { createWorkflowEngine, createVisualizer } from '@allternit/workflow-engine';
9
- *
10
- * const engine = createWorkflowEngine();
11
- *
12
- * // Register workflow
13
- * engine.registerWorkflow({
14
- * id: 'my-workflow',
15
- * name: 'My Workflow',
16
- * version: '1.0.0',
17
- * nodes: [
18
- * { id: 'trigger', type: 'trigger:manual', name: 'Start' },
19
- * { id: 'http', type: 'http:request', name: 'Fetch Data', config: { method: 'GET' } },
20
- * { id: 'output', type: 'output:result', name: 'Return Result' },
21
- * ],
22
- * connections: [
23
- * { id: 'c1', source: 'trigger', target: 'http' },
24
- * { id: 'c2', source: 'http', target: 'output' },
25
- * ],
26
- * });
27
- *
28
- * // Execute workflow
29
- * const execution = await engine.execute('my-workflow', { url: 'https://api.example.com' });
30
- * console.log(execution.status); // 'completed'
31
- * ```
32
- */
33
- export type { Workflow, WorkflowNode, Connection, WorkflowExecution, ExecutionContext, ExecutionState, NodeExecution, ExecutionStatus, ExecutionError, ParameterSchema, Variable, InputMapping, OutputMapping, Trigger, ErrorHandlingConfig, NodeType, NodeCategory, PortDefinition, WorkflowEngineConfig, WorkflowHooks, Position, } from './types';
34
- export { createWorkflowEngine, globalWorkflowEngine, type WorkflowEngine, } from './engine/workflow-engine';
35
- export { createScheduler, globalScheduler, type Scheduler, type SchedulerConfig, type Task, type SchedulerStatus, } from './scheduler';
36
- export { createVisualizer, globalVisualizer, type WorkflowVisualizer, type VisualizerConfig, type VisualLayout, type VisualNode, type VisualConnection, type Bounds, } from './visualizer';
37
- export declare const VERSION = "0.1.0";
38
- //# sourceMappingURL=index.d.ts.map
package/dist/index.js.bak DELETED
@@ -1,41 +0,0 @@
1
- /**
2
- * A2R Workflow Engine
3
- *
4
- * Visual workflow orchestration with DAG support.
5
- *
6
- * @example
7
- * ```typescript
8
- * import { createWorkflowEngine, createVisualizer } from '@allternit/workflow-engine';
9
- *
10
- * const engine = createWorkflowEngine();
11
- *
12
- * // Register workflow
13
- * engine.registerWorkflow({
14
- * id: 'my-workflow',
15
- * name: 'My Workflow',
16
- * version: '1.0.0',
17
- * nodes: [
18
- * { id: 'trigger', type: 'trigger:manual', name: 'Start' },
19
- * { id: 'http', type: 'http:request', name: 'Fetch Data', config: { method: 'GET' } },
20
- * { id: 'output', type: 'output:result', name: 'Return Result' },
21
- * ],
22
- * connections: [
23
- * { id: 'c1', source: 'trigger', target: 'http' },
24
- * { id: 'c2', source: 'http', target: 'output' },
25
- * ],
26
- * });
27
- *
28
- * // Execute workflow
29
- * const execution = await engine.execute('my-workflow', { url: 'https://api.example.com' });
30
- * console.log(execution.status); // 'completed'
31
- * ```
32
- */
33
- // Engine
34
- export { createWorkflowEngine, globalWorkflowEngine, } from './engine/workflow-engine';
35
- // Scheduler
36
- export { createScheduler, globalScheduler, } from './scheduler';
37
- // Visualizer
38
- export { createVisualizer, globalVisualizer, } from './visualizer';
39
- // Version
40
- export const VERSION = '0.1.0';
41
- //# sourceMappingURL=index.js.map
@@ -1,53 +0,0 @@
1
- /**
2
- * Workflow Scheduler
3
- *
4
- * Handles parallel execution, queuing, and resource management.
5
- */
6
- import type { WorkflowExecution, WorkflowNode } from '../types';
7
- export interface SchedulerConfig {
8
- /** Max concurrent tasks */
9
- maxConcurrency?: number;
10
- /** Queue size limit */
11
- queueLimit?: number;
12
- /** Default task timeout (ms) */
13
- defaultTimeout?: number;
14
- /** Enable task prioritization */
15
- enablePriority?: boolean;
16
- }
17
- export interface Task {
18
- id: string;
19
- node: WorkflowNode;
20
- execution: WorkflowExecution;
21
- priority?: number;
22
- timeout?: number;
23
- }
24
- export interface Scheduler {
25
- /** Submit task for execution */
26
- submit(task: Task): Promise<unknown>;
27
- /** Submit multiple tasks (parallel) */
28
- submitAll(tasks: Task[]): Promise<unknown[]>;
29
- /** Cancel task */
30
- cancel(taskId: string): boolean;
31
- /** Get queue status */
32
- getStatus(): SchedulerStatus;
33
- /** Pause scheduler */
34
- pause(): void;
35
- /** Resume scheduler */
36
- resume(): void;
37
- }
38
- export interface SchedulerStatus {
39
- running: number;
40
- queued: number;
41
- completed: number;
42
- failed: number;
43
- isPaused: boolean;
44
- }
45
- /**
46
- * Create workflow scheduler
47
- */
48
- export declare function createScheduler(config?: SchedulerConfig): Scheduler;
49
- /**
50
- * Global scheduler instance
51
- */
52
- export declare const globalScheduler: Scheduler;
53
- //# sourceMappingURL=index.d.ts.map