npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ooneex/workflow

v0.1.0

Published

Workflow utilities for Ooneex

Readme

@ooneex/workflow

Transition-based workflow engine for TypeScript — compose business processes from small, conditional, reversible steps with automatic rollback on failure.

Bun TypeScript MIT License

Features

Transition-based - Break a process into small, focused transitions

Conditional execution - Each transition decides whether it runs via isActive

Automatic rollback - Executed transitions roll back in reverse order when a step fails

Dependency injection - Transitions are resolved from @ooneex/container

Decorator registration - Register workflows and transitions with a single decorator

Typed exceptions - Failures surface as a WorkflowException carrying context

Lifecycle events - React to each step with onStart, onFinish, and onFail hooks

Async-friendly - Every transition member may be sync or async

Type-Safe - Full TypeScript support with generic Data and Output types

Installation

bun add @ooneex/workflow

Concepts

A workflow is an ordered list of transitions. When you run a workflow:

  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 the ITransition interface:

| Member | Purpose | | --- | --- | | getName() | Unique, human-readable name (used in error messages). | | getDescription() | Short description of what the transition does. | | isActive(data, context?) | Returns whether this transition should run. | | 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. |

Usage

Define transitions

import { decorator, type ITransition } from '@ooneex/workflow';

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

@decorator.transition()
export class ChargePayment 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) => {
    const chargeId = await paymentGateway.charge(data.orderId, data.amount);
    return chargeId;
  };

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

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

@decorator.transition()
export class SendReceipt implements ITransition<OrderData, string> {
  public getName = () => 'send-receipt';
  public getDescription = () => 'Emails the receipt to the customer';
  public isActive = () => true;

  public handler = async (data: OrderData) => {
    await mailer.sendReceipt(data.orderId);
    return data.orderId;
  };

  public rollback = () => {};
  public onStart = () => {};
  public onFinish = () => {};
  public onFail = () => {};
}

Define a workflow

import {
  decorator,
  Workflow,
  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[] => [
    ChargePayment,
    SendReceipt,
  ];
}

Run the workflow

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 });
  console.log(output); // "ord_123" — output of the last executed transition
} catch (error) {
  if (error instanceof WorkflowException) {
    console.error(error.message); // Workflow "checkout" failed at transition "send-receipt".
    console.error(error.data); // { workflow, transition, error }
  }
}

Passing a context

run accepts an optional second argument that is forwarded to every transition member (isActive, handler, rollback, onStart, onFinish, onFail). Use it for request-scoped values such as the current user or a request id.

await workflow.run(
  { orderId: 'ord_123', amount: 4200 },
  { requestId: 'req_1', user: currentUser },
);

Rollback semantics

When a handler throws:

  • Only transitions whose handler completed successfully are rolled back.
  • The transition that threw is not rolled back (its work never finished).
  • Rollbacks run in reverse execution order and are awaited before the WorkflowException is rethrown.
handler:one → handler:two → handler:failing (throws)
                          ← rollback:two ← rollback:one

Lifecycle events

Each transition exposes three hooks that fire around its handler:

  • onStart(data, context?) — runs just before the handler.
  • onFinish(data, output, context?) — runs after the handler resolves, receiving its output.
  • onFail(data, error, context?) — runs when the handler throws, receiving the error, before rollback begins.

The hooks run inside the same guarded step as the handler, so a throw from onStart or onFinish is treated like a handler failure: it triggers onFail, rolls back the already-executed transitions, and throws a WorkflowException.

onStart → handler → onFinish        (success)
onStart → handler ✗ → onFail        (failure) → rollback → WorkflowException

Error handling

A failed run throws a WorkflowException with:

  • message — e.g. Workflow "checkout" failed at transition "charge-payment".
  • key"WORKFLOW_RUN_FAILED"
  • status500 (Internal Server Error)
  • data{ workflow, transition, error }, where error is the original message (non-Error throwables are stringified).

API

Workflow<Data, Output>

Abstract base class. Subclasses implement getName, getDescription, and getTransitions. Provides run(data, context?).

decorator

  • decorator.workflow(scope?) — registers a workflow class in the container.
  • decorator.transition(scope?) — registers a transition class in the container.

Both default to EContainerScope.Singleton.

Types

  • ITransition<Data, Output> — interface implemented by transitions.
  • IWorkflow<Data, Output> — interface implemented by Workflow.
  • WorkflowTransitionClassType — constructor type for a transition class.
  • WorkflowClassType — constructor type for a workflow class.

License

This project is licensed under the MIT License - see the LICENSE file for details.