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

ramda-lens-groups

v2.1.0

Published

An easy way to manage a set of ramda lenses focused on props of a particular object type

Downloads

5

Readme

ramda-lens-groups

Full ramda-lens-group API documentation is here

ramda-lens-groups provides a set of utilities meant to help manage the complexity that can come along with creating lenses for objects with large sets of properties and nested objects. The ramda-lens-groups implementation relies on ramda lenses and utilities, and also makes heavy use of ramda-adjunct.

If you want to see why lenses are so handy, have a look here

A lens group is simply a collection of lenses that, as a whole, reference a set of properties associated with an object 'type'. Each lens in the group is focused on a particular property.

Conceptually, a lens group can be visualized like this:

const cat = {       // catLensGroup
  id:    1001,      //  <---- idLens
  name:  'fuzzball',//  <---- nameLens
  color: 'black',   //  <---- colorLens
  mood:  'aloof'    //  <---- moodLens
}

Creating A Lens Group

Lens groups are created by supplying a list of property names, optional defaults for those properties, and an optional path to the object in the case of nesting

import LG from 'ramda-lens-groups';

const catLg = LG.create (
  ['id', 'name',    'color',    'mood' ],   // prop names
  [-1,   'defName', 'defColor', 'defMood' ] // defaults
);

const catInMyLifeLg = LG.create (
  ['id', 'name',    'color',    'mood' ],    // prop names
  [-1,   'defName', 'defColor', 'defMood' ], // defaults
  [ 'pets', 'myCat']                         // path
);

Operating on individual properties of an object

const myCat = { name: 'sunshine', color: 'orange' };

const catLg = LG.create (
  ['id', 'name',    'color',    'mood' ],   // prop names
  [-1,   'defName', 'defColor', 'defMood' ] // defaults
);

LG.view(catLg, 'name', myCat); //=> 'sunshine'
LG.view(catLg, 'color', myCat); //=> 'orange'
LG.view(catLg, 'mood', myCat); //=> 'undefined'
LG.viewOr(catLg, 'mood', 'confused', myCat ); // 'confused'
LG.viewOrDef(catLg, 'mood', myCat); //=> 'defMood'

const moodyCat = LG.set(catLg, 'mood', 'grumpy', myCat);
LG.view(catLg, 'mood', moodyCat); //=> 'grumpy'
LG.viewOr(catLg, 'mood', 'confused', moodyCat ); //=> 'grumpy'
LG.viewOrDef(catLg, 'mood', moodyCat); //=> 'grumpy'
LG.view(catLg, 'mood', myCat); //=> undefined

Operating on objects nested within other objects

const myCat = { name: 'sunshine', color: 'orange' };
const myLife = { pets : { myCat }};

const catInMyLifeLg = LG.create (
  ['id', 'name',    'color',    'mood' ],    // prop names
  [-1,   'defName', 'defColor', 'defMood' ], // defaults
  [ 'pets', 'myCat']                         // path
);

LG.viewOrDef(catInMyLifeLg, 'name', myLife); //=> 'sunshine'
LG.viewOrDef(catInMyLifeLg, 'mood', myLife); //=> 'defMood'

const myMoodyLife = LG.set(catInMyLifeLg, 'mood', 'grumpy', myLife);
LG.viewOrDef(catInMyLifeLg, 'mood', myLife); //=> 'defMood'
LG.viewOrDef(catInMyLifeLg, 'mood', myMoodyLife); //=> 'grumpy'

Viewing the lens group target

A lens group can be used to extract the entire target from within a nested object.

const catInMyLifeLg = LG.create (
  ['id', 'name',    'color',    'mood' ],    // prop names
  [-1,   'defName', 'defColor', 'defMood' ], // defaults
  [ 'pets', 'myCat']                         // path
);
const myLife = { pets : { myCat }};
const myMoodyLife = LG.set(catInMyLifeLg, 'mood', 'grumpy', myLife);

LG.viewTarget(catInMyLifeLg, myLife); //=> { name: 'sunshine', color: 'orange' }
LG.viewTarget(catInMyLifeLg, myMoodyLife); //=> { name: 'sunshine', color: 'orange', mood: 'grumpy' }

Creating and cloning objects using lens groups

const myCat = { name: 'sunshine', color: 'orange' };
const myLife = { pets : { myCat }};

const catLg = LG.create (
  ['id', 'name',    'color',    'mood' ],   // prop names
  [-1,   'defName', 'defColor', 'defMood' ] // defaults
);

const catInMyLifeLg = LG.create (
  ['id', 'name',    'color',    'mood' ],    // prop names
  [-1,   'defName', 'defColor', 'defMood' ], // defaults
  [ 'pets', 'myCat']                         // path
);

LG.def(catLg); //=> { id: -1, name: 'defName', color: 'defColor', mood: 'defMood' }
LG.clone(catLg,myCat); //=> { name: 'sunshine', color: 'orange' }
LG.cloneWithDef(catLg,myCat); //=> { id: -1, name: 'sunshine', color: 'orange', mood: 'defMood' }
LG.clone(catInMyLifeLg,myLife); //=> { name: 'sunshine', color: 'orange' }

Specializing lens groups

A new lens group can be created as a specializaiton of an existing lens group

const myCat = { name: 'sunshine', color: 'orange' };

const catLg = LG.create (
  ['id', 'name',    'color',    'mood' ],   // prop names
  [-1,   'defName', 'defColor', 'defMood' ] // defaults
);


const catLgMinus = LG.remove(['id', 'mood'], catLg);
LG.def(catLgMinus); //=> { name: 'defName', color: 'defColor' }
const catLgPlus = LG.add(['weight'], [99], catLg);
LG.def(catLg); //=> { id: -1, name: 'defName', color: 'defColor', mood: 'defMood', weight: 99 }

const catShow = { houseCats: { myCat } };
const myCatInShowLg = LG.prependPath( ['houseCats', 'myCat'], catLg );
LG.viewTarget(myCatInShowLg, catShow);//=> { name: 'sunshine', color: 'orange' }

Creating your own custom functions

Lens group operators are curried, so that you can create your own custom functions

const myCat = { name: 'sunshine', color: 'orange' };
const catLg = LG.create (
  ['id', 'name',    'color',    'mood' ],   // prop names
  [-1,   'defName', 'defColor', 'defMood' ] // defaults
);

const viewCat = LG.view(catLg);
const viewCatOrDef = LG.viewOrDef(catLg);
const viewCatName = LG.view(catLg, 'name');

viewCat('color', myCat); //=> orange
viewCatOrDef('mood', myCat); //=> defMood
viewCatName(myCat); //=> sunshine

const cloneCat = LG.clone(catLg);
const cloneCatWithDef = LG.cloneWithDef(catLg);

cloneCat(myCat); //=> { name: 'sunshine', color: 'orange' }
cloneCatWithDef(myCat); //=> { id: -1, name: 'sunshine', color: 'orange', mood: 'defMood' }

Putting it all togehter, a more complex example

const myCat = { name: 'sunshine', color: 'orange' };

const catLg = LG.create (
  ['id', 'name',    'color',    'mood' ],   // prop names
  [-1,   'defName', 'defColor', 'defMood' ] // defaults
);

const secretCatLg = R.compose(
  LG.add(['secretName', 'secretPower', 'secretHandShake'], []),
  LG.remove(['id', 'mood'])
)(catLg);

const mySecretCat = R.compose(
  LG.set(secretCatLg, 'secretName', '009Lives'),
  LG.set(secretCatLg, 'secretPower', 'clawAttack'),
  LG.set(secretCatLg, 'secretHandShake', 'pawPound'),
  LG.cloneWithDef(secretCatLg)
)(myCat);
// =>
// { name: 'sunshine',
//    color: 'orange',
//    secretHandShake: 'pawPound',
//    secretPower: 'clawAttack',
//    secretName: '009Lives' }

const showEntryFormLg = LG.create(['whyParticipating', 'yourCat']);

const blankShowApplication = {
  whyParticipating: 'enter your reason for participating here',
  yourCat: 'put your primped cat here'
};

const showApplicationBeforePrimping = R.compose(
  LG.set(showEntryFormLg, 'whyParticipating', 'I like to show my cat off' ),
  LG.set(showEntryFormLg, 'yourCat', mySecretCat )
)(blankShowApplication);
// =>
// { whyParticipating: 'I like to show my cat off',
//     yourCat: {
//       name: 'sunshine',
//       color: 'orange',
//       secretHandShake: 'pawPound',
//       secretPower: 'clawAttack',
//       secretName: '009Lives' }}

const showCatLg = R.compose(
  LG.appendPath(['yourCat']),
  LG.remove(['secretName', 'secretPower', 'secretHandShake']),
  LG.add(['breed'], ['fancy breed']),
  LG.add(['mood'], ['sociable'])
)(secretCatLg);

const showApplicationAfterPrimping = LG.setTarget(
  showCatLg,
  LG.cloneWithDef(showCatLg, showApplicationBeforePrimping),
  showApplicationBeforePrimping);
//=>
// { whyParticipating: 'I like to show my cat off',
//   yourCat: {
//     name: 'sunshine',
//     color: 'orange',
//     mood: 'sociable',
//     breed: 'fancy breed' } }

const driveToShow = ()=> 'hwy 66, first left after the ocean';
const presentAtShow = LG.viewTarget(showCatLg);

driveToShow();
presentAtShow(showApplicationAfterPrimping);
//=>
// { name: 'sunshine',
//   color: 'orange',
//   mood: 'sociable',
//   breed: 'fancy breed' }

Full ramda-lens-group API documentation is here

Why Lenses

Lenses greatly simplify 2 tasks

  • Handling fault tolerant access to values on nested objects
  • Honoring immutability when setting values within nested object

Fault tolerant property access

Lets have a look at this old friend, a server response which may or may not be complete

const rsp1 = { data : {items: ['i1', 'i2']}}; // expected case
const rsp2 = { data : {items: undefined }};   // oops
const rsp3 = { data : undefined};             // "
const rsp4 = undefined;                       // "

console.log(rsp1.data.items.length); //=> 2
console.log(rsp2.data.items.length); //=> cannot read property 'length' of undefined (doh!)
console.log((rsp2.data.items||[]).length); //=> 0 (klunky)
console.log((rsp3.data.items||[]).length); //=> Cannot read property 'items' of undefined (doh!)
console.log(((rsp3.data||{}).items||[]).length); //=> 0 (super klunky)
console.log(((rsp4.data||{}).items||[]).length); //=> Cannot read property 'data' of undefined
console.log((((rsp4||{}).data||{}).items||[]).length); //=> 0 (works for all cases, but klunky^3)

Using ramda lenses and a ramda-adjunct helper, the klunkiness vanishes. RA.viewOr() which is based on R.view() is smart enough to handle a breakage anywhere in the path to the value that you are after.

const itemsLens = R.lensPath(['data','items']);
var viewItems = RA.viewOr([], itemsLens); // partially applied fxn, works in all cases

console.log((viewItems(rsp1)).length); //=> 2
console.log((viewItems(rsp2)).length); //=> 0
console.log((viewItems(rsp3)).length); //=> 0
console.log((viewItems(rsp4)).length); //=> 0

Honoring immutability when setting object properties

Lets look at the very common case of immutably updating an existing state object

const state = {
  animals : { dogs: 'fido', cat: 'garfield' },
  hobby : 'pets'
};

// The approach below is allot of work and error prone
// As state gets broader and deeper, it can become a bit of a nightmare
const newState = {
  ...state,
  animals: {
    dogs: state.animals.dogs,
    cats: 'tiger'
  },
};

console.log(state); //=> { animals: { dogs: 'fido', cat: 'garfield' }, hobby: 'pets'
console.log(newState); //=> { animals: { dogs: 'fido', cats: 'tiger' }, hobby: 'pets' }

The Ramda lens operation used to set values within nested objects honors immutability. This is very powerful when managing complex states immutably

const state = {
  animals : { dogs: 'fido', cat: 'garfield' },
  hobby : 'pets'
};

const catsLens = R.lensPath(['animals', 'cat'] );

// Very straight forward (compare to the 7 liner in the previous example).
// Does not mutate state!
const newState2 = R.set(catsLens, 'tiger' , state );

console.log(state); //=> { animals: { dogs: 'fido', cat: 'garfield' }, hobby: 'pets' }
console.log(newState2); //=> { animals: { dogs: 'fido', cat: 'tiger' }, hobby: 'pets' }

Full ramda-lens-group API documentation is here