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  [![npm downloads](https://img.shields.io/npm/dw/before-after-hook.svg)](https://www.npmjs.com/package/before-after-hook)
     6  [![Test](https://github.com/gr2m/before-after-hook/actions/workflows/test.yml/badge.svg)](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)