npm package: @msverma/task-executor
Description: Task Executor to handle Synchronous or Asynchronous Tasks
Author: Mohit Verma
Latest version: 1.0.4

# @msverma/task-executor
Task Executor to handle Synchronous or Asynchronous Tasks

## Description

Private React component designed to handle synchronous and asynchronous tasks.

It is a sophisticated tool for managing and visualizing complex asynchronous operations. It allows you to define a set of tasks with dependencies, and it will execute them in the correct order, with support for:

-   **Dependency Management**: Tasks can depend on the output of other tasks.
-   **Execution Modes**: Supports both sequential and parallel execution of tasks.
-   **Retry Logic**: Automatically retries failed tasks with configurable delay and attempt limits.
-   **Timeouts**: Configure timeouts for tasks to prevent them from running indefinitely.
-   **Status Tracking**: Provides detailed status for each task (`IDLE`, `PENDING`, `COMPLETED`, `FAILED`).

This module is developed using `React`, `Material-UI`, and `Vite`.

## Peer Dependencies

This library relies on several peer dependencies that need to be present in your project.

```json
  "@emotion/react": ">=11.14",
  "@emotion/styled": ">=11.14",
  "@mui/icons-material": ">=6.4",
  "@mui/material": ">=6.4",
  "@mui/system": ">=6.4",
  "react": ">=19.0",
  "react-dom": ">=19.0"
```

You can install them by running:

```bash
npm install @emotion/react @emotion/styled @mui/material @mui/icons-material @mui/system react react-dom
```

## Installation

`@msverma/task-executor` is available on the npm registry and can be installed by running the following command:

```bash
npm install @msverma/task-executor
```

## Usage

Here are a couple of examples of how to use the `TaskExecutor` and `TaskExecutorStatus` components.

### Example 1: Basic Task Execution

This example shows how to set up a simple `TaskExecutor` with a list of tasks and a button to execute them.

```tsx
import {
    TaskExecutor,
    TaskExecutorStatus,
    type ITaskConfig
} from '@msverma/task-executor';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';

// Define your tasks
const tasks: ITaskConfig<unknown, Record<string, unknown>>[] = [
    {
        id: 'task1',
        name: 'Task 1',
        parallel: true,
        action: () =>
            new Promise((resolve) => {
                setTimeout(resolve, 1000);
            })
    },
    {
        id: 'task2',
        name: 'Task 2',
        parallel: true,
        action: () =>
            new Promise((resolve) => {
                setTimeout(resolve, 2000);
            })
    }
];

function App() {
    return (
        <TaskExecutor
            tasks={tasks}
            onComplete={() => console.info('All tasks completed!')}
            onError={(error) => console.error('A task failed:', error)}
            render={({execute, isExecuting, tasks: taskInstances}) => (
                <div>
                    <Button
                        onClick={execute}
                        disabled={isExecuting}
                        variant="contained"
                    >
                        {isExecuting ? 'Executing...' : 'Run All Tasks'}
                    </Button>
                    <Box mt={2}>
                        {taskInstances.map((task) => (
                            <Box
                                key={task.id}
                                display="flex"
                                alignItems="center"
                                gap={1}
                            >
                                <TaskExecutorStatus taskId={task.id} />
                                <Typography>{task.name}</Typography>
                            </Box>
                        ))}
                    </Box>
                </div>
            )}
        />
    );
}

export default App;
```

### Example 2: Tasks with Dependencies

This example demonstrates how to define tasks with dependencies on other tasks. `task-3` will only start after `task-1` and `task-2` have successfully completed.

```tsx
import {
    TaskExecutor,
    TaskExecutorStatus,
    type ITaskConfig
} from '@msverma/task-executor';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';

// Mock API call
const mockApiCall = (duration: number) =>
    new Promise((resolve) => {
        setTimeout(() => resolve(`Completed in ${duration}ms`), duration);
    });

// Define tasks with dependencies
const tasksWithDeps: ITaskConfig<unknown, Record<string, unknown>>[] = [
    {
        id: 'task-1',
        name: 'Fetch User Data',
        parallel: true,
        action: () => mockApiCall(1000)
    },
    {
        id: 'task-2',
        name: 'Fetch Product Data',
        parallel: true,
        action: () => mockApiCall(1500)
    },
    {
        id: 'task-3',
        name: 'Combine Data',
        dependencies: {
            user: {taskId: 'task-1', transform: (data) => data},
            product: {taskId: 'task-2', transform: (data) => data}
        },
        action: (deps: Record<string, unknown>) => {
            console.info('Dependencies output:', deps);
            return Promise.resolve(
                `User: ${deps.user as string}, Product: ${deps.product as string}`
            );
        }
    }
];

function AppWithDependencies() {
    return (
        <TaskExecutor
            tasks={tasksWithDeps}
            onComplete={() => console.info('All dependent tasks completed!')}
            onError={(error) => console.error('A task failed:', error)}
            render={({execute, isExecuting, tasks: taskInstances}) => (
                <div>
                    <Button
                        onClick={execute}
                        disabled={isExecuting}
                        variant="contained"
                        color="primary"
                    >
                        {isExecuting ? 'Executing...' : 'Run Dependent Tasks'}
                    </Button>
                    <Box mt={2}>
                        {taskInstances.map((task) => (
                            <Box
                                key={task.id}
                                display="flex"
                                alignItems="center"
                                gap={1}
                            >
                                <TaskExecutorStatus taskId={task.id} />
                                <Typography>{task.name}</Typography>
                            </Box>
                        ))}
                    </Box>
                </div>
            )}
        />
    );
}

export default AppWithDependencies;
```

## API Reference

This section provides a detailed reference for the props and configurations available in the `@msverma/task-executor` components.

### `TaskExecutor`

The main component that manages and executes tasks.

#### Props

| Prop         | Type                                               | Description                                                                                             |
| :----------- | :------------------------------------------------- | :------------------------------------------------------------------------------------------------------ |
| `tasks`      | `ITaskConfig[]`                                    | **Required.** An array of task configuration objects to be executed. See `ITaskConfig` for more details. |
| `onComplete` | `() => void`                                       | **Required.** Callback function that is invoked when all tasks have been successfully completed.        |
| `onError`    | `(error: Error) => void`                           | **Required.** Callback function that is invoked when any task fails.                                    |
| `render`     | `(props: TRenderProps) => React.ReactNode` | **Required.** A render prop that provides state and actions to your UI.                                 |

---

### `render` Prop

The `render` prop function receives an object with the following properties:

| Prop          | Type                   | Description                                                              |
| :------------ | :--------------------- | :----------------------------------------------------------------------- |
| `tasks`       | `ITaskConfig[]`        | The array of task instances that are being managed.                      |
| `isDirty`     | `boolean`                | `true` if tasks have been modified and are ready to be executed.         |
| `isExecuting` | `boolean`              | `true` if tasks are currently running.                                   |
| `error`       | `Error \| null`        | An `Error` object if a task has failed, otherwise `null`.                  |
| `execute`     | `() => Promise<void>`  | A function to start the execution of all tasks.                          |
| `reset`       | `() => void`           | A function to reset the state of all tasks to their initial `IDLE` state. |

---

### Task Configuration (`ITaskConfig`)

Each task in the `tasks` array is an object with the following properties:

| Prop           | Type                                                                       | Description                                                                                                                                                                                                                                              |
| :------------- | :------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`           | `string`                                                                   | **Required.** A unique identifier for the task.                                                                                                                                                                                                          |
| `name`         | `string`                                                                   | **Required.** A human-readable name for the task, used for display purposes.                                                                                                                                                                             |
| `action`       | `(deps: TDeps) => Promise<TResult>`                                      | **Required.** The asynchronous function to be executed for this task. It receives the resolved outputs from its dependencies.                                                                                                                              |
| `dependencies` | `Record<string, ITaskDependency>`                                          | An object defining the dependencies for this task. The keys are used to pass the resolved dependency outputs to the `action` function. Each `ITaskDependency` object specifies the `taskId` and an optional `transform` function to process the output. |
| `parallel`     | `boolean`                                                                  | If `true`, this task can be executed in parallel with other `parallel` tasks that do not have dependencies on each other. Defaults to `false`.                                                                                                           |
| `maxRetries`   | `number`                                                                   | The maximum number of times to retry the task if it fails. Defaults to `0`.                                                                                                                                                                              |
| `retryDelay`   | `number`                                                                   | The delay in milliseconds between retry attempts. Defaults to `0`.                                                                                                                                                                                       |
| `timeout`      | `number`                                                                   | The maximum time in milliseconds that a task is allowed to run before it is considered failed. No default timeout.                                                                                                                                   |

---

### `TaskExecutorStatus`

A component to display the status of an individual task.

#### Props

| Prop             | Type                                                 | Description                                                                                                         |
| :--------------- | :--------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------ |
| `taskId`         | `string`                                             | **Required.** The unique identifier of the task whose status you want to display.                                   |
| `gap`            | `number`                                             | The spacing in pixels between the status icon and the metadata content.                                             |
| `alignItems`     | `'center' \| 'flex-start' \| ...`                      | The alignment of items within the status container.                                                                 |
| `flexDirection`  | `'row' \| 'column'`                                  | The direction of the flex layout.                                                                                   |
| `icons`          | `{[key in ETaskStatus]: React.ReactNode}`            | An object to provide custom icons for each task status (`IDLE`, `PENDING`, `COMPLETED`, `FAILED`).                   |
| `renderTaskMeta` | `(meta: ITaskExecutionMeta) => React.ReactNode` | A function to render custom content next to the status icon. It receives the task's metadata as an argument.       |