@avidjs/compose
Version:
Composes an array of middleware functions into a call-able middleware stack.
113 lines (96 loc) • 2.74 kB
JavaScript
/**
* @file lib/index.js
*/
;
/**
* An Avid context object.
* @typedef {Object} Context
* @property {Request} request
* @property {Response} response
*/
/**
* An Avid middleware function.
* @callback Middleware
* @param {Context} ctx
* @param {Function} next
*/
/**
* Composes an array of middleware functions into a call-able middleware stack.
* Throws TypeError if the middleware argument is not an array.
* @param {Array.<Middleware>} middleware
* @return {Function}
* @throws {TypeError}
* @example
* const avid = require('@avidjs/avid');
* const compose = require('@avidjs/compose');
*
* let app = avid();
*
* let middleware = compose([
* async (ctx, next) => {
* console.log('first');
* await next();
* console.log('fifth');
* },
* async (ctx, next) => {
* console.log('second');
* await next();
* console.log('fourth');
* }
* ]);
*
* app.use(middleware);
*
* app.use(async (ctx, next) => {
* console.log('third');
* ctx.res.end();
* });
*
* app.listen(3000);
*/
module.exports = function compose(middleware) {
if (!Array.isArray(middleware)) {
throw new TypeError('middleware stack must be an array');
}
/**
* Returns a call-able middleware stack.
* @param {Context} ctx
* @param {Middleware} next
* @return {Promise.<Context>}
*/
return function stack(ctx, next) {
/**
* Used to determine if `next()` has been called too often. Begins at -1,
* corresponding to an index "before" the bounds of an array, and is
* incremented by one with each call to `next()` for comparison in order
* to ensure that the middleware is traversed one at a time.
* @type {Number}
*/
let counter = -1;
/**
* Recursively executes the next middleware in the stack with the given
* context object. Resolves to the context object if there are no more
* middleware functions remaining in the middleware to be composed, or if
* there are no more middleware functions left in the external stack.
* @param {Context} ctx
* @param {Number} index
* @return {Promise.<Context>}
* @private
*/
return (function unwind(index) {
if (index <= counter) {
return Promise.reject(new Error('`next()` called multiple times'));
}
counter = index;
let fn = (index === middleware.length) ? next : middleware[index];
if (!fn) {
return Promise.resolve(ctx);
}
try {
return Promise.resolve(fn(ctx, () => unwind(index + 1)));
} catch (error) {
return Promise.reject(error);
}
})(0);
};
};