Gathering detailed insights and metrics for rambda
Gathering detailed insights and metrics for rambda
Gathering detailed insights and metrics for rambda
Gathering detailed insights and metrics for rambda
Typescript focused FP library similar to Remeda and Ramda
npm install rambda
Typescript
Module System
Node Version
NPM Version
99.7
Supply Chain
100
Quality
84.6
Maintenance
100
Vulnerability
100
License
JavaScript (58.37%)
TypeScript (41.63%)
Total Downloads
119,141,946
Last Day
85,533
Last Week
1,966,585
Last Month
8,343,622
Last Year
69,792,818
MIT License
1,725 Stars
2,393 Commits
89 Forks
15 Watchers
3 Branches
61 Contributors
Updated on Jul 04, 2025
Minified
Minified + Gzipped
Latest Version
10.2.0
Package Id
rambda@10.2.0
Unpacked Size
659.20 kB
Size
126.79 kB
File Count
134
NPM Version
10.9.2
Node Version
22.14.0
Published on
May 16, 2025
Cumulative downloads
Total Downloads
Last Day
10.9%
85,533
Compared to previous day
Last Week
-5.4%
1,966,585
Compared to previous week
Last Month
2.9%
8,343,622
Compared to previous month
Last Year
146.7%
69,792,818
Compared to previous year
Rambda
is TypeScript-focused utility library similar to Remeda
, Ramda
and Radashi
.
Initially it started as faster alternative to functional programming library Ramda
, but in order to address many TypeScript issues, now Rambda
takes a separate path. - Documentation
1import { pipe, map, filter } from 'rambda' 2 3const result = pipe( 4 [1, 2, 3, 4], 5 filter(x => x > 2), 6 map(x => x * 2), 7) 8// => [6, 8]
You can test this example in Rambda's REPL
Mixing Functional Programming
and TypeScript
is not easy.
One way to solve this is to focus what can be actually achieved and refrain from what is not possible.
R.pipe
as the main way to use RambdaAll methods are meant to be used as part of R.pipe
chain
This is the main purpose of functional programming, i.e. to pass data through a chain of functions.
Having R.pipe(input, ...fns)
helps TypeScript to infer the types of the input and the output.
Here is one example why R.pipe
is better than Ramda.pipe
:
1const list = [1, 2, 3]; 2 3it('within pipe', () => { 4 const result = pipe( 5 list, 6 filter((x) => { 7 x; // $ExpectType number 8 return x > 1; 9 }), 10 ); 11 result; // $ExpectType number[] 12}); 13it('within Ramda.pipe requires explicit types', () => { 14 Ramda.pipe( 15 (x) => x, 16 filter<number>((x) => { 17 x; // $ExpectType number 18 return x > 1; 19 }), 20 filter((x: number) => { 21 x; // $ExpectType number 22 return x > 1; 23 }), 24 )(list); 25});
The idea is to give TypeScript
users only the most useful methods and let them implement the rest. No magic logic methods that are hard to remember. You shouldn't need to read the documentation to understand what a method does. Its name and signature should be enough.
Methods that are simply to remember only by its name. Complex logic shouldn't be part of utility library, but part of your codebase.
Keep only methods which are both useful and which behaviour is obvious from its name. For example, R.innerJoin
is kept, but R.identical
, R.move
is removed. Methods such as R.toLower
, R.length
provide little value. Such method are omitted from Rambda on purpose.
Some generic methods such as curry
and assoc
is not easy to be expressed in TypeScript. For this reason Rambda
omits such methods.
No R.cond
or R.ifElse
as they make the chain less readable.
No R.length
as it adds very little value.
No R.difference
as user must remember the order of the inputs, i.e. which is compared to and which is compared against.
Because of the focus on R.pipe
, there is only one way to use each method. This helps with testing and also with TypeScript definitions.
R.methodName(input1)(input2)
R.methodName(input1, input2)(input3)
import * as R from "https://deno.land/x/rambda/mod.ts";
R.filter(x => x > 1)([1, 2, 3])
R.path
Standard usage of R.path
is R.path(['a', 'b'])({a: {b: 1} })
.
In Rambda you have the choice to use dot notation(which is arguably more readable):
R.path('a.b')({a: {b: 1} })
Please note that since path input is turned into array, i.e. if you want R.path(['a','1', 'b'])({a: {'1': {b: 2}}})
to return 2
, you will have to pass array path, not string path. If you pass a.1.b
, it will turn path input to ['a', 1, 'b']
.
R.pick
and R.omit
Similar to dot notation, but the separator is comma(,
) instead of dot(.
).
R.pick('a,b', {a: 1 , b: 2, c: 3} })
// No space allowed between properties
Since Rambda
methods doesn't use so many internals, it is faster than Ramda
.
Prior to version 10
, benchmark summary was included, but now the main selling point is the TypeScript focus, not performance so this is no longer included.
Up until version 9.4.2
, the aim of Rambda was to match as much as possible the Ramda API.
Documentation site of Rambda
version 9.4.2
is available here.
From version 10.0.0
onwards, Rambda will start to diverge from Ramda in order to address some of the issues that Ramda has.
Currently, Rambda
includes 32 methods that differ from Ramda
and shares 83 methods with it.
-- Typescript support - this is the main reason for the divergence. Most of design decisions in Rambda are made with Typescript in mind.
-- Methods that imply side-effect, which is not FP oriented, e.g. R.forEach
.
-- Naming of methods that doesn't match developer's expectation, such as R.chain
, which should be called flatMap
.
-- Naming of methods is sometimes too generic to be remembered such as R.update
, R.modify
, R.where
.
-- Methods that are already present in standard JavaScript, such as R.toLower
, R.length
.
-- R.compose
doesn't have the best possible TypeScript support.
1 2addProp<T extends object, P extends PropertyKey, V extends unknown>( 3 prop: P, 4 value: V 5): (obj: T) => MergeTypes<T & Record<P, V>>
It adds new key-value pair to the object.
1const result = R.pipe( 2 { a: 1, b: 'foo' }, 3 R.addProp('c', 3) 4) 5// => { a: 1, b: 'foo', c: 3 }
Try this R.addProp example in Rambda REPL
1addProp<T extends object, P extends PropertyKey, V extends unknown>( 2 prop: P, 3 value: V 4): (obj: T) => MergeTypes<T & Record<P, V>>;
1export function addProp(key, value) { 2 return obj => ({ ...obj, [key]: value }) 3}
1import { addProp } from "./addProp.js" 2 3test('happy', () => { 4 const result = addProp('a', 1)({ b: 2 }) 5 const expected = { a: 1, b: 2 } 6 7 expect(result).toEqual(expected) 8})
1import { addProp, pipe } from 'rambda' 2 3it('R.addProp', () => { 4 const result = pipe({ a: 1, b: 'foo' }, addProp('c', 3)) 5 result.a // $ExpectType number 6 result.b // $ExpectType string 7 result.c // $ExpectType number 8})
1 2addPropToObjects< 3 T extends object, 4 K extends string, 5 R 6>( 7 property: K, 8 fn: (input: T) => R 9): (list: T[]) => MergeTypes<T & { [P in K]: R }>[]
It receives list of objects and add new property to each item.
The value is based on result of fn
function, which receives the current object as argument.
1const result = R.pipe( 2 [ 3 {a: 1, b: 2}, 4 {a: 3, b: 4}, 5 ], 6 R.addPropToObjects( 7 'c', 8 (x) => String(x.a + x.b), 9 ) 10) 11// => [{a: 1, b: 2, c: '3'}, {a: 3, b: 4, c: '7'}]
Try this R.addPropToObjects example in Rambda REPL
1addPropToObjects< 2 T extends object, 3 K extends string, 4 R 5>( 6 property: K, 7 fn: (input: T) => R 8): (list: T[]) => MergeTypes<T & { [P in K]: R }>[];
1import { mapFn } from './map.js' 2 3export function addPropToObjects ( 4 property, 5 fn 6){ 7 return listOfObjects => mapFn( 8 (obj) => ({ 9 ...(obj), 10 [property]: fn(obj) 11 }), 12 listOfObjects 13 ) 14}
1import { pipe } from "./pipe.js" 2import { addPropToObjects } from "./addPropToObjects.js" 3 4test('R.addPropToObjects', () => { 5 let result = pipe( 6 [ 7 {a: 1, b: 2}, 8 {a: 3, b: 4}, 9 ], 10 addPropToObjects( 11 'c', 12 (x) => String(x.a + x.b), 13 ) 14 ) 15 expect(result).toEqual([ 16 { a: 1, b: 2, c: '3' }, 17 { a: 3, b: 4, c: '7' }, 18 ]) 19})
1import { addPropToObjects, pipe } from 'rambda' 2 3it('R.addPropToObjects', () => { 4 let result = pipe( 5 [ 6 {a: 1, b: 2}, 7 {a: 3, b: 4}, 8 ], 9 addPropToObjects( 10 'c', 11 (x) => String(x.a + x.b), 12 ) 13 ) 14 result // $ExpectType { a: number; b: number; c: string; }[] 15})
1 2all<T>(predicate: (x: T) => boolean): (list: T[]) => boolean
It returns true
, if all members of array list
returns true
, when applied as argument to predicate
function.
1const list = [ 0, 1, 2, 3, 4 ] 2const predicate = x => x > -1 3 4const result = R.pipe( 5 list, 6 R.all(predicate) 7) // => true
Try this R.all example in Rambda REPL
1all<T>(predicate: (x: T) => boolean): (list: T[]) => boolean;
1export function all(predicate) { 2 return list => { 3 for (let i = 0; i < list.length; i++) { 4 if (!predicate(list[i])) { 5 return false 6 } 7 } 8 9 return true 10 } 11}
1import { all } from './all.js' 2 3const list = [0, 1, 2, 3, 4] 4 5test('when true', () => { 6 const fn = x => x > -1 7 8 expect(all(fn)(list)).toBeTruthy() 9}) 10 11test('when false', () => { 12 const fn = x => x > 2 13 14 expect(all(fn)(list)).toBeFalsy() 15})
1import * as R from 'rambda' 2 3describe('all', () => { 4 it('happy', () => { 5 const result = R.pipe( 6 [1, 2, 3], 7 R.all(x => { 8 x // $ExpectType number 9 return x > 0 10 }), 11 ) 12 result // $ExpectType boolean 13 }) 14})
1 2allPass<F extends (...args: any[]) => boolean>(predicates: readonly F[]): F
It returns true
, if all functions of predicates
return true
, when input
is their argument.
1const list = [[1, 2, 3, 4], [3, 4, 5]] 2const result = R.pipe( 3 list, 4 R.filter(R.allPass([R.includes(2), R.includes(3)])) 5) // => [[1, 2, 3, 4]]
Try this R.allPass example in Rambda REPL
1allPass<F extends (...args: any[]) => boolean>(predicates: readonly F[]): F;
1export function allPass(predicates) { 2 return input => { 3 let counter = 0 4 while (counter < predicates.length) { 5 if (!predicates[counter](input)) { 6 return false 7 } 8 counter++ 9 } 10 11 return true 12 } 13}
1import { allPass } from './allPass.js' 2import { filter } from './filter.js' 3import { includes } from './includes.js' 4import { pipe } from './pipe.js' 5 6const list = [ 7 [1, 2, 3, 4], 8 [3, 4, 5], 9] 10test('happy', () => { 11 const result = pipe(list, filter(allPass([includes(2), includes(3)]))) 12 expect(result).toEqual([[1, 2, 3, 4]]) 13}) 14 15test('when returns false', () => { 16 const result = pipe(list, filter(allPass([includes(12), includes(31)]))) 17 expect(result).toEqual([]) 18})
1import * as R from 'rambda' 2 3describe('allPass', () => { 4 it('happy', () => { 5 const list = [ 6 [1, 2, 3, 4], 7 [3, 4, 5], 8 ] 9 const result = R.pipe(list, R.map(R.allPass([R.includes(3), R.includes(4)]))) 10 result // $ExpectType boolean[] 11 }) 12})
1 2any<T>(predicate: (x: T) => boolean): (list: T[]) => boolean
It returns true
, if at least one member of list
returns true, when passed to a predicate
function.
1const list = [1, 2, 3] 2const predicate = x => x * x > 8 3R.any(fn)(list) 4// => true
Try this R.any example in Rambda REPL
1any<T>(predicate: (x: T) => boolean): (list: T[]) => boolean;
1export function any(predicate) { 2 return list => { 3 let counter = 0 4 while (counter < list.length) { 5 if (predicate(list[counter], counter)) { 6 return true 7 } 8 counter++ 9 } 10 11 return false 12 } 13}
1import { any } from './any.js' 2 3const list = [1, 2, 3] 4 5test('happy', () => { 6 expect(any(x => x > 2)(list)).toBeTruthy() 7})
1import { any, pipe } from 'rambda' 2 3it('R.any', () => { 4 const result = pipe( 5 [1, 2, 3], 6 any(x => { 7 x // $ExpectType number 8 return x > 2 9 }), 10 ) 11 result // $ExpectType boolean 12})
1 2anyPass<T, TF1 extends T, TF2 extends T>( 3 predicates: [(a: T) => a is TF1, (a: T) => a is TF2], 4): (a: T) => a is TF1 | TF2
It accepts list of predicates
and returns a function. This function with its input
will return true
, if any of predicates
returns true
for this input
.
1const isBig = x => x > 20 2const isOdd = x => x % 2 === 1 3const input = 11 4 5const fn = R.anyPass( 6 [isBig, isOdd] 7) 8 9const result = fn(input) 10// => true
Try this R.anyPass example in Rambda REPL
1anyPass<T, TF1 extends T, TF2 extends T>( 2 predicates: [(a: T) => a is TF1, (a: T) => a is TF2], 3): (a: T) => a is TF1 | TF2; 4anyPass<T, TF1 extends T, TF2 extends T, TF3 extends T>( 5 predicates: [(a: T) => a is TF1, (a: T) => a is TF2, (a: T) => a is TF3], 6): (a: T) => a is TF1 | TF2 | TF3; 7anyPass<T, TF1 extends T, TF2 extends T, TF3 extends T>( 8 predicates: [(a: T) => a is TF1, (a: T) => a is TF2, (a: T) => a is TF3], 9): (a: T) => a is TF1 | TF2 | TF3; 10anyPass<T, TF1 extends T, TF2 extends T, TF3 extends T, TF4 extends T>( 11 predicates: [(a: T) => a is TF1, (a: T) => a is TF2, (a: T) => a is TF3, (a: T) => a is TF4], 12): (a: T) => a is TF1 | TF2 | TF3 | TF4; 13... 14...
1export function anyPass(predicates) { 2 return input => { 3 let counter = 0 4 while (counter < predicates.length) { 5 if (predicates[counter](input)) { 6 return true 7 } 8 counter++ 9 } 10 11 return false 12 } 13}
1import { anyPass } from './anyPass.js' 2 3test('happy', () => { 4 const rules = [x => typeof x === 'string', x => x > 10] 5 const predicate = anyPass(rules) 6 expect(predicate('foo')).toBeTruthy() 7 expect(predicate(6)).toBeFalsy() 8}) 9 10test('happy', () => { 11 const rules = [x => typeof x === 'string', x => x > 10] 12 13 expect(anyPass(rules)(11)).toBeTruthy() 14 expect(anyPass(rules)(undefined)).toBeFalsy() 15}) 16 17const obj = { 18 a: 1, 19 b: 2, 20} 21 22test('when returns true', () => { 23 const conditionArr = [val => val.a === 1, val => val.a === 2] 24 25 expect(anyPass(conditionArr)(obj)).toBeTruthy() 26}) 27 28test('when returns false', () => { 29 const conditionArr = [val => val.a === 2, val => val.b === 3] 30 31 expect(anyPass(conditionArr)(obj)).toBeFalsy() 32}) 33 34test('with empty predicates list', () => { 35 expect(anyPass([])(3)).toBeFalsy() 36})
1import { anyPass, filter } from 'rambda' 2 3describe('anyPass', () => { 4 it('issue #604', () => { 5 const plusEq = (w: number, x: number, y: number, z: number) => w + x === y + z 6 const result = anyPass([plusEq])(3, 3, 3, 3) 7 8 result // $ExpectType boolean 9 }) 10 it('issue #642', () => { 11 const isGreater = (num: number) => num > 5 12 const pred = anyPass([isGreater]) 13 const xs = [0, 1, 2, 3] 14 15 const filtered1 = filter(pred)(xs) 16 filtered1 // $ExpectType number[] 17 const filtered2 = xs.filter(pred) 18 filtered2 // $ExpectType number[] 19 }) 20 it('functions as a type guard', () => { 21 const isString = (x: unknown): x is string => typeof x === 'string' 22 const isNumber = (x: unknown): x is number => typeof x === 'number' 23 const isBoolean = (x: unknown): x is boolean => typeof x === 'boolean' 24 25 const isStringNumberOrBoolean = anyPass([isString, isNumber, isBoolean]) 26 27 const aValue: unknown = 1 28 29 if (isStringNumberOrBoolean(aValue)) { 30 aValue // $ExpectType string | number | boolean 31 } 32 }) 33})
1 2append<T>(el: T): (list: T[]) => T[]
It adds element x
at the end of iterable
.
1const x = 'foo' 2 3const result = R.append(x, ['bar', 'baz']) 4// => ['bar', 'baz', 'foo']
Try this R.append example in Rambda REPL
1append<T>(el: T): (list: T[]) => T[]; 2append<T>(el: T): (list: readonly T[]) => T[];
1import { cloneList } from './_internals/cloneList.js' 2 3export function append(x) { 4 return list => { 5 const clone = cloneList(list) 6 clone.push(x) 7 8 return clone 9 } 10}
1import { append } from './append.js' 2 3test('happy', () => { 4 expect(append('tests')(['write', 'more'])).toEqual(['write', 'more', 'tests']) 5}) 6 7test('append to empty array', () => { 8 expect(append('tests')([])).toEqual(['tests']) 9})
1import { append, pipe, prepend } from 'rambda' 2 3const listOfNumbers = [1, 2, 3] 4 5describe('R.append/R.prepend', () => { 6 it('happy', () => { 7 const result = pipe(listOfNumbers, append(4), prepend(0)) 8 result // $ExpectType number[] 9 }) 10 it('with object', () => { 11 const result = pipe([{ a: 1 }], append({ a: 10 }), prepend({ a: 20 })) 12 result // $ExpectType { a: number; }[] 13 }) 14})
1 2ascend<T>(fn: (obj: T) => Ord): (a: T, b: T)=> Ordering
Helper function to be used with R.sort
to sort list in ascending order.
1const result = R.pipe( 2 [{a: 1}, {a: 2}, {a: 0}], 3 R.sort(R.ascend(R.prop('a'))) 4) 5// => [{a: 0}, {a: 1}, {a: 2}]
Try this R.ascend example in Rambda REPL
1ascend<T>(fn: (obj: T) => Ord): (a: T, b: T)=> Ordering;
1export function createCompareFunction(a, b, winner, loser) { 2 if (a === b) { 3 return 0 4 } 5 6 return a < b ? winner : loser 7} 8 9export function ascend(getFunction) { 10 return (a, b) => { 11 const aValue = getFunction(a) 12 const bValue = getFunction(b) 13 14 return createCompareFunction(aValue, bValue, -1, 1) 15} 16}
1import { ascend } from './ascend.js' 2import { descend } from './descend.js' 3import { sort } from './sort.js' 4 5test('ascend', () => { 6 const result = sort( 7 ascend(x => x.a))( 8 [{a:1}, {a:3}, {a:2}], 9 ) 10 expect(result).toEqual([{a:1}, {a:2}, {a:3}]) 11}) 12 13test('descend', () => { 14 const result = sort( 15 descend(x => x.a))( 16 [{a:1}, {a:3}, {a:2}], 17 ) 18 expect(result).toEqual([{a:3}, {a:2}, {a:1}]) 19})
1import { pipe, ascend, sort } from 'rambda' 2 3it('R.ascend', () => { 4 const result = pipe( 5 [{a:1}, {a:2}], 6 sort(ascend(x => x.a)) 7 ) 8 result // $ExpectType { a: number; }[] 9})
1 2assertType<T, U extends T>(fn: (x: T) => x is U) : (x: T) => U
It helps to make sure that input is from specific type. Similar to R.convertToType
, but it actually checks the type of the input value. If fn
input returns falsy value, then the function will throw an error.
1assertType<T, U extends T>(fn: (x: T) => x is U) : (x: T) => U;
1export function assertType(fn) { 2 return (x) => { 3 if (fn(x)) { 4 return x 5 } 6 throw new Error('type assertion failed in R.assertType') 7 } 8}
1import { assertType } from './assertType.js' 2import { pipe } from './pipe.js' 3 4test('happy', () => { 5 const result = pipe( 6 [1, 2, 3], 7 assertType((x) => x.length === 3), 8 ) 9 expect(result).toEqual([1, 2, 3]) 10}) 11 12test('throw', () => { 13 expect(() => { 14 pipe( 15 [1, 2, 3], 16 assertType((x) => x.length === 4), 17 ) 18 }).toThrow('type assertion failed in R.assertType') 19})
1import { pipe, assertType } from 'rambda' 2 3type Book = { 4 title: string 5 year: number 6} 7 8type BookToRead = Book & { 9 bookmarkFlag: boolean 10} 11 12function isBookToRead(book: Book): book is BookToRead { 13 return (book as BookToRead).bookmarkFlag !== undefined 14} 15 16it('R.assertType', () => { 17 const result = pipe( 18 { title: 'Book1', year: 2020, bookmarkFlag: true }, 19 assertType(isBookToRead), 20 ) 21 result // $ExpectType BookToRead 22})
1 2checkObjectWithSpec<T>(spec: T): <U>(testObj: U) => boolean
It returns true
if all each property in conditions
returns true
when applied to corresponding property in input
object.
1const condition = R.checkObjectWithSpec({ 2 a : x => typeof x === "string", 3 b : x => x === 4 4}) 5const input = { 6 a : "foo", 7 b : 4, 8 c : 11, 9} 10 11const result = condition(input) 12// => true
Try this R.checkObjectWithSpec example in Rambda REPL
1checkObjectWithSpec<T>(spec: T): <U>(testObj: U) => boolean;
1export function checkObjectWithSpec(conditions) { 2 return input => { 3 let shouldProceed = true 4 for (const prop in conditions) { 5 if (!shouldProceed) { 6 continue 7 } 8 const result = conditions[prop](input[prop]) 9 if (shouldProceed && result === false) { 10 shouldProceed = false 11 } 12 } 13 14 return shouldProceed 15 } 16}
1import { checkObjectWithSpec } from './checkObjectWithSpec.js' 2import { equals } from './equals.js' 3 4test('when true', () => { 5 const result = checkObjectWithSpec({ 6 a: equals('foo'), 7 b: equals('bar'), 8 })({ 9 a: 'foo', 10 b: 'bar', 11 x: 11, 12 y: 19, 13 }) 14 15 expect(result).toBeTruthy() 16}) 17 18test('when false | early exit', () => { 19 let counter = 0 20 const equalsFn = expected => input => { 21 counter++ 22 23 return input === expected 24 } 25 const predicate = checkObjectWithSpec({ 26 a: equalsFn('foo'), 27 b: equalsFn('baz'), 28 }) 29 expect( 30 predicate({ 31 a: 'notfoo', 32 b: 'notbar', 33 }), 34 ).toBeFalsy() 35 expect(counter).toBe(1) 36})
1import { checkObjectWithSpec, equals } from 'rambda' 2 3describe('R.checkObjectWithSpec', () => { 4 it('happy', () => { 5 const input = { 6 a: 'foo', 7 b: 'bar', 8 x: 11, 9 y: 19, 10 } 11 const conditions = { 12 a: equals('foo'), 13 b: equals('bar'), 14 } 15 const result = checkObjectWithSpec(conditions)(input) 16 result // $ExpectType boolean 17 }) 18})
1 2compact<T>(list: T[]): Array<StrictNonNullable<T>>
It removes null
and undefined
members from list or object input.
1const result = R.pipe( 2 { 3 a: [ undefined, '', 'a', 'b', 'c'], 4 b: [1,2, null, 0, undefined, 3], 5 c: { a: 1, b: 2, c: 0, d: undefined, e: null, f: false }, 6 }, 7 x => ({ 8 a: R.compact(x.a), 9 b: R.compact(x.b), 10 c: R.compact(x.c) 11 }) 12) 13// => { a: ['a', 'b', 'c'], b: [1, 2, 3], c: { a: 1, b: 2, c: 0, f: false } }
Try this R.compact example in Rambda REPL
1compact<T>(list: T[]): Array<StrictNonNullable<T>>; 2compact<T extends object>(record: T): { 3 [K in keyof T as Exclude<T[K], null | undefined> extends never 4 ? never 5 : K 6 ]: Exclude<T[K], null | undefined> 7};
1import { isArray } from './_internals/isArray.js' 2import { reject } from './reject.js' 3import { rejectObject } from './rejectObject.js' 4 5const isNullOrUndefined = x => x === null || x === undefined 6 7export function compact(input){ 8 if(isArray(input)){ 9 return reject(isNullOrUndefined)(input) 10 } 11 return rejectObject(isNullOrUndefined)(input) 12}
1import { compact } from './compact.js' 2import { pipe } from './pipe.js' 3 4test('happy', () => { 5 const result = pipe( 6 { 7 a: [ undefined, 'a', 'b', 'c'], 8 b: [1,2, null, 0, undefined, 3], 9 c: { a: 1, b: 2, c: 0, d: undefined, e: null, f: false }, 10 }, 11 x => ({ 12 a: compact(x.a), 13 b: compact(x.b), 14 c: compact(x.c) 15 }) 16 ) 17 expect(result.a).toEqual(['a', 'b', 'c']) 18 expect(result.b).toEqual([1,2,0,3]) 19 expect(result.c).toEqual({ a: 1, b: 2,c:0, f: false }) 20})
1import { compact, pipe } from 'rambda' 2 3it('R.compact', () => { 4 let result = pipe( 5 { 6 a: [ undefined, '', 'a', 'b', 'c', null ], 7 b: [1,2, null, 0, undefined, 3], 8 c: { a: 1, b: 2, c: 0, d: undefined, e: null, f: false }, 9 }, 10 x => ({ 11 a: compact(x.a), 12 b: compact(x.b), 13 c: compact(x.c) 14 }) 15 ) 16 17 result.a // $ExpectType string[] 18 result.b // $ExpectType number[] 19 result.c // $ExpectType { a: number; b: number; c: number; f: boolean; } 20})
1 2complement<T extends any[]>(predicate: (...args: T) => unknown): (...args: T) => boolean
It returns inverted
version of origin
function that accept input
as argument.
The return value of inverted
is the negative boolean value of origin(input)
.
1const fn = x => x > 5 2const inverted = complement(fn) 3 4const result = [ 5 fn(7), 6 inverted(7) 7] => [ true, false ]
Try this R.complement example in Rambda REPL
1complement<T extends any[]>(predicate: (...args: T) => unknown): (...args: T) => boolean;
1export function complement(fn) { 2 return (...input) => !fn(...input) 3}
1import { complement } from './complement.js' 2 3test('happy', () => { 4 const fn = complement(x => x.length === 0) 5 6 expect(fn([1, 2, 3])).toBeTruthy() 7}) 8 9test('with multiple parameters', () => { 10 const between = (a, b, c) => a < b && b < c 11 const f = complement(between) 12 expect(f(4, 5, 11)).toBeFalsy() 13 expect(f(12, 2, 6)).toBeTruthy() 14})
1import { complement } from 'rambda' 2 3describe('R.complement', () => { 4 it('happy', () => { 5 const fn = complement((x: number) => x > 10) 6 const result = fn(1) 7 result // $ExpectType boolean 8 }) 9})
1 2concat<T>(x: T[]): (y: T[]) => T[]
It returns a new string or array, which is the result of merging x
and y
.
1R.concat([1, 2])([3, 4]) // => [1, 2, 3, 4] 2R.concat('foo')('bar') // => 'foobar'
Try this R.concat example in Rambda REPL
1concat<T>(x: T[]): (y: T[]) => T[]; 2concat(x: string): (y: string) => string;
1export function concat(x) { 2 return y => (typeof x === 'string' ? `${x}${y}` : [...x, ...y]) 3}
1import { concat, pipe } from 'rambda' 2 3const list1 = [1, 2, 3] 4const list2 = [4, 5, 6] 5 6it('R.concat', () => { 7 const result = pipe(list1, concat(list2)) 8 result // $ExpectType number[] 9 const resultString = pipe('foo', concat('list2')) 10 resultString // $ExpectType string 11})
1 2convertToType<T>(x: unknown) : T
It helps to convert a value to a specific type. It is useful when you have to overcome TypeScript's type inference.
1convertToType<T>(x: unknown) : T;
1export function convertToType(x) { 2 return x 3}
1import { convertToType, pipe } from 'rambda' 2 3const list = [1, 2, 3] 4 5it('R.convertToType', () => { 6 const result = pipe(list, 7 convertToType<string[]>, 8 x => { 9 x // $ExpectType string[] 10 return x 11 } 12 ) 13 result // $ExpectType string[] 14})
1 2count<T>(predicate: (x: T) => boolean): (list: T[]) => number
It counts how many times predicate
function returns true
, when supplied with iteration of list
.
1const list = [{a: 1}, 1, {a:2}] 2const result = R.count(x => x.a !== undefined)(list) 3// => 2
Try this R.count example in Rambda REPL
1count<T>(predicate: (x: T) => boolean): (list: T[]) => number;
1import { isArray } from './_internals/isArray.js' 2 3export function count(predicate) { 4 return list => { 5 if (!isArray(list)) { 6 return 0 7 } 8 9 return list.filter(x => predicate(x)).length 10 } 11}
1import { count } from './count.js' 2 3const predicate = x => x.a !== undefined 4 5test('with empty list', () => { 6 expect(count(predicate)([])).toBe(0) 7}) 8 9test('happy', () => { 10 const list = [1, 2, { a: 1 }, 3, { a: 1 }] 11 12 expect(count(predicate)(list)).toBe(2) 13})
1import { count, pipe } from 'rambda' 2 3const list = [1, 2, 3] 4const predicate = (x: number) => x > 1 5 6it('R.count', () => { 7 const result = pipe(list, count(predicate)) 8 result // $ExpectType number 9})
1 2countBy<T>(fn: (x: T) => string | number): (list: T[]) => { [index: string]: number }
It counts elements in a list after each instance of the input list is passed through transformFn
function.
1const list = [ 'a', 'A', 'b', 'B', 'c', 'C' ] 2 3const result = countBy(x => x.toLowerCase())( list) 4const expected = { a: 2, b: 2, c: 2 } 5// => `result` is equal to `expected`
Try this R.countBy example in Rambda REPL
1countBy<T>(fn: (x: T) => string | number): (list: T[]) => { [index: string]: number };
1export function countBy(fn) { 2 return list => { 3 const willReturn = {} 4 5 list.forEach(item => { 6 const key = fn(item) 7 if (!willReturn[key]) { 8 willReturn[key] = 1 9 } else { 10 willReturn[key]++ 11 } 12 }) 13 14 return willReturn 15 } 16}
1import { countBy } from './countBy.js' 2 3const list = ['a', 'A', 'b', 'B', 'c', 'C'] 4 5test('happy', () => { 6 const result = countBy(x => x.toLowerCase())(list) 7 expect(result).toEqual({ 8 a: 2, 9 b: 2, 10 c: 2, 11 }) 12})
1import { countBy, pipe } from 'rambda' 2 3const list = ['a', 'A', 'b', 'B', 'c', 'C'] 4 5it('R.countBy', () => { 6 const result = pipe( 7 list, 8 countBy(x => x.toLowerCase()), 9 ) 10 result.a // $ExpectType number 11 result.foo // $ExpectType number 12 result // $ExpectType { [index: string]: number; } 13})
1 2createObjectFromKeys<const K extends readonly PropertyKey[], V>( 3 fn: (key: K[number]) => V 4): (keys: K) => { [P in K[number]]: V }
1const result = R.createObjectFromKeys( 2 (x, index) => `${x}-${index}` 3)(['a', 'b', 'c']) 4// => {a: 'a-0', b: 'b-1', c: 'c-2'}
Try this R.createObjectFromKeys example in Rambda REPL
1createObjectFromKeys<const K extends readonly PropertyKey[], V>( 2 fn: (key: K[number]) => V 3): (keys: K) => { [P in K[number]]: V }; 4createObjectFromKeys<const K extends readonly PropertyKey[], V>( 5 fn: (key: K[number], index: number) => V 6): (keys: K) => { [P in K[number]]: V };
1export function createObjectFromKeys(keys) { 2 return fn => { 3 const result = {} 4 keys.forEach((key, index) => { 5 result[key] = fn(key, index) 6 }) 7 8 return result 9 } 10}
1import { createObjectFromKeys } from './createObjectFromKeys.js' 2 3test('happy', () => { 4 const result = createObjectFromKeys(['a', 'b'])((key, index) => key.toUpperCase() + index) 5 const expected = { a: 'A0', b: 'B1' } 6 7 expect(result).toEqual(expected) 8})
1 2defaultTo<T>(defaultValue: T): (input: unknown) => T
It returns defaultValue
, if all of inputArguments
are undefined
, null
or NaN
.
Else, it returns the first truthy inputArguments
instance(from left to right).
:boom: Typescript Note: Pass explicit type annotation when used with R.pipe/R.compose for better type inference
1R.defaultTo('foo')('bar') // => 'bar' 2R.defaultTo('foo'))(undefined) // => 'foo' 3 4// Important - emtpy string is not falsy value 5R.defaultTo('foo')('') // => 'foo'
Try this R.defaultTo example in Rambda REPL
1defaultTo<T>(defaultValue: T): (input: unknown) => T;
1function isFalsy(input) { 2 return input === undefined || input === null || Number.isNaN(input) === true 3} 4 5export function defaultTo(defaultArgument) { 6 return input => isFalsy(input) ? defaultArgument : input 7}
1import { defaultTo } from './defaultTo.js' 2 3test('with undefined', () => { 4 expect(defaultTo('foo')(undefined)).toBe('foo') 5}) 6 7test('with null', () => { 8 expect(defaultTo('foo')(null)).toBe('foo') 9}) 10 11test('with NaN', () => { 12 expect(defaultTo('foo')(Number.NaN)).toBe('foo') 13}) 14 15test('with empty string', () => { 16 expect(defaultTo('foo')('')).toBe('') 17}) 18 19test('with false', () => { 20 expect(defaultTo('foo')(false)).toBeFalsy() 21}) 22 23test('when inputArgument passes initial check', () => { 24 expect(defaultTo('foo')('bar')).toBe('bar') 25})
1import { defaultTo, pipe } from 'rambda' 2 3describe('R.defaultTo', () => { 4 it('happy', () => { 5 const result = pipe('bar' as unknown, defaultTo('foo')) 6 7 result // $ExpectType string 8 }) 9})
1 2descend<T>(fn: (obj: T) => Ord): (a: T, b: T)=> Ordering
Helper function to be used with R.sort
to sort list in descending order.
1const result = R.pipe( 2 [{a: 1}, {a: 2}, {a: 0}], 3 R.sort(R.descend(R.prop('a'))) 4) 5// => [{a: 2}, {a: 1}, {a: 0}]
Try this R.descend example in Rambda REPL
1descend<T>(fn: (obj: T) => Ord): (a: T, b: T)=> Ordering;
1import { createCompareFunction } from './ascend.js' 2 3export function descend(getFunction) { 4 return (a, b) => { 5 const aValue = getFunction(a) 6 const bValue = getFunction(b) 7 8 return createCompareFunction(aValue, bValue, 1, -1) 9 } 10}
1 2drop<T>(howMany: number): (list: T[]) => T[]
It returns howMany
items dropped from beginning of list.
1R.drop(2)(['foo', 'bar', 'baz']) // => ['baz']
Try this R.drop example in Rambda REPL
1drop<T>(howMany: number): (list: T[]) => T[];
1export function drop(howManyToDrop, ) { 2 return list => list.slice(howManyToDrop > 0 ? howManyToDrop : 0) 3}
1import { drop } from './drop.js' 2 3test('with array', () => { 4 expect(drop(2)(['foo', 'bar', 'baz'])).toEqual(['baz']) 5 expect(drop(3)(['foo', 'bar', 'baz'])).toEqual([]) 6 expect(drop(4)(['foo', 'bar', 'baz'])).toEqual([]) 7}) 8 9test('with non-positive count', () => { 10 expect(drop(0)([1, 2, 3])).toEqual([1, 2, 3]) 11 expect(drop(-1)([1, 2, 3])).toEqual([1, 2, 3]) 12 expect(drop(Number.NEGATIVE_INFINITY)([1, 2, 3])).toEqual([1, 2, 3]) 13})
1import { drop, pipe } from 'rambda' 2 3it('R.drop', () => { 4 const result = pipe([1, 2, 3, 4], drop(2)) 5 result // $ExpectType number[] 6})
1 2dropLast<T>(howMany: number): (list: T[]) => T[]
It returns howMany
items dropped from the end of list.
1dropLast<T>(howMany: number): (list: T[]) => T[];
1export function dropLast(numberItems) { 2 return list => (numberItems > 0 ? list.slice(0, -numberItems) : list.slice()) 3}
1import { dropLast } from './dropLast.js' 2 3test('with array', () => { 4 expect(dropLast(2)(['foo', 'bar', 'baz'])).toEqual(['foo']) 5 expect(dropLast(3)(['foo', 'bar', 'baz'])).toEqual([]) 6 expect(dropLast(4)(['foo', 'bar', 'baz'])).toEqual([]) 7}) 8 9test('with non-positive count', () => { 10 expect(dropLast(0)([1, 2, 3])).toEqual([1, 2, 3]) 11 expect(dropLast(-1)([1, 2, 3])).toEqual([1, 2, 3]) 12 expect(dropLast(Number.NEGATIVE_INFINITY)([1, 2, 3])).toEqual([1, 2, 3]) 13})
1 2dropLastWhile<T>(predicate: (x: T, index: number) => boolean): (list: T[]) => T[]
1const list = [1, 2, 3, 4, 5]; 2const predicate = x => x >= 3 3 4const result = dropLastWhile(predicate)(list); 5// => [1, 2]
Try this R.dropLastWhile example in Rambda REPL
1dropLastWhile<T>(predicate: (x: T, index: number) => boolean): (list: T[]) => T[]; 2dropLastWhile<T>(predicate: (x: T) => boolean): (list: T[]) => T[];
1export function dropLastWhile(predicate) { 2 return list => { 3 if (list.length === 0) { 4 return list 5 } 6 7 const toReturn = [] 8 let counter = list.length 9 10 while (counter) { 11 const item = list[--counter] 12 if (!predicate(item, counter)) { 13 toReturn.push(item) 14 break 15 } 16 } 17 18 while (counter) { 19 toReturn.push(list[--counter]) 20 } 21 22 return toReturn.reverse() 23 } 24}
1import { dropLastWhile } from './dropLastWhile.js' 2 3const list = [1, 2, 3, 4, 5] 4 5test('with list', () => { 6 const result = dropLastWhile(x => x >= 3)(list) 7 expect(result).toEqual([1, 2]) 8}) 9 10test('with empty list', () => { 11 expect(dropLastWhile(() => true)([])).toEqual([]) 12})
1 2dropRepeatsBy<T, U>(fn: (x: T) => U): (list: T[]) => T[]
1const result = R.dropRepeatsBy( 2 Math.abs, 3 [1, -1, 2, 3, -3] 4) 5// => [1, 2, 3]
Try this R.dropRepeatsBy example in Rambda REPL
1dropRepeatsBy<T, U>(fn: (x: T) => U): (list: T[]) => T[];
1 2dropRepeatsWith<T>(predicate: (x: T, y: T) => boolean): (list: T[]) => T[]
1const list = [{a:1,b:2}, {a:1,b:3}, {a:2, b:4}] 2const result = R.dropRepeatsWith(R.prop('a'))(list) 3 4// => [{a:1,b:2}, {a:2, b:4}]
Try this R.dropRepeatsWith example in Rambda REPL
1dropRepeatsWith<T>(predicate: (x: T, y: T) => boolean): (list: T[]) => T[];
1 2dropWhile<T>(predicate: (x: T, index: number) => boolean): (list: T[]) => T[]
1const list = [1, 2, 3, 4] 2const predicate = x => x < 3 3const result = R.dropWhile(predicate)(list) 4// => [3, 4]
Try this R.dropWhile example in Rambda REPL
1dropWhile<T>(predicate: (x: T, index: number) => boolean): (list: T[]) => T[]; 2dropWhile<T>(predicate: (x: T) => boolean): (list: T[]) => T[];
1export function dropWhile(predicate) { 2 return iterable => { 3 const toReturn = [] 4 let counter = 0 5 6 while (counter < iterable.length) { 7 const item = iterable[counter++] 8 if (!predicate(item, counter)) { 9 toReturn.push(item) 10 break 11 } 12 } 13 14 while (counter < iterable.length) { 15 toReturn.push(iterable[counter++]) 16 } 17 18 return toReturn 19 } 20}
1import { dropWhile } from './dropWhile.js' 2 3const list = [1, 2, 3, 4] 4 5test('happy', () => { 6 const predicate = (x, i) => { 7 expect(typeof i).toBe('number') 8 return x < 3 9 } 10 const result = dropWhile(predicate)(list) 11 expect(result).toEqual([3, 4]) 12}) 13 14test('always false', () => { 15 const predicate = () => 0 16 const result = dropWhile(predicate)(list) 17 expect(result).toEqual(list) 18})
1import { dropWhile, pipe } from 'rambda' 2 3const list = [1, 2, 3] 4 5describe('R.dropWhile', () => { 6 it('happy', () => { 7 const result = pipe( 8 list, 9 dropWhile(x => x > 1), 10 ) 11 12 result // $ExpectType number[] 13 }) 14 it('with index', () => { 15 const result = pipe( 16 list, 17 dropWhile((x, i) => { 18 i // $ExpectType number 19 return x + i > 2 20 }), 21 ) 22 23 result // $ExpectType number[] 24 }) 25})
1 2eqBy<T>(fn: (x: T) => unknown, a: T): (b: T) => boolean
1const result = R.eqBy(Math.abs, 5)(-5) 2// => true
Try this R.eqBy example in Rambda REPL
1eqBy<T>(fn: (x: T) => unknown, a: T): (b: T) => boolean;
1import { equalsFn } from './equals.js' 2 3export function eqBy(fn, a) { 4 return b => equalsFn(fn(a), fn(b)) 5}
1import { eqBy } from './eqBy.js' 2 3test('deteremines whether two values map to the same value in the codomain', () => { 4 expect(eqBy(Math.abs, 5)(5)).toBe(true) 5 expect(eqBy(Math.abs, 5)(-5)).toBe(true) 6 expect(eqBy(Math.abs, -5)(5)).toBe(true) 7 expect(eqBy(Math.abs, -5)(-5)).toBe(true) 8 expect(eqBy(Math.abs, 42)(99)).toBe(false) 9}) 10 11test('has R.equals semantics', () => { 12 expect(eqBy(Math.abs, Number.NaN)(Number.NaN)).toBe(true) 13 expect(eqBy(Math.abs, [42])([42])).toBe(true) 14 expect(eqBy(x => x, { a: 1 })({ a: 1 })).toBe(true) 15 expect(eqBy(x => x, { a: 1 })({ a: 2 })).toBe(false) 16})
1 2eqProps<T, K extends keyof T>(prop: K, obj1: T): (obj2: T) => boolean
It returns true
if property prop
in obj1
is equal to property prop
in obj2
according to R.equals
.
1const obj1 = {a: 1, b:2} 2const obj2 = {a: 1, b:3} 3const result = R.eqProps('a', obj1)(obj2) 4// => true
Try this R.eqProps example in Rambda REPL
1eqProps<T, K extends keyof T>(prop: K, obj1: T): (obj2: T) => boolean;
1import { equalsFn } from './equals.js' 2 3export function eqProps(property, objA) { 4 return objB => equalsFn( objA[property], objB[property] ) 5}
1import { eqProps } from './eqProps.js' 2 3const obj1 = { 4 a: 1, 5 b: 2, 6} 7const obj2 = { 8 a: 1, 9 b: 3, 10} 11 12test('props are equal', () => { 13 const result = eqProps('a', obj1)(obj2) 14 expect(result).toBeTruthy() 15}) 16 17test('props are not equal', () => { 18 const result = eqProps('b', obj1)(obj2) 19 expect(result).toBeFalsy() 20}) 21 22test('prop does not exist', () => { 23 const result = eqProps('c', obj1)(obj2) 24 expect(result).toBeTruthy() 25})
1import { eqProps, pipe } from 'rambda' 2 3const obj1 = { a: { b: 1 }, c: 2 } 4const obj2 = { a: { b: 1 }, c: 3 } 5 6it('R.eqProps', () => { 7 const result = pipe(obj1, eqProps('a', obj2)) 8 9 result // $ExpectType boolean 10})
1 2equals<T>(x: T, y: T): boolean
It deeply compares x
and y
and returns true
if they are equal.
:boom: It doesn't handle cyclical data structures and functions
1R.equals( 2 [1, {a:2}, [{b: 3}]], 3 [1, {a:2}, [{b: 3}]] 4) // => true
Try this R.equals example in Rambda REPL
1equals<T>(x: T, y: T): boolean; 2equals<T>(x: T): (y: T) => boolean;
1import { isArray } from './_internals/isArray.js' 2import { type } from './type.js' 3 4export function _lastIndexOf(valueToFind, list) { 5 if (!isArray(list)) { 6 throw new Error(`Cannot read property 'indexOf' of ${list}`) 7 } 8 9 const typeOfValue = type(valueToFind) 10 if (!['Array', 'NaN', 'Object', 'RegExp'].includes(typeOfValue)) { 11 return list.lastIndexOf(valueToFind) 12 } 13 14 const { length } = list 15 let index = length 16 let foundIndex = -1 17 18 while (--index > -1 && foundIndex === -1) { 19 if (equalsFn(list[index], valueToFind)) { 20 foundIndex = index 21 } 22 } 23 24 return foundIndex 25} 26 27export function _indexOf(valueToFind, list) { 28 if (!isArray(list)) { 29 throw new Error(`Cannot read property 'indexOf' of ${list}`) 30 } 31 32 const typeOfValue = type(valueToFind) 33 if (!['Array', 'NaN', 'Object', 'RegExp'].includes(typeOfValue)) { 34 return list.indexOf(valueToFind) 35 } 36 37 let index = -1 38 let foundIndex = -1 39 const { length } = list 40 41 while (++index < length && foundIndex === -1) { 42 if (equalsFn(list[index], valueToFind)) { 43 foundIndex = index 44 } 45 } 46 47 return foundIndex 48} 49 50function _arrayFromIterator(iter) { 51 const list = [] 52 let next 53 while (!(next = iter.next()).done) { 54 list.push(next.value) 55 } 56 57 return list 58} 59 60function _compareSets(a, b) { 61 if (a.size !== b.size) { 62 return false 63 } 64 65 const aList = _arrayFromIterator(a.values()) 66 const bList = _arrayFromIterator(b.values()) 67 68 const filtered = aList.filter(aInstance => _indexOf(aInstance, bList) === -1) 69 70 return filtered.length === 0 71} 72 73function compareErrors(a, b) { 74 if (a.message !== b.message) { 75 return false 76 } 77 if (a.toString !== b.toString) { 78 return false 79 } 80 81 return a.toString() === b.toString() 82} 83 84function parseDate(maybeDate) { 85 if (!maybeDate.toDateString) { 86 return [false] 87 } 88 89 return [true, maybeDate.getTime()] 90} 91 92function parseRegex(maybeRegex) { 93 if (maybeRegex.constructor !== RegExp) { 94 return [false] 95 } 96 97 return [true, maybeRegex.toString()] 98} 99 100export function equalsFn(a, b) { 101 if (Object.is(a, b)) { 102 return true 103 } 104 105 const aType = type(a) 106 107 if (aType !== type(b)) { 108 return false 109 } 110 if (aType === 'Function') { 111 return a.name === undefined ? false : a.name === b.name 112 } 113 114 if (['NaN', 'Null', 'Undefined'].includes(aType)) { 115 return true 116 } 117 118 if (['BigInt', 'Number'].includes(aType)) { 119 if (Object.is(-0, a) !== Object.is(-0, b)) { 120 return false 121 } 122 123 return a.toString() === b.toString() 124 } 125 126 if (['Boolean', 'String'].includes(aType)) { 127 return a.toString() === b.toString() 128 } 129 130 if (aType === 'Array') { 131 const aClone = Array.from(a) 132 const bClone = Array.from(b) 133 134 if (aClone.toString() !== bClone.toString()) { 135 return false 136 } 137 138 let loopArrayFlag = true 139 aClone.forEach((aCloneInstance, aCloneIndex) => { 140 if (loopArrayFlag) { 141 if ( 142 aCloneInstance !== bClone[aCloneIndex] && 143 !equalsFn(aCloneInstance, bClone[aCloneIndex]) 144 ) { 145 loopArrayFlag = false 146 } 147 } 148 }) 149 150 return loopArrayFlag 151 } 152 153 const aRegex = parseRegex(a) 154 const bRegex = parseRegex(b) 155 156 if (aRegex[0]) { 157 return bRegex[0] ? aRegex[1] === bRegex[1] : false 158 } 159 if (bRegex[0]) { 160 return false 161 } 162 163 const aDate = parseDate(a) 164 const bDate = parseDate(b) 165 166 if (aDate[0]) { 167 return bDate[0] ? aDate[1] === bDate[1] : false 168 } 169 if (bDate[0]) { 170 return false 171 } 172 173 if (a instanceof Error) { 174 if (!(b instanceof Error)) { 175 return false 176 } 177 178 return compareErrors(a, b) 179 } 180 181 if (aType === 'Set') { 182 return _compareSets(a, b) 183 } 184 185 if (aType === 'Object') { 186 const aKeys = Object.keys(a) 187 188 if (aKeys.length !== Object.keys(b).length) { 189 return false 190 } 191 192 let loopObjectFlag = true 193 aKeys.forEach(aKeyInstance => { 194 if (loopObjectFlag) { 195 const aValue = a[aKeyInstance] 196 const bValue = b[aKeyInstance] 197 198 if (aValue !== bValue && !equalsFn(aValue, bValue)) { 199 loopObjectFlag = false 200 } 201 } 202 }) 203 204 return loopObjectFlag 205 } 206 207 return false 208} 209export function equals(a) { 210 return b => equalsFn(a, b) 211}
1import { equalsFn } from './equals.js' 2 3test('compare functions', () => { 4 function foo() {} 5 function bar() {} 6 const baz = () => {} 7 8 const expectTrue = equalsFn(foo, foo) 9 const expectFalseFirst = equalsFn(foo, bar) 10 const expectFalseSecond = equalsFn(foo, baz) 11 12 expect(expectTrue).toBeTruthy() 13 expect(expectFalseFirst).toBeFalsy() 14 expect(expectFalseSecond).toBeFalsy() 15}) 16 17test('with array of objects', () => { 18 const list1 = [{ a: 1 }, [{ b: 2 }]] 19 const list2 = [{ a: 1 }, [{ b: 2 }]] 20 const list3 = [{ a: 1 }, [{ b: 3 }]] 21 22 expect(equalsFn(list1, list2)).toBeTruthy() 23 expect(equalsFn(list1, list3)).toBeFalsy() 24}) 25 26test('with regex', () => { 27 expect(equalsFn(/s/, /s/)).toBeTruthy() 28 expect(equalsFn(/s/, /d/)).toBeFalsy() 29 expect(equalsFn(/a/gi, /a/gi)).toBeTruthy() 30 expect(equalsFn(/a/gim, /a/gim)).toBeTruthy() 31 expect(equalsFn(/a/gi, /a/i)).toBeFalsy() 32}) 33 34test('not a number', () => { 35 expect(equalsFn([Number.NaN], [Number.NaN])).toBeTruthy() 36}) 37 38test('new number', () => { 39 expect(equalsFn(new Number(0), new Number(0))).toBeTruthy() 40 expect(equalsFn(new Number(0), new Number(1))).toBeFalsy() 41 expect(equalsFn(new Number(1), new Number(0))).toBeFalsy() 42}) 43 44test('new string', () => { 45 expect(equalsFn(new String(''), new String(''))).toBeTruthy() 46 expect(equalsFn(new String(''), new String('x'))).toBeFalsy() 47 expect(equalsFn(new String('x'), new String(''))).toBeFalsy() 48 expect(equalsFn(new String('foo'), new String('foo'))).toBeTruthy() 49 expect(equalsFn(new String('foo'), new String('bar'))).toBeFalsy() 50 expect(equalsFn(new String('bar'), new String('foo'))).toBeFalsy() 51}) 52 53test('new Boolean', () => { 54 expect(e
No vulnerabilities found.
Reason
30 commit(s) and 5 issue activity found in the last 90 days -- score normalized to 10
Reason
no dangerous workflow patterns detected
Reason
license file detected
Details
Reason
no binaries found in the repo
Reason
5 existing vulnerabilities detected
Details
Reason
security policy file detected
Details
Reason
Found 0/12 approved changesets -- score normalized to 0
Reason
detected GitHub workflow tokens with excessive permissions
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
Reason
project is not fuzzed
Details
Reason
branch protection not enabled on development/release branches
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Score
Last Scanned on 2025-06-30
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More