# kareem [![Build Status](https://travis-ci.org/vkarpov15/kareem.svg?branch=master)](https://travis-ci.org/vkarpov15/kareem) [![Coverage Status](https://img.shields.io/coveralls/vkarpov15/kareem.svg)](https://coveralls.io/r/vkarpov15/kareem) Re-imagined take on the [hooks](http://npmjs.org/package/hooks) module, meant to offer additional flexibility in allowing you to execute hooks whenever necessary, as opposed to simply wrapping a single function. Named for the NBA's all-time leading scorer Kareem Abdul-Jabbar, known for his mastery of the [hook shot](http://en.wikipedia.org/wiki/Kareem_Abdul-Jabbar#Skyhook) # API ## pre hooks Much like [hooks](https://npmjs.org/package/hooks), kareem lets you define pre and post hooks: pre hooks are called before a given function executes. Unlike hooks, kareem stores hooks and other internal state in a separate object, rather than relying on inheritance. Furthermore, kareem exposes an `execPre()` function that allows you to execute your pre hooks when appropriate, giving you more fine-grained control over your function hooks. #### It runs without any hooks specified ```javascript hooks.execPre('cook', null, function() { done(); }); ``` #### It runs basic serial pre hooks pre hook functions take one parameter, a "done" function that you execute when your pre hook is finished. ```javascript var count = 0; hooks.pre('cook', function(done) { ++count; done(); }); hooks.execPre('cook', null, function() { assert.equal(1, count); done(); }); ``` #### It can run multipe pre hooks ```javascript var count1 = 0; var count2 = 0; hooks.pre('cook', function(done) { ++count1; done(); }); hooks.pre('cook', function(done) { ++count2; done(); }); hooks.execPre('cook', null, function() { assert.equal(1, count1); assert.equal(1, count2); done(); }); ``` #### It can run fully synchronous pre hooks If your pre hook function takes no parameters, its assumed to be fully synchronous. ```javascript var count1 = 0; var count2 = 0; hooks.pre('cook', function() { ++count1; }); hooks.pre('cook', function() { ++count2; }); hooks.execPre('cook', null, function(error) { assert.equal(null, error); assert.equal(1, count1); assert.equal(1, count2); done(); }); ``` #### It properly attaches context to pre hooks Pre save hook functions are bound to the second parameter to `execPre()` ```javascript hooks.pre('cook', function(done) { this.bacon = 3; done(); }); hooks.pre('cook', function(done) { this.eggs = 4; done(); }); var obj = { bacon: 0, eggs: 0 }; // In the pre hooks, `this` will refer to `obj` hooks.execPre('cook', obj, function(error) { assert.equal(null, error); assert.equal(3, obj.bacon); assert.equal(4, obj.eggs); done(); }); ``` #### It can execute parallel (async) pre hooks Like the hooks module, you can declare "async" pre hooks - these take two parameters, the functions `next()` and `done()`. `next()` passes control to the next pre hook, but the underlying function won't be called until all async pre hooks have called `done()`. ```javascript hooks.pre('cook', true, function(next, done) { this.bacon = 3; next(); setTimeout(function() { done(); }, 5); }); hooks.pre('cook', true, function(next, done) { next(); var _this = this; setTimeout(function() { _this.eggs = 4; done(); }, 10); }); hooks.pre('cook', function(next) { this.waffles = false; next(); }); var obj = { bacon: 0, eggs: 0 }; hooks.execPre('cook', obj, function() { assert.equal(3, obj.bacon); assert.equal(4, obj.eggs); assert.equal(false, obj.waffles); done(); }); ``` #### It supports returning a promise You can also return a promise from your pre hooks instead of calling `next()`. When the returned promise resolves, kareem will kick off the next middleware. ```javascript hooks.pre('cook', function() { return new Promise(resolve => { setTimeout(() => { this.bacon = 3; resolve(); }, 100); }); }); var obj = { bacon: 0 }; hooks.execPre('cook', obj, function() { assert.equal(3, obj.bacon); done(); }); ``` ## post hooks #### It runs without any hooks specified ```javascript hooks.execPost('cook', null, [1], function(error, eggs) { assert.ifError(error); assert.equal(1, eggs); done(); }); ``` #### It executes with parameters passed in ```javascript hooks.post('cook', function(eggs, bacon, callback) { assert.equal(1, eggs); assert.equal(2, bacon); callback(); }); hooks.execPost('cook', null, [1, 2], function(error, eggs, bacon) { assert.ifError(error); assert.equal(1, eggs); assert.equal(2, bacon); done(); }); ``` #### It can use synchronous post hooks ```javascript var execed = {}; hooks.post('cook', function(eggs, bacon) { execed.first = true; assert.equal(1, eggs); assert.equal(2, bacon); }); hooks.post('cook', function(eggs, bacon, callback) { execed.second = true; assert.equal(1, eggs); assert.equal(2, bacon); callback(); }); hooks.execPost('cook', null, [1, 2], function(error, eggs, bacon) { assert.ifError(error); assert.equal(2, Object.keys(execed).length); assert.ok(execed.first); assert.ok(execed.second); assert.equal(1, eggs); assert.equal(2, bacon); done(); }); ``` ## wrap() #### It wraps pre and post calls into one call ```javascript hooks.pre('cook', true, function(next, done) { this.bacon = 3; next(); setTimeout(function() { done(); }, 5); }); hooks.pre('cook', true, function(next, done) { next(); var _this = this; setTimeout(function() { _this.eggs = 4; done(); }, 10); }); hooks.pre('cook', function(next) { this.waffles = false; next(); }); hooks.post('cook', function(obj) { obj.tofu = 'no'; }); var obj = { bacon: 0, eggs: 0 }; var args = [obj]; args.push(function(error, result) { assert.ifError(error); assert.equal(null, error); assert.equal(3, obj.bacon); assert.equal(4, obj.eggs); assert.equal(false, obj.waffles); assert.equal('no', obj.tofu); assert.equal(obj, result); done(); }); hooks.wrap( 'cook', function(o, callback) { assert.equal(3, obj.bacon); assert.equal(4, obj.eggs); assert.equal(false, obj.waffles); assert.equal(undefined, obj.tofu); callback(null, o); }, obj, args); ``` ## createWrapper() #### It wraps wrap() into a callable function ```javascript hooks.pre('cook', true, function(next, done) { this.bacon = 3; next(); setTimeout(function() { done(); }, 5); }); hooks.pre('cook', true, function(next, done) { next(); var _this = this; setTimeout(function() { _this.eggs = 4; done(); }, 10); }); hooks.pre('cook', function(next) { this.waffles = false; next(); }); hooks.post('cook', function(obj) { obj.tofu = 'no'; }); var obj = { bacon: 0, eggs: 0 }; var cook = hooks.createWrapper( 'cook', function(o, callback) { assert.equal(3, obj.bacon); assert.equal(4, obj.eggs); assert.equal(false, obj.waffles); assert.equal(undefined, obj.tofu); callback(null, o); }, obj); cook(obj, function(error, result) { assert.ifError(error); assert.equal(3, obj.bacon); assert.equal(4, obj.eggs); assert.equal(false, obj.waffles); assert.equal('no', obj.tofu); assert.equal(obj, result); done(); }); ``` ## clone() #### It clones a Kareem object ```javascript var k1 = new Kareem(); k1.pre('cook', function() {}); k1.post('cook', function() {}); var k2 = k1.clone(); assert.deepEqual(['cook'], Object.keys(k2._pres)); assert.deepEqual(['cook'], Object.keys(k2._posts)); ``` ## merge() #### It pulls hooks from another Kareem object ```javascript var k1 = new Kareem(); var test1 = function() {}; k1.pre('cook', test1); k1.post('cook', function() {}); var k2 = new Kareem(); var test2 = function() {}; k2.pre('cook', test2); var k3 = k2.merge(k1); assert.equal(k3._pres['cook'].length, 2); assert.equal(k3._pres['cook'][0].fn, test2); assert.equal(k3._pres['cook'][1].fn, test1); assert.equal(k3._posts['cook'].length, 1); ```