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 🙏

© 2024 – Pkg Stats / Ryan Hefner

flowed

v1.14.7

Published

A fast and reliable flow engine for orchestration and more uses in *Node.js*, *Deno* and the browser

Downloads

1,027

Readme

Installation

npm i flowed

Browser

From public CDN

<script src="https://cdn.jsdelivr.net/npm/flowed@latest/dist/lib/flowed.js" charset="utf-8"></script>

Or replace latest in the URL for your desired version.

From package

<script src="./dist/lib/flowed.js" charset="utf-8"></script>

Getting Started

Main Features

Parallel execution

In order to run tasks in parallel, you don't have to do anything. Simply adding them to a flow, they will run in parallel, of course if they don't have dependence on each other.

Parallel Tasks

const flow = {
  tasks: {
    A: {
      provides: ['resultA'],
      resolver: {
        name: 'flowed::Noop',
      },
    },
    B: {
      provides: ['resultB'],
      resolver: {
        name: 'flowed::Noop',
      },
    },
    C: {
      requires: ['resultA', 'resultB'],
      resolver: {
        name: 'flowed::Noop',
      },
    },
  },
}
FlowManager.run(flow);

Dependency management

Only specifying dependent inputs and outputs the flow manages dependencies automatically, executing in the correct order, maximizing parallelism, and at the same time waiting for expected results when required. Note that you can specify dependence between tasks arbitrarily, not only when one of them needs results from the other.

Dependent Tasks

const flow = {
  tasks: {
    A: {
      provides: ['resultA'],
      resolver: {
        name: 'flowed::Noop',
      },
    },
    B: {
      requires: ['resultA'],
      provides: ['resultB'],
      resolver: {
        name: 'flowed::Noop',
      },
    },
    C: {
      requires: ['resultB'],
      resolver: {
        name: 'flowed::Noop',
      },
    },
  },
}
FlowManager.run(flow);

Asynchronous and synchronous tasks

Each task in a flow is associated to a "resolver", which is the piece of code that runs when the task executes. So, resolvers resolve the goals of tasks. Well, those resolvers can be synchronous (simply returning its results) or asynchronous (returning a promise). The flow will take care of promises, waiting for results according to dependencies.

JSON based flow specifications

The flow specifications are written in JSON format, which means:

  • They are easily serialized/unserialized for storage and transmission.
  • They are developer and IDE friendly, very easy to read and understand.
  • They are easily or actually directly transformable from and to JavaScript objects.
  • They are actually supported for most modern programming languages.
  • They are API friendly.

Or in a few words, JSON specs means saving a lot of time and headaches.

Parametrized running

The same flow can be ran many times with different parameters provided from outside. Parameters can satisfy requirements for tasks that expect for them, in the same way they can be provided as output of another task. That also means, if you already have pre-calculated results, you can provide them to the flow and accelerate its execution.

Scoped visibility for tasks

All tasks in a flow, even the ones that have the same resolver, have a private space of names for parameters and results. That means:

  • When developing a new resolver, the developer don't have take care of name collisions.
  • Resolvers are always reusable between flows.

But, what happends with resourses that I want to share all through the flow? Loggers, connections, application references, whatever. In that case, you can use a "context" that's also available in all the flow tasks. All resources in the contexts are shared.

Run flows from string, object, file or URL

Flow can be ran from a specification provided from a JSON string, a JavaScript object, a JSON file or an accessible URL that serves the spec in JSON.

Pause/Resume and Stop/Reset functions

Flow executions can be paused and resumed with task granularity. The same for stopping and resetting, that last being to set the flow up to start from the beginning.

Inline parameters transformation

Task parameters can be transformed before running each task using an inline template.

In this example, given the current date new Date(), a simple flow is used to get a plane JavaScript object with day, month and year.

The template embedded in the flow is:

{
  day: '{{date.getDate()}}',
  month: '{{date.getMonth() + 1}}',
  year: '{{date.getFullYear()}}'
}

Where date is known by the template because it is in the requires list of the task convertToObj.

const flow = {
  tasks: {
    getDate: {
      provides: ['date'],
      resolver: {
        name: 'flowed::Echo',
        params: { in: { value: new Date() } },
        results: { out: 'date' },
      }
    },
    convertToObj: {
      requires: ['date'],
      provides: ['result'],
      resolver: {
        name: 'flowed::Echo',
        params: {
          in: {
            transform: {
              day: '{{date.getDate()}}',
              month: '{{date.getMonth() + 1}}',
              year: '{{date.getFullYear()}}',
            }
          }
        },
        results: { out: 'result' },
      }
    }
  },
};
FlowManager.run(flow, {}, ['result']);

The result got today (01/03/2020) is { day: 3, month: 1, year: 2020 }.

In order to use the benefits of the template transformation I highly recommend to take a look at the ST documentation and check the features and examples. Also play with this tool provided by ST, and design your templates dynamically.

Cyclic flows

Flows can have cyclic dependencies forming loops. In order to run these flows, external parameters must raise the first execution.

Timer example:

This resolver will check a context value to know if the clock must continue ticking or not.

Also the current tick number is output to the console.

class Print {
  exec({ message }, context) {
    return new Promise((resolve, reject) => {

      let stop = false;
      context.counter++;
      if (context.counter === context.limit) {
        stop = true;
      }

      setTimeout(() => {
        console.log(message, context.counter);
        resolve(stop ? {} : { continue: true });
      }, 1000);
    });
  }
}

And the flow execution:

flowed.FlowManager.run({
    tasks: {
      tick: {
        requires: ['continue'],
        provides: ['continue'],
        resolver: {
          name: 'Print',
          params: { message: { value: 'Tick' } },
          results: { continue: 'continue' },
        }
      },
    },
  }, { continue: true }, [], { Print }, { counter: 0, limit: 5 },
);

The expected console output is:

Tick 1
Tick 2
Tick 3
Tick 4
Tick 5

Note that the task requires and provides the same value continue.

In order to solve the cyclic dependency and start running the flow, the first value for continue is provided in the parameters { continue: true }. Otherwise, the flow would never start.

The dependency loops can be formed by any number of tasks, not only by the same as in this simple example.

Library with reusable frequently used tasks

For several common tasks, resolvers are provided in the bundle, so you don't have to worry about programming the same thing over and over again. You just have to take care of your custom code.

For more information, please check the Built-In resolver library documentation

Or go directly to the desired resolver details:

Plugin system

Using the Flowed plugin system, any developer can add their own resolver library and easily integrate it into the flow engine.

Custom resolver libraries can even be published and distributed as independent NPM packages.

Debugging

(debugging doc coming soon...)