1.8.88 • Published 10 months ago

@taktikorg/mollitia-dolore v1.8.88

Weekly downloads
-
License
MIT
Repository
github
Last release
10 months ago

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 '@taktikorg/mollitia-dolore';

// 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

less cssiterateObject.getPrototypeOfdatastructurelibphonenumberES8bundlingcomparerequestRegExp.prototype.flagsutilsArray.prototype.containscss variableoperating-systemlinkelbpostcssforEachlocalTypeBoxECMAScript 5passwordtoobjectspinnersserializeinrouteglobalInt8Arrayexit-codemake direrrorworkspace:*figletUint16Arrayfile systemdom-testing-librarytestgitignorecommand-lineargsArray.prototype.flatMaptouchpipeuploadFunction.prototype.nameRegExp#flagsmacosinternalRxselfminimalvpcstylesheetdeepcloneqschannelbannerquoteECMAScript 6settingscolumnFloat64ArraydataViewnativeECMAScript 2022kinesisrobustrecursivecolumnseffect-tsjsonreadablestreameslintconfigiecryptes5pruneprototypereducerutilIteratoreventEmitterclientfast-deep-copymakeconsumeworkerpluginarraybufferrgbfsextrabddObject.entries256dataviewtypedarrayfindLastIndexECMAScript 2018commanderhotsetterhas-ownarraypushconcatUint8ClampedArrayebsassignoptimistpyyamlcolorcryptotypedUint32ArrayStreamscreatedeepiterationObject.fromEntriesbindstyled-componentstelephoneindicatordiffInt32ArrayinterruptsES2019browserliststylingMicrosoftcjkhardlinksdependenciesmockacornbincertificatesasciiremovematchesentriesloadbalancingwindowBigInt64ArrayhasOwnpicomatchchromeprettycollectionArray.prototype.flatzerotranspileArray.prototype.includesreadoutputclassnamesless mixinsvalidationwgetreact-testing-libraryjoischeme-validationpropertysameValueZerocore-jstoSortedString.prototype.matchAlloptiongetOwnPropertyDescriptorstoragegatewayxhrregularsyntaxsidecommandawesomesaucees-abstractes2018computed-typescloudtrailECMAScript 2020optimizersignalanimationshrinkwrapArray.prototype.flattencheckglobalsCSSStyleDeclarationfromrapidpreprocessorpolyfillnodejsutilityroute53Objectfront-endidentifiers
1.8.88

10 months ago

1.8.87

10 months ago

1.8.86

10 months ago

1.8.85

10 months ago

1.8.84

10 months ago

1.8.83

10 months ago

1.8.82

11 months ago

1.8.81

11 months ago

1.8.80

11 months ago

1.8.79

11 months ago

1.8.78

11 months ago

1.8.77

11 months ago

1.8.76

11 months ago

1.8.75

11 months ago

1.8.74

11 months ago

1.8.73

11 months ago

1.8.72

11 months ago

1.8.71

11 months ago

1.8.70

11 months ago

1.8.69

11 months ago

1.8.68

11 months ago

1.8.67

11 months ago

1.8.66

11 months ago

1.8.65

11 months ago

1.8.64

11 months ago

1.7.64

11 months ago

1.7.63

11 months ago

1.7.62

11 months ago

1.7.61

11 months ago

1.6.61

11 months ago

1.6.60

11 months ago

1.6.59

11 months ago

1.6.58

11 months ago

1.6.57

11 months ago

1.6.56

11 months ago

1.6.55

11 months ago

1.6.54

12 months ago

1.6.53

12 months ago

1.6.52

12 months ago

1.6.51

12 months ago

1.5.51

12 months ago

1.5.50

12 months ago

1.5.49

12 months ago

1.5.48

12 months ago

1.5.47

12 months ago

1.5.46

12 months ago

1.5.45

12 months ago

1.5.44

12 months ago

1.5.43

12 months ago

1.5.42

12 months ago

1.4.42

12 months ago

1.4.41

12 months ago

1.4.40

12 months ago

1.4.39

12 months ago

1.4.38

12 months ago

1.4.37

12 months ago

1.4.36

1 year ago

1.4.35

1 year ago

1.4.34

1 year ago

1.4.33

1 year ago

1.3.33

1 year ago

1.3.32

1 year ago

1.3.31

1 year ago

1.3.30

1 year ago

1.3.29

1 year ago

1.3.28

1 year ago

1.3.27

1 year ago

1.3.26

1 year ago

1.3.25

1 year ago

1.3.24

1 year ago

1.3.23

1 year ago

1.3.22

1 year ago

1.3.21

1 year ago

1.3.20

1 year ago

1.3.19

1 year ago

1.3.18

1 year ago

1.3.17

1 year ago

1.3.16

1 year ago

1.3.15

1 year ago

1.3.14

1 year ago

1.3.13

1 year ago

1.3.12

1 year ago

1.3.11

1 year ago

1.2.11

1 year ago

1.2.10

1 year ago

1.2.9

1 year ago

1.2.8

1 year ago

1.1.8

1 year ago

1.1.7

1 year ago

1.1.6

1 year ago

1.1.5

1 year ago

1.0.5

1 year ago

1.0.4

1 year ago

1.0.3

1 year ago

1.0.2

1 year ago

1.0.1

1 year ago

1.0.0

1 year ago