hookified 2.1.1 → 3.0.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.
- package/README.md +129 -2
- package/dist/browser/index.global.js +949 -890
- package/dist/browser/index.global.js.map +1 -1
- package/dist/browser/index.js +934 -886
- package/dist/browser/index.js.map +1 -1
- package/dist/node/index.cjs +900 -912
- package/dist/node/index.d.cts +730 -631
- package/dist/node/index.d.ts +730 -631
- package/dist/node/index.js +895 -882
- package/package.json +31 -28
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# Features
|
|
13
13
|
- Simple replacement for EventEmitter
|
|
14
14
|
- Async / Sync Middleware Hooks for Your Methods
|
|
15
|
-
- ESM / CJS with Types
|
|
15
|
+
- ESM / CJS with Types
|
|
16
16
|
- Browser Support and Delivered via CDN
|
|
17
17
|
- Ability to throw errors in hooks
|
|
18
18
|
- Ability to pass in a logger (such as Pino) for errors
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
- Deprecation warnings for hooks with `deprecatedHooks`
|
|
21
21
|
- Control deprecated hook execution with `allowDeprecated`
|
|
22
22
|
- WaterfallHook for sequential data transformation pipelines
|
|
23
|
+
- ParallelHook for concurrent fan-out execution with collected results
|
|
23
24
|
- No package dependencies and only 250KB in size
|
|
24
25
|
- Fast and Efficient with [Benchmarks](#benchmarks)
|
|
25
26
|
- Maintained on a regular basis!
|
|
@@ -27,11 +28,13 @@
|
|
|
27
28
|
# Table of Contents
|
|
28
29
|
- [Installation](#installation)
|
|
29
30
|
- [Usage](#usage)
|
|
31
|
+
- [Migrating from v2 to v3](#migrating-from-v2-to-v3)
|
|
30
32
|
- [Migrating from v1 to v2](#migrating-from-v1-to-v2)
|
|
31
33
|
- [Using it in the Browser](#using-it-in-the-browser)
|
|
32
34
|
- [Hooks](#hooks)
|
|
33
35
|
- [Standard Hook](#standard-hook)
|
|
34
|
-
- [Waterfall Hook](#
|
|
36
|
+
- [Waterfall Hook](#waterfall-hook)
|
|
37
|
+
- [Parallel Hook](#parallel-hook)
|
|
35
38
|
- [API - Hooks](#api---hooks)
|
|
36
39
|
- [.allowDeprecated](#allowdeprecated)
|
|
37
40
|
- [.deprecatedHooks](#deprecatedhooks)
|
|
@@ -342,6 +345,114 @@ wh.removeHook(myHook); // returns true
|
|
|
342
345
|
console.log(wh.hooks.length); // 0
|
|
343
346
|
```
|
|
344
347
|
|
|
348
|
+
## Parallel Hook
|
|
349
|
+
|
|
350
|
+
The `ParallelHook` class fans a single invocation out to many registered hook functions concurrently via `Promise.allSettled`, then calls a final handler with the aggregated outcomes — including failures. Unlike `WaterfallHook`, hooks do not see each other's results: every hook receives the same `initialArgs` and runs in parallel. It implements the `IHook` interface, so it integrates directly with `Hookified.onHook()`, and the final handler still fires whether the hook is invoked directly or through `Hookified.hook()`.
|
|
351
|
+
|
|
352
|
+
Per-hook functions receive a `ParallelHookContext`:
|
|
353
|
+
|
|
354
|
+
| Property | Type | Description |
|
|
355
|
+
|----------|------|-------------|
|
|
356
|
+
| `initialArgs` | `any` | The original arguments passed to `handler()`. Single argument stays as-is; multiple arguments become an array. |
|
|
357
|
+
|
|
358
|
+
The final handler receives a `ParallelHookFinalContext`:
|
|
359
|
+
|
|
360
|
+
| Property | Type | Description |
|
|
361
|
+
|----------|------|-------------|
|
|
362
|
+
| `initialArgs` | `any` | Same value passed to every hook. |
|
|
363
|
+
| `results` | `Map<ParallelHookFn, ParallelHookResult>` | One entry per registered hook, keyed by the hook function reference for direct lookup. Iteration order matches registration order. |
|
|
364
|
+
|
|
365
|
+
Each `ParallelHookResult` value is a discriminated union — failures are reported, not thrown:
|
|
366
|
+
|
|
367
|
+
| Field | Type | Description |
|
|
368
|
+
|-------|------|-------------|
|
|
369
|
+
| `status` | `"fulfilled" \| "rejected"` | Discriminator. |
|
|
370
|
+
| `result` | `TResult` | Present when `status === "fulfilled"`. The value the hook returned. Defaults to `any`; tighten via the `ParallelHook<TArgs, TResult>` generic. |
|
|
371
|
+
| `reason` | `unknown` | Present when `status === "rejected"`. The error or value the hook threw. Stays `unknown` regardless of the result generic, since errors in JS aren't typed. |
|
|
372
|
+
|
|
373
|
+
**Basic usage with `Hookified`:**
|
|
374
|
+
|
|
375
|
+
```javascript
|
|
376
|
+
import { Hookified, ParallelHook } from 'hookified';
|
|
377
|
+
|
|
378
|
+
class MyClass extends Hookified {
|
|
379
|
+
constructor() { super(); }
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const myClass = new MyClass();
|
|
383
|
+
|
|
384
|
+
const sendEmailHook = async ({ initialArgs }) => sendEmail(initialArgs);
|
|
385
|
+
const sendSlackHook = async ({ initialArgs }) => sendSlack(initialArgs);
|
|
386
|
+
const sendWebhookHook = async ({ initialArgs }) => sendWebhook(initialArgs);
|
|
387
|
+
|
|
388
|
+
const ph = new ParallelHook('notify', ({ results }) => {
|
|
389
|
+
// Look up a specific hook's outcome by reference
|
|
390
|
+
const emailOutcome = results.get(sendEmailHook);
|
|
391
|
+
if (emailOutcome?.status === 'rejected') {
|
|
392
|
+
console.error('email failed:', emailOutcome.reason);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Or iterate every result in registration order
|
|
396
|
+
for (const [hook, r] of results) {
|
|
397
|
+
if (r.status === 'fulfilled') {
|
|
398
|
+
console.log('ok:', r.result);
|
|
399
|
+
} else {
|
|
400
|
+
console.error('failed:', r.reason);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
ph.addHook(sendEmailHook);
|
|
406
|
+
ph.addHook(sendSlackHook);
|
|
407
|
+
ph.addHook(sendWebhookHook);
|
|
408
|
+
|
|
409
|
+
// Register with Hookified — works because ParallelHook implements IHook
|
|
410
|
+
myClass.onHook(ph);
|
|
411
|
+
|
|
412
|
+
// All three notification hooks fire concurrently, then the final handler runs
|
|
413
|
+
await myClass.hook('notify', { user: 'alice', message: 'hi' });
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
**Tightening the result type:**
|
|
417
|
+
|
|
418
|
+
When every hook returns the same shape, pass generics so `result` is fully typed instead of `any`:
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
import { ParallelHook } from 'hookified';
|
|
422
|
+
|
|
423
|
+
type NotifyArgs = { user: string; message: string };
|
|
424
|
+
type NotifyResult = { channel: string; messageId: string };
|
|
425
|
+
|
|
426
|
+
const ph = new ParallelHook<NotifyArgs, NotifyResult>('notify', ({ results }) => {
|
|
427
|
+
for (const [, r] of results) {
|
|
428
|
+
if (r.status === 'fulfilled') {
|
|
429
|
+
console.log(`${r.result.channel}: ${r.result.messageId}`);
|
|
430
|
+
} else {
|
|
431
|
+
console.error(r.reason); // still `unknown` — errors aren't typed
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
ph.addHook(async ({ initialArgs }) => ({ channel: 'email', messageId: '1' }));
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
**Managing hooks:**
|
|
440
|
+
|
|
441
|
+
```javascript
|
|
442
|
+
const ph = new ParallelHook('process', ({ results }) => {
|
|
443
|
+
console.log(results.size); // number of hooks that ran
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
const myHook = ({ initialArgs }) => initialArgs + 1;
|
|
447
|
+
ph.addHook(myHook);
|
|
448
|
+
|
|
449
|
+
// Remove a hook by reference
|
|
450
|
+
ph.removeHook(myHook); // returns true
|
|
451
|
+
|
|
452
|
+
// Access the hooks array
|
|
453
|
+
console.log(ph.hooks.length); // 0
|
|
454
|
+
```
|
|
455
|
+
|
|
345
456
|
# API - Hooks
|
|
346
457
|
|
|
347
458
|
> All examples below assume the following setup unless otherwise noted:
|
|
@@ -1253,6 +1364,22 @@ This shows how on par `hookified` is to the native `EventEmitter` and popular `e
|
|
|
1253
1364
|
|
|
1254
1365
|
_Note: the `EventEmitter` version is Nodejs versioning._
|
|
1255
1366
|
|
|
1367
|
+
# Migrating from v2 to v3
|
|
1368
|
+
|
|
1369
|
+
v3 has no API changes. The only breaking change is the minimum Node.js version requirement.
|
|
1370
|
+
|
|
1371
|
+
## Breaking Changes
|
|
1372
|
+
|
|
1373
|
+
| Change | Summary |
|
|
1374
|
+
|---|---|
|
|
1375
|
+
| Node.js version | Minimum required version is now `>=22.18.0` (previously no `engines` constraint) |
|
|
1376
|
+
|
|
1377
|
+
### Node.js >=22.18.0 required
|
|
1378
|
+
|
|
1379
|
+
The `engines` field in `package.json` now requires Node.js 22.18.0 or later. Node.js 20 reached end-of-life in April 2026.
|
|
1380
|
+
|
|
1381
|
+
**Migration:** Upgrade to Node.js 22 LTS or Node.js 24+. No code changes are required — the library API is identical to v2.
|
|
1382
|
+
|
|
1256
1383
|
# Migrating from v1 to v2
|
|
1257
1384
|
|
|
1258
1385
|
## Quick Guide
|