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

@obvibase/utils

v10.1.0

Published

TypeScript utilities for those who don't like utilities

Downloads

5

Readme

@obvibase/utils

npm version gzip size gzip size

TypeScript utilities for those who don't like utilities.

Installing

yarn add @obvibase/utils

or

npm install @obvibase/utils --save

Minimal API

Based on "only one way to do it" principle, this library provides a utility only when something can't be easily done with vanilla JavaScript. For example, we do not provide a function to get an object's property value, so instead of get('a') you would just write value => value.a. This is because we see the mental overhead of choosing among multiple ways to write a piece of code as higher cost compared to doing more typing.

Pipeline operator ponyfill

The library includes a function applyPipe which takes between 1 and 12 arguments: applyPipe(x, a, b) is equivalent to b(a(x)), or using the pipeline operator, x |> a |> b. Type inference works well with this function, and if any one of the proposed flavors of the pipeline operator eventually reaches stage 3 and starts to be supported in TypeScript, it would be straightforward to build a codemod to convert the function to the operator.

The library intentionally doesn't include a pipe function that would compose functions without applying the resulting function to an argument, mainly because this would go against "only one way to do it".

Objects, arrays, maps and sets

The library includes non-mutating functions for working with objects, arrays, maps, and sets.

:bulb: If you use TypeScript 4.1+, make sure you enable strictly checked indexed access using --noUncheckedIndexedAccess compiler flag.

Iterables

Functions for working with iterables have signatures that try to stay close to equivalent RxJS operators, but have names like mapIterable that do not clash with RxJS.

How-to:

  • Get an element's index: zipIterables(rangeIterable(), yourIterable) (returns an iterable of [<element index>, <element>]).

  • Get a flag indicating if the element is the first element: zipIterables(firstIterable, yourIterable) (returns an iterable of [boolean, <element>]).

  • Find an element matching a predicate: applyPipe(yourIterable, filter(yourPredicate), firstInIterable).

:bulb: If filtering an iterable changes type of the elements, use flatMapIterable instead of filterIterable: the type of elements in

applyPipe(
  [1, undefined],
  filterIterable((value) => value !== undefined),
);

will be inferred as Iterable<number | undefined>, while for

applyPipe(
  [1, undefined],
  flatMapIterable((value) => (value === undefined ? [] : [value])),
);

it will be inferred as Iterable<number>. The same trick works when filtering arrays and observables.

Comparison functions

The library exports types

type CompareFunction<T> = (to: T, from: T) => number;
type EqualFunction<T> = (from: T, to: T) => boolean;

It provides implementations of CompareFunction for primitive types and a function lexicographicCompare to compose CompareFunctions.

It also provides implementations of EqualFunction for objects, iterables, maps, and sets, and a function deepEqual that recursively delegates to those functions depending on the object type.

Lenses

First let's talk about how we define a lens. When building React components, it's convenient to work with a type which we'll call StateView, a combination of a value and a setter:

type StateView<A> = [value: A, set: (value: A) => void];

Values returned by React's setState hook can be treated as values of this type, and it is also what you would want to pass to an input element such as a textbox to create a two-way binding. In this library we actually define StateView as a subtype of another type called View (you'll soon see why):

type View<S, A> = [value: A, set: (value: A) => S];
type StateView<A> = View<void, A>;

and we define a Lens as a function that transforms a view View<S, A> into another view View<S, B> (it follows that a lens will transform a StateView into another StateView).

To see how this works, we'll write a React component using the following two functions provided by the library:

  • objectProp: a lens which zooms in on an object's property, e.g. objectProp('a') will transform a value of type StateView<{ a: number }> into a value of type StateView<number>.

  • bindingProps: a helper function that converts a StateView into an object with props that React input components understand, e.g. ['x', set] would be transformed into { value: 'x', onChange: ({ currentTarget: { value } }) => set(value) }.

Here's what the component will look like:

type State = { a: string; b?: { c: string } };

/**
 * A component that encapsulates presentation logic but is agnostic as to how we
 * manage state.
 */
const StatelessComponent = ({ stateView }: { stateView: StateView<State> }) => (
  <div>
    {/* An input bound to 'a'. */}
    <input {...applyPipe(stateView, objectProp('a'), bindingProps)} />
    {applyPipe(stateView, objectProp('b'), ([value, set]) =>
      // If 'b' is absent,...
      value === undefined ? (
        // ...a button that adds a default value for 'b',...
        <button onClick={() => set({ c: '' })}>Add 'b'</button>
      ) : (
        // ...otherwise (if 'b' is present), an input bound to 'c'.
        <input
          {...applyPipe([value, set] as const, objectProp('c'), bindingProps)}
        />
      ),
    )}
  </div>
);

export const StatefulComponent = () => {
  const stateView = React.useState<State>({ a: '' });
  return <StatelessComponent {...{ stateView }} />;
};

In the code above, TypeScript successfully infers the types, and as we get to a point where we need to type 'a', 'b', or 'c', IntelliSense shows correct suggestions.

Checkbox is different from other inputs in that we have to use checked prop instead of value, so when binding a checkbox, instead of bindingProps use bindingPropsCheckbox.

In the component example we used objectProp lens to transform a StateView into another StateView, but like other lenses, it also works on StateView's supertype View. Thanks to that, we can use objectProp in the conventional way to immutably set a property nested within a larger structure, as in the following example of a reducer that sets the value of b in { a: { b: string; c: string } }:

type State = { a: { b: string; c: string } };

const sampleReducer = (state: State, action: { payload: string }) =>
  applyPipe(
    [state, (value) => value] as View<State, State>,
    // Transforms values into `View<State, { b: string; c: string }>`.
    objectProp('a'),
    // Transforms values into `View<State, string>`.
    objectProp('b'),
    // `set` takes a value for `b` and returns a new `State`.
    ([, set]) => set(action.payload),
  );

expect(sampleReducer({ a: { b: '', c: '' } }, { payload: 'x' })).toEqual({
  a: { b: 'x', c: '' },
});

There is a simple helper function rootView which converts a value into a view [value, <identity function>] and which we can use to replace the first argument in the applyPipe call above, including the type signature, with just rootView(state).

The only other lens-related utilities that are left to mention are:

  • mapProp: a lens to zoom in on a value stored in a Map.

  • setProp: a lens to zoom in on presence of an element in a Set.

Miscellaneous

  • memoize: a utility to memoize values using WeakMap.

  • assertNever: utility used to typecheck that a conditional has exhausted all possibilities, e.g. if a has type 0 | 1, you could write a === 0 ? 'zero' : a === 1 ? 'one' : assertNever(a).


Contributing guidelines