npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@warangel580/utils

v0.4.0

Published

Common operations on data without side-effects

Downloads

29

Readme

Utils

Common operations on the most used data structures : single values (strings, numbers, ...), arrays and objects.

Dealing with only "simple" types allows to model complex systems with only simple functions usable on everything.

As a design philosophy, all functions use "data" as a first argument and use no side-effects so they're easily reusable on iterable data.

Installation

npm install @warangel580/utils

or

yarn add @warangel580/utils

Import

// ES6
import { transform, get, set } from "@warangel580/utils";
// NodeJS
const { transform, get, set } = require("@warangel580/utils");

API

Note: _ means "any other parameter"

Types

isNil(data)

Return true if data is null or undefined

isNil(undefined) // => true
isNil(null)      // => true
isNil(_)         // => false

isFunction(data)

Return true if data is a function

isFunction(() => {}) // => true
isFunction(_)        // => false

isArray(data)

Return true if data is an array

isArray([/* ... */]) // => true
isArray(_)           // => false

isObject(data)

Return true if data is an array

isObject({/* ... */}) // => true
isObject(_)           // => false

isIterable(data)

Return true if data is iterable, i.e. is an array or an object

isIterable([/* ... */]) // => true
isIterable({/* ... */}) // => true
isIterable(_)           // => false

Iterators

reduce(data, fn, initialValue)

Like Array.reduce but works on objects too.

Iterates on data by doing next = fn(current, value, key, data), starting with initialValue as current.

If data is not iterable, it returns initialValue.

const add = (a, b) => a + b;

reduce([1, 2, 3],          add, 0) // => 6
reduce({a: 1, b: 2, c: 3}, add, 0) // => 6
reduce(undefined,          add, 0) // => 0

transform(initialValue, data, fn)

Like reduce but arguments are swapped for readability, for example when we're trying to "transform" an existing value instead of creating a new one.

transform({a: 1}, [{b: 2}, {c: 3}], (current, next) => {
  return Object.assign(current, next);
}) // => {a: 1, b: 2, c: 3}

map(data, fn)

Like Array.map but works on objects too.

Iterates on data by doing value = fn(value, key, data)

If data is not iterable, it returns data.

const inc = x => x + 1;

map([1, 2, 3],          inc) // => [2, 3, 4]
map({a: 1, b: 2, c: 3}, inc) // => {a: 2, b: 3, c: 4}
map(null,               inc) // => null

filter(data, fn)

Like Array.filter but works on objects too.

Iterates on data by doing keep = fn(value, key, data)

If data is not iterable, it returns data.

const isEven = x => x % 2 == 0;

reduce([1, 2, 3],          isEven) // => [2]
reduce({a: 1, b: 2, c: 3}, isEven) // => {b: 2}
reduce(null,               isEven) // => null

each(data, fn)

Like Array.forEach but works on objects too.

Iterates on data by doing fn(value, key, data)

If data is not iterable, it returns data.

const log = v => console.log(v)

each([1, 2, 3],          log) // logs(1) then (2), (3)
each({a: 1, b: 2, c: 3}, log) // logs(1) then (2), (3)
each(null,               log) // (nothing happens)

eachSync(data, async fn)

Like each but works with promises sequentially.

Iterates on data by doing fn(value, key, data)

If data is not iterable, it returns data.

const apiSaveUser = async (user) => await Api.saveUser(user);

eachSync([user1, user2, user3], apiSaveUser) // saves user (1) then (2), then (3) (not at the same time)

Side-effects

debug(data, ...rest)

Returns data after console.log(...rest, data) so it's easy to add debug() to existing code

// => console.log("a", a, "b", b, "=>", a + b)
// => return a + b
return debug(a + b, "a", a, "b", b, "=>");

tap(data)

Returns data after applying side-effect to allow chaining

// Adding a user in a single line
return tap(users, users => users.push(user));

copy(data)

Returns a shallow-copy of data to ensure that it doesn't change later with side-effects.

let prev = [1, 2, 3, 4];
let next = copy(prev);

// Editing number with side-effects
next.push(5);

// Previous hasn't changed
prev // => [1, 2, 3, 4]

clone(data)

Same as copy, but returns a deep copy (slow!) of data.

parseJson(data, defaultValue = {})

Parse json without failing.

parseJson('{"foo":"bar"}')  // => {foo: "bar"}
parseJson('{invalid json}') // => {}
parseJson('{invalid json}', undefined) // => undefined

toJson(data, pretty = false, replacer = null)

Transforms data into JSON representation.

pretty can be a number, which is the number of spaces used for indentation (2 by default if pretty === true)

toJson({foo: "bar"})  // => '{"foo":"bar"}'
toJson({a: 1, b: 2}, true)  /* => '{
  "a": 1,
  "b": 2
}' */

tryCatch(fn, onCatch)

Returns fn result or run/return onCatch if something goes wrong.

let fail = () => { throw 'ERR' }

tryCatch(fail)             // => undefined
tryCatch(fail, 42)         // => 42
tryCatch(fail, err => err) // => 'ERR'

Getters - Setters

get(data, path, notFoundValue = undefined)

Get value from data.

let userId   = get(response, ['user', userId, 'comments'], [])
// => <comments> or []
let userName = get(user, 'active', false);
// => <active> or false

set(data, path, newValue)

Set a value (without side-effects) in a deep data tree of values.

let user = {
  username: "warangel580",
  game: {
    score: 10,
  }
};

user = set(user, 'active', true);
user = set(user, ['game', 'state'], 'done');
user = set(user, ['game', 'score'], s => s * 2);

user /* => {
  username: "warangel580",
  active: true,
  game: {
    state: 'done',
    score: 20,
  }
} */

setUnsafe(data, path, newValue)

Like set but doesn't copy data before editing it, which is way faster on large datasets.

You have the responsability to copy data if you need to avoid side-effects.

let before = {};

after = setUnsafe(before, 'foo', 'bar');

before /* => {
  foo: "bar",
} */

after /* => {
  foo: "bar",
} */

Data

size(data)

Get data size, like Array.length.

size([1, 2, 3])                     // => 3
size({a: 1, b, 2, c: {x: 3, y: 4}}) // => 3
size(null)                          // => undefined

keys(data)

Get data keys, like Object.keys().

keys({a: 1, b, 2, c: {x: 3, y: 4}}) // ['a', 'b', 'c']

values(data)

Get data values, like Object.values().

values({a: 1, b, 2, c: {x: 3, y: 4}}) // => [1, 2, {x: 3, y: 4}]

entries(data)

Get data entries, like Object.entries().

let data = {a: 1, b: {x: 2, y: [3, 4]}, c: ['foo', 'bar']};

entries(data) // => [['a', 1], ['b', {x: 2, y: [3,4]}], ['c', ['foo', 'bar']]]

randomIn(data)

Get a random value in data

// Results may vary ;)
randomIn([1, 2, 3])          // => 3
randomIn([1, 2, 3])          // => 2
randomIn({a: 1, b: 2, c: 3}) // => 1
randomIn({a: 1, b: 2, c: 3}) // => 3

randomEntryIn(data)

Get a random [key, value] in data

// Results may vary ;)
randomEntryIn([1, 2, 3])          // => [2, 3]
randomEntryIn([1, 2, 3])          // => [1, 2]
randomEntryIn({a: 1, b: 2, c: 3}) // => ['a', 1]
randomEntryIn({a: 1, b: 2, c: 3}) // => ['c', 3]

randomKeyIn(data)

Get a random value in data

// Results may vary ;)
randomIn([1, 2, 3])          // => 2
randomIn([1, 2, 3])          // => 0
randomIn({a: 1, b: 2, c: 3}) // => 'a'
randomIn({a: 1, b: 2, c: 3}) // => 'c'

sort(data, comparator)

Sort data (without side-effects), like Array.sort().

Note that object order is "temporary" (not enforced by javascript) but still useful for displaying data.

sort({a:1, b:7, c:4}, (v1, v2) => v2 - v1) // => {b:7, c:4, a:1}

sortUnsafe(data, comparator)

Like sortUnsafe but doesn't copy data before sorting it, which is way faster on large datasets.

You have the responsability to copy data if you need to avoid side-effects.

sortUnsafe({a:1, b:7, c:4}, (v1, v2) => v2 - v1) // => {b:7, c:4, a:1}

Arrays

pushLast(data, ...values)

Pure version of Array.push

pushLast(["a"], "b", "c") // => ["a", "b", "c"]

pushLastUnsafe(data, ...values)

Like pushLast but doesn't copy data before editing it, which is way faster on large datasets.

You have the responsability to copy data if you need to avoid side-effects.

pushLastUnsafe(["a"], "b", "c") // => ["a", "b", "c"]

pushFirst(data, ...values)

Pure version of Array.unshift

pushFirst(["a"], "b", "c") // => ["b", "c", "a"]

pushFirstUnsafe(data, ...values)

Like pushFirst but doesn't copy data before editing it, which is way faster on large datasets.

You have the responsability to copy data if you need to avoid side-effects.

pushFirstUnsafe(["a"], "b", "c") // => ["b", "c", "a"]

popFirst(data)

Pure version of Array.pop, returns [firstElement, ...otherElements]

popFirst(["a", "b", "c"]) // => ["a", ["b", "c"]]

popLast(data)

Pure version of Array.shift, returns [...elements, lastElement]

popLast(["a", "b", "c"]) // => [["a", "b"], "c"]

concat(...datas)

Return all arrays concatenated

concat(["a", "b"], ["c", "d"], ["e"]) // => ["a", "b", "c", "d", "e"]

partition(data, n, ...datas)

Concats arrays then cut them in n-sized arrays

concat(["1", "2"], 3, ["3", "4"], ["5"], ["6"]) // => [["1", "2", "3"], ["4", "5", "6"]]

toPairs(...datas)

Returns a partition of size 2, useful for making pairs.

toPairs(["1", "2"], ["3", "4"], ["5"], ["6"]) // => [["1", "2"], ["3", "4"], ["5", "6"]]

Objects

merge(...datas, fn?)

Merge objects with Object.assign or fn(current, next) if given

merge({a:1}, {b:2}, {b:3, c:4}) // => {a:1, b:3, c:4}
merge({b:1}, {b:2}, {b:3}, (current, next) => {
  return transform(current, next, (v, k) => {
    return get(current, k, 0) + v;
  });
}) // {b: 6}

Values

or(...datas)

Returns the "most truthy" value given, useful for default values

or(undefined, [])          // => []
or({a: 1},    {})          // => {}
or(undefined, null, "foo") // => "foo"

range(size)

Returns an array of size filled with indexes

range(5) // => [0, 1, 2, 3, 4]

when(...kvs)

TODO: not sure of API for this one, going to improve it later

match(data, kvs)

TODO: not sure of API for this one, going to improve it later

Functions

using(...values, fn)

Use multiple unnamed expressions in a function, which allows local naming without let.

using(1 + 1, 3 * 4, (v1, v2) => v1 * v2) // => 24

call(data, fnName, ...args)

Returns data[fnName](...args)

call(["a", "b", "c"], "slice", -2) // => ["b", "c"]

defer(fn, ...args)

Simplify callbacks that use data as first arg

map(array, value => get(value, 'key'))
// becomes
map(array, defer(get, 'key'))

If you alias defer to _, you can write

map(array, _(get, 'key'))

pipe(data, ...fns)

Apply fns to data successively

// get admin user names
pipe(users,
  _(filter, _(get, 'isAdmin', false)),
  _(map,    _(get, 'name'))
)

Updating package

  • https://zellwk.com/blog/publish-to-npm/ (just use np)