@taktikorg/mollitia-dolore v1.8.88
Rowan
A lightweight async middleware library.
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
andnext
callback and should return a Promise. You are required to callnext
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
andnext
. It is expected thatprocess
will return aPromise<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
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago