Détail du package

kareem

mongoosejs13.4mApache-2.03.0.0

Next-generation take on pre/post function hooks

readme

kareem

Build Status

Re-imagined take on the 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 2nd all-time leading scorer Kareem Abdul-Jabbar, known for his mastery of the hook shot

API

pre hooks

Much like 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

hooks.execPre('cook', null, function() {
  // ...
});

It runs basic serial pre hooks

pre hook functions take one parameter, a "done" function that you execute when your pre hook is finished.

let count = 0;

hooks.pre('cook', function(done) {
  ++count;
  done();
});

hooks.execPre('cook', null, function() {
  assert.equal(1, count);
});

It can run multiple pre hooks

let count1 = 0;
let 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);
});

It can run fully synchronous pre hooks

If your pre hook function takes no parameters, its assumed to be fully synchronous.

let count1 = 0;
let 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);
});

It properly attaches context to pre hooks

Pre save hook functions are bound to the second parameter to execPre()

hooks.pre('cook', function(done) {
  this.bacon = 3;
  done();
});

hooks.pre('cook', function(done) {
  this.eggs = 4;
  done();
});

const 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);
});

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().

hooks.pre('cook', true, function(next, done) {
  this.bacon = 3;
  next();
  setTimeout(function() {
    done();
  }, 5);
});

hooks.pre('cook', true, function(next, done) {
  next();
  const _this = this;
  setTimeout(function() {
    _this.eggs = 4;
    done();
  }, 10);
});

hooks.pre('cook', function(next) {
  this.waffles = false;
  next();
});

const 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);
});

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.

hooks.pre('cook', function() {
  return new Promise(resolve => {
    setTimeout(() => {
      this.bacon = 3;
      resolve();
    }, 100);
  });
});

const obj = { bacon: 0 };

hooks.execPre('cook', obj, function() {
  assert.equal(3, obj.bacon);
});

post hooks

It runs without any hooks specified

hooks.execPost('cook', null, [1], function(error, eggs) {
  assert.ifError(error);
  assert.equal(1, eggs);
  done();
});

It executes with parameters passed in

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);
});

It can use synchronous post hooks

const 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);
});

It supports returning a promise

You can also return a promise from your post hooks instead of calling next(). When the returned promise resolves, kareem will kick off the next middleware.

hooks.post('cook', function() {
  return new Promise(resolve => {
    setTimeout(() => {
      this.bacon = 3;
      resolve();
    }, 100);
  });
});

const obj = { bacon: 0 };

hooks.execPost('cook', obj, obj, function() {
  assert.equal(obj.bacon, 3);
});

wrap()

It wraps pre and post calls into one call

hooks.pre('cook', true, function(next, done) {
  this.bacon = 3;
  next();
  setTimeout(function() {
    done();
  }, 5);
});

hooks.pre('cook', true, function(next, done) {
  next();
  const _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';
});

const obj = { bacon: 0, eggs: 0 };

const 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);
});

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

hooks.pre('cook', true, function(next, done) {
  this.bacon = 3;
  next();
  setTimeout(function() {
    done();
  }, 5);
});

hooks.pre('cook', true, function(next, done) {
  next();
  const _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';
});

const obj = { bacon: 0, eggs: 0 };

const 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);
});

clone()

It clones a Kareem object

const k1 = new Kareem();
k1.pre('cook', function() {});
k1.post('cook', function() {});

const k2 = k1.clone();
assert.deepEqual(Array.from(k2._pres.keys()), ['cook']);
assert.deepEqual(Array.from(k2._posts.keys()), ['cook']);

merge()

It pulls hooks from another Kareem object

const k1 = new Kareem();
const test1 = function() {};
k1.pre('cook', test1);
k1.post('cook', function() {});

const k2 = new Kareem();
const test2 = function() {};
k2.pre('cook', test2);
const k3 = k2.merge(k1);
assert.equal(k3._pres.get('cook').length, 2);
assert.equal(k3._pres.get('cook')[0].fn, test2);
assert.equal(k3._pres.get('cook')[1].fn, test1);
assert.equal(k3._posts.get('cook').length, 1);

changelog

Changelog

3.0.0 (2025-11-18)

  • BREAKING CHANGE: make execPre async and drop callback support #39
  • BREAKING CHANGE: require Node 18
  • feat: overwriteArguments support #42

2.6.0 (2024-03-04)

  • feat: add TypeScript types

2.5.1 (2023-01-06)

  • fix: avoid passing final callback to pre hook, because calling the callback can mess up hook execution #36 Automattic/mongoose#12836

2.5.0 (2022-12-01)

  • feat: add errorHandler option to post() #34

2.4.0 (2022-06-13)

  • feat: add overwriteResult() and skipWrappedFunction() for more advanced control flow

2.3.4 (2022-02-10)

  • perf: various performance improvements #27 #24 #23 #22 #21 #20

2.3.3 (2021-12-26)

  • fix: handle sync errors in wrap()

2.3.2 (2020-12-08)

  • fix: handle sync errors in pre hooks if there are multiple hooks

2.3.0 (2018-09-24)

2.2.3 (2018-09-10)

2.2.2 (2018-09-10)

  • chore: release 2.2.2 (3f0144d)
  • fix: allow merge() to not clone (e628d65)

2.2.1 (2018-06-05)

  • chore: release 2.2.1 (4625a64)
  • chore: remove lockfile from git (7f3e4e6)
  • fix: handle numAsync correctly when merging (fef8e7e)
  • test: repro issue with not copying numAsync (952d9db)

2.2.0 (2018-06-05)

2.1.0 (2018-05-16)

  • chore: release 2.1.0 (ba5f1bc)
  • feat: add option to check wrapped function return value for promises (c9d7dd1)
  • refactor: use const in wrap() (0fc21f9)

2.0.7 (2018-04-28)

2.0.6 (2018-03-22)

2.0.5 (2018-02-22)

2.0.4 (2018-02-08)

2.0.3 (2018-02-01)

2.0.2 (2018-01-24)

2.0.1 (2018-01-09)

  • chore: release 2.0.1 with lockfile bump (09c44fb)

2.0.0 (2018-01-09)

  • chore: bump marked re: security (cc564a9)
  • chore: release 2.0.0 (f511d1c)

2.0.0-rc5 (2017-12-23)

  • chore: fix build on node 4+5 (6dac5a4)
  • chore: fix built on node 4 + 5 again (434ef0a)
  • chore: release 2.0.0-rc5 (25a32ee)

2.0.0-rc4 (2017-12-22)

2.0.0-rc3 (2017-12-22)

2.0.0-rc2 (2017-12-21)

  • chore: release 2.0.0-rc2 (76325fa)
  • fix: ensure next() and done() run in next tick (6c20684)

2.0.0-rc1 (2017-12-21)

2.0.0-rc0 (2017-12-17)

1.5.0 (2017-07-20)

1.4.2 (2017-07-06)

1.4.1 (2017-04-25)

  • chore: release 1.4.1 (5ecf0c2)
  • fix: handle numAsyncPres with clone() (c72e857), closes #8
  • test: repro #8 (9b4d6b2), closes #8

1.4.0 (2017-04-19)

1.3.0 (2017-03-26)

  • chore: release 1.3.0 (f3a9e50)
  • feat: pass function args to execPre (4dd466d)

1.2.1 (2017-02-03)

1.2.0 (2017-01-02)

1.1.5 (2016-12-13)

1.1.4 (2016-12-09)

  • chore: release 1.1.4 (ece401c)
  • chore: run tests on node 6 (e0cb1cb)
  • fix: only copy own properties in clone() (dfe28ce), closes #7

1.1.3 (2016-06-27)

  • chore: release 1.1.3 (87171c8)
  • fix: couple more issues with arg processing (c65f523)

1.1.2 (2016-06-27)

1.1.1 (2016-06-27)

  • chore: release 1.1.1 (8bb3050)
  • fix: skip error handlers if no error (0eb3a44)

1.1.0 (2016-05-11)

  • chore: release 1.1.0 (85332d9)
  • chore: test on node 4 and node 5 (1faefa1)
  • 100% coverage again (c9aee4e)
  • add support for error post hooks (d378113)
  • basic setup for sync hooks #4 (55aa081), closes #4
  • proof of concept for error handlers (e4a07d9)
  • refactor out handleWrapError helper (b19af38)

1.0.1 (2015-05-10)

1.0.0 (2015-01-28)

0.0.8 (2015-01-27)

0.0.7 (2015-01-04)

0.0.6 (2015-01-01)

  • Update docs and bump 0.0.6 (92c12a7)

0.0.5 (2015-01-01)

0.0.4 (2014-12-13)

  • Bump 0.0.4, run docs generation (51a15fe)
  • Use correct post parameters in wrap() (9bb5da3)

0.0.3 (2014-12-12)

  • Add npm test script, fix small bug with args not getting passed through post (49e3e68)
  • Bump 0.0.3 (65621d8)
  • Update readme (901388b)

0.0.2 (2014-12-12)

  • Add github repo and bump 0.0.2 (59db8be)

0.0.1 (2014-12-12)

2.2.5 (2018-09-24)

2.2.4 (2018-09-24)

2.2.3 (2018-09-24)

2.2.3 (2018-09-10)

2.2.2 (2018-09-10)

  • chore: release 2.2.2 (3f0144d)
  • fix: allow merge() to not clone (e628d65)

2.2.1 (2018-06-05)

  • chore: release 2.2.1 (4625a64)
  • chore: remove lockfile from git (7f3e4e6)
  • fix: handle numAsync correctly when merging (fef8e7e)
  • test: repro issue with not copying numAsync (952d9db)

2.2.0 (2018-06-05)

2.1.0 (2018-05-16)

  • chore: release 2.1.0 (ba5f1bc)
  • feat: add option to check wrapped function return value for promises (c9d7dd1)
  • refactor: use const in wrap() (0fc21f9)

2.0.7 (2018-04-28)

2.0.6 (2018-03-22)

2.0.5 (2018-02-22)

2.0.4 (2018-02-08)

2.0.3 (2018-02-01)

2.0.2 (2018-01-24)

2.0.1 (2018-01-09)

  • chore: release 2.0.1 with lockfile bump (09c44fb)

2.0.0 (2018-01-09)

  • chore: bump marked re: security (cc564a9)
  • chore: release 2.0.0 (f511d1c)

2.0.0-rc5 (2017-12-23)

  • chore: fix build on node 4+5 (6dac5a4)
  • chore: fix built on node 4 + 5 again (434ef0a)
  • chore: release 2.0.0-rc5 (25a32ee)

2.0.0-rc4 (2017-12-22)

2.0.0-rc3 (2017-12-22)

2.0.0-rc2 (2017-12-21)

  • chore: release 2.0.0-rc2 (76325fa)
  • fix: ensure next() and done() run in next tick (6c20684)

2.0.0-rc1 (2017-12-21)

2.0.0-rc0 (2017-12-17)

1.5.0 (2017-07-20)

1.4.2 (2017-07-06)

1.4.1 (2017-04-25)

  • chore: release 1.4.1 (5ecf0c2)
  • fix: handle numAsyncPres with clone() (c72e857), closes #8
  • test: repro #8 (9b4d6b2), closes #8

1.4.0 (2017-04-19)

1.3.0 (2017-03-26)

  • chore: release 1.3.0 (f3a9e50)
  • feat: pass function args to execPre (4dd466d)

1.2.1 (2017-02-03)

1.2.0 (2017-01-02)

1.1.5 (2016-12-13)

1.1.4 (2016-12-09)

  • chore: release 1.1.4 (ece401c)
  • chore: run tests on node 6 (e0cb1cb)
  • fix: only copy own properties in clone() (dfe28ce), closes #7

1.1.3 (2016-06-27)

  • chore: release 1.1.3 (87171c8)
  • fix: couple more issues with arg processing (c65f523)

1.1.2 (2016-06-27)

1.1.1 (2016-06-27)

  • chore: release 1.1.1 (8bb3050)
  • fix: skip error handlers if no error (0eb3a44)

1.1.0 (2016-05-11)

  • chore: release 1.1.0 (85332d9)
  • chore: test on node 4 and node 5 (1faefa1)
  • 100% coverage again (c9aee4e)
  • add support for error post hooks (d378113)
  • basic setup for sync hooks #4 (55aa081), closes #4
  • proof of concept for error handlers (e4a07d9)
  • refactor out handleWrapError helper (b19af38)

1.0.1 (2015-05-10)

1.0.0 (2015-01-28)

0.0.8 (2015-01-27)

0.0.7 (2015-01-04)

0.0.6 (2015-01-01)

  • Update docs and bump 0.0.6 (92c12a7)

0.0.5 (2015-01-01)

0.0.4 (2014-12-13)

  • Bump 0.0.4, run docs generation (51a15fe)
  • Use correct post parameters in wrap() (9bb5da3)

0.0.3 (2014-12-12)

  • Add npm test script, fix small bug with args not getting passed through post (49e3e68)
  • Bump 0.0.3 (65621d8)
  • Update readme (901388b)

0.0.2 (2014-12-12)

  • Add github repo and bump 0.0.2 (59db8be)

0.0.1 (2014-12-12)