hookified 2.1.1 → 2.2.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 +111 -1
- 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 +12 -11
package/README.md
CHANGED
|
@@ -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!
|
|
@@ -31,7 +32,8 @@
|
|
|
31
32
|
- [Using it in the Browser](#using-it-in-the-browser)
|
|
32
33
|
- [Hooks](#hooks)
|
|
33
34
|
- [Standard Hook](#standard-hook)
|
|
34
|
-
- [Waterfall Hook](#
|
|
35
|
+
- [Waterfall Hook](#waterfall-hook)
|
|
36
|
+
- [Parallel Hook](#parallel-hook)
|
|
35
37
|
- [API - Hooks](#api---hooks)
|
|
36
38
|
- [.allowDeprecated](#allowdeprecated)
|
|
37
39
|
- [.deprecatedHooks](#deprecatedhooks)
|
|
@@ -342,6 +344,114 @@ wh.removeHook(myHook); // returns true
|
|
|
342
344
|
console.log(wh.hooks.length); // 0
|
|
343
345
|
```
|
|
344
346
|
|
|
347
|
+
## Parallel Hook
|
|
348
|
+
|
|
349
|
+
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()`.
|
|
350
|
+
|
|
351
|
+
Per-hook functions receive a `ParallelHookContext`:
|
|
352
|
+
|
|
353
|
+
| Property | Type | Description |
|
|
354
|
+
|----------|------|-------------|
|
|
355
|
+
| `initialArgs` | `any` | The original arguments passed to `handler()`. Single argument stays as-is; multiple arguments become an array. |
|
|
356
|
+
|
|
357
|
+
The final handler receives a `ParallelHookFinalContext`:
|
|
358
|
+
|
|
359
|
+
| Property | Type | Description |
|
|
360
|
+
|----------|------|-------------|
|
|
361
|
+
| `initialArgs` | `any` | Same value passed to every hook. |
|
|
362
|
+
| `results` | `Map<ParallelHookFn, ParallelHookResult>` | One entry per registered hook, keyed by the hook function reference for direct lookup. Iteration order matches registration order. |
|
|
363
|
+
|
|
364
|
+
Each `ParallelHookResult` value is a discriminated union — failures are reported, not thrown:
|
|
365
|
+
|
|
366
|
+
| Field | Type | Description |
|
|
367
|
+
|-------|------|-------------|
|
|
368
|
+
| `status` | `"fulfilled" \| "rejected"` | Discriminator. |
|
|
369
|
+
| `result` | `TResult` | Present when `status === "fulfilled"`. The value the hook returned. Defaults to `any`; tighten via the `ParallelHook<TArgs, TResult>` generic. |
|
|
370
|
+
| `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. |
|
|
371
|
+
|
|
372
|
+
**Basic usage with `Hookified`:**
|
|
373
|
+
|
|
374
|
+
```javascript
|
|
375
|
+
import { Hookified, ParallelHook } from 'hookified';
|
|
376
|
+
|
|
377
|
+
class MyClass extends Hookified {
|
|
378
|
+
constructor() { super(); }
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const myClass = new MyClass();
|
|
382
|
+
|
|
383
|
+
const sendEmailHook = async ({ initialArgs }) => sendEmail(initialArgs);
|
|
384
|
+
const sendSlackHook = async ({ initialArgs }) => sendSlack(initialArgs);
|
|
385
|
+
const sendWebhookHook = async ({ initialArgs }) => sendWebhook(initialArgs);
|
|
386
|
+
|
|
387
|
+
const ph = new ParallelHook('notify', ({ results }) => {
|
|
388
|
+
// Look up a specific hook's outcome by reference
|
|
389
|
+
const emailOutcome = results.get(sendEmailHook);
|
|
390
|
+
if (emailOutcome?.status === 'rejected') {
|
|
391
|
+
console.error('email failed:', emailOutcome.reason);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Or iterate every result in registration order
|
|
395
|
+
for (const [hook, r] of results) {
|
|
396
|
+
if (r.status === 'fulfilled') {
|
|
397
|
+
console.log('ok:', r.result);
|
|
398
|
+
} else {
|
|
399
|
+
console.error('failed:', r.reason);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
ph.addHook(sendEmailHook);
|
|
405
|
+
ph.addHook(sendSlackHook);
|
|
406
|
+
ph.addHook(sendWebhookHook);
|
|
407
|
+
|
|
408
|
+
// Register with Hookified — works because ParallelHook implements IHook
|
|
409
|
+
myClass.onHook(ph);
|
|
410
|
+
|
|
411
|
+
// All three notification hooks fire concurrently, then the final handler runs
|
|
412
|
+
await myClass.hook('notify', { user: 'alice', message: 'hi' });
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
**Tightening the result type:**
|
|
416
|
+
|
|
417
|
+
When every hook returns the same shape, pass generics so `result` is fully typed instead of `any`:
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
import { ParallelHook } from 'hookified';
|
|
421
|
+
|
|
422
|
+
type NotifyArgs = { user: string; message: string };
|
|
423
|
+
type NotifyResult = { channel: string; messageId: string };
|
|
424
|
+
|
|
425
|
+
const ph = new ParallelHook<NotifyArgs, NotifyResult>('notify', ({ results }) => {
|
|
426
|
+
for (const [, r] of results) {
|
|
427
|
+
if (r.status === 'fulfilled') {
|
|
428
|
+
console.log(`${r.result.channel}: ${r.result.messageId}`);
|
|
429
|
+
} else {
|
|
430
|
+
console.error(r.reason); // still `unknown` — errors aren't typed
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
ph.addHook(async ({ initialArgs }) => ({ channel: 'email', messageId: '1' }));
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
**Managing hooks:**
|
|
439
|
+
|
|
440
|
+
```javascript
|
|
441
|
+
const ph = new ParallelHook('process', ({ results }) => {
|
|
442
|
+
console.log(results.size); // number of hooks that ran
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
const myHook = ({ initialArgs }) => initialArgs + 1;
|
|
446
|
+
ph.addHook(myHook);
|
|
447
|
+
|
|
448
|
+
// Remove a hook by reference
|
|
449
|
+
ph.removeHook(myHook); // returns true
|
|
450
|
+
|
|
451
|
+
// Access the hooks array
|
|
452
|
+
console.log(ph.hooks.length); // 0
|
|
453
|
+
```
|
|
454
|
+
|
|
345
455
|
# API - Hooks
|
|
346
456
|
|
|
347
457
|
> All examples below assume the following setup unless otherwise noted:
|