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

rowan

v2.0.0

Published

A lightweight async middleware library

Downloads

123

Readme

Rowan

A lightweight async middleware library.

584023-200

NPM version NPM downloads Travis Status codecov

Usage

Rowan can be used to build asynchronous middleware-style control-flow and error-handling, with particular focus on providing a rich typescript experience.

Create an instance of the Rowan class (or derivation) and call use with a middleware function

import {Rowan} from 'rowan';

// Create a (derived) app
const app = new Rowan();

// Add middleware and handlers
app.use(async (ctx) => {
  console.log(`foo: ${ctx.foo}`);
});

Once the middleware is all setup you call process and pass along the context.

// Use it 
await app.execute({ foo: "bar!" });

... which in this example would output to console:

foo: bar!

Processors

Processors are either a Handler<Ctx>, AutoHandler<Ctx> or Middleware<Ctx> type signature.

  • Handler is a two-parameter function that will be given the ctx and next callback and should return a Promise. You are required to call next if you wish processing to continue to the next middleware processors in the chain.
app.use(async (ctx, next) => {
  ctx["start"] = Date.now();
  await next();
  ctx["finish"] = Date.now();
});
  • AutoHandler is a one-parameter function that will be given the ctx object. The next processor in the chain will automatically be called for you, unless you throw an Error.
app.use(async (ctx) => {
  ctx.data = JSON.parse(ctx.raw);
});
  • Middleware is a object containing a method process that will be called with two-parameters: ctx and next. It is expected that process will return a Promise<void>.
app.use({
  async process(ctx, next){
    await next();
    consol.log("Complete");
  }
});

Helpers

after-if

calls next and if the predicate returns true executes its middleware

let foo = new Rowan();

foo.use(new AfterIf(
  async (ctx) => ctx.valid, [
  async (ctx) => {
    console.log("valid message: ", ctx.msg);
  }
]));

foo.use(async (ctx) => {
  console.log("validate...")
  if (ctx.msg && ctx.msg.length > 5) {
    ctx.valid = true
  }
})

async function main() {
  await foo.process({ msg: "hello" });
  await foo.process({ msg: "hello world" });
}

main().catch(console.log);

outputs:

validate...
validate...
valid message:  hello world

after

calls next first, then executes its own middleware afterwards

let foo = new Rowan();

foo.use(new After([
  async (ctx) => {
    console.log(ctx.output);
  }
]));

foo.use(async (ctx) => {
  console.log("processing...")
  ctx.output = ctx.msg;
});

async function main() {
  await foo.process({ msg: "hello" });
  await foo.process({ msg: "hello world" });
}

main().catch(console.log);

outputs:

processing...
hello
processing...
hello world

catch

wraps its own middleware with a try...catch

foo.use(
  new Catch(
    async (err, ctx) => {
      console.log("caught: ", err.message);
    },
    new Rowan()
      .use(
        async (ctx) => {
          if (ctx != "foo") {
            throw Error("ctx must be 'foo'");
          }
        })
      .use({
        meta: { name: "Moo" },
        async process(ctx, next) {
          console.log("Moo!");
          return next();
        }
      })
  ));

async function main() {
  await foo.process('foo');
  await foo.process('bar');
}

main().catch(console.log);

outputs:

Moo!
caught: ctx must be 'foo'

if

let foo = new Rowan<string>();

foo.use(
  new If(
    async (ctx: string) => {
      return ctx.startsWith("foo");
    },
    [async (ctx) => {
      console.log("IF...", ctx);
    }],
    /** terminate if predicate() == true */
    true, 
  )
);

foo.use(async (ctx) => {
  console.log("Else...", ctx);
})

async function main() {  
  await foo.process('foo');
  await foo.process('foobar');
  await foo.process('bar');
}

main().catch(console.log);

outputs:

IF... foo
IF... foobar
Else... bar

Tools

Rowan.hierarchy()

used to build a meta hierarchy from processors that have a middleware field defined.

let foo = new Rowan(undefined, { name: "FOO" });
let bar = new Rowan();

bar.meta.name = "Bar";

bar.use((ctx, next) => {
  console.log("boo1:", ctx);
  return next();
}, { name: "Boo1" });

bar.use(Object.assign((ctx, next) => {
  console.log("boo2:", ctx);
  return next();
}, { meta: { name: "Boo2" } }));

bar.use({
  meta: { name: "Boo3" },
  middleware: [{
    meta: { name: "Custom" },
    process(x, n) { console.log("Custom:", x); return n() }
  }],
  process: function (ctx, next) {
    console.log("Boo3:", ctx);
    return Rowan.process(this.middleware, ctx, next);
  }
});

foo.use(bar);

console.log(JSON.stringify(Rowan.hierarchy(foo), null, 2));

outputs:

{
  "meta": {
    "name": "FOO"
  },
  "children": [
    {
      "meta": {
        "name": "Bar"
      },
      "children": [
        {
          "meta": {
            "name": "Boo1"
          }
        },
        {
          "meta": {
            "name": "Boo2"
          }
        },
        {
          "meta": {
            "name": "Boo3"
          },
          "children": [
            {
              "meta": {
                "name": "Custom"
              }
            }
          ]
        }
      ]
    }
  ]
}

Advanced

Rowan.process()

executes and chains a sequence of Middleware, setting up the next callback for each.


async function main(next: ()=>Promise<void>) {
  Rowan.process(
    [{
      async process(ctx, next) {
        console.log("first");
        return next();
      }
    },
    {
      async process(ctx, next) {
        console.log("second");
        return next();
      }
    }],
    {
      msg: "hello"
    },
    //... optional next
    next
  )
}
main(async () => {
  console.log("END")
}).catch(console.log);

outputs:

first
second
END

Rowan.convertToMiddleware()

used interally to convert supported Handler types into valid Middleware.

Rowan.convertToMiddleware(async (ctx)=>{}, {name: "foo"});

results in:

{ 
  meta: { name: 'foo' }, 
  process: [Function] 
}

Build

npm install
npm test

there is an example.ts that you can run with ts-node

ts-node example

Credits

"Rowan" Icon courtesy of The Noun Project, by ludmil, under CC 3.0