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 🙏

© 2025 – Pkg Stats / Ryan Hefner

theater-flow

v0.1.2

Published

A library for building flows; safe data processing inspired by Monads

Readme

flow

Flow is a simple and easy effect manager for javascript and typescript. If you are struggling with runtime errors or if you want to add safety in your app, this library might interest you ℹ️

It's based on functional programming concept such as Monad to provides safe and "error less" flow of execution for any apps.

Get Started

To install flow using your favourite package manager:

bun install theater-flow

What is a flow

A flow is an execution safe zone. It means that any errors that can happens during a flow can be safely "catch" and represented in your app. Technically, it becomes possible to recover from any errors that can happens in your programms.

Basic Usage

Creating a Flow

A flow is a safe execution zone that handles both successful operations and errors in a predictable way. Here's how to create a basic flow:

import { flow, run } from "theater-flow";

// Create a simple flow that performs division
const divideFlow = flow((x: number, y: number) => {
  if (y === 0) {
    throw new Error("Division by zero is not allowed");
  }
  return x / y;
});

Creating Multiple Flows

You can chain multiple operations in a flow using various operators. Here's an example that processes a string through multiple transformations:

import { flow, run, map } from "theater-flow";

const processString = flow(
  // First operation: receive initial string
  (input: string) => input.toLowerCase(),

  // Map to transform the result
  map((str) => str.replace(/\s+/g, "_")),

  // Another transformation
  map((str) => str.toUpperCase()),

  // Final transformation
  map((str) => `Processed: ${str}`)
);

Running a Flow

To execute a flow, use the run function. Here's how to run flows and handle their results:

import { flow, run, isSuccess, isFailure } from "theater-flow";

const greetFlow = flow((name: string) => `Hello, ${name}!`);

async function example() {
  // Run the flow
  const result = await run(greetFlow, "John");

  // The result will contain status, value, and cause properties
  console.log(result);
  // Output: { status: 'success', value: 'Hello, John!', cause: undefined }
}

Retrieving Success Data

When a flow succeeds, you can access its value using the isSuccess helper:

import { flow, run, isSuccess } from "theater-flow";

const calculateFlow = flow((x: number, y: number) => x + y);

async function handleSuccess() {
  const result = await run(calculateFlow, 5, 3);

  if (isSuccess(result)) {
    console.log("Success value:", result.value); // Output: Success value: 8
    return result.value;
  }
}

Handling Errors

When a flow fails, you can access the error cause using the isFailure helper:

import { flow, run, isFailure } from "theater-flow";

const divideFlow = flow((x: number, y: number) => {
  if (y === 0) {
    throw new Error("Division by zero");
  }
  return x / y;
});

async function handleError() {
  const result = await run(divideFlow, 10, 0);

  if (isFailure(result)) {
    console.log("Error message:", result.cause.message); // Output: Error message: Division by zero
    // You can also access the full error cause
    console.log("Error cause:", result.cause);
  }
}

The flow system automatically wraps errors in a structured format, making it easier to handle and recover from errors in a consistent way. The cause property contains detailed information about what went wrong, allowing you to make informed decisions about how to handle the error.

This basic usage covers the fundamental patterns for working with flows. The library provides additional operators like bind, recover, map, and others for more advanced use cases and error handling strategies.

Using Result

In Flow, everything revolves around the Result type - think of it as a box that can contain either success 🎉, failure 💥, or abort 🛑. Understanding these types is crucial for mastering Flow.

Anatomy of a Result

A Result can be one of three types:

// Success: Everything went according to plan! 🎉
type Success<T> = {
  status: "success";
  value: T;
  cause: undefined;
};

// Failure: Oops, something went wrong! 💥
type Failure = {
  status: "failure";
  value: undefined;
  cause: Error;
};

// Abort: "I'm gonna stop you right there" 🛑
type Abort = {
  status: "abort";
  value: undefined;
  cause: undefined;
};

Creating Results

You can create results using helper functions:

import { success, failure, abort } from "theater-flow";

// When your code is living its best life
const happyPath = success("I'm feeling good!");
// { status: 'success', value: "I'm feeling good!", cause: undefined }

// When your code had a rough day
const badDay = failure(new Error("I forgot my coffee ☕"));
// { status: 'failure', value: undefined, cause: Error("I forgot my coffee ☕") }

// When your code decides to take a break
const timeOut = abort();
// { status: 'abort', value: undefined, cause: undefined }

Checking Result Types

Flow provides type guards to safely check what kind of result you're dealing with:

import { isSuccess, isFailure, isAbort } from "theater-flow";

function handleUserLogin(result: Result<User>) {
  if (isSuccess(result)) {
    console.log(`Welcome back, ${result.value.name}! 👋`);
    return;
  }

  if (isFailure(result)) {
    console.log(`Auth failed: ${result.cause.message} 😢`);
    return;
  }

  if (isAbort(result)) {
    console.log("Login process was cancelled. Did someone pull the plug? 🔌");
    return;
  }
}

Working with Results in Practice

Here's a more realistic example showing how results flow through your application:

import { flow, run, map, recover } from "theater-flow";

const makeSandwich = flow(
  async (ingredients: string[]) => {
    if (ingredients.length === 0) {
      throw new Error("Can't make a sandwich out of thin air! 🌫️");
    }

    if (!ingredients.includes("bread")) {
      throw new Error("A sandwich without bread is just... sad toppings 😢");
    }

    return `A delicious sandwich with ${ingredients.join(", ")} 🥪`;
  },
  recover((error) => `Sandwich making failed: ${error.message}`),
  map((result) => `${result} - Bon appétit! 🍽️`)
);

async function lunchTime() {
  // Success case
  const perfect = await run(makeSandwich, ["bread", "cheese", "ham"]);
  // Result: "A delicious sandwich with bread, cheese, ham 🥪 - Bon appétit! 🍽️"

  // Failure case (but recovered!)
  const noIngredients = await run(makeSandwich, []);
  // Result: "Sandwich making failed: Can't make a sandwich out of thin air! 🌫️ - Bon appétit! 🍽️"

  // Another failure case (but recovered!)
  const noBread = await run(makeSandwich, ["cheese", "ham"]);
  // Result: "Sandwich making failed: A sandwich without bread is just... sad toppings 😢 - Bon appétit! 🍽️"
}

Pro Tips for Working with Results

  1. Always handle all possible states (success, failure, abort) when checking results
  2. Use type guards (isSuccess, isFailure, isAbort) for type-safe access to values and causes
  3. Remember that success carries a value, failure carries a cause, and abort carries neither
  4. Results are immutable - once created, they can't be changed (just like that sandwich you made... no takebacks!)

Now that you understand the Result type system, you're ready to dive into Flow's operators and create more complex flows! 🌊

Using Operators

Flow comes with a toolkit of operators that are like Swiss Army knives for your code - except they won't accidentally cut you when you put them in your pocket! 🔪 Let's meet our cast of characters:

The map Operator

Think of map as your code's personal makeover artist - it takes your successful values and gives them a fresh new look, while completely ignoring the failures (just like how we ignore our ex's social media posts 😅):

import { flow, run, map } from "theater-flow";

const makeItFancy = flow(
  () => "hello",
  map((value) => value.toUpperCase()), // Giving it the LOUD treatment
  map(async (value) => value.length), // Counting its characters (async, because counting is hard)
  map((value) => value * 2) // Double it, because why not?
);

// Will turn our humble "hello" into a whopping 10
// (5 letters * 2, for those who skipped math class)
const result = await run(makeItFancy);

The bind Operator

bind is like a matchmaker for flows - it introduces one flow to another and hopes they'll hit it off. Perfect for when you want your flows to have a meaningful relationship 💑:

import { flow, run, bind } from "theater-flow";

const coffeeFlow = flow((type: string) => `${type} coffee`);
const addMilkFlow = flow((coffee: string) => `${coffee} with milk`);
const addSugarFlow = flow((coffee: string) => `${coffee} and sugar`);

const makeMyMorningBetter = flow(
  () => "espresso",
  bind(coffeeFlow), // First date with coffee
  bind(addMilkFlow), // Getting milky
  bind(addSugarFlow) // Sweet ending
);
// Result: "espresso coffee with milk and sugar" - Because adulting is hard

The recover Operator

recover is your code's safety net - like having a friend ready to catch you after a trust fall, except this friend actually shows up 🤸‍♂️:

import { flow, run, recover, map } from "theater-flow"

const divideLikeYouMeanIt = flow(
  (x: number, y: number) => {
    if (y === 0) throw new Error("Division by zero? Not today, Satan! 😈")
    return x / y
  },
  recover(error => `Math said no: ${error.message}`),
  map(result => `🧮 ${result}`)
)

// Will return "🧮 Math said no: Division by zero? Not today, Satan!"
const oops = await run(divideLikeYouMean It, 10, 0)

The eff Operator

eff is like that friend who promises to keep a secret - it does something on the side but pretends nothing happened (perfect for logging, or sneaking cookies from the jar 🍪):

import { flow, run, eff, map } from "theater-flow";

const sneakyFlow = flow(
  () => "super secret message",
  eff(async (value) => {
    console.log(`Don't tell anyone, but: ${value}`); // What happens in eff, stays in eff
  }),
  map((value) => value.toUpperCase())
);

The rightMap and rightBind Operators

These are like the evil twins of map and bind - they only work on failures. Perfect for when you want to make your errors more... entertaining 😈:

import { flow, run, rightMap, rightBind } from "theater-flow";

const makeErrorsFun = flow(
  () => {
    throw new Error("boring error message");
  },
  rightMap((error) => `🎭 ${error.message}`),
  rightBind(flow((msg) => `Why so serious? ${msg} 🃏`))
);

To async or not to async? 🤔

Here's a little secret about Flow: it doesn't care whether your functions are async or not - it's like that friend who accepts you whether you're a morning person or a night owl! Let's see this in action:

import { flow, run, map } from "theater-flow";

const whoCaresAboutAsync = flow(
  () => "I am sync", // Sync function? Cool! 😎
  map(async (value) => `${value} but now async`), // Async function? Also cool! 🚀
  map((value) => value.toUpperCase()), // Back to sync? No problem! 👍
  map(async (value) => `${value}!!!`) // Async again? Whatever! 🤷‍♂️
);

// Flow handles all of these transitions seamlessly
const result = await run(whoCaresAboutAsync);
// Result: "I AM SYNC BUT NOW ASYNC!!!"

But wait, there's more! Your flow functions can also return Result values directly. It's like ordering a pizza and getting it pre-sliced - sometimes you want to do the work yourself:

import { flow, run, success, failure, map } from "theater-flow";

const iKnowWhatImDoing = flow(
  // Sync function returning a Result
  (x: number) =>
    x > 0
      ? success("Positive vibes only! ✨")
      : failure(new Error("Why so negative? 😢")),

  // Async function returning a Result
  map(async (value) => {
    if (value.includes("vibes")) {
      return success("Groovy! 🕺");
    }
    return failure(new Error("Not groovy enough 😤"));
  }),

  // Mix and match as you please!
  map((value) => success(`${value} Let's dance! 💃`))
);

// All these styles play nicely together
const goodMood = await run(iKnowWhatImDoing, 42);
// Result: { status: 'success', value: "Groovy! Let's dance! 💃", cause: undefined }

const badMood = await run(iKnowWhatImDoing, -1);
// Result: { status: 'failure', cause: Error("Why so negative? 😢"), value: undefined }

Think of Flow as your code's personal assistant who:

  • Doesn't judge your async/sync lifestyle choices 🧘‍♀️
  • Handles all the Promise wrapping/unwrapping for you 🎁
  • Lets you return Results directly when you're feeling confident 💪
  • Makes everything work together like a well-choreographed dance routine 🕺💃

Remember: Whether you're going async, sync, or doing the Result-returning cha-cha, Flow's got your back! It's like having a very supportive friend who's also really good at organizing your chaos. 🌪️➡️✨

Pro Tips for Operator Mastery

  1. Mix and match operators like you're making a cocktail 🍸 - but remember, too many operators might give your code a hangover
  2. Always handle your errors - they have feelings too!
  3. When in doubt, map it out
  4. bind responsibly - not every flow needs to be chained together (we're writing code, not making paperclips)
  5. Use eff when you want to be sneaky, but remember: with great power comes great responsibility 🕷️

Remember, these operators are your friends (except when they're not, but that's probably your fault). Use them wisely, and they'll turn your error-prone code into a well-oiled machine that practically runs itself!

Now go forth and flow responsibly! 🌊