react-pyworkflow 0.1.2
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.
- package/README.md +166 -0
- package/dist/index.d.mts +93 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.js +477 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +448 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# react-pyworkflow ⚛️
|
|
2
|
+
|
|
3
|
+
A lightweight, client-side JavaScript/TypeScript workflow automation and orchestration library for React.
|
|
4
|
+
|
|
5
|
+
Ported directly from the python version of `pyworkflow`, it lets you define async execution graphs (pipelines), handle complex dependencies, run tasks sequentially or concurrently, execute retries with delays, and track real-time status updates directly within React components.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install react-pyworkflow
|
|
13
|
+
# or
|
|
14
|
+
yarn add react-pyworkflow
|
|
15
|
+
# or
|
|
16
|
+
pnpm add react-pyworkflow
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- ⚛️ **React Hook Integration**: State triggers auto-re-renders of your React components dynamically during execution.
|
|
24
|
+
- ⚡ **Concurrent Execution**: Run independent branches in parallel via `Promise.all` with a single option switch.
|
|
25
|
+
- 🔄 **Automatic Retries**: Retries with configurable delays.
|
|
26
|
+
- 🛑 **Error and Callback Handling**: Control execution with `continueOnFailure` and `onFailure` hooks.
|
|
27
|
+
- 🔀 **Conditional Runs**: Skip tasks dynamically using context-based predicate checks.
|
|
28
|
+
- 🛠️ **Full TypeScript Support**: Out of the box ESM and CJS builds with `.d.ts` type declarations.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
### 1. Define Workflows and Tasks
|
|
35
|
+
Create a workflow graph exactly like PyWorkflow:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { Workflow, Task } from "react-pyworkflow";
|
|
39
|
+
|
|
40
|
+
// 1. Create a workflow
|
|
41
|
+
export const dataPipeline = new Workflow("Data Pipeline");
|
|
42
|
+
|
|
43
|
+
// 2. Define tasks
|
|
44
|
+
const fetchTask = new Task({
|
|
45
|
+
name: "Fetch Data",
|
|
46
|
+
fn: async () => {
|
|
47
|
+
const res = await fetch("https://api.example.com/data");
|
|
48
|
+
return res.json();
|
|
49
|
+
},
|
|
50
|
+
retries: 2,
|
|
51
|
+
retryDelay: 1000, // 1s
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const filterTask = new Task({
|
|
55
|
+
name: "Filter Data",
|
|
56
|
+
dependsOn: ["Fetch Data"],
|
|
57
|
+
fn: (context) => {
|
|
58
|
+
// Upstream task results are automatically injected into context
|
|
59
|
+
const data = context["Fetch Data"];
|
|
60
|
+
return data.filter((item: any) => item.active);
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const saveTask = new Task({
|
|
65
|
+
name: "Save Data",
|
|
66
|
+
dependsOn: ["Filter Data"],
|
|
67
|
+
fn: async (context) => {
|
|
68
|
+
const filtered = context["Filter Data"];
|
|
69
|
+
await fetch("https://api.example.com/save", {
|
|
70
|
+
method: "POST",
|
|
71
|
+
body: JSON.stringify(filtered),
|
|
72
|
+
});
|
|
73
|
+
return "Saved Successfully!";
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// 3. Compose workflow
|
|
78
|
+
dataPipeline.addTask(fetchTask).addTask(filterTask).addTask(saveTask);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 2. Connect to React Components
|
|
82
|
+
Use the `useWorkflow` hook to bind execution state reactively:
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
import React from "react";
|
|
86
|
+
import { useWorkflow } from "react-pyworkflow";
|
|
87
|
+
import { dataPipeline } from "./pipeline";
|
|
88
|
+
|
|
89
|
+
export default function PipelineMonitor() {
|
|
90
|
+
const { state, taskStates, outputs, errors, isRunning, run, reset } = useWorkflow(dataPipeline);
|
|
91
|
+
|
|
92
|
+
const handleStart = () => {
|
|
93
|
+
run({ parallel: true }); // Runs independent tasks concurrently
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div style={{ padding: "20px", fontFamily: "sans-serif" }}>
|
|
98
|
+
<h2>Workflow: {dataPipeline.name}</h2>
|
|
99
|
+
<p>Status: <strong>{state}</strong></p>
|
|
100
|
+
|
|
101
|
+
<div style={{ margin: "20px 0" }}>
|
|
102
|
+
<button onClick={handleStart} disabled={isRunning}>
|
|
103
|
+
Start Pipeline
|
|
104
|
+
</button>
|
|
105
|
+
<button onClick={reset} disabled={isRunning} style={{ marginLeft: "10px" }}>
|
|
106
|
+
Reset
|
|
107
|
+
</button>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<h3>Tasks:</h3>
|
|
111
|
+
<ul>
|
|
112
|
+
{Array.from(dataPipeline.tasks.keys()).map((name) => (
|
|
113
|
+
<li key={name} style={{ margin: "10px 0" }}>
|
|
114
|
+
<strong>{name}</strong>: <span style={{ color: getStatusColor(taskStates[name]) }}>{taskStates[name]}</span>
|
|
115
|
+
{outputs[name] && <pre>{JSON.stringify(outputs[name], null, 2)}</pre>}
|
|
116
|
+
{errors[name] && <p style={{ color: "red" }}>Error: {errors[name]}</p>}
|
|
117
|
+
</li>
|
|
118
|
+
))}
|
|
119
|
+
</ul>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function getStatusColor(status: string) {
|
|
125
|
+
switch (status) {
|
|
126
|
+
case "COMPLETED": return "green";
|
|
127
|
+
case "RUNNING":
|
|
128
|
+
case "RETRYING": return "orange";
|
|
129
|
+
case "FAILED": return "red";
|
|
130
|
+
case "SKIPPED": return "blue";
|
|
131
|
+
default: return "gray";
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## API Reference
|
|
139
|
+
|
|
140
|
+
### `Workflow`
|
|
141
|
+
- `constructor(name: string)`
|
|
142
|
+
- `addTask(task: Task): this` — Add a task to the workflow.
|
|
143
|
+
- `addTasks(tasks: Task[]): this` — Add multiple tasks at once.
|
|
144
|
+
- `then(task: Task): this` — Add a task that depends on the previously added task.
|
|
145
|
+
- `reset(): void` — Reset the workflow and all tasks back to `PENDING`.
|
|
146
|
+
- `validate(): void` — Validate dependencies and detect cycles.
|
|
147
|
+
- `run(options?: { parallel?: boolean }): Promise<ExecutionReport>` — Run the workflow.
|
|
148
|
+
|
|
149
|
+
### `Task`
|
|
150
|
+
- `constructor(options: TaskOptions)`
|
|
151
|
+
- **Options**:
|
|
152
|
+
- `name`: `string` (required, unique)
|
|
153
|
+
- `fn`: `(context) => Promise<any> | any` (required, execution body)
|
|
154
|
+
- `retries`: `number` (optional, default: 0)
|
|
155
|
+
- `retryDelay`: `number` (optional, default: 0ms)
|
|
156
|
+
- `timeout`: `number` (optional, timeout in ms)
|
|
157
|
+
- `dependsOn`: `string[]` (optional, dependency task names)
|
|
158
|
+
- `condition`: `(context) => boolean | Promise<boolean>` (optional)
|
|
159
|
+
- `onFailure`: `(taskName, error) => void` (optional callback)
|
|
160
|
+
- `continueOnFailure`: `boolean` (optional, default: false)
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
MIT © PyWorkflow Contributors
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
type TaskState = "PENDING" | "RUNNING" | "COMPLETED" | "FAILED" | "SKIPPED" | "RETRYING" | "CANCELLED";
|
|
2
|
+
type WorkflowState = "CREATED" | "RUNNING" | "COMPLETED" | "FAILED" | "CANCELLED" | "PAUSED";
|
|
3
|
+
interface TaskResult<T = any> {
|
|
4
|
+
state: TaskState;
|
|
5
|
+
output?: T;
|
|
6
|
+
error?: string;
|
|
7
|
+
exception?: Error;
|
|
8
|
+
startedAt?: number;
|
|
9
|
+
finishedAt?: number;
|
|
10
|
+
attempt: number;
|
|
11
|
+
}
|
|
12
|
+
interface ExecutionReport {
|
|
13
|
+
success: boolean;
|
|
14
|
+
results: Record<string, any>;
|
|
15
|
+
error?: Error;
|
|
16
|
+
failedTasks: string[];
|
|
17
|
+
skippedTasks: string[];
|
|
18
|
+
}
|
|
19
|
+
interface TaskOptions<T = any> {
|
|
20
|
+
name: string;
|
|
21
|
+
fn: (context: Record<string, any>) => Promise<T> | T;
|
|
22
|
+
description?: string;
|
|
23
|
+
retries?: number;
|
|
24
|
+
retryDelay?: number;
|
|
25
|
+
timeout?: number;
|
|
26
|
+
dependsOn?: string[];
|
|
27
|
+
condition?: (context: Record<string, any>) => boolean | Promise<boolean>;
|
|
28
|
+
onFailure?: (taskName: string, error: Error) => void | Promise<void>;
|
|
29
|
+
continueOnFailure?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
declare class Task<T = any> {
|
|
33
|
+
name: string;
|
|
34
|
+
fn: (context: Record<string, any>) => Promise<T> | T;
|
|
35
|
+
description: string;
|
|
36
|
+
retries: number;
|
|
37
|
+
retryDelay: number;
|
|
38
|
+
timeout?: number;
|
|
39
|
+
dependsOn: string[];
|
|
40
|
+
condition?: (context: Record<string, any>) => boolean | Promise<boolean>;
|
|
41
|
+
onFailure?: (taskName: string, error: Error) => void | Promise<void>;
|
|
42
|
+
continueOnFailure: boolean;
|
|
43
|
+
state: TaskState;
|
|
44
|
+
output?: T;
|
|
45
|
+
error?: string;
|
|
46
|
+
history: TaskResult<T>[];
|
|
47
|
+
startedAt?: number;
|
|
48
|
+
finishedAt?: number;
|
|
49
|
+
attempts: number;
|
|
50
|
+
constructor(options: TaskOptions<T>);
|
|
51
|
+
reset(): void;
|
|
52
|
+
run(context: Record<string, any>): Promise<TaskResult<T>>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
declare class Workflow {
|
|
56
|
+
name: string;
|
|
57
|
+
tasks: Map<string, Task>;
|
|
58
|
+
state: WorkflowState;
|
|
59
|
+
context: Record<string, any>;
|
|
60
|
+
startedAt?: number;
|
|
61
|
+
finishedAt?: number;
|
|
62
|
+
private insertionOrder;
|
|
63
|
+
private stateChangeListeners;
|
|
64
|
+
private taskStateChangeListeners;
|
|
65
|
+
constructor(name: string);
|
|
66
|
+
addTask(task: Task): this;
|
|
67
|
+
addTasks(tasks: Task[]): this;
|
|
68
|
+
then(task: Task): this;
|
|
69
|
+
reset(): void;
|
|
70
|
+
onStateChange(listener: (state: WorkflowState) => void): () => void;
|
|
71
|
+
onTaskStateChange(listener: (taskName: string, state: TaskState) => void): () => void;
|
|
72
|
+
private notifyStateChange;
|
|
73
|
+
private notifyTaskStateChange;
|
|
74
|
+
validate(): void;
|
|
75
|
+
getTopologicalLevels(): string[][];
|
|
76
|
+
run(options?: {
|
|
77
|
+
parallel?: boolean;
|
|
78
|
+
}): Promise<ExecutionReport>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
declare function useWorkflow(workflow: Workflow): {
|
|
82
|
+
state: WorkflowState;
|
|
83
|
+
taskStates: Record<string, TaskState>;
|
|
84
|
+
outputs: Record<string, any>;
|
|
85
|
+
errors: Record<string, string>;
|
|
86
|
+
isRunning: boolean;
|
|
87
|
+
run: (options?: {
|
|
88
|
+
parallel?: boolean;
|
|
89
|
+
}) => Promise<ExecutionReport>;
|
|
90
|
+
reset: () => void;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export { type ExecutionReport, Task, type TaskOptions, type TaskResult, type TaskState, Workflow, type WorkflowState, useWorkflow };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
type TaskState = "PENDING" | "RUNNING" | "COMPLETED" | "FAILED" | "SKIPPED" | "RETRYING" | "CANCELLED";
|
|
2
|
+
type WorkflowState = "CREATED" | "RUNNING" | "COMPLETED" | "FAILED" | "CANCELLED" | "PAUSED";
|
|
3
|
+
interface TaskResult<T = any> {
|
|
4
|
+
state: TaskState;
|
|
5
|
+
output?: T;
|
|
6
|
+
error?: string;
|
|
7
|
+
exception?: Error;
|
|
8
|
+
startedAt?: number;
|
|
9
|
+
finishedAt?: number;
|
|
10
|
+
attempt: number;
|
|
11
|
+
}
|
|
12
|
+
interface ExecutionReport {
|
|
13
|
+
success: boolean;
|
|
14
|
+
results: Record<string, any>;
|
|
15
|
+
error?: Error;
|
|
16
|
+
failedTasks: string[];
|
|
17
|
+
skippedTasks: string[];
|
|
18
|
+
}
|
|
19
|
+
interface TaskOptions<T = any> {
|
|
20
|
+
name: string;
|
|
21
|
+
fn: (context: Record<string, any>) => Promise<T> | T;
|
|
22
|
+
description?: string;
|
|
23
|
+
retries?: number;
|
|
24
|
+
retryDelay?: number;
|
|
25
|
+
timeout?: number;
|
|
26
|
+
dependsOn?: string[];
|
|
27
|
+
condition?: (context: Record<string, any>) => boolean | Promise<boolean>;
|
|
28
|
+
onFailure?: (taskName: string, error: Error) => void | Promise<void>;
|
|
29
|
+
continueOnFailure?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
declare class Task<T = any> {
|
|
33
|
+
name: string;
|
|
34
|
+
fn: (context: Record<string, any>) => Promise<T> | T;
|
|
35
|
+
description: string;
|
|
36
|
+
retries: number;
|
|
37
|
+
retryDelay: number;
|
|
38
|
+
timeout?: number;
|
|
39
|
+
dependsOn: string[];
|
|
40
|
+
condition?: (context: Record<string, any>) => boolean | Promise<boolean>;
|
|
41
|
+
onFailure?: (taskName: string, error: Error) => void | Promise<void>;
|
|
42
|
+
continueOnFailure: boolean;
|
|
43
|
+
state: TaskState;
|
|
44
|
+
output?: T;
|
|
45
|
+
error?: string;
|
|
46
|
+
history: TaskResult<T>[];
|
|
47
|
+
startedAt?: number;
|
|
48
|
+
finishedAt?: number;
|
|
49
|
+
attempts: number;
|
|
50
|
+
constructor(options: TaskOptions<T>);
|
|
51
|
+
reset(): void;
|
|
52
|
+
run(context: Record<string, any>): Promise<TaskResult<T>>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
declare class Workflow {
|
|
56
|
+
name: string;
|
|
57
|
+
tasks: Map<string, Task>;
|
|
58
|
+
state: WorkflowState;
|
|
59
|
+
context: Record<string, any>;
|
|
60
|
+
startedAt?: number;
|
|
61
|
+
finishedAt?: number;
|
|
62
|
+
private insertionOrder;
|
|
63
|
+
private stateChangeListeners;
|
|
64
|
+
private taskStateChangeListeners;
|
|
65
|
+
constructor(name: string);
|
|
66
|
+
addTask(task: Task): this;
|
|
67
|
+
addTasks(tasks: Task[]): this;
|
|
68
|
+
then(task: Task): this;
|
|
69
|
+
reset(): void;
|
|
70
|
+
onStateChange(listener: (state: WorkflowState) => void): () => void;
|
|
71
|
+
onTaskStateChange(listener: (taskName: string, state: TaskState) => void): () => void;
|
|
72
|
+
private notifyStateChange;
|
|
73
|
+
private notifyTaskStateChange;
|
|
74
|
+
validate(): void;
|
|
75
|
+
getTopologicalLevels(): string[][];
|
|
76
|
+
run(options?: {
|
|
77
|
+
parallel?: boolean;
|
|
78
|
+
}): Promise<ExecutionReport>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
declare function useWorkflow(workflow: Workflow): {
|
|
82
|
+
state: WorkflowState;
|
|
83
|
+
taskStates: Record<string, TaskState>;
|
|
84
|
+
outputs: Record<string, any>;
|
|
85
|
+
errors: Record<string, string>;
|
|
86
|
+
isRunning: boolean;
|
|
87
|
+
run: (options?: {
|
|
88
|
+
parallel?: boolean;
|
|
89
|
+
}) => Promise<ExecutionReport>;
|
|
90
|
+
reset: () => void;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export { type ExecutionReport, Task, type TaskOptions, type TaskResult, type TaskState, Workflow, type WorkflowState, useWorkflow };
|