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

flowzilla

v1.0.4

Published

<div align="center"> <a href="https://github.com/bkniffler/flowzilla"> <img alt="flowzilla" src="https://raw.githubusercontent.com/bkniffler/flowzilla/master/assets/logo.png" height="250px" /> </a> </div> <div align="center"> <strong>Create flex

Downloads

8

Readme

flowzilla

Create flexible and extandable data flows by encapsulating operations into skills, making an ordered chain that can handle whatever request you throw at it. As soon as you send an action, flowzilla will run through all of its skills, mutating the action's value along until it is done. Each skill can also hook into the return chain to modify the value on its way back. The executional interface to flowzilla is based on promises, though internally, due to performance and flexibility, flowzilla uses callbacks. Thus, it can easily handle async stuff like http requests, image transformation, etc.

Flowzilla is based a lot on the idea of middlewares (made popular by expressjs) but is completely agnostic to what kind of operations it handles. It is also inspired by redux, though only on the action/middleware part. It excels in cases where you'd work with class overwriting and/or hooks to allow extending some part of functionality. A classic example is a database with different adapters and plugins (like soft-delete, auditing, time-stamping), a server that can handle requests, an authentication/authorization system, an API client. Basically anything that has a strong focus on how data flows.

It can also help you check your flow by providing a tracker that will fire on start and completion of your chain and each time a skill is entered.

This library works on node and in the browser, has no dependencies except for tslib (no dependencies, 2kb gzipped) and is tree shackable.

Get started

https://codepen.io/bkniffler/pen/eoNRWo?editors=0012

const { Flowzilla } = flowzilla;

// Declare the retry skill
function retrySkill(type, value, flow) {
  // Get retries from context
  const currentRetries = flow.get('retries', 0);
  // Compare with maxRetries from context
  if (currentRetries < flow.get('maxRetries', 0)) {
    // Call reset on fail, providing the current retries as new context
    flow.catch(err => flow.reset(type, value, { retries: currentRetries + 1 }));
  }
  // Continue normally
  flow(value);
}

// Declare the fetch skill
async function fetchSkill(type, value, flow) {
  // Check current type
  if (type === 'fetch') {
    // Fetch using browser' fetch & return the json response
    flow.return(await fetch(value).then(response => response.json()));
  } else {
    flow(value);
  }
}

// Define your own class, inheriting Flowzilla to define your own methods
class MyHTTPClient extends Flowzilla {
  constructor() {
    super();
    // Add both skills from above
    this.addSkill('retry', retrySkill);
    this.addSkill('fetch', fetchSkill);
  }
  // Define a fetch method that calls flowzilla.run with 'fetch' action type, a url and maxRetries as context
  fetch(url) {
    return this.run('fetch', url, {
      maxRetries: 3
    });
  }
}

// Try it!
async function work() {
  const client = new MyHTTPClient();
  console.log(
    await client.fetch('https://jsonplaceholder.typicode.com/todos/1')
  );
}

work();

Table of Contents

Install

Yarn/NPM

yarn add flowzilla
npm i flowzilla

CDN

A browser version is available on https://cdn.jsdelivr.net/npm/flowzilla

API Documentation

Flowzillas' structure is fairly simple. There is a Flowzilla class, which you add Skill functions to. Then you can run your flows. Read more about all API arguments below.

Flowzilla

const flowzilla = new Flowzilla();
flowzilla.addSkill((type, value, flow) => {
  if (type === 'append') {
    value.push(1);
  }
  flow(value);
});
flowzilla
  .run('append', [0])
  .then(result => console.log('Result', result))
  .catch(err => console.error(err));

Methods

constructor

Initiate a new instance, optionally providing a name.

  • const flowzilla = new Flowzilla(name?: string)

flowzilla.addSkill

Add a skill to flowzilla. Read more about skills in the #Skill section. You can control the order of your skills by either adding them in the according order or by providing the position.

  • flowzilla.addSkill(skill: Skill): void
  • flowzilla.addSkill(name: string, skill: Skill): void
  • flowzilla.addSkill(name: string, skill: Skill, position: 'AFTER'|'BEFORE'|'START'|'END', anchor?: any | any[]): void
// Add a skill with named function
function firstSkill(type, value, flow) {
  flow(value);
}
flowzilla.addSkill(firstSkill);

// Simple skill that does nothing
const simpleSkill = (type, value, flow) => flow(value);
// Add a skill with name
flowzilla.addSkill('skill1337', simpleSkill);
// Add a skill at start
flowzilla.addSkill(simpleSkill, 'START');
// Add a skill after firstSkill
flowzilla.addSkill(simpleSkill, 'AFTER', firstSkill);
// Add a skill before skill1337
flowzilla.addSkill(simpleSkill, 'BEFORE', 'skill1337');
// Add a skill before firstSkill and skill1337
flowzilla.addSkill(simpleSkill, 'BEFORE', [firstSkill, 'skill1337']);
// Add multiple skills
flowzilla.addSkill([simpleSkill, simpleSkill]);

flowzilla.removeSkill

Remove a skill from flowzilla, either by name or by the function.

  • flowzilla.removeSkill(skill: Skill | string): void

flowzilla.skill(s)Count

Number of skills currently in flowzilla instance.

  • flowzilla.skill(s)Count: number

flowzilla.run

Dispatch an action into flowzilla, optionally providing an initialValue and a context.

  • flowzilla.run<T>(type: string, initialValue?: any, context?: any): Promise<T>
flowzilla
  .run('fetch', { id: '123' }, { accessToken: 'BEARER 123' })
  .then(result => console.log('Result', result))
  .catch(err => console.error(err));

flowzilla.runSync

Dispatch an action into flowzilla, optionally providing an initialValue and a context. Will return whatever first skill returns. Handy for adding change-listeners.

  • flowzilla.runSync<T>(type: string, initialValue: any, context: any): Promise<T>
flowzilla.addSkill(function calc(type, value, flow) {
  if (type === 'multiply') {
    return value.reduce((sum, n) => sum * n, 1);
  }
});
const value = flowzilla.runSync('multiply', [2, 2]); // => 4;

Skill

A skill is only just a function

  • (type: string, value: any, flow: Flow): void
const calculator = (type, value, flow) => {
  if (type === 'add') {
    flow.return(value[0] + value[1]);
  } else if (type === 'multiply') {
    flow.return(value[0] * value[1]);
  } else {
    // Do nothing
    flow(value);
  }
};

Arguments

type

Type is a string that is defined when calling Flowzilla.run.

value

The value can be anything, and it can be altered in each skill.

flow

flow exposes multiple functions to control your data flow, read more in the section below.

Flow

  • flow(newValue) will continue to next skill (if any) or return (if none)
  • flow.return(finalValue) will force to return with specified value instead of proceeding to next
  • flow.run('new-action', value): Promise will start a new flow and await its value before continuing
  • flow.reset('new-action', value) will stop the current flow and start a new one
  • flow.catch((err, previousErrorHandler) => void) will add an error handler for subsequent flows
  • flow.get('key', defaultValue) get value from context
  • flow.set('key', value) set value in context

Guides

Context

You can get and set context inside of skills using flow.set(key: string, value: any) and flow.get(key: string, defaultValue?: any). Check below for an example.

Error Handling

You can catch any errors by using flowzilla.run(...).catch(err => void), but you can also catch subsequent skills from inside a skill using flow.catch((err, previousErrorHandler) => void).

Here is a simple retry mechanism using error handlers and context.

const flowzilla = new Flowzilla();
flowzilla.addSkill('retry', (type, value, flow) => {
  const maxRetries = flow.get('maxRetries', 0);
  const currentRetries = flow.get('retries', maxRetries);
  if (currentRetries < 3) {
    // Call reset on fail, providing the current retries as new context
    flow.catch(err => flow.reset(type, value, { retries: currentRetries + 1 }));
  }
  flow(value);
});
flowzilla.addSkill('error', async (type, value, flow) => {
  if (type === 'fetch') {
    flow.return(await fetch(value).then(response => response.json()));
  }
});
flowzilla
  // Provide context
  .run('fetch', 'https://jsonplaceholder.typicode.com/todos/1', {
    maxRetries: 3
  })
  .then(result => console.log('Result', result))
  .catch(err => console.error(err));

Examples

HTTP Client

Checkout Codepen Playground

Performance

The overhead of running flowzilla compared to using callbacks or a chain of promises is very low. Checkout the (simple) benchmark at: https://github.com/bkniffler/flowzilla/blob/master/src/tests/benchmark.test.ts

# Macbook Pro 13
callback x 761 ops/sec ±1.80% (74 runs sampled)
promise x 750 ops/sec ±0.99% (76 runs sampled)
flowzilla x 758 ops/sec ±0.98% (74 runs sampled)