├── src ├── types │ ├── Prop.ts │ ├── StringMap.ts │ ├── Spec.ts │ ├── AnyObject.ts │ ├── GenericObject.ts │ └── index.ts ├── identity.d.ts ├── last.d.ts ├── isInteger.d.ts ├── isUndefined.d.ts ├── isMap.d.ts ├── isNil.d.ts ├── isSet.d.ts ├── isNumber.d.ts ├── identity.js ├── first.d.ts ├── getType.d.ts ├── isBlankString.d.ts ├── isNil.js ├── uniq.d.ts ├── _internal │ ├── index.js │ ├── getConstructorName.js │ ├── _map.js │ ├── areAllFunctions.js │ ├── _includes.js │ └── coerce.js ├── isPrimitive.d.ts ├── isPromise.d.ts ├── isUndefined.js ├── clone.d.ts ├── isNegativeNumber.d.ts ├── isPositiveNumber.d.ts ├── last.js ├── omitUndefined.d.ts ├── values.d.ts ├── isNegativeInteger.d.ts ├── isPositiveInteger.d.ts ├── omitNull.d.ts ├── toTitleCase.d.ts ├── isNumber.js ├── first.js ├── isPrime.d.ts ├── toNumber.d.ts ├── entries.d.ts ├── isInteger.js ├── each.d.ts ├── isSameType.d.ts ├── toLowerCase.d.ts ├── toUpperCase.d.ts ├── toUriEncoded.d.ts ├── unthunk.d.ts ├── flatten.d.ts ├── has.d.ts ├── isObject.d.ts ├── forIn.d.ts ├── has.js ├── isNegativeNumber.js ├── isPositiveNumber.js ├── isStrictEqual.d.ts ├── toKebabCase.d.ts ├── intersection.d.ts ├── isMap.js ├── isSet.js ├── escapeHtml.d.ts ├── mapString.d.ts ├── toInteger.d.ts ├── isBlankString.js ├── isNegativeInteger.js ├── isPositiveInteger.js ├── invert.d.ts ├── isZero.d.ts ├── filterString.d.ts ├── difference.d.ts ├── toSnakeCase.d.ts ├── values.js ├── isEmpty.d.ts ├── contains.d.ts ├── isObjectish.d.ts ├── isPromise.js ├── uniqBy.d.ts ├── toLowerCase.js ├── toUpperCase.js ├── each.js ├── pick.d.ts ├── omit.d.ts ├── toTitleCase.js ├── mapObject.d.ts ├── concat.d.ts ├── find.d.ts ├── unthunk.js ├── entries.js ├── filterObject.d.ts ├── isSameType.js ├── any.d.ts ├── isStrictEqual.js ├── toCamelCase.d.ts ├── hasNestedProp.d.ts ├── isZero.js ├── promiseChain.d.ts ├── assign.d.ts ├── findIndex.d.ts ├── mapObjectRecursive.d.ts ├── propEquals.d.ts ├── contains.js ├── invert.js ├── size.d.ts ├── isArrayish.d.ts ├── merge.d.ts ├── promiseCompose.d.ts ├── renameKeys.d.ts ├── toNumber.js ├── forIn.js ├── getType.js ├── pipe.d.ts ├── compose.d.ts ├── propOr.d.ts ├── omitNull.js ├── omitUndefined.js ├── isEqual.d.ts ├── mapString.js ├── eitherOr.d.ts ├── propEquals.js ├── shim.d.ts ├── toSnakeCase.js ├── uniq.js ├── any.js ├── mapObject.js ├── filterString.js ├── memoize.d.ts ├── toInteger.js ├── toKebabCase.js ├── isEmpty.js ├── omit.js ├── filterObject.js ├── find.js ├── pick.js ├── propAt.d.ts ├── promiseCompose.js ├── propOr.js ├── renameKeys.js ├── combine.d.ts ├── findIndex.js ├── escapeHtml.js ├── prepend.d.ts ├── propIs.d.ts ├── size.js ├── toUriEncoded.js ├── assign.js ├── reduce.d.ts ├── toCamelCase.js ├── propIs.js ├── is.d.ts ├── curryN.d.ts ├── flatten.js ├── reduce.js ├── clone.js ├── eitherOr.js ├── curry.d.ts ├── append.d.ts ├── propSet.d.ts ├── difference.js ├── hasNestedProp.js ├── promiseAll.d.ts ├── filter.d.ts ├── cond.d.ts ├── concat.js ├── memoize.js ├── toHashCode.js ├── intersection.js ├── shim.js ├── map.d.ts ├── pipe.js ├── compose.js ├── mapObjectRecursive.js ├── prepend.js ├── combine.js ├── isPrimitive.js ├── convergeZip.d.ts ├── promiseChain.js ├── propAt.js ├── merge.js ├── isPrime.js ├── uniqBy.js ├── fuzzy.d.ts ├── is.js ├── append.js ├── converge.d.ts ├── cond.js ├── filter.js ├── convergeZip.js └── isArrayish.js ├── .gitignore ├── media ├── logo.png ├── logo_small.png ├── logo_waves.png ├── logo_sunset.png └── logo_sunset_rounded.png ├── nodemon.json ├── .npmignore ├── test ├── shim.test.js ├── omitNull.test.js ├── omitUndefined.test.js ├── benchmarks │ ├── size_object.js │ ├── escape.js │ ├── isPromise.js │ ├── size_array.js │ ├── size_string.js │ ├── invert.js │ ├── last.js │ ├── first.js │ ├── isObject.js │ ├── assign.js │ ├── any.js │ ├── isEmpty.js │ ├── flatten.js │ ├── values.js │ ├── entries.js │ ├── pipe.js │ ├── compose.js │ ├── filterObject.js │ ├── find.js │ ├── findIndex.js │ ├── mapString.js │ ├── clone.js │ ├── concat.js │ ├── cloneDeep.js │ ├── propEquals.js │ ├── propAt.js │ ├── mapObject.js │ ├── uniq.js │ ├── difference.js │ ├── map.js │ ├── uniqBy.js │ ├── filter.js │ ├── merge.js │ ├── pick.js │ ├── omit.js │ ├── isEqual.js │ └── contains.js ├── has.test.js ├── renameKeys.test.js ├── uniq.test.js ├── omit.test.js ├── values.test.js ├── clone.test.js ├── assign.test.js ├── invert.test.js ├── isZero.test.js ├── pick.test.js ├── toSnakeCase.test.js ├── isNegativeNumber.test.js ├── isPositiveNumber.test.js ├── isNegativeInteger.test.js ├── isPositiveInteger.test.js ├── size.test.js ├── difference.test.js ├── isNumber.test.js ├── contains.test.js ├── case.test.js ├── isPrimitive.test.js ├── hasNestedProp.test.js ├── toInteger.test.js ├── intersection.test.js ├── toNumber.test.js ├── escapeHtml.test.js ├── getType.test.js ├── fuzzy.test.js ├── cond.test.js ├── toUriEncoded.test.js ├── composeAndPipe.test.js ├── entries.test.js ├── combine.test.js ├── concat.test.js └── memoize.test.js ├── .babelrc ├── tsconfig.json ├── jsdoc.json └── rollup.config.js /src/types/Prop.ts: -------------------------------------------------------------------------------- 1 | export type Prop = string | number | symbol 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | docs/ 3 | coverage/ 4 | .nyc_output/ 5 | .env 6 | -------------------------------------------------------------------------------- /media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizonatribe/vanillas/HEAD/media/logo.png -------------------------------------------------------------------------------- /src/types/StringMap.ts: -------------------------------------------------------------------------------- 1 | export interface StringMap { 2 | [v: string]: string 3 | } 4 | -------------------------------------------------------------------------------- /src/types/Spec.ts: -------------------------------------------------------------------------------- 1 | export interface Spec { 2 | [v: string]: ((val: any) => any) 3 | } 4 | -------------------------------------------------------------------------------- /media/logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizonatribe/vanillas/HEAD/media/logo_small.png -------------------------------------------------------------------------------- /media/logo_waves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizonatribe/vanillas/HEAD/media/logo_waves.png -------------------------------------------------------------------------------- /src/types/AnyObject.ts: -------------------------------------------------------------------------------- 1 | export interface AnyObject { 2 | [v: string]: any | AnyObject 3 | } 4 | -------------------------------------------------------------------------------- /src/types/GenericObject.ts: -------------------------------------------------------------------------------- 1 | export type GenericObject = { 2 | [K in keyof T]: T[K] 3 | } 4 | -------------------------------------------------------------------------------- /media/logo_sunset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizonatribe/vanillas/HEAD/media/logo_sunset.png -------------------------------------------------------------------------------- /media/logo_sunset_rounded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arizonatribe/vanillas/HEAD/media/logo_sunset_rounded.png -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./StringMap" 2 | export * from "./AnyObject" 3 | export * from "./GenericObject" 4 | export * from "./Spec" 5 | export * from "./Prop" 6 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "quiet": true, 3 | "ignore": ["node_modules"], 4 | "watch": ["./test", "./src"], 5 | "execMap": { 6 | "js": "clear && tape --require=@babel/register test" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .env 2 | .eslintrc 3 | .travis.yml 4 | assets/ 5 | docs/ 6 | src/ 7 | test/ 8 | benchmark/ 9 | coverage/ 10 | jsdoc.json 11 | nodemon.json 12 | rollup.config.js 13 | BENCHMARKS.md 14 | -------------------------------------------------------------------------------- /src/identity.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A function that always returns the value passed to it 3 | * @param {*} v - A value to be returned 4 | * @returns {*} The value passed in 5 | */ 6 | export default function identity(v: T): T 7 | -------------------------------------------------------------------------------- /src/last.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Retrieves the last value from an Array 3 | * @param {Array<*>} arr - An array of any kind of values 4 | * @returns {*} The last value from a given array 5 | */ 6 | export default function last(arr: any[]): any 7 | -------------------------------------------------------------------------------- /src/isInteger.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks a value to see if it is an integer. 3 | * @param {*} val - A value (of any type) 4 | * @returns {boolean} Whether or not the value is an integer 5 | */ 6 | export default function isInteger(val: any): val is number 7 | -------------------------------------------------------------------------------- /src/isUndefined.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks to see if a value is undefined 3 | * @param {*} val - A value (of any type) 4 | * @returns {boolean} Whether or not the value is undefined 5 | */ 6 | export default function isUndefined(val: any): val is undefined 7 | -------------------------------------------------------------------------------- /src/isMap.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks to see if a value is a Map or WeakMap 3 | * @param {*} val - A value (of any type) 4 | * @returns {boolean} Whether or not the value is a Map or WeakMap 5 | */ 6 | export default function isMap(val: any): val is Map 7 | -------------------------------------------------------------------------------- /src/isNil.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks to see if a value is null OR undefined 3 | * @param {*} val - A value (of any type) 4 | * @returns {boolean} Whether or not the value is null or undefined 5 | */ 6 | export default function isNil(val: any): val is null | undefined 7 | -------------------------------------------------------------------------------- /src/isSet.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks to see if a value is a Set or WeakSet 3 | * @param {*} val - A value (of any type) 4 | * @returns {boolean} Whether or not the value is a Set or WeakSet 5 | */ 6 | export default function isSet(val: any): val is Set | WeakSet 7 | -------------------------------------------------------------------------------- /src/isNumber.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if a given value is numeric 3 | * 4 | * @function 5 | * @param {number} val A value to verify is a number 6 | * @returns {boolean} Whether or not the given value is a number 7 | */ 8 | export default function isNumber(val: any): val is number 9 | -------------------------------------------------------------------------------- /src/identity.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A function that always returns the value passed to it 3 | * 4 | * @function 5 | * @name identity 6 | * @param {*} v A value to be returned 7 | * @returns {*} The value passed in 8 | */ 9 | function identity(v) { 10 | return v 11 | } 12 | 13 | export default identity 14 | -------------------------------------------------------------------------------- /src/first.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Extracts the first value of an array of values. 3 | * @param {Array<*>} arr - An array of values from which to extract the first value 4 | * @returns {*} The value at the first index of the supplied array (which may be undefined) 5 | */ 6 | export default function first(arr: any[]): any 7 | -------------------------------------------------------------------------------- /src/getType.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Gets the type for any value. If available will inspect the constructor name, otherwise will use the typeof 3 | * @param {*} val - A value of any kind 4 | * @returns {string} The stringified representation of the value's type 5 | */ 6 | export default function getType(val: any): typeof val 7 | -------------------------------------------------------------------------------- /src/isBlankString.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks a value to see if it is a String containing either no characters OR no characters _except_ for whitespace. 3 | * @param {*} val - A value of any type 4 | * @returns {boolean} Whether or not the value is a blank string 5 | */ 6 | export default function isBlankString(val: any): val is string 7 | -------------------------------------------------------------------------------- /src/isNil.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks to see if a value is null OR undefined 3 | * 4 | * @function 5 | * @name isNil 6 | * @param {*} val A value (of any type) 7 | * @returns {boolean} Whether or not the value is null or undefined 8 | */ 9 | function isNil(val) { 10 | return val == null 11 | } 12 | 13 | export default isNil 14 | -------------------------------------------------------------------------------- /src/uniq.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Filters an array of values down to only those which are unique 3 | * @param {Array<*>} list - An array of values which may or may not contain duplicates 4 | * @returns {Array<*>} A new list containing only the unique values from the original array 5 | */ 6 | export default function uniq(list: T): T 7 | -------------------------------------------------------------------------------- /src/_internal/index.js: -------------------------------------------------------------------------------- 1 | import _map from "./_map" 2 | import _includes from "./_includes" 3 | import areAllFunctions from "./areAllFunctions" 4 | import getConstructorName from "./getConstructorName" 5 | 6 | export * from "./coerce" 7 | 8 | export { 9 | _map, 10 | _includes, 11 | areAllFunctions, 12 | getConstructorName 13 | } 14 | -------------------------------------------------------------------------------- /test/shim.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import shim from "../src/shim" 3 | 4 | tape("\"shim\" proxies objects several layers deep", t => { 5 | const shimdow = shim({location: { pathname: "wrong one" }}) 6 | t.equal(shimdow.location.pathname, "wrong one") 7 | t.doesNotThrow(() => shimdow.location.origin) 8 | t.end() 9 | }) 10 | -------------------------------------------------------------------------------- /src/isPrimitive.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if a given value is of a primitive type (ie, Boolean, String, Number, or Symbol). 3 | * @param {*} val - A value which may be of a primitive type 4 | * @returns {boolean} Whether or not the value is primitive 5 | */ 6 | export default function isPrimitive(val: any): val is boolean | number | string | Symbol 7 | -------------------------------------------------------------------------------- /src/isPromise.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if a value is a JavaScript Promise. This just means a deferred object/function with a method named `then`. 3 | * @param {*} val - A value of any type which may be a promise 4 | * @returns {boolean} Whether or not the value is a promise 5 | */ 6 | export default function isPromise(val: any): val is Promise 7 | -------------------------------------------------------------------------------- /src/isUndefined.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks to see if a value is undefined 3 | * 4 | * @function 5 | * @name isUndefined 6 | * @param {*} val A value (of any type) 7 | * @returns {boolean} Whether or not the value is undefined 8 | */ 9 | function isUndefined(val) { 10 | return val === undefined 11 | } 12 | 13 | export default isUndefined 14 | -------------------------------------------------------------------------------- /src/clone.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Recursively copies the content of an Object into a new Object 5 | * @param {object} obj - An Object (or Array) from which to create a deep copy 6 | * @returns {object} The new (cloned) Object (or Array) 7 | */ 8 | export default function clone(obj: T | T[]): T | T[] 9 | -------------------------------------------------------------------------------- /src/isNegativeNumber.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if a given numeric value is negative. 3 | * 4 | * @function 5 | * @name isNegativeNumber 6 | * @param {number} val A value to verify is a negative number 7 | * @returns {boolean} Whether or not the given value is a negative number 8 | */ 9 | export default function isNegativeNumber(val: number): boolean 10 | -------------------------------------------------------------------------------- /src/isPositiveNumber.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if a given numeric value is positive. 3 | * 4 | * @function 5 | * @name isPositiveNumber 6 | * @param {number} val A value to verify is a positive number 7 | * @returns {boolean} Whether or not the given value is a positive number 8 | */ 9 | export default function isPositiveNumber(val: number): boolean 10 | -------------------------------------------------------------------------------- /src/last.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Retrieves the last value from an Array 3 | * 4 | * @function 5 | * @name last 6 | * @param {Array<*>} arr An array of any kind of values 7 | * @returns {*} The last value from a given array 8 | */ 9 | function last(arr) { 10 | return arr.length > 0 ? arr[arr.length - 1] : undefined 11 | } 12 | 13 | export default last 14 | -------------------------------------------------------------------------------- /src/omitUndefined.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Removes all `undefined` values from a given object 5 | * @param obj - An Object from which to copy and remove undefined 6 | * @returns A copy of the original Object, but without any `undefined` values 7 | */ 8 | export default function omitUndefined(obj: T): T 9 | -------------------------------------------------------------------------------- /src/values.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * A simple polyfill for Object.values() 5 | * @param {object} obj - An Object whose values need to be retrieved 6 | * @returns {Array<*>} A list of all the values in the provided Object, ordered by keys 7 | */ 8 | export default function values(obj: T): T[keyof T][] 9 | -------------------------------------------------------------------------------- /test/omitNull.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import omitNull from "../src/omitNull" 3 | 4 | tape("\"omitNull\" removes `null` or `undefined` values from an object", t => { 5 | t.deepEqual( 6 | omitNull({lorem: "ipsum", dolor: "sit", amet: null, consectetur: undefined }), 7 | {lorem: "ipsum", dolor: "sit" } 8 | ) 9 | t.end() 10 | }) 11 | -------------------------------------------------------------------------------- /src/isNegativeInteger.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if a given numeric value is a negative integer. 3 | * 4 | * @function 5 | * @name isNegativeInteger 6 | * @param {number} val A value to verify is a negative integer 7 | * @returns {boolean} Whether or not the given value is a negative integer 8 | */ 9 | export default function isNegativeInteger(val: number): boolean 10 | -------------------------------------------------------------------------------- /src/isPositiveInteger.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if a given numeric value is a positive integer. 3 | * 4 | * @function 5 | * @name isPositiveInteger 6 | * @param {number} val A value to verify is a positive integer 7 | * @returns {boolean} Whether or not the given value is a positive integer 8 | */ 9 | export default function isPositiveInteger(val: number): boolean 10 | -------------------------------------------------------------------------------- /src/omitNull.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Removes all `null` or `undefined` values from a given object 5 | * @param obj - An Object from which to copy and remove null/undefined 6 | * @returns A copy of the original Object, but without any `null` or `undefined` values 7 | */ 8 | export default function omitNull(obj: T): T 9 | -------------------------------------------------------------------------------- /src/toTitleCase.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Transforms a string value into one which is title-cased. The first letter of any word is capitalized. 3 | * @param {string} str - A string which may contain uppercase characters 4 | * @returns {string} A new string that is an lowercase representation of the original string 5 | */ 6 | export default function toTitleCase(str: string): string 7 | -------------------------------------------------------------------------------- /src/isNumber.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if a given value is numeric 3 | * 4 | * @function 5 | * @name isNumber 6 | * @param {number} val A value to verify is a number 7 | * @returns {boolean} Whether or not the given value is a number 8 | */ 9 | function isNumber(val) { 10 | return typeof val === "number" && Number.isFinite(val) 11 | } 12 | 13 | export default isNumber 14 | -------------------------------------------------------------------------------- /test/omitUndefined.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import omitUndefined from "../src/omitUndefined" 3 | 4 | tape("\"omitUndefined\" removes `undefined` values from an object", t => { 5 | t.deepEqual( 6 | omitUndefined({lorem: "ipsum", dolor: "sit", amet: null, consectetur: undefined }), 7 | {lorem: "ipsum", dolor: "sit", amet: null } 8 | ) 9 | t.end() 10 | }) 11 | -------------------------------------------------------------------------------- /src/first.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Extracts the first value of an array of values. 3 | * 4 | * @function 5 | * @name first 6 | * @param {Array<*>} val An array of values from which to extract the first value 7 | * @returns {*} The value at the first index of the supplied array (which may be undefined) 8 | */ 9 | function first(val) { 10 | return val[0] 11 | } 12 | 13 | export default first 14 | -------------------------------------------------------------------------------- /src/isPrime.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if a given numeric value is a prime number. 3 | * This is any integer value divisible only by itself and 1. 4 | * 5 | * @function 6 | * @name isPrime 7 | * @param {number} val A value to verify is a prime number 8 | * @returns {boolean} Whether or not the given value is a prime number 9 | */ 10 | export default function isPrime(val: any): boolean 11 | -------------------------------------------------------------------------------- /src/toNumber.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Coerces a given string value to a number (if valid). 3 | * 4 | * @function 5 | * @name toNumber 6 | * @param {string} val A string value to coerce to a number 7 | * @returns {number|undefined} A numeric representation of the original value, or `undefined` if it cannot be coerced to a number. 8 | */ 9 | export default function toNumber(val: string): number | undefined 10 | -------------------------------------------------------------------------------- /src/entries.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Extracts an Array of key/value pairs from an Object. 5 | * @param {object} obj - The input object from which to extract prop keys and values 6 | * @returns {Array} An Array of key/value pairs corresponding to those on the input object 7 | */ 8 | export default function entries(obj: T): (keyof T | T[keyof T])[] 9 | -------------------------------------------------------------------------------- /src/isInteger.js: -------------------------------------------------------------------------------- 1 | import isNumber from "./isNumber" 2 | 3 | /** 4 | * Checks a value to see if it is an integer. 5 | * 6 | * @function 7 | * @name isInteger 8 | * @param {*} val A value (of any type) 9 | * @returns {boolean} Whether or not the value is an integer 10 | */ 11 | function isInteger(val) { 12 | return isNumber(val) && Number.isInteger(val) 13 | } 14 | 15 | export default isInteger 16 | -------------------------------------------------------------------------------- /test/benchmarks/size_object.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import { size } from "../../src" 4 | import { solarSystem } from "../__mocks__" 5 | 6 | test("sizeObject", benchmark => { 7 | benchmark( 8 | () => size(solarSystem), 9 | "Vanillas \"size\"" 10 | ) 11 | benchmark( 12 | () => _.size(solarSystem), 13 | "Lodash \"size\"" 14 | ) 15 | }) 16 | -------------------------------------------------------------------------------- /src/each.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A faster forEach that provides the same API as native. 3 | * @param {function} fn - A Function to execute for each iteration. It will receive the value, index and full array (respectively) as args 4 | * @param {Array<*>} arr - An Array to iterate over (any value will be passed into the iterate Function) 5 | */ 6 | export default function each(fn: (...params: any[]) => any, arr: any[]): void 7 | -------------------------------------------------------------------------------- /src/isSameType.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Inspects two values to see if they are the same type. 3 | * The typeof and (if necessary) constructor names are inspected during this check. 4 | * @param {*} val1 - A value (of any type) 5 | * @param {*} val2 - A value (of any type) 6 | * @returns {boolean} Whether or not the two values are of the same type 7 | */ 8 | export default function isSameType(val1: any, val2: any): boolean 9 | -------------------------------------------------------------------------------- /src/toLowerCase.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple wrapper around String.prototype.toLowerCase() that is provided for consistency with the non-native string case methods (`toKebabCase()`, `toCamelCase()`, etc) 3 | * @param {string} str - A string which may contain uppercase characters 4 | * @returns {string} A new string that is an lowercase representation of the original string 5 | */ 6 | export default function toLowerCase(str: string): string 7 | -------------------------------------------------------------------------------- /src/toUpperCase.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple wrapper around String.prototype.toUpperCase() that is provided for consistency with the non-native string case methods (`toKebabCase()`, `toCamelCase()`, etc) 3 | * @param {string} str - A string which may contain lowercase characters 4 | * @returns {string} A new string that is an uppercase representation of the original string 5 | */ 6 | export default function toUpperCase(str: string): string 7 | -------------------------------------------------------------------------------- /src/toUriEncoded.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Transforms an object's key/value pairs into an encoded URI string, delimited by ampersands & 5 | * @param {object} obj - An object whose key/value pairs need to be serialized into a single string. 6 | * @returns {string} A new string that represents the key/value pairs on the originating object 7 | */ 8 | export default function toUriEncoded(obj: AnyObject): string 9 | -------------------------------------------------------------------------------- /src/unthunk.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Transforms a thunk (of however many levels deep) into a single function that will received all the args at once 3 | * @param {function} thunk - A function that returns a function (which may return a function, and so on) 4 | * @returns {function} A single function that is ready to receive all the arguments at once 5 | */ 6 | export default function unthunk(thunk: (...params: any[]) => any): (...params: any[]) => any 7 | -------------------------------------------------------------------------------- /src/flatten.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Extracts nested arrays (of any depth) from a provided array, placing them onto on single new array. 3 | * @param {Array|*>} arr - An array of values that may or may not be nested arrays themselves 4 | * @returns {Array<*>} A new array of values, but with any nested arrays from the original input extracted onto one single (flat) array 5 | */ 6 | export default function flatten(arr: T | T[]): T 7 | -------------------------------------------------------------------------------- /test/benchmarks/escape.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import { escapeHtml } from "../../src" 4 | 5 | const val = "don't take my & mis-use it for \"evil\"" 6 | 7 | test("escape", benchmark => { 8 | benchmark( 9 | () => escapeHtml(val), 10 | "Vanillas \"escapeHtml\"" 11 | ) 12 | benchmark( 13 | () => _.escape(val), 14 | "Lodash \"escape\"" 15 | ) 16 | }) 17 | -------------------------------------------------------------------------------- /src/has.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Checks if a given Object contains a specified prop name 5 | * @param {string} key - A prop name to look for in the object 6 | * @param {object} obj - An Object to inspect for a given prop 7 | * @returns {boolean} Whether the object contains the specified prop 8 | */ 9 | export function has(key: string | number | Symbol, obj: T): key is K 10 | -------------------------------------------------------------------------------- /src/isObject.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * A high-speed, mostly adequate check of a value which may be an Object. 5 | * This excludes values that are _technically_ an Object but in practice are not what you _really_ mean when you speak of Objects. 6 | * @param val - A value (of any type) 7 | * @returns Whether or not the value is an Object 8 | */ 9 | export default function isObject(val: any): val is AnyObject | {} 10 | -------------------------------------------------------------------------------- /src/forIn.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * A light wrapper around native `for .. in`, but will only iterate over an Object's own properties. 5 | * @param {function} fn - A function to execute iteratively, which will receive the `key`, `value`, and `object` (respectively) 6 | * @param {object} obj - An object whose keys will be iterated over 7 | */ 8 | export default function forIn(fn: (...params: any[]) => any, obj: AnyObject): void 9 | -------------------------------------------------------------------------------- /src/has.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if a given Object contains a specified prop name 3 | * 4 | * @function 5 | * @name has 6 | * @param {string} key A prop name to look for in the object 7 | * @param {object} obj An Object to inspect for a given prop 8 | * @returns {boolean} Whether the object contains the specified prop 9 | */ 10 | function has(key, obj) { 11 | return Object.prototype.hasOwnProperty.call(obj, key) 12 | } 13 | 14 | export default has 15 | -------------------------------------------------------------------------------- /src/isNegativeNumber.js: -------------------------------------------------------------------------------- 1 | import isNumber from "./isNumber" 2 | 3 | /** 4 | * Checks if a given numeric value is negative. 5 | * 6 | * @function 7 | * @name isNegativeNumber 8 | * @param {number} val A value to verify is a negative number 9 | * @returns {boolean} Whether or not the given value is a negative number 10 | */ 11 | function isNegativeNumber(val) { 12 | return isNumber(val) && +val < 0 13 | } 14 | 15 | export default isNegativeNumber 16 | -------------------------------------------------------------------------------- /src/isPositiveNumber.js: -------------------------------------------------------------------------------- 1 | import isNumber from "./isNumber" 2 | 3 | /** 4 | * Checks if a given numeric value is positive. 5 | * 6 | * @function 7 | * @name isPositiveNumber 8 | * @param {number} val A value to verify is a positive number 9 | * @returns {boolean} Whether or not the given value is a positive number 10 | */ 11 | function isPositiveNumber(val) { 12 | return isNumber(val) && +val > 0 13 | } 14 | 15 | export default isPositiveNumber 16 | -------------------------------------------------------------------------------- /src/isStrictEqual.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Inspects two values to see if they are strictly equal, meaning no type coercion or deepyly nested equality checks are performed. 3 | * A very simple triple equals is all that is used. 4 | * @param {*} val1 - A value (of any type) 5 | * @param {*} val2 - A value (of any type) 6 | * @returns {boolean} Whether or not the two values are strictly equal 7 | */ 8 | export default function isStrictEqual(val1: any, val2: any): boolean 9 | -------------------------------------------------------------------------------- /src/toKebabCase.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Transforms a string value into one which is hyphenated. 3 | * Whitespace and underscores are replaced with hyphens, and uppercase letters are interpreted as boundaries for new hyphenated words. 4 | * @param {string} str - A string which may contain uppercase characters 5 | * @returns {string} A new string that is a hyphenated representation of the original string 6 | */ 7 | export default function toKebabCase(str: string): string 8 | -------------------------------------------------------------------------------- /src/intersection.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Compares two lists of Strings/Numbers and returns the values that are in common (intersect) between the two lists 3 | * @param {Array} arr1 - An Array of Strings/Numbers 4 | * @param {Array} arr2 - An Array of Strings/Numbers 5 | * @returns {Array} The values in common between the two lists 6 | */ 7 | export default function intersection(arr1: T, arr2: T): T 8 | -------------------------------------------------------------------------------- /src/isMap.js: -------------------------------------------------------------------------------- 1 | import getConstructorName from "./_internal/getConstructorName" 2 | 3 | /** 4 | * Checks to see if a value is a Map or WeakMap 5 | * 6 | * @function 7 | * @name isMap 8 | * @param {*} val A value (of any type) 9 | * @returns {boolean} Whether or not the value is a Map or WeakMap 10 | */ 11 | function isMap(val) { 12 | return getConstructorName(val) === "Map" || getConstructorName(val) === "WeakMap" 13 | } 14 | 15 | export default isMap 16 | -------------------------------------------------------------------------------- /src/isSet.js: -------------------------------------------------------------------------------- 1 | import getConstructorName from "./_internal/getConstructorName" 2 | 3 | /** 4 | * Checks to see if a value is a Set or WeakSet 5 | * 6 | * @function 7 | * @name isSet 8 | * @param {*} val A value (of any type) 9 | * @returns {boolean} Whether or not the value is a Set or WeakSet 10 | */ 11 | function isSet(val) { 12 | return getConstructorName(val) === "Set" || getConstructorName(val) === "WeakSet" 13 | } 14 | 15 | export default isSet 16 | -------------------------------------------------------------------------------- /src/escapeHtml.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts ampersands, angle brackets, apostrophes and blockquotes to their HTML encoded equivalents 3 | * 4 | * @function 5 | * @name escapeHtml 6 | * @param {string} val A string values to escape 7 | * @returns {string} The original value (converted to string) and with any of the unallowed characters properly escaped (null/undefined values are converted to '') 8 | */ 9 | export default function escapeHtml(val: string): string 10 | -------------------------------------------------------------------------------- /test/has.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import has from "../src/has" 3 | import { has as hasCurried } from "../src/curried" 4 | 5 | tape("\"has\" inspects own props on a given object for a specified key", t => { 6 | t.equal(has("orange", {red: "blue", orange: "purple"}), true) 7 | t.equal(hasCurried("orange")({red: "blue", orange: "purple"}), true, "can be curried") 8 | t.equal(has("green", {red: "blue", orange: "purple"}), false) 9 | t.end() 10 | }) 11 | -------------------------------------------------------------------------------- /src/mapString.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Applies a mapping function you provide over every character in a given string. 3 | * @param {function} fn - A mapping function that is invoked on every char in the provided String value 4 | * @param {string} str - A string value to map over 5 | * @returns {string} A new String value that is the result of the mapping operation over the original string 6 | */ 7 | export default function mapString(fn: (...params: any[]) => any, str: string): string 8 | -------------------------------------------------------------------------------- /src/toInteger.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts a given value to an Integer and rounds up or down floating point values appropriately. 3 | * If the values is a `Boolean`, then `true` will yield `1` and `false will yield `0`. 4 | * If `NaN` then zero will always be returned. 5 | * @param {string} str - A string which may be numeric 6 | * @returns {number} Either the successfully converted number or zero (if it was NaN) 7 | */ 8 | export default function toInteger(str: string): number 9 | -------------------------------------------------------------------------------- /test/benchmarks/isPromise.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import ispromise from "is-promise" 3 | import { isPromise } from "../../src" 4 | 5 | const val = { 6 | catch() { }, 7 | then() { }, 8 | finally() { } 9 | } 10 | 11 | test("isPromise", benchmark => { 12 | benchmark( 13 | () => isPromise(val), 14 | "Vanillas \"isPromise\"" 15 | ) 16 | benchmark( 17 | () => ispromise(val), 18 | "is-promise \"isPromise\"" 19 | ) 20 | }) 21 | -------------------------------------------------------------------------------- /src/isBlankString.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks a value to see if it is a String containing either no characters OR no characters _except_ for whitespace. 3 | * 4 | * @function 5 | * @name isBlankString 6 | * @param {*} val A value of any type 7 | * @returns {boolean} Whether or not the value is a blank string 8 | */ 9 | function isBlankString(val) { 10 | return typeof val === "string" && (val.length === 0 || /^\s*$/.test(val)) 11 | } 12 | 13 | export default isBlankString 14 | -------------------------------------------------------------------------------- /src/isNegativeInteger.js: -------------------------------------------------------------------------------- 1 | import isInteger from "./isInteger" 2 | 3 | /** 4 | * Checks if a given numeric value is a negative integer. 5 | * 6 | * @function 7 | * @name isNegativeInteger 8 | * @param {number} val A value to verify is a negative integer 9 | * @returns {boolean} Whether or not the given value is a negative integer 10 | */ 11 | function isNegativeInteger(val) { 12 | return isInteger(val) && +val < 0 13 | } 14 | 15 | export default isNegativeInteger 16 | -------------------------------------------------------------------------------- /src/isPositiveInteger.js: -------------------------------------------------------------------------------- 1 | import isInteger from "./isInteger" 2 | 3 | /** 4 | * Checks if a given numeric value is a positive integer. 5 | * 6 | * @function 7 | * @name isPositiveInteger 8 | * @param {number} val A value to verify is a positive integer 9 | * @returns {boolean} Whether or not the given value is a positive integer 10 | */ 11 | function isPositiveInteger(val) { 12 | return isInteger(val) && +val > 0 13 | } 14 | 15 | export default isPositiveInteger 16 | -------------------------------------------------------------------------------- /src/invert.d.ts: -------------------------------------------------------------------------------- 1 | import { StringMap } from "./types" 2 | 3 | /** 4 | * Swaps the values for keys in a given object. So the values in that object should be the kind that _can_ be converted to unique string values 5 | * @param {object} obj - An object whose values _can_ be swapped for keys 6 | * @returns {object} A new object whose keys were the values from the original object 7 | */ 8 | export default function flipKeyValues(obj: T): { [k in T[keyof T]]: keyof T } 9 | -------------------------------------------------------------------------------- /src/isZero.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if a given value is zero or a string representing zero. 3 | * For string values, what we want is "0" or "0.0" but not "" or " " (which also coerce to zero). 4 | * 5 | * @function 6 | * @name isZero 7 | * @param {number|string} val A value to verify is zero or a zero-like string. 8 | * @returns {boolean} Whether or not the given value is zero or a zero-like string. 9 | */ 10 | export default function isZero(val: any): val is 0 | "0" | "0.0" 11 | -------------------------------------------------------------------------------- /src/filterString.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Applies a filtering function you provide over every character in a given string. 3 | * @param {function} fn - A filtering function that is invoked on every char in the provided String value 4 | * @param {string} str - A string value to filter over 5 | * @returns {string} A new String value that is the result of the filtering operation over the original string 6 | */ 7 | export default function filterString(fn: (...params: any[]) => any, str: string): string 8 | -------------------------------------------------------------------------------- /test/renameKeys.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import renameKeys from "../src/renameKeys" 3 | 4 | tape("\"renameKeys\" changes the names of any specified keys to new names", t => { 5 | t.deepEqual( 6 | renameKeys({lorem: "Lorem"}, {lorem: "ipsum"}), 7 | { Lorem: "ipsum"} 8 | ) 9 | t.deepEqual( 10 | renameKeys({dolor: "dollar"}, {lorem: "ipsum", dolor: "sit", amet: "yeah"}), 11 | { lorem: "ipsum", dollar: "sit", amet: "yeah"} 12 | ) 13 | t.end() 14 | }) 15 | -------------------------------------------------------------------------------- /src/difference.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Compares two lists of Strings/Numbers and returns the values that are different between the two lists 3 | * @param {Array} arr1 - An Array of Strings/Numbers 4 | * @param {Array} arr2 - An Array of Strings/Numbers 5 | * @returns {Array} An array of values that are different between the two lists 6 | */ 7 | export default function difference( 8 | arr1: T, 9 | arr2: T 10 | ): T 11 | -------------------------------------------------------------------------------- /src/toSnakeCase.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Transforms a string value into one which is separated by underscores. 3 | * Whitespace and hyphens are replaced with underscores, and uppercase letters are interpreted as boundaries for new underscore-separated words. 4 | * @param {string} str - A string which may contain uppercase characters or hyphens 5 | * @returns {string} A new string that is an lowercase representation of the original string 6 | */ 7 | export default function toSnakeCase(str: string): string 8 | -------------------------------------------------------------------------------- /src/values.js: -------------------------------------------------------------------------------- 1 | import forIn from "./forIn" 2 | 3 | /** 4 | * A simple polyfill for Object.values() 5 | * 6 | * @function 7 | * @name values 8 | * @param {object} obj An Object whose values need to be retrieved 9 | * @returns {Array<*>} A list of all the values in the provided Object, ordered by keys 10 | */ 11 | function values(obj) { 12 | const arr = [] 13 | forIn((key, val) => { 14 | arr.push(val) 15 | }, obj) 16 | return arr 17 | } 18 | 19 | export default values 20 | -------------------------------------------------------------------------------- /test/uniq.test.js: -------------------------------------------------------------------------------- 1 | import test from "tape" 2 | import uniq from "../src/uniq" 3 | 4 | test("\"uniq\" makes sure a list contains only unique values", t => { 5 | t.deepEqual(uniq([1, 1, 1, 3, 3, 3, 5, 5, 5]), [1, 3, 5], "duplicate numbers removed") 6 | t.deepEqual( 7 | uniq(["abc", "abcdefg", "abcdefg", "hijklmnop", "qrs", "tuv", "tuv", "wxy&z"]), 8 | ["abc", "abcdefg", "hijklmnop", "qrs", "tuv", "wxy&z"], 9 | "duplicate strings are removed" 10 | ) 11 | t.end() 12 | }) 13 | -------------------------------------------------------------------------------- /src/isEmpty.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if a value is empty. Arrays, Objects, Strings, Sets, and Null/Undefined values are considered empty if their length (or size) prop is zero (or if they are Null or Undefined). 3 | * Whitespace-only strings are NOT considered empty (use `isBlankString` instead). 4 | * @param {*} val - A value of any type which may be considered empty 5 | * @returns {boolean} Whether or not the value is empty 6 | */ 7 | export default function isEmpty(val: any): val is null | undefined 8 | -------------------------------------------------------------------------------- /test/benchmarks/size_array.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import R from "ramda" 4 | import { size } from "../../src" 5 | 6 | const val = [1, 2, 3, 4, 5, 6, 7, 8, 9] 7 | 8 | test("size", benchmark => { 9 | benchmark( 10 | () => size(val), 11 | "Vanillas \"size\"" 12 | ) 13 | benchmark( 14 | () => _.size(val), 15 | "Lodash \"size\"" 16 | ) 17 | benchmark( 18 | () => R.length(val), 19 | "Ramda \"length\"" 20 | ) 21 | }) 22 | -------------------------------------------------------------------------------- /test/omit.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import omit from "../src/omit" 3 | 4 | tape("\"omit\" removes specified props from an object", t => { 5 | t.deepEqual(omit(["lorem"], {lorem: "ipsum"}), {}) 6 | t.deepEqual( 7 | omit(["lorem", "dolor"], {lorem: "ipsum", dolor: "sit amet", consectetur: "adipiscing elit"}), 8 | {consectetur: "adipiscing elit"} 9 | ) 10 | t.deepEqual(omit([], {lorem: "ipsum"}), {lorem: "ipsum"}, "handles no props gracefully") 11 | t.end() 12 | }) 13 | -------------------------------------------------------------------------------- /src/_internal/getConstructorName.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Safely retrieves the constructor name for the native JavaScript type 3 | * 4 | * @function 5 | * @private 6 | * @name getConstructorName 7 | * @param {*} val A value of any type 8 | * @returns {string} The constructor name (if there is one) for the value 9 | */ 10 | function getConstructorName(val) { 11 | try { 12 | return val.constructor.name 13 | } catch (err) { 14 | return "" 15 | } 16 | } 17 | 18 | export default getConstructorName 19 | -------------------------------------------------------------------------------- /src/contains.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if a given value is present in a String OR Array 3 | * @param {string|number} val - A value which may be present in the String/Array 4 | * @param {Array|string} arr - An Array or String which may contain the provided value 5 | * @returns {boolean} Whether or not the String|Array contains the provided value 6 | */ 7 | export default function contains( 8 | val: string | number, 9 | arr: readonly string[] | readonly number[] | string 10 | ): boolean 11 | -------------------------------------------------------------------------------- /src/isObjectish.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tests whether or not a given value is object-like 3 | * This means a hashmap, map, weak map, object literal, instantiated custom class, 4 | * but _not_ some of the kinds of constructs which JavaScript considers to be technically an object (Error, Array, Null, Function, Date, etc.) 5 | * @param {*} val - A value that may or may not be object-like 6 | * @returns {boolean} Whether or not the value is object-like 7 | */ 8 | export default function isObjectish(val: any): boolean 9 | -------------------------------------------------------------------------------- /test/benchmarks/size_string.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import R from "ramda" 4 | import { size } from "../../src" 5 | 6 | const val = "lorem ipsum dolor sit amet" 7 | 8 | test("sizeString", benchmark => { 9 | benchmark( 10 | () => size(val), 11 | "Vanillas \"size\"" 12 | ) 13 | benchmark( 14 | () => _.size(val), 15 | "Lodash \"size\"" 16 | ) 17 | benchmark( 18 | () => R.length(val), 19 | "Ramda \"length\"" 20 | ) 21 | }) 22 | -------------------------------------------------------------------------------- /src/isPromise.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if a value is a JavaScript Promise. This just means a deferred object/function with a method named `then`. 3 | * 4 | * @function 5 | * @name isPromise 6 | * @param {*} val A value of any type which may be a promise 7 | * @returns {boolean} Whether or not the value is a promise 8 | */ 9 | function isPromise(val) { 10 | return val !== null && (typeof val === "function" || typeof val === "object") && typeof val.then === "function" 11 | } 12 | 13 | export default isPromise 14 | -------------------------------------------------------------------------------- /test/benchmarks/invert.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import R from "ramda" 4 | import * as raw from "radash" 5 | import { invert } from "../../src" 6 | import { hook } from "../__mocks__" 7 | 8 | test("invert", (benchmark) => { 9 | benchmark(() => invert(hook), "Vanillas \"invert\"") 10 | benchmark(() => _.invert(hook), "Lodash \"invert\"") 11 | benchmark(() => raw.invert(hook), "Radash \"invert\"") 12 | benchmark(() => R.invertObj(hook), "Ramda \"invertObj\"") 13 | }) 14 | -------------------------------------------------------------------------------- /test/benchmarks/last.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import * as raw from "radash" 4 | import R from "ramda" 5 | import { last } from "../../src" 6 | import { companies } from "../__mocks__" 7 | 8 | test("last", (benchmark) => { 9 | benchmark(() => last(companies), "Vanillas \"last\"") 10 | benchmark(() => _.last(companies), "Lodash \"last\"") 11 | benchmark(() => R.last(companies), "Ramda \"last\"") 12 | benchmark(() => raw.last(companies), "Radash \"last\"") 13 | }) 14 | -------------------------------------------------------------------------------- /src/uniqBy.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Filters an array of values down to only those which are unique, based on a provided predicate function (or shorthand for retrieving a prop inside an object) 3 | * @param {function} pred - A predicate function 4 | * @param {Array<*>} list - An array of values which may or may not contain duplicates 5 | * @returns {Array<*>} A new list containing only the unique values from the original array 6 | */ 7 | export default function uniqBy(pred: ((...params: any[]) => any) | string, list: T): T 8 | -------------------------------------------------------------------------------- /src/toLowerCase.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple wrapper around String.prototype.toLowerCase() that is provided for consistency with the non-native string case methods (`toKebabCase()`, `toCamelCase()`, etc) 3 | * 4 | * @function 5 | * @name toLowerCase 6 | * @param {string} str A string which may contain uppercase characters 7 | * @returns {string} A new string that is an lowercase representation of the original string 8 | */ 9 | function toLowerCase(str) { 10 | return str.toLowerCase() 11 | } 12 | 13 | export default toLowerCase 14 | -------------------------------------------------------------------------------- /src/toUpperCase.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple wrapper around String.prototype.toUpperCase() that is provided for consistency with the non-native string case methods (`toKebabCase()`, `toCamelCase()`, etc) 3 | * 4 | * @function 5 | * @name toUpperCase 6 | * @param {string} str A string which may contain lowercase characters 7 | * @returns {string} A new string that is an uppercase representation of the original string 8 | */ 9 | function toUpperCase(str) { 10 | return str.toUpperCase() 11 | } 12 | 13 | export default toUpperCase 14 | -------------------------------------------------------------------------------- /test/benchmarks/first.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import * as raw from "radash" 4 | import R from "ramda" 5 | import { first } from "../../src" 6 | import { companies } from "../__mocks__" 7 | 8 | test("first", (benchmark) => { 9 | benchmark(() => first(companies), "Vanillas \"first\"") 10 | benchmark(() => _.first(companies), "Lodash \"first\"") 11 | benchmark(() => R.head(companies), "Ramda \"first\"") 12 | benchmark(() => raw.first(companies), "Radash \"first\"") 13 | }) 14 | -------------------------------------------------------------------------------- /test/benchmarks/isObject.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import * as Fx from "fxjs" 4 | import { isObject } from "../../src" 5 | import { hook } from "../__mocks__" 6 | 7 | test("isObject", benchmark => { 8 | benchmark( 9 | () => isObject(hook), 10 | "Vanillas \"isObject\"" 11 | ) 12 | benchmark( 13 | () => Fx.isObject(hook), 14 | "FxJs \"isObject\"" 15 | ) 16 | benchmark( 17 | () => _.isPlainObject(hook), 18 | "Lodash \"isPlainObject\"" 19 | ) 20 | }) 21 | -------------------------------------------------------------------------------- /test/values.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import values from "../src/values" 3 | import { hook } from "./__mocks__" 4 | 5 | tape("\"values\" retrieves all the values from an Object, returning as a list", t => { 6 | t.deepEqual(values(undefined), [], "handles undefined") 7 | t.deepEqual(values({}), [], "empty objects means empty array") 8 | t.deepEqual( 9 | values(hook), 10 | ["dustin", "robin", "julia", "bob"], 11 | "all the values from the object are placed into an array" 12 | ) 13 | t.end() 14 | }) 15 | -------------------------------------------------------------------------------- /src/each.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A faster forEach that provides the same API as native. 3 | * 4 | * @function 5 | * @name each 6 | * @param {function} fn A Function to execute for each iteration. It will receive the value, index and full array (respectively) as args 7 | * @param {Array<*>} arr An Array to iterate over (any value will be passed into the iterate Function) 8 | */ 9 | function each(fn, arr) { 10 | for (let i = 0, count = arr.length; i < count; i++) { 11 | fn(arr[i], i, arr) 12 | } 13 | } 14 | 15 | export default each 16 | -------------------------------------------------------------------------------- /src/pick.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Removes everything _except_ the specified keys from an object (after cloning the Object). 5 | * @param {Array} keys - An array of keys to search for in the Object and include from the output 6 | * @param {object} obj - An Object from which to copy and remove keys 7 | * @returns {object} A copy of the original Object, but with _only_ the specified keys 8 | */ 9 | export default function pick(keys: K[], obj: T): Pick 10 | -------------------------------------------------------------------------------- /src/omit.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject, Prop } from "./types" 2 | 3 | /** 4 | * Removes specified keys from an object (after cloning the Object). 5 | * @param {Array} keys - An array of keys to search for in the Object and exclude from the output 6 | * @param {object} obj - An Object from which to copy and remove keys 7 | * @returns {object} A copy of the original Object, but without the specified keys 8 | */ 9 | export default function omit(keys: K[], obj: T): Pick> 10 | 11 | -------------------------------------------------------------------------------- /src/toTitleCase.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Transforms a string value into one which is title-cased. The first letter of any word is capitalized. 3 | * 4 | * @function 5 | * @name toTitleCase 6 | * @param {string} str A string which may contain uppercase characters 7 | * @returns {string} A new string that is an lowercase representation of the original string 8 | */ 9 | function toTitleCase(str) { 10 | return str.split(/\s/) 11 | .map(s => `${s.charAt(0).toUpperCase()}${s.slice(1)}`) 12 | .join(" ") 13 | } 14 | 15 | export default toTitleCase 16 | -------------------------------------------------------------------------------- /src/mapObject.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Applies a mapping function you provide over every value in a given Object. 5 | * @param {function} fn - A mapping function that is invoked on every value in the provided Object 6 | * @param {object} obj - An Object whose values will be mapped over 7 | * @returns {object} A new Object that is the result of the mapping operation over all the values in the original Object 8 | */ 9 | export default function mapObject(fn: (...params: any[]) => any, obj: T): T 10 | -------------------------------------------------------------------------------- /src/concat.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Adds the values from one Array onto another Array, returned as a new Array (ie, it does not mutate the first Array). 3 | * This operation is recursive, so you can supply as many arrays as you wish. 4 | * @param {Array<*>} firstArr - An Array of values (of any type) 5 | * @param {Array<*>} secondArr - An Array of values (of any type) 6 | * @returns {Array<*>} A new Array with the values from the second array concatenated onto those from the first 7 | */ 8 | export default function concat(firstArr: T, secondArr: T): T 9 | -------------------------------------------------------------------------------- /src/find.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Find a single value from an array of values, based on criteria defined in a predicate function. 3 | * @param {function} pred - A predicate function to apply to the array of values (It should take a val as input and return a Boolean as output). 4 | * @param {Array<*>} arr - An array of values from which to find one particular matching value 5 | * @returns {*} Either a value from the array that matched the predicate function or undefined (if no match) 6 | */ 7 | export default function find(pred: (...params: any[]) => any, arr: any[]): any 8 | -------------------------------------------------------------------------------- /src/unthunk.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Transforms a thunk (of however many levels deep) into a single function that will received all the args at once 3 | * 4 | * @function 5 | * @name unthunk 6 | * @param {function} thunk A function that returns a function (which may return a function, and so on) 7 | * @returns {function} A single function that is ready to receive all the arguments at once 8 | */ 9 | function unthunk(thunk) { 10 | return function inner(...args) { 11 | return args.reduce((f, a) => f(a), thunk) 12 | } 13 | } 14 | 15 | export default unthunk 16 | -------------------------------------------------------------------------------- /src/entries.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Extracts an Array of key/value pairs from an Object. 3 | * 4 | * @function 5 | * @name entries 6 | * @param {object} obj The input object from which to extract prop keys and values 7 | * @returns {Array} An Array of key/value pairs corresponding to those on the input object 8 | */ 9 | function entries(obj) { 10 | const keys = Object.keys(obj) 11 | const len = keys.length 12 | for (let i = 0; i < len; i++) { 13 | keys[i] = [keys[i], obj[keys[i]]] 14 | } 15 | return keys 16 | } 17 | 18 | export default entries 19 | -------------------------------------------------------------------------------- /src/filterObject.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Applies a filtering function you provide over every value in a given Object. 5 | * @param {function} fn - A filtering function that is invoked on every value in the provided Object 6 | * @param {object} obj - An Object whose values will be filtered 7 | * @returns {object} A new Object that is the result of the filtering operation over all the values in the original Object 8 | */ 9 | export default function filterObject(fn: (...params: any[]) => any, obj: T): T 10 | -------------------------------------------------------------------------------- /src/isSameType.js: -------------------------------------------------------------------------------- 1 | import getType from "./getType" 2 | 3 | /** 4 | * Inspects two values to see if they are the same type. 5 | * The typeof and (if necessary) constructor names are inspected during this check. 6 | * 7 | * @function 8 | * @name isSameType 9 | * @param {*} val1 A value (of any type) 10 | * @param {*} val2 A value (of any type) 11 | * @returns {boolean} Whether or not the two values are of the same type 12 | */ 13 | function isSameType(val1, val2) { 14 | return getType(val1) === getType(val2) 15 | } 16 | 17 | export default isSameType 18 | -------------------------------------------------------------------------------- /src/any.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Check an Array of items (of any type) to see if any item satisfies a given predicate function. 3 | * Exits when the first match is found. 4 | * @param {function} pred - A predicate function to evaluate against each item in a given array 5 | * @param {Array<*>} arr - An array of items to evaluate against the predicate function 6 | * @returns {boolean} Whether or not any items in the array matched the predicate function 7 | */ 8 | export default function any( 9 | pred: (...params: any[]) => any, 10 | arr: readonly any[] 11 | ): boolean 12 | -------------------------------------------------------------------------------- /src/isStrictEqual.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Inspects two values to see if they are strictly equal, meaning no type coercion or deepyly nested equality checks are performed. 3 | * A very simple triple equals is all that is used. 4 | * 5 | * @function 6 | * @name isStrictEqual 7 | * @param {*} firstVal A value (of any type) 8 | * @param {*} secondVal A value (of any type) 9 | * @returns {boolean} Whether or not the two values are strictly equal 10 | */ 11 | function isStrictEqual(firstVal, secondVal) { 12 | return firstVal === secondVal 13 | } 14 | 15 | export default isStrictEqual 16 | -------------------------------------------------------------------------------- /src/toCamelCase.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Transforms a string value into one which is hyphenated. 3 | * Hyphens and underscores are removed and interpred as the boundaries for new words. 4 | * The first letter of each new word - not preceded by whitespace - is capitalized. 5 | * @param {string} str - A string which may contain underscores and hyphens and/or may be title-cased. 6 | * @returns {string} A new string that is without hyphens and underscores and the first letter of every new word boundary is capitalized, unless preceded by whitespace 7 | */ 8 | export default function toCamelCase(str: string): string 9 | -------------------------------------------------------------------------------- /src/hasNestedProp.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Checks if a given Object contains a (potentially) nested property of a specified path 5 | * @param {string|Array} prop - A prop name, a dot-separated prop path, or an array of prop path "pieces" to look for in the object 6 | * @param {object} obj - An Object to inspect for a given prop at the specified path 7 | * @returns {boolean} Whether the object contains the specified prop path 8 | */ 9 | export default function hasNestedProp( 10 | prop: string[] | string, 11 | obj: T 12 | ): boolean 13 | -------------------------------------------------------------------------------- /src/isZero.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if a given value is zero or a string representing zero. 3 | * For string values, what we want is "0" or "0.0" but not "" or " " (which also coerce to zero). 4 | * 5 | * @function 6 | * @name isZero 7 | * @param {number|string} val A value to verify is zero or a zero-like string. 8 | * @returns {boolean} Whether or not the given value is zero or a zero-like string. 9 | */ 10 | function isZero(val) { 11 | return val === 0 || ( 12 | typeof val === "string" 13 | && val.trim().length > 0 14 | && +val === 0 15 | ) 16 | } 17 | 18 | export default isZero 19 | -------------------------------------------------------------------------------- /src/promiseChain.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Gathers an Array of Promises (or of Functions that return Promises) and executes them in sequential order they appear in the Array. 3 | * The value from the last will be supplied to the next (in case you need it). 4 | * @param {Array} requests - An array of Promises (or of Functions that return Promises) which need to be executed in sequential order 5 | * @returns {Promise<*>} A Promise that will resolve when each of the requests completes 6 | */ 7 | export default function promiseChain(...requests: (Promise[] | ((...params: any[]) => void)[])[]): Promise 8 | -------------------------------------------------------------------------------- /src/assign.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Assigns the values from one or more Objects onto another Object. 5 | * This mutates the original Object. 6 | * @param {object} obj - An Object to mutate with the values from one (or more) additionally supplied Objects 7 | * @param {Array} ...resOfObjects - One or more Objects to extract from and assign onto the first Object 8 | * @returns {object} The first object mutated with the values from any other object passed in 9 | */ 10 | export default function assign( 11 | obj: AnyObject, 12 | ...resOfObjects: AnyObject[] 13 | ): AnyObject 14 | -------------------------------------------------------------------------------- /src/findIndex.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Find the index of a single value from an array of values, based on criteria defined in a predicate function. 3 | * @param {function} pred - A predicate function to apply to the array of values (It should take a val as input and return a Boolean as output). 4 | * @param {Array<*>} arr - An array of values from which to find the index of one particular matching value 5 | * @returns {number} Either the index of the value from the array that matched the predicate function or negative one (-1, if no match). 6 | */ 7 | export default function findIndex(pred: (...params: any[]) => any, arr: any[]): number 8 | -------------------------------------------------------------------------------- /src/mapObjectRecursive.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Applies a mapping function you provide over every value in a given Object (recursively). 5 | * @param {function} fn - A mapping function that is invoked on every value in the provided Object 6 | * @param {object} obj - An Object whose values will be mapped over (recursively) 7 | * @returns {object} A new Object that is the result of the mapping operation over all the values in the original Object 8 | */ 9 | export default function mapObjectRecursive( 10 | fn: (...params: any[]) => any, 11 | obj: T 12 | ): T 13 | -------------------------------------------------------------------------------- /src/propEquals.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Looks for a specified key on an Object you provide and checks to see if its corresponding value equals the value you specifiy. 5 | * @param {string|Array} prop - A key to search for on the Object 6 | * @param {*} val - A value that the extracted prop will be compared against 7 | * @param {object} obj - An object which may contain a specified prop 8 | * @returns {boolean} Whether or not the requested prop equals the specified value 9 | */ 10 | export default function propEquals(prop: string | string[], val: any, obj: AnyObject): boolean 11 | -------------------------------------------------------------------------------- /src/contains.js: -------------------------------------------------------------------------------- 1 | import { _includes } from "./_internal/_includes" 2 | 3 | /** 4 | * Checks if a given value is present in a String OR Array 5 | * 6 | * @function 7 | * @name contains 8 | * @param {string | number} val A value which may be present in the String/Array 9 | * @param {Array<*> | string} arr An Array or String which may contain the provided value 10 | * @returns {boolean} Whether or not the String|Array contains the provided value 11 | */ 12 | function contains(val, arr) { 13 | return Array.isArray(arr) 14 | ? _includes(val, arr) 15 | : arr.indexOf(val) !== -1 16 | } 17 | 18 | export default contains 19 | -------------------------------------------------------------------------------- /src/invert.js: -------------------------------------------------------------------------------- 1 | import forIn from "./forIn" 2 | 3 | /** 4 | * Swaps the values for keys in a given object. So the values in that object should be the kind that _can_ be converted to unique string values 5 | * 6 | * @function 7 | * @name flipKeyValues 8 | * @param {object} obj An object whose values _can_ be swapped for keys 9 | * @returns {object} A new object whose keys were the values from the original object 10 | */ 11 | function flipKeyValues(obj) { 12 | const newObj = {} 13 | forIn((key, val) => { 14 | newObj[String(val)] = key 15 | }, obj) 16 | return newObj 17 | } 18 | 19 | export default flipKeyValues 20 | -------------------------------------------------------------------------------- /src/size.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Checks the length (or size) of many different types of values: 5 | * - Array 6 | * - Set 7 | * - Map 8 | * - Object (num of keys) 9 | * - String (num of chars) 10 | * - Function (num of params) 11 | * @param {object|string|Map|Set|function|Array<*>} val - A value of type Object, String, Array or Function 12 | * @returns {number} The length of the String or Array, OR the number of keys in the Object 13 | */ 14 | export default function size(val: AnyObject | Set | Map | string | any[] | ((...params: any[]) => any)): number 15 | -------------------------------------------------------------------------------- /src/isArrayish.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if a given value is "array-like". 3 | * 4 | * This includes: 5 | * - Array 6 | * - Set 7 | * - WeakSet 8 | * - Float64Array 9 | * - Float32Array 10 | * - Int32Array 11 | * - Uint16Array 12 | * - Int16Array 13 | * - Uint8ClampedArray 14 | * - Uint8Array 15 | * - Int8Array 16 | * @param {*} val - A value to check as being an array 17 | * @returns {boolean} Whether the value is an array-like type 18 | */ 19 | export default function isArrayish(val: any): val is (Set|WeakSet|Float32Array|Float64Array|Int32Array|Uint8Array|Int16Array|Uint8ClampedArray|Uint8Array|Int8Array|any[]) 20 | -------------------------------------------------------------------------------- /src/merge.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Merges the values from 2 or more Objects together into a new Object. 5 | * Null and Undefined values are handled gracefully, and if the second value is a primitive it will be returned as-is, instead of trying to merge it onto the first. 6 | * @param {object} val - The first value to merge onto (will not get mutated though) 7 | * @param {object} val2 - A value to merge onto the first 8 | * @returns {object} A new value that contains the combined values from all the values passed in 9 | */ 10 | export default function merge(val: T, val2: T): T 11 | -------------------------------------------------------------------------------- /src/promiseCompose.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Gathers an Array of Promises (or of Functions that return Promises) and executes them from right to left. 3 | * You can pass them all together in either a single array, or one by one as arguments (ie, in the style of either `apply` or `call`). 4 | * @param {Array} requests - An array of Promises (or of Functions that return Promises) which need to be executed in sequential order 5 | * @returns {Promise<*>} A Promise that will resolve when each of the requests completes 6 | */ 7 | export default function promiseCompose(...requests: (Promise[] | ((...params: any[]) => void)[])[]): Promise 8 | -------------------------------------------------------------------------------- /src/renameKeys.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Renames a set of keys in a given object (removing the old ones) 5 | * @param {object} keyMap - An object whose keys are the _current_ key names and whose values are the _new_ key names 6 | * @param {object} obj - An Object whose keys will be renamed 7 | * @returns {object} A new Object that has all the specified keys renamed to their new names 8 | */ 9 | export function renameKeys< 10 | T extends AnyObject, 11 | KM extends { [S in keyof T]: string } 12 | >( 13 | keyMap: KM, 14 | obj: T 15 | ): { 16 | [k in (KM[keyof KM])]: T[keyof T] 17 | } & T 18 | -------------------------------------------------------------------------------- /src/toNumber.js: -------------------------------------------------------------------------------- 1 | import isZero from "./isZero" 2 | import isNumber from "./isNumber" 3 | 4 | /** 5 | * Coerces a given string value to a number (if valid). 6 | * 7 | * @function 8 | * @name toNumber 9 | * @param {string} val A string value to coerce to a number 10 | * @returns {number|undefined} A numeric representation of the original value, or `undefined` if it cannot be coerced to a number. 11 | */ 12 | function toNumber(val) { 13 | if (isZero(val)) { 14 | return 0 15 | } 16 | 17 | const num = +val 18 | return isNumber(num) && num !== 0 19 | ? num 20 | : undefined 21 | } 22 | 23 | export default toNumber 24 | -------------------------------------------------------------------------------- /src/_internal/_map.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Applies a mapping function over a list of values 3 | * 4 | * @function 5 | * @private 6 | * @name _map 7 | * @param {function} fn A function to map over a list of values 8 | * @param {Array<*>} arr A list of values over which to apply a mapping function 9 | * @returns {Array<*>} The original list of values after the mapping function has been applied to (and potentially transform) them 10 | */ 11 | export function _map(fn, arr) { 12 | const len = arr.length 13 | const newArr = new Array(len) 14 | for (let i = 0; i < len; i++) { 15 | newArr[i] = fn(arr[i]) 16 | } 17 | return newArr 18 | } 19 | -------------------------------------------------------------------------------- /src/_internal/areAllFunctions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if a list of values are all functions 3 | * 4 | * @function 5 | * @private 6 | * @name areAllFunctions 7 | * @param {Array<*>} fns A list of values which may or may not all be functions 8 | * @returns {boolean} Whether or not a list of functions was passed in 9 | */ 10 | function areAllFunctions(fns) { 11 | return fns.some((f, idx) => { 12 | if (typeof f !== "function") { 13 | throw new Error(`Argument #${idx + 1} passed to "compose()" is not a function:\n"${JSON.stringify(f)}"`) 14 | } 15 | return true 16 | }) 17 | } 18 | 19 | export default areAllFunctions 20 | -------------------------------------------------------------------------------- /src/forIn.js: -------------------------------------------------------------------------------- 1 | import has from "./has" 2 | 3 | /** 4 | * A light wrapper around native `for .. in`, but will only iterate over an Object's own properties. 5 | * 6 | * @function 7 | * @name forIn 8 | * @param {function} fn A function to execute iteratively, which will receive the `key`, `value`, and `object` (respectively) 9 | * @param {object} obj An object whose keys will be iterated over 10 | */ 11 | function forIn(fn, obj) { 12 | // eslint-disable-next-line no-restricted-syntax 13 | for (const key in obj) { 14 | if (has(key, obj)) { 15 | fn(key, obj[key], obj) 16 | } 17 | } 18 | } 19 | 20 | export default forIn 21 | -------------------------------------------------------------------------------- /src/getType.js: -------------------------------------------------------------------------------- 1 | import isNil from "./isNil" 2 | import toTitleCase from "./toTitleCase" 3 | import getConstructorName from "./_internal/getConstructorName" 4 | 5 | /** 6 | * Gets the type for any value. If available will inspect the constructor name, otherwise will use the typeof 7 | * 8 | * @function 9 | * @name getType 10 | * @param {*} val A value of any kind 11 | * @returns {string} The stringified representation of the value's type 12 | */ 13 | function getType(val) { 14 | if (isNil(val)) { 15 | return `${val}` 16 | } 17 | return getConstructorName(val) || toTitleCase(typeof val) 18 | } 19 | 20 | export default getType 21 | -------------------------------------------------------------------------------- /src/pipe.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates a chain of Functions that will be executed in sequnce (from left to right), with the value from the previous Function fed into the next Function. 3 | * The value that the chain of functions will executed on can be provided later. 4 | * @param {Array} ...fns - One or more function to execute (in sequential order) on a value that will be supplied later 5 | * @returns {function} A single Function that is ready to receive a value and pass it through the piped chain of Functions 6 | */ 7 | export default function pipe( 8 | ...fns: ((...params: any[]) => any)[] 9 | ): (...params: any[]) => any 10 | 11 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { 4 | "loose": true, 5 | "targets": { 6 | "browsers": ["last 2 versions"] 7 | } 8 | }] 9 | ], 10 | "plugins": [ 11 | "@babel/proposal-object-rest-spread" 12 | ], 13 | "env": { 14 | "cjs": { 15 | "plugins": [ 16 | "@babel/proposal-object-rest-spread", 17 | "add-module-exports" 18 | ] 19 | }, 20 | "umd": { 21 | "presets": [ 22 | ["@babel/preset-env", { 23 | "targets": { 24 | "browsers": ["ie >= 11"] 25 | } 26 | }] 27 | ] 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/compose.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates a chain of Functions that will be executed in sequnce (from right to left), with the value from the previous Function fed into the next Function. 3 | * The value that the chain of functions will executed on can be provided later. 4 | * @param {Array} ...fns - One or more function to execute (in sequential order) on a value that will be supplied later 5 | * @returns {function} A single Function that is ready to receive a value and pass it through the composed chain of Functions 6 | */ 7 | export default function compose( 8 | ...fns: ((...params: any[]) => any)[] 9 | ): (...params: any[]) => any 10 | -------------------------------------------------------------------------------- /src/propOr.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Attempts to find a specified key on an Object you provide, and if not found will fall back to an additional value you specify. 5 | * @param {*} fallback - A value to fall back on if the requested key does not exist on the provided Object 6 | * @param {string|Array} prop - A key to search for on the Object 7 | * @param {object} obj - An object which may contain a specified prop 8 | * @returns {*} Either the requested prop (from the Object) or the fallback value 9 | */ 10 | export default function propOr(fallback: any, prop: string | string[], obj: AnyObject): any 11 | -------------------------------------------------------------------------------- /test/clone.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import clone from "../src/clone" 3 | import { magic, magicWithoutKids } from "./__mocks__" 4 | 5 | tape("\"clone\" copies an object from another", t => { 6 | const { Malfoy, Potter, Weasley } = magic 7 | const { Snape, Dumbledore, Black } = magicWithoutKids 8 | const magicClone = clone(magic) 9 | 10 | t.deepEqual(magicClone, magic) 11 | 12 | Object.assign(magicClone, magicWithoutKids) 13 | t.deepEqual(magicClone, { Malfoy, Potter, Weasley, Snape, Dumbledore, Black }) 14 | t.deepEqual(magic, { Malfoy, Potter, Weasley }, "original source object is unaltered") 15 | t.end() 16 | }) 17 | -------------------------------------------------------------------------------- /src/omitNull.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Removes all `null` or `undefined` values from a given object 3 | * 4 | * @function 5 | * @name omitNull 6 | * @param {object} obj An Object from which to copy and remove null/undefined 7 | * @returns {object} A copy of the original Object, but without any `null` or `undefined` values 8 | */ 9 | function omitNull(obj) { 10 | const newObj = {} 11 | const keys = Object.keys(obj) 12 | const numOfKeys = keys.length 13 | for (let i = 0; i < numOfKeys; i++) { 14 | if (obj[keys[i]] != null) { 15 | newObj[keys[i]] = obj[keys[i]] 16 | } 17 | } 18 | return newObj 19 | } 20 | 21 | export default omitNull 22 | -------------------------------------------------------------------------------- /test/benchmarks/assign.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-object-spread */ 2 | import { test } from "reality-check" 3 | import * as Fx from "fxjs" 4 | import { assign } from "../../src" 5 | import { hook, sphere, jurrassicPark } from "../__mocks__" 6 | 7 | test("assign", benchmark => { 8 | benchmark( 9 | () => assign({}, hook, sphere, jurrassicPark), 10 | "Vanillas \"assign\"" 11 | ) 12 | 13 | benchmark( 14 | () => Fx.assign({}, hook, sphere, jurrassicPark), 15 | "FxJs \"assign\"" 16 | ) 17 | 18 | benchmark( 19 | () => Object.assign({}, hook, sphere, jurrassicPark), 20 | "(native) \"Object.assign()\"" 21 | ) 22 | }) 23 | -------------------------------------------------------------------------------- /src/omitUndefined.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Removes all `undefined` values from a given object 3 | * 4 | * @function 5 | * @name omitUndefined 6 | * @param {object} obj An Object from which to copy and remove undefined 7 | * @returns {object} A copy of the original Object, but without any `undefined` values 8 | */ 9 | function omitUndefined(obj) { 10 | const newObj = {} 11 | const keys = Object.keys(obj) 12 | const numOfKeys = keys.length 13 | for (let i = 0; i < numOfKeys; i++) { 14 | if (obj[keys[i]] !== undefined) { 15 | newObj[keys[i]] = obj[keys[i]] 16 | } 17 | } 18 | return newObj 19 | } 20 | 21 | export default omitUndefined 22 | -------------------------------------------------------------------------------- /test/benchmarks/any.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash" 2 | import R from "ramda" 3 | import * as Fx from "fxjs" 4 | import { test } from "reality-check" 5 | 6 | import { any } from "../../src" 7 | 8 | const arr = ["dustin", "robin", "julia", "bob"] 9 | const predicate = val => /uli/.test(val) 10 | 11 | test("Any", benchmark => { 12 | benchmark(() => any(predicate, arr), "Vanillas \"any\"") 13 | benchmark(() => _.some(arr, predicate), "Lodash \"some\"") 14 | benchmark(() => Fx.some(predicate, arr), "FxJs \"some\"") 15 | benchmark(() => R.any(predicate, arr), "Ramda \"any\"") 16 | benchmark(() => arr.some(predicate), "(native) \"Array.some()\"") 17 | }) 18 | -------------------------------------------------------------------------------- /src/isEqual.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if two provided values are deeply equal. 3 | * If Objects or Arrays (or Array-like values) are provided, they are inspected recursively. 4 | * Primitive values are checked to see if they are stricly equal (ie triple equals; no type coercion). 5 | * @param {*} firstVal - A value which may be null, undefined, a JavaScript primitive value, an array of values, an array-like value, or an object 6 | * @param {*} secondVal - A value which may be null, undefined, a JavaScript 7 | * @returns {boolean} Whether or not the two values are deeply equal 8 | */ 9 | export default function isEqual(firstVal: any, secondVal: any): boolean 10 | -------------------------------------------------------------------------------- /test/benchmarks/isEmpty.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import R from "ramda" 4 | import * as Fx from "fxjs" 5 | import { isEmpty } from "../../src" 6 | import { hook } from "../__mocks__" 7 | 8 | const val = Object.values(hook) 9 | 10 | test("isEmpty", benchmark => { 11 | benchmark( 12 | () => isEmpty(val), 13 | "Vanillas \"isEmpty\"" 14 | ) 15 | benchmark( 16 | () => _.isEmpty(val), 17 | "Lodash \"isEmpty\"" 18 | ) 19 | benchmark( 20 | () => R.isEmpty(val), 21 | "Ramda \"isEmpty\"" 22 | ) 23 | benchmark( 24 | () => Fx.isEmpty(val), 25 | "FxJs \"isEmpty\"" 26 | ) 27 | }) 28 | -------------------------------------------------------------------------------- /src/mapString.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Applies a mapping function you provide over every character in a given string. 3 | * 4 | * @function 5 | * @name mapString 6 | * @param {function} fn A mapping function that is invoked on every char in the provided String value 7 | * @param {string} str A string value to map over 8 | * @returns {string} A new String value that is the result of the mapping operation over the original string 9 | */ 10 | function mapString(fn, str) { 11 | const len = str.length 12 | let newStr = "" 13 | for (let i = 0; i < len; i++) { 14 | newStr += fn(str[i], i, str) 15 | } 16 | return newStr 17 | } 18 | 19 | export default mapString 20 | -------------------------------------------------------------------------------- /src/_internal/_includes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A faster version of the native Array.includes. 3 | * It simply checks to see if a given list contains a given value 4 | * 5 | * @function 6 | * @private 7 | * @name _includes 8 | * @param {*} val A value (of any type) to search the array for 9 | * @param {Array<*>} arr A list of values which may or may not contain the specified value 10 | * @returns {boolean} Whether or not the provided value is in the provided list of values 11 | */ 12 | export function _includes(val, arr) { 13 | let idx = arr.length 14 | while (idx--) { 15 | if (arr[idx] === val) { 16 | return true 17 | } 18 | } 19 | return false 20 | } 21 | -------------------------------------------------------------------------------- /src/eitherOr.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A function that accepts two functions and a value and will return the first result which is "truthy". 3 | * This function is curried because it doesn't make any sense to even have it unless you have the functions ahead of time but not the value. 4 | * @param {function} fnA - The first function to be executed on the value 5 | * @param {function} fnB - The second function to be executed on the value 6 | * @param {*} val - A value to be passed into the functions 7 | * @returns {*} The result of executing value passed in 8 | */ 9 | export default function eitherOr(fnA: (...params: any[]) => any, fnB: (...params: any[]) => any, val: any): any 10 | -------------------------------------------------------------------------------- /src/propEquals.js: -------------------------------------------------------------------------------- 1 | import propAt from "./propAt" 2 | 3 | /** 4 | * Looks for a specified key on an Object you provide and checks to see if its corresponding value equals the value you specifiy. 5 | * 6 | * @function 7 | * @name propEquals 8 | * @param {string} prop A key to search for on the Object 9 | * @param {*} val A value that the extracted prop will be compared against 10 | * @param {object} obj An object which may contain a specified prop 11 | * @returns {boolean} Whether or not the requested prop equals the specified value 12 | */ 13 | function propEquals(prop, val, obj) { 14 | return propAt(prop, obj) === val 15 | } 16 | 17 | export default propEquals 18 | -------------------------------------------------------------------------------- /src/shim.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Recursively shims an Object. 5 | * Every time the getter is invoked (which happens whenever prop paths are referenced in the consuming code), the path is shimmed with another Proxy. 6 | * It is not a polyfill but rather a way to keep deep prop paths that may not exist on the source object from throwing an error. 7 | * @param {object} obj - An object that will be the Proxy's source 8 | * @returns {object} An Object that will return props on the source Object if they exist but safely handle missing prop paths without throwing errors. 9 | */ 10 | export default function shim(obj: AnyObject): typeof Proxy 11 | -------------------------------------------------------------------------------- /src/toSnakeCase.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Transforms a string value into one which is separated by underscores. 3 | * Whitespace and hyphens are replaced with underscores, and uppercase letters are interpreted as boundaries for new underscore-separated words. 4 | * 5 | * @function 6 | * @name toSnakeCase 7 | * @param {string} str A string which may contain uppercase characters or hyphens 8 | * @returns {string} A new string that is an lowercase representation of the original string 9 | */ 10 | function toSnakeCase(str) { 11 | return str 12 | .replace(/([a-z0-9])(?=[A-Z])/g, "$1_") 13 | .replace(/[\s._-]+/g, "_") 14 | .toLowerCase() 15 | } 16 | 17 | export default toSnakeCase 18 | -------------------------------------------------------------------------------- /src/uniq.js: -------------------------------------------------------------------------------- 1 | import { _includes } from "./_internal/_includes" 2 | 3 | /** 4 | * Filters an array of values down to only those which are unique 5 | * 6 | * @function 7 | * @name uniq 8 | * @param {Array<*>} list An array of values which may or may not contain duplicates 9 | * @returns {Array<*>} A new list containing only the unique values from the original array 10 | */ 11 | function uniq(list) { 12 | let idx = -1 13 | const newArr = [] 14 | const len = list.length 15 | while (++idx < len) { 16 | const val = list[idx] 17 | if (!_includes(val, newArr)) { 18 | newArr.push(val) 19 | } 20 | } 21 | return newArr 22 | } 23 | 24 | export default uniq 25 | -------------------------------------------------------------------------------- /src/any.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Check an Array of items (of any type) to see if any item satisfies a given predicate function. 3 | * Exits when the first match is found. 4 | * 5 | * @function 6 | * @name any 7 | * @param {function} pred A predicate function to evaluate against each item in a given array 8 | * @param {Array<*>} arr An array of items to evaluate against the predicate function 9 | * @returns {boolean} Whether or not any items in the array matched the predicate function 10 | */ 11 | function any(pred, arr) { 12 | const len = arr.length 13 | for (let i = 0; i < len; i++) { 14 | if (pred(arr[i])) return true 15 | } 16 | return false 17 | } 18 | 19 | export default any 20 | -------------------------------------------------------------------------------- /src/mapObject.js: -------------------------------------------------------------------------------- 1 | import forIn from "./forIn" 2 | 3 | /** 4 | * Applies a mapping function you provide over every value in a given Object. 5 | * 6 | * @function 7 | * @name mapObject 8 | * @param {function} fn A mapping function that is invoked on every value in the provided Object 9 | * @param {object} obj An Object whose values will be mapped over 10 | * @returns {object} A new Object that is the result of the mapping operation over all the values in the original Object 11 | */ 12 | function mapObject(fn, obj) { 13 | const newObj = {} 14 | forIn((key, val, ob) => { 15 | newObj[key] = fn(val, key, ob) 16 | }, obj) 17 | return newObj 18 | } 19 | 20 | export default mapObject 21 | -------------------------------------------------------------------------------- /src/filterString.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Applies a filtering function you provide over every character in a given string. 3 | * 4 | * @function 5 | * @name filterString 6 | * @param {function} fn A filtering function that is invoked on every char in the provided String value 7 | * @param {string} str A string value to filter over 8 | * @returns {string} A new String value that is the result of the filtering operation over the original string 9 | */ 10 | function filterString(fn, str) { 11 | const len = str.length 12 | let newStr = "" 13 | for (let i = 0; i < len; i++) { 14 | newStr += fn(str[i], i, str) ? str[i] : "" 15 | } 16 | return newStr 17 | } 18 | 19 | export default filterString 20 | -------------------------------------------------------------------------------- /src/memoize.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Takes a snapshot of the input args and the output result for a provided function, and on repeated usage will shortcut invoking the function and return the cached output instead, whenever the same input args are supplied to the function. 3 | * @param {function} fn - A function whose input values (supplied later) will be cached with its output result, so that the invoking the function can be skipped the next time the same values are passed to it 4 | * @returns {function} A memoized version of the original function. It will cache the input values supplied to it each time it is used 5 | */ 6 | export default function memoize(fn: (...params: any[]) => any): (...params: any[]) => any 7 | -------------------------------------------------------------------------------- /test/assign.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import assign from "../src/assign" 3 | import { hook, sphere, jurrassicPark } from "./__mocks__" 4 | 5 | tape("\"assign\" copies one (or more) objects onto another", t => { 6 | const movies = {} 7 | assign(movies, hook, sphere, jurrassicPark) 8 | t.deepEqual( 9 | movies, { 10 | Hoffman: "dustin", 11 | Williams: "robin", 12 | Roberts: "julia", 13 | Hoskins: "bob", 14 | Jackson: "samuel", 15 | Stone: "sharon", 16 | Latifah: "queen", 17 | Neill: "sam", 18 | Knight: "wayne", 19 | Goldblum: "jeff" 20 | }, 21 | "mutates the first object passed in" 22 | ) 23 | t.end() 24 | }) 25 | -------------------------------------------------------------------------------- /test/invert.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import invert from "../src/invert" 3 | import { hook } from "./__mocks__" 4 | 5 | tape("\"invert\" removes specified props from an object", t => { 6 | t.deepEqual( 7 | invert(hook), { 8 | dustin: "Hoffman", 9 | robin: "Williams", 10 | julia: "Roberts", 11 | bob: "Hoskins" 12 | } 13 | ) 14 | t.deepEqual( 15 | invert({ 16 | a: 1, 17 | b: { lorem: "ipsum" }, 18 | c: [1, 2, 3, 4], 19 | d: "dee" 20 | }), { 21 | 1: "a", 22 | "[object Object]": "b", 23 | "1,2,3,4": "c", 24 | dee: "d" 25 | }, 26 | "converts values to string" 27 | ) 28 | t.end() 29 | }) 30 | -------------------------------------------------------------------------------- /test/benchmarks/flatten.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import * as raw from "radash" 4 | import R from "ramda" 5 | import * as Fx from "fxjs" 6 | import { flatten } from "../../src" 7 | import { solarSystem } from "../__mocks__" 8 | 9 | const arr = Object.values(solarSystem) 10 | .map((p) => p.moons) 11 | .filter(Boolean) 12 | 13 | test("flatten", (benchmark) => { 14 | benchmark(() => flatten(arr), "Vanillas \"flatten\"") 15 | benchmark(() => _.flatten(arr), "Lodash \"flatten\"") 16 | benchmark(() => raw.flat(arr), "Radash \"flat\"") 17 | benchmark(() => R.flatten(arr), "Ramda \"flatten\"") 18 | benchmark(() => Fx.flat(arr), "FxJs \"flat\"") 19 | }) 20 | -------------------------------------------------------------------------------- /src/toInteger.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts a given value to an Integer and rounds up or down floating point values appropriately. 3 | * If the values is a `Boolean`, then `true` will yield `1` and `false will yield `0`. 4 | * If `NaN` then zero will always be returned. 5 | * 6 | * @function 7 | * @name toInteger 8 | * @param {string} str A string which may be numeric 9 | * @returns {number} Either the successfully converted number or zero (if it was NaN) 10 | */ 11 | function toInteger(str) { 12 | if (typeof str === "boolean") { 13 | return str === true ? 1 : 0 14 | } 15 | 16 | const num = parseFloat(str) 17 | return Number.isNaN(num) ? 0 : Math.round(num) 18 | } 19 | 20 | export default toInteger 21 | -------------------------------------------------------------------------------- /test/benchmarks/values.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import R from "ramda" 4 | import * as Fx from "fxjs" 5 | import { values } from "../../src" 6 | import { hook } from "../__mocks__" 7 | 8 | test("values", benchmark => { 9 | benchmark( 10 | () => values(hook), 11 | "Vanillas \"values\"" 12 | ) 13 | benchmark( 14 | () => _.values(hook), 15 | "Lodash \"values\"" 16 | ) 17 | benchmark( 18 | () => R.values(hook), 19 | "Ramda \"values\"" 20 | ) 21 | benchmark( 22 | () => Fx.values(hook), 23 | "FxJs \"values\"" 24 | ) 25 | benchmark( 26 | () => Object.values(hook), 27 | "(native) \"Object.values()\"" 28 | ) 29 | }) 30 | -------------------------------------------------------------------------------- /src/toKebabCase.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Transforms a string value into one which is hyphenated. 3 | * Whitespace and underscores are replaced with hyphens, and uppercase letters are interpreted as boundaries for new hyphenated words. 4 | * 5 | * @function 6 | * @name toKebabCase 7 | * @param {string} str A string which may contain uppercase characters 8 | * @returns {string} A new string that is a hyphenated representation of the original string 9 | */ 10 | function toKebabCase(str) { 11 | return str 12 | .replace(/(\S)(\s+)(\S)/g, "$1-$3") 13 | .replace(/([a-z0-9])(?=[A-Z])/g, "$1-") 14 | .replace(/_/g, "-") 15 | .replace(/--/g, "-") 16 | .toLowerCase() 17 | } 18 | 19 | export default toKebabCase 20 | -------------------------------------------------------------------------------- /test/isZero.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import isZero from "../src/isZero" 3 | 4 | tape("\"isZero\" can properly identify numeric zero or a numeric string which is zero", t => { 5 | t.equal(isZero(0), true, "0 (of type 'number')") 6 | t.equal(isZero("0"), true, "'0' (of type 'string')") 7 | t.equal(isZero("0.0"), true, "'0.0' (of type 'string')") 8 | t.equal(isZero("00.00"), true, "'00.00' (of type 'string')") 9 | t.equal(isZero("0.0.0"), false, "'0.0.0' (semver like)") 10 | t.equal(isZero(""), false, "'' (blank string)") 11 | t.equal(isZero(" "), false, "' ' (blank string)") 12 | t.equal(isZero(null), false, "null") 13 | t.equal(isZero(undefined), false, "undefined") 14 | t.end() 15 | }) 16 | -------------------------------------------------------------------------------- /test/benchmarks/entries.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import R from "ramda" 4 | import * as Fx from "fxjs" 5 | import { entries } from "../../src" 6 | import { hook } from "../__mocks__" 7 | 8 | test("entries", benchmark => { 9 | benchmark( 10 | () => entries(hook), 11 | "Vanillas \"entries\"" 12 | ) 13 | benchmark( 14 | () => _.toPairs(hook), 15 | "Lodash \"toPairs\"" 16 | ) 17 | benchmark( 18 | () => R.toPairs(hook), 19 | "Ramda \"toPairs\"" 20 | ) 21 | benchmark( 22 | () => Fx.entries(hook), 23 | "FxJs \"entries\"" 24 | ) 25 | benchmark( 26 | () => Object.entries(hook), 27 | "(native) \"Object.entries( )\"" 28 | ) 29 | }) 30 | -------------------------------------------------------------------------------- /src/isEmpty.js: -------------------------------------------------------------------------------- 1 | import isObject from "./isObject" 2 | 3 | /** 4 | * Checks if a value is empty. Arrays, Objects, Strings, Sets, and Null/Undefined values are considered empty if their length (or size) prop is zero (or if they are Null or Undefined). 5 | * Whitespace-only strings are NOT considered empty (use `isBlankString` instead). 6 | * 7 | * @function 8 | * @name isEmpty 9 | * @param {*} val A value of any type which may be considered empty 10 | * @returns {boolean} Whether or not the value is empty 11 | */ 12 | function isEmpty(val) { 13 | return val == null || 14 | val.length === 0 || 15 | val.size === 0 || 16 | (isObject(val) && Object.keys(val).length === 0) 17 | } 18 | 19 | export default isEmpty 20 | -------------------------------------------------------------------------------- /src/omit.js: -------------------------------------------------------------------------------- 1 | import forIn from "./forIn" 2 | import { _includes } from "./_internal/_includes" 3 | 4 | /** 5 | * Removes specified keys from an object (after cloning the Object). 6 | * 7 | * @function 8 | * @name omit 9 | * @param {Array} keys An array of keys to search for in the Object and exclude from the output 10 | * @param {object} obj An Object from which to copy and remove keys 11 | * @returns {object} A copy of the original Object, but without the specified keys 12 | */ 13 | function omit(keys, obj) { 14 | const newObj = {} 15 | forIn((key, val) => { 16 | if (!_includes(key, keys)) { 17 | newObj[key] = val 18 | } 19 | }, obj) 20 | return newObj 21 | } 22 | 23 | export default omit 24 | -------------------------------------------------------------------------------- /test/pick.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import pick from "../src/pick" 3 | 4 | tape("\"pick\" removes specified props from an object", t => { 5 | t.deepEqual(pick(["lorem"], {lorem: "ipsum", dolor: "sit amet"}), {lorem: "ipsum"}) 6 | t.deepEqual( 7 | pick(["lorem", "dolor"], {lorem: "ipsum", dolor: "sit amet", consectetur: "adipiscing elit"}), 8 | {lorem: "ipsum", dolor: "sit amet"} 9 | ) 10 | t.deepEqual(pick([], {lorem: "ipsum"}), {}, "handles no props gracefully") 11 | t.deepEqual( 12 | pick(["lorem", "sit"], {lorem: "ipsum", dolor: "sit amet", consectetur: "adipiscing elit"}), 13 | {lorem: "ipsum"}, 14 | "Won't pick props that aren't on the object already" 15 | ) 16 | t.end() 17 | }) 18 | -------------------------------------------------------------------------------- /test/toSnakeCase.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import toSnakeCase from "../src/toSnakeCase" 3 | 4 | tape("\"toSnakeCase\" will convert the a given string to an underscore-separated value", t => { 5 | t.equal(toSnakeCase("lorem"), "lorem", "values without any case changes or hyphens are returned as-is") 6 | t.equal(toSnakeCase("loremIpsumDolorSitAmet"), "lorem_ipsum_dolor_sit_amet", "camel-case changed to snake case") 7 | t.equal(toSnakeCase("lorem-ipsum-dolor-sit-amet"), "lorem_ipsum_dolor_sit_amet", "kebab-case changed to snake case") 8 | t.equal( 9 | toSnakeCase("lorem-ipsumDolor-sitAmet"), 10 | "lorem_ipsum_dolor_sit_amet", 11 | "mixed case still changed to snake case" 12 | ) 13 | t.end() 14 | }) 15 | -------------------------------------------------------------------------------- /test/benchmarks/pipe.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import R from "ramda" 3 | import * as Fx from "fxjs" 4 | import { pipe } from "../../src" 5 | 6 | const val = 1.3 7 | const add4 = a => a + 4 8 | const subtract7 = a => a - 7 9 | const divide3 = a => a / 3 10 | const randomNumber = Math.random() 11 | const random = () => randomNumber 12 | 13 | test("pipe", benchmark => { 14 | benchmark( 15 | () => pipe(random, add4, subtract7, divide3)(val), 16 | "Vanillas \"pipe\"" 17 | ) 18 | benchmark( 19 | () => R.pipe(random, add4, subtract7, divide3)(val), 20 | "Ramda \"pipe\"" 21 | ) 22 | benchmark( 23 | () => Fx.pipe(random, add4, subtract7, divide3)(val), 24 | "FxJs \"pipe\"" 25 | ) 26 | }) 27 | -------------------------------------------------------------------------------- /src/filterObject.js: -------------------------------------------------------------------------------- 1 | import forIn from "./forIn" 2 | 3 | /** 4 | * Applies a filtering function you provide over every value in a given Object. 5 | * 6 | * @function 7 | * @name filterObject 8 | * @param {function} fn A filtering function that is invoked on every value in the provided Object 9 | * @param {object} obj An Object whose values will be filtered 10 | * @returns {object} A new Object that is the result of the filtering operation over all the values in the original Object 11 | */ 12 | function filterObject(fn, obj) { 13 | const newObj = {} 14 | forIn((key, val, ob) => { 15 | if (fn(val, key, ob)) { 16 | newObj[key] = val 17 | } 18 | }, obj) 19 | return newObj 20 | } 21 | 22 | export default filterObject 23 | -------------------------------------------------------------------------------- /src/find.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable consistent-return */ 2 | 3 | /** 4 | * Find a single value from an array of values, based on criteria defined in a predicate function. 5 | * 6 | * @function 7 | * @name find 8 | * @param {function} pred A predicate function to apply to the array of values (It should take a val as input and return a Boolean as output). 9 | * @param {Array<*>} arr An array of values from which to find one particular matching value 10 | * @returns {*} Either a value from the array that matched the predicate function or undefined (if no match) 11 | */ 12 | function find(pred, arr) { 13 | const len = arr.length 14 | for (let i = 0; i < len; i++) { 15 | if (pred(arr[i])) return arr[i] 16 | } 17 | } 18 | 19 | export default find 20 | -------------------------------------------------------------------------------- /src/pick.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Removes everything _except_ the specified keys from an object (after cloning the Object). 3 | * 4 | * @function 5 | * @name pick 6 | * @param {Array} keys An array of keys to search for in the Object and include from the output 7 | * @param {object} obj An Object from which to copy and remove keys 8 | * @returns {object} A copy of the original Object, but with _only_ the specified keys 9 | */ 10 | function pick(keys, obj) { 11 | const newObj = {} 12 | const numOfKeys = keys.length 13 | for (let i = 0; i < numOfKeys; i++) { 14 | if (Object.prototype.hasOwnProperty.call(obj, keys[i])) { 15 | newObj[keys[i]] = obj[keys[i]] 16 | } 17 | } 18 | return newObj 19 | } 20 | 21 | export default pick 22 | -------------------------------------------------------------------------------- /test/benchmarks/compose.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import R from "ramda" 3 | import { compose as reduxCompose } from "redux" 4 | import { compose } from "../../src" 5 | 6 | const add4 = a => a + 4 7 | const subtract7 = a => a - 7 8 | const divide3 = a => a / 3 9 | const randomNumber = Math.random() 10 | const random = () => randomNumber 11 | 12 | test("tests", benchmark => { 13 | benchmark( 14 | () => compose(divide3, subtract7, add4, random)(), 15 | "Vanillas \"compose\"" 16 | ) 17 | benchmark( 18 | () => reduxCompose(divide3, subtract7, add4, random)(), 19 | "Redux \"compose\"" 20 | ) 21 | benchmark( 22 | () => R.compose(divide3, subtract7, add4, random)(), 23 | "Ramda \"compose\"" 24 | ) 25 | }) 26 | -------------------------------------------------------------------------------- /src/propAt.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Looks for a specified key on an Object you provide. 5 | * The is performed safely and will not throw an error if something on the prop path chain you specify doesn't exist. 6 | * Will always return `undefined` if a prop path cannot be resolved (rather than throwing). 7 | * @param {string|Array} prop - A top-level key OR a deeply nested prop path (which may be represented as an array or as a single dot-delimited string) 8 | * @param {object} obj - An object which may contain a specified prop 9 | * @returns {*} The value associated with the nested prop path OR undefined if it does not exist 10 | */ 11 | export default function propAt(prop: string | string[], obj: AnyObject): any | undefined 12 | -------------------------------------------------------------------------------- /src/promiseCompose.js: -------------------------------------------------------------------------------- 1 | import promiseChain from "./promiseChain" 2 | 3 | /** 4 | * Gathers an Array of Promises (or of Functions that return Promises) and executes them from right to left. 5 | * You can pass them all together in either a single array, or one by one as arguments (ie, in the style of either `apply` or `call`). 6 | * 7 | * @function 8 | * @name promiseCompose 9 | * @param {Array> | Array} requests An array of Promises (or of Functions that return Promises) which need to be executed in sequential order 10 | * @returns {Promise<*>} A Promise that will resolve when each of the requests completes 11 | */ 12 | function promiseCompose(...requests) { 13 | return promiseChain(requests.reverse()) 14 | } 15 | 16 | export default promiseCompose 17 | -------------------------------------------------------------------------------- /src/propOr.js: -------------------------------------------------------------------------------- 1 | import propAt from "./propAt" 2 | import isUndefined from "./isUndefined" 3 | 4 | /** 5 | * Attempts to find a specified key on an Object you provide, and if not found will fall back to an additional value you specify. 6 | * 7 | * @function 8 | * @name propOr 9 | * @param {*} fallback A value to fall back on if the requested key does not exist on the provided Object 10 | * @param {string} prop A key to search for on the Object 11 | * @param {object} obj An object which may contain a specified prop 12 | * @returns {*} Either the requested prop (from the Object) or the fallback value 13 | */ 14 | function propOr(fallback, prop, obj) { 15 | const val = propAt(prop, obj) 16 | return isUndefined(val) ? fallback : val 17 | } 18 | 19 | export default propOr 20 | -------------------------------------------------------------------------------- /test/isNegativeNumber.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import isNegativeNumber from "../src/isNegativeNumber" 3 | 4 | tape("\"isNegativeNumber\" can properly identify a negative numeric value", t => { 5 | t.equal(isNegativeNumber(0), false, "0") 6 | t.equal(isNegativeNumber(1), false, "1") 7 | t.equal(isNegativeNumber("-1"), false, "negative one (string)") 8 | t.equal( 9 | isNegativeNumber(Math.floor(Math.random() * 1000)), 10 | false, 11 | "random positive number" 12 | ) 13 | t.equal( 14 | isNegativeNumber(-Math.random() * 1000), 15 | true, 16 | "random negative number" 17 | ) 18 | t.equal( 19 | isNegativeNumber(-Math.floor(Math.random() * 1000)), 20 | true, 21 | "random negative integer" 22 | ) 23 | t.end() 24 | }) 25 | -------------------------------------------------------------------------------- /test/isPositiveNumber.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import isPositiveNumber from "../src/isPositiveNumber" 3 | 4 | tape("\"isPositiveNumber\" can properly identify a positive numeric value", t => { 5 | t.equal(isPositiveNumber(0), false, "0") 6 | t.equal(isPositiveNumber(-1), false, "1") 7 | t.equal(isPositiveNumber("1"), false, "positive one (string)") 8 | t.equal( 9 | isPositiveNumber(-Math.floor(Math.random() * 1000)), 10 | false, 11 | "random negative number" 12 | ) 13 | t.equal( 14 | isPositiveNumber(Math.random() * 1000), 15 | true, 16 | "random positive number" 17 | ) 18 | t.equal( 19 | isPositiveNumber(Math.floor(Math.random() * 1000)), 20 | true, 21 | "random positive integer" 22 | ) 23 | t.end() 24 | }) 25 | -------------------------------------------------------------------------------- /src/renameKeys.js: -------------------------------------------------------------------------------- 1 | import forIn from "./forIn" 2 | import has from "./has" 3 | 4 | /** 5 | * Renames a set of keys in a given object (removing the old ones) 6 | * 7 | * @function 8 | * @name renameKeys 9 | * @param {object} keyMap An object whose keys are the _current_ key names and whose values are the _new_ key names 10 | * @param {object} obj An Object whose keys will be renamed 11 | * @returns {object} A new Object that has all the specified keys renamed to their new names 12 | */ 13 | function renameKeys(keyMap, obj) { 14 | const newObj = {} 15 | forIn((key, val) => { 16 | if (has(key, keyMap)) { 17 | newObj[keyMap[key]] = val 18 | } else { 19 | newObj[key] = val 20 | } 21 | }, obj) 22 | return newObj 23 | } 24 | 25 | export default renameKeys 26 | -------------------------------------------------------------------------------- /test/isNegativeInteger.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import isNegativeInteger from "../src/isNegativeInteger" 3 | 4 | tape("\"isNegativeInteger\" can properly identify a negative integer value", t => { 5 | t.equal(isNegativeInteger(0), false, "0") 6 | t.equal(isNegativeInteger(1), false, "1") 7 | t.equal(isNegativeInteger("-1"), false, "negative one (string)") 8 | t.equal( 9 | isNegativeInteger(Math.floor(Math.random() * 1000)), 10 | false, 11 | "random positive number" 12 | ) 13 | t.equal( 14 | isNegativeInteger(-Math.random() * 1000 - 0.01), 15 | false, 16 | "random negative number" 17 | ) 18 | t.equal( 19 | isNegativeInteger(-Math.floor(Math.random() * 1000)), 20 | true, 21 | "random negative integer" 22 | ) 23 | t.end() 24 | }) 25 | -------------------------------------------------------------------------------- /test/isPositiveInteger.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import isPositiveInteger from "../src/isPositiveInteger" 3 | 4 | tape("\"isPositiveInteger\" can properly identify a positive integer value", t => { 5 | t.equal(isPositiveInteger(0), false, "0") 6 | t.equal(isPositiveInteger(-1), false, "1") 7 | t.equal(isPositiveInteger("1"), false, "positive one (string)") 8 | t.equal( 9 | isPositiveInteger(-Math.floor(Math.random() * 1000)), 10 | false, 11 | "random negative number" 12 | ) 13 | t.equal( 14 | isPositiveInteger(Math.random() * 1000 + 0.01), 15 | false, 16 | "random positive number" 17 | ) 18 | t.equal( 19 | isPositiveInteger(Math.floor(Math.random() * 1000)), 20 | true, 21 | "random positive integer" 22 | ) 23 | t.end() 24 | }) 25 | -------------------------------------------------------------------------------- /src/combine.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Combines two values of the same type (if it makes sense to combine them). 5 | * Numbers are summarized, strings and arrays are concatenated together, and true objects are merged (the second value merged on top of the first). 6 | * In any other case only the first value is returned. 7 | * @param {object|Array<*>|number} val1 - The base value to be combined with 8 | * @param {object|Array<*>|number} val2 - The value to combine 9 | * @returns {object|Array<*>|number} If the values are of the same type, this represents the combined value of the two of them. Otherwise only the first value is returned 10 | */ 11 | export default function combine( 12 | val1: T, 13 | val2: T 14 | ): T 15 | -------------------------------------------------------------------------------- /src/findIndex.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Find the index of a single value from an array of values, based on criteria defined in a predicate function. 3 | * 4 | * @function 5 | * @name findIndex 6 | * @param {function} pred A predicate function to apply to the array of values (It should take a val as input and return a Boolean as output). 7 | * @param {Array<*>} arr An array of values from which to find the index of one particular matching value 8 | * @returns {number} Either the index of the value from the array that matched the predicate function or negative one (-1, if no match). 9 | */ 10 | function findIndex(pred, arr) { 11 | let i = -1 12 | const len = arr.length 13 | while (++i < len) { 14 | if (pred(arr[i])) return i 15 | } 16 | return -1 17 | } 18 | 19 | export default findIndex 20 | -------------------------------------------------------------------------------- /src/escapeHtml.js: -------------------------------------------------------------------------------- 1 | const escapeChars = { 2 | "&": "&", 3 | "<": "<", 4 | ">": ">", 5 | /* eslint-disable-next-line quotes */ 6 | '"': """, 7 | "'": "'" 8 | } 9 | 10 | /** 11 | * Converts ampersands, angle brackets, apostrophes and blockquotes to their HTML encoded equivalents 12 | * 13 | * @function 14 | * @name escapeHtml 15 | * @param {string} val A string values to escape 16 | * @returns {string} The original value (converted to string) and with any of the unallowed characters properly escaped (null/undefined values are converted to '') 17 | */ 18 | function escapeHtml(val) { 19 | return /[&<>"']/.test(`${val}`) 20 | ? `${val}`.replace(/[&<>"']/g, key => escapeChars[key]) 21 | : `${val == null ? "" : val}` 22 | } 23 | 24 | export default escapeHtml 25 | -------------------------------------------------------------------------------- /test/benchmarks/filterObject.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import R from "ramda" 3 | import * as raw from "radash" 4 | import { filterObject } from "../../src" 5 | import { hook } from "../__mocks__" 6 | 7 | const pred = (str) => /n$/.test(str) 8 | 9 | test("filterObject", (benchmark) => { 10 | benchmark(() => filterObject((str) => pred(str), hook), "Vanillas \"filterObject\"") 11 | benchmark(() => raw.shake(hook, (str) => !pred(str)), "Radash \"shake\"") 12 | benchmark(() => R.filter((str) => pred(str), hook), "Ramda \"filter\"") 13 | benchmark( 14 | () => 15 | Object.entries(hook) 16 | .filter(([_, val]) => pred(val)) 17 | .reduce((o, [key, v]) => ({ ...o, [key]: v }), {}), 18 | "(native) \"Object.entries().filter().reduce()\"" 19 | ) 20 | }) 21 | -------------------------------------------------------------------------------- /test/benchmarks/find.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import R from "ramda" 4 | import * as Fx from "fxjs" 5 | import { find } from "../../src" 6 | import { hook } from "../__mocks__" 7 | 8 | const arr = Object.values(hook) 9 | const predicate = val => /uli/.test(val) 10 | 11 | test("find", benchmark => { 12 | benchmark( 13 | () => find(predicate, arr), 14 | "Vanillas \"find\"" 15 | ) 16 | benchmark( 17 | () => _.find(arr, predicate), 18 | "Lodash \"find\"" 19 | ) 20 | benchmark( 21 | () => R.find(predicate, arr), 22 | "Ramda \"find\"" 23 | ) 24 | benchmark( 25 | () => Fx.find(predicate, arr), 26 | "FxJs \"find\"" 27 | ) 28 | benchmark( 29 | () => arr.find(predicate), 30 | "(native) \"Array.find()\"" 31 | ) 32 | }) 33 | -------------------------------------------------------------------------------- /test/size.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import size from "../src/size" 3 | 4 | tape("\"size\" checks the length of strings, objects, and arrays", t => { 5 | t.equal(size("lorem ipsum dolor sit amet"), 26, "measures the size of strings") 6 | t.equal(size(["lorem", "ipsum"]), 2, "measures the size of arrays") 7 | t.equal(size(new Set(["lorem", "lorem", "ipsum", "dolor", "sit", "amet"])), 5, "measures the size of sets") 8 | const mappy = new Map() 9 | mappy.set("lorem", "ipsum dolor sit amet") 10 | mappy.set("consectetur", "adipiscing elit") 11 | t.equal(size(mappy), 2, "measures the size of maps") 12 | t.equal( 13 | size({lorem: "ipsum", dolor: "sit amet", consectetur: "adipiscing elit"}), 14 | 3, 15 | "measures the number of props on an object" 16 | ) 17 | t.end() 18 | }) 19 | -------------------------------------------------------------------------------- /src/prepend.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Merges two values together, placing the characters (or values) from one before those from the other. 5 | * @param {string|number|object|Array<*>} firstVal - An Array, Object, String or Number that the will have a new value(s) merged before its own characters/values 6 | * @param {string|number|object|Array<*>} secondVal - An Array, Object, String or Number that the will merge _before_ those from the first provided value 7 | * @returns {string|number|object|Array<*>} A new Array, Object, or String that has the characters/values from the second provided value merged _before_ those from the first provided value 8 | */ 9 | export default function prepend( 10 | firstVal: T, 11 | secondVal: T 12 | ): T 13 | -------------------------------------------------------------------------------- /test/difference.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import difference from "../src/difference" 3 | import { difference as differenceCurried } from "../src/curried" 4 | 5 | tape("\"difference\" gets the values different between two arrays", t => { 6 | const hook = [ 7 | "dustin", 8 | "robin", 9 | "julia", 10 | "bob" 11 | ] 12 | const sphere = [ 13 | "dustin", 14 | "samuel", 15 | "sharon", 16 | "queen" 17 | ] 18 | t.deepEqual( 19 | difference(hook, sphere), 20 | ["robin", "julia", "bob"] 21 | ) 22 | t.deepEqual( 23 | difference([1, 2, 3, 4, 5, 6, 7, 8, 9], [2, 4, 6, 8, 10, 12]), 24 | [1, 3, 5, 7, 9], 25 | "numbers" 26 | ) 27 | t.deepEqual( 28 | differenceCurried(hook)(sphere), 29 | ["robin", "julia", "bob"], 30 | "can be curried" 31 | ) 32 | t.end() 33 | }) 34 | -------------------------------------------------------------------------------- /test/isNumber.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import isNumber from "../src/isNumber" 3 | 4 | tape("\"isNumber\" can properly identify a numeric value", t => { 5 | t.equal(isNumber(0), true, "0") 6 | t.equal(isNumber(1), true, "1") 7 | t.equal(isNumber(-1), true, "1") 8 | t.equal(isNumber("1"), false, "one (string)") 9 | t.equal( 10 | isNumber(Math.floor(Math.random() * 1000)), 11 | true, 12 | "random positive number" 13 | ) 14 | t.equal( 15 | isNumber(-Math.random() * 1000), 16 | true, 17 | "random negative number" 18 | ) 19 | t.equal( 20 | isNumber(Math.floor(Math.random() * 1000)), 21 | true, 22 | "random positive integer" 23 | ) 24 | t.equal( 25 | isNumber(-Math.floor(Math.random() * 1000)), 26 | true, 27 | "random negative integer" 28 | ) 29 | t.end() 30 | }) 31 | -------------------------------------------------------------------------------- /src/propIs.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Looks for a specified key on an Object you provide and checks to see if its corresponding value is of the type you specifiy. 5 | * @param {function} type - A JavaScript type constructor function (ie `Boolean`, `RegExp`, `Date`, `Array`, `Object`, `Number`, `String`, etc) OR a string represention of the type (ie, "boolean", "regexp", "date", "array", "object", "number", "string", etc) 6 | * @param {string|Array} prop - A key to search for on the Object 7 | * @param {object} obj - An object which may contain a specified prop 8 | * @returns {boolean} Whether or not the requested prop is of the type specified 9 | */ 10 | export default function propIs( 11 | type: ((...params: any[]) => any) | string, 12 | prop: string | string[], 13 | obj: AnyObject 14 | ): boolean 15 | -------------------------------------------------------------------------------- /src/size.js: -------------------------------------------------------------------------------- 1 | import isUndefined from "./isUndefined" 2 | 3 | /** 4 | * Checks the length (or size) of many different types of values: 5 | * - Array 6 | * - Set 7 | * - Map 8 | * - Object (num of keys) 9 | * - String (num of chars) 10 | * - Function (num of params) 11 | * 12 | * @function 13 | * @name size 14 | * @param {object | string | Array<*> | function} val A value of type Object, String, Array or Function 15 | * @returns {number} The length of the String or Array, OR the number of keys in the Object 16 | */ 17 | function size(val) { 18 | if (!isUndefined(val.length)) { 19 | return val.length 20 | } 21 | if (!isUndefined(val.size)) { 22 | return val.size 23 | } 24 | if (typeof val === "object") { 25 | return Object.keys(val).length 26 | } 27 | return undefined 28 | } 29 | 30 | export default size 31 | -------------------------------------------------------------------------------- /src/toUriEncoded.js: -------------------------------------------------------------------------------- 1 | import forIn from "./forIn" 2 | import isPrimitive from "./isPrimitive" 3 | 4 | /** 5 | * Transforms an object's key/value pairs into an encoded URI string, delimited by ampersands & 6 | * 7 | * @function 8 | * @name toUriEncoded 9 | * @param {object} obj An object whose key/value pairs need to be serialized into a single string. 10 | * @returns {string} A new string that represents the key/value pairs on the originating object 11 | */ 12 | function toUriEncoded(obj = {}) { 13 | let strUri = "" 14 | 15 | forIn((key, val) => { 16 | if (isPrimitive(val)) { 17 | strUri += `&${key}=${val}` 18 | } else if (Array.isArray(val)) { 19 | strUri += `&${key}=${val.filter(isPrimitive).join(",")}` 20 | } 21 | }, obj) 22 | 23 | return encodeURI(strUri.slice(1)) 24 | } 25 | 26 | export default toUriEncoded 27 | -------------------------------------------------------------------------------- /test/contains.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import contains from "../src/contains" 3 | import { contains as containsCurried } from "../src/curried" 4 | 5 | tape("\"contains\" checks if a given value is present in a String OR an Array", t => { 6 | const hook = [ 7 | "dustin", 8 | "robin", 9 | "julia", 10 | "bob" 11 | ] 12 | t.equal( 13 | contains("samuel", hook), 14 | false, 15 | "a string is NOT present in an array" 16 | ) 17 | t.equal( 18 | contains("bob", hook), 19 | true, 20 | "a string is present in an array" 21 | ) 22 | t.equal( 23 | contains("samuel", "my name is samuel l. jackson"), 24 | true, 25 | "a string is present in another string" 26 | ) 27 | t.equal( 28 | containsCurried("dustin")(hook), 29 | true, 30 | "can be curried" 31 | ) 32 | t.end() 33 | }) 34 | -------------------------------------------------------------------------------- /src/assign.js: -------------------------------------------------------------------------------- 1 | import forIn from "./forIn" 2 | 3 | /** 4 | * Assigns the values from one or more Objects onto another Object. 5 | * This mutates the original Object. 6 | * 7 | * @function 8 | * @name assign 9 | * @param {object} obj An Object to mutate with the values from one (or more) additionally supplied Objects 10 | * @param {object} ...resOfObjects One or more Objects to extract from and assign onto the first Object 11 | * @returns {object} The first object mutated with the values from any other object passed in 12 | */ 13 | function assign(obj, ...resOfObjects) { 14 | const numOfObjs = resOfObjects.length 15 | for (let i = 0; i < numOfObjs; i++) { 16 | const currentObj = resOfObjects[i] || {} 17 | forIn((key, val) => { 18 | obj[key] = val 19 | }, currentObj) 20 | } 21 | return obj 22 | } 23 | 24 | export default assign 25 | -------------------------------------------------------------------------------- /src/reduce.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * A simple wrapper around native Array.prototype.reduce(), for use in a compose/pipe chain of functions. 5 | * Can also reduce the key/value pairs of an object, if one is supplied in place of an array. 6 | * @param {function} fn - A function to control the reduction of each item in the array|object into the single output value 7 | * @param {*} defaultVal - A starting value for the reduction accumulator 8 | * @param {Array<*>|object} arr - An array of values of any type OR an object containing key/value pairs 9 | * @returns {Array<*>|object} The original Array|Object somehow reduced to one value, according to the supplied function 10 | */ 11 | export default function reduce( 12 | fn: (...params: any[]) => any, 13 | defaultVal: any, 14 | arr: T 15 | ): T 16 | -------------------------------------------------------------------------------- /src/toCamelCase.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Transforms a string value into one which is camel cased. 3 | * Hyphens and underscores are removed and interpred as the boundaries for new words. 4 | * The first letter of each new word - not preceded by whitespace - is capitalized. 5 | * 6 | * @function 7 | * @name toCamelCase 8 | * @param {string} str A string which may contain underscores and hyphens and/or may be title-cased. 9 | * @returns {string} A new string that is without hyphens and underscores and the first letter of every new word boundary is capitalized, unless preceded by whitespace 10 | */ 11 | function toCamelCase(str) { 12 | return ( 13 | str[0].toLowerCase() + 14 | str 15 | .split(/[\s._-]/) 16 | .map((s) => s[0].toUpperCase() + s.slice(1)) 17 | .join("") 18 | .slice(1) 19 | ) 20 | } 21 | 22 | export default toCamelCase 23 | -------------------------------------------------------------------------------- /src/propIs.js: -------------------------------------------------------------------------------- 1 | import propAt from "./propAt" 2 | import is from "./is" 3 | 4 | /** 5 | * Looks for a specified key on an Object you provide and checks to see if its corresponding value is of the type you specifiy. 6 | * 7 | * @function 8 | * @name propIs 9 | * @param {function | string} type A JavaScript type constructor function (ie `Boolean`, `RegExp`, `Date`, `Array`, `Object`, `Number`, `String`, etc) OR a string represention of the type (ie, "boolean", "regexp", "date", "array", "object", "number", "string", etc) 10 | * @param {string} prop A key to search for on the Object 11 | * @param {object} obj An object which may contain a specified prop 12 | * @returns {boolean} Whether or not the requested prop is of the type specified 13 | */ 14 | function propIs(type, prop, obj) { 15 | return is(type, propAt(prop, obj)) 16 | } 17 | 18 | export default propIs 19 | -------------------------------------------------------------------------------- /src/is.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks to see if a value is a certain type. 3 | * You may specify that type as a case-insensitive string 4 | * (ie, 'string', 'boolean', 'object', 'function', 'array', 'regexp', 'date', 'set, 'map'), OR a JavaScript type constructor function (ie, String, Function, Boolean, Array, RegExp, Date, Set, Map). 5 | * @example 6 | * is('boolean', true) 7 | * is('array', [1, 2, 3]) 8 | * is(RegExp, /[a-z0-9]/) 9 | * is(Function, () => null) 10 | * @param {function} ofType - A JavaScript type constructor function (like `Function`, `String`, `RegExp`, `Boolean`, `Array`, `Object`, etc.) or a string value matching the name of one 11 | * @param {*} val - A value (of any type) 12 | * @returns {boolean} Whether or not the value matches the specified type 13 | */ 14 | export default function is(ofType: ((...params: any[]) => any) | string, val: any): boolean 15 | -------------------------------------------------------------------------------- /test/benchmarks/findIndex.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import R from "ramda" 4 | import * as Fx from "fxjs" 5 | import { findIndex } from "../../src" 6 | import { hook } from "../__mocks__" 7 | 8 | const arr = Object.values(hook) 9 | const predicate = val => /uli/.test(val) 10 | 11 | test("findIndex", benchmark => { 12 | benchmark( 13 | () => findIndex(predicate, arr), 14 | "Vanillas \"findIndex\"" 15 | ) 16 | benchmark( 17 | () => _.findIndex(arr, predicate), 18 | "Lodash \"findIndex\"" 19 | ) 20 | benchmark( 21 | () => R.findIndex(predicate, arr), 22 | "Ramda \"findIndex\"" 23 | ) 24 | benchmark( 25 | () => Fx.findIndex(predicate, arr), 26 | "FxJs \"findIndex\"" 27 | ) 28 | benchmark( 29 | () => arr.findIndex(predicate), 30 | "(native) \"Array.findIndex()\"" 31 | ) 32 | }) 33 | -------------------------------------------------------------------------------- /src/curryN.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Same as the regular curry function, but you must also specify the number of arguments that the curried function will take before being fully executed. 3 | * You would use this in cases where it cannot be inferred from the curried function itself. 4 | * @param {number} arity - The number of arguments the curried function will received before being executed 5 | * @param {function} fn - A Function whose signature needs to changed from requiring all at once to providing them one (or more) at a time. 6 | * @returns {function} A new Function that will wait until all (arity) arguments have been supplied before returning a result (otherwise it will continue to return a new Function that is ready to receive the next argument) 7 | */ 8 | export default function curryN( 9 | arity: number, 10 | fn: (...params: any[]) => any 11 | ): (...params: any[]) => any 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "allowJs": true, 5 | "noEmit": false, 6 | "strict": true, 7 | "sourceMap": true, 8 | "skipLibCheck": true, 9 | "noImplicitAny": false, 10 | "noImplicitThis": false, 11 | "resolveJsonModule": true, 12 | "emitDecoratorMetadata": true, 13 | "experimentalDecorators": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "target": "es6", 16 | "declaration": true, 17 | "lib": ["ES2019"], 18 | "module": "commonjs", 19 | "moduleResolution": "node", 20 | "outDir": "build", 21 | "esModuleInterop": true, 22 | "baseUrl": "." 23 | }, 24 | "exclude": [ 25 | "node_modules", 26 | "scripts", 27 | "benchmark", 28 | "docs", 29 | "build", 30 | "test" 31 | ], 32 | "include": [ 33 | "src/**/*" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /test/case.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import toTitleCase from "../src/toTitleCase" 3 | import toKebabCase from "../src/toKebabCase" 4 | 5 | tape("\"toTitleCase\" will convert the first letter of a word to uppercase", t => { 6 | t.equal(toTitleCase("lorem"), "Lorem") 7 | t.equal(toTitleCase("lorem ipsum"), "Lorem Ipsum") 8 | t.equal(toTitleCase(" lorem ipsum dolor sit amet "), " Lorem Ipsum Dolor Sit Amet ") 9 | t.end() 10 | }) 11 | 12 | tape("\"toKebabCase\" will hyphenate a camelcased, titlecased, or snakecased string", t => { 13 | t.equal(toKebabCase("lorem"), "lorem") 14 | t.equal(toKebabCase("loremIpsum"), "lorem-ipsum") 15 | t.equal(toKebabCase(" lorem_ipsum_dolorSitAmet "), " lorem-ipsum-dolor-sit-amet ") 16 | t.equal(toKebabCase("lorem99Ipsum"), "lorem99-ipsum") 17 | t.equal(toKebabCase("Lorem ipsum_dolor__Sit-amet"), "lorem-ipsum-dolor-sit-amet") 18 | t.end() 19 | }) 20 | -------------------------------------------------------------------------------- /test/benchmarks/mapString.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import { mapString } from "../../src" 3 | 4 | const val = "lorem ipsum dolor sit amet. consectetur adipiscing elit." 5 | 6 | /* eslint-disable-next-line jsdoc/require-jsdoc */ 7 | function mapStringArr(fn, str) { 8 | const len = str.length 9 | const strArr = Array(len) 10 | for (let i = 0; i < len; i++) { 11 | strArr[i] = fn(str[i], i, str) 12 | } 13 | return strArr.join("") 14 | } 15 | 16 | test("mapString", benchmark => { 17 | benchmark( 18 | () => mapString(str => str.toUpperCase(), val), 19 | "Vanillas \"mapString\"" 20 | ) 21 | benchmark( 22 | () => mapStringArr(str => str.toUpperCase(), val), 23 | "Char array" 24 | ) 25 | benchmark( 26 | () => val.split("") 27 | .map(str => str.toUpperCase()) 28 | .join(""), 29 | "(native) \"String.split().map().join()\"" 30 | ) 31 | }) 32 | -------------------------------------------------------------------------------- /test/isPrimitive.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import isPrimitive from "../src/isPrimitive" 3 | 4 | tape("\"isPrimitive\" checks for JavaScript primitives", t => { 5 | t.equal(isPrimitive(true), true, "Booleans are primitive") 6 | t.equal(isPrimitive("no YOU'RE primitive!"), true, "Strings are primitive") 7 | t.equal(isPrimitive(99), true, "Numbers are primitive") 8 | t.equal(isPrimitive(Symbol("a")), true, "Symbols are primitive") 9 | t.equal(isPrimitive({}), false, "Objects are NOT primitive") 10 | t.equal(isPrimitive([]), false, "Arrays are NOT primitive") 11 | t.equal(isPrimitive(new Date()), false, "Date are NOT considered primitive in the case we might usually mean") 12 | t.equal(isPrimitive(new RegExp()), false, "RegExes are NOT considered primitive in the case we might usually mean") 13 | t.equal(isPrimitive(() => 42), false, "Functions are NOT primitive") 14 | t.end() 15 | }) 16 | -------------------------------------------------------------------------------- /src/flatten.js: -------------------------------------------------------------------------------- 1 | import isArrayish from "./isArrayish" 2 | 3 | /** 4 | * Extracts nested arrays (of any depth) from a provided array, placing them onto on single new array. 5 | * 6 | * @function 7 | * @name flatten 8 | * @param {Array>|Array<*>} arr An array of values that may or may not be nested arrays themselves 9 | * @returns {Array<*>} A new array of values, but with any nested arrays from the original input extracted onto one single (flat) array 10 | */ 11 | function flatten(arr) { 12 | const newArr = [] 13 | let i = -1 14 | const len = arr.length 15 | while (++i < len) { 16 | const val = arr[i] 17 | if (isArrayish(val)) { 18 | let j = -1 19 | const nlen = val.length 20 | while (++j < nlen) { 21 | newArr.push(val[j]) 22 | } 23 | } else { 24 | newArr.push(val) 25 | } 26 | } 27 | return newArr 28 | } 29 | 30 | export default flatten 31 | -------------------------------------------------------------------------------- /test/benchmarks/clone.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-object-spread */ 2 | import { test } from "reality-check" 3 | import _ from "lodash" 4 | import R from "ramda" 5 | import * as Fx from "fxjs" 6 | import { clone } from "../../src" 7 | import { hook } from "../__mocks__" 8 | 9 | test("clone", benchmark => { 10 | benchmark( 11 | () => clone(hook), 12 | "Vanillas \"clone\"" 13 | ) 14 | benchmark( 15 | () => _.clone(hook), 16 | "Lodash \"clone\"" 17 | ) 18 | benchmark( 19 | () => R.clone(hook), 20 | "Ramda \"clone\"" 21 | ) 22 | benchmark( 23 | () => Fx.clone(hook), 24 | "FxJs \"clone\"" 25 | ) 26 | benchmark( 27 | () => { 28 | const newObj = { ...hook } 29 | return newObj 30 | }, 31 | "(native) \"clone by destructuring\"" 32 | ) 33 | benchmark( 34 | () => Object.assign({}, hook), 35 | "(native) \"Object.assign({}, ..)\"" 36 | ) 37 | }) 38 | -------------------------------------------------------------------------------- /test/hasNestedProp.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import hasNestedProp from "../src/hasNestedProp" 3 | import { hasNestedProp as hasCurried } from "../src/curried" 4 | 5 | tape("\"hasNestedProp\" inspects own props on a given object for a specified key", t => { 6 | t.equal( 7 | hasNestedProp("orange", {red: "blue", orange: "purple"}), 8 | true, 9 | "Handles a single (root-level) key easily" 10 | ) 11 | 12 | const latin = {lorem: { ipsum: { dolor: { sit: "amet" } } } } 13 | 14 | t.equal( 15 | hasNestedProp("lorem.ipsum.dolor.sit", latin), 16 | true, 17 | "nested prop path that is dot-separated" 18 | ) 19 | t.equal( 20 | hasNestedProp(["lorem", "ipsum", "dolor", "sit"], latin), 21 | true, 22 | "nested prop path that is an array of path \"pieces\"" 23 | ) 24 | t.equal(hasCurried(["lorem", "ipsum", "dolor", "sit"])(latin), true, "can be curried") 25 | t.end() 26 | }) 27 | -------------------------------------------------------------------------------- /test/benchmarks/concat.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import R from "ramda" 4 | import { concat } from "../../src" 5 | import { hook, sphere } from "../__mocks__" 6 | 7 | const hookFirstNames = Object.values(hook) 8 | const sphereFirstNames = Object.values(sphere) 9 | 10 | test("concat", benchmark => { 11 | benchmark( 12 | () => concat(hookFirstNames, sphereFirstNames), 13 | "Vanillas \"concat\"" 14 | ) 15 | benchmark( 16 | () => _.concat(hookFirstNames, sphereFirstNames), 17 | "Lodash \"concat\"" 18 | ) 19 | benchmark( 20 | () => R.concat(hookFirstNames, sphereFirstNames), 21 | "Ramda \"concat\"" 22 | ) 23 | benchmark( 24 | () => hookFirstNames.concat(sphereFirstNames), 25 | "(native) \"Array.concat()\"" 26 | ) 27 | benchmark( 28 | () => [...hookFirstNames, ...sphereFirstNames], 29 | "(native) \"[ ...val1, ...val2 ]\"" 30 | ) 31 | }) 32 | -------------------------------------------------------------------------------- /src/reduce.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A simple wrapper around native Array.prototype.reduce(), for use in a compose/pipe chain of functions. 3 | * Can also reduce the key/value pairs of an object, if one is supplied in place of an array. 4 | * 5 | * @function 6 | * @name reduce 7 | * @param {function} fn A function to control the reduction of each item in the array|object into the single output value 8 | * @param {*} defaultVal A starting value for the reduction accumulator 9 | * @param {Array<*>|object} arr An array of values of any type OR an object containing key/value pairs 10 | * @returns {*} The original Array|Object somehow reduced to one value, according to the supplied function 11 | */ 12 | function reduce(fn, defaultVal, arr) { 13 | return typeof arr.reduce === "function" 14 | ? arr.reduce(fn, defaultVal) 15 | : Object.keys(arr).reduce((acc, key) => fn(acc, arr[key], key), defaultVal) 16 | } 17 | 18 | export default reduce 19 | -------------------------------------------------------------------------------- /test/toInteger.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import toInteger from "../src/toInteger" 3 | 4 | tape("\"toInteger\" will convert the a given value to an integer", t => { 5 | t.equal(toInteger("lorem"), 0, "values that would be NaN will be returned as 0") 6 | t.equal(toInteger("17.53"), 18, "rounds up to the next whole number when appropriate") 7 | t.equal(toInteger("17.33"), 17, "rounds down to the next whole number when appropriate") 8 | t.equal(toInteger(17.99), 18, "handles values that are already numeric") 9 | t.equal(toInteger({}), 0, "objects cannot be converted") 10 | t.equal(toInteger([]), 0, "arrays cannot be converted") 11 | t.equal(toInteger(new Date()), 0, "dates cannot be converted") 12 | t.equal(toInteger(new RegExp()), 0, "regexes cannot be converted") 13 | t.equal(toInteger(true), 1, "A value of \"true\" is one") 14 | t.equal(toInteger(false), 0, "A value of \"false\" is zero") 15 | t.end() 16 | }) 17 | -------------------------------------------------------------------------------- /test/intersection.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import intersection from "../src/intersection" 3 | import { intersection as intersectionCurried } from "../src/curried" 4 | 5 | tape("\"intersection\" gets the values in common between two arrays", t => { 6 | const hook = [ 7 | "dustin", 8 | "robin", 9 | "julia", 10 | "bob" 11 | ] 12 | const sphere = [ 13 | "dustin", 14 | "samuel", 15 | "sharon", 16 | "queen" 17 | ] 18 | t.deepEqual( 19 | intersection(hook, sphere), 20 | ["dustin"] 21 | ) 22 | t.deepEqual( 23 | intersection([1, 2, 3, 4, 5, 6, 7, 8, 9], [2, 4, 6, 8, 10, 12]), 24 | [2, 4, 6, 8], 25 | "numbers" 26 | ) 27 | t.deepEqual( 28 | intersectionCurried(hook)(sphere), 29 | ["dustin"], 30 | "can be curried" 31 | ) 32 | t.deepEqual( 33 | intersection([], sphere), 34 | [], 35 | "empty arrays behave as expected" 36 | ) 37 | t.end() 38 | }) 39 | -------------------------------------------------------------------------------- /src/_internal/coerce.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ensures a given value is wrapped in a function (leaves alone the value if it is already a function). 3 | * 4 | * @function 5 | * @private 6 | * @name toFunction 7 | * @param {function|*} v A value which may be a function or a value a function should return 8 | * @returns {function} Either the original value (if it was a function) or a function which returns the original value 9 | */ 10 | export const toFunction = v => (typeof v === "function" ? v : () => v) 11 | 12 | /** 13 | * Ensures a given value is wrapped in an array (leaves alone the value if it is already an array). 14 | * 15 | * @function 16 | * @private 17 | * @name toArray 18 | * @param {Array<*>|*} v A value which may be an array or should be placed into an array 19 | * @returns {Array<*>} Either the original value (if it was an array) or an array containing the original value 20 | */ 21 | export const toArray = v => (Array.isArray(v) ? v : [v]) 22 | -------------------------------------------------------------------------------- /src/clone.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable consistent-return */ 2 | import isNil from "./isNil" 3 | import isObject from "./isObject" 4 | import forIn from "./forIn" 5 | 6 | /** 7 | * Recursively copies the content of an Object into a new Object 8 | * 9 | * @function 10 | * @name clone 11 | * @param {object|Array<*>} obj An Object (or Array) from which to create a deep copy 12 | * @returns {object|Array<*>} The new (cloned) Object (or Array) 13 | */ 14 | function clone(obj) { 15 | if (!isNil(obj)) { 16 | if (Array.isArray(obj)) { 17 | return obj.map(v => (isObject(v) ? clone(v) : v)) 18 | } 19 | if (isObject(obj)) { 20 | const newObj = {} 21 | forIn((key, val) => { 22 | if (isObject(val) || Array.isArray(val)) { 23 | newObj[key] = clone(val) 24 | } else { 25 | newObj[key] = val 26 | } 27 | }, obj) 28 | return newObj 29 | } 30 | } 31 | } 32 | 33 | export default clone 34 | -------------------------------------------------------------------------------- /src/eitherOr.js: -------------------------------------------------------------------------------- 1 | import curry from "./curry" 2 | 3 | /** 4 | * A function that accepts two functions and a value and will return the first result which is "truthy". 5 | * This function is curried because it doesn't make any sense to even have it unless you have the functions ahead of time but not the value. 6 | * 7 | * @function 8 | * @name eitherOr 9 | * @param {function} fnA The first function to be executed on the value 10 | * @param {function} fnB The second function to be executed on the value 11 | * @param {*} val A value to be passed into the functions 12 | * @returns {*} The result of executing value passed in 13 | */ 14 | function eitherOr(fnA, fnB, val) { 15 | if (typeof fnA !== "function") { 16 | throw new TypeError(`${fnA} is not a function`) 17 | } 18 | if (typeof fnB !== "function") { 19 | throw new TypeError(`${fnB} is not a function`) 20 | } 21 | return fnA(val) || fnB(val) 22 | } 23 | 24 | export default curry(eitherOr) 25 | -------------------------------------------------------------------------------- /src/curry.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Takes a Function whose params are meant to be supplied all at once and changes it so they can be supplied one at a time. 3 | * As each argument is supplied a new Function is returned that is ready to receive the next argument. 4 | * This continues until all arguments for your origianl function have been supplied and then the actual result is returned. 5 | * 6 | * Note: you cannot set default values for curried function params (again, you _cannot_ set default values for curried function params) 7 | * @param {function} fn - A Function whose signature needs to changed from requiring all at once to providing them one (or more) at a time. 8 | * @returns {function} A new Function that will wait until all arguments have been supplied before returning a result (otherwise it will continue to return a new Function that is ready to receive the next argument) 9 | */ 10 | export default function curry(fn: (...params: any[]) => any): (...params: any[]) => any 11 | -------------------------------------------------------------------------------- /test/toNumber.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import toNumber from "../src/toNumber" 3 | 4 | tape("\"toNumber\" can convert a numeric string value to a number", t => { 5 | t.equal(toNumber("0"), 0, "0") 6 | t.equal(toNumber(undefined), undefined, "undefined") 7 | t.equal(toNumber(null), undefined, "null") 8 | t.equal(toNumber(""), undefined, "blank string") 9 | t.equal(toNumber("0.00"), 0, "0.00") 10 | t.equal(toNumber("1"), 1, "1") 11 | t.equal(toNumber("-1"), -1, "-1") 12 | 13 | const randomPositiveNumber = Math.floor(Math.random() * 1000) 14 | t.equal( 15 | toNumber(`${randomPositiveNumber}`), 16 | randomPositiveNumber, 17 | `random positive number: ${randomPositiveNumber}` 18 | ) 19 | 20 | const randomNegativeNumber = -Math.floor(Math.random() * 1000) 21 | t.equal( 22 | toNumber(`${randomNegativeNumber}`), 23 | randomNegativeNumber, 24 | `random negative number: ${randomNegativeNumber}` 25 | ) 26 | 27 | t.end() 28 | }) 29 | -------------------------------------------------------------------------------- /src/append.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Blends two values together based on their type. 5 | * If both are objects this would result in a new object with the second value's props merged onto the first's. 6 | * If the first value is an array, this would result in a new array with the second value concatenated onto the first value. 7 | * If both values are strings or numbers, a new string will be returned with the second value added onto the first. 8 | * @param {string|number|object|Array<*>} firstVal - A value that will have another appended onto 9 | * @param {string|number|object|Array<*>} secondVal - A value to append to the first value 10 | * @returns {string|number|object|Array<*>} A new Array, Object, or String that has the characters/values from the second provided value merged _after_ those from the first provided value 11 | */ 12 | export default function append( 13 | firstVal: T, 14 | secondVal: T 15 | ): T 16 | -------------------------------------------------------------------------------- /src/propSet.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Looks for a specified key on an Object you provide and sets it to the provided value. 5 | * If the path does not exist, it will be created (you can check for the path via `propIs` or `propAt` or `propEquals` first if you don't wish to create the path every time). 6 | * The is performed safely and will not throw an error if somethign on the prop path chain you specify doesn't exist. 7 | * @param {string|Array} prop - A top-level key OR a deeply nested prop path (which may be represented as an array or as a single dot-delimited string) 8 | * @param {*} val - A value to be placed at the provided property path 9 | * @param {object} obj - An object which onto which the value will be placed 10 | * @returns {object} The original object, but modified to have the provided value placed at the specified path it does not exist 11 | */ 12 | export default function propSet(prop: string | string[], val: any, obj: AnyObject): any 13 | -------------------------------------------------------------------------------- /test/benchmarks/cloneDeep.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import R from "ramda" 4 | import * as Fx from "fxjs" 5 | import { clone } from "../../src" 6 | import { hook, sphere, jurrassicPark } from "../__mocks__" 7 | 8 | const val = { hook, sphere, jurrassicPark } 9 | 10 | test("cloneDeep", benchmark => { 11 | benchmark( 12 | () => clone(val), 13 | "Vanillas \"clone\"" 14 | ) 15 | benchmark( 16 | () => _.cloneDeep(val), 17 | "Lodash \"cloneDeep\"" 18 | ) 19 | benchmark( 20 | () => R.clone(val), 21 | "Ramda \"clone\"" 22 | ) 23 | benchmark( 24 | () => Fx.clone(val), 25 | "FxJs \"clone\"" 26 | ) 27 | benchmark( 28 | () => { 29 | const newObj = { 30 | ...val, 31 | hook: { ...val.hook }, 32 | sphere: { ...val.sphere }, 33 | jurrassicPark: { ...val.jurrassicPark } 34 | } 35 | return newObj 36 | }, 37 | "(native) \"clone by destructuring\"" 38 | ) 39 | }) 40 | -------------------------------------------------------------------------------- /src/difference.js: -------------------------------------------------------------------------------- 1 | import { _includes } from "./_internal/_includes" 2 | 3 | /** 4 | * Compares two lists of Strings/Numbers and returns the values that are different between the two lists 5 | * 6 | * @function 7 | * @name difference 8 | * @param {Array|Array} arr1 An Array of Strings 9 | * @param {Array|Array} arr2 An Array of Strings 10 | * @returns {Array|Array} An array of values that are different between the two lists 11 | */ 12 | function difference(arr1, arr2) { 13 | const diff = [] 14 | const len1 = arr1.length 15 | const len2 = arr2.length 16 | 17 | if (len1 === 0 && len2 === 0) { 18 | return diff 19 | } else if (len1 === 0) { 20 | return arr2 21 | } else if (len2 === 0) { 22 | return arr1 23 | } 24 | 25 | for (let i = 0; i < len1; i++) { 26 | const val = arr1[i] 27 | if (!_includes(val, arr2)) { 28 | diff.push(val) 29 | } 30 | } 31 | 32 | return diff 33 | } 34 | 35 | export default difference 36 | -------------------------------------------------------------------------------- /src/hasNestedProp.js: -------------------------------------------------------------------------------- 1 | import has from "./has" 2 | 3 | /** 4 | * Checks if a given Object contains a (potentially) nested property of a specified path 5 | * 6 | * @function 7 | * @name hasNestedProp 8 | * @param {Array | string} prop A prop name, a dot-separated prop path, or an array of prop path "pieces" to look for in the object 9 | * @param {object} obj An Object to inspect for a given prop at the specified path 10 | * @returns {boolean} Whether the object contains the specified prop path 11 | */ 12 | function hasNestedProp(prop, obj) { 13 | const paths = typeof prop === "string" ? prop.split(".") : prop 14 | const len = paths.length 15 | if (len === 0) return false 16 | let idx = 0 17 | let val = obj[paths[idx]] 18 | let hasProp = has(paths[idx], obj) 19 | while (++idx < len && hasProp === true) { 20 | hasProp = has(paths[idx], val) 21 | if (hasProp) { 22 | val = val[paths[idx]] 23 | } 24 | } 25 | return hasProp 26 | } 27 | 28 | export default hasNestedProp 29 | -------------------------------------------------------------------------------- /src/promiseAll.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Gathers an Array of Functions that return Promises and returns an Array of results, once they have all completed. 3 | * The only difference between this and native Promise.all() is that these promises will _all_ be resolved/rejected before the final Promise (containing all the results) is returned. With native Promise.all(), the first unhandled rejection will cause the whole endeavor to be terminated. 4 | * Addtionally you can pass in a flag to force caught errors to be ignored entirely. 5 | * @param {Array} requests - An array of Functions that return Promises 6 | * @param {boolean} ignoreErrors - Whether or not to ignore errors entirely (this will cause all the results to be returned and any Errors will be returned in place of the results) 7 | * @returns {Promise<*>} A Promise that will resolve once all of the Promises are resolved/rejected 8 | */ 9 | export default function promiseAll(requests: ((...params: any[]) => void)[], ignoreErrors: boolean): Promise 10 | -------------------------------------------------------------------------------- /test/escapeHtml.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import { escapeHtml } from "../src" 3 | 4 | const val = "don't take my & mis-use it for \"evil\"" 5 | 6 | tape("\"escapeHtml\" will html-escape certain values that pose a risk for XSS and SQL injection", t => { 7 | t.equal(escapeHtml(), "", "empty args means empty string returned") 8 | t.equal(escapeHtml(null), "", "null means empty string returned") 9 | t.equal(escapeHtml(999), "999", "numbers are returned as-is") 10 | t.equal(escapeHtml(["abc &", "def's"]), "abc &,def's", "can handle arrays too") 11 | t.equal( 12 | escapeHtml(val), 13 | "don't take my <insert_name_of_thing_here> & mis-use it for "evil"", 14 | "converts apostrophes, blockquotes, ampersands and angle brackets" 15 | ) 16 | t.equal( 17 | escapeHtml("lorem ipsum dolor sit amet"), 18 | "lorem ipsum dolor sit amet", 19 | "strings not needing to be escaped are returned as-is" 20 | ) 21 | 22 | t.end() 23 | }) 24 | -------------------------------------------------------------------------------- /src/filter.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Applies a filtering function you provide over a value you provide, according to its type. 5 | * String values will have the filtering function applied over every character in the String. 6 | * Objects will have the filtering function applied to every value in the Object. 7 | * Arrays (or Array-like values) will have the filtering function applied to every value in the Array. 8 | * If the type of your value is none of the above, the value will be returned as-is. 9 | * @param {function} fn - A filtering function that is invoked on the provided value 10 | * @param {object|string|Array<*>} val - An Object/Array/String whose values/chars will be filtered 11 | * @returns {object|string|Array<*>} A new value that is the result of the filtering operation over all the chars or values in the original String/Object/Array 12 | */ 13 | export default function filter( 14 | fn: (...params: any[]) => any, 15 | val: T 16 | ): T 17 | -------------------------------------------------------------------------------- /src/cond.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Several pieces of conditional logic to apply against a value and the _first_ one which matches will have a corresponding transformation applied to it. 3 | * 4 | * Supply an Array of pairs: 5 | * - the first value is the conditional logic (`Function`) to match against the value 6 | * - the second value is the transformation (`Function`) to apply to the value if the condition was matched 7 | * 8 | * __Note__: if your tranformation is _not_ a function then it will returned as-is in response to a succesfully met condition 9 | * @param {Array>} conditionalTransforms - An array of arrays (which have two values: the condition function and the transformation function) 10 | * @param {*} val - A value of any type that will be transformed according to the appropriate condition. 11 | * @returns {*} The provided value transformed by the appropriate matching conditional transformation 12 | */ 13 | export default function cond(conditionalTransforms: ((...params: any[]) => void)[][], val: any): any 14 | -------------------------------------------------------------------------------- /jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true, 4 | "dictionaries": ["jsdoc"] 5 | }, 6 | "plugins": [ 7 | "plugins/markdown" 8 | ], 9 | "source": { 10 | "includePattern": ".js$", 11 | "excludePattern": "(node_modules/|docs)", 12 | "include": ["src", "package.json", "README.md"] 13 | }, 14 | "templates": { 15 | "cleverLinks": true, 16 | "monospaceLinks": false, 17 | "search": true, 18 | "navLinks": [{ 19 | "label": "Github", 20 | "href": "https://github.com/arizonatribe/vanillas" 21 | }], 22 | "better-docs": { 23 | "name": "Vanillas", 24 | "title": "Simple and sweet utility functions for JavaScript and TypeScript applications", 25 | "navigation": [{ 26 | "label": "Github", 27 | "href": "https://github.com/arizonatribe/vanillas" 28 | }] 29 | } 30 | }, 31 | "opts": { 32 | "destination": "./docs/", 33 | "encoding": "utf8", 34 | "recurse": true, 35 | "template": "node_modules/better-docs" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/concat.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Adds the values from one Array onto another Array, returned as a new Array (ie, it does not mutate the first Array). 3 | * This operation is recursive, so you can supply as many arrays as you wish. 4 | * 5 | * @function 6 | * @name concat 7 | * @param {Array<*>} firstArr An Array of values (of any type) 8 | * @param {Array<*>} secondArr An Array of values (of any type) 9 | * @returns {Array<*>} A new Array with the values from the second array concatenated onto those from the first 10 | */ 11 | function concat(firstArr, secondArr, ...restOfArrs) { 12 | const firstLen = firstArr.length 13 | const len = secondArr.length + firstLen 14 | const newArr = new Array(len) 15 | for (let i = 0; i < firstLen; i++) { 16 | newArr[i] = firstArr[i] 17 | } 18 | for (let i = firstLen; i < len; i++) { 19 | newArr[i] = secondArr[i - firstLen] 20 | } 21 | if (restOfArrs.length) { 22 | return concat(newArr, restOfArrs[0], ...restOfArrs.slice(1)) 23 | } 24 | return newArr 25 | } 26 | 27 | export default concat 28 | -------------------------------------------------------------------------------- /src/memoize.js: -------------------------------------------------------------------------------- 1 | import has from "./has" 2 | 3 | /** 4 | * Takes a snapshot of the input args and the output result for a provided function, and on repeated usage will shortcut invoking the function and return the cached output instead, whenever the same input args are supplied to the function. 5 | * 6 | * @function 7 | * @name memoize 8 | * @param {function} fn A function whose input values (supplied later) will be cached with its output result, so that the invoking the function can be skipped the next time the same values are passed to it 9 | * @returns {function} A memoized version of the original function. It will cache the input values supplied to it each time it is used 10 | */ 11 | function memoize(fn) { 12 | const cache = {} 13 | /* eslint-disable-next-line jsdoc/require-jsdoc */ 14 | function inner(...args) { 15 | const key = JSON.stringify(args) 16 | if (!has(key, cache)) { 17 | cache[key] = fn.apply(fn, args) 18 | } 19 | return cache[key] 20 | } 21 | return inner 22 | } 23 | 24 | export default memoize 25 | -------------------------------------------------------------------------------- /src/toHashCode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates a numeric hash code (integer) between 1 and the specified `limit`. 3 | * The sum of all the string char codes is the dividend and the limit is the divisor, but the _remainder_ (integer) will be the actual hash code. 4 | * 5 | * @function 6 | * @name toHashCode 7 | * @param {string|number} val A string or numeric value for which to generate the hash code 8 | * @param {number} [limit=26] The limit of the range of hash code values (this will be the divisor in the modulus division) 9 | * @returns {number} A numeric representation of the characters in the provided string (will return zero if a string|number is not provided as input) 10 | */ 11 | function toHashCode(val, limit = 26) { 12 | let sum = 0 13 | const strVal = typeof val === "string" 14 | ? val 15 | : typeof val === "number" 16 | ? `${val}` 17 | : "" 18 | 19 | for (let i = 0, len = strVal.length; i < len; i++) { 20 | sum += strVal.charCodeAt(i) 21 | } 22 | 23 | return sum % limit 24 | } 25 | 26 | export default toHashCode 27 | -------------------------------------------------------------------------------- /src/intersection.js: -------------------------------------------------------------------------------- 1 | import { _includes } from "./_internal/_includes" 2 | 3 | /** 4 | * Compares two lists of Strings/Numbers and returns the values that are in common (intersect) between the two lists 5 | * 6 | * @function 7 | * @name intersection 8 | * @param {Array | Array} arr1 An Array of Strings/Numbers 9 | * @param {Array | Array} arr2 An Array of Strings/Numbers 10 | * @returns {Array | Array} The values in common between the two lists 11 | */ 12 | function intersection(arr1, arr2) { 13 | const diff = [] 14 | const len1 = arr1.length 15 | const len2 = arr2.length 16 | if (len1 === 0 || len2 === 0) return diff 17 | 18 | for (let i = 0; i < len1; i++) { 19 | const val = arr1[i] 20 | if (_includes(val, arr2)) { 21 | diff.push(val) 22 | } 23 | } 24 | for (let j = 0; j < len2; j++) { 25 | const val = arr2[j] 26 | if (_includes(val, arr1) && !_includes(val, diff)) { 27 | diff.push(val) 28 | } 29 | } 30 | return diff 31 | } 32 | 33 | export default intersection 34 | -------------------------------------------------------------------------------- /test/benchmarks/propEquals.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import R from "ramda" 3 | import { propEquals } from "../../src" 4 | import { propEquals as curriedPropEquals } from "../../src/curried" 5 | import val from "../__mocks__" 6 | 7 | test("propEquals", benchmark => { 8 | benchmark( 9 | () => propEquals(["pantheon", "Mercury", "greek"], "Hermes", val), 10 | "Vanillas \"propEquals\"" 11 | ) 12 | benchmark( 13 | () => R.pathEq(["pantheon", "Mercury", "greek"], "Hermes", val), 14 | "Ramda \"pathEq\"" 15 | ) 16 | benchmark( 17 | () => val && val.pantheon && val.pantheon.Mercury && val.pantheon.Mercury.greek === "Hermes", 18 | "(native) \"&& until you find it\"" 19 | ) 20 | }) 21 | 22 | test("curriedPropEquals", benchmark => { 23 | benchmark( 24 | () => curriedPropEquals(["pantheon", "Mercury", "greek"])("Hermes")(val), 25 | "Vanillas (curried) \"propEquals\"" 26 | ) 27 | benchmark( 28 | () => R.pathEq(["pantheon", "Mercury", "greek"])("Hermes")(val), 29 | "Ramda (curried) \"pathEq\"" 30 | ) 31 | }) 32 | -------------------------------------------------------------------------------- /src/shim.js: -------------------------------------------------------------------------------- 1 | import isNil from "./isNil" 2 | import isObject from "./isObject" 3 | 4 | const getter = { 5 | get(target, key) { 6 | if (isObject(target[key])) { 7 | return new Proxy(target[key], getter) 8 | } 9 | if (isNil(target[key])) { 10 | return new Proxy({[Symbol.toPrimitive]: String}, getter) 11 | } 12 | return target[key] 13 | } 14 | } 15 | 16 | /** 17 | * Recursively shims an Object. 18 | * Every time the getter is invoked (which happens whenever prop paths are referenced in the consuming code), the path is shimmed with another Proxy. 19 | * It is not a polyfill but rather a way to keep deep prop paths that may not exist on the source object from throwing an error. 20 | * 21 | * @function 22 | * @name shim 23 | * @param {object} obj An object that will be the Proxy's source 24 | * @returns {Proxy} An Object that will return props on the source Object if they exist but safely handle missing prop paths without throwing errors. 25 | */ 26 | function shim(obj) { 27 | return new Proxy(obj, getter) 28 | } 29 | 30 | export default shim 31 | -------------------------------------------------------------------------------- /src/map.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyObject } from "./types" 2 | 3 | /** 4 | * Applies a mapping function you provide over a value you provide, according to its type. 5 | * String values will have the mapping function applied over every character in the String. 6 | * Objects will have the mapping function applied to every value in the Object. 7 | * Arrays (or Array-like values) will have the mapping function applied to every value in the Array. 8 | * If the type of your value is none of the above, the value will be returned as-is. 9 | * Also, this mapping operation does _not_ mutate the original value. 10 | * @param {function} fn - A mapping function that is invoked on the provided value 11 | * @param {object|string|Array<*>} val - An Object/Array/String whose values/chars will be mapped over 12 | * @returns {object|string|Array<*>} A new value that is the result of the mapping operation over all the chars or values in the original String/Object/Array 13 | */ 14 | export default function map ( 15 | fn: (...params: any[]) => any, 16 | val: T 17 | ): T 18 | -------------------------------------------------------------------------------- /src/pipe.js: -------------------------------------------------------------------------------- 1 | import areAllFunctions from "./_internal/areAllFunctions" 2 | import identity from "./identity" 3 | 4 | /** 5 | * Creates a chain of Functions that will be executed in sequnce (from left to right), with the value from the previous Function fed into the next Function. 6 | * The value that the chain of functions will executed on can be provided later. 7 | * 8 | * @function 9 | * @name pipe 10 | * @param {function} ...fns One or more function to execute (in sequential order) on a value that will be supplied later 11 | * @returns {function} A single Function that is ready to receive a value and pass it through the piped chain of Functions 12 | */ 13 | function pipe(...fns) { 14 | const fnlen = fns.length 15 | if (fnlen === 0 || !areAllFunctions(fns)) return identity 16 | /* eslint-disable-next-line jsdoc/require-jsdoc */ 17 | function inner(...args) { 18 | let result = fns[0](...args) 19 | for (let i = 1; i < fnlen; i++) { 20 | result = fns[i](result) 21 | } 22 | return result 23 | } 24 | return inner 25 | } 26 | 27 | export default pipe 28 | -------------------------------------------------------------------------------- /src/compose.js: -------------------------------------------------------------------------------- 1 | import areAllFunctions from "./_internal/areAllFunctions" 2 | import identity from "./identity" 3 | 4 | /** 5 | * Creates a chain of Functions that will be executed in sequnce (from right to left), with the value from the previous Function fed into the next Function. 6 | * The value that the chain of functions will executed on can be provided later. 7 | * 8 | * @function 9 | * @name compose 10 | * @param {function} ...fns One or more function to execute (in sequential order) on a value that will be supplied later 11 | * @returns {function} A single Function that is ready to receive a value and pass it through the composed chain of Functions 12 | */ 13 | function compose(...fns) { 14 | let fnlen = fns.length 15 | if (fnlen === 0 || !areAllFunctions(fns)) return identity 16 | /* eslint-disable-next-line jsdoc/require-jsdoc */ 17 | function inner(...args) { 18 | let result = fns[--fnlen](...args) 19 | while (--fnlen >= 0) { 20 | result = fns[fnlen](result) 21 | } 22 | return result 23 | } 24 | return inner 25 | } 26 | 27 | export default compose 28 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | const { babel } = require("@rollup/plugin-babel") 2 | const commonjs = require("@rollup/plugin-commonjs") 3 | const { nodeResolve } = require("@rollup/plugin-node-resolve") 4 | const replace = require("@rollup/plugin-replace") 5 | const {terser} = require("rollup-plugin-terser") 6 | 7 | const env = process.env.NODE_ENV 8 | 9 | const config = { 10 | input: "src/index.js", 11 | output: { 12 | name: "vanillas", 13 | exports: "named", 14 | indent: false, 15 | file: "build/dist/vanillas.js", 16 | format: "umd" 17 | }, 18 | plugins: [ 19 | nodeResolve({jsnext: true}), 20 | commonjs(), 21 | babel({exclude: "node_modules/**"}), 22 | replace({"process.env.NODE_ENV": JSON.stringify(env)}) 23 | ] 24 | } 25 | 26 | if (env === "production") { 27 | config.output.file = "build/dist/vanillas.min.js" 28 | config.plugins.push( 29 | terser({ 30 | compress: { 31 | pure_getters: true, 32 | unsafe: true, 33 | unsafe_comps: true, 34 | warnings: false 35 | } 36 | }) 37 | ) 38 | } 39 | 40 | module.exports = config 41 | -------------------------------------------------------------------------------- /test/benchmarks/propAt.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import R from "ramda" 4 | import { propAt } from "../../src" 5 | import { propAt as curriedPropAt } from "../../src/curried" 6 | import val from "../__mocks__" 7 | 8 | test("propAt", benchmark => { 9 | benchmark( 10 | () => propAt(["pantheon", "Jupiter", "moons"], val), 11 | "Vanillas \"propAt\"" 12 | ) 13 | benchmark( 14 | () => _.get(val, ["pantheon", "Jupiter", "moons"]), 15 | "Lodash \"get\"" 16 | ) 17 | benchmark( 18 | () => R.path(["pantheon", "Jupiter", "moons"], val), 19 | "Ramda \"path\"" 20 | ) 21 | benchmark( 22 | () => val && val.pantheon && val.pantheon.Jupiter && val.pantheon.Jupiter.moons, 23 | "(native) \"&& until you find it\"" 24 | ) 25 | }) 26 | 27 | test("curriedPropAt", benchmark => { 28 | benchmark( 29 | () => curriedPropAt(["pantheon", "Jupiter", "moons"])(val), 30 | "Vanillas (curried) \"propAt\"" 31 | ) 32 | benchmark( 33 | () => R.path(["pantheon", "Jupiter", "moons"])(val), 34 | "Ramda (curried) \"path\"" 35 | ) 36 | }) 37 | -------------------------------------------------------------------------------- /src/mapObjectRecursive.js: -------------------------------------------------------------------------------- 1 | import forIn from "./forIn" 2 | import isObject from "./isObject" 3 | 4 | /** 5 | * Applies a mapping function you provide over every value in a given Object (recursively). 6 | * 7 | * @function 8 | * @name mapObjectRecursive 9 | * @param {function} fn A mapping function that is invoked on every value in the provided Object 10 | * @param {object} obj An Object whose values will be mapped over (recursively) 11 | * @returns {object} A new Object that is the result of the mapping operation over all the values in the original Object 12 | */ 13 | function mapObjectRecursive(fn, obj) { 14 | const newObj = {} 15 | forIn((key, val, ob) => { 16 | /* Looks redundant, but is written this way for speed */ 17 | if ( 18 | typeof val === "object" 19 | && val != null 20 | && ((val.constructor && val.constructor.name === "Object") || isObject(val)) 21 | ) { 22 | newObj[key] = mapObjectRecursive(fn, val) 23 | } else { 24 | newObj[key] = fn(val, key, ob) 25 | } 26 | }, obj) 27 | return newObj 28 | } 29 | 30 | export default mapObjectRecursive 31 | -------------------------------------------------------------------------------- /test/benchmarks/mapObject.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import * as raw from "radash" 4 | import R from "ramda" 5 | import { mapObject as FxMap } from "fxjs" 6 | import { mapObject } from "../../src" 7 | import { hook } from "../__mocks__" 8 | 9 | test("mapObject", (benchmark) => { 10 | benchmark(() => mapObject((str) => str.toUpperCase(), hook), "Vanillas \"mapObject\"") 11 | benchmark(() => _.mapValues(hook, (str) => str.toUpperCase()), "Lodash \"mapValues\"") 12 | benchmark(() => raw.mapValues(hook, (str) => str.toUpperCase()), "Radash \"mapValues\"") 13 | benchmark(() => FxMap((str) => str.toUpperCase(), hook), "FxJs \"map\"") 14 | benchmark(() => R.map((str) => str.toUpperCase(), hook), "Ramda \"map\"") 15 | benchmark( 16 | () => Object.keys(hook).reduce((o, key) => ({ ...o, [key]: hook[key].toUpperCase() }), {}), 17 | "(native) \"Object.keys( ).reduce()\"" 18 | ) 19 | benchmark( 20 | () => Object.entries(hook).reduce((o, [key, v]) => ({ ...o, [key]: v.toUpperCase() }), {}), 21 | "(native) \"Object.entries().reduce()\"" 22 | ) 23 | }) 24 | -------------------------------------------------------------------------------- /src/prepend.js: -------------------------------------------------------------------------------- 1 | import merge from "./merge" 2 | 3 | /** 4 | * Merges two values together, placing the characters (or values) from one before those from the other. 5 | * 6 | * @function 7 | * @name prepend 8 | * @param {Array<*> | object | string | number} firstVal An Array, Object, String or Number that the will have a new value(s) merged before its own characters/values 9 | * @param {Array<*> | object | string | number} secondVal An Array, Object, String or Number that the will merge _before_ those from the first provided value 10 | * @returns {Array<*> | object | string} A new Array, Object, or String that has the characters/values from the second provided value merged _before_ those from the first provided value 11 | */ 12 | function prepend(firstVal, secondVal) { 13 | if ([firstVal, secondVal].every(val => typeof val === "string" || typeof val === "number")) { 14 | return `${secondVal}${firstVal}` 15 | } 16 | if (Array.isArray(firstVal) && Array.isArray(secondVal)) { 17 | return [...secondVal, ...firstVal] 18 | } 19 | return merge(secondVal, firstVal) 20 | } 21 | 22 | export default prepend 23 | -------------------------------------------------------------------------------- /src/combine.js: -------------------------------------------------------------------------------- 1 | import merge from "./merge" 2 | import concat from "./concat" 3 | import isObject from "./isObject" 4 | 5 | /** 6 | * Combines two values of the same type (if it makes sense to combine them). 7 | * Numbers are summarized, strings and arrays are concatenated together, and true objects are merged (the second value merged on top of the first). 8 | * In any other case only the first value is returned. 9 | * 10 | * @function 11 | * @name combine 12 | * @param {*} val1 The base value to be combined with 13 | * @param {*} val2 The value to combine 14 | * @returns {*} If the values are of the same type, this represents the combined value of the two of them. Otherwise only the first value is returned 15 | */ 16 | function combine(val1, val2) { 17 | if ([val1, val2].every(v => typeof v === "number" || typeof v === "string")) { 18 | return val1 + val2 19 | } 20 | if (isObject(val1) && isObject(val2)) { 21 | return merge(val1, val2) 22 | } 23 | if (Array.isArray(val1) && Array.isArray(val2)) { 24 | return concat(val1, val2) 25 | } 26 | return val1 27 | } 28 | 29 | export default combine 30 | -------------------------------------------------------------------------------- /src/isPrimitive.js: -------------------------------------------------------------------------------- 1 | import getConstructorName from "./_internal/getConstructorName" 2 | 3 | /** 4 | * Checks if a given type name is a primitve type 5 | * 6 | * @function 7 | * @private 8 | * @name _isPrimitiveName 9 | * @param {string} typeName The name of the type 10 | * @returns {boolean} Whether or not the type's name is a primitive type name 11 | */ 12 | function _isPrimitiveName(typeName) { 13 | switch (typeName) { 14 | case "boolean": 15 | case "number": 16 | case "string": 17 | case "Boolean": 18 | case "Number": 19 | case "String": 20 | case "Symbol": 21 | return true 22 | default: 23 | return false 24 | } 25 | } 26 | 27 | /** 28 | * Checks if a given value is of a primitive type (ie, Boolean, String, Number, or Symbol). 29 | * 30 | * @function 31 | * @name isPrimitive 32 | * @param {*} val A value which may be of a primitive type 33 | * @returns {boolean} Whether or not the value is primitive 34 | */ 35 | function isPrimitive(val) { 36 | return _isPrimitiveName(typeof val) || _isPrimitiveName(getConstructorName(val)) 37 | } 38 | 39 | export default isPrimitive 40 | -------------------------------------------------------------------------------- /test/benchmarks/uniq.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import * as raw from "radash" 4 | import R from "ramda" 5 | import * as Fx from "fxjs" 6 | import { uniq } from "../../src" 7 | 8 | const nums = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9] 9 | const strs = ["abc", "abcdefg", "abc", "def", "defg", "def", "ghi", "klm", "nop", "eee", "eee", "nop"] 10 | 11 | test("uniqNumbers", (benchmark) => { 12 | benchmark(() => uniq(nums), "Vanillas \"uniq\" (list of nums)") 13 | benchmark(() => _.uniq(nums), "Lodash \"uniq\" (list of nums)") 14 | benchmark(() => raw.unique(nums), "Radash \"unique\" (list of nums)") 15 | benchmark(() => R.uniq(nums), "Ramda \"uniq\" (list of nums)") 16 | benchmark(() => Fx.uniq(nums), "FxJs \"uniq\" (list of nums)") 17 | }) 18 | 19 | test("uniqStrings", (benchmark) => { 20 | benchmark(() => uniq(strs), "Vanillas \"uniq\" (list of strings)") 21 | benchmark(() => _.uniq(strs), "Lodash \"uniq\" (list of strings)") 22 | benchmark(() => R.uniq(strs), "Ramda \"uniq\" (list of strings)") 23 | benchmark(() => Fx.uniq(strs), "FxJs \"uniq\" (list of strings)") 24 | }) 25 | -------------------------------------------------------------------------------- /src/convergeZip.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Run several Functions (or composed chains of Functions) onto the same input and converges their results as arguments to another Function. 3 | * Compare to "converge" however the difference is that this is _always_ intended to be used with multiple input values _and_ each of those values corresponds to one of the forked functions. 4 | * They all still converge into one Function, but the forked function just don't receive the same input values. 5 | * @param {function} fn - A Function to converge the results (from executing all the others) into 6 | * @param {Array} ...forkedFunctions - Two or more Functions (should be at least two, otherwise you're using the wrong util; use compose instead) that will later receive the same input 7 | * @returns {function} A wrapped Function that is ready to receive multiple values that each correspond to one of the fork functions, converging those results as _arguments_ to the first Function you supplied 8 | */ 9 | export default function convergeZip( 10 | fn: (...params: any[]) => any, 11 | ...forkedFunctions: ((...params: any[]) => any)[] 12 | ): (...params: any[]) => any 13 | -------------------------------------------------------------------------------- /test/benchmarks/difference.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import * as raw from "radash" 4 | import R from "ramda" 5 | import * as Fx from "fxjs" 6 | import { difference } from "../../src" 7 | import { difference as curriedDifference } from "../../src/curried" 8 | import { composers, hook } from "../__mocks__" 9 | 10 | const movie = Object.keys(hook) 11 | const music = Object.keys(composers) 12 | 13 | test("difference", (benchmark) => { 14 | benchmark(() => difference(movie, music), "Vanillas \"difference\"") 15 | benchmark(() => _.difference(movie, music), "Lodash \"difference\"") 16 | benchmark(() => raw.diff(movie, music), "Radash \"diff\"") 17 | benchmark(() => Fx.difference(music, movie), "FxJs \"difference\"") 18 | benchmark(() => R.difference(movie, music), "Ramda \"difference\"") 19 | }) 20 | 21 | test("curriedDifference", (benchmark) => { 22 | benchmark(() => curriedDifference(movie)(music), "Vanillas \"difference\" (curried)") 23 | benchmark(() => Fx.difference(music)(movie), "FxJs \"difference\" (curried)") 24 | benchmark(() => R.difference(movie)(music), "Ramda \"difference\" (curried)") 25 | }) 26 | -------------------------------------------------------------------------------- /test/getType.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import getType from "../src/getType" 3 | 4 | tape("\"getType\" can handle native and non-native type lookup", t => { 5 | t.equal(getType(null), "null") 6 | t.equal(getType(undefined), "undefined") 7 | t.equal(getType(false), "Boolean") 8 | t.equal(getType(Symbol("Ƭ̵̬̊")), "Symbol") 9 | t.equal(getType(0), "Number") 10 | t.equal(getType(" "), "String") 11 | t.equal(getType(new Int8Array()), "Int8Array") 12 | t.equal(getType(new Uint8Array()), "Uint8Array") 13 | t.equal(getType(new Uint8ClampedArray()), "Uint8ClampedArray") 14 | t.equal(getType(new Int16Array()), "Int16Array") 15 | t.equal(getType(new Uint16Array()), "Uint16Array") 16 | t.equal(getType(new Int32Array()), "Int32Array") 17 | t.equal(getType(new Float32Array()), "Float32Array") 18 | t.equal(getType(new Float64Array()), "Float64Array") 19 | t.equal(getType(new Set()), "Set") 20 | t.equal(getType(new WeakSet()), "WeakSet") 21 | t.equal(getType(Object.create(null)), "Object") 22 | t.equal(getType({}), "Object") 23 | t.equal(getType(new Map()), "Map") 24 | t.equal(getType(new WeakMap()), "WeakMap") 25 | t.end() 26 | }) 27 | -------------------------------------------------------------------------------- /src/promiseChain.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gathers an Array of Promises (or of Functions that return Promises) and executes them in sequential order they appear in the Array. 3 | * The value from the last will be supplied to the next (in case you need it). 4 | * 5 | * @function 6 | * @name promiseChain 7 | * @param {Array> | Array} requests An array of Promises (or of Functions that return Promises) which need to be executed in sequential order 8 | * @returns {Promise<*>} A Promise that will resolve when each of the requests completes 9 | */ 10 | function promiseChain(...requests) { 11 | const reqs = Array.isArray(requests[0]) ? requests[0] : requests 12 | const len = reqs.length 13 | const promisesPromises = new Array(len) 14 | const getNext = (req, lastVal) => (typeof req === "function" ? req(lastVal) : req) 15 | 16 | let chain = Promise.resolve() 17 | for (let i = 0; i < len; i++) { 18 | promisesPromises[i] = ( 19 | /* eslint-disable-next-line no-multi-assign */ 20 | chain = chain.then(val => getNext(reqs[i], val)) 21 | ) 22 | } 23 | 24 | return Promise.all(promisesPromises) 25 | } 26 | 27 | export default promiseChain 28 | -------------------------------------------------------------------------------- /src/propAt.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable consistent-return */ 2 | 3 | /** 4 | * Looks for a specified key on an Object you provide. 5 | * The is performed safely and will not throw an error if something on the prop path chain you specify doesn't exist. 6 | * Will always return `undefined` if a prop path cannot be resolved (rather than throwing). 7 | * 8 | * @function 9 | * @name propAt 10 | * @param {string | Array} prop A top-level key OR a deeply nested prop path (which may be represented as an array or as a single dot-delimited string) 11 | * @param {object} obj An object which may contain a specified prop 12 | * @returns {*|undefined} The value associated with the nested prop path OR undefined if it does not exist 13 | */ 14 | function propAt(prop, obj) { 15 | try { 16 | const paths = typeof prop === "string" ? prop.split(".") : prop 17 | const len = paths.length 18 | if (len === 0) return undefined 19 | let idx = 0 20 | let val = obj[paths[idx]] 21 | while (++idx < len) { 22 | val = val[paths[idx]] 23 | } 24 | return val 25 | } catch (err) { 26 | // Prop wasn't found; returning undefined 27 | } 28 | } 29 | 30 | export default propAt 31 | -------------------------------------------------------------------------------- /test/fuzzy.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import fuzzy from "../src/fuzzy" 3 | import { fuzzy as fuzzyCurried } from "../src/curried" 4 | import { companies } from "./__mocks__" 5 | 6 | tape("\"filter\" can apply a function to an Object, String, or something that is Array-like", t => { 7 | const needle = "dickinson" 8 | 9 | const expectedMatches = [ 10 | { name: "Dickinson, Orn and Gerlach", uri: "http://rosella.com" }, 11 | { name: "Dickinson Group", uri: "http://wilford.biz" }, 12 | { name: "Wehner - Dickinson", uri: "https://mariana.net" }, 13 | { name: "Dickinson Inc", uri: "https://bette.biz" } 14 | ] 15 | t.deepEqual( 16 | fuzzy(s => s.name, needle, true, companies), 17 | [], 18 | "no matches when case-sensitive" 19 | ) 20 | t.deepEqual( 21 | fuzzy(s => s.name, needle, false, companies), 22 | expectedMatches, 23 | "can be case insensitive" 24 | ) 25 | t.deepEqual( 26 | fuzzy(null, needle, false, companies.map(c => c.name)), 27 | expectedMatches.map(c => c.name), 28 | "can be a string list" 29 | ) 30 | t.deepEqual( 31 | fuzzyCurried(s => s.name)(needle, false)(companies), 32 | expectedMatches, 33 | "can also be curried" 34 | ) 35 | t.end() 36 | }) 37 | -------------------------------------------------------------------------------- /src/merge.js: -------------------------------------------------------------------------------- 1 | import forIn from "./forIn" 2 | 3 | /** 4 | * Merges the values from 2 or more Objects or Arrays together into a new Object/Array. 5 | * Null and Undefined values are handled gracefully, and if the second value is a primitive it will be returned as-is, instead of trying to merge it onto the first. 6 | * 7 | * @function 8 | * @name merge 9 | * @param {Array>} ...val Values to merge together (values with higher precedence should be provided last) 10 | * @returns {object|Array<*>} A new value that contains the combined values from all the values passed in 11 | */ 12 | function merge(...vals) { 13 | const numOfVals = vals.length 14 | if (vals[0] === undefined || vals[1] === undefined) { 15 | return vals[1] !== undefined ? vals[1] : vals[0] 16 | } 17 | if ( 18 | numOfVals === 2 19 | && (vals[1] == null || typeof vals[1] !== "object" || vals[1].constructor.name !== "Object") 20 | ) { 21 | return vals[1] 22 | } 23 | const newObj = { } 24 | forIn((key, val) => { newObj[key] = val }, vals[0]) 25 | for (let i = 1; i < numOfVals; i++) { 26 | forIn((key, val) => { 27 | newObj[key] = merge(newObj[key], val) 28 | }, vals[i]) 29 | } 30 | return newObj 31 | } 32 | 33 | export default merge 34 | -------------------------------------------------------------------------------- /test/benchmarks/map.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import R from "ramda" 4 | import { map as FxMap } from "fxjs" 5 | import { map } from "../../src" 6 | import { map as mapCurried } from "../../src/curried" 7 | import { hook } from "../__mocks__" 8 | 9 | const arr = Object.values(hook) 10 | 11 | test("map", benchmark => { 12 | benchmark( 13 | () => map(str => str.toUpperCase(), arr), 14 | "Vanillas \"map\"" 15 | ) 16 | benchmark( 17 | () => _.map(arr, str => str.toUpperCase()), 18 | "Lodash \"map\"" 19 | ) 20 | benchmark( 21 | () => FxMap(str => str.toUpperCase(), arr), 22 | "FxJs \"map\"" 23 | ) 24 | benchmark( 25 | () => R.map(str => str.toUpperCase(), arr), 26 | "Ramda \"map\"" 27 | ) 28 | benchmark( 29 | () => arr.map(str => str.toUpperCase()), 30 | "(native) \"Array.map()\"" 31 | ) 32 | }) 33 | 34 | test("curriedMap", benchmark => { 35 | benchmark( 36 | () => mapCurried(str => str.toUpperCase())(arr), 37 | "Vanillas (curried) \"map\"" 38 | ) 39 | benchmark( 40 | () => FxMap(str => str.toUpperCase())(arr), 41 | "FxJs (curried) \"map\"" 42 | ) 43 | benchmark( 44 | () => R.map(str => str.toUpperCase())(arr), 45 | "Ramda (curried) \"map\"" 46 | ) 47 | }) 48 | -------------------------------------------------------------------------------- /test/benchmarks/uniqBy.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import R from "ramda" 4 | import * as Fx from "fxjs" 5 | import { uniqBy } from "../../src" 6 | import { profiles } from "../__mocks__" 7 | 8 | test("uniqBy", benchmark => { 9 | benchmark( 10 | () => uniqBy("age", profiles), 11 | "Vanillas \"uniqBy\" (top-level prop)" 12 | ) 13 | benchmark( 14 | () => _.uniqBy(profiles, "age"), 15 | "Lodash \"uniqBy\" (top-level prop)" 16 | ) 17 | benchmark( 18 | () => R.uniqBy(R.prop("age"), profiles), 19 | "Ramda \"uniqBy\" (top-level prop)" 20 | ) 21 | benchmark( 22 | () => Fx.uniqBy(obj => obj.age, profiles), 23 | "FxJs \"uniqBy\" (top-level prop)" 24 | ) 25 | }) 26 | 27 | test("deeplyUniqBy", benchmark => { 28 | benchmark( 29 | () => uniqBy("favorites.color", profiles), 30 | "Vanillas \"uniqBy\" (nested prop)" 31 | ) 32 | benchmark( 33 | () => _.uniqBy(profiles, "favorites.color"), 34 | "Lodash \"uniqBy\" (nested prop)" 35 | ) 36 | benchmark( 37 | () => R.uniqBy(R.path(["favorites", "color"]), profiles), 38 | "Ramda \"uniqBy\" (nested prop)" 39 | ) 40 | benchmark( 41 | () => Fx.uniqBy(obj => obj.favorites.color, profiles), 42 | "FxJs \"uniqBy\" (nested prop)" 43 | ) 44 | }) 45 | -------------------------------------------------------------------------------- /test/cond.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import cond from "../src/cond" 3 | import { hook, sphere, counterCultureIcons, composers } from "./__mocks__" 4 | 5 | /* eslint-disable-next-line max-len */ 6 | tape("\"cond\" checks a given value against a set of conditions and applies a corresponding transformation when the first matching condition is met", t => { 7 | const conditionalTransforms = [ 8 | [v => v.Hoffman === "dustin" && v.Williams === "robin", "Hook was released in 1991"], 9 | [v => v.Hoffman === "albert", "Strange discoveries can happen when researching obstetrics"], 10 | [v => /^ralph/.test(v.Williams), comp => 11 | Object.keys(comp).map(key => `${comp[key]} ${key.toLowerCase()}`).join(", ") 12 | ] 13 | ] 14 | t.equal( 15 | cond(conditionalTransforms, hook), 16 | "Hook was released in 1991" 17 | ) 18 | t.equal( 19 | cond(conditionalTransforms, counterCultureIcons), 20 | "Strange discoveries can happen when researching obstetrics" 21 | ) 22 | t.equal( 23 | cond(conditionalTransforms, composers), 24 | "ralph vaughan williams, camille saintsaens, gustav mahler, richard wagner" 25 | ) 26 | t.equal(cond(conditionalTransforms, sphere), undefined, "all conditions are unmet (fall-through) returns undefined") 27 | t.end() 28 | }) 29 | -------------------------------------------------------------------------------- /src/isPrime.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if a given numeric value is a prime number. 3 | * This is any integer value divisible only by itself and 1. 4 | * 5 | * @function 6 | * @name isPrime 7 | * @param {number} val A value to verify is a prime number 8 | * @returns {boolean} Whether or not the given value is a prime number 9 | */ 10 | function isPrime(val) { 11 | if (!Number.isInteger(val) || val <= 1 || (val !== 2 && val % 2 === 0)) { 12 | return false 13 | } 14 | 15 | /* In checking if a given number is prime, 16 | * it isn't necessary to even check if it is 17 | * divisible by the majority of numbers between itself and 1. 18 | * Nothing greater than a third of the value need be checked, 19 | * but by getting a third/fourth/fifth/sixth/etc of the value 20 | * and using that as the cutoff limit to check for divisibility, 21 | * we can save many iterations. 22 | */ 23 | let divideValBy = 3 24 | 25 | while (divideValBy < val && divideValBy <= (val / divideValBy)) { 26 | if ( 27 | val % divideValBy === 0 || ( 28 | Number.isInteger(val / divideValBy) 29 | && val % (val / divideValBy) === 0 30 | ) 31 | ) { 32 | return false 33 | } 34 | 35 | divideValBy++ 36 | } 37 | 38 | return true 39 | } 40 | 41 | export default isPrime 42 | -------------------------------------------------------------------------------- /src/uniqBy.js: -------------------------------------------------------------------------------- 1 | import propAt from "./propAt" 2 | import { _includes } from "./_internal/_includes" 3 | 4 | /** 5 | * Filters an array of values down to only those which are unique, based on a provided predicate function (or shorthand for retrieving a prop inside an object) 6 | * 7 | * @function 8 | * @name uniqBy 9 | * @param {function | string} pred A predicate function 10 | * @param {Array<*>} list An array of values which may or may not contain duplicates 11 | * @returns {Array<*>} A new list containing only the unique values from the original array 12 | */ 13 | function uniqBy(pred, list) { 14 | let fn 15 | if (typeof pred === "string" && pred.split(".").length === 1) { 16 | fn = o => o[pred] 17 | } else if (Array.isArray(pred) && pred.length === 1) { 18 | fn = o => o[pred[0]] 19 | } else if (typeof pred === "function") { 20 | fn = pred 21 | } else { 22 | fn = o => propAt(pred, o) 23 | } 24 | 25 | let idx = -1 26 | const newArr = [] 27 | const compared = [] 28 | const len = list.length 29 | 30 | while (++idx < len) { 31 | const val = list[idx] 32 | const valley = fn(val) 33 | if (!_includes(valley, compared)) { 34 | newArr.push(val) 35 | compared.push(valley) 36 | } 37 | } 38 | return newArr 39 | } 40 | 41 | export default uniqBy 42 | -------------------------------------------------------------------------------- /src/fuzzy.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Performs a fuzzy search on a list of strings or objects. 3 | * If a list of objects, provided the prop extraction function so the search can find the correct field(s) 4 | * This is heavily inspired by (most of) the algorithm used by [Matt York's](https://github.com/myork/fuzzy) fuzzy search function, 5 | * however several features were not carried over and his implementation of that alrgorithm has been significantly changed to achieve a 25% speed improvement. 6 | * Please see his original work - called [fuzzy](https://www.npmjs.com/package/fuzzy) MIT - if you need some of his additional options. 7 | * @param {function} propFn - A function which will extract all the fields which you wish to fuzzy search on. Omit if the list is a list of strings 8 | * @param {string} needle - The search value itself 9 | * @param {boolean} [caseSensitive = false] - Whether or not to perform a case-sensitive search 10 | * @param {Array} arr - An array of string values or objects which have string values to be searched on 11 | * @returns {Array} The filtered list of search results 12 | */ 13 | export default function fuzzy( 14 | propFn: (...params: any[]) => any, 15 | needle: string, 16 | caseSensitive: boolean, 17 | arr: T 18 | ): T 19 | -------------------------------------------------------------------------------- /src/is.js: -------------------------------------------------------------------------------- 1 | import getConstructorName from "./_internal/getConstructorName" 2 | import getType from "./getType" 3 | 4 | /** 5 | * Checks to see if a value is a certain type. 6 | * You may specify that type as a case-insensitive string 7 | * (ie, 'string', 'boolean', 'object', 'function', 'array', 'regexp', 'date', 'set, 'map'), OR a JavaScript type constructor function (ie, String, Function, Boolean, Array, RegExp, Date, Set, Map). 8 | * 9 | * @function 10 | * @name is 11 | * @param {function | string} ofType A JavaScript type constructor function (like `Function`, `String`, `RegExp`, `Boolean`, `Array`, `Object`, etc.) or a string value matching the name of one 12 | * @param {*} val A value (of any type) 13 | * @returns {boolean} Whether or not the value matches the specified type 14 | * @example 15 | * is('boolean', true) 16 | * is('array', [1, 2, 3]) 17 | * is(RegExp, /[a-z0-9]/) 18 | * is(Function, () => null) 19 | * 20 | */ 21 | function is(ofType, val) { 22 | return ofType === val || 23 | (typeof ofType === "string" && getType(val).toLowerCase() === ofType.toLowerCase()) || 24 | (ofType && !ofType.name && getType(ofType) === getType(val)) || 25 | (ofType && ofType.name && getConstructorName(ofType) === "Function" && getType(val) === ofType.name) 26 | } 27 | 28 | export default is 29 | -------------------------------------------------------------------------------- /src/append.js: -------------------------------------------------------------------------------- 1 | import merge from "./merge" 2 | 3 | /** 4 | * Blends two values together based on their type. 5 | * If both are objects this would result in a new object with the second value's props merged onto the first's. 6 | * If the first value is an array, this would result in a new array with the second value concatenated onto the first value. 7 | * If both values are strings or numbers, a new string will be returned with the second value added onto the first. 8 | * 9 | * @function 10 | * @name append 11 | * @param {string | number | Object | Array<*>} firstVal A value that will have another appended onto 12 | * @param {string | number | Object | Array<*>} secondVal A value to append to the first value 13 | * @returns {Array<*> | object | string} A new Array, Object, or String that has the characters/values from the second provided value merged _after_ those from the first provided value 14 | */ 15 | function append(firstVal, secondVal) { 16 | if ([firstVal, secondVal].every(val => typeof val === "string" || typeof val === "number")) { 17 | return `${firstVal}${secondVal}` 18 | } 19 | if (Array.isArray(firstVal) && Array.isArray(secondVal)) { 20 | return [...firstVal, ...secondVal] 21 | } 22 | return merge(firstVal, secondVal) 23 | } 24 | 25 | export default append 26 | -------------------------------------------------------------------------------- /test/benchmarks/filter.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import R from "ramda" 4 | import * as Fx from "fxjs" 5 | import { filter } from "../../src" 6 | import { filter as filterCurried } from "../../src/curried" 7 | import { hook } from "../__mocks__" 8 | 9 | const pred = str => /n$/.test(str) 10 | const arr = Object.values(hook) 11 | 12 | test("filter", benchmark => { 13 | benchmark( 14 | () => filter(str => pred(str), arr), 15 | "Vanillas \"filter\"" 16 | ) 17 | benchmark( 18 | () => _.filter(arr, str => pred(str)), 19 | "Lodash \"filter\"" 20 | ) 21 | benchmark( 22 | () => R.filter(str => pred(str), arr), 23 | "Ramda \"filter\"" 24 | ) 25 | benchmark( 26 | () => Fx.filter(str => pred(str), arr), 27 | "FxJs \"filter\"" 28 | ) 29 | benchmark( 30 | () => arr.filter(str => pred(str)), 31 | "(native) \"Array.filter()\"" 32 | ) 33 | }) 34 | 35 | test("curriedFilterTests", benchmark => { 36 | benchmark( 37 | () => filterCurried(str => pred(str))(arr), 38 | "Vanillas (curried) \"filter\"" 39 | ) 40 | benchmark( 41 | () => R.filter(str => pred(str))(arr), 42 | "Ramda (curried) \"filter\"" 43 | ) 44 | benchmark( 45 | () => Fx.filter(str => pred(str))(arr), 46 | "FxJs (curried) \"filter\"" 47 | ) 48 | }) 49 | -------------------------------------------------------------------------------- /test/benchmarks/merge.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-object-spread */ 2 | import { test } from "reality-check" 3 | import _ from "lodash" 4 | import R from "ramda" 5 | import * as Fx from "fxjs" 6 | import { merge } from "../../src" 7 | import { merge as curriedMerge } from "../../src/curried" 8 | import { hook, sphere } from "../__mocks__" 9 | 10 | test("merge", benchmark => { 11 | benchmark( 12 | () => merge(hook, sphere), 13 | "Vanillas \"merge\"" 14 | ) 15 | // Three arguments supplied here is because Lodash cheats: they mutate the first object 16 | benchmark( 17 | () => _.merge({}, hook, sphere), 18 | "Lodash \"merge\"" 19 | ) 20 | benchmark( 21 | () => R.mergeDeepLeft(hook, sphere), 22 | "Ramda \"merge\"" 23 | ) 24 | benchmark( 25 | () => Fx.merge(hook, sphere), 26 | "FxJs \"merge\"" 27 | ) 28 | benchmark( 29 | () => Object.assign({}, hook, sphere), 30 | "(native) \"Object.assign({}, ..)\"" 31 | ) 32 | }) 33 | 34 | test("curriedMerge", benchmark => { 35 | benchmark( 36 | () => curriedMerge(hook)(sphere), 37 | "Vanillas (curried) \"merge\"" 38 | ) 39 | benchmark( 40 | () => R.merge(hook)(sphere), 41 | "Ramda (curried) \"merge\"" 42 | ) 43 | benchmark( 44 | () => Fx.merge(hook)(sphere), 45 | "FxJs (curried) \"merge\"" 46 | ) 47 | }) 48 | -------------------------------------------------------------------------------- /src/converge.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Run several Functions (or composed chains of Functions) onto the same input and converges their results as arguments to another Function. 3 | * 4 | * A common example might be to take an Object that needs to be transformed in several different ways and then merged into one final Object. 5 | * In that case you can supply a "merge" (or "assign") Function as the convergence Function, and then pass all your Object transforming Functions as additional arguments. 6 | * When you're ready to receive the actual input Object it will be forked into all the transform Functions and their results will converge into the merge/assign Function you supplied first. 7 | * @param {function} fn - A Function to converge the results (from executing all the others) into 8 | * @param {Array} ...forkedFunctions - Two or more Functions (should be at least two, otherwise you're using the wrong util; use compose instead) that will later receive the same input 9 | * @returns {function} A wrapped Function that is ready to receive a value(s) and pass it (in parallel) into the other Functions, converging those results as _arguments_ to the first Function you supplied 10 | */ 11 | export default function converge( 12 | fn: (...params: any[]) => any, 13 | ...forkedFunctions: ((...params: any[]) => any)[] 14 | ): (...params: any[]) => any 15 | -------------------------------------------------------------------------------- /test/toUriEncoded.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import toUriEncoded from "../src/toUriEncoded" 3 | 4 | tape("\"toUriEncoded\" will convert the a given value to an integer", t => { 5 | t.equal(toUriEncoded(), "", "empty args means empty string returned") 6 | t.equal(toUriEncoded({ lorem: "ipsum" }), "lorem=ipsum") 7 | t.equal( 8 | toUriEncoded({ lorem: "ipsum dolor sit amet" }), 9 | "lorem=ipsum%20dolor%20sit%20amet", 10 | "should be URI encoded" 11 | ) 12 | t.equal( 13 | toUriEncoded({ lorem: "ipsum dolor sit amet", consectetur: "adipiscing elit" }), 14 | "lorem=ipsum%20dolor%20sit%20amet&consectetur=adipiscing%20elit", 15 | "multiple key/val pairs serialized into a single string" 16 | ) 17 | t.equal( 18 | toUriEncoded({ latin: ["lorem", (new Date()), (new RegExp()), "ipsum", "dolor", "sit", "amet"] }), 19 | "latin=lorem,ipsum,dolor,sit,amet", 20 | "handles arrays" 21 | ) 22 | const objWithProtoStuff = { 23 | one: 101, 24 | two: 202, 25 | three: 303, 26 | sub: { 27 | red: "october", 28 | u: "boat", 29 | yellow: "submarine" 30 | }, 31 | presentParticiple: l => `${l}ing` 32 | } 33 | t.equal( 34 | toUriEncoded(objWithProtoStuff), 35 | "one=101&two=202&three=303", 36 | "no functions or sub-objects serialized" 37 | ) 38 | 39 | t.end() 40 | }) 41 | -------------------------------------------------------------------------------- /src/cond.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Several pieces of conditional logic to apply against a value and the _first_ one which matches will have a corresponding transformation applied to it. 3 | * 4 | * Supply an Array of pairs: 5 | * - the first value is the conditional logic (`Function`) to match against the value 6 | * - the second value is the transformation (`Function`) to apply to the value if the condition was matched 7 | * 8 | * __Note__: if your tranformation is _not_ a function then it will returned as-is in response to a succesfully met condition 9 | * 10 | * @function 11 | * @name cond 12 | * @param {Array>} conditionalTransforms An array of arrays (which have two values: the condition function and the transformation function) 13 | * @param {*} val A value of any type that will be transformed according to the appropriate condition. 14 | * @returns {*} The provided value transformed by the appropriate matching conditional transformation 15 | */ 16 | function cond(conditionalTransforms, val) { 17 | let result 18 | conditionalTransforms.some(([ifCondition, transform = val]) => { 19 | if (ifCondition(val)) { 20 | result = typeof transform === "function" 21 | ? transform(val) 22 | : transform 23 | return true 24 | } 25 | return false 26 | }) 27 | return result 28 | } 29 | 30 | export default cond 31 | -------------------------------------------------------------------------------- /test/benchmarks/pick.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import R from "ramda" 4 | import * as raw from "radash" 5 | import * as Fx from "fxjs" 6 | import { pick } from "../../src" 7 | import { pick as curriedPick } from "../../src/curried" 8 | import { solarSystem } from "../__mocks__" 9 | 10 | test("pick", (benchmark) => { 11 | benchmark(() => pick(["Mercury", "Venus", "Mars"], solarSystem), "Vanillas \"pick\"") 12 | benchmark(() => _.pick(solarSystem, ["Mercury", "Venus", "Mars"]), "Lodash \"pick\"") 13 | benchmark(() => raw.pick(solarSystem, ["Mercury", "Venus", "Mars"]), "Radash \"pick\"") 14 | benchmark(() => R.pick(["Mercury", "Venus", "Mars"], solarSystem), "Ramda \"pick\"") 15 | benchmark(() => Fx.pick(["Mercury", "Venus", "Mars"], solarSystem), "FxJs \"pick\"") 16 | benchmark( 17 | () => ["Mercury", "Venus", "Mars"].reduce((newObj, key) => ({ ...newObj, [key]: solarSystem[key] }), {}), 18 | "(native) \"Array.prototype.reduce\"" 19 | ) 20 | }) 21 | 22 | test("curriedPick", (benchmark) => { 23 | benchmark(() => curriedPick(["Mercury", "Venus", "Mars"])(solarSystem), "Vanillas (curried) \"pick\"") 24 | benchmark(() => R.pick(["Mercury", "Venus", "Mars"])(solarSystem), "Ramda (curried) \"pick\"") 25 | benchmark(() => Fx.pick(["Mercury", "Venus", "Mars"])(solarSystem), "FxJs (curried) \"pick\"") 26 | }) 27 | -------------------------------------------------------------------------------- /src/filter.js: -------------------------------------------------------------------------------- 1 | import isArrayish from "./isArrayish" 2 | import isObject from "./isObject" 3 | import filterString from "./filterString" 4 | import filterObject from "./filterObject" 5 | 6 | /** 7 | * Applies a filtering function you provide over a value you provide, according to its type. 8 | * String values will have the filtering function applied over every character in the String. 9 | * Objects will have the filtering function applied to every value in the Object. 10 | * Arrays (or Array-like values) will have the filtering function applied to every value in the Array. 11 | * If the type of your value is none of the above, the value will be returned as-is. 12 | * 13 | * @function 14 | * @name filter 15 | * @param {function} fn A filtering function that is invoked on the provided value 16 | * @param {object | Array<*> | string} val An Object/Array/String whose values/chars will be filtered 17 | * @returns {object | Array<*> | string} A new value that is the result of the filtering operation over all the chars or values in the original String/Object/Array 18 | */ 19 | function filter(fn, val) { 20 | if (Array.isArray(val)) return val.filter(fn) 21 | if (isObject(val)) return filterObject(fn, val) 22 | if (typeof val === "string") return filterString(fn, val) 23 | if (isArrayish(val)) return val.filter(fn) 24 | return val 25 | } 26 | 27 | export default filter 28 | -------------------------------------------------------------------------------- /test/benchmarks/omit.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import * as raw from "radash" 4 | import R from "ramda" 5 | import * as Fx from "fxjs" 6 | import { omit } from "../../src" 7 | import { omit as curriedOmit } from "../../src/curried" 8 | import { pantheon } from "../__mocks__" 9 | 10 | const exclude = ["Jupiter", "Saturn", "Neptune"] 11 | 12 | test("omit", (benchmark) => { 13 | benchmark(() => omit(exclude, pantheon), "Vanillas \"omit\"") 14 | benchmark(() => _.omit(pantheon, exclude), "Lodash \"omit\"") 15 | benchmark(() => raw.omit(pantheon, exclude), "Radash \"omit\"") 16 | benchmark(() => R.omit(exclude, pantheon), "Ramda \"omit\"") 17 | benchmark(() => Fx.omit(exclude, pantheon), "FxJs \"omit\"") 18 | benchmark( 19 | () => 20 | Object.keys(pantheon) 21 | .filter((key) => !exclude.includes(key)) 22 | .reduce((newObj, key) => ({ ...newObj, [key]: pantheon[key] }), {}), 23 | "(native) \"Object.keys().filter().reduce()\"" 24 | ) 25 | }) 26 | 27 | test("curriedOmit", (benchmark) => { 28 | benchmark(() => curriedOmit(["Jupiter", "Saturn", "Neptune"])(pantheon), "Vanillas (curried) \"omit\"") 29 | benchmark(() => R.omit(["Jupiter", "Saturn", "Neptune"])(pantheon), "Ramda (curried) \"omit\"") 30 | benchmark(() => Fx.omit(["Jupiter", "Saturn", "Neptune"])(pantheon), "FxJs (curried) \"omit\"") 31 | }) 32 | -------------------------------------------------------------------------------- /test/composeAndPipe.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import compose from "../src/compose" 3 | import pipe from "../src/pipe" 4 | 5 | const uppercase = str => str.toUpperCase() 6 | const addSpaces = str => str.split("").map(s => ` ${s}`).join("") 7 | const add = (a, b) => a + b 8 | const divideByTwo = a => a / 2 9 | 10 | tape("\"compose\" creates a chain of functions to execute on some input", t => { 11 | t.equal(compose(addSpaces, uppercase)("lorem"), " L O R E M") 12 | t.equal( 13 | compose(divideByTwo, add)(10, 30), 14 | 20, 15 | "first function in the chain can be of any arity" 16 | ) 17 | t.equal( 18 | compose()(7), 19 | 7, 20 | "if no functions are provided to compose, if behaves as an identity function" 21 | ) 22 | t.throws(() => compose(null)(7), "any arg passed to compose has to be a function") 23 | t.end() 24 | }) 25 | 26 | tape("\"pipe\" creates a chain of functions to execute on some input (left to right)", t => { 27 | t.equal(pipe(uppercase, addSpaces)("lorem"), " L O R E M") 28 | t.equal( 29 | pipe(add, divideByTwo)(10, 30), 30 | 20, 31 | "first function in the chain can be of any arity" 32 | ) 33 | t.equal( 34 | pipe()(7), 35 | 7, 36 | "if no functions are provided to pipe, if behaves as an identity function" 37 | ) 38 | t.throws(() => pipe(null)(7), "any arg passed to pipe has to be a function") 39 | t.end() 40 | }) 41 | -------------------------------------------------------------------------------- /test/entries.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import entries from "../src/entries" 3 | 4 | tape("\"entries\" extracts key/value pairs from an object and returns them as an Array of key/value pairs", t => { 5 | const hook = { 6 | Hoffman: "dustin", 7 | Williams: "robin", 8 | Roberts: "julia", 9 | Hoskins: "bob" 10 | } 11 | t.deepEqual( 12 | entries(hook), [ 13 | ["Hoffman", "dustin"], 14 | ["Williams", "robin"], 15 | ["Roberts", "julia"], 16 | ["Hoskins", "bob"] 17 | ] 18 | ) 19 | const movies = { 20 | hook: { 21 | Hoffman: "dustin", 22 | Williams: "robin", 23 | Roberts: "julia", 24 | Hoskins: "bob" 25 | }, 26 | sphere: { 27 | Hoffman: "dustin", 28 | Jackson: "samuel", 29 | Stone: "sharon", 30 | Latifah: "queen" 31 | } 32 | } 33 | const pairs = entries(movies) 34 | pairs[1].Smith = "maggie" 35 | 36 | t.deepEqual( 37 | movies, { 38 | hook: { 39 | Hoffman: "dustin", 40 | Williams: "robin", 41 | Roberts: "julia", 42 | Hoskins: "bob" 43 | }, 44 | sphere: { 45 | Hoffman: "dustin", 46 | Jackson: "samuel", 47 | Stone: "sharon", 48 | Latifah: "queen" 49 | } 50 | }, 51 | "mutating members of the new key/val array does not mutate the sub-objects on the original object" 52 | ) 53 | t.end() 54 | }) 55 | -------------------------------------------------------------------------------- /test/combine.test.js: -------------------------------------------------------------------------------- 1 | import test from "tape" 2 | import combine from "../src/combine" 3 | 4 | test("\"combine\" will join, summarize, or merge two values together", (t) => { 5 | t.equal(combine(1, 2), 3, "Numeric combination") 6 | t.equal(combine("foo", "bar"), "foobar", "String combination") 7 | t.equal(combine(2, "two"), "2two", "Number and a String") 8 | t.equal(combine("two", 2), "two2", "String and a Number") 9 | t.deepEqual( 10 | combine([1, 2, 3], [4, 5, 6]), 11 | [1, 2, 3, 4, 5, 6], 12 | "Array combination" 13 | ) 14 | t.deepEqual( 15 | combine({lorem: "ipsum"}, {dolor: "sit"}), 16 | {lorem: "ipsum", dolor: "sit"}, 17 | "Object combination" 18 | ) 19 | t.end() 20 | }) 21 | 22 | test("\"combine\" will return the first value if it makes no sense to combine them", (t) => { 23 | t.equal(combine(true, false), true, "Boolean combination?") 24 | t.equal(combine(false, true), false, "Boolean combination?") 25 | t.equal(combine(false, {}), false, "Boolean and Object?") 26 | t.deepEqual(combine({}, false), {}, "Object and Boolean?") 27 | t.deepEqual(combine([1, 2, 3], {}), [1, 2, 3], "Array and Object?") 28 | t.deepEqual(combine({a: "b"}, [1, 2, 3]), {a: "b"}, "Object and Array?") 29 | t.equal(combine(null, null), null, "Null") 30 | t.equal(combine(undefined, null), undefined, "undefined") 31 | t.equal(combine(null, undefined), null, "undefined") 32 | t.end() 33 | }) 34 | -------------------------------------------------------------------------------- /test/benchmarks/isEqual.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import R from "ramda" 4 | import { isEqual } from "../../src" 5 | import { isEqual as curriedIsEqual } from "../../src/curried" 6 | import { pantheon, solarSystem } from "../__mocks__" 7 | 8 | test("areObjectsEqualTests", benchmark => { 9 | benchmark( 10 | () => isEqual(solarSystem, pantheon), 11 | "Vanillas \"isEqual\" (object equality)" 12 | ) 13 | benchmark( 14 | () => _.isEqual(solarSystem, pantheon), 15 | "Lodash \"isEqual\" (object equality)" 16 | ) 17 | benchmark( 18 | () => R.equals(solarSystem, pantheon), 19 | "Ramda \"equals\" (object equality)" 20 | ) 21 | }) 22 | 23 | test("areArraysEqualTests", benchmark => { 24 | benchmark( 25 | () => isEqual(solarSystem.Jupiter.moons, pantheon.Jupiter.moons), 26 | "Vanillas \"isEqual\" (array equality)" 27 | ) 28 | benchmark( 29 | () => _.isEqual(solarSystem.Jupiter.moons, pantheon.Jupiter.moons), 30 | "Lodash \"isEqual\" (array equality)" 31 | ) 32 | benchmark( 33 | () => R.equals(solarSystem.Jupiter.moons, pantheon.Jupiter.moons), 34 | "Ramda \"equals\" (array equality)" 35 | ) 36 | }) 37 | 38 | test("curriedIsEqualTests", benchmark => { 39 | benchmark( 40 | () => curriedIsEqual(solarSystem)(pantheon), 41 | "Vanillas (curried) \"isEqual\"" 42 | ) 43 | benchmark( 44 | () => R.equals(solarSystem)(pantheon), 45 | "Ramda (curried) \"equals\"" 46 | ) 47 | }) 48 | -------------------------------------------------------------------------------- /src/convergeZip.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Run several Functions (or composed chains of Functions) onto the same input and converges their results as arguments to another Function. 3 | * Compare to "converge" however the difference is that this is _always_ intended to be used with multiple input values _and_ each of those values corresponds to one of the forked functions. 4 | * They all still converge into one Function, but the forked function just don't receive the same input values. 5 | * 6 | * @function 7 | * @name convergeZip 8 | * @param {function} fn A Function to converge the results (from executing all the others) into 9 | * @param {function} ...forkedFunctions Two or more Functions (should be at least two, otherwise you're using the wrong util; use compose instead) that will later receive the same input 10 | * @returns {function} A wrapped Function that is ready to receive multiple values that each correspond to one of the fork functions, converging those results as _arguments_ to the first Function you supplied 11 | */ 12 | function convergeZip(fn, ...forkedFunctions) { 13 | const isArrayUsage = Array.isArray(forkedFunctions[0]) 14 | /* eslint-disable-next-line jsdoc/require-jsdoc */ 15 | function inner(...args) { 16 | const ars = Array.isArray(args[0]) && isArrayUsage ? args[0] : args 17 | const fns = (isArrayUsage ? forkedFunctions[0] : forkedFunctions).slice(0, ars.length) 18 | return fn(...ars.map((a, i) => fns[i](a))) 19 | } 20 | return inner 21 | } 22 | 23 | export default convergeZip 24 | -------------------------------------------------------------------------------- /test/concat.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import concat from "../src/concat" 3 | 4 | tape("\"concat\" can blend two Array-like objects together", t => { 5 | const hook = [ 6 | "dustin", 7 | "robin", 8 | "julia", 9 | "bob" 10 | ] 11 | const sphere = [ 12 | "dustin", 13 | "samuel", 14 | "sharon", 15 | "queen" 16 | ] 17 | const jurassicPark = [ 18 | "wayne", 19 | "sam", 20 | "samuel", 21 | "laura" 22 | ] 23 | const timeline = [ 24 | "paul", 25 | "gerard", 26 | "frances", 27 | "david" 28 | ] 29 | t.deepEqual( 30 | concat(hook, sphere), [ 31 | "dustin", 32 | "robin", 33 | "julia", 34 | "bob", 35 | "dustin", 36 | "samuel", 37 | "sharon", 38 | "queen" 39 | ], 40 | "concatenates two array-like objects" 41 | ) 42 | t.deepEqual( 43 | hook, [ 44 | "dustin", 45 | "robin", 46 | "julia", 47 | "bob" 48 | ], 49 | "concat does not mutate the first array" 50 | ) 51 | t.deepEqual( 52 | concat(hook, sphere, jurassicPark, timeline), [ 53 | "dustin", 54 | "robin", 55 | "julia", 56 | "bob", 57 | "dustin", 58 | "samuel", 59 | "sharon", 60 | "queen", 61 | "wayne", 62 | "sam", 63 | "samuel", 64 | "laura", 65 | "paul", 66 | "gerard", 67 | "frances", 68 | "david" 69 | ], 70 | "concatenates three or more array-like objects" 71 | ) 72 | t.end() 73 | }) 74 | -------------------------------------------------------------------------------- /test/benchmarks/contains.js: -------------------------------------------------------------------------------- 1 | import { test } from "reality-check" 2 | import _ from "lodash" 3 | import R from "ramda" 4 | import * as Fx from "fxjs" 5 | import { contains } from "../../src" 6 | import { magic } from "../__mocks__" 7 | 8 | const { sons } = magic.Weasley["Arthur and Molly"] 9 | 10 | test("contains", benchmark => { 11 | benchmark( 12 | () => contains("Ronald", sons), 13 | "Vanillas \"contains\" (array)" 14 | ) 15 | benchmark( 16 | () => _.includes(sons, "Ronald"), 17 | "Lodash \"includes\" (array)" 18 | ) 19 | benchmark( 20 | () => R.contains("Ronald", sons), 21 | "Ramda \"contains\" (array)" 22 | ) 23 | benchmark( 24 | () => Fx.includes("Ronald", sons), 25 | "FxJs \"includes\" (array)" 26 | ) 27 | benchmark( 28 | () => sons.includes("Ronald"), 29 | "(native) \"Array.includes()\"" 30 | ) 31 | }) 32 | 33 | test("containsStringTests", benchmark => { 34 | benchmark( 35 | () => contains("Ronald", "Ronald McDonald"), 36 | "Vanillas \"contains\" (string)" 37 | ) 38 | benchmark( 39 | () => _.includes("Ronald McDonald", "Ronald"), 40 | "Lodash \"includes\" (string)" 41 | ) 42 | benchmark( 43 | () => R.contains("Ronald", "Ronald McDonald"), 44 | "Ramda \"contains\" (string)" 45 | ) 46 | benchmark( 47 | () => R.contains("Ronald", "Ronald McDonald"), 48 | "FxJs \"includes\" (string)" 49 | ) 50 | benchmark( 51 | () => "Ronald McDonald".includes("Ronald"), 52 | "(native) \"String.includes()\"" 53 | ) 54 | }) 55 | -------------------------------------------------------------------------------- /test/memoize.test.js: -------------------------------------------------------------------------------- 1 | import tape from "tape" 2 | import memoize from "../src/memoize" 3 | 4 | tape("\"memoize\" can apply a function to an Object, String, or something that is Array-like", t => { 5 | let numOfCalls = 0 6 | 7 | /* function to memoize (with a side-effect that helps us to unit test it) */ 8 | /* eslint-disable-next-line jsdoc/require-jsdoc */ 9 | function addWithLogging(a, b) { 10 | numOfCalls++ 11 | return a + b 12 | } 13 | 14 | /* Now an inner cache has been created and each time "memoizedAdd()" is 15 | * invoked, the cache will be used when the function is invoked with the same 16 | * args that was called with previously, otherwise will re-invoke 17 | * "addWithLogging" and cache and then return the result */ 18 | const memoizedAdd = memoize(addWithLogging) 19 | 20 | t.equal(memoizedAdd(2, 3), 5, "the memoized function is being called when the cache is empty") 21 | t.equal(numOfCalls, 1, "confirm that \"addWithLogging\" was called") 22 | t.equal(memoizedAdd(2, 3), 5, "the cached value is being returned") 23 | t.equal(numOfCalls, 1, "config that \"addWithLogging\" was NOT called again") 24 | t.equal(memoizedAdd(2, 2), 4, "the non-cached new value is being returned") 25 | t.equal(numOfCalls, 2, "confirm the non-memoized args trigger \"addWithLogging\" to be called again") 26 | t.equal(memoizedAdd(2, 2), 4, "the cached value is being returned") 27 | t.equal(numOfCalls, 2, "config that \"addWithLogging\" was NOT called again") 28 | t.deepEqual() 29 | t.end() 30 | }) 31 | -------------------------------------------------------------------------------- /src/isArrayish.js: -------------------------------------------------------------------------------- 1 | import isNil from "./isNil" 2 | import getConstructorName from "./_internal/getConstructorName" 3 | 4 | /** 5 | * Checks if a given type's constructor name is that for a list or array like type 6 | * 7 | * @function 8 | * @private 9 | * @name isArrayLike 10 | * @param {string} name The constructor name for a given type 11 | * @returns {boolean} Whether or not the type's constructor is a list/array like type 12 | */ 13 | function isArrayLike(name) { 14 | switch (name) { 15 | case "Set": 16 | case "WeakSet": 17 | case "Array": 18 | case "Float64Array": 19 | case "Float32Array": 20 | case "Int32Array": 21 | case "Uint16Array": 22 | case "Int16Array": 23 | case "Uint8ClampedArray": 24 | case "Uint8Array": 25 | case "Int8Array": 26 | return true 27 | default: 28 | return false 29 | } 30 | } 31 | 32 | /** 33 | * Checks if a given value is "array-like". 34 | * 35 | * This includes: 36 | * - Array 37 | * - Set 38 | * - WeakSet 39 | * - Float64Array 40 | * - Float32Array 41 | * - Int32Array 42 | * - Uint16Array 43 | * - Int16Array 44 | * - Uint8ClampedArray 45 | * - Uint8Array 46 | * - Int8Array 47 | * 48 | * @function 49 | * @name isArrayish 50 | * @param {*} val A value to check as being an array 51 | * @returns {boolean} Whether the value is an array-like type 52 | */ 53 | function isArrayish(val) { 54 | return Array.isArray(val) || (!isNil(val) && isArrayLike(getConstructorName(val))) 55 | } 56 | 57 | export default isArrayish 58 | --------------------------------------------------------------------------------