# point-less

> pointless state interface

Latest version **1.1.0** (published 2017-05-26) · ISC license · 0 weekly downloads

## Install

```sh
npm install point-less
pnpm add point-less
yarn add point-less
bun add point-less
```

## Health

**Score 15/100 (F)** — status: abandoned.

Positive: no vulnerabilities.

Warnings: low downloads; no types; no esm support.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 1.1.0 |
| Published | 2017-05-26 |
| First published | 2017-04-08 |
| Weekly downloads | 0 |
| License | ISC |
| TypeScript types | none |
| Module format | CommonJS |
| Dependencies | 2 |
| Known vulnerabilities | 0 (+2 in 1 direct dependencies) |
| Install scripts | no |
| Author | Nick Fireman |
| Maintainers | graftss |

## Links

- npm: https://www.npmjs.com/package/point-less
- npm.io page: https://npm.io/package/point-less

## Dependencies (2)

- [ramda](https://npm.io/package/ramda.md) ^0.23.0
- [immutable](https://npm.io/package/immutable.md) ^3.8.1

## Recent versions

- 1.1.0 (latest) — 2017-05-26
- 1.0.0 — 2017-05-19
- 0.2.2 — 2017-05-17
- 0.2.1 — 2017-05-16
- 0.2.0 — 2017-04-14
- 0.1.0 — 2017-04-08

## README

# point-less

## installation

```bash
yarn add point-less
```

or

```bash
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,

```javascript
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 `pathAlias`es.
For example:

```javascript
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:

```javascript
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:

```javascript
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:

```javascript
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:

```javascript
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:

```javascript
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
`pathAlias`es in `pathMap`. Each `pathAlias` will have its corresponding value
in `state` set to `updates[pathAlias]`. As a somewhat contrived example:

```javascript
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 `path`s

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:

```javascript
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`:

```javascript
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
```

---
_Source: https://npm.io/package/point-less · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
