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 🙏

© 2024 – Pkg Stats / Ryan Hefner

omnitool

v0.0.8

Published

Utility library centered on strong typing, simple FP, and unified iterable operations [EARLY]

Downloads

13

Readme

Omnitool

Omnitool is a strongly typed, general purpose utility library for JavaScript and TypeScript.

Alternatives

  • LoDash / Underscore
  • Ramda

Features

  • Fully typed from the start
  • Type information is never lost
  • Iterable operations work on all iterables
  • Iterable operations are chainable
  • Immutability is favored over mutability
  • Mutation is rare and obvious
  • Errors are favored over null/undefined

* The following documentation is a work in progress. *

Getting Started

import { O } from "omnitool"
/* Or: const O = require("omnitool") */

const result = O.range(10).done()
console.log(result)

Iterable Operations

For the most part, the iteration functions of Omnitool are its defining factor.

Omnitool's iteration functions are mostly based on underlying iterable objects called omni-sequences (OmniSequence). These objects wrap arrays, strings, generators and all other iterables and add extended functionality. Omni-sequences come in two variants, pipes (Pipe) and spans (Span). Generally you won't have to worry about which one you are using as you will interact with both through the OmniSequence interface.

The flow of manipulating an iterable with Omnitool is usually one of the following:

  • Iterable -> Omni-sequence(s) -> Iterable
  • Iterable -> Omni-sequence(s) -> Value
  • Iterable -> Value

So the omni-sequence is the middle man that makes things more general. Think of containers (arrays, objects, maps, sets...) as solids and omni-sequences as a fluid you can shape into whatever you want.

Notes

  • Omni-sequences should generally only be used when transforming or iterating data, not storing it
  • They are only guaranteed to be iterable once
  • Operations are immutable
  • Operations can be called as free functions (O.map($iterable, $function)) or via property access ($omni-sequence.map($function)).

Pipes

Pipes are omni-sequences in which each element is created lazily, meaning they are created as they are used. Pipes are really just generators with extended functionality.

Pipes are usually returned when an operation delivers a sequence of elements that may have been:

  • Transformed in some way (O.map())
  • Non-continguous in the source iterable (O.filter())
  • Generated without a source (O.range())
  • Generated infinitely (O.sequence())
  • Sourced from something other than an array or string
for (const i of O.range(100)) {
    console.log(i)
} 

In the above code, O.range() does not create an array, but a pipe that generates the integers from 0 to 99. If we do want an array we can simply call the done() function on the pipe:

O.range(100).done() /* ==> [0, 1, 2, 3, ..., 99] */ 

Because pipes don't have to hold the memory of all elements at once, they can yield elements infinitely.

const increment = (number) => number + 1

O.sequence(0, increment)                /* ==> (0, 1, 2, 3, 4, 5, ...) */
O.sequence(0, increment).take(3).done() /* ==> [0, 1, 2] */ 

Pipe Examples

const numbers = [1, 2, 3, 4, 5]
const object = {
    a: 1, 
    b: 2, 
    c: 3
}

const even = (number) => number % 2 === 0
const increment = (number) => number + 1
const double = (number) => number * 2 
const toString = (element) => element.toString()

O.filter(numbers, even)             /* ==> (0, 2, 4) */

O.map(numbers, toString)            /* ==> ("1", "2", "3", "4", "5") */
O.map(numbers, toString).repeat()   /* ==> ("1", "2", "3", "4", "5", "1", "2", ...) */

O.repeat(numbers)                   /* ==> (1, 2, 3, 4, 5, 1, 2, 3, 4, 5, ...) */
O.repeat(numbers).take(6)           /* ==> (1, 2, 3, 4, 5, 1) */

O.sequence(() => 0)                 /* ==> (0, 0, 0, 0, ...) */
O.sequence(() => 0).take(3).done()  /* ==> [0, 0, 0] */

O.chunk(numbers, 2)  /* ==> ([1, 2], [3, 4], [5]) */

O.permute([1, 2, 3]) /* ==> ([ 0, 1, 2 ],
                             [ 1, 0, 2 ],
                             [ 2, 0, 1 ],
                             [ 0, 2, 1 ],
                             [ 1, 2, 0 ],
                             [ 2, 1, 0 ]) */

O.sequence(0, increment).filter(even)                   /* ==> (0, 2, 4, 6, ...) */
O.sequence(0, increment).filter(even).map(toString)     /* ==> ("0", "2", "4", "6", ...) */

O.sequence(1, double)                                   /* ==> (1, 2, 4, 8, ...) */
O.sequence(1, double).take(3).done()                    /* ==> [1, 2, 4] */

O.pairs(object)                         /* ==> (["a", 1], ["b", 2], ["c", 3]) */
O.pairs(object).map(([key, value]) => { /* ==> (["A", 2], ["B", 3], ["C", 4]) */
    return [O.upper(key), value + 1]
})
O.pairs(object).map(([key, value]) => { /* ==> {"A": 2, "B": 3, "C": 4} */
    return [O.upper(key), value + 1]
}).toObject()

Spans

Spans are omni-sequences that represent a slice of an existing array or string. They are usually more performant than pipes. Spans support O(1) access time rather than O(n) and can be iterated multiple times.

Spans are usually returned when an operation delivers a group of elements that may have been:

  • Drawn from a contiguous section of an array, string or other span (O.slice())
  • Placed into a new array that needed to be allocated anyway (O.takeLast() on a pipe)
const numbers = [1, 2, 3, 4, 5]

O.slice(numbers, 1, 3) /* ==> (2, 3) */ 

In the above code, O.slice() does not allocate a new array, but a span over the source array "numbers" from a start index (1) to a non-inclusive end index (3). Because of this, slice() and slice-like operations are relatively cheap. The slice() function in particular becomes an O(1) operation when used on arrays, strings and other spans.

Once again, if we do want an array we can just call the done() function.

O.slice(numbers, 1, 3).done() /* ==> [2, 3] */ 

Pipe Examples

const numbers = [1, 2, 3, 4, 5]
const lessThanThree = (number) => number < 3
const greaterThanThree = (number) => number > 3

O.slice(numbers, 1, 3)                          /* ==> (2, 3) */ 

O.drop(numbers, 2)                              /* ==> (3, 4, 5) */
O.drop(numbers, lessThanThree)                  /* ==> (3, 4, 5) */

O.take(numbers, 2)                              /* ==> (1, 2) */
O.take(numbers, lessThanThree)                  /* ==> (1, 2) */

O.dropLast(numbers)                             /* ==> (1, 2, 3, 4) */

O.dropLast(numbers, 2)                          /* ==> (1, 2, 3) */
O.takeLast(numbers, 2)                          /* ==> (4, 5) */

O.dropLast(numbers, greaterThanThree)           /* ==> (1, 2, 3) */
O.takeLast(numbers, greaterThanThree)           /* ==> (4, 5) */

On Infinite Sequences

With Omnitool, it's simple to create and use infinite sequences. The following expression generates an infinite sequence of all powers of two:

const double = (number) => number * 2

O.sequence(1, double) /* ==> (1, 2, 4, 8, 16, ...) */

To get a finite sequence or some another value out of an infinite sequence, use a function that limits the bounds.

O.sequence(1, double).take(5).done()                    /* ==> [1, 2, 4, 8, 16] */
O.sequence(1, double).take((current) => x <= 16).done() /* ==> [1, 2, 4, 8, 16] */
O.sequence(1, double).at(3)                             /* ==> 8 */

Be careful with infinite sequences. It's rather easy to get into infinite loops.

const increment = (number) => number + 1

O.sequence(0, increment).done()      /* PROBLEM: Infinitely large array */
O.sequence(0, increment).last()      /* PROBLEM: No last element */
O.sequence(0, increment).lastIndex() /* PROBLEM: No last index */
O.sequence(0, increment).takeLast(3) /* PROBLEM: No last elements to take */