> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ooneex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Workflow

> Compose business processes from small, conditional, reversible transitions with automatic rollback on failure.

The `@ooneex/workflow` component is a transition-based workflow engine. A workflow is an ordered list of **transitions** — small, focused steps that each decide whether they run, do their work, and know how to undo it. When you `run` a workflow, the active transitions execute in order; if any step throws, the engine rolls back the ones that already succeeded, in reverse, and raises a typed `WorkflowException`.

## Why this component

* **Transition-based.** Break a process into small, focused steps instead of one monolithic function.
* **Conditional execution.** Each transition's `isActive(data, context?)` decides whether it runs for the current data.
* **Automatic rollback.** When a step fails, the executed transitions roll back in reverse order before the error propagates.
* **Lifecycle hooks.** React around every step with `onStart`, `onFinish`, and `onFail`.
* **Container-managed.** Register workflows and transitions with a decorator and resolve them from `@ooneex/container`.
* **Type-safe.** Generic `Data` and `Output` types flow through the workflow and its transitions.

## How it works

A workflow extends the abstract `Workflow<Data, Output>` class and implements `getName`, `getDescription`, and `getTransitions`. Each transition implements the `ITransition<Data, Output>` interface. Calling `run(data, context?)` does the following:

1. Every transition's `isActive(data, context?)` is evaluated to build the list of **active** transitions.
2. Active transitions run in order. For each one: `onStart` fires, then `handler(data, context?)` produces an output, then `onFinish` fires with that output.
3. The output of the **last** executed transition is returned.
4. If a step throws, that transition's `onFail` fires, the transitions executed so far are rolled back in **reverse** order, and a `WorkflowException` is thrown.

A transition implements these members:

| Member                             | Purpose                                                         |
| ---------------------------------- | --------------------------------------------------------------- |
| `getName()`                        | Unique, human-readable name (used in error messages).           |
| `getDescription()`                 | Short description of what the transition does.                  |
| `isActive(data, context?)`         | Whether this transition should run — return `false` to skip it. |
| `handler(data, context?)`          | Performs the work and returns the output.                       |
| `rollback(data, context?)`         | Undoes the work if a later transition fails.                    |
| `onStart(data, context?)`          | Fires before the handler runs.                                  |
| `onFinish(data, output, context?)` | Fires after the handler succeeds, with its output.              |
| `onFail(data, error, context?)`    | Fires when the handler (or a hook) throws, with the error.      |

Every member may be synchronous or asynchronous. The optional `context` passed to `run` is forwarded to every member, so request-scoped values such as the current user or a request id reach each step.

Rollback is precise: only transitions whose `handler` completed are rolled back, the transition that threw is **not** (its work never finished), and rollbacks are awaited before the `WorkflowException` is rethrown.

```text theme={null}
onStart → handler → onFinish        (success)
onStart → handler ✗ → onFail        (failure) → rollback (reverse) → WorkflowException
```

## Decorator and usage

### `@decorator.transition()`

Registers a transition class with the container. It accepts an optional scope (defaults to `EContainerScope.Singleton`).

```typescript theme={null}
import { decorator } from "@ooneex/workflow";
import type { ITransition } from "@ooneex/workflow";

interface OrderData extends Record<string, unknown> {
  orderId: string;
  amount: number;
}

@decorator.transition()
export class ChargePaymentTransition implements ITransition<OrderData, string> {
  public getName = () => "charge-payment";
  public getDescription = () => "Charges the customer for the order";
  public isActive = (data: OrderData) => data.amount > 0;

  public handler = async (data: OrderData) => {
    return paymentGateway.charge(data.orderId, data.amount);
  };

  public rollback = async (data: OrderData) => {
    await paymentGateway.refund(data.orderId);
  };

  public onStart = (data: OrderData) => logger.info(`Charging ${data.orderId}`);
  public onFinish = (data: OrderData, chargeId: string) => logger.info(`Charged ${chargeId}`);
  public onFail = (data: OrderData, error: unknown) => logger.error("Charge failed", error);
}
```

### `@decorator.workflow()`

Registers a workflow class with the container. It also accepts an optional scope (defaults to `EContainerScope.Singleton`). List the transition classes in `getTransitions()` in the order they should run — return the class references only, never instances, since they are resolved from the container.

```typescript theme={null}
import { decorator, Workflow } from "@ooneex/workflow";
import type { WorkflowTransitionClassType } from "@ooneex/workflow";

@decorator.workflow()
export class CheckoutWorkflow extends Workflow<OrderData, string> {
  public getName = () => "checkout";
  public getDescription = () => "Processes an order from payment to receipt";

  public getTransitions = (): WorkflowTransitionClassType[] => [
    ChargePaymentTransition,
    SendReceiptTransition,
  ];
}
```

Resolve the workflow from the container and `run` it. The output is whatever the last executed transition returned:

```typescript theme={null}
import { container } from "@ooneex/container";
import { WorkflowException } from "@ooneex/workflow";

const workflow = container.get(CheckoutWorkflow);

try {
  const output = await workflow.run(
    { orderId: "ord_123", amount: 4200 },
    { requestId: "req_1", user: currentUser }, // optional context
  );
  console.log(output);
} catch (error) {
  if (error instanceof WorkflowException) {
    console.error(error.message); // Workflow "checkout" failed at transition "...".
    console.error(error.data); // { workflow, transition, error }
  }
}
```

## Exceptions

A failed run throws a `WorkflowException`. It carries a machine-readable `key`, a human-readable `message`, an HTTP `status`, and a `data` object.

| Key                   | When                                                                                                                                        |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `WORKFLOW_RUN_FAILED` | A transition's `handler` or a lifecycle hook threw during `run()`. The executed transitions have already been rolled back in reverse order. |

The `message` reads `Workflow "<name>" failed at transition "<name>".`, the `status` is `500` (Internal Server Error), and `data` is `{ workflow, transition, error }`, where `error` is the original message (non-`Error` throwables are stringified).

```typescript theme={null}
import { WorkflowException } from "@ooneex/workflow";

try {
  await workflow.run(data);
} catch (error) {
  if (error instanceof WorkflowException) {
    logger.error(`Workflow error [${error.key}]: ${error.message}`, error.data);
  } else {
    throw error;
  }
}
```

## Best practices

* **One concern per transition.** Keep each transition focused on a single, nameable action so it stays easy to test and roll back.
* **Make `rollback` the true inverse of `handler`.** Whatever side effect a handler creates — a charge, a reservation, a record — its rollback should undo it.
* **Guard with `isActive`.** Skip steps that don't apply to the current data instead of branching inside the handler.
* **Order transitions deliberately.** They run in the order returned by `getTransitions()`, and roll back in reverse — put reversible, cheap steps first where you can.
* **Return class references, never instances.** Transitions are resolved from the container; list the classes in `getTransitions()`.
* **Use `context` for request-scoped values.** Pass the current user or request id as the second `run` argument rather than threading them through `Data`.
* **Catch `WorkflowException` at the boundary.** Inspect its stable `key` and `data` to report the failing workflow and transition.

## CLI command

Scaffold a workflow with `workflow:create` and each of its steps with `workflow:transition:create`. The generators write the class and a matching test under the target module and install `@ooneex/workflow` if it is missing.

```bash theme={null}
# Interactive: prompts for the name
ooneex workflow:create
ooneex workflow:transition:create

# Provide the name
ooneex workflow:create --name=Checkout
ooneex workflow:transition:create --name=ChargePayment

# Target a module and overwrite
ooneex workflow:create --name=Checkout --module=orders --override
```

| Option       | Description                                                                                                     | Default             |
| ------------ | --------------------------------------------------------------------------------------------------------------- | ------------------- |
| `--name`     | Class name. `workflow:create` appends the `Workflow` suffix; `workflow:transition:create` appends `Transition`. | Prompted if omitted |
| `--module`   | Target module the class is generated into.                                                                      | `shared`            |
| `--override` | Overwrite an existing class without prompting.                                                                  | `false`             |

`workflow:create` writes the class under `modules/<module>/src/workflows/<Name>Workflow.ts` as a `Workflow` subclass with an empty transition list, ready for you to fill in:

```typescript theme={null}
import type { WorkflowTransitionClassType } from "@ooneex/workflow";
import { decorator, Workflow } from "@ooneex/workflow";

/** Data threaded through CheckoutWorkflow and each of its transitions. */
export type CheckoutWorkflowDataType = Record<string, unknown>;

@decorator.workflow()
export class CheckoutWorkflow extends Workflow<CheckoutWorkflowDataType> {
  public getName = (): string => "checkout";

  public getDescription = (): string => "";

  /** Transitions run in order; each one's `isActive` decides if it executes. */
  public getTransitions = (): WorkflowTransitionClassType[] => [];
}
```

`workflow:transition:create` writes the class under `modules/<module>/src/workflows/transitions/<Name>Transition.ts` as an `ITransition` stub with every member ready to implement:

```typescript theme={null}
import type { ITransition } from "@ooneex/workflow";
import { decorator } from "@ooneex/workflow";

/** Data threaded through the workflow run and read by ChargePaymentTransition. */
export type ChargePaymentTransitionDataType = Record<string, unknown>;

@decorator.transition()
export class ChargePaymentTransition
  implements ITransition<ChargePaymentTransitionDataType, unknown>
{
  public getName = (): string => "charge-payment";

  public getDescription = (): string => "";

  /** Whether this transition should run for the given data. */
  public isActive = (_data: ChargePaymentTransitionDataType): boolean => true;

  /** Performs the transition's work and returns its output. */
  public handler = async (data: ChargePaymentTransitionDataType): Promise<unknown> => {
    return data;
  };

  /** Undoes handler() when a later transition in the workflow fails. */
  public rollback = async (_data: ChargePaymentTransitionDataType): Promise<void> => {};

  /** Runs before handler(). */
  public onStart = async (_data: ChargePaymentTransitionDataType): Promise<void> => {};

  /** Runs after handler() resolves successfully. */
  public onFinish = async (
    _data: ChargePaymentTransitionDataType,
    _output: unknown,
  ): Promise<void> => {};

  /** Runs when handler() throws. */
  public onFail = async (
    _data: ChargePaymentTransitionDataType,
    _error: unknown,
  ): Promise<void> => {};
}
```

See [workflow:create](/cli/commands/workflow-create) and [workflow:transition:create](/cli/commands/workflow-transition-create) for the full command references.

## Use with Claude and Codex

The generators ship matching `workflow:create` and `workflow:transition:create` skills. They run the scaffold and then guide your AI agent through completing the workflow — defining the `Data` type, listing the transitions in order, and implementing each transition's `isActive`, `handler`, and `rollback`. Initialize the skills once for your agent:

<Tabs>
  <Tab title="Claude">
    ```bash theme={null}
    ooneex claude:init
    ```

    Then ask Claude in natural language — it maps the request to the generators, runs them, and fills in the implementation:

    ```text Prompt icon="terminal" wrap theme={null}
    Create an order workflow with pending, paid, and shipped states.
    ```
  </Tab>

  <Tab title="Codex">
    ```bash theme={null}
    ooneex codex:init
    ```

    Then ask Codex in natural language — it maps the request to the generators, runs them, and fills in the implementation:

    ```text Prompt icon="terminal" wrap theme={null}
    Create an order workflow with pending, paid, and shipped states.
    ```
  </Tab>
</Tabs>

For example, the prompt above maps to `workflow:create --name=Order`, then a `workflow:transition:create` for each step, wiring the transitions into `getTransitions()` in order.
