├── .npmrc ├── .npmignore ├── SECURITY.md ├── .gitignore ├── package.json ├── .github └── workflows │ └── test.yml ├── docs.js ├── index.d.ts ├── test ├── misc.test.js ├── pre.test.js ├── post.test.js ├── examples.test.js └── wrap.test.js ├── .eslintrc.json ├── README.md ├── LICENSE ├── index.js └── CHANGELOG.md /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | docs.js 2 | test 3 | .github 4 | .eslintrc.json 5 | .npmrc 6 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security contact information 2 | 3 | To report a security vulnerability, please use the 4 | [Tidelift security contact](https://tidelift.com/security). 5 | Tidelift will coordinate the fix and disclosure. 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | 30 | package-lock.json 31 | yarn.lock 32 | 33 | .nyc_output 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kareem", 3 | "version": "3.0.0", 4 | "description": "Next-generation take on pre/post function hooks", 5 | "main": "index.js", 6 | "scripts": { 7 | "lint": "eslint .", 8 | "test": "mocha ./test/*", 9 | "test-coverage": "nyc --reporter lcov mocha ./test/*", 10 | "docs": "node ./docs.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/mongoosejs/kareem.git" 15 | }, 16 | "devDependencies": { 17 | "acquit": "1.x", 18 | "acquit-ignore": "0.2.x", 19 | "eslint": "8.20.0", 20 | "mocha": "9.2.0", 21 | "nyc": "15.1.0" 22 | }, 23 | "author": "Valeri Karpov ", 24 | "license": "Apache-2.0", 25 | "engines": { 26 | "node": ">=18.0.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | pull_request: 4 | push: 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | test: 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | node: [18, 20, 22] 15 | os: [ubuntu-24.04] 16 | name: Node ${{ matrix.node }} 17 | steps: 18 | - uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 # v3 19 | 20 | - name: Setup node 21 | uses: actions/setup-node@5b52f097d36d4b0b2f94ed6de710023fbb8b2236 # v3.1.0 22 | with: 23 | node-version: ${{ matrix.node }} 24 | 25 | - run: npm install 26 | 27 | - run: npm run test-coverage 28 | 29 | lint: 30 | runs-on: ${{ matrix.os }} 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | node: [22] 35 | os: [ubuntu-24.04] 36 | name: Lint 37 | steps: 38 | - uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 # v3 39 | 40 | - name: Setup node 41 | uses: actions/setup-node@5b52f097d36d4b0b2f94ed6de710023fbb8b2236 # v3.1.0 42 | with: 43 | node-version: ${{ matrix.node }} 44 | 45 | - run: npm install 46 | 47 | - run: npm run lint 48 | -------------------------------------------------------------------------------- /docs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const acquit = require('acquit'); 4 | const fs = require('fs'); 5 | 6 | require('acquit-ignore')(); 7 | 8 | const content = fs.readFileSync('./test/examples.test.js').toString(); 9 | const blocks = acquit.parse(content); 10 | 11 | // include the README until after the specified tag as static non-generated content 12 | const existingReadme = fs.readFileSync('./README.md').toString(); 13 | const searchRegion = ''; 14 | const untilIndex = existingReadme.indexOf(searchRegion); 15 | 16 | if (untilIndex === -1) { 17 | } 18 | 19 | let mdOutput = existingReadme.substring(0, untilIndex + searchRegion.length) + '\n\n# API'; 20 | 21 | for (const describe of blocks) { 22 | mdOutput += '\n\n'; 23 | mdOutput += '## ' + describe.contents; 24 | // only add spacing and comments, if there are comments 25 | if (describe.comments[0]) { 26 | mdOutput += '\n\n'; 27 | // acquit "trimEachLine" does not actually trim the last line for some reason 28 | mdOutput += acquit.trimEachLine(describe.comments[0]).trim(); 29 | } 30 | 31 | for (const it of describe.blocks) { 32 | mdOutput += '\n\n'; 33 | mdOutput += '### It ' + it.contents + '\n\n'; 34 | 35 | if (it.comments[0]) { 36 | mdOutput += acquit.trimEachLine(it.comments[0]) + '\n'; 37 | } 38 | 39 | mdOutput += '```javascript\n'; 40 | mdOutput += it.code + '\n'; 41 | mdOutput += '```'; 42 | } 43 | } 44 | 45 | mdOutput += '\n'; 46 | 47 | fs.writeFileSync('README.md', mdOutput); 48 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "kareem" { 2 | export default class Kareem { 3 | static skipWrappedFunction(): SkipWrappedFunction; 4 | static overwriteMiddlewareResult(): OverwriteMiddlewareResult; 5 | static overwriteArguments(): OverwriteArguments; 6 | 7 | pre(name: string | RegExp, fn: Function): this; 8 | pre(name: string | RegExp, options: Record, fn: Function, error?: any, unshift?: boolean): this; 9 | post(name: string | RegExp, fn: Function): this; 10 | post(name: string | RegExp, options: Record, fn: Function, unshift?: boolean): this; 11 | 12 | clone(): Kareem; 13 | merge(other: Kareem, clone?: boolean): this; 14 | 15 | createWrapper(name: string, fn: Function, context?: any, options?: Record): Function; 16 | createWrapperSync(name: string, fn: Function): Function; 17 | hasHooks(name: string): boolean; 18 | filter(fn: Function): Kareem; 19 | 20 | wrap(name: string, fn: Function, context: any, args: any[], options?: Record): Function; 21 | 22 | execPostSync(name: string, context: any, args: any[]): any; 23 | execPost(name: string, context: any, args: any[], options?: Record, callback?: Function): void; 24 | execPreSync(name: string, context: any, args: any[]): any; 25 | execPre(name: string, context: any, args: any[], callback?: Function): void; 26 | } 27 | 28 | class SkipWrappedFunction {} 29 | class OverwriteMiddlewareResult {} 30 | class OverwriteArguments {} 31 | } 32 | -------------------------------------------------------------------------------- /test/misc.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const Kareem = require('../'); 5 | const { describe, it } = require('mocha'); 6 | 7 | describe('hasHooks', function() { 8 | it('returns false for toString (Automattic/mongoose#6538)', function() { 9 | const k = new Kareem(); 10 | assert.ok(!k.hasHooks('toString')); 11 | }); 12 | }); 13 | 14 | describe('filter', function() { 15 | it('returns clone with only hooks that match `fn()`', function() { 16 | const k1 = new Kareem(); 17 | 18 | k1.pre('update', { document: true }, f1); 19 | k1.pre('update', { query: true }, f2); 20 | k1.pre('remove', { document: true }, f3); 21 | 22 | k1.post('update', { document: true }, f1); 23 | k1.post('update', { query: true }, f2); 24 | k1.post('remove', { document: true }, f3); 25 | 26 | const k2 = k1.filter(hook => hook.document); 27 | assert.equal(k2._pres.get('update').length, 1); 28 | assert.equal(k2._pres.get('update')[0].fn, f1); 29 | assert.equal(k2._pres.get('remove').length, 1); 30 | assert.equal(k2._pres.get('remove')[0].fn, f3); 31 | 32 | assert.equal(k2._posts.get('update').length, 1); 33 | assert.equal(k2._posts.get('update')[0].fn, f1); 34 | assert.equal(k2._posts.get('remove').length, 1); 35 | assert.equal(k2._posts.get('remove')[0].fn, f3); 36 | 37 | const k3 = k1.filter(hook => hook.query); 38 | assert.equal(k3._pres.get('update').length, 1); 39 | assert.equal(k3._pres.get('update')[0].fn, f2); 40 | assert.ok(!k3._pres.has('remove')); 41 | 42 | assert.equal(k3._posts.get('update').length, 1); 43 | assert.equal(k3._posts.get('update')[0].fn, f2); 44 | assert.ok(!k3._posts.has('remove')); 45 | 46 | function f1() {} 47 | function f2() {} 48 | function f3() {} 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended" 4 | ], 5 | "ignorePatterns": [ 6 | "docs", 7 | "tools", 8 | "dist", 9 | "website.js", 10 | "test/files/*", 11 | "benchmarks" 12 | ], 13 | "overrides": [], 14 | "plugins": [], 15 | "parserOptions": { 16 | "ecmaVersion": 2020 17 | }, 18 | "env": { 19 | "node": true, 20 | "es6": true 21 | }, 22 | "rules": { 23 | "no-unused-vars": ["error", { "argsIgnorePattern": "^_" } ], 24 | "comma-style": "error", 25 | "indent": [ 26 | "error", 27 | 2, 28 | { 29 | "SwitchCase": 1, 30 | "VariableDeclarator": 2 31 | } 32 | ], 33 | "keyword-spacing": "error", 34 | "no-whitespace-before-property": "error", 35 | "no-buffer-constructor": "warn", 36 | "no-console": "off", 37 | "no-constant-condition": "off", 38 | "no-multi-spaces": "error", 39 | "func-call-spacing": "error", 40 | "no-trailing-spaces": "error", 41 | "no-undef": "error", 42 | "no-unneeded-ternary": "error", 43 | "no-const-assign": "error", 44 | "no-useless-rename": "error", 45 | "no-dupe-keys": "error", 46 | "space-in-parens": [ 47 | "error", 48 | "never" 49 | ], 50 | "spaced-comment": [ 51 | "error", 52 | "always", 53 | { 54 | "block": { 55 | "markers": [ 56 | "!" 57 | ], 58 | "balanced": true 59 | } 60 | } 61 | ], 62 | "key-spacing": [ 63 | "error", 64 | { 65 | "beforeColon": false, 66 | "afterColon": true 67 | } 68 | ], 69 | "comma-spacing": [ 70 | "error", 71 | { 72 | "before": false, 73 | "after": true 74 | } 75 | ], 76 | "array-bracket-spacing": 1, 77 | "arrow-spacing": [ 78 | "error", 79 | { 80 | "before": true, 81 | "after": true 82 | } 83 | ], 84 | "object-curly-spacing": [ 85 | "error", 86 | "always" 87 | ], 88 | "comma-dangle": [ 89 | "error", 90 | "never" 91 | ], 92 | "no-unreachable": "error", 93 | "quotes": [ 94 | "error", 95 | "single" 96 | ], 97 | "quote-props": [ 98 | "error", 99 | "as-needed" 100 | ], 101 | "semi": "error", 102 | "no-extra-semi": "error", 103 | "semi-spacing": "error", 104 | "no-spaced-func": "error", 105 | "no-throw-literal": "error", 106 | "space-before-blocks": "error", 107 | "space-before-function-paren": [ 108 | "error", 109 | "never" 110 | ], 111 | "space-infix-ops": "error", 112 | "space-unary-ops": "error", 113 | "no-var": "warn", 114 | "prefer-const": "warn", 115 | "strict": [ 116 | "error", 117 | "global" 118 | ], 119 | "no-restricted-globals": [ 120 | "error", 121 | { 122 | "name": "context", 123 | "message": "Don't use Mocha's global context" 124 | } 125 | ], 126 | "no-prototype-builtins": "off", 127 | "no-empty": "off", 128 | "eol-last": "warn", 129 | "no-multiple-empty-lines": ["warn", { "max": 2 }] 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /test/pre.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const Kareem = require('../'); 5 | const { beforeEach, describe, it } = require('mocha'); 6 | 7 | describe('execPre', function() { 8 | let hooks; 9 | 10 | beforeEach(function() { 11 | hooks = new Kareem(); 12 | }); 13 | 14 | it('handles errors with multiple pres', async function() { 15 | const execed = {}; 16 | 17 | hooks.pre('cook', async function() { 18 | execed.first = true; 19 | }); 20 | 21 | hooks.pre('cook', async function() { 22 | execed.second = true; 23 | throw new Error('error!'); 24 | }); 25 | 26 | hooks.pre('cook', async function() { 27 | execed.third = true; 28 | }); 29 | 30 | await assert.rejects(hooks.execPre('cook', null), /error!/); 31 | assert.equal(2, Object.keys(execed).length); 32 | assert.ok(execed.first); 33 | assert.ok(execed.second); 34 | }); 35 | 36 | it('sync errors', async function() { 37 | let called = 0; 38 | 39 | hooks.pre('cook', function() { 40 | throw new Error('woops!'); 41 | }); 42 | 43 | hooks.pre('cook', function() { 44 | ++called; 45 | }); 46 | 47 | await assert.rejects(hooks.execPre('cook', null), /woops!/); 48 | assert.equal(called, 0); 49 | }); 50 | 51 | it('unshift', function() { 52 | const f1 = function() {}; 53 | const f2 = function() {}; 54 | hooks.pre('cook', false, f1); 55 | hooks.pre('cook', false, f2, null, true); 56 | assert.strictEqual(hooks._pres.get('cook')[0].fn, f2); 57 | assert.strictEqual(hooks._pres.get('cook')[1].fn, f1); 58 | }); 59 | 60 | it('throws error if no function', function() { 61 | assert.throws(() => hooks.pre('test'), /got "undefined"/); 62 | }); 63 | 64 | it('arbitrary options', function() { 65 | const f1 = function() {}; 66 | const f2 = function() {}; 67 | hooks.pre('cook', { foo: 'bar' }, f1); 68 | hooks.pre('cook', { bar: 'baz' }, f2, null, true); 69 | assert.equal(hooks._pres.get('cook')[1].foo, 'bar'); 70 | assert.equal(hooks._pres.get('cook')[0].bar, 'baz'); 71 | }); 72 | 73 | it('handles sync errors in pre if there are more hooks', async function() { 74 | const execed = {}; 75 | 76 | hooks.pre('cook', function() { 77 | execed.first = true; 78 | throw new Error('Oops!'); 79 | }); 80 | 81 | hooks.pre('cook', function() { 82 | execed.second = true; 83 | }); 84 | 85 | const err = await hooks.execPre('cook', null).then(() => null, err => err); 86 | assert.ok(err); 87 | assert.ok(execed.first); 88 | assert.equal(err.message, 'Oops!'); 89 | }); 90 | 91 | it('supports skipWrappedFunction', async function() { 92 | const execed = {}; 93 | 94 | hooks.pre('cook', function() { 95 | throw Kareem.skipWrappedFunction(42); 96 | }); 97 | 98 | hooks.pre('cook', function() { 99 | execed.second = true; 100 | }); 101 | 102 | const err = await hooks.execPre('cook', null).then(() => null, err => err); 103 | assert.ok(execed.second); 104 | assert.ok(err instanceof Kareem.skipWrappedFunction); 105 | }); 106 | 107 | it('supports overwriteArguments with return', async function() { 108 | hooks.pre('init', function(obj) { 109 | if (typeof obj === 'string') { 110 | return Kareem.overwriteArguments({ name: obj }); 111 | } 112 | }); 113 | 114 | const args = await hooks.execPre('init', null, ['test']); 115 | assert.strictEqual(args.length, 1); 116 | assert.strictEqual(typeof args[0], 'object'); 117 | assert.strictEqual(args[0].name, 'test'); 118 | }); 119 | 120 | it('supports overwriteArguments with throw', async function() { 121 | hooks.pre('init', function(obj) { 122 | if (typeof obj === 'string') { 123 | throw Kareem.overwriteArguments({ name: obj }); 124 | } 125 | }); 126 | 127 | const args = await hooks.execPre('init', null, ['test']); 128 | assert.strictEqual(args.length, 1); 129 | assert.strictEqual(typeof args[0], 'object'); 130 | assert.strictEqual(args[0].name, 'test'); 131 | }); 132 | 133 | it('supports overwriteArguments with multiple hooks', async function() { 134 | hooks.pre('init', function(obj) { 135 | if (typeof obj === 'string') { 136 | return Kareem.overwriteArguments({ name: obj }); 137 | } 138 | }); 139 | 140 | hooks.pre('init', function(obj) { 141 | if (obj && typeof obj === 'object' && !obj.modified) { 142 | return Kareem.overwriteArguments({ ...obj, modified: true }); 143 | } 144 | }); 145 | 146 | const args = await hooks.execPre('init', null, ['test']); 147 | assert.strictEqual(args.length, 1); 148 | assert.deepStrictEqual(args[0], { name: 'test', modified: true }); 149 | }); 150 | 151 | it('supports overwriteArguments with async functions', async function() { 152 | hooks.pre('init', async function(obj) { 153 | if (typeof obj === 'string') { 154 | return Kareem.overwriteArguments({ name: obj }); 155 | } 156 | }); 157 | 158 | const args = await hooks.execPre('init', null, ['async-test']); 159 | assert.strictEqual(args.length, 1); 160 | assert.strictEqual(typeof args[0], 'object'); 161 | assert.strictEqual(args[0].name, 'async-test'); 162 | }); 163 | }); 164 | 165 | describe('execPreSync', function() { 166 | let hooks; 167 | 168 | beforeEach(function() { 169 | hooks = new Kareem(); 170 | }); 171 | 172 | it('executes hooks synchronously', function() { 173 | const execed = {}; 174 | 175 | hooks.pre('cook', function() { 176 | execed.first = true; 177 | }); 178 | 179 | hooks.pre('cook', function() { 180 | execed.second = true; 181 | }); 182 | 183 | hooks.execPreSync('cook', null); 184 | assert.ok(execed.first); 185 | assert.ok(execed.second); 186 | }); 187 | 188 | it('works with no hooks specified', function() { 189 | assert.doesNotThrow(function() { 190 | hooks.execPreSync('cook', null); 191 | }); 192 | }); 193 | 194 | it('supports overwriteArguments', function() { 195 | hooks.pre('init', function(obj) { 196 | if (typeof obj === 'string') { 197 | return Kareem.overwriteArguments({ name: obj }); 198 | } 199 | }); 200 | 201 | const args = hooks.execPreSync('init', null, ['test']); 202 | assert.strictEqual(args.length, 1); 203 | assert.strictEqual(typeof args[0], 'object'); 204 | assert.strictEqual(args[0].name, 'test'); 205 | }); 206 | 207 | it('supports overwriteArguments with multiple hooks', function() { 208 | hooks.pre('init', function(obj) { 209 | if (typeof obj === 'string') { 210 | return Kareem.overwriteArguments({ name: obj }); 211 | } 212 | }); 213 | 214 | hooks.pre('init', function(obj) { 215 | if (obj && typeof obj === 'object' && !obj.modified) { 216 | return Kareem.overwriteArguments({ ...obj, modified: true }); 217 | } 218 | }); 219 | 220 | const args = hooks.execPreSync('init', null, ['test']); 221 | assert.strictEqual(args.length, 1); 222 | assert.deepStrictEqual(args[0], { name: 'test', modified: true }); 223 | }); 224 | }); 225 | -------------------------------------------------------------------------------- /test/post.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const Kareem = require('../'); 5 | const { beforeEach, describe, it } = require('mocha'); 6 | 7 | describe('execPost', function() { 8 | let hooks; 9 | 10 | beforeEach(function() { 11 | hooks = new Kareem(); 12 | }); 13 | 14 | it('handles errors', async function() { 15 | hooks.post('cook', function(eggs, callback) { 16 | callback('error!'); 17 | }); 18 | 19 | await assert.rejects( 20 | () => hooks.execPost('cook', null, [4]), 21 | error => error === 'error!' 22 | ); 23 | }); 24 | 25 | it('unshift', function() { 26 | const f1 = function() {}; 27 | const f2 = function() {}; 28 | hooks.post('cook', f1); 29 | hooks.post('cook', f2, true); 30 | assert.strictEqual(hooks._posts.get('cook')[0].fn, f2); 31 | assert.strictEqual(hooks._posts.get('cook')[1].fn, f1); 32 | }); 33 | 34 | it('arbitrary options', function() { 35 | const f1 = function() {}; 36 | const f2 = function() {}; 37 | hooks.post('cook', { foo: 'bar' }, f1); 38 | hooks.post('cook', { bar: 'baz' }, f2, true); 39 | assert.equal(hooks._posts.get('cook')[1].foo, 'bar'); 40 | assert.equal(hooks._posts.get('cook')[0].bar, 'baz'); 41 | }); 42 | 43 | it('throws error if no function', function() { 44 | assert.throws(() => hooks.post('test'), /got "undefined"/); 45 | }); 46 | 47 | it('multiple posts', async function() { 48 | hooks.post('cook', function(eggs, callback) { 49 | setTimeout( 50 | function() { 51 | callback(); 52 | }, 53 | 5); 54 | }); 55 | 56 | hooks.post('cook', function(eggs, callback) { 57 | setTimeout( 58 | function() { 59 | callback(); 60 | }, 61 | 5); 62 | }); 63 | 64 | const eggs = await hooks.execPost('cook', null, [4]); 65 | assert.equal(eggs, 4); 66 | }); 67 | 68 | it('error posts', async function() { 69 | const called = {}; 70 | hooks.post('cook', function(eggs, callback) { 71 | called.first = true; 72 | callback(); 73 | }); 74 | 75 | hooks.post('cook', function(eggs, callback) { 76 | called.second = true; 77 | callback(new Error('fail')); 78 | }); 79 | 80 | hooks.post('cook', function() { 81 | assert.ok(false); 82 | }); 83 | 84 | hooks.post('cook', function(error, eggs, callback) { 85 | called.fourth = true; 86 | assert.equal(error.message, 'fail'); 87 | callback(new Error('fourth')); 88 | }); 89 | 90 | hooks.post('cook', function(error, eggs, callback) { 91 | called.fifth = true; 92 | assert.equal(error.message, 'fourth'); 93 | callback(new Error('fifth')); 94 | }); 95 | 96 | const err = await hooks.execPost('cook', null, [4]).then(() => null, err => err); 97 | assert.ok(err); 98 | assert.equal(err.message, 'fifth'); 99 | assert.deepEqual(called, { 100 | first: true, 101 | second: true, 102 | fourth: true, 103 | fifth: true 104 | }); 105 | }); 106 | 107 | it('error posts with errorHandler option', async function() { 108 | const called = {}; 109 | hooks.post('cook', function(eggs, callback) { 110 | called.first = true; 111 | callback(); 112 | }); 113 | 114 | hooks.post('cook', function(eggs, callback) { 115 | called.second = true; 116 | callback(new Error('fail')); 117 | }); 118 | 119 | hooks.post('cook', function() { 120 | assert.ok(false); 121 | }); 122 | 123 | hooks.post('cook', { errorHandler: true }, function() { 124 | called.fourth = true; 125 | return Promise.resolve(); 126 | }); 127 | 128 | const err = await hooks.execPost('cook', null, [4]).then(() => null, err => err); 129 | assert.ok(err); 130 | assert.deepEqual(called, { 131 | first: true, 132 | second: true, 133 | fourth: true 134 | }); 135 | }); 136 | 137 | it('error posts with initial error', async function() { 138 | const called = {}; 139 | 140 | hooks.post('cook', function() { 141 | assert.ok(false); 142 | }); 143 | 144 | hooks.post('cook', function(error, eggs, callback) { 145 | called.second = true; 146 | assert.equal(error.message, 'fail'); 147 | callback(new Error('second')); 148 | }); 149 | 150 | hooks.post('cook', function(error, eggs, callback) { 151 | called.third = true; 152 | assert.equal(error.message, 'second'); 153 | callback(new Error('third')); 154 | }); 155 | 156 | hooks.post('cook', function(error, eggs, callback) { 157 | called.fourth = true; 158 | assert.equal(error.message, 'third'); 159 | callback(); 160 | }); 161 | 162 | const options = { error: new Error('fail') }; 163 | const err = await hooks.execPost('cook', null, [4], options).then(() => null, err => err); 164 | assert.ok(err); 165 | assert.equal(err.message, 'third'); 166 | assert.deepEqual(called, { 167 | second: true, 168 | third: true, 169 | fourth: true 170 | }); 171 | }); 172 | 173 | it('supports returning a promise', async function() { 174 | let calledPost = 0; 175 | 176 | hooks.post('cook', function() { 177 | return new Promise(resolve => { 178 | setTimeout(() => { 179 | ++calledPost; 180 | resolve(); 181 | }, 100); 182 | }); 183 | }); 184 | 185 | await hooks.execPost('cook', null, [], {}); 186 | assert.equal(calledPost, 1); 187 | }); 188 | 189 | it('supports overwriteResult', async function() { 190 | hooks.post('cook', function(eggs, callback) { 191 | callback(Kareem.overwriteResult(5)); 192 | }); 193 | 194 | hooks.post('cook', function(eggs, callback) { 195 | assert.equal(eggs, 5); 196 | callback(); 197 | }); 198 | 199 | const options = {}; 200 | const eggs = await hooks.execPost('cook', null, [4], options); 201 | assert.equal(eggs, 5); 202 | }); 203 | 204 | it('supports sync returning overwriteResult', async function() { 205 | hooks.post('cook', function() { 206 | return Kareem.overwriteResult(5); 207 | }); 208 | 209 | hooks.post('cook', function(eggs, callback) { 210 | assert.equal(eggs, 5); 211 | callback(); 212 | }); 213 | 214 | const options = {}; 215 | const eggs = await hooks.execPost('cook', null, [4], options); 216 | assert.equal(eggs, 5); 217 | }); 218 | 219 | it('supports sync overwriteResult', function() { 220 | hooks.post('cook', function() { 221 | return Kareem.overwriteResult(5); 222 | }); 223 | 224 | hooks.post('cook', function(eggs) { 225 | assert.equal(eggs, 5); 226 | }); 227 | 228 | const options = {}; 229 | const res = hooks.execPostSync('cook', null, [4], options); 230 | assert.deepEqual(res, [5]); 231 | }); 232 | 233 | it('supports overwriteResult with promises', async function() { 234 | hooks.post('cook', function() { 235 | return Promise.resolve(Kareem.overwriteResult(5)); 236 | }); 237 | 238 | hooks.post('cook', function(eggs) { 239 | assert.equal(eggs, 5); 240 | }); 241 | 242 | const options = {}; 243 | const eggs = await hooks.execPost('cook', null, [4], options); 244 | assert.equal(eggs, 5); 245 | }); 246 | }); 247 | 248 | describe('execPostSync', function() { 249 | let hooks; 250 | 251 | beforeEach(function() { 252 | hooks = new Kareem(); 253 | }); 254 | 255 | it('executes hooks synchronously', function() { 256 | const execed = {}; 257 | 258 | hooks.post('cook', function() { 259 | execed.first = true; 260 | }); 261 | 262 | hooks.post('cook', function() { 263 | execed.second = true; 264 | }); 265 | 266 | hooks.execPostSync('cook', null); 267 | assert.ok(execed.first); 268 | assert.ok(execed.second); 269 | }); 270 | 271 | it('works with no hooks specified', function() { 272 | assert.doesNotThrow(function() { 273 | hooks.execPostSync('cook', null); 274 | }); 275 | }); 276 | }); 277 | -------------------------------------------------------------------------------- /test/examples.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const { beforeEach, describe, it } = require('mocha'); 5 | const Kareem = require('../'); 6 | 7 | // NOTE: this file has some empty comment lines to workaround https://github.com/vkarpov15/acquit/issues/30 8 | 9 | /* Much like [hooks](https://npmjs.org/package/hooks), kareem lets you define 10 | * pre and post hooks: pre hooks are called before a given function executes. 11 | * Unlike hooks, kareem stores hooks and other internal state in a separate 12 | * object, rather than relying on inheritance. Furthermore, kareem exposes 13 | * an `execPre()` function that allows you to execute your pre hooks when 14 | * appropriate, giving you more fine-grained control over your function hooks. 15 | */ 16 | describe('pre hooks', function() { 17 | let hooks; 18 | 19 | beforeEach(function() { 20 | hooks = new Kareem(); 21 | }); 22 | 23 | it('runs without any hooks specified', async function() { 24 | await hooks.execPre('cook', null); 25 | }); 26 | 27 | /* pre hook functions can return a promise that resolves when finished. 28 | */ 29 | it('runs basic serial pre hooks', async function() { 30 | let count = 0; 31 | 32 | hooks.pre('cook', function() { 33 | ++count; 34 | return Promise.resolve(); 35 | }); 36 | 37 | await hooks.execPre('cook', null); 38 | assert.equal(1, count); 39 | }); 40 | 41 | it('can run multiple pre hooks', async function() { 42 | let count1 = 0; 43 | let count2 = 0; 44 | 45 | hooks.pre('cook', function() { 46 | ++count1; 47 | return Promise.resolve(); 48 | }); 49 | 50 | hooks.pre('cook', function() { 51 | ++count2; 52 | return Promise.resolve(); 53 | }); 54 | 55 | await hooks.execPre('cook', null); 56 | assert.equal(1, count1); 57 | assert.equal(1, count2); 58 | }); 59 | 60 | /* If your pre hook function takes no parameters, its assumed to be 61 | * fully synchronous. 62 | */ 63 | it('can run fully synchronous pre hooks', async function() { 64 | let count1 = 0; 65 | let count2 = 0; 66 | 67 | hooks.pre('cook', function() { 68 | ++count1; 69 | }); 70 | 71 | hooks.pre('cook', function() { 72 | ++count2; 73 | }); 74 | 75 | await hooks.execPre('cook', null); 76 | assert.equal(1, count1); 77 | assert.equal(1, count2); 78 | }); 79 | 80 | /* Pre save hook functions are bound to the second parameter to `execPre()` 81 | */ 82 | it('properly attaches context to pre hooks', async function() { 83 | hooks.pre('cook', function() { 84 | this.bacon = 3; 85 | }); 86 | 87 | hooks.pre('cook', function() { 88 | this.eggs = 4; 89 | }); 90 | 91 | const obj = { bacon: 0, eggs: 0 }; 92 | 93 | // In the pre hooks, `this` will refer to `obj` 94 | await hooks.execPre('cook', obj); 95 | assert.equal(3, obj.bacon); 96 | assert.equal(4, obj.eggs); 97 | }); 98 | 99 | /* You can also return a promise from your pre hooks instead of calling 100 | * `next()`. When the returned promise resolves, kareem will kick off the 101 | * next middleware. 102 | */ 103 | it('supports returning a promise', async function() { 104 | hooks.pre('cook', function() { 105 | return new Promise(resolve => { 106 | setTimeout(() => { 107 | this.bacon = 3; 108 | resolve(); 109 | }, 100); 110 | }); 111 | }); 112 | 113 | const obj = { bacon: 0 }; 114 | 115 | await hooks.execPre('cook', obj); 116 | assert.equal(3, obj.bacon); 117 | }); 118 | }); 119 | 120 | // 121 | describe('post hooks', function() { 122 | let hooks; 123 | 124 | beforeEach(function() { 125 | hooks = new Kareem(); 126 | }); 127 | 128 | it('runs without any hooks specified', async function() { 129 | const [eggs] = await hooks.execPost('cook', null, [1]); 130 | assert.equal(eggs, 1); 131 | }); 132 | 133 | it('executes with parameters passed in', async function() { 134 | hooks.post('cook', function(eggs, bacon, callback) { 135 | assert.equal(eggs, 1); 136 | assert.equal(bacon, 2); 137 | callback(); 138 | }); 139 | 140 | const [eggs, bacon] = await hooks.execPost('cook', null, [1, 2]); 141 | assert.equal(eggs, 1); 142 | assert.equal(bacon, 2); 143 | }); 144 | 145 | it('can use synchronous post hooks', async function() { 146 | const execed = {}; 147 | 148 | hooks.post('cook', function(eggs, bacon) { 149 | execed.first = true; 150 | assert.equal(eggs, 1); 151 | assert.equal(bacon, 2); 152 | }); 153 | 154 | hooks.post('cook', function(eggs, bacon, callback) { 155 | execed.second = true; 156 | assert.equal(eggs, 1); 157 | assert.equal(bacon, 2); 158 | callback(); 159 | }); 160 | 161 | const [eggs, bacon] = await hooks.execPost('cook', null, [1, 2]); 162 | assert.equal(Object.keys(execed).length, 2); 163 | assert.ok(execed.first); 164 | assert.ok(execed.second); 165 | assert.equal(eggs, 1); 166 | assert.equal(bacon, 2); 167 | }); 168 | 169 | /* You can also return a promise from your post hooks instead of calling 170 | * `next()`. When the returned promise resolves, kareem will kick off the 171 | * next middleware. 172 | */ 173 | it('supports returning a promise', async function() { 174 | hooks.post('cook', function() { 175 | return new Promise(resolve => { 176 | setTimeout(() => { 177 | this.bacon = 3; 178 | resolve(); 179 | }, 100); 180 | }); 181 | }); 182 | 183 | const obj = { bacon: 0 }; 184 | 185 | await hooks.execPost('cook', obj, [obj]); 186 | assert.equal(obj.bacon, 3); 187 | }); 188 | }); 189 | 190 | // 191 | describe('wrap()', function() { 192 | let hooks; 193 | 194 | beforeEach(function() { 195 | hooks = new Kareem(); 196 | }); 197 | 198 | it('wraps pre and post calls into one call', async function() { 199 | hooks.pre('cook', function() { 200 | return new Promise(resolve => { 201 | this.bacon = 3; 202 | setTimeout(() => { 203 | resolve(); 204 | }, 5); 205 | }); 206 | }); 207 | 208 | hooks.pre('cook', function() { 209 | this.eggs = 4; 210 | return Promise.resolve(); 211 | }); 212 | 213 | hooks.pre('cook', function() { 214 | this.waffles = false; 215 | return Promise.resolve(); 216 | }); 217 | 218 | hooks.post('cook', function(obj) { 219 | obj.tofu = 'no'; 220 | }); 221 | 222 | const obj = { bacon: 0, eggs: 0 }; 223 | 224 | const args = [obj]; 225 | 226 | const result = await hooks.wrap( 227 | 'cook', 228 | function(o) { 229 | assert.equal(obj.bacon, 3); 230 | assert.equal(obj.eggs, 4); 231 | assert.equal(obj.waffles, false); 232 | assert.equal(obj.tofu, undefined); 233 | return o; 234 | }, 235 | obj, 236 | args); 237 | 238 | assert.equal(obj.bacon, 3); 239 | assert.equal(obj.eggs, 4); 240 | assert.equal(obj.waffles, false); 241 | assert.equal(obj.tofu, 'no'); 242 | assert.equal(result, obj); 243 | }); 244 | }); 245 | 246 | // 247 | describe('createWrapper()', function() { 248 | let hooks; 249 | 250 | beforeEach(function() { 251 | hooks = new Kareem(); 252 | }); 253 | 254 | it('wraps wrap() into a callable function', async function() { 255 | hooks.pre('cook', function() { 256 | this.bacon = 3; 257 | return Promise.resolve(); 258 | }); 259 | 260 | hooks.pre('cook', function() { 261 | return new Promise(resolve => { 262 | this.eggs = 4; 263 | setTimeout(function() { 264 | resolve(); 265 | }, 10); 266 | }); 267 | }); 268 | 269 | hooks.pre('cook', function() { 270 | this.waffles = false; 271 | return Promise.resolve(); 272 | }); 273 | 274 | hooks.post('cook', function(obj) { 275 | obj.tofu = 'no'; 276 | }); 277 | 278 | const obj = { bacon: 0, eggs: 0 }; 279 | 280 | const cook = hooks.createWrapper( 281 | 'cook', 282 | function(o) { 283 | assert.equal(3, obj.bacon); 284 | assert.equal(4, obj.eggs); 285 | assert.equal(false, obj.waffles); 286 | assert.equal(undefined, obj.tofu); 287 | return o; 288 | }, 289 | obj); 290 | 291 | const result = await cook(obj); 292 | assert.equal(obj.bacon, 3); 293 | assert.equal(obj.eggs, 4); 294 | assert.equal(obj.waffles, false); 295 | assert.equal(obj.tofu, 'no'); 296 | 297 | assert.equal(result, obj); 298 | }); 299 | }); 300 | 301 | // 302 | describe('clone()', function() { 303 | it('clones a Kareem object', function() { 304 | const k1 = new Kareem(); 305 | k1.pre('cook', function() {}); 306 | k1.post('cook', function() {}); 307 | 308 | const k2 = k1.clone(); 309 | assert.deepEqual(Array.from(k2._pres.keys()), ['cook']); 310 | assert.deepEqual(Array.from(k2._posts.keys()), ['cook']); 311 | }); 312 | }); 313 | 314 | // 315 | describe('merge()', function() { 316 | it('pulls hooks from another Kareem object', function() { 317 | const k1 = new Kareem(); 318 | const test1 = function() {}; 319 | k1.pre('cook', test1); 320 | k1.post('cook', function() {}); 321 | 322 | const k2 = new Kareem(); 323 | const test2 = function() {}; 324 | k2.pre('cook', test2); 325 | const k3 = k2.merge(k1); 326 | assert.equal(k3._pres.get('cook').length, 2); 327 | assert.equal(k3._pres.get('cook')[0].fn, test2); 328 | assert.equal(k3._pres.get('cook')[1].fn, test1); 329 | assert.equal(k3._posts.get('cook').length, 1); 330 | }); 331 | }); 332 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kareem 2 | 3 | [![Build Status](https://github.com/mongoosejs/kareem/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/mongoosejs/kareem/actions/workflows/test.yml) 4 | 5 | 6 | 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. 7 | 8 | Named for the NBA's 2nd all-time leading scorer Kareem Abdul-Jabbar, known for his mastery of the [hook shot](http://en.wikipedia.org/wiki/Kareem_Abdul-Jabbar#Skyhook) 9 | 10 | 11 | 12 | 13 | 14 | # API 15 | 16 | ## pre hooks 17 | 18 | Much like [hooks](https://npmjs.org/package/hooks), kareem lets you define 19 | pre and post hooks: pre hooks are called before a given function executes. 20 | Unlike hooks, kareem stores hooks and other internal state in a separate 21 | object, rather than relying on inheritance. Furthermore, kareem exposes 22 | an `execPre()` function that allows you to execute your pre hooks when 23 | appropriate, giving you more fine-grained control over your function hooks. 24 | 25 | ### It runs without any hooks specified 26 | 27 | ```javascript 28 | hooks.execPre('cook', null, function() { 29 | // ... 30 | }); 31 | ``` 32 | 33 | ### It runs basic serial pre hooks 34 | 35 | pre hook functions take one parameter, a "done" function that you execute 36 | when your pre hook is finished. 37 | 38 | ```javascript 39 | let count = 0; 40 | 41 | hooks.pre('cook', function(done) { 42 | ++count; 43 | done(); 44 | }); 45 | 46 | hooks.execPre('cook', null, function() { 47 | assert.equal(1, count); 48 | }); 49 | ``` 50 | 51 | ### It can run multiple pre hooks 52 | 53 | ```javascript 54 | let count1 = 0; 55 | let count2 = 0; 56 | 57 | hooks.pre('cook', function(done) { 58 | ++count1; 59 | done(); 60 | }); 61 | 62 | hooks.pre('cook', function(done) { 63 | ++count2; 64 | done(); 65 | }); 66 | 67 | hooks.execPre('cook', null, function() { 68 | assert.equal(1, count1); 69 | assert.equal(1, count2); 70 | }); 71 | ``` 72 | 73 | ### It can run fully synchronous pre hooks 74 | 75 | If your pre hook function takes no parameters, its assumed to be 76 | fully synchronous. 77 | 78 | ```javascript 79 | let count1 = 0; 80 | let count2 = 0; 81 | 82 | hooks.pre('cook', function() { 83 | ++count1; 84 | }); 85 | 86 | hooks.pre('cook', function() { 87 | ++count2; 88 | }); 89 | 90 | hooks.execPre('cook', null, function(error) { 91 | assert.equal(null, error); 92 | assert.equal(1, count1); 93 | assert.equal(1, count2); 94 | }); 95 | ``` 96 | 97 | ### It properly attaches context to pre hooks 98 | 99 | Pre save hook functions are bound to the second parameter to `execPre()` 100 | 101 | ```javascript 102 | hooks.pre('cook', function(done) { 103 | this.bacon = 3; 104 | done(); 105 | }); 106 | 107 | hooks.pre('cook', function(done) { 108 | this.eggs = 4; 109 | done(); 110 | }); 111 | 112 | const obj = { bacon: 0, eggs: 0 }; 113 | 114 | // In the pre hooks, `this` will refer to `obj` 115 | hooks.execPre('cook', obj, function(error) { 116 | assert.equal(null, error); 117 | assert.equal(3, obj.bacon); 118 | assert.equal(4, obj.eggs); 119 | }); 120 | ``` 121 | 122 | ### It can execute parallel (async) pre hooks 123 | 124 | Like the hooks module, you can declare "async" pre hooks - these take two 125 | parameters, the functions `next()` and `done()`. `next()` passes control to 126 | the next pre hook, but the underlying function won't be called until all 127 | async pre hooks have called `done()`. 128 | 129 | ```javascript 130 | hooks.pre('cook', true, function(next, done) { 131 | this.bacon = 3; 132 | next(); 133 | setTimeout(function() { 134 | done(); 135 | }, 5); 136 | }); 137 | 138 | hooks.pre('cook', true, function(next, done) { 139 | next(); 140 | const _this = this; 141 | setTimeout(function() { 142 | _this.eggs = 4; 143 | done(); 144 | }, 10); 145 | }); 146 | 147 | hooks.pre('cook', function(next) { 148 | this.waffles = false; 149 | next(); 150 | }); 151 | 152 | const obj = { bacon: 0, eggs: 0 }; 153 | 154 | hooks.execPre('cook', obj, function() { 155 | assert.equal(3, obj.bacon); 156 | assert.equal(4, obj.eggs); 157 | assert.equal(false, obj.waffles); 158 | }); 159 | ``` 160 | 161 | ### It supports returning a promise 162 | 163 | You can also return a promise from your pre hooks instead of calling 164 | `next()`. When the returned promise resolves, kareem will kick off the 165 | next middleware. 166 | 167 | ```javascript 168 | hooks.pre('cook', function() { 169 | return new Promise(resolve => { 170 | setTimeout(() => { 171 | this.bacon = 3; 172 | resolve(); 173 | }, 100); 174 | }); 175 | }); 176 | 177 | const obj = { bacon: 0 }; 178 | 179 | hooks.execPre('cook', obj, function() { 180 | assert.equal(3, obj.bacon); 181 | }); 182 | ``` 183 | 184 | ## post hooks 185 | 186 | ### It runs without any hooks specified 187 | 188 | ```javascript 189 | hooks.execPost('cook', null, [1], function(error, eggs) { 190 | assert.ifError(error); 191 | assert.equal(1, eggs); 192 | done(); 193 | }); 194 | ``` 195 | 196 | ### It executes with parameters passed in 197 | 198 | ```javascript 199 | hooks.post('cook', function(eggs, bacon, callback) { 200 | assert.equal(1, eggs); 201 | assert.equal(2, bacon); 202 | callback(); 203 | }); 204 | 205 | hooks.execPost('cook', null, [1, 2], function(error, eggs, bacon) { 206 | assert.ifError(error); 207 | assert.equal(1, eggs); 208 | assert.equal(2, bacon); 209 | }); 210 | ``` 211 | 212 | ### It can use synchronous post hooks 213 | 214 | ```javascript 215 | const execed = {}; 216 | 217 | hooks.post('cook', function(eggs, bacon) { 218 | execed.first = true; 219 | assert.equal(1, eggs); 220 | assert.equal(2, bacon); 221 | }); 222 | 223 | hooks.post('cook', function(eggs, bacon, callback) { 224 | execed.second = true; 225 | assert.equal(1, eggs); 226 | assert.equal(2, bacon); 227 | callback(); 228 | }); 229 | 230 | hooks.execPost('cook', null, [1, 2], function(error, eggs, bacon) { 231 | assert.ifError(error); 232 | assert.equal(2, Object.keys(execed).length); 233 | assert.ok(execed.first); 234 | assert.ok(execed.second); 235 | assert.equal(1, eggs); 236 | assert.equal(2, bacon); 237 | }); 238 | ``` 239 | 240 | ### It supports returning a promise 241 | 242 | You can also return a promise from your post hooks instead of calling 243 | `next()`. When the returned promise resolves, kareem will kick off the 244 | next middleware. 245 | 246 | ```javascript 247 | hooks.post('cook', function() { 248 | return new Promise(resolve => { 249 | setTimeout(() => { 250 | this.bacon = 3; 251 | resolve(); 252 | }, 100); 253 | }); 254 | }); 255 | 256 | const obj = { bacon: 0 }; 257 | 258 | hooks.execPost('cook', obj, obj, function() { 259 | assert.equal(obj.bacon, 3); 260 | }); 261 | ``` 262 | 263 | ## wrap() 264 | 265 | ### It wraps pre and post calls into one call 266 | 267 | ```javascript 268 | hooks.pre('cook', true, function(next, done) { 269 | this.bacon = 3; 270 | next(); 271 | setTimeout(function() { 272 | done(); 273 | }, 5); 274 | }); 275 | 276 | hooks.pre('cook', true, function(next, done) { 277 | next(); 278 | const _this = this; 279 | setTimeout(function() { 280 | _this.eggs = 4; 281 | done(); 282 | }, 10); 283 | }); 284 | 285 | hooks.pre('cook', function(next) { 286 | this.waffles = false; 287 | next(); 288 | }); 289 | 290 | hooks.post('cook', function(obj) { 291 | obj.tofu = 'no'; 292 | }); 293 | 294 | const obj = { bacon: 0, eggs: 0 }; 295 | 296 | const args = [obj]; 297 | args.push(function(error, result) { 298 | assert.ifError(error); 299 | assert.equal(null, error); 300 | assert.equal(3, obj.bacon); 301 | assert.equal(4, obj.eggs); 302 | assert.equal(false, obj.waffles); 303 | assert.equal('no', obj.tofu); 304 | 305 | assert.equal(obj, result); 306 | }); 307 | 308 | hooks.wrap( 309 | 'cook', 310 | function(o, callback) { 311 | assert.equal(3, obj.bacon); 312 | assert.equal(4, obj.eggs); 313 | assert.equal(false, obj.waffles); 314 | assert.equal(undefined, obj.tofu); 315 | callback(null, o); 316 | }, 317 | obj, 318 | args); 319 | ``` 320 | 321 | ## createWrapper() 322 | 323 | ### It wraps wrap() into a callable function 324 | 325 | ```javascript 326 | hooks.pre('cook', true, function(next, done) { 327 | this.bacon = 3; 328 | next(); 329 | setTimeout(function() { 330 | done(); 331 | }, 5); 332 | }); 333 | 334 | hooks.pre('cook', true, function(next, done) { 335 | next(); 336 | const _this = this; 337 | setTimeout(function() { 338 | _this.eggs = 4; 339 | done(); 340 | }, 10); 341 | }); 342 | 343 | hooks.pre('cook', function(next) { 344 | this.waffles = false; 345 | next(); 346 | }); 347 | 348 | hooks.post('cook', function(obj) { 349 | obj.tofu = 'no'; 350 | }); 351 | 352 | const obj = { bacon: 0, eggs: 0 }; 353 | 354 | const cook = hooks.createWrapper( 355 | 'cook', 356 | function(o, callback) { 357 | assert.equal(3, obj.bacon); 358 | assert.equal(4, obj.eggs); 359 | assert.equal(false, obj.waffles); 360 | assert.equal(undefined, obj.tofu); 361 | callback(null, o); 362 | }, 363 | obj); 364 | 365 | cook(obj, function(error, result) { 366 | assert.ifError(error); 367 | assert.equal(3, obj.bacon); 368 | assert.equal(4, obj.eggs); 369 | assert.equal(false, obj.waffles); 370 | assert.equal('no', obj.tofu); 371 | 372 | assert.equal(obj, result); 373 | }); 374 | ``` 375 | 376 | ## clone() 377 | 378 | ### It clones a Kareem object 379 | 380 | ```javascript 381 | const k1 = new Kareem(); 382 | k1.pre('cook', function() {}); 383 | k1.post('cook', function() {}); 384 | 385 | const k2 = k1.clone(); 386 | assert.deepEqual(Array.from(k2._pres.keys()), ['cook']); 387 | assert.deepEqual(Array.from(k2._posts.keys()), ['cook']); 388 | ``` 389 | 390 | ## merge() 391 | 392 | ### It pulls hooks from another Kareem object 393 | 394 | ```javascript 395 | const k1 = new Kareem(); 396 | const test1 = function() {}; 397 | k1.pre('cook', test1); 398 | k1.post('cook', function() {}); 399 | 400 | const k2 = new Kareem(); 401 | const test2 = function() {}; 402 | k2.pre('cook', test2); 403 | const k3 = k2.merge(k1); 404 | assert.equal(k3._pres.get('cook').length, 2); 405 | assert.equal(k3._pres.get('cook')[0].fn, test2); 406 | assert.equal(k3._pres.get('cook')[1].fn, test1); 407 | assert.equal(k3._posts.get('cook').length, 1); 408 | ``` 409 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2014-2022 mongoosejs 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /test/wrap.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const Kareem = require('../'); 5 | const { beforeEach, describe, it } = require('mocha'); 6 | 7 | describe('wrap()', function() { 8 | let hooks; 9 | 10 | beforeEach(function() { 11 | hooks = new Kareem(); 12 | }); 13 | 14 | it('handles pre errors', async function() { 15 | hooks.pre('cook', function() { 16 | throw new Error('error!'); 17 | }); 18 | 19 | hooks.post('cook', function(obj) { 20 | obj.tofu = 'no'; 21 | }); 22 | 23 | const obj = { bacon: 0, eggs: 0 }; 24 | 25 | await assert.rejects(async() => { 26 | await hooks.wrap( 27 | 'cook', 28 | function(o) { 29 | // Should never get called 30 | assert.ok(false); 31 | return o; 32 | }, 33 | obj, 34 | [obj]); 35 | }, err => { 36 | assert.equal(err.message, 'error!'); 37 | assert.equal(obj.tofu, undefined); 38 | return true; 39 | }); 40 | }); 41 | 42 | it('handles pre errors when no callback defined', async function() { 43 | hooks.pre('cook', function() { 44 | throw new Error('error!'); 45 | }); 46 | 47 | hooks.post('cook', function(obj) { 48 | obj.tofu = 'no'; 49 | }); 50 | 51 | const obj = { bacon: 0, eggs: 0 }; 52 | 53 | const args = [obj]; 54 | 55 | await assert.rejects(async() => { 56 | await hooks.wrap( 57 | 'cook', 58 | function(o) { 59 | // Should never get called 60 | assert.ok(false); 61 | return o; 62 | }, 63 | obj, 64 | args); 65 | }, err => { 66 | assert.equal(err.message, 'error!'); 67 | assert.equal(obj.tofu, undefined); 68 | return true; 69 | }); 70 | }); 71 | 72 | it('handles errors in wrapped function', async function() { 73 | hooks.pre('cook', function() {}); 74 | 75 | hooks.post('cook', function(obj) { 76 | obj.tofu = 'no'; 77 | }); 78 | 79 | const obj = { bacon: 0, eggs: 0 }; 80 | 81 | const args = [obj]; 82 | 83 | await assert.rejects(async() => { 84 | await hooks.wrap( 85 | 'cook', 86 | function() { 87 | throw new Error('error!'); 88 | }, 89 | obj, 90 | args); 91 | }, err => { 92 | assert.equal(err.message, 'error!'); 93 | assert.equal(obj.tofu, undefined); 94 | return true; 95 | }); 96 | }); 97 | 98 | it('handles errors in post', async function() { 99 | hooks.pre('cook', function() {}); 100 | 101 | hooks.post('cook', function(obj, callback) { 102 | obj.tofu = 'no'; 103 | callback(new Error('error!')); 104 | }); 105 | 106 | const obj = { bacon: 0, eggs: 0 }; 107 | 108 | const args = [obj]; 109 | 110 | await assert.rejects(async() => { 111 | await hooks.wrap( 112 | 'cook', 113 | function(o) { 114 | return o; 115 | }, 116 | obj, 117 | args); 118 | }, err => { 119 | assert.equal(err.message, 'error!'); 120 | assert.equal(obj.tofu, 'no'); 121 | return true; 122 | }); 123 | }); 124 | 125 | it('defers errors to post hooks if enabled', async function() { 126 | hooks.pre('cook', function() { 127 | throw new Error('fail'); 128 | }); 129 | 130 | hooks.post('cook', function(error, res, callback) { 131 | callback(new Error('another error occurred')); 132 | }); 133 | 134 | await assert.rejects(async() => { 135 | await hooks.wrap( 136 | 'cook', 137 | function() { 138 | assert.ok(false); 139 | }, 140 | null, 141 | [], 142 | { numCallbackParams: 1 }); 143 | }, err => { 144 | assert.equal(err.message, 'another error occurred'); 145 | return true; 146 | }); 147 | }); 148 | 149 | it('error handlers with no callback', async function() { 150 | hooks.pre('cook', function() { 151 | throw new Error('fail'); 152 | }); 153 | 154 | hooks.postError('cook', function(error) { 155 | assert.equal(error.message, 'fail'); 156 | }); 157 | 158 | const args = []; 159 | 160 | await assert.rejects(async() => { 161 | await hooks.wrap( 162 | 'cook', 163 | function() { 164 | assert.ok(false); 165 | }, 166 | null, 167 | args); 168 | }, /fail/); 169 | }); 170 | 171 | it('error handlers do not execute with no error', async function() { 172 | hooks.post('cook', function(error, res, callback) { 173 | callback(new Error('another error occurred')); 174 | }); 175 | 176 | await hooks.wrap( 177 | 'cook', 178 | async function() { 179 | return; 180 | }, 181 | null, 182 | [] 183 | ); 184 | }); 185 | 186 | it('works with no args', async function() { 187 | hooks.pre('cook', function() {}); 188 | 189 | hooks.post('cook', function(res, callback) { 190 | obj.tofu = 'no'; 191 | callback(); 192 | }); 193 | 194 | const obj = { bacon: 0, eggs: 0 }; 195 | 196 | const args = []; 197 | 198 | await hooks.wrap( 199 | 'cook', 200 | async function() { 201 | return null; 202 | }, 203 | obj, 204 | args); 205 | 206 | assert.equal(obj.tofu, 'no'); 207 | }); 208 | 209 | it('handles pre errors with no args', async function() { 210 | hooks.pre('cook', function() { 211 | throw new Error('error!'); 212 | }); 213 | 214 | hooks.post('cook', function(callback) { 215 | obj.tofu = 'no'; 216 | callback(); 217 | }); 218 | 219 | const obj = { bacon: 0, eggs: 0 }; 220 | 221 | const args = []; 222 | 223 | await assert.rejects(async() => { 224 | await hooks.wrap( 225 | 'cook', 226 | function() { 227 | return null; 228 | }, 229 | obj, 230 | args); 231 | }, err => { 232 | assert.equal(err.message, 'error!'); 233 | assert.equal(obj.tofu, undefined); 234 | return true; 235 | }); 236 | }); 237 | 238 | it('handles wrapped function errors with no args', async function() { 239 | hooks.pre('cook', function() { 240 | obj.waffles = false; 241 | }); 242 | 243 | hooks.post('cook', function(callback) { 244 | obj.tofu = 'no'; 245 | callback(); 246 | }); 247 | 248 | const obj = { bacon: 0, eggs: 0 }; 249 | 250 | const args = []; 251 | 252 | await assert.rejects(async() => { 253 | await hooks.wrap( 254 | 'cook', 255 | function() { 256 | throw new Error('error!'); 257 | }, 258 | obj, 259 | args); 260 | }, err => { 261 | assert.equal(err.message, 'error!'); 262 | assert.equal(obj.waffles, false); 263 | assert.equal(obj.tofu, undefined); 264 | return true; 265 | }); 266 | }); 267 | 268 | it('supports overwriteResult', async function() { 269 | hooks.post('cook', function(res, callback) { 270 | callback(Kareem.overwriteResult(5)); 271 | }); 272 | 273 | const result = await hooks.wrap( 274 | 'cook', 275 | function() { 276 | return 4; 277 | }, 278 | null, 279 | []); 280 | 281 | assert.equal(result, 5); 282 | }); 283 | 284 | it('supports skipWrappedFunction', async function() { 285 | const execed = {}; 286 | hooks.pre('cook', function pre() { 287 | execed.pre = true; 288 | throw Kareem.skipWrappedFunction(3); 289 | }); 290 | 291 | hooks.post('cook', function(res, callback) { 292 | assert.equal(res, 3); 293 | execed.post = true; 294 | callback(); 295 | }); 296 | 297 | const result = await hooks.wrap( 298 | 'cook', 299 | function wrapped() { 300 | execed.wrapped = true; 301 | }, 302 | null, 303 | []); 304 | 305 | assert.equal(result, 3); 306 | assert.ok(execed.pre); 307 | assert.ok(execed.post); 308 | assert.ok(!execed.wrapped); 309 | }); 310 | 311 | it('supports skipWrappedFunction with arguments', async function() { 312 | const execed = {}; 313 | hooks.pre('cook', function pre(arg) { 314 | execed.pre = true; 315 | assert.strictEqual(4, arg); 316 | throw Kareem.skipWrappedFunction(3); 317 | }); 318 | 319 | hooks.post('cook', function(res, callback) { 320 | assert.equal(3, res); 321 | execed.post = true; 322 | callback(); 323 | }); 324 | 325 | const args = [4]; 326 | 327 | const result = await hooks.wrap( 328 | 'cook', 329 | function wrapped() { 330 | execed.wrapped = true; 331 | return null; 332 | }, 333 | null, 334 | args 335 | ); 336 | 337 | assert.equal(result, 3); 338 | assert.ok(execed.pre); 339 | assert.ok(execed.post); 340 | assert.ok(!execed.wrapped); 341 | }); 342 | 343 | it('handles post errors with no args', async function() { 344 | hooks.pre('cook', function() { 345 | obj.waffles = false; 346 | }); 347 | 348 | hooks.post('cook', function(res, callback) { 349 | obj.tofu = 'no'; 350 | callback(new Error('error!')); 351 | }); 352 | 353 | const obj = { bacon: 0, eggs: 0 }; 354 | 355 | const args = []; 356 | 357 | const err = await hooks.wrap( 358 | 'cook', 359 | function() { 360 | return; 361 | }, 362 | obj, 363 | args 364 | ).then(() => null, err => err); 365 | 366 | assert.equal(err.message, 'error!'); 367 | assert.equal(obj.waffles, false); 368 | assert.equal(obj.tofu, 'no'); 369 | }); 370 | 371 | it('catches sync errors', async function() { 372 | hooks.pre('cook', function() {}); 373 | 374 | hooks.post('cook', function() {}); 375 | 376 | const err = await hooks.wrap( 377 | 'cook', 378 | function() { 379 | throw new Error('oops!'); 380 | }, 381 | null, 382 | [] 383 | ).then(() => null, err => err); 384 | 385 | assert.equal(err.message, 'oops!'); 386 | }); 387 | 388 | it('sync wrappers', function() { 389 | let calledPre = 0; 390 | let calledFn = 0; 391 | let calledPost = 0; 392 | hooks.pre('cook', function() { 393 | ++calledPre; 394 | }); 395 | 396 | hooks.post('cook', function() { 397 | ++calledPost; 398 | }); 399 | 400 | const wrapper = hooks.createWrapperSync('cook', function() { ++calledFn; }); 401 | 402 | wrapper(); 403 | 404 | assert.equal(calledPre, 1); 405 | assert.equal(calledFn, 1); 406 | assert.equal(calledPost, 1); 407 | }); 408 | 409 | it('sync wrappers with overwriteResult', function() { 410 | hooks.pre('cook', function() { 411 | }); 412 | 413 | hooks.post('cook', function() { 414 | return Kareem.overwriteResult(5); 415 | }); 416 | 417 | const wrapper = hooks.createWrapperSync('cook', function() { return 4; }); 418 | 419 | assert.strictEqual(wrapper(), 5); 420 | }); 421 | 422 | it('supports overwriteArguments in wrap()', async function() { 423 | const execed = {}; 424 | hooks.pre('init', function(obj) { 425 | execed.pre = true; 426 | if (typeof obj === 'string') { 427 | return Kareem.overwriteArguments({ name: obj }); 428 | } 429 | }); 430 | 431 | const result = await hooks.wrap( 432 | 'init', 433 | function(obj) { 434 | execed.wrapped = true; 435 | assert.strictEqual(typeof obj, 'object'); 436 | assert.strictEqual(obj.name, 'test'); 437 | return obj; 438 | }, 439 | null, 440 | ['test']); 441 | 442 | assert.ok(execed.pre); 443 | assert.ok(execed.wrapped); 444 | assert.deepStrictEqual(result, { name: 'test' }); 445 | }); 446 | 447 | it('supports overwriteArguments with throw in wrap()', async function() { 448 | const execed = {}; 449 | hooks.pre('init', function(obj) { 450 | execed.pre = true; 451 | if (typeof obj === 'string') { 452 | throw Kareem.overwriteArguments({ name: obj }); 453 | } 454 | }); 455 | 456 | const result = await hooks.wrap( 457 | 'init', 458 | function(obj) { 459 | execed.wrapped = true; 460 | assert.strictEqual(typeof obj, 'object'); 461 | assert.strictEqual(obj.name, 'test'); 462 | return obj; 463 | }, 464 | null, 465 | ['test']); 466 | 467 | assert.ok(execed.pre); 468 | assert.ok(execed.wrapped); 469 | assert.deepStrictEqual(result, { name: 'test' }); 470 | }); 471 | 472 | it('supports overwriteArguments with multiple pre hooks', async function() { 473 | hooks.pre('init', function(obj) { 474 | if (typeof obj === 'string') { 475 | return Kareem.overwriteArguments({ name: obj }); 476 | } 477 | }); 478 | 479 | hooks.pre('init', function(obj) { 480 | if (obj && typeof obj === 'object' && !obj.modified) { 481 | return Kareem.overwriteArguments({ ...obj, modified: true }); 482 | } 483 | }); 484 | 485 | const result = await hooks.wrap( 486 | 'init', 487 | function(obj) { 488 | assert.strictEqual(typeof obj, 'object'); 489 | assert.strictEqual(obj.name, 'test'); 490 | assert.strictEqual(obj.modified, true); 491 | return obj; 492 | }, 493 | null, 494 | ['test']); 495 | 496 | assert.deepStrictEqual(result, { name: 'test', modified: true }); 497 | }); 498 | 499 | it('supports overwriteArguments in sync wrappers', function() { 500 | hooks.pre('cook', function(obj) { 501 | if (typeof obj === 'string') { 502 | return Kareem.overwriteArguments({ name: obj }); 503 | } 504 | }); 505 | 506 | const wrapper = hooks.createWrapperSync('cook', function(obj) { 507 | assert.strictEqual(typeof obj, 'object'); 508 | assert.strictEqual(obj.name, 'hello'); 509 | return obj; 510 | }); 511 | 512 | const result = wrapper('hello'); 513 | assert.deepStrictEqual(result, { name: 'hello' }); 514 | }); 515 | 516 | it('supports overwriteArguments with multiple arguments', async function() { 517 | hooks.pre('process', function(a, b, c) { 518 | return Kareem.overwriteArguments(a + 1, b + 2, c + 3); 519 | }); 520 | 521 | const result = await hooks.wrap( 522 | 'process', 523 | function(a, b, c) { 524 | assert.strictEqual(a, 2); 525 | assert.strictEqual(b, 4); 526 | assert.strictEqual(c, 6); 527 | return a + b + c; 528 | }, 529 | null, 530 | [1, 2, 3]); 531 | 532 | assert.strictEqual(result, 12); 533 | }); 534 | }); 535 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Create a new instance 5 | */ 6 | function Kareem() { 7 | this._pres = new Map(); 8 | this._posts = new Map(); 9 | } 10 | 11 | Kareem.skipWrappedFunction = function skipWrappedFunction() { 12 | if (!(this instanceof Kareem.skipWrappedFunction)) { 13 | return new Kareem.skipWrappedFunction(...arguments); 14 | } 15 | 16 | this.args = [...arguments]; 17 | }; 18 | 19 | Kareem.overwriteResult = function overwriteResult() { 20 | if (!(this instanceof Kareem.overwriteResult)) { 21 | return new Kareem.overwriteResult(...arguments); 22 | } 23 | 24 | this.args = [...arguments]; 25 | }; 26 | 27 | Kareem.overwriteArguments = function overwriteArguments() { 28 | if (!(this instanceof Kareem.overwriteArguments)) { 29 | return new Kareem.overwriteArguments(...arguments); 30 | } 31 | 32 | this.args = [...arguments]; 33 | }; 34 | 35 | /** 36 | * Execute all "pre" hooks for "name" 37 | * @param {String} name The hook name to execute 38 | * @param {*} context Overwrite the "this" for the hook 39 | * @param {Array} args arguments passed to the pre hooks 40 | * @returns {Array} The potentially modified arguments 41 | */ 42 | Kareem.prototype.execPre = async function execPre(name, context, args) { 43 | const pres = this._pres.get(name) || []; 44 | const numPres = pres.length; 45 | let $args = args; 46 | let skipWrappedFunction = null; 47 | 48 | if (!numPres) { 49 | return $args; 50 | } 51 | 52 | for (const pre of pres) { 53 | const args = []; 54 | const _args = [null].concat($args); 55 | for (let i = 1; i < _args.length; ++i) { 56 | if (i === _args.length - 1 && typeof _args[i] === 'function') { 57 | continue; // skip callbacks to avoid accidentally calling the callback from a hook 58 | } 59 | args.push(_args[i]); 60 | } 61 | 62 | try { 63 | const maybePromiseLike = pre.fn.apply(context, args); 64 | if (isPromiseLike(maybePromiseLike)) { 65 | const result = await maybePromiseLike; 66 | if (result instanceof Kareem.overwriteArguments) { 67 | $args = result.args; 68 | } 69 | } else if (maybePromiseLike instanceof Kareem.overwriteArguments) { 70 | $args = maybePromiseLike.args; 71 | } 72 | } catch (error) { 73 | if (error instanceof Kareem.skipWrappedFunction) { 74 | skipWrappedFunction = error; 75 | continue; 76 | } 77 | if (error instanceof Kareem.overwriteArguments) { 78 | $args = error.args; 79 | continue; 80 | } 81 | throw error; 82 | } 83 | } 84 | 85 | if (skipWrappedFunction) { 86 | throw skipWrappedFunction; 87 | } 88 | 89 | return $args; 90 | }; 91 | 92 | /** 93 | * Execute all "pre" hooks for "name" synchronously 94 | * @param {String} name The hook name to execute 95 | * @param {*} context Overwrite the "this" for the hook 96 | * @param {Array} [args] Apply custom arguments to the hook 97 | * @returns {Array} The potentially modified arguments 98 | */ 99 | Kareem.prototype.execPreSync = function(name, context, args) { 100 | const pres = this._pres.get(name) || []; 101 | const numPres = pres.length; 102 | let $args = args || []; 103 | 104 | for (let i = 0; i < numPres; ++i) { 105 | const result = pres[i].fn.apply(context, $args); 106 | if (result instanceof Kareem.overwriteArguments) { 107 | $args = result.args; 108 | } 109 | } 110 | 111 | return $args; 112 | }; 113 | 114 | /** 115 | * Execute all "post" hooks for "name" 116 | * @param {String} name The hook name to execute 117 | * @param {*} context Overwrite the "this" for the hook 118 | * @param {Array} args Apply custom arguments to the hook 119 | * @param {*} options Optional options or directly the callback 120 | * @returns {void} 121 | */ 122 | Kareem.prototype.execPost = async function execPost(name, context, args, options) { 123 | const posts = this._posts.get(name) || []; 124 | const numPosts = posts.length; 125 | 126 | let firstError = null; 127 | if (options && options.error) { 128 | firstError = options.error; 129 | } 130 | 131 | if (!numPosts) { 132 | if (firstError != null) { 133 | throw firstError; 134 | } 135 | return args; 136 | } 137 | 138 | for (const currentPost of posts) { 139 | const post = currentPost.fn; 140 | let numArgs = 0; 141 | const newArgs = []; 142 | const argLength = args.length; 143 | for (let i = 0; i < argLength; ++i) { 144 | if (!args[i] || !args[i]._kareemIgnore) { 145 | numArgs += 1; 146 | newArgs.push(args[i]); 147 | } 148 | } 149 | // If numCallbackParams set, fill in the rest with null to enforce consistent number of args 150 | if (options?.numCallbackParams != null) { 151 | numArgs = options.numCallbackParams; 152 | for (let i = newArgs.length; i < numArgs; ++i) { 153 | newArgs.push(null); 154 | } 155 | } 156 | 157 | let resolve; 158 | let reject; 159 | const cbPromise = new Promise((_resolve, _reject) => { 160 | resolve = _resolve; 161 | reject = _reject; 162 | }); 163 | newArgs.push(function nextCallback(err) { 164 | if (err) { 165 | reject(err); 166 | } else { 167 | resolve(); 168 | } 169 | }); 170 | 171 | if (firstError) { 172 | if (isErrorHandlingMiddleware(currentPost, numArgs)) { 173 | try { 174 | const res = post.apply(context, [firstError].concat(newArgs)); 175 | if (isPromiseLike(res)) { 176 | await res; 177 | } else if (post.length === numArgs + 2) { 178 | // `numArgs + 2` because we added the error and the callback 179 | await cbPromise; 180 | } 181 | } catch (error) { 182 | if (error instanceof Kareem.overwriteResult) { 183 | args = error.args; 184 | continue; 185 | } 186 | firstError = error; 187 | } 188 | } else { 189 | continue; 190 | } 191 | } else { 192 | if (isErrorHandlingMiddleware(currentPost, numArgs)) { 193 | // Skip error handlers if no error 194 | continue; 195 | } else { 196 | let res = null; 197 | try { 198 | res = post.apply(context, newArgs); 199 | if (isPromiseLike(res)) { 200 | res = await res; 201 | } else if (post.length === numArgs + 1) { 202 | // If post function takes a callback, wait for the post function to call the callback 203 | res = await cbPromise; 204 | } 205 | } catch (error) { 206 | if (error instanceof Kareem.overwriteResult) { 207 | args = error.args; 208 | continue; 209 | } 210 | firstError = error; 211 | continue; 212 | } 213 | 214 | if (res instanceof Kareem.overwriteResult) { 215 | args = res.args; 216 | continue; 217 | } 218 | } 219 | } 220 | } 221 | 222 | if (firstError != null) { 223 | throw firstError; 224 | } 225 | 226 | return args; 227 | }; 228 | 229 | /** 230 | * Execute all "post" hooks for "name" synchronously 231 | * @param {String} name The hook name to execute 232 | * @param {*} context Overwrite the "this" for the hook 233 | * @param {Array} args Apply custom arguments to the hook 234 | * @returns {Array} The used arguments 235 | */ 236 | Kareem.prototype.execPostSync = function(name, context, args) { 237 | const posts = this._posts.get(name) || []; 238 | const numPosts = posts.length; 239 | 240 | for (let i = 0; i < numPosts; ++i) { 241 | const res = posts[i].fn.apply(context, args || []); 242 | if (res instanceof Kareem.overwriteResult) { 243 | args = res.args; 244 | } 245 | } 246 | 247 | return args; 248 | }; 249 | 250 | /** 251 | * Create a synchronous wrapper for "fn" 252 | * @param {String} name The name of the hook 253 | * @param {Function} fn The function to wrap 254 | * @returns {Function} The wrapped function 255 | */ 256 | Kareem.prototype.createWrapperSync = function(name, fn) { 257 | const _this = this; 258 | return function syncWrapper() { 259 | const modifiedArgs = _this.execPreSync(name, this, Array.from(arguments)); 260 | 261 | const toReturn = fn.apply(this, modifiedArgs); 262 | 263 | const result = _this.execPostSync(name, this, [toReturn]); 264 | 265 | return result[0]; 266 | }; 267 | }; 268 | 269 | /** 270 | * Executes pre hooks, followed by the wrapped function, followed by post hooks. 271 | * @param {String} name The name of the hook 272 | * @param {Function} fn The function for the hook 273 | * @param {*} context Overwrite the "this" for the hook 274 | * @param {Array} args Apply custom arguments to the hook 275 | * @param {Object} options Additional options for the hook 276 | * @returns {void} 277 | */ 278 | Kareem.prototype.wrap = async function wrap(name, fn, context, args, options) { 279 | let ret; 280 | let skipWrappedFunction = false; 281 | let modifiedArgs = args; 282 | try { 283 | modifiedArgs = await this.execPre(name, context, args); 284 | } catch (error) { 285 | if (error instanceof Kareem.skipWrappedFunction) { 286 | ret = error.args; 287 | skipWrappedFunction = true; 288 | } else { 289 | await this.execPost(name, context, args, { ...options, error }); 290 | } 291 | } 292 | 293 | if (!skipWrappedFunction) { 294 | ret = await fn.apply(context, modifiedArgs); 295 | } 296 | 297 | ret = await this.execPost(name, context, [ret], options); 298 | 299 | return ret[0]; 300 | }; 301 | 302 | /** 303 | * Filter current instance for something specific and return the filtered clone 304 | * @param {Function} fn The filter function 305 | * @returns {Kareem} The cloned and filtered instance 306 | */ 307 | Kareem.prototype.filter = function(fn) { 308 | const clone = this.clone(); 309 | 310 | const pres = Array.from(clone._pres.keys()); 311 | for (const name of pres) { 312 | const hooks = this._pres.get(name). 313 | map(h => Object.assign({}, h, { name: name })). 314 | filter(fn); 315 | 316 | if (hooks.length === 0) { 317 | clone._pres.delete(name); 318 | continue; 319 | } 320 | 321 | clone._pres.set(name, hooks); 322 | } 323 | 324 | const posts = Array.from(clone._posts.keys()); 325 | for (const name of posts) { 326 | const hooks = this._posts.get(name). 327 | map(h => Object.assign({}, h, { name: name })). 328 | filter(fn); 329 | 330 | if (hooks.length === 0) { 331 | clone._posts.delete(name); 332 | continue; 333 | } 334 | 335 | clone._posts.set(name, hooks); 336 | } 337 | 338 | return clone; 339 | }; 340 | 341 | /** 342 | * Check for a "name" to exist either in pre or post hooks 343 | * @param {String} name The name of the hook 344 | * @returns {Boolean} "true" if found, "false" otherwise 345 | */ 346 | Kareem.prototype.hasHooks = function(name) { 347 | return this._pres.has(name) || this._posts.has(name); 348 | }; 349 | 350 | /** 351 | * Create a Wrapper for "fn" on "name" and return the wrapped function 352 | * @param {String} name The name of the hook 353 | * @param {Function} fn The function to wrap 354 | * @param {*} context Overwrite the "this" for the hook 355 | * @param {Object} [options] 356 | * @returns {Function} The wrapped function 357 | */ 358 | Kareem.prototype.createWrapper = function(name, fn, context, options) { 359 | const _this = this; 360 | if (!this.hasHooks(name)) { 361 | // Fast path: if there's no hooks for this function, just return the function 362 | return fn; 363 | } 364 | return function kareemWrappedFunction() { 365 | const _context = context || this; 366 | return _this.wrap(name, fn, _context, Array.from(arguments), options); 367 | }; 368 | }; 369 | 370 | /** 371 | * Register a new hook for "pre" 372 | * @param {String} name The name of the hook 373 | * @param {Object} [options] 374 | * @param {Function} fn The function to register for "name" 375 | * @param {never} error Unused 376 | * @param {Boolean} [unshift] Wheter to "push" or to "unshift" the new hook 377 | * @returns {Kareem} 378 | */ 379 | Kareem.prototype.pre = function(name, options, fn, error, unshift) { 380 | if (typeof options === 'function') { 381 | fn = options; 382 | options = {}; 383 | } else if (options == null) { 384 | options = {}; 385 | } 386 | 387 | const pres = this._pres.get(name) || []; 388 | this._pres.set(name, pres); 389 | 390 | if (typeof fn !== 'function') { 391 | throw new Error('pre() requires a function, got "' + typeof fn + '"'); 392 | } 393 | 394 | if (unshift) { 395 | pres.unshift(Object.assign({}, options, { fn: fn })); 396 | } else { 397 | pres.push(Object.assign({}, options, { fn: fn })); 398 | } 399 | 400 | return this; 401 | }; 402 | 403 | /** 404 | * Register a new hook for "post" 405 | * @param {String} name The name of the hook 406 | * @param {Object} [options] 407 | * @param {Boolean} [options.errorHandler] Whether this is an error handler 408 | * @param {Function} fn The function to register for "name" 409 | * @param {Boolean} [unshift] Wheter to "push" or to "unshift" the new hook 410 | * @returns {Kareem} 411 | */ 412 | Kareem.prototype.post = function(name, options, fn, unshift) { 413 | const posts = this._posts.get(name) || []; 414 | 415 | if (typeof options === 'function') { 416 | unshift = !!fn; 417 | fn = options; 418 | options = {}; 419 | } 420 | 421 | if (typeof fn !== 'function') { 422 | throw new Error('post() requires a function, got "' + typeof fn + '"'); 423 | } 424 | 425 | if (unshift) { 426 | posts.unshift(Object.assign({}, options, { fn: fn })); 427 | } else { 428 | posts.push(Object.assign({}, options, { fn: fn })); 429 | } 430 | this._posts.set(name, posts); 431 | return this; 432 | }; 433 | 434 | /** 435 | * Register a new error handler for "name" 436 | * @param {String} name The name of the hook 437 | * @param {Object} [options] 438 | * @param {Function} fn The function to register for "name" 439 | * @param {Boolean} [unshift] Wheter to "push" or to "unshift" the new hook 440 | * @returns {Kareem} 441 | */ 442 | 443 | Kareem.prototype.postError = function postError(name, options, fn, unshift) { 444 | if (typeof options === 'function') { 445 | unshift = !!fn; 446 | fn = options; 447 | options = {}; 448 | } 449 | return this.post(name, { ...options, errorHandler: true }, fn, unshift); 450 | }; 451 | 452 | /** 453 | * Clone the current instance 454 | * @returns {Kareem} The cloned instance 455 | */ 456 | Kareem.prototype.clone = function() { 457 | const n = new Kareem(); 458 | 459 | for (const key of this._pres.keys()) { 460 | const clone = this._pres.get(key).slice(); 461 | n._pres.set(key, clone); 462 | } 463 | for (const key of this._posts.keys()) { 464 | n._posts.set(key, this._posts.get(key).slice()); 465 | } 466 | 467 | return n; 468 | }; 469 | 470 | /** 471 | * Merge "other" into self or "clone" 472 | * @param {Kareem} other The instance to merge with 473 | * @param {Kareem} [clone] The instance to merge onto (if not defined, using "this") 474 | * @returns {Kareem} The merged instance 475 | */ 476 | Kareem.prototype.merge = function(other, clone) { 477 | clone = arguments.length === 1 ? true : clone; 478 | const ret = clone ? this.clone() : this; 479 | 480 | for (const key of other._pres.keys()) { 481 | const sourcePres = ret._pres.get(key) || []; 482 | const deduplicated = other._pres.get(key). 483 | // Deduplicate based on `fn` 484 | filter(p => sourcePres.map(_p => _p.fn).indexOf(p.fn) === -1); 485 | const combined = sourcePres.concat(deduplicated); 486 | ret._pres.set(key, combined); 487 | } 488 | for (const key of other._posts.keys()) { 489 | const sourcePosts = ret._posts.get(key) || []; 490 | const deduplicated = other._posts.get(key). 491 | filter(p => sourcePosts.indexOf(p) === -1); 492 | ret._posts.set(key, sourcePosts.concat(deduplicated)); 493 | } 494 | 495 | return ret; 496 | }; 497 | 498 | function isPromiseLike(v) { 499 | return (typeof v === 'object' && v !== null && typeof v.then === 'function'); 500 | } 501 | 502 | function isErrorHandlingMiddleware(post, numArgs) { 503 | if (post.errorHandler) { 504 | return true; 505 | } 506 | return post.fn.length === numArgs + 2; 507 | } 508 | 509 | module.exports = Kareem; 510 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | ## 3.0.0 (2025-11-18) 5 | 6 | * BREAKING CHANGE: make execPre async and drop callback support #39 7 | * BREAKING CHANGE: require Node 18 8 | * feat: overwriteArguments support #42 9 | 10 | 11 | ## 2.6.0 (2024-03-04) 12 | 13 | * feat: add TypeScript types 14 | 15 | 16 | ## 2.5.1 (2023-01-06) 17 | 18 | * fix: avoid passing final callback to pre hook, because calling the callback can mess up hook execution #36 Automattic/mongoose#12836 19 | 20 | 21 | ## 2.5.0 (2022-12-01) 22 | 23 | * feat: add errorHandler option to `post()` #34 24 | 25 | 26 | ## 2.4.0 (2022-06-13) 27 | 28 | * feat: add `overwriteResult()` and `skipWrappedFunction()` for more advanced control flow 29 | 30 | 31 | ## 2.3.4 (2022-02-10) 32 | 33 | * perf: various performance improvements #27 #24 #23 #22 #21 #20 34 | 35 | 36 | ## 2.3.3 (2021-12-26) 37 | 38 | * fix: handle sync errors in `wrap()` 39 | 40 | 41 | ## 2.3.2 (2020-12-08) 42 | 43 | * fix: handle sync errors in pre hooks if there are multiple hooks 44 | 45 | 46 | ## 2.3.0 (2018-09-24) 47 | 48 | * chore(release): 2.2.3 ([c8f2695](https://github.com/vkarpov15/kareem/commit/c8f2695)) 49 | * chore(release): 2.2.4 ([a377a4f](https://github.com/vkarpov15/kareem/commit/a377a4f)) 50 | * chore(release): 2.2.5 ([5a495e3](https://github.com/vkarpov15/kareem/commit/5a495e3)) 51 | * fix(filter): copy async pres correctly with `filter()` ([1b1ed8a](https://github.com/vkarpov15/kareem/commit/1b1ed8a)), closes [Automattic/mongoose#3054](https://github.com/Automattic/mongoose/issues/3054) 52 | * feat: add filter() function ([1f641f4](https://github.com/vkarpov15/kareem/commit/1f641f4)) 53 | * feat: support storing options on pre and post hooks ([59220b9](https://github.com/vkarpov15/kareem/commit/59220b9)) 54 | 55 | 56 | 57 | 58 | ## 2.2.3 (2018-09-10) 59 | 60 | * chore: release 2.2.3 ([af653a3](https://github.com/vkarpov15/kareem/commit/af653a3)) 61 | 62 | 63 | 64 | 65 | ## 2.2.2 (2018-09-10) 66 | 67 | * chore: release 2.2.2 ([3f0144d](https://github.com/vkarpov15/kareem/commit/3f0144d)) 68 | * fix: allow merge() to not clone ([e628d65](https://github.com/vkarpov15/kareem/commit/e628d65)) 69 | 70 | 71 | 72 | 73 | ## 2.2.1 (2018-06-05) 74 | 75 | * chore: release 2.2.1 ([4625a64](https://github.com/vkarpov15/kareem/commit/4625a64)) 76 | * chore: remove lockfile from git ([7f3e4e6](https://github.com/vkarpov15/kareem/commit/7f3e4e6)) 77 | * fix: handle numAsync correctly when merging ([fef8e7e](https://github.com/vkarpov15/kareem/commit/fef8e7e)) 78 | * test: repro issue with not copying numAsync ([952d9db](https://github.com/vkarpov15/kareem/commit/952d9db)) 79 | 80 | 81 | 82 | 83 | ## 2.2.0 (2018-06-05) 84 | 85 | * chore: release 2.2.0 ([ff9ad03](https://github.com/vkarpov15/kareem/commit/ff9ad03)) 86 | * fix: use maps instead of objects for _pres and _posts so `toString()` doesn't get reported as having ([55df303](https://github.com/vkarpov15/kareem/commit/55df303)), closes [Automattic/mongoose#6538](https://github.com/Automattic/mongoose/issues/6538) 87 | 88 | 89 | 90 | 91 | ## 2.1.0 (2018-05-16) 92 | 93 | * chore: release 2.1.0 ([ba5f1bc](https://github.com/vkarpov15/kareem/commit/ba5f1bc)) 94 | * feat: add option to check wrapped function return value for promises ([c9d7dd1](https://github.com/vkarpov15/kareem/commit/c9d7dd1)) 95 | * refactor: use const in wrap() ([0fc21f9](https://github.com/vkarpov15/kareem/commit/0fc21f9)) 96 | 97 | 98 | 99 | 100 | ## 2.0.7 (2018-04-28) 101 | 102 | * chore: release 2.0.7 ([0bf91e6](https://github.com/vkarpov15/kareem/commit/0bf91e6)) 103 | * feat: add `hasHooks()` ([225f18d](https://github.com/vkarpov15/kareem/commit/225f18d)), closes [Automattic/mongoose#6385](https://github.com/Automattic/mongoose/issues/6385) 104 | 105 | 106 | 107 | 108 | ## 2.0.6 (2018-03-22) 109 | 110 | * chore: release 2.0.6 ([f3d406b](https://github.com/vkarpov15/kareem/commit/f3d406b)) 111 | * fix(wrap): ensure fast path still wraps function in `nextTick()` for chaining ([7000494](https://github.com/vkarpov15/kareem/commit/7000494)), closes [Automattic/mongoose#6250](https://github.com/Automattic/mongoose/issues/6250) [dsanel/mongoose-delete#36](https://github.com/dsanel/mongoose-delete/issues/36) 112 | 113 | 114 | 115 | 116 | ## 2.0.5 (2018-02-22) 117 | 118 | * chore: release 2.0.5 ([3286612](https://github.com/vkarpov15/kareem/commit/3286612)) 119 | * perf(createWrapper): don't create wrapper if there are no hooks ([5afc5b9](https://github.com/vkarpov15/kareem/commit/5afc5b9)), closes [Automattic/mongoose#6126](https://github.com/Automattic/mongoose/issues/6126) 120 | 121 | 122 | 123 | 124 | ## 2.0.4 (2018-02-08) 125 | 126 | * chore: release 2.0.4 ([2ab0293](https://github.com/vkarpov15/kareem/commit/2ab0293)) 127 | 128 | 129 | 130 | 131 | ## 2.0.3 (2018-02-01) 132 | 133 | * chore: release 2.0.3 ([3c1abe5](https://github.com/vkarpov15/kareem/commit/3c1abe5)) 134 | * fix: use process.nextTick() re: Automattic/mongoose#6074 ([e5bfe33](https://github.com/vkarpov15/kareem/commit/e5bfe33)), closes [Automattic/mongoose#6074](https://github.com/Automattic/mongoose/issues/6074) 135 | 136 | 137 | 138 | 139 | ## 2.0.2 (2018-01-24) 140 | 141 | * chore: fix license ([a9d755c](https://github.com/vkarpov15/kareem/commit/a9d755c)), closes [#10](https://github.com/vkarpov15/kareem/issues/10) 142 | * chore: release 2.0.2 ([fe87ab6](https://github.com/vkarpov15/kareem/commit/fe87ab6)) 143 | 144 | 145 | 146 | 147 | ## 2.0.1 (2018-01-09) 148 | 149 | * chore: release 2.0.1 with lockfile bump ([09c44fb](https://github.com/vkarpov15/kareem/commit/09c44fb)) 150 | 151 | 152 | 153 | 154 | ## 2.0.0 (2018-01-09) 155 | 156 | * chore: bump marked re: security ([cc564a9](https://github.com/vkarpov15/kareem/commit/cc564a9)) 157 | * chore: release 2.0.0 ([f511d1c](https://github.com/vkarpov15/kareem/commit/f511d1c)) 158 | 159 | 160 | 161 | 162 | ## 2.0.0-rc5 (2017-12-23) 163 | 164 | * chore: fix build on node 4+5 ([6dac5a4](https://github.com/vkarpov15/kareem/commit/6dac5a4)) 165 | * chore: fix built on node 4 + 5 again ([434ef0a](https://github.com/vkarpov15/kareem/commit/434ef0a)) 166 | * chore: release 2.0.0-rc5 ([25a32ee](https://github.com/vkarpov15/kareem/commit/25a32ee)) 167 | 168 | 169 | 170 | 171 | ## 2.0.0-rc4 (2017-12-22) 172 | 173 | * chore: release 2.0.0-rc4 ([49fc083](https://github.com/vkarpov15/kareem/commit/49fc083)) 174 | * BREAKING CHANGE: deduplicate when merging hooks re: Automattic/mongoose#2945 ([d458573](https://github.com/vkarpov15/kareem/commit/d458573)), closes [Automattic/mongoose#2945](https://github.com/Automattic/mongoose/issues/2945) 175 | 176 | 177 | 178 | 179 | ## 2.0.0-rc3 (2017-12-22) 180 | 181 | * chore: release 2.0.0-rc3 ([adaaa00](https://github.com/vkarpov15/kareem/commit/adaaa00)) 182 | * feat: support returning promises from middleware functions ([05b4480](https://github.com/vkarpov15/kareem/commit/05b4480)), closes [Automattic/mongoose#3779](https://github.com/Automattic/mongoose/issues/3779) 183 | 184 | 185 | 186 | 187 | ## 2.0.0-rc2 (2017-12-21) 188 | 189 | * chore: release 2.0.0-rc2 ([76325fa](https://github.com/vkarpov15/kareem/commit/76325fa)) 190 | * fix: ensure next() and done() run in next tick ([6c20684](https://github.com/vkarpov15/kareem/commit/6c20684)) 191 | 192 | 193 | 194 | 195 | ## 2.0.0-rc1 (2017-12-21) 196 | 197 | * chore: improve test coverage re: Automattic/mongoose#3232 ([7b45cf0](https://github.com/vkarpov15/kareem/commit/7b45cf0)), closes [Automattic/mongoose#3232](https://github.com/Automattic/mongoose/issues/3232) 198 | * chore: release 2.0.0-rc1 ([9b83f52](https://github.com/vkarpov15/kareem/commit/9b83f52)) 199 | * BREAKING CHANGE: report sync exceptions as errors, only allow calling next() and done() once ([674adcc](https://github.com/vkarpov15/kareem/commit/674adcc)), closes [Automattic/mongoose#3483](https://github.com/Automattic/mongoose/issues/3483) 200 | 201 | 202 | 203 | 204 | ## 2.0.0-rc0 (2017-12-17) 205 | 206 | * chore: release 2.0.0-rc0 ([16b44b5](https://github.com/vkarpov15/kareem/commit/16b44b5)) 207 | * BREAKING CHANGE: drop support for node < 4 ([9cbb8c7](https://github.com/vkarpov15/kareem/commit/9cbb8c7)) 208 | * BREAKING CHANGE: remove useLegacyPost and add several new features ([6dd8531](https://github.com/vkarpov15/kareem/commit/6dd8531)), closes [Automattic/mongoose#3232](https://github.com/Automattic/mongoose/issues/3232) 209 | 210 | 211 | 212 | 213 | ## 1.5.0 (2017-07-20) 214 | 215 | * chore: release 1.5.0 ([9c491a0](https://github.com/vkarpov15/kareem/commit/9c491a0)) 216 | * fix: improve post error handlers results ([9928dd5](https://github.com/vkarpov15/kareem/commit/9928dd5)), closes [Automattic/mongoose#5466](https://github.com/Automattic/mongoose/issues/5466) 217 | 218 | 219 | 220 | 221 | ## 1.4.2 (2017-07-06) 222 | 223 | * chore: release 1.4.2 ([8d14ac5](https://github.com/vkarpov15/kareem/commit/8d14ac5)) 224 | * fix: correct args re: Automattic/mongoose#5405 ([3f28ae6](https://github.com/vkarpov15/kareem/commit/3f28ae6)), closes [Automattic/mongoose#5405](https://github.com/Automattic/mongoose/issues/5405) 225 | 226 | 227 | 228 | 229 | ## 1.4.1 (2017-04-25) 230 | 231 | * chore: release 1.4.1 ([5ecf0c2](https://github.com/vkarpov15/kareem/commit/5ecf0c2)) 232 | * fix: handle numAsyncPres with clone() ([c72e857](https://github.com/vkarpov15/kareem/commit/c72e857)), closes [#8](https://github.com/vkarpov15/kareem/issues/8) 233 | * test: repro #8 ([9b4d6b2](https://github.com/vkarpov15/kareem/commit/9b4d6b2)), closes [#8](https://github.com/vkarpov15/kareem/issues/8) 234 | 235 | 236 | 237 | 238 | ## 1.4.0 (2017-04-19) 239 | 240 | * chore: release 1.4.0 ([101c5f5](https://github.com/vkarpov15/kareem/commit/101c5f5)) 241 | * feat: add merge() function ([285325e](https://github.com/vkarpov15/kareem/commit/285325e)) 242 | 243 | 244 | 245 | 246 | ## 1.3.0 (2017-03-26) 247 | 248 | * chore: release 1.3.0 ([f3a9e50](https://github.com/vkarpov15/kareem/commit/f3a9e50)) 249 | * feat: pass function args to execPre ([4dd466d](https://github.com/vkarpov15/kareem/commit/4dd466d)) 250 | 251 | 252 | 253 | 254 | ## 1.2.1 (2017-02-03) 255 | 256 | * chore: release 1.2.1 ([d97081f](https://github.com/vkarpov15/kareem/commit/d97081f)) 257 | * fix: filter out _kareemIgnored args for error handlers re: Automattic/mongoose#4925 ([ddc7aeb](https://github.com/vkarpov15/kareem/commit/ddc7aeb)), closes [Automattic/mongoose#4925](https://github.com/Automattic/mongoose/issues/4925) 258 | * fix: make error handlers handle errors in pre hooks ([af38033](https://github.com/vkarpov15/kareem/commit/af38033)), closes [Automattic/mongoose#4927](https://github.com/Automattic/mongoose/issues/4927) 259 | 260 | 261 | 262 | 263 | ## 1.2.0 (2017-01-02) 264 | 265 | * chore: release 1.2.0 ([033225c](https://github.com/vkarpov15/kareem/commit/033225c)) 266 | * chore: upgrade deps ([f9e9a09](https://github.com/vkarpov15/kareem/commit/f9e9a09)) 267 | * feat: add _kareemIgnore re: Automattic/mongoose#4836 ([7957771](https://github.com/vkarpov15/kareem/commit/7957771)), closes [Automattic/mongoose#4836](https://github.com/Automattic/mongoose/issues/4836) 268 | 269 | 270 | 271 | 272 | ## 1.1.5 (2016-12-13) 273 | 274 | * chore: release 1.1.5 ([1a9f684](https://github.com/vkarpov15/kareem/commit/1a9f684)) 275 | * fix: correct field name ([04a0e9d](https://github.com/vkarpov15/kareem/commit/04a0e9d)) 276 | 277 | 278 | 279 | 280 | ## 1.1.4 (2016-12-09) 281 | 282 | * chore: release 1.1.4 ([ece401c](https://github.com/vkarpov15/kareem/commit/ece401c)) 283 | * chore: run tests on node 6 ([e0cb1cb](https://github.com/vkarpov15/kareem/commit/e0cb1cb)) 284 | * fix: only copy own properties in clone() ([dfe28ce](https://github.com/vkarpov15/kareem/commit/dfe28ce)), closes [#7](https://github.com/vkarpov15/kareem/issues/7) 285 | 286 | 287 | 288 | 289 | ## 1.1.3 (2016-06-27) 290 | 291 | * chore: release 1.1.3 ([87171c8](https://github.com/vkarpov15/kareem/commit/87171c8)) 292 | * fix: couple more issues with arg processing ([c65f523](https://github.com/vkarpov15/kareem/commit/c65f523)) 293 | 294 | 295 | 296 | 297 | ## 1.1.2 (2016-06-27) 298 | 299 | * chore: release 1.1.2 ([8e102b6](https://github.com/vkarpov15/kareem/commit/8e102b6)) 300 | * fix: add early return ([4feda4e](https://github.com/vkarpov15/kareem/commit/4feda4e)) 301 | 302 | 303 | 304 | 305 | ## 1.1.1 (2016-06-27) 306 | 307 | * chore: release 1.1.1 ([8bb3050](https://github.com/vkarpov15/kareem/commit/8bb3050)) 308 | * fix: skip error handlers if no error ([0eb3a44](https://github.com/vkarpov15/kareem/commit/0eb3a44)) 309 | 310 | 311 | 312 | 313 | ## 1.1.0 (2016-05-11) 314 | 315 | * chore: release 1.1.0 ([85332d9](https://github.com/vkarpov15/kareem/commit/85332d9)) 316 | * chore: test on node 4 and node 5 ([1faefa1](https://github.com/vkarpov15/kareem/commit/1faefa1)) 317 | * 100% coverage again ([c9aee4e](https://github.com/vkarpov15/kareem/commit/c9aee4e)) 318 | * add support for error post hooks ([d378113](https://github.com/vkarpov15/kareem/commit/d378113)) 319 | * basic setup for sync hooks #4 ([55aa081](https://github.com/vkarpov15/kareem/commit/55aa081)), closes [#4](https://github.com/vkarpov15/kareem/issues/4) 320 | * proof of concept for error handlers ([e4a07d9](https://github.com/vkarpov15/kareem/commit/e4a07d9)) 321 | * refactor out handleWrapError helper ([b19af38](https://github.com/vkarpov15/kareem/commit/b19af38)) 322 | 323 | 324 | 325 | 326 | ## 1.0.1 (2015-05-10) 327 | 328 | * Fix #1 ([de60dc6](https://github.com/vkarpov15/kareem/commit/de60dc6)), closes [#1](https://github.com/vkarpov15/kareem/issues/1) 329 | * release 1.0.1 ([6971088](https://github.com/vkarpov15/kareem/commit/6971088)) 330 | * Run tests on iojs in travis ([adcd201](https://github.com/vkarpov15/kareem/commit/adcd201)) 331 | * support legacy post hook behavior in wrap() ([23fa74c](https://github.com/vkarpov15/kareem/commit/23fa74c)) 332 | * Use node 0.12 in travis ([834689d](https://github.com/vkarpov15/kareem/commit/834689d)) 333 | 334 | 335 | 336 | 337 | ## 1.0.0 (2015-01-28) 338 | 339 | * Tag 1.0.0 ([4c5a35a](https://github.com/vkarpov15/kareem/commit/4c5a35a)) 340 | 341 | 342 | 343 | 344 | ## 0.0.8 (2015-01-27) 345 | 346 | * Add clone function ([688bba7](https://github.com/vkarpov15/kareem/commit/688bba7)) 347 | * Add jscs for style checking ([5c93149](https://github.com/vkarpov15/kareem/commit/5c93149)) 348 | * Bump 0.0.8 ([03c0d2f](https://github.com/vkarpov15/kareem/commit/03c0d2f)) 349 | * Fix jscs config, add gulp rules ([9989abf](https://github.com/vkarpov15/kareem/commit/9989abf)) 350 | * fix Makefile typo ([1f7e61a](https://github.com/vkarpov15/kareem/commit/1f7e61a)) 351 | 352 | 353 | 354 | 355 | ## 0.0.7 (2015-01-04) 356 | 357 | * Bump 0.0.7 ([98ef173](https://github.com/vkarpov15/kareem/commit/98ef173)) 358 | * fix LearnBoost/mongoose#2553 - use null instead of undefined for err ([9157b48](https://github.com/vkarpov15/kareem/commit/9157b48)), closes [LearnBoost/mongoose#2553](https://github.com/LearnBoost/mongoose/issues/2553) 359 | * Regenerate docs ([2331cdf](https://github.com/vkarpov15/kareem/commit/2331cdf)) 360 | 361 | 362 | 363 | 364 | ## 0.0.6 (2015-01-01) 365 | 366 | * Update docs and bump 0.0.6 ([92c12a7](https://github.com/vkarpov15/kareem/commit/92c12a7)) 367 | 368 | 369 | 370 | 371 | ## 0.0.5 (2015-01-01) 372 | 373 | * Add coverage rule to Makefile ([825a91c](https://github.com/vkarpov15/kareem/commit/825a91c)) 374 | * Add coveralls to README ([fb52369](https://github.com/vkarpov15/kareem/commit/fb52369)) 375 | * Add coveralls to travis ([93f6f15](https://github.com/vkarpov15/kareem/commit/93f6f15)) 376 | * Add createWrapper() function ([ea77741](https://github.com/vkarpov15/kareem/commit/ea77741)) 377 | * Add istanbul code coverage ([6eceeef](https://github.com/vkarpov15/kareem/commit/6eceeef)) 378 | * Add some more comments for examples ([c5b0c6f](https://github.com/vkarpov15/kareem/commit/c5b0c6f)) 379 | * Add travis ([e6dcb06](https://github.com/vkarpov15/kareem/commit/e6dcb06)) 380 | * Add travis badge to docs ([ad8c9b3](https://github.com/vkarpov15/kareem/commit/ad8c9b3)) 381 | * Add wrap() tests, 100% coverage ([6945be4](https://github.com/vkarpov15/kareem/commit/6945be4)) 382 | * Better test coverage for execPost ([d9ad539](https://github.com/vkarpov15/kareem/commit/d9ad539)) 383 | * Bump 0.0.5 ([69875b1](https://github.com/vkarpov15/kareem/commit/69875b1)) 384 | * Docs fix ([15b7098](https://github.com/vkarpov15/kareem/commit/15b7098)) 385 | * Fix silly mistake in docs generation ([50373eb](https://github.com/vkarpov15/kareem/commit/50373eb)) 386 | * Fix typo in readme ([fec4925](https://github.com/vkarpov15/kareem/commit/fec4925)) 387 | * Linkify travis badge ([92b25fe](https://github.com/vkarpov15/kareem/commit/92b25fe)) 388 | * Make travis run coverage ([747157b](https://github.com/vkarpov15/kareem/commit/747157b)) 389 | * Move travis status badge ([d52e89b](https://github.com/vkarpov15/kareem/commit/d52e89b)) 390 | * Quick fix for coverage ([50bbddb](https://github.com/vkarpov15/kareem/commit/50bbddb)) 391 | * Typo fix ([adea794](https://github.com/vkarpov15/kareem/commit/adea794)) 392 | 393 | 394 | 395 | 396 | ## 0.0.4 (2014-12-13) 397 | 398 | * Bump 0.0.4, run docs generation ([51a15fe](https://github.com/vkarpov15/kareem/commit/51a15fe)) 399 | * Use correct post parameters in wrap() ([9bb5da3](https://github.com/vkarpov15/kareem/commit/9bb5da3)) 400 | 401 | 402 | 403 | 404 | ## 0.0.3 (2014-12-12) 405 | 406 | * Add npm test script, fix small bug with args not getting passed through post ([49e3e68](https://github.com/vkarpov15/kareem/commit/49e3e68)) 407 | * Bump 0.0.3 ([65621d8](https://github.com/vkarpov15/kareem/commit/65621d8)) 408 | * Update readme ([901388b](https://github.com/vkarpov15/kareem/commit/901388b)) 409 | 410 | 411 | 412 | 413 | ## 0.0.2 (2014-12-12) 414 | 415 | * Add github repo and bump 0.0.2 ([59db8be](https://github.com/vkarpov15/kareem/commit/59db8be)) 416 | 417 | 418 | 419 | 420 | ## 0.0.1 (2014-12-12) 421 | 422 | * Add basic docs ([ad29ea4](https://github.com/vkarpov15/kareem/commit/ad29ea4)) 423 | * Add pre hooks ([2ffc356](https://github.com/vkarpov15/kareem/commit/2ffc356)) 424 | * Add wrap function ([68c540c](https://github.com/vkarpov15/kareem/commit/68c540c)) 425 | * Bump to version 0.0.1 ([a4bfd68](https://github.com/vkarpov15/kareem/commit/a4bfd68)) 426 | * Initial commit ([4002458](https://github.com/vkarpov15/kareem/commit/4002458)) 427 | * Initial deposit ([98fc489](https://github.com/vkarpov15/kareem/commit/98fc489)) 428 | * Post hooks ([395b67c](https://github.com/vkarpov15/kareem/commit/395b67c)) 429 | * Some basic setup work ([82df75e](https://github.com/vkarpov15/kareem/commit/82df75e)) 430 | * Support sync pre hooks ([1cc1b9f](https://github.com/vkarpov15/kareem/commit/1cc1b9f)) 431 | * Update package.json description ([978da18](https://github.com/vkarpov15/kareem/commit/978da18)) 432 | 433 | 434 | 435 | 436 | ## 2.2.5 (2018-09-24) 437 | 438 | 439 | 440 | 441 | 442 | ## 2.2.4 (2018-09-24) 443 | 444 | 445 | 446 | 447 | 448 | ## 2.2.3 (2018-09-24) 449 | 450 | * fix(filter): copy async pres correctly with `filter()` ([1b1ed8a](https://github.com/vkarpov15/kareem/commit/1b1ed8a)), closes [Automattic/mongoose#3054](https://github.com/Automattic/mongoose/issues/3054) 451 | * feat: add filter() function ([1f641f4](https://github.com/vkarpov15/kareem/commit/1f641f4)) 452 | * feat: support storing options on pre and post hooks ([59220b9](https://github.com/vkarpov15/kareem/commit/59220b9)) 453 | 454 | 455 | 456 | 457 | ## 2.2.3 (2018-09-10) 458 | 459 | * chore: release 2.2.3 ([af653a3](https://github.com/vkarpov15/kareem/commit/af653a3)) 460 | 461 | 462 | 463 | 464 | ## 2.2.2 (2018-09-10) 465 | 466 | * chore: release 2.2.2 ([3f0144d](https://github.com/vkarpov15/kareem/commit/3f0144d)) 467 | * fix: allow merge() to not clone ([e628d65](https://github.com/vkarpov15/kareem/commit/e628d65)) 468 | 469 | 470 | 471 | 472 | ## 2.2.1 (2018-06-05) 473 | 474 | * chore: release 2.2.1 ([4625a64](https://github.com/vkarpov15/kareem/commit/4625a64)) 475 | * chore: remove lockfile from git ([7f3e4e6](https://github.com/vkarpov15/kareem/commit/7f3e4e6)) 476 | * fix: handle numAsync correctly when merging ([fef8e7e](https://github.com/vkarpov15/kareem/commit/fef8e7e)) 477 | * test: repro issue with not copying numAsync ([952d9db](https://github.com/vkarpov15/kareem/commit/952d9db)) 478 | 479 | 480 | 481 | 482 | ## 2.2.0 (2018-06-05) 483 | 484 | * chore: release 2.2.0 ([ff9ad03](https://github.com/vkarpov15/kareem/commit/ff9ad03)) 485 | * fix: use maps instead of objects for _pres and _posts so `toString()` doesn't get reported as having ([55df303](https://github.com/vkarpov15/kareem/commit/55df303)), closes [Automattic/mongoose#6538](https://github.com/Automattic/mongoose/issues/6538) 486 | 487 | 488 | 489 | 490 | ## 2.1.0 (2018-05-16) 491 | 492 | * chore: release 2.1.0 ([ba5f1bc](https://github.com/vkarpov15/kareem/commit/ba5f1bc)) 493 | * feat: add option to check wrapped function return value for promises ([c9d7dd1](https://github.com/vkarpov15/kareem/commit/c9d7dd1)) 494 | * refactor: use const in wrap() ([0fc21f9](https://github.com/vkarpov15/kareem/commit/0fc21f9)) 495 | 496 | 497 | 498 | 499 | ## 2.0.7 (2018-04-28) 500 | 501 | * chore: release 2.0.7 ([0bf91e6](https://github.com/vkarpov15/kareem/commit/0bf91e6)) 502 | * feat: add `hasHooks()` ([225f18d](https://github.com/vkarpov15/kareem/commit/225f18d)), closes [Automattic/mongoose#6385](https://github.com/Automattic/mongoose/issues/6385) 503 | 504 | 505 | 506 | 507 | ## 2.0.6 (2018-03-22) 508 | 509 | * chore: release 2.0.6 ([f3d406b](https://github.com/vkarpov15/kareem/commit/f3d406b)) 510 | * fix(wrap): ensure fast path still wraps function in `nextTick()` for chaining ([7000494](https://github.com/vkarpov15/kareem/commit/7000494)), closes [Automattic/mongoose#6250](https://github.com/Automattic/mongoose/issues/6250) [dsanel/mongoose-delete#36](https://github.com/dsanel/mongoose-delete/issues/36) 511 | 512 | 513 | 514 | 515 | ## 2.0.5 (2018-02-22) 516 | 517 | * chore: release 2.0.5 ([3286612](https://github.com/vkarpov15/kareem/commit/3286612)) 518 | * perf(createWrapper): don't create wrapper if there are no hooks ([5afc5b9](https://github.com/vkarpov15/kareem/commit/5afc5b9)), closes [Automattic/mongoose#6126](https://github.com/Automattic/mongoose/issues/6126) 519 | 520 | 521 | 522 | 523 | ## 2.0.4 (2018-02-08) 524 | 525 | * chore: release 2.0.4 ([2ab0293](https://github.com/vkarpov15/kareem/commit/2ab0293)) 526 | 527 | 528 | 529 | 530 | ## 2.0.3 (2018-02-01) 531 | 532 | * chore: release 2.0.3 ([3c1abe5](https://github.com/vkarpov15/kareem/commit/3c1abe5)) 533 | * fix: use process.nextTick() re: Automattic/mongoose#6074 ([e5bfe33](https://github.com/vkarpov15/kareem/commit/e5bfe33)), closes [Automattic/mongoose#6074](https://github.com/Automattic/mongoose/issues/6074) 534 | 535 | 536 | 537 | 538 | ## 2.0.2 (2018-01-24) 539 | 540 | * chore: fix license ([a9d755c](https://github.com/vkarpov15/kareem/commit/a9d755c)), closes [#10](https://github.com/vkarpov15/kareem/issues/10) 541 | * chore: release 2.0.2 ([fe87ab6](https://github.com/vkarpov15/kareem/commit/fe87ab6)) 542 | 543 | 544 | 545 | 546 | ## 2.0.1 (2018-01-09) 547 | 548 | * chore: release 2.0.1 with lockfile bump ([09c44fb](https://github.com/vkarpov15/kareem/commit/09c44fb)) 549 | 550 | 551 | 552 | 553 | ## 2.0.0 (2018-01-09) 554 | 555 | * chore: bump marked re: security ([cc564a9](https://github.com/vkarpov15/kareem/commit/cc564a9)) 556 | * chore: release 2.0.0 ([f511d1c](https://github.com/vkarpov15/kareem/commit/f511d1c)) 557 | 558 | 559 | 560 | 561 | ## 2.0.0-rc5 (2017-12-23) 562 | 563 | * chore: fix build on node 4+5 ([6dac5a4](https://github.com/vkarpov15/kareem/commit/6dac5a4)) 564 | * chore: fix built on node 4 + 5 again ([434ef0a](https://github.com/vkarpov15/kareem/commit/434ef0a)) 565 | * chore: release 2.0.0-rc5 ([25a32ee](https://github.com/vkarpov15/kareem/commit/25a32ee)) 566 | 567 | 568 | 569 | 570 | ## 2.0.0-rc4 (2017-12-22) 571 | 572 | * chore: release 2.0.0-rc4 ([49fc083](https://github.com/vkarpov15/kareem/commit/49fc083)) 573 | * BREAKING CHANGE: deduplicate when merging hooks re: Automattic/mongoose#2945 ([d458573](https://github.com/vkarpov15/kareem/commit/d458573)), closes [Automattic/mongoose#2945](https://github.com/Automattic/mongoose/issues/2945) 574 | 575 | 576 | 577 | 578 | ## 2.0.0-rc3 (2017-12-22) 579 | 580 | * chore: release 2.0.0-rc3 ([adaaa00](https://github.com/vkarpov15/kareem/commit/adaaa00)) 581 | * feat: support returning promises from middleware functions ([05b4480](https://github.com/vkarpov15/kareem/commit/05b4480)), closes [Automattic/mongoose#3779](https://github.com/Automattic/mongoose/issues/3779) 582 | 583 | 584 | 585 | 586 | ## 2.0.0-rc2 (2017-12-21) 587 | 588 | * chore: release 2.0.0-rc2 ([76325fa](https://github.com/vkarpov15/kareem/commit/76325fa)) 589 | * fix: ensure next() and done() run in next tick ([6c20684](https://github.com/vkarpov15/kareem/commit/6c20684)) 590 | 591 | 592 | 593 | 594 | ## 2.0.0-rc1 (2017-12-21) 595 | 596 | * chore: improve test coverage re: Automattic/mongoose#3232 ([7b45cf0](https://github.com/vkarpov15/kareem/commit/7b45cf0)), closes [Automattic/mongoose#3232](https://github.com/Automattic/mongoose/issues/3232) 597 | * chore: release 2.0.0-rc1 ([9b83f52](https://github.com/vkarpov15/kareem/commit/9b83f52)) 598 | * BREAKING CHANGE: report sync exceptions as errors, only allow calling next() and done() once ([674adcc](https://github.com/vkarpov15/kareem/commit/674adcc)), closes [Automattic/mongoose#3483](https://github.com/Automattic/mongoose/issues/3483) 599 | 600 | 601 | 602 | 603 | ## 2.0.0-rc0 (2017-12-17) 604 | 605 | * chore: release 2.0.0-rc0 ([16b44b5](https://github.com/vkarpov15/kareem/commit/16b44b5)) 606 | * BREAKING CHANGE: drop support for node < 4 ([9cbb8c7](https://github.com/vkarpov15/kareem/commit/9cbb8c7)) 607 | * BREAKING CHANGE: remove useLegacyPost and add several new features ([6dd8531](https://github.com/vkarpov15/kareem/commit/6dd8531)), closes [Automattic/mongoose#3232](https://github.com/Automattic/mongoose/issues/3232) 608 | 609 | 610 | 611 | 612 | ## 1.5.0 (2017-07-20) 613 | 614 | * chore: release 1.5.0 ([9c491a0](https://github.com/vkarpov15/kareem/commit/9c491a0)) 615 | * fix: improve post error handlers results ([9928dd5](https://github.com/vkarpov15/kareem/commit/9928dd5)), closes [Automattic/mongoose#5466](https://github.com/Automattic/mongoose/issues/5466) 616 | 617 | 618 | 619 | 620 | ## 1.4.2 (2017-07-06) 621 | 622 | * chore: release 1.4.2 ([8d14ac5](https://github.com/vkarpov15/kareem/commit/8d14ac5)) 623 | * fix: correct args re: Automattic/mongoose#5405 ([3f28ae6](https://github.com/vkarpov15/kareem/commit/3f28ae6)), closes [Automattic/mongoose#5405](https://github.com/Automattic/mongoose/issues/5405) 624 | 625 | 626 | 627 | 628 | ## 1.4.1 (2017-04-25) 629 | 630 | * chore: release 1.4.1 ([5ecf0c2](https://github.com/vkarpov15/kareem/commit/5ecf0c2)) 631 | * fix: handle numAsyncPres with clone() ([c72e857](https://github.com/vkarpov15/kareem/commit/c72e857)), closes [#8](https://github.com/vkarpov15/kareem/issues/8) 632 | * test: repro #8 ([9b4d6b2](https://github.com/vkarpov15/kareem/commit/9b4d6b2)), closes [#8](https://github.com/vkarpov15/kareem/issues/8) 633 | 634 | 635 | 636 | 637 | ## 1.4.0 (2017-04-19) 638 | 639 | * chore: release 1.4.0 ([101c5f5](https://github.com/vkarpov15/kareem/commit/101c5f5)) 640 | * feat: add merge() function ([285325e](https://github.com/vkarpov15/kareem/commit/285325e)) 641 | 642 | 643 | 644 | 645 | ## 1.3.0 (2017-03-26) 646 | 647 | * chore: release 1.3.0 ([f3a9e50](https://github.com/vkarpov15/kareem/commit/f3a9e50)) 648 | * feat: pass function args to execPre ([4dd466d](https://github.com/vkarpov15/kareem/commit/4dd466d)) 649 | 650 | 651 | 652 | 653 | ## 1.2.1 (2017-02-03) 654 | 655 | * chore: release 1.2.1 ([d97081f](https://github.com/vkarpov15/kareem/commit/d97081f)) 656 | * fix: filter out _kareemIgnored args for error handlers re: Automattic/mongoose#4925 ([ddc7aeb](https://github.com/vkarpov15/kareem/commit/ddc7aeb)), closes [Automattic/mongoose#4925](https://github.com/Automattic/mongoose/issues/4925) 657 | * fix: make error handlers handle errors in pre hooks ([af38033](https://github.com/vkarpov15/kareem/commit/af38033)), closes [Automattic/mongoose#4927](https://github.com/Automattic/mongoose/issues/4927) 658 | 659 | 660 | 661 | 662 | ## 1.2.0 (2017-01-02) 663 | 664 | * chore: release 1.2.0 ([033225c](https://github.com/vkarpov15/kareem/commit/033225c)) 665 | * chore: upgrade deps ([f9e9a09](https://github.com/vkarpov15/kareem/commit/f9e9a09)) 666 | * feat: add _kareemIgnore re: Automattic/mongoose#4836 ([7957771](https://github.com/vkarpov15/kareem/commit/7957771)), closes [Automattic/mongoose#4836](https://github.com/Automattic/mongoose/issues/4836) 667 | 668 | 669 | 670 | 671 | ## 1.1.5 (2016-12-13) 672 | 673 | * chore: release 1.1.5 ([1a9f684](https://github.com/vkarpov15/kareem/commit/1a9f684)) 674 | * fix: correct field name ([04a0e9d](https://github.com/vkarpov15/kareem/commit/04a0e9d)) 675 | 676 | 677 | 678 | 679 | ## 1.1.4 (2016-12-09) 680 | 681 | * chore: release 1.1.4 ([ece401c](https://github.com/vkarpov15/kareem/commit/ece401c)) 682 | * chore: run tests on node 6 ([e0cb1cb](https://github.com/vkarpov15/kareem/commit/e0cb1cb)) 683 | * fix: only copy own properties in clone() ([dfe28ce](https://github.com/vkarpov15/kareem/commit/dfe28ce)), closes [#7](https://github.com/vkarpov15/kareem/issues/7) 684 | 685 | 686 | 687 | 688 | ## 1.1.3 (2016-06-27) 689 | 690 | * chore: release 1.1.3 ([87171c8](https://github.com/vkarpov15/kareem/commit/87171c8)) 691 | * fix: couple more issues with arg processing ([c65f523](https://github.com/vkarpov15/kareem/commit/c65f523)) 692 | 693 | 694 | 695 | 696 | ## 1.1.2 (2016-06-27) 697 | 698 | * chore: release 1.1.2 ([8e102b6](https://github.com/vkarpov15/kareem/commit/8e102b6)) 699 | * fix: add early return ([4feda4e](https://github.com/vkarpov15/kareem/commit/4feda4e)) 700 | 701 | 702 | 703 | 704 | ## 1.1.1 (2016-06-27) 705 | 706 | * chore: release 1.1.1 ([8bb3050](https://github.com/vkarpov15/kareem/commit/8bb3050)) 707 | * fix: skip error handlers if no error ([0eb3a44](https://github.com/vkarpov15/kareem/commit/0eb3a44)) 708 | 709 | 710 | 711 | 712 | ## 1.1.0 (2016-05-11) 713 | 714 | * chore: release 1.1.0 ([85332d9](https://github.com/vkarpov15/kareem/commit/85332d9)) 715 | * chore: test on node 4 and node 5 ([1faefa1](https://github.com/vkarpov15/kareem/commit/1faefa1)) 716 | * 100% coverage again ([c9aee4e](https://github.com/vkarpov15/kareem/commit/c9aee4e)) 717 | * add support for error post hooks ([d378113](https://github.com/vkarpov15/kareem/commit/d378113)) 718 | * basic setup for sync hooks #4 ([55aa081](https://github.com/vkarpov15/kareem/commit/55aa081)), closes [#4](https://github.com/vkarpov15/kareem/issues/4) 719 | * proof of concept for error handlers ([e4a07d9](https://github.com/vkarpov15/kareem/commit/e4a07d9)) 720 | * refactor out handleWrapError helper ([b19af38](https://github.com/vkarpov15/kareem/commit/b19af38)) 721 | 722 | 723 | 724 | 725 | ## 1.0.1 (2015-05-10) 726 | 727 | * Fix #1 ([de60dc6](https://github.com/vkarpov15/kareem/commit/de60dc6)), closes [#1](https://github.com/vkarpov15/kareem/issues/1) 728 | * release 1.0.1 ([6971088](https://github.com/vkarpov15/kareem/commit/6971088)) 729 | * Run tests on iojs in travis ([adcd201](https://github.com/vkarpov15/kareem/commit/adcd201)) 730 | * support legacy post hook behavior in wrap() ([23fa74c](https://github.com/vkarpov15/kareem/commit/23fa74c)) 731 | * Use node 0.12 in travis ([834689d](https://github.com/vkarpov15/kareem/commit/834689d)) 732 | 733 | 734 | 735 | 736 | ## 1.0.0 (2015-01-28) 737 | 738 | * Tag 1.0.0 ([4c5a35a](https://github.com/vkarpov15/kareem/commit/4c5a35a)) 739 | 740 | 741 | 742 | 743 | ## 0.0.8 (2015-01-27) 744 | 745 | * Add clone function ([688bba7](https://github.com/vkarpov15/kareem/commit/688bba7)) 746 | * Add jscs for style checking ([5c93149](https://github.com/vkarpov15/kareem/commit/5c93149)) 747 | * Bump 0.0.8 ([03c0d2f](https://github.com/vkarpov15/kareem/commit/03c0d2f)) 748 | * Fix jscs config, add gulp rules ([9989abf](https://github.com/vkarpov15/kareem/commit/9989abf)) 749 | * fix Makefile typo ([1f7e61a](https://github.com/vkarpov15/kareem/commit/1f7e61a)) 750 | 751 | 752 | 753 | 754 | ## 0.0.7 (2015-01-04) 755 | 756 | * Bump 0.0.7 ([98ef173](https://github.com/vkarpov15/kareem/commit/98ef173)) 757 | * fix LearnBoost/mongoose#2553 - use null instead of undefined for err ([9157b48](https://github.com/vkarpov15/kareem/commit/9157b48)), closes [LearnBoost/mongoose#2553](https://github.com/LearnBoost/mongoose/issues/2553) 758 | * Regenerate docs ([2331cdf](https://github.com/vkarpov15/kareem/commit/2331cdf)) 759 | 760 | 761 | 762 | 763 | ## 0.0.6 (2015-01-01) 764 | 765 | * Update docs and bump 0.0.6 ([92c12a7](https://github.com/vkarpov15/kareem/commit/92c12a7)) 766 | 767 | 768 | 769 | 770 | ## 0.0.5 (2015-01-01) 771 | 772 | * Add coverage rule to Makefile ([825a91c](https://github.com/vkarpov15/kareem/commit/825a91c)) 773 | * Add coveralls to README ([fb52369](https://github.com/vkarpov15/kareem/commit/fb52369)) 774 | * Add coveralls to travis ([93f6f15](https://github.com/vkarpov15/kareem/commit/93f6f15)) 775 | * Add createWrapper() function ([ea77741](https://github.com/vkarpov15/kareem/commit/ea77741)) 776 | * Add istanbul code coverage ([6eceeef](https://github.com/vkarpov15/kareem/commit/6eceeef)) 777 | * Add some more comments for examples ([c5b0c6f](https://github.com/vkarpov15/kareem/commit/c5b0c6f)) 778 | * Add travis ([e6dcb06](https://github.com/vkarpov15/kareem/commit/e6dcb06)) 779 | * Add travis badge to docs ([ad8c9b3](https://github.com/vkarpov15/kareem/commit/ad8c9b3)) 780 | * Add wrap() tests, 100% coverage ([6945be4](https://github.com/vkarpov15/kareem/commit/6945be4)) 781 | * Better test coverage for execPost ([d9ad539](https://github.com/vkarpov15/kareem/commit/d9ad539)) 782 | * Bump 0.0.5 ([69875b1](https://github.com/vkarpov15/kareem/commit/69875b1)) 783 | * Docs fix ([15b7098](https://github.com/vkarpov15/kareem/commit/15b7098)) 784 | * Fix silly mistake in docs generation ([50373eb](https://github.com/vkarpov15/kareem/commit/50373eb)) 785 | * Fix typo in readme ([fec4925](https://github.com/vkarpov15/kareem/commit/fec4925)) 786 | * Linkify travis badge ([92b25fe](https://github.com/vkarpov15/kareem/commit/92b25fe)) 787 | * Make travis run coverage ([747157b](https://github.com/vkarpov15/kareem/commit/747157b)) 788 | * Move travis status badge ([d52e89b](https://github.com/vkarpov15/kareem/commit/d52e89b)) 789 | * Quick fix for coverage ([50bbddb](https://github.com/vkarpov15/kareem/commit/50bbddb)) 790 | * Typo fix ([adea794](https://github.com/vkarpov15/kareem/commit/adea794)) 791 | 792 | 793 | 794 | 795 | ## 0.0.4 (2014-12-13) 796 | 797 | * Bump 0.0.4, run docs generation ([51a15fe](https://github.com/vkarpov15/kareem/commit/51a15fe)) 798 | * Use correct post parameters in wrap() ([9bb5da3](https://github.com/vkarpov15/kareem/commit/9bb5da3)) 799 | 800 | 801 | 802 | 803 | ## 0.0.3 (2014-12-12) 804 | 805 | * Add npm test script, fix small bug with args not getting passed through post ([49e3e68](https://github.com/vkarpov15/kareem/commit/49e3e68)) 806 | * Bump 0.0.3 ([65621d8](https://github.com/vkarpov15/kareem/commit/65621d8)) 807 | * Update readme ([901388b](https://github.com/vkarpov15/kareem/commit/901388b)) 808 | 809 | 810 | 811 | 812 | ## 0.0.2 (2014-12-12) 813 | 814 | * Add github repo and bump 0.0.2 ([59db8be](https://github.com/vkarpov15/kareem/commit/59db8be)) 815 | 816 | 817 | 818 | 819 | ## 0.0.1 (2014-12-12) 820 | 821 | * Add basic docs ([ad29ea4](https://github.com/vkarpov15/kareem/commit/ad29ea4)) 822 | * Add pre hooks ([2ffc356](https://github.com/vkarpov15/kareem/commit/2ffc356)) 823 | * Add wrap function ([68c540c](https://github.com/vkarpov15/kareem/commit/68c540c)) 824 | * Bump to version 0.0.1 ([a4bfd68](https://github.com/vkarpov15/kareem/commit/a4bfd68)) 825 | * Initial commit ([4002458](https://github.com/vkarpov15/kareem/commit/4002458)) 826 | * Initial deposit ([98fc489](https://github.com/vkarpov15/kareem/commit/98fc489)) 827 | * Post hooks ([395b67c](https://github.com/vkarpov15/kareem/commit/395b67c)) 828 | * Some basic setup work ([82df75e](https://github.com/vkarpov15/kareem/commit/82df75e)) 829 | * Support sync pre hooks ([1cc1b9f](https://github.com/vkarpov15/kareem/commit/1cc1b9f)) 830 | * Update package.json description ([978da18](https://github.com/vkarpov15/kareem/commit/978da18)) 831 | --------------------------------------------------------------------------------