github.com/nektos/act@v0.2.63/pkg/runner/testdata/actions/node12/node_modules/before-after-hook/README.md (about) 1 # before-after-hook 2 3 > asynchronous hooks for internal functionality 4 5 [](https://www.npmjs.com/package/before-after-hook) 6 [](https://github.com/gr2m/before-after-hook/actions/workflows/test.yml) 7 8 ## Usage 9 10 ### Singular hook 11 12 ```js 13 // instantiate singular hook API 14 const hook = new Hook.Singular(); 15 16 // Create a hook 17 function getData(options) { 18 return hook(fetchFromDatabase, options) 19 .then(handleData) 20 .catch(handleGetError); 21 } 22 23 // register before/error/after hooks. 24 // The methods can be async or return a promise 25 hook.before(beforeHook); 26 hook.error(errorHook); 27 hook.after(afterHook); 28 29 getData({ id: 123 }); 30 ``` 31 32 ### Hook collection 33 34 ```js 35 // instantiate hook collection API 36 const hookCollection = new Hook.Collection(); 37 38 // Create a hook 39 function getData(options) { 40 return hookCollection("get", fetchFromDatabase, options) 41 .then(handleData) 42 .catch(handleGetError); 43 } 44 45 // register before/error/after hooks. 46 // The methods can be async or return a promise 47 hookCollection.before("get", beforeHook); 48 hookCollection.error("get", errorHook); 49 hookCollection.after("get", afterHook); 50 51 getData({ id: 123 }); 52 ``` 53 54 ### Hook.Singular vs Hook.Collection 55 56 There's no fundamental difference between the `Hook.Singular` and `Hook.Collection` hooks except for the fact that a hook from a collection requires you to pass along the name. Therefore the following explanation applies to both code snippets as described above. 57 58 The methods are executed in the following order 59 60 1. `beforeHook` 61 2. `fetchFromDatabase` 62 3. `afterHook` 63 4. `handleData` 64 65 `beforeHook` can mutate `options` before it’s passed to `fetchFromDatabase`. 66 67 If an error is thrown in `beforeHook` or `fetchFromDatabase` then `errorHook` is 68 called next. 69 70 If `afterHook` throws an error then `handleGetError` is called instead 71 of `handleData`. 72 73 If `errorHook` throws an error then `handleGetError` is called next, otherwise 74 `afterHook` and `handleData`. 75 76 You can also use `hook.wrap` to achieve the same thing as shown above (collection example): 77 78 ```js 79 hookCollection.wrap("get", async (getData, options) => { 80 await beforeHook(options); 81 82 try { 83 const result = getData(options); 84 } catch (error) { 85 await errorHook(error, options); 86 } 87 88 await afterHook(result, options); 89 }); 90 ``` 91 92 ## Install 93 94 ``` 95 npm install before-after-hook 96 ``` 97 98 Or download [the latest `before-after-hook.min.js`](https://github.com/gr2m/before-after-hook/releases/latest). 99 100 ## API 101 102 - [Singular Hook Constructor](#singular-hook-api) 103 - [Hook Collection Constructor](#hook-collection-api) 104 105 ## Singular hook API 106 107 - [Singular constructor](#singular-constructor) 108 - [hook.api](#singular-api) 109 - [hook()](#singular-api) 110 - [hook.before()](#singular-api) 111 - [hook.error()](#singular-api) 112 - [hook.after()](#singular-api) 113 - [hook.wrap()](#singular-api) 114 - [hook.remove()](#singular-api) 115 116 ### Singular constructor 117 118 The `Hook.Singular` constructor has no options and returns a `hook` instance with the 119 methods below: 120 121 ```js 122 const hook = new Hook.Singular(); 123 ``` 124 125 Using the singular hook is recommended for [TypeScript](#typescript) 126 127 ### Singular API 128 129 The singular hook is a reference to a single hook. This means that there's no need to pass along any identifier (such as a `name` as can be seen in the [Hook.Collection API](#hookcollectionapi)). 130 131 The API of a singular hook is exactly the same as a collection hook and we therefore suggest you read the [Hook.Collection API](#hookcollectionapi) and leave out any use of the `name` argument. Just skip it like described in this example: 132 133 ```js 134 const hook = new Hook.Singular(); 135 136 // good 137 hook.before(beforeHook); 138 hook.after(afterHook); 139 hook(fetchFromDatabase, options); 140 141 // bad 142 hook.before("get", beforeHook); 143 hook.after("get", afterHook); 144 hook("get", fetchFromDatabase, options); 145 ``` 146 147 ## Hook collection API 148 149 - [Collection constructor](#collection-constructor) 150 - [hookCollection.api](#hookcollectionapi) 151 - [hookCollection()](#hookcollection) 152 - [hookCollection.before()](#hookcollectionbefore) 153 - [hookCollection.error()](#hookcollectionerror) 154 - [hookCollection.after()](#hookcollectionafter) 155 - [hookCollection.wrap()](#hookcollectionwrap) 156 - [hookCollection.remove()](#hookcollectionremove) 157 158 ### Collection constructor 159 160 The `Hook.Collection` constructor has no options and returns a `hookCollection` instance with the 161 methods below 162 163 ```js 164 const hookCollection = new Hook.Collection(); 165 ``` 166 167 ### hookCollection.api 168 169 Use the `api` property to return the public API: 170 171 - [hookCollection.before()](#hookcollectionbefore) 172 - [hookCollection.after()](#hookcollectionafter) 173 - [hookCollection.error()](#hookcollectionerror) 174 - [hookCollection.wrap()](#hookcollectionwrap) 175 - [hookCollection.remove()](#hookcollectionremove) 176 177 That way you don’t need to expose the [hookCollection()](#hookcollection) method to consumers of your library 178 179 ### hookCollection() 180 181 Invoke before and after hooks. Returns a promise. 182 183 ```js 184 hookCollection(nameOrNames, method /*, options */); 185 ``` 186 187 <table> 188 <thead> 189 <tr> 190 <th>Argument</th> 191 <th>Type</th> 192 <th>Description</th> 193 <th>Required</th> 194 </tr> 195 </thead> 196 <tr> 197 <th align="left"><code>name</code></th> 198 <td>String or Array of Strings</td> 199 <td>Hook name, for example <code>'save'</code>. Or an array of names, see example below.</td> 200 <td>Yes</td> 201 </tr> 202 <tr> 203 <th align="left"><code>method</code></th> 204 <td>Function</td> 205 <td>Callback to be executed after all before hooks finished execution successfully. <code>options</code> is passed as first argument</td> 206 <td>Yes</td> 207 </tr> 208 <tr> 209 <th align="left"><code>options</code></th> 210 <td>Object</td> 211 <td>Will be passed to all before hooks as reference, so they can mutate it</td> 212 <td>No, defaults to empty object (<code>{}</code>)</td> 213 </tr> 214 </table> 215 216 Resolves with whatever `method` returns or resolves with. 217 Rejects with error that is thrown or rejected with by 218 219 1. Any of the before hooks, whichever rejects / throws first 220 2. `method` 221 3. Any of the after hooks, whichever rejects / throws first 222 223 Simple Example 224 225 ```js 226 hookCollection( 227 "save", 228 function (record) { 229 return store.save(record); 230 }, 231 record 232 ); 233 // shorter: hookCollection('save', store.save, record) 234 235 hookCollection.before("save", function addTimestamps(record) { 236 const now = new Date().toISOString(); 237 if (record.createdAt) { 238 record.updatedAt = now; 239 } else { 240 record.createdAt = now; 241 } 242 }); 243 ``` 244 245 Example defining multiple hooks at once. 246 247 ```js 248 hookCollection( 249 ["add", "save"], 250 function (record) { 251 return store.save(record); 252 }, 253 record 254 ); 255 256 hookCollection.before("add", function addTimestamps(record) { 257 if (!record.type) { 258 throw new Error("type property is required"); 259 } 260 }); 261 262 hookCollection.before("save", function addTimestamps(record) { 263 if (!record.type) { 264 throw new Error("type property is required"); 265 } 266 }); 267 ``` 268 269 Defining multiple hooks is helpful if you have similar methods for which you want to define separate hooks, but also an additional hook that gets called for all at once. The example above is equal to this: 270 271 ```js 272 hookCollection( 273 "add", 274 function (record) { 275 return hookCollection( 276 "save", 277 function (record) { 278 return store.save(record); 279 }, 280 record 281 ); 282 }, 283 record 284 ); 285 ``` 286 287 ### hookCollection.before() 288 289 Add before hook for given name. 290 291 ```js 292 hookCollection.before(name, method); 293 ``` 294 295 <table> 296 <thead> 297 <tr> 298 <th>Argument</th> 299 <th>Type</th> 300 <th>Description</th> 301 <th>Required</th> 302 </tr> 303 </thead> 304 <tr> 305 <th align="left"><code>name</code></th> 306 <td>String</td> 307 <td>Hook name, for example <code>'save'</code></td> 308 <td>Yes</td> 309 </tr> 310 <tr> 311 <th align="left"><code>method</code></th> 312 <td>Function</td> 313 <td> 314 Executed before the wrapped method. Called with the hook’s 315 <code>options</code> argument. Before hooks can mutate the passed options 316 before they are passed to the wrapped method. 317 </td> 318 <td>Yes</td> 319 </tr> 320 </table> 321 322 Example 323 324 ```js 325 hookCollection.before("save", function validate(record) { 326 if (!record.name) { 327 throw new Error("name property is required"); 328 } 329 }); 330 ``` 331 332 ### hookCollection.error() 333 334 Add error hook for given name. 335 336 ```js 337 hookCollection.error(name, method); 338 ``` 339 340 <table> 341 <thead> 342 <tr> 343 <th>Argument</th> 344 <th>Type</th> 345 <th>Description</th> 346 <th>Required</th> 347 </tr> 348 </thead> 349 <tr> 350 <th align="left"><code>name</code></th> 351 <td>String</td> 352 <td>Hook name, for example <code>'save'</code></td> 353 <td>Yes</td> 354 </tr> 355 <tr> 356 <th align="left"><code>method</code></th> 357 <td>Function</td> 358 <td> 359 Executed when an error occurred in either the wrapped method or a 360 <code>before</code> hook. Called with the thrown <code>error</code> 361 and the hook’s <code>options</code> argument. The first <code>method</code> 362 which does not throw an error will set the result that the after hook 363 methods will receive. 364 </td> 365 <td>Yes</td> 366 </tr> 367 </table> 368 369 Example 370 371 ```js 372 hookCollection.error("save", function (error, options) { 373 if (error.ignore) return; 374 throw error; 375 }); 376 ``` 377 378 ### hookCollection.after() 379 380 Add after hook for given name. 381 382 ```js 383 hookCollection.after(name, method); 384 ``` 385 386 <table> 387 <thead> 388 <tr> 389 <th>Argument</th> 390 <th>Type</th> 391 <th>Description</th> 392 <th>Required</th> 393 </tr> 394 </thead> 395 <tr> 396 <th align="left"><code>name</code></th> 397 <td>String</td> 398 <td>Hook name, for example <code>'save'</code></td> 399 <td>Yes</td> 400 </tr> 401 <tr> 402 <th align="left"><code>method</code></th> 403 <td>Function</td> 404 <td> 405 Executed after wrapped method. Called with what the wrapped method 406 resolves with the hook’s <code>options</code> argument. 407 </td> 408 <td>Yes</td> 409 </tr> 410 </table> 411 412 Example 413 414 ```js 415 hookCollection.after("save", function (result, options) { 416 if (result.updatedAt) { 417 app.emit("update", result); 418 } else { 419 app.emit("create", result); 420 } 421 }); 422 ``` 423 424 ### hookCollection.wrap() 425 426 Add wrap hook for given name. 427 428 ```js 429 hookCollection.wrap(name, method); 430 ``` 431 432 <table> 433 <thead> 434 <tr> 435 <th>Argument</th> 436 <th>Type</th> 437 <th>Description</th> 438 <th>Required</th> 439 </tr> 440 </thead> 441 <tr> 442 <th align="left"><code>name</code></th> 443 <td>String</td> 444 <td>Hook name, for example <code>'save'</code></td> 445 <td>Yes</td> 446 </tr> 447 <tr> 448 <th align="left"><code>method</code></th> 449 <td>Function</td> 450 <td> 451 Receives both the wrapped method and the passed options as arguments so it can add logic before and after the wrapped method, it can handle errors and even replace the wrapped method altogether 452 </td> 453 <td>Yes</td> 454 </tr> 455 </table> 456 457 Example 458 459 ```js 460 hookCollection.wrap("save", async function (saveInDatabase, options) { 461 if (!record.name) { 462 throw new Error("name property is required"); 463 } 464 465 try { 466 const result = await saveInDatabase(options); 467 468 if (result.updatedAt) { 469 app.emit("update", result); 470 } else { 471 app.emit("create", result); 472 } 473 474 return result; 475 } catch (error) { 476 if (error.ignore) return; 477 throw error; 478 } 479 }); 480 ``` 481 482 See also: [Test mock example](examples/test-mock-example.md) 483 484 ### hookCollection.remove() 485 486 Removes hook for given name. 487 488 ```js 489 hookCollection.remove(name, hookMethod); 490 ``` 491 492 <table> 493 <thead> 494 <tr> 495 <th>Argument</th> 496 <th>Type</th> 497 <th>Description</th> 498 <th>Required</th> 499 </tr> 500 </thead> 501 <tr> 502 <th align="left"><code>name</code></th> 503 <td>String</td> 504 <td>Hook name, for example <code>'save'</code></td> 505 <td>Yes</td> 506 </tr> 507 <tr> 508 <th align="left"><code>beforeHookMethod</code></th> 509 <td>Function</td> 510 <td> 511 Same function that was previously passed to <code>hookCollection.before()</code>, <code>hookCollection.error()</code>, <code>hookCollection.after()</code> or <code>hookCollection.wrap()</code> 512 </td> 513 <td>Yes</td> 514 </tr> 515 </table> 516 517 Example 518 519 ```js 520 hookCollection.remove("save", validateRecord); 521 ``` 522 523 ## TypeScript 524 525 This library contains type definitions for TypeScript. 526 527 ### Type support for `Singular`: 528 529 ```ts 530 import { Hook } from "before-after-hook"; 531 532 type TOptions = { foo: string }; // type for options 533 type TResult = { bar: number }; // type for result 534 type TError = Error; // type for error 535 536 const hook = new Hook.Singular<TOptions, TResult, TError>(); 537 538 hook.before((options) => { 539 // `options.foo` has `string` type 540 541 // not allowed 542 options.foo = 42; 543 544 // allowed 545 options.foo = "Forty-Two"; 546 }); 547 548 const hookedMethod = hook( 549 (options) => { 550 // `options.foo` has `string` type 551 552 // not allowed, because it does not satisfy the `R` type 553 return { foo: 42 }; 554 555 // allowed 556 return { bar: 42 }; 557 }, 558 { foo: "Forty-Two" } 559 ); 560 ``` 561 562 You can choose not to pass the types for options, result or error. So, these are completely valid: 563 564 ```ts 565 const hook = new Hook.Singular<O, R>(); 566 const hook = new Hook.Singular<O>(); 567 const hook = new Hook.Singular(); 568 ``` 569 570 In these cases, the omitted types will implicitly be `any`. 571 572 ### Type support for `Collection`: 573 574 `Collection` also has strict type support. You can use it like this: 575 576 ```ts 577 import { Hook } from "before-after-hook"; 578 579 type HooksType = { 580 add: { 581 Options: { type: string }; 582 Result: { id: number }; 583 Error: Error; 584 }; 585 save: { 586 Options: { type: string }; 587 Result: { id: number }; 588 }; 589 read: { 590 Options: { id: number; foo: number }; 591 }; 592 destroy: { 593 Options: { id: number; foo: string }; 594 }; 595 }; 596 597 const hooks = new Hook.Collection<HooksType>(); 598 599 hooks.before("destroy", (options) => { 600 // `options.id` has `number` type 601 }); 602 603 hooks.error("add", (err, options) => { 604 // `options.type` has `string` type 605 // `err` is `instanceof Error` 606 }); 607 608 hooks.error("save", (err, options) => { 609 // `options.type` has `string` type 610 // `err` has type `any` 611 }); 612 613 hooks.after("save", (result, options) => { 614 // `options.type` has `string` type 615 // `result.id` has `number` type 616 }); 617 ``` 618 619 You can choose not to pass the types altogether. In that case, everything will implicitly be `any`: 620 621 ```ts 622 const hook = new Hook.Collection(); 623 ``` 624 625 Alternative imports: 626 627 ```ts 628 import { Singular, Collection } from "before-after-hook"; 629 630 const hook = new Singular(); 631 const hooks = new Collection(); 632 ``` 633 634 ## Upgrading to 1.4 635 636 Since version 1.4 the `Hook` constructor has been deprecated in favor of returning `Hook.Singular` in an upcoming breaking release. 637 638 Version 1.4 is still 100% backwards-compatible, but if you want to continue using hook collections, we recommend using the `Hook.Collection` constructor instead before the next release. 639 640 For even more details, check out [the PR](https://github.com/gr2m/before-after-hook/pull/52). 641 642 ## See also 643 644 If `before-after-hook` is not for you, have a look at one of these alternatives: 645 646 - https://github.com/keystonejs/grappling-hook 647 - https://github.com/sebelga/promised-hooks 648 - https://github.com/bnoguchi/hooks-js 649 - https://github.com/cb1kenobi/hook-emitter 650 651 ## License 652 653 [Apache 2.0](LICENSE)