npm package: @madooei/evok
Description: A minimal framework for building event-driven workflows in TypeScript
Author: Ali Madooei
Latest version: 0.2.1

# Evok: Minimal Event-Driven Workflow Framework

A minimal framework for building event-driven workflows in TypeScript. Designed to coordinate logic across tasks, AI agents, or microservices, while remaining unopinionated and easy to extend.

**Features:**

- Written in TypeScript
- Builds to both modern ES modules and CommonJS formats
- Provides TypeScript type definitions
- ESLint for code linting
- Prettier for code formatting
- Vitest for testing
- Tsup for building
- Minimal dependencies

## Installation

```bash
npm install @madooei/evok
```

## Usage

Evok is a minimalist TypeScript library for building event-driven workflows using composable steps and declarative orchestration logic.

### Core Concepts

#### State

The workflow operates on a shared state object (store). You define its shape using TypeScript types:

```typescript
interface State {
  data: {
    greeting?: string;
  };
}
```

#### WorkflowEvent

Steps emit events to signal transitions. Events contain a `type` and optional `payload`:

```typescript
interface WorkflowEvent {
  type: string;
  payload?: Record<string, unknown>;
}
```

#### Step

Each step performs a unit of work:

```typescript
interface Step<State> extends LifecycleHooks<State> {
  id: string; // unique identifier for the step
  // main logic to run, returning new state and optional events
  run: (state: State) => Promise<{ state: State; events?: WorkflowEvent[] }>;
  params?: Record<string, unknown>; // optional static config per step
}
```

#### Workflow

A workflow contains steps and orchestrates their execution via emitted events:

```typescript
interface Workflow<State> extends Step<State> {
  steps: Record<string, Step<State>>; // map of step IDs to Step instances for fast lookup
  start: string; // ID of the starting step
  // receives events and can return an array of step IDs to activate next
  onEvent?: (event: WorkflowEvent, state: State) => Promise<string[]>;
}
```

### Example Usage

```typescript
import {
  createWorkflow,
  setLogLevel,
  type Step,
  type Workflow,
} from "@madooei/evok";

// 1. Define State
interface State {
  data: {
    greeting?: string;
  };
}

// 2. Define Steps
const createGreetingStep: Step<State> = {
  id: "createGreeting",
  async run() {
    const greeting = "Hello, World!";
    return {
      state: { data: { greeting } },
      events: [{ type: "greeting:created", payload: greeting }],
    };
  },
  onStart: async () => console.log("Starting createGreetingStep"),
  onComplete: async () => console.log("Completed createGreetingStep"),
  onError: async (error) =>
    console.error("Error in createGreetingStep:", error),
};

const printGreetingStep: Step<State> = {
  id: "printGreeting",
  async run(state) {
    const prefix = this.params?.prefix || "";
    const postfix = this.params?.postfix || "";
    console.log(prefix, state.data.greeting, postfix);
    return { state };
  },
  params: { prefix: "🎉🎉🎉", postfix: "🎉🎉🎉" },
  onStart: async () => console.log("Starting printGreetingStep"),
  onComplete: async () => console.log("Completed printGreetingStep"),
  onError: async (error) => console.error("Error in printGreetingStep:", error),
};

// 3. Define Workflow
const helloFlow = createWorkflow<State>({
  id: "helloFlow",
  start: "createGreeting",
  steps: {
    createGreeting: createGreetingStep,
    printGreeting: printGreetingStep,
  },
  onEvent: async (event) => {
    if (event.type === "greeting:created") {
      return ["printGreeting"];
    }
    return [];
  },
  onStart: async () => console.log("Starting helloFlow"),
  onComplete: async () => console.log("Completed helloFlow"),
  onError: async (error) => console.error("Error in helloFlow:", error),
});

// 4. Execute
await helloFlow.run({ data: {} });
```

### Logging

Evok includes built-in logging functionality:

```typescript
import { setLogLevel } from "evok";

// Set log level to control verbosity
setLogLevel("trace"); // 'trace', 'info'
```

## Cloning the Repository

To make your workflow more organized, it's a good idea to clone this repository into a directory named `evok-workspace`. This helps differentiate the workspace from the `evok` located in the `packages` directory.

```bash
git clone https://github.com/madooei/evok evok-workspace

cd evok-workspace
```

## Repository Structure

- `packages` — Contains the primary package(s) for this repository (e.g., `evok`). Each package is self-contained and can be copied out and used independently.
- `examples` — Contains examples of how to use the packages. Each example is a minimal, standalone project.
- `playgrounds` — Contains demos of the dependencies of the primary package(s). Each playground is a minimal, standalone project.
- `docs` — Contains various documentation for users and developers.
- `.github` — Contains GitHub-specific files, such as workflows and issue templates.

## How to Use This Repo

- To work on a package, go to `packages/<package-name>` and follow its README.
- To try an example, go to `examples/<example-name>` and follow its README.
- To run the playground, go to `playground/<package-name>` and follow its README.
- For documentation, see the `docs` folder.

### Using a VSCode Multi-root Workspace

With Visual Studio Code, you can enhance your development experience by using a multi-root workspace to access packages, examples, and playgrounds simultaneously. This approach is more efficient than opening the root directory, or each package or example separately.

To set up a multi-root workspace:

1. Open Visual Studio Code.
2. Navigate to `File > Open Workspace from File...`.
3. Select the `evok.code-workspace` file located at the root of the repository. This action will open all specified folders in one workspace.

The `evok.code-workspace` file can be customized to include different folders or settings. Here's a typical configuration:

```json
{
  "folders": [
    {
      "path": "packages/evok"
    },
    {
      "path": "examples/agents"
    },
    {
      "path": "playgrounds/empty"
    }
  ],
  "settings": {
    // Add any workspace-specific settings here, for example:
    "git.openRepositoryInParentFolders": "always"
  }
}
```

## Developing the Package

Change to the package directory and install dependencies:

```bash
cd packages/evok
npm install
```

- Read the [Project Roadmap](../../docs/ROADMAP.md) for project goals, status, evolution, and development guidelines.
- Read the [Development Guide](DEVELOPMENT.md) for detailed information on the package architecture, build configuration, and implementation patterns.
- Follow the [Contributing Guide](../../docs/CONTRIBUTING.md) for contribution guidelines, coding standards, and best practices.

## Package Management

When you are ready to publish your package:

```bash
npm run release
```

This single command will:

- Validate your code with the full validation pipeline
- Analyze commits to determine version bump
- Update package.json version and changelog
- Build the package
- Create and push git tag
- Create GitHub release
- Publish to NPM

> [!TIP]
> For detailed information about package publishing, versioning, and local development workflows, see the [NPM Package Management Guide](../../docs/guides/npm-package.md).