Skip to content

Commit

Permalink
Merge pull request #28 from dictor93/feature/flow
Browse files Browse the repository at this point in the history
Features: flow, invert, mapValues, uniqBy
  • Loading branch information
vigan-abd authored Jun 12, 2024
2 parents 59afc56 + 4341550 commit 6f4bb7c
Show file tree
Hide file tree
Showing 14 changed files with 291 additions and 2 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#1.13.0
- feat: flow
- feat: invert
- feat: mapValues
- feat: uniqBy
- feat: groupBy

#1.12.0
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ Currently supported utils:
- `camelize` - simple camel case
- `capitalize` - make first char uppercase
- `cloneDeep` - deep clone functionality for objects
- `flow` - generate a composite function that returns the result of provided functions called by the chain; each previous function result passes as the argument of the next function in the chain
- `freezeDeep` - deep freezes objects
- `get` - get the object members by path
- `getArrayHasIntersect` - checks if arrays have at least one common value
- `getArrayUniq` - gets unique values form array
- `invert` - Inverts the key-value pairs of an object
- `groupBy` - groups the elements of an array by a specified key.
- `isEmpty` - checks if value is an empty object, collection, map, or set
- `isEqual` - check if passed two values are equal
Expand All @@ -21,6 +23,7 @@ Currently supported utils:
- `isPlainObject` - checks if input is object, not null object and not array object
- `isString` - checks if input is a string
- `mapKeys` - creates new object with the same values but with keys mapped by the provided function
- `mapValues` - Maps the values of an object or array using the provided iteratee function or property path
- `max` - computes the maximum value of array. If array is empty or falsey, undefined is returned
- `merge` - deep merge functionality for objects
- `min` - computes the minimum value of array. If array is empty or falsey, undefined is returned
Expand All @@ -31,3 +34,4 @@ Currently supported utils:
- `shuffle` - performs pseudo random shuffle on clone of the array
- `sum` - calculate sum of array items
- `sumBy` - calculate sum of array items using iteratee function or string shortcut
- `uniqBy` - get unique values of array by the iteratee function or property path
6 changes: 5 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
export function camelize (str: string): string
export function capitalize (str: string): string
export function getErrorMessage (obj: obj): string
export function cloneDeep (obj: Object): Object
export function flow (funcs: Array<Function>): Function
export function freezeDeep (obj: Object): Object
export function get (obj: Object, path: string | Array<string | number>, defaultValue: any): any
export function getArrayHasIntersect (arr1: Array<any>, arr2: Array<any>): boolean
export function getArrayUniq (arr: Array<any>): Array<any>
export function getErrorMessage (obj: obj): string
export function invert (obj: Object): Object
export function groupBy<T> (array: Array<T>, key: string | ((item: T) => string)): { [key: string]: Array<T> }
export function isEmpty (val: any): boolean
export function isEqual (value: any, another: any): boolean
Expand All @@ -17,6 +19,7 @@ export function isObject (verifiable: any): Boolean
export function isPlainObject (val: any): boolean
export function isString (val: any): boolean
export function mapKeys(obj: Object, mapper: (val: any, key: string) => string): Object
export function mapValues(obj: Object, mapper: (val: any, key: string) => any): Object
export function max (array: Array<any>): any
export function merge (obj: Object, ...sources: Object[]): Object
export function min (array: Array<any>): any
Expand All @@ -27,3 +30,4 @@ export function pickBy (obj: Object, predicate: (val: any, key: string) => boole
export function shuffle<T> (array: Array<T>): Array<T>
export function sum (values: Array<string>): number
export function sumBy (values: Array, iteratee: Function | string): number
export function uniqBy (array: Array, iteratee: Function | string): Array
10 changes: 9 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
const camelize = require('./src/camelize')
const capitalize = require('./src/capitalize')
const cloneDeep = require('./src/cloneDeep')
const flow = require('./src/flow')
const freezeDeep = require('./src/freezeDeep')
const get = require('./src/get')
const getArrayHasIntersect = require('./src/getArrayHasIntersect')
const getArrayUniq = require('./src/getArrayUniq')
const getErrorMessage = require('./src/getErrorMessage')
const invert = require('./src/invert')
const groupBy = require('./src/groupBy')
const isEmpty = require('./src/isEmpty')
const isEqual = require('./src/isEqual')
Expand All @@ -19,6 +21,7 @@ const isObject = require('./src/isObject')
const isPlainObject = require('./src/isPlainObject')
const isString = require('./src/isString')
const mapKeys = require('./src/mapKeys')
const mapValues = require('./src/mapValues')
const max = require('./src/max')
const merge = require('./src/merge')
const min = require('./src/min')
Expand All @@ -29,6 +32,7 @@ const pickBy = require('./src/pickBy')
const shuffle = require('./src/shuffle')
const sum = require('./src/sum')
const sumBy = require('./src/sumBy')
const uniqBy = require('./src/uniqBy')

module.exports = {
camelize,
Expand All @@ -39,6 +43,7 @@ module.exports = {
getArrayHasIntersect,
getArrayUniq,
getErrorMessage,
invert,
groupBy,
isEmpty,
isEqual,
Expand All @@ -49,7 +54,9 @@ module.exports = {
isObject,
isPlainObject,
isString,
flow,
mapKeys,
mapValues,
max,
merge,
min,
Expand All @@ -59,5 +66,6 @@ module.exports = {
pickBy,
shuffle,
sum,
sumBy
sumBy,
uniqBy
}
16 changes: 16 additions & 0 deletions src/flow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use strict'

/**
* Executes a series of functions in a pipeline, passing the result of each function as the argument to the next function.
*
* @param {Array<Function>} fns - An array of functions to be executed in order.
* @returns {any} - The final result of the pipeline.
* @throws {Error} - Throws an error if any element in the array is not a function.
*/
const flow = (fns = []) => {
if (!fns.length) return (arg) => arg
if (fns.find(fn => typeof fn !== 'function')) throw new Error('All elements should be functions')
return (...arg) => fns.reduce((acc, fn, i) => i ? fn(acc) : fn(...arg), arg)
}

module.exports = flow
17 changes: 17 additions & 0 deletions src/invert.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict'

/**
* Inverts the key-value pairs of an object.
*
* @param {object} object - The object to invert.
* @returns {object} - The new object with inverted key-value pairs.
*/
const invert = (object) => {
const newObject = {}
for (const key in object) {
newObject[object[key]] = key
}
return newObject
}

module.exports = invert
21 changes: 21 additions & 0 deletions src/mapValues.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use strict'

const get = require('./get')

/**
* Maps the values of an object or array using the provided iteratee function or property path.
*
* @param {object|Array} src - The source object or array to map.
* @param {Function|string} iteratee - The iteratee function or property path to map the values.
* @returns {object} - The new object with mapped values.
*/
const mapValues = (src, iteratee) => {
const result = {}
const _iteratee = typeof iteratee === 'function' ? iteratee : (value) => get(value, iteratee)
for (const key in src) {
result[key] = _iteratee(src[key])
}
return result
}

module.exports = mapValues
23 changes: 23 additions & 0 deletions src/uniqBy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict'

const get = require('./get')

/**
* Get unique values of array by the iteratee function or property path.
*
* @param {Array} arr - The src array.
* @param {Function|string} iteratee - The iteratee function or property path to check the unique values.
* @returns {Array} - The result array unique by iteratee.
*/
const uniqBy = (arr, iteratee) => {
const set = new Set()
const _iteratee = typeof iteratee === 'function' ? iteratee : (value) => get(value, iteratee)
return arr.filter(el => {
const val = iteratee ? _iteratee(el) : el
if (set.has(val)) return false
set.add(val)
return true
})
}

module.exports = uniqBy
46 changes: 46 additions & 0 deletions test/flow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use strict'

/* eslint-env mocha */

const assert = require('assert')
const flow = require('../src/flow')

describe('flow', () => {
it('should return the input argument if no functions are provided', () => {
const result = flow()('input')
assert.strictEqual(result, 'input')
})

it('should apply the provided functions in sequence', () => {
const addOne = (num) => num + 1
const double = (num) => num * 2
const subtractFive = (num) => num - 5

const result = flow([addOne, double, subtractFive])(10)
assert.strictEqual(result, 17)
})

it('should throw an error if any element in the array is not a function', () => {
const fn1 = () => { }
const fn2 = 123
const fn3 = () => { }

assert.throws(() => flow([fn1, fn2, fn3]), Error)
})

it('should work with functions that take multiple arguments', () => {
const add = (a, b, c) => a + b + c
const square = (a) => a * a

const result = flow([add, square])(2, 3, 4)
assert.strictEqual(result, 81)
})

it('should work with complex arguments', () => {
const add = (a, b) => [{ a, b }, a + b]
const multiply = ([args, res]) => [args, args.a * args.b * res]
const subtract = ([args, res]) => res - args.a

assert.strictEqual(flow([add, multiply, subtract])(2, 3), 28)
})
})
32 changes: 32 additions & 0 deletions test/getErrorMessage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use strict'

/* eslint-env mocha */

const assert = require('assert')
const getErrorMessage = require('../src/getErrorMessage')

describe('getErrorMessage', () => {
it('should return the error message when it exists', () => {
const error = new Error('Something went wrong')
const errorMessage = getErrorMessage(error)
assert.strictEqual(errorMessage, 'Something went wrong')
})

it('should return an empty string when the error message is not provided', () => {
const error = new Error()
const errorMessage = getErrorMessage(error)
assert.strictEqual(errorMessage, '')
})

it('should return the base error code when it exists', () => {
const error = new Error('foo bar ERR_INVALID_INPUT')
const errorMessage = getErrorMessage(error)
assert.strictEqual(errorMessage, 'ERR_INVALID_INPUT')
})

it('should return the full error message when it does not match the base error code pattern', () => {
const error = new Error('Some other error')
const errorMessage = getErrorMessage(error)
assert.strictEqual(errorMessage, 'Some other error')
})
})
26 changes: 26 additions & 0 deletions test/invert.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict'

/* eslint-env jest */

const assert = require('assert')
const invert = require('../src/invert')

describe('invert', () => {
it('should invert an object', () => {
const object = { a: 1, b: 2 }
const actual = invert(object)

assert.deepStrictEqual(actual, { 1: 'a', 2: 'b' })
assert.deepStrictEqual(invert(actual), { a: '1', b: '2' })
})

it('should work with values that shadow keys on `Object.prototype`', () => {
const object = { a: 'hasOwnProperty', b: 'constructor' }
assert.deepStrictEqual(invert(object), { hasOwnProperty: 'a', constructor: 'b' })
})

it('should work with an object that has a `length` property', () => {
const object = { 0: 'a', 1: 'b', length: 2 }
assert.deepStrictEqual(invert(object), { a: '0', b: '1', 2: 'length' })
})
})
26 changes: 26 additions & 0 deletions test/mapValues.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict'

/* eslint-env mocha */

const assert = require('assert')
const mapValues = require('../src/mapValues')

describe('mapValues', () => {
const array = [1, 2]
const object = { a: 1, b: 2 }

it('should map values in `object` to a new object', () => {
const actual = mapValues(object, String)
assert.deepStrictEqual(actual, { a: '1', b: '2' })
})

it('should treat arrays like objects', () => {
const actual = mapValues(array, String)
assert.deepStrictEqual(actual, { 0: '1', 1: '2' })
})

it('should work with `_.property` shorthands', () => {
const actual = mapValues({ a: { b: 2 } }, 'b')
assert.deepStrictEqual(actual, { a: 2 })
})
})
12 changes: 12 additions & 0 deletions test/sumBy.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,16 @@ describe('sumBy', () => {
assert.strictEqual(sumBy([{ foo: 1 }, { foo: 2 }, { foo: 3 }], {}), 0)
assert.strictEqual(sumBy([{ foo: 1 }, { foo: 2 }, { foo: 3 }], null), 0)
})

it('should return 0 if array is empty', () => {
assert.strictEqual(sumBy([], (i) => i.foo), 0)
})

it('should return 0 if first argument is not an array', () => {
assert.strictEqual(sumBy({}, (i) => i.foo), 0)
})

it('should skip zero or nullable elements', () => {
assert.strictEqual(sumBy([1, 2, undefined, null, 0], (i) => i), 3)
})
})
Loading

0 comments on commit 6f4bb7c

Please sign in to comment.