npm package: @itentialopensource/app-utils
Description: A set of commonly used utility functions
Author: Itential
Latest version: 2.0.0

# app-utils

`app-utils` provides some helpful utility functions.

Table of Contents

- [Installation](#installation)
- [Usage](#usage)
- [Included functions](#included-functions)

## Installation

`npm install --save @itentialopensource/app-utils`

## Usage

Require `app-utils`

```javascript
const appUtils = require('@itentialopensource/app-utils');
```

## Included functions

- `safeGet(object, path, defaultValue = null)`: Safely try to extract a property from an object. If the property cannot be found, return a default value.

  ```javascript
  const shapes = {
    triangle: {
      numSides: 3,
      types: [
        { name: 'equilateral', numEqualSides: 3 },
        { name: 'isosceles',   numEqualSides: 2 },
        { name: '30-60-90',    numEqualSides: 0 }
      ]
    },
    quadrilateral: {
      numSides: 4,
      types: [
        { name: 'square' , numEqualSides: 4 }
      ]
    }
  };
  let numEqualSides = safeGet(shapes, ['triangle', 'types', 1, 'numEqualSides'], 0);
  // numEqualSides -> 2

  numEqualSides = safeGet(shapes, ['quadrilateral', 'types', 1, 'numEqualSides'], 0);
  // numEqualSides -> 0
  // Since there is no second element in the quadrilateral types array,
  // numEqualSides takes the default value of 0.
  ```

- `safePut(object, path, value)`: Put a value into an object at the specified path, creating the path along the way if it doesn't exist.

  ```javascript
  // Continuing the shapes example
  const rhombus = { name: 'rhombus', numEqualSides: 4 };
  // Here the index -1 tells safePut to add the item to the end of the array
  const shapesWithRhombus = safePut(shapes, ['quadrilateral', 'types', -1], rhombus);
  
  numEqualSides = safeGet(shapesWithRhombus, ['quadrilateral', 'types', 1, 'numEqualSides'], 0);
  // numEqualSides -> 4


  // safePut can also be used for creating new objects
  const deeplyNested = safePut({}, ['a', 'b', 'c', 'd', 'e'], {});
  // -> deeplyNested -> {
  //      a: {
  //        b: {
  //          c: {
  //            d: {
  //              e: {}
  //            }
  //          }
  //        }
  //      }
  //    }
  ```

- `asyncFilter(arr, evalFn)`: Filter array elements according to an async evaluation function.

  ```javascript
  const arr = [1, 2, 3, 4, 5];
  const evalFn = async (num) => {
        // 10 millisecond delay
        await (() => new Promise(resolve => setTimeout(resolve, 10)))();
        return num > 3;
  };
  
  const butTheArrayBuiltIn = await Promise.all(arr.filter(num => evalFn(num)));
  // butTheArrayBuiltIn -> [1, 2, 3, 4, 5]
  // Unfortunately, Array.prototype.filter is synchronous and doesn't return what you'd expect.
  
  const greaterThanThree = await asyncFilter(arr, evalFn);
  // greaterThanThree -> [4, 5]
  ```

- `promisesInChunks(promises, options)`: Perform Promise.all in chunks instead of all at once--great for not overwhelming a device with requests.

  ```javascript
  const promises = [
      () => Promise.resolve(1),
      () => Promise.resolve(2),
      () => Promise.resolve(3),
      () => Promise.resolve(4)
  ];
  const options = { chunkSize: 2, waitTime: 500 };
  // Evaluate two promises at a time with a half second delay between evaluations.
  const resolvedPromises = await promisesInChunks(promises, options);
  // resolvedPromises -> [1, 2, 3, 4]
  ```

- `toCogFn(fn, callback, ...args)`: Wrap a function in the standard form necessary to expose it as a task or endpoint in IAP. Callbacks and error handling are taken care of for you, greatly reducing the size of `cog.js`.

  ```javascript
  // cog.js

  const operations = require('./lib/operations');

  // Expose a library function as a Workflow Builder task
  const numerator = 6;
  const denominator = 3;
  const quotient = operations.divide(numerator, denominator);
  // quotient -> 2

  class AppMath {
    /**
    * @pronghornType method
    * @name divide
    * @summary Divide two numbers
    * @param {number} numerator
    * @param {number} denominator
    * @param {callback} callback
    * @return {number} quotient
    * @roles admin
    * @task true
    */
    divide(numerator, denominator, callback) {
      toCogFn(operations.divide, callback, numerator, denominator);
    }
  }
  ```

- `isObject(x)`: Determine if a variable is bound to an object (in the strict sense).

    ```javascript
    isObject({}); // -> true
    
    [] instanceof Object;   // -> true
    typeof [] === 'object'; // -> true
    isObject([]);           // -> false
    ```

- `safeHasOwnProperty(object, property)`: Perform Object.hasOwnProperty safely according to <https://eslint.org/docs/rules/no-prototype-builtins>

    ```javascript
    function safeHasOwnProperty(object, property) {
      return Object.prototype.hasOwnProperty.call(object, property);
    }
  
    const dog = { name: 'Fido' };
    safeHasOwnObject(dog, 'name');        // -> true
    safeHasOwnObject(dog, 'favoriteToy'); // -> false
    ```