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