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

point-less

v1.1.0

Published

pointless state interface

Downloads

3

Readme

point-less

installation

yarn add point-less

or

npm i point-less

concepts

lens methods

Lenses are a very deep, abstract concept, but in practice it can still be useful to use only the two most basic lens operations, view and over, on the most basic type of lens, which essentially is just a path.

view(path, object) reads the value of object at the path path.

over(path, updater, object) updates the value of object at the path path, to be whatever the function updater returns. updater receives the current value as an argument. So for example,

const path = ['info', 'count'];
const updater = x => x + 1;
const object = {
  info: {
    name: 'fran',
    count: 7
  }
};

over(path, updater, object); // { info: { name: 'fran', count: 8 } }

The third basic lens method set is a special case of over, where the updater is a constant function. set(path, value, object) will set the value of object at the path path to be value (which is equivalent to over(path, () => value, object)).

path maps

A path is an array of values corresponding to chained property lookups in an object. For example, the path ['name', 'first'] in an object person would correspond to person.name.first.

A pathMap is an object where every value is either a path or a pathMap. The keys of a pathMap are called pathAliases. For example:

const personMap = {
  firstName: ['name', 'first'],
  lastName: ['name', 'last'],
  age: ['age']
};

const familyMap = {
  mom: personMap,
  dad: personMap,
  name: ['name']
};

Here, familyMap would (partially) describe the following object:

const family = {
  mom: {
    name: {
      first: 'Shirley',
      last: 'Schmidt'
    },
    age: 70
  },

  dad: {
    name: {
      first: 'Denny',
      last: 'Crane'
    },
    age: 86
  },

  name: 'Crane'
}

module api

Given a pathMap and lens methods { view, set, over }, this library aims to automate some of the boilerplate inherent in reading and updating state.

pointless(pathMap, lensInterface = vanilla)

Takes a pathMap as input and returns a stateInterface instance. It accepts an optional second argument lensInterface, which describes how data is read and updated. The default lensInterface, vanilla, works on plain JS objects.

enhanceLensInterface(lensPath, view, over)

Generates a lensInterface instance based on the three atomic functions provided. These functions should act in the same way as their ramda namesakes (to generate the vanilla interface, the ramda functions themselves are used).

vanilla

A lensInterface instance used for working with regular JS objects.

immutable

A lensInterface instance used for working with immutable objects (caveat: the way this is implemented is really lazy, but it's enough to cover all functionality provided by the stateInterface API, as of now).

stateInterface api

view, set, over

Each of the three basic lens operations have properties corresponding to the properties of the given pathMap. For example:

const { pointless } = require('point-less');

const pathMap = { firstName: ['name', 'first'] };
const { view, over } = pointless(pathMap);

const brahms = { name: { first: 'johannes'} };

view.firstName(brahms); // 'johannes'

const capitalize = name => name[0].toUpper() + name.slice(1);
over.firstName(capitalize, brahms); // { name: { first: 'Johannes' } }
view[pathAlias](state)

Reads the value of state at the path associated to pathAlias.

set[pathAlias](value, state)

Sets the value of state to value, at the path associated to pathAlias.

over[pathAlias](updater, state, ...extraArgs)

Sets the value of state at the path associated to pathAlias. The new value is computed as updater(state, ...extraArgs).

at(...subpathArgs)

at returns a relativeStateInterface at a path computed from subpathArgs. That is, it uses the same pathMap given to the original state interface, but all paths are implicitly prepended with the extra path given to at. As a result, you can read and update an object even if the pathMap only applies to a subobject. For example:

const pathMap = {
  lastName: ['name', 'last']
};

const data = {
  users: [
    { name: { first: 'debra', last: 'messing' } },
    { name: { first: 'debra', last: 'missing' } }
  ]
};

const substate = pointless(pathMap).at(['users', 1]);

substate.view.lastName(data); // 'missing'

// updates the last name in the context of the full object; `newData` is still
// the same shape as `data`.
const newData = substate.set.lastName('amassing', data);

newData.users[1]; // { name: { first: 'debra', last: 'amassing' } }

A relativeStateInterface is also a stateInterface, so the API of the latter also applies to the former. In addition, a relativeStateInterface also has an all property. This gives a full { view, set, over } interface on the entire object at the subpath. Continuing the above:

const newUser = { name: { first: 'deer', last: 'hunter' } };

const newData = substate.all.set(newUser, data);

newData.users[1] === newUser; // true

The subpath is computed from ...subpathArgs by fully flattening the array subpathArgs. That is, at(['a', 'b', 'c']) is equivalent to at('a', 'b', 'c') and at([[[['a']]], ['b']], 'c').

In addition, any function in the flattened subpath array can be used to dynamically select a path after the state has been received. Any function found in subpath will receive as arguments both state and any other arguments after state. Continuing the above:

const substate = pointless(pathMap).at('users', (_, action) => action.index);

const updateFirst = substate.firstName.over((_, action) => action.firstName);

const newData = updateFirst(data, { index: 1, firstName: 'barbara' });

newData.users[1]; // { name: { first: 'barbara', last: 'massing' } };

overWith(selectors, updater, state)

overWith is a shortcut for a special case of an over call, to facilitate using multiple pieces of the current state to update multiple pieces.

selectors is an array of functions which take the state as input. Whatever they return is passed into the arguments of updater, another user-provided function. Note that the selectors don't need to be simple property reads from a stateInterface; any function that takes the state as input, such as a memoized selector, would work.

The return value of updater must be an object updates, whose keys are pathAliases in pathMap. Each pathAlias will have its corresponding value in state set to updates[pathAlias]. As a somewhat contrived example:

const pathMap = {
  birthDay: ['dob', 'day'],
  birthMonth: ['dob', 'month'],
  birthYear: ['dob', 'year'],
  birthStr: ['birthStr']
};

const { view, overWith } = pointless(pathMap);

const computeBirthStr = overWith(
  [view.birthDay, view.birthMonth, view.birthYear],
  (day, month, year) => ({ birthStr: [month, day, year].join('/') })
);

const data = { dob: { day: 15, month: 5, year: 1992 } };

computeBirthStr(data); // { dob: { ... }, birthStr: '5/15/1992' }

other functionality

functions in paths

A function can be used as an element of a path array, when the path should depend on the current state or otherwise won't be known until a lens operation is called. The motivating case for this was to pick out an index in an array, for example:

const actionUser = ['users', (state, action) => action.index];

const pathMap = {
  actionUser,
  actionUsername: [...actionUser, 'name']
};

const data = {
  users: [{ name: 'momoney' }, { name: 'moproblems' }]
};

const state = pointless(pathMap);

state.view.actionUser(data, { index: 1 }); // { name: 'moproblems' };

const newData = state.over.actionUsername(
  (state, action) => action.newName,
  data,
  { index: 1, newName: 'moped' }
);

newData; // { users: [{ name: 'momoney' }, { name: 'moped' }] }

nested path maps

As stated above, a pathMap can have another pathMap as one of its values. In that case, the pathAlias follows the same chain of properties as in the pathMap:

const userGroupPathMap = {
  count: ['count'],
};

const nestedPathMap = {
  online: userGroupPathMap,
  offline: userGroupPathMap
};

const data = {
  online: { count: 7 },
  offline: { count: 100 }
};

const state = pointless(nestedPathMap);

state.view.online.size(data); // 7
state.view.offline.size(data); // 100