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  [![npm downloads](https://img.shields.io/npm/dw/before-after-hook.svg)](https://www.npmjs.com/package/before-after-hook)
     6  [![Build Status](https://travis-ci.org/gr2m/before-after-hook.svg?branch=master)](https://travis-ci.org/gr2m/before-after-hook)
     7  [![Coverage Status](https://coveralls.io/repos/gr2m/before-after-hook/badge.svg?branch=master)](https://coveralls.io/r/gr2m/before-after-hook?branch=master)
     8  [![Greenkeeper badge](https://badges.greenkeeper.io/gr2m/before-after-hook.svg)](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)