kareem 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +133 -161
  3. package/index.js +14 -4
  4. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ <a name="3.1.0"></a>
4
+ ## 3.1.0 (2026-01-12)
5
+
6
+ * feat(exec): add filter option to allow executing hooks based on a filter function #43
7
+
3
8
  <a name="3.0.0"></a>
4
9
  ## 3.0.0 (2025-11-18)
5
10
 
package/README.md CHANGED
@@ -15,37 +15,28 @@ Named for the NBA's 2nd all-time leading scorer Kareem Abdul-Jabbar, known for h
15
15
 
16
16
  ## pre hooks
17
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.
18
+ NOTE: this file has some empty comment lines to workaround https://github.com/vkarpov15/acquit/issues/30
24
19
 
25
20
  ### It runs without any hooks specified
26
21
 
27
22
  ```javascript
28
- hooks.execPre('cook', null, function() {
29
- // ...
30
- });
23
+ await hooks.execPre('cook', null);
31
24
  ```
32
25
 
33
26
  ### It runs basic serial pre hooks
34
27
 
35
- pre hook functions take one parameter, a "done" function that you execute
36
- when your pre hook is finished.
28
+ pre hook functions can return a promise that resolves when finished.
37
29
 
38
30
  ```javascript
39
31
  let count = 0;
40
32
 
41
- hooks.pre('cook', function(done) {
33
+ hooks.pre('cook', function() {
42
34
  ++count;
43
- done();
35
+ return Promise.resolve();
44
36
  });
45
37
 
46
- hooks.execPre('cook', null, function() {
47
- assert.equal(1, count);
48
- });
38
+ await hooks.execPre('cook', null);
39
+ assert.equal(1, count);
49
40
  ```
50
41
 
51
42
  ### It can run multiple pre hooks
@@ -54,20 +45,19 @@ hooks.execPre('cook', null, function() {
54
45
  let count1 = 0;
55
46
  let count2 = 0;
56
47
 
57
- hooks.pre('cook', function(done) {
48
+ hooks.pre('cook', function() {
58
49
  ++count1;
59
- done();
50
+ return Promise.resolve();
60
51
  });
61
52
 
62
- hooks.pre('cook', function(done) {
53
+ hooks.pre('cook', function() {
63
54
  ++count2;
64
- done();
55
+ return Promise.resolve();
65
56
  });
66
57
 
67
- hooks.execPre('cook', null, function() {
68
- assert.equal(1, count1);
69
- assert.equal(1, count2);
70
- });
58
+ await hooks.execPre('cook', null);
59
+ assert.equal(1, count1);
60
+ assert.equal(1, count2);
71
61
  ```
72
62
 
73
63
  ### It can run fully synchronous pre hooks
@@ -87,11 +77,9 @@ hooks.pre('cook', function() {
87
77
  ++count2;
88
78
  });
89
79
 
90
- hooks.execPre('cook', null, function(error) {
91
- assert.equal(null, error);
92
- assert.equal(1, count1);
93
- assert.equal(1, count2);
94
- });
80
+ await hooks.execPre('cook', null);
81
+ assert.equal(1, count1);
82
+ assert.equal(1, count2);
95
83
  ```
96
84
 
97
85
  ### It properly attaches context to pre hooks
@@ -99,63 +87,20 @@ hooks.execPre('cook', null, function(error) {
99
87
  Pre save hook functions are bound to the second parameter to `execPre()`
100
88
 
101
89
  ```javascript
102
- hooks.pre('cook', function(done) {
90
+ hooks.pre('cook', function() {
103
91
  this.bacon = 3;
104
- done();
105
92
  });
106
93
 
107
- hooks.pre('cook', function(done) {
94
+ hooks.pre('cook', function() {
108
95
  this.eggs = 4;
109
- done();
110
96
  });
111
97
 
112
98
  const obj = { bacon: 0, eggs: 0 };
113
99
 
114
100
  // 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
- });
101
+ await hooks.execPre('cook', obj);
102
+ assert.equal(3, obj.bacon);
103
+ assert.equal(4, obj.eggs);
159
104
  ```
160
105
 
161
106
  ### It supports returning a promise
@@ -176,9 +121,32 @@ hooks.pre('cook', function() {
176
121
 
177
122
  const obj = { bacon: 0 };
178
123
 
179
- hooks.execPre('cook', obj, function() {
180
- assert.equal(3, obj.bacon);
124
+ await hooks.execPre('cook', obj);
125
+ assert.equal(3, obj.bacon);
126
+ ```
127
+
128
+ ### It supports filtering which hooks to run
129
+
130
+ You can pass a `filter` option to `execPre()` to select which hooks
131
+ to run. The filter function receives each hook object and should return
132
+ `true` to run the hook or `false` to skip it.
133
+
134
+ ```javascript
135
+ const execed = [];
136
+
137
+ const fn1 = function() { execed.push('first'); };
138
+ fn1.skipMe = true;
139
+ hooks.pre('cook', fn1);
140
+
141
+ const fn2 = function() { execed.push('second'); };
142
+ hooks.pre('cook', fn2);
143
+
144
+ // Only runs fn2, skips fn1 because fn1.skipMe is true
145
+ await hooks.execPre('cook', null, [], {
146
+ filter: hook => !hook.fn.skipMe
181
147
  });
148
+
149
+ assert.deepStrictEqual(execed, ['second']);
182
150
  ```
183
151
 
184
152
  ## post hooks
@@ -186,27 +154,22 @@ hooks.execPre('cook', obj, function() {
186
154
  ### It runs without any hooks specified
187
155
 
188
156
  ```javascript
189
- hooks.execPost('cook', null, [1], function(error, eggs) {
190
- assert.ifError(error);
191
- assert.equal(1, eggs);
192
- done();
193
- });
157
+ const [eggs] = await hooks.execPost('cook', null, [1]);
158
+ assert.equal(eggs, 1);
194
159
  ```
195
160
 
196
161
  ### It executes with parameters passed in
197
162
 
198
163
  ```javascript
199
164
  hooks.post('cook', function(eggs, bacon, callback) {
200
- assert.equal(1, eggs);
201
- assert.equal(2, bacon);
165
+ assert.equal(eggs, 1);
166
+ assert.equal(bacon, 2);
202
167
  callback();
203
168
  });
204
169
 
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
- });
170
+ const [eggs, bacon] = await hooks.execPost('cook', null, [1, 2]);
171
+ assert.equal(eggs, 1);
172
+ assert.equal(bacon, 2);
210
173
  ```
211
174
 
212
175
  ### It can use synchronous post hooks
@@ -216,25 +179,23 @@ const execed = {};
216
179
 
217
180
  hooks.post('cook', function(eggs, bacon) {
218
181
  execed.first = true;
219
- assert.equal(1, eggs);
220
- assert.equal(2, bacon);
182
+ assert.equal(eggs, 1);
183
+ assert.equal(bacon, 2);
221
184
  });
222
185
 
223
186
  hooks.post('cook', function(eggs, bacon, callback) {
224
187
  execed.second = true;
225
- assert.equal(1, eggs);
226
- assert.equal(2, bacon);
188
+ assert.equal(eggs, 1);
189
+ assert.equal(bacon, 2);
227
190
  callback();
228
191
  });
229
192
 
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
- });
193
+ const [eggs, bacon] = await hooks.execPost('cook', null, [1, 2]);
194
+ assert.equal(Object.keys(execed).length, 2);
195
+ assert.ok(execed.first);
196
+ assert.ok(execed.second);
197
+ assert.equal(eggs, 1);
198
+ assert.equal(bacon, 2);
238
199
  ```
239
200
 
240
201
  ### It supports returning a promise
@@ -255,9 +216,32 @@ hooks.post('cook', function() {
255
216
 
256
217
  const obj = { bacon: 0 };
257
218
 
258
- hooks.execPost('cook', obj, obj, function() {
259
- assert.equal(obj.bacon, 3);
219
+ await hooks.execPost('cook', obj, [obj]);
220
+ assert.equal(obj.bacon, 3);
221
+ ```
222
+
223
+ ### It supports filtering which hooks to run
224
+
225
+ You can pass a `filter` option to `execPost()` to select which hooks
226
+ to run. The filter function receives each hook object and should return
227
+ `true` to run the hook or `false` to skip it.
228
+
229
+ ```javascript
230
+ const execed = [];
231
+
232
+ const fn1 = function() { execed.push('first'); };
233
+ fn1.skipMe = true;
234
+ hooks.post('cook', fn1);
235
+
236
+ const fn2 = function() { execed.push('second'); };
237
+ hooks.post('cook', fn2);
238
+
239
+ // Only runs fn2, skips fn1 because fn1.skipMe is true
240
+ await hooks.execPost('cook', null, [], {
241
+ filter: hook => !hook.fn.skipMe
260
242
  });
243
+
244
+ assert.deepStrictEqual(execed, ['second']);
261
245
  ```
262
246
 
263
247
  ## wrap()
@@ -265,26 +249,23 @@ hooks.execPost('cook', obj, obj, function() {
265
249
  ### It wraps pre and post calls into one call
266
250
 
267
251
  ```javascript
268
- hooks.pre('cook', true, function(next, done) {
269
- this.bacon = 3;
270
- next();
271
- setTimeout(function() {
272
- done();
273
- }, 5);
252
+ hooks.pre('cook', function() {
253
+ return new Promise(resolve => {
254
+ this.bacon = 3;
255
+ setTimeout(() => {
256
+ resolve();
257
+ }, 5);
258
+ });
274
259
  });
275
260
 
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);
261
+ hooks.pre('cook', function() {
262
+ this.eggs = 4;
263
+ return Promise.resolve();
283
264
  });
284
265
 
285
- hooks.pre('cook', function(next) {
266
+ hooks.pre('cook', function() {
286
267
  this.waffles = false;
287
- next();
268
+ return Promise.resolve();
288
269
  });
289
270
 
290
271
  hooks.post('cook', function(obj) {
@@ -294,28 +275,24 @@ hooks.post('cook', function(obj) {
294
275
  const obj = { bacon: 0, eggs: 0 };
295
276
 
296
277
  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
278
 
308
- hooks.wrap(
279
+ const result = await hooks.wrap(
309
280
  '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);
281
+ function(o) {
282
+ assert.equal(obj.bacon, 3);
283
+ assert.equal(obj.eggs, 4);
284
+ assert.equal(obj.waffles, false);
285
+ assert.equal(obj.tofu, undefined);
286
+ return o;
316
287
  },
317
288
  obj,
318
289
  args);
290
+
291
+ assert.equal(obj.bacon, 3);
292
+ assert.equal(obj.eggs, 4);
293
+ assert.equal(obj.waffles, false);
294
+ assert.equal(obj.tofu, 'no');
295
+ assert.equal(result, obj);
319
296
  ```
320
297
 
321
298
  ## createWrapper()
@@ -323,26 +300,23 @@ hooks.wrap(
323
300
  ### It wraps wrap() into a callable function
324
301
 
325
302
  ```javascript
326
- hooks.pre('cook', true, function(next, done) {
303
+ hooks.pre('cook', function() {
327
304
  this.bacon = 3;
328
- next();
329
- setTimeout(function() {
330
- done();
331
- }, 5);
305
+ return Promise.resolve();
332
306
  });
333
307
 
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);
308
+ hooks.pre('cook', function() {
309
+ return new Promise(resolve => {
310
+ this.eggs = 4;
311
+ setTimeout(function() {
312
+ resolve();
313
+ }, 10);
314
+ });
341
315
  });
342
316
 
343
- hooks.pre('cook', function(next) {
317
+ hooks.pre('cook', function() {
344
318
  this.waffles = false;
345
- next();
319
+ return Promise.resolve();
346
320
  });
347
321
 
348
322
  hooks.post('cook', function(obj) {
@@ -353,24 +327,22 @@ const obj = { bacon: 0, eggs: 0 };
353
327
 
354
328
  const cook = hooks.createWrapper(
355
329
  'cook',
356
- function(o, callback) {
330
+ function(o) {
357
331
  assert.equal(3, obj.bacon);
358
332
  assert.equal(4, obj.eggs);
359
333
  assert.equal(false, obj.waffles);
360
334
  assert.equal(undefined, obj.tofu);
361
- callback(null, o);
335
+ return o;
362
336
  },
363
337
  obj);
364
338
 
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);
339
+ const result = await cook(obj);
340
+ assert.equal(obj.bacon, 3);
341
+ assert.equal(obj.eggs, 4);
342
+ assert.equal(obj.waffles, false);
343
+ assert.equal(obj.tofu, 'no');
371
344
 
372
- assert.equal(obj, result);
373
- });
345
+ assert.equal(result, obj);
374
346
  ```
375
347
 
376
348
  ## clone()
package/index.js CHANGED
@@ -37,10 +37,15 @@ Kareem.overwriteArguments = function overwriteArguments() {
37
37
  * @param {String} name The hook name to execute
38
38
  * @param {*} context Overwrite the "this" for the hook
39
39
  * @param {Array} args arguments passed to the pre hooks
40
+ * @param {Object} [options] Optional options
41
+ * @param {Function} [options.filter] Filter function to select which hooks to run
40
42
  * @returns {Array} The potentially modified arguments
41
43
  */
42
- Kareem.prototype.execPre = async function execPre(name, context, args) {
43
- const pres = this._pres.get(name) || [];
44
+ Kareem.prototype.execPre = async function execPre(name, context, args, options) {
45
+ let pres = this._pres.get(name) || [];
46
+ if (options?.filter) {
47
+ pres = pres.filter(options.filter);
48
+ }
44
49
  const numPres = pres.length;
45
50
  let $args = args;
46
51
  let skipWrappedFunction = null;
@@ -116,11 +121,16 @@ Kareem.prototype.execPreSync = function(name, context, args) {
116
121
  * @param {String} name The hook name to execute
117
122
  * @param {*} context Overwrite the "this" for the hook
118
123
  * @param {Array} args Apply custom arguments to the hook
119
- * @param {*} options Optional options or directly the callback
124
+ * @param {Object} [options] Optional options
125
+ * @param {Error} [options.error] Error to pass to error-handling middleware
126
+ * @param {Function} [options.filter] Filter function to select which hooks to run
120
127
  * @returns {void}
121
128
  */
122
129
  Kareem.prototype.execPost = async function execPost(name, context, args, options) {
123
- const posts = this._posts.get(name) || [];
130
+ let posts = this._posts.get(name) || [];
131
+ if (options?.filter) {
132
+ posts = posts.filter(options.filter);
133
+ }
124
134
  const numPosts = posts.length;
125
135
 
126
136
  let firstError = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kareem",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "Next-generation take on pre/post function hooks",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -17,7 +17,7 @@
17
17
  "acquit": "1.x",
18
18
  "acquit-ignore": "0.2.x",
19
19
  "eslint": "8.20.0",
20
- "mocha": "9.2.0",
20
+ "mocha": "11.x",
21
21
  "nyc": "15.1.0"
22
22
  },
23
23
  "author": "Valeri Karpov <val@karpov.io>",