Comparing version
119
loadware.js
@@ -1,107 +0,26 @@ | ||
/* | ||
loadware.js - middleware handling | ||
Normalizes a varied group of middleware descriptors into an array of middleware. | ||
loadware(['body-parser', [() => {}], ['cookie-parser', { session: () => {} }]]) => | ||
[() => {}, () => {}, () => {}, () => {}] | ||
*/ | ||
// loadware.js - Turn different middleware descriptors into an array of middleware | ||
require('app-module-path').addPath(process.cwd()); | ||
let isObject = obj => obj.toString() === '[object Object]'; | ||
// Retrieve the first key of an object | ||
let getKey = obj => isObject(obj) ? Object.keys(obj)[0] : false; | ||
// Retrieve the first value of an object | ||
let getValue = obj => obj[getKey(obj)]; | ||
// Put it all into a single array of non-arrays recursively | ||
// ['a', ['b', ['c', ...]]] => ['a', 'b', 'c', ...] | ||
let flat = middle => { | ||
while (middle.filter(mid => mid instanceof Array).length) { | ||
middle = middle.reduce((all, arg) => all.concat(arg), []); | ||
} | ||
return middle; | ||
} | ||
let flat = arr => arr.reduce((good, one) => { | ||
let flatten = Array.isArray(one) ? flat(one) : one || []; | ||
return good.concat(flatten) | ||
}, []); | ||
// Fetches the absolute path from the root | ||
// ['a', 'b'] => [require('a'), require('b')] | ||
// Note: this doesn't work: 'require(mid)' | ||
let include = mid => typeof mid === 'string' | ||
? require(require('path').resolve(mid)) | ||
: mid; | ||
// Expand a complex object into several simple ones | ||
// [{ a: 'a', b: 'b', c: 'c' }] => [{ a: 'a' }, { b: 'b' }, { c: 'c' }] | ||
let expand = middle => { | ||
middle = middle.map(mid => { | ||
if (!isObject(mid)) return mid; | ||
// Throw an error if there's something that is not a function anymore | ||
// [{ a: 'b' }] => throw new Error(); | ||
let others = mid => { | ||
if (mid instanceof Function) return mid; | ||
throw new Error("Only boolean, string, array or function can be middleware"); | ||
} | ||
let arr = []; | ||
for (let key in mid) { | ||
arr.push({ [key]: mid[key] }); | ||
} | ||
return arr; | ||
}); | ||
return flat(middle); | ||
}; | ||
// Get them all together in a single object | ||
let join = (obj, one) => Object.assign({}, obj, one); | ||
// Retrieve an object with the last known config | ||
let retrieveObjs = middle => middle.filter(isObject).reduce(join, {}); | ||
// Keep only the last copy for a named middleware | ||
let removeDups = (middle, objs) => middle.map(getKey) | ||
// Flag which objects to remove | ||
.map((key, i, all) => !key || all.lastIndexOf(key) === i) | ||
// Map the good copies to the original value | ||
.map((good, i) => good && middle[i]) | ||
// Defined && not an object || defined && a non-empty object | ||
.filter(mid => mid && (!getKey(mid) || getValue(mid))); | ||
module.exports = (...middle) => { | ||
// Make it into a non-null list of functions | ||
middle = flat(middle); | ||
// Put object with several keys into different objects | ||
// [{ a: 'a', b: 'b' }, { c: 'c' }] => [{ a: 'a' }, { b: 'b' }, { c: 'c' }] | ||
middle = expand(middle); | ||
// Get an object with each of the named middlewares | ||
// [{ a: 'a' }, { b: 'b' }, { a: 'c' }] => [{ a: 'c', b: 'b' }] | ||
let objs = retrieveObjs(middle); | ||
// Remove duplicated, previous objects | ||
// [{ a: 'a' }, { b: 'b' }, { a: 'c' }] => [{ b: 'b' }, { a: 'c' }] | ||
middle = removeDups(middle, objs); | ||
// Finally keep only the callback | ||
// [{ b: 'b' }, { a: 'c' }] => ['b', 'c'] | ||
middle = middle.map(mid => isObject(mid) ? getValue(mid) : mid); | ||
// Include from string | ||
// ['a', 'b'] => [require('a'), require('b')] | ||
middle = middle.map(mid => { | ||
if (typeof mid === 'string') { | ||
// Fetches the absolute path from the root | ||
// Note: this doesn't work: 'require(mid)' | ||
return require(require('path').resolve(mid)); | ||
} | ||
return mid; | ||
}); | ||
return middle; | ||
}; | ||
// The actual glue for them all | ||
module.exports = (...middle) => flat(middle).map(include).filter(others); |
{ | ||
"name": "loadware", | ||
"version": "1.0.0", | ||
"version": "2.0.0", | ||
"description": "A library to make sense of a bunch of middleware definitions and return a simple array of middleware\"", | ||
@@ -19,3 +19,3 @@ "main": "loadware.js", | ||
], | ||
"author": "Francisco Presencia <public@francisco.io>", | ||
"author": "Francisco Presencia <public@francisco.io> (http://francisco.io/)", | ||
"license": "MIT", | ||
@@ -27,2 +27,3 @@ "bugs": { | ||
"devDependencies": { | ||
"express": "^4.14.0", | ||
"jest": "^17.0.3", | ||
@@ -29,0 +30,0 @@ "pray": "^1.0.2" |
@@ -19,2 +19,4 @@ # loadware | ||
The middleware can be a string, a function or an array of any of the previous. | ||
This is part of another project which is WIP right now, but I think this is independently enough so it can be launched separately. |
let pray = require('pray'); | ||
let express = require('express'); | ||
let loadware = require('../loadware'); | ||
@@ -6,5 +7,13 @@ | ||
let allFn = arr => arr.forEach(pray.isFn); | ||
let fn = ctx => {}; | ||
let throws = cb => { | ||
let error; | ||
try { cb(); } catch(err) { error = err; } | ||
if (typeof error === 'undefined') { | ||
throw new Error(cb + ' did not throw an error as expected.'); | ||
} | ||
} | ||
describe('loadware.js', function() { | ||
describe('Initialization', () => { | ||
it('Can be used empty', () => { | ||
@@ -14,22 +23,60 @@ pray([])(loadware()); | ||
it('Ignores falsy values', () => { | ||
expect(loadware([], 0, "", false, null, undefined).length).toBe(0); | ||
}); | ||
it('Rejects numbers', () => { | ||
throws(() => loadware(5)); | ||
}); | ||
it('Rejects different objects', () => { | ||
throws(() => loadware({ a: 'b' })); | ||
throws(() => loadware(new Date())); | ||
throws(() => loadware(new Promise(() => {}))); | ||
}); | ||
}); | ||
describe('works with strings', () => { | ||
it('Can load from a string', () => { | ||
pray(allFn)(loadware('./tests/a')); | ||
pray(allFn)(loadware('./tests/a')); | ||
}); | ||
it('handles many arguments', () => { | ||
expect(loadware('./tests/a', './tests/b', './tests/c').length).toBe(3); | ||
}); | ||
it('handles nested strings', () => { | ||
expect(loadware(['./tests/a'], ['./tests/b', './tests/c']).length).toBe(3); | ||
}); | ||
it('handles deeply nested strings', () => { | ||
expect(loadware(['./tests/a', ['./tests/b', ['./tests/c']]]).length).toBe(3); | ||
}); | ||
it('Throws an error when non-existing string', () => { | ||
throws(() => loadware('./tests/dsfs')); | ||
}); | ||
}) | ||
describe('works with functions', () => { | ||
it('Converts function to array', () => { | ||
let demo = function(){}; | ||
pray([demo])(loadware(demo)); | ||
pray([fn])(loadware(fn)); | ||
}); | ||
it('Joins objects with the same key', () => { | ||
pray(allFn)(loadware({ a: './tests/a', b: './tests/b' }, { a: './tests/c' })); | ||
it('handles many arguments', () => { | ||
expect(loadware(fn, fn, fn, fn, fn, fn).length).toBe(6); | ||
}); | ||
it('Can load several objects and they overwrite each other where appropriate', () => { | ||
let fn = function(){}; | ||
let loaded = loadware({ a: './tests/a', b: './tests/b' }, { c: './tests/c' }, fn, { b: false }); | ||
pray(allFn)(loaded); | ||
expect(loaded).toHaveLength(3); | ||
it('handles nested arrays', () => { | ||
expect(loadware([fn], [fn, fn], [fn, fn, fn]).length).toBe(6); | ||
}); | ||
it('handles deeply nested arrays', () => { | ||
expect(loadware([fn, [fn, [fn, [fn, [fn, [fn]]]]]]).length).toBe(6); | ||
}); | ||
// When passing Router() it only rendered the last one since it had some properties | ||
@@ -43,6 +90,24 @@ it('Treats a function as a function even if it has properties', () => { | ||
}); | ||
}); | ||
it('Default stays the same', () => { | ||
pray(allFn)(loadware([{ hi: './tests/a' }])); | ||
describe('plays well with others', () => { | ||
it('works with express router USE', () => { | ||
let router = express.Router(); | ||
router.use('/', fn); | ||
pray([router])(loadware(router)); | ||
}); | ||
it('works with express router GET', () => { | ||
let router = express.Router(); | ||
router.get('/', fn); | ||
pray([router])(loadware(router)); | ||
}); | ||
it('works with express router POST', () => { | ||
let router = express.Router(); | ||
router.post('/', fn); | ||
pray([router])(loadware(router)); | ||
}); | ||
}); |
22
10%6913
-4.19%3
50%110
-0.9%