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

comp-select

v0.0.2

Published

Create transform functions.

Downloads

4

Readme

Comp-Select: Composable Reselect

A re-select enhancer designed to work along-side your existing selectors. It allows document linking and composable transformations written in a declarative manner giving you part of the power of an ORM without all the work.

Goals

  • Allow easy linking between records
  • Allow composable transformations
  • Uses existing re-select api and memoization
  • No strict schemas

Example

State shape

type State = {
  fooHash: {
    [Foo.id]: Foo,
  },
  selectedFooIds: Array<Foo.id>,
  barHash: {
    [Bar.id]: Bar,
  };
}

type Foo = {
  id: FooId,
  links: {
    bars: Array<Bar.id>,
  }
};

type BarHash = {
  [BarId]: Bar,
};

type Bar = {
  id: BarId,
  name: string,
};

Dependency selectors

const getSelectedFooIds = (state) => state.selectedFooIds
const getFooHash = (state) => state.fooHash
const getBarHash = (state) => state.barHash

The new way

const getBarNamesFromFooIds = composableSelector(getSelectedFooIds)
  .populate(getFooHash);
  .populate({
    'links.bars': getBarHash,
  })
  .flatten('links.bars')
  .getEach('name')
  .create();

The old way

const getBarNamesFromFooIds = createSelector(getSelectedFooIds, getFooHash, getBarHash,
  (selectedFooIds, fooHash, barHash) => {
    const selectedFoos = selectedFooIds.map((id) => fooHash[id]);

    const selectedFooWithBars = [...selectedFoos]((foo) => {
      foo.links.bars = foo.links.bars.map((barId) => barHash[barId]);
    });

    const selectedBars = [].concat(
      ...selectedFooWithBars.map((foo) => get(foo, 'links.bars')));

    return selectedBars.map((bar) => bar.name);
  });

API - Transformers

There are three main categories of Transformers

Static Transformers

Takes static values as arguments such as path.

For example, in order to get target value

obj = { outer: { inner: { superInner: 'target value' } } };

You can use get

.get('outer.inner.superInner')

The path used, outer.inner.superInner never changes.

.get(Path): *

  • Given an Object
  • Returns value at the path

.getEach(Path): Array<*>

  • Given an Array of Objects
  • Returns value at the path for each

.flatten(Path): Array<*>

  • Given an Array of Objects with Arrays at the path
  • Returns values at the path for each, flattened

.keys()

.values()

.slice(index, length): Array<*>

Selector Transformers

Selector based transformers work like re-select selectors.

Each takes a list of selectors, ending with a resultFunc Function. resultFunc will receive the results of each selector in order, the final argument will be the previous output.

.select(selectorA, selectorB, (a, b, previousOutput) => {
  // Do something
})

Selector Transformers don't require selectors as arguments. For example:

composableSelector(getItems)
  .filter((item) => item.isValid)

Only the resultFunc is provided, no selectors.

.select(...inputSelectors, resultFunc): *

  • Operates on anything
  • Accepts 0 to n inputSelectors
  • Requires a resultFunc as the last argument
    • resultFunc receives selector outputs as arguments, in order
    • resultFunc receives previous output as the last argument
  • returns any value

.filter(...inputSelectors, resultFunc): Array<*>

  • Operates on an array
  • Accepts 0 to n inputSelectors
  • Requires a resultFunc as the last argument
    • resultFunc receives selector outputs as arguments, in order
    • resultFunc gets called for each item in the previous output
    • resultFunc is expected to return true or false
  • returns any value

.map(...inputSelectors, resultFunc): *

  • Operates on an array
  • Accepts 0 to n inputSelectors
  • Requires a resultFunc as the last argument
    • resultFunc receives selector outputs as arguments, in order
    • resultFunc gets called for each item in the previous output
    • resultFunc is expected to return true or false
  • returns any value

Populating fields

populate is used to populate id fields with records from a hash selector.

  • It operates on arrays of ids or arrays of records.
  • It can be used in three ways.

1. Link from list of ids

.populate(SELECTOR)
  • Operates on a list of ids
  • Finds record on SELECTOR result for each id
  • Returns an array
const getSelectedFoos = composableSelector(getSelectedFooIds)
  .populate(getFooHash);

2. Link single path

.populate(PATH, SELECTOR)
  • Operates on a list of records
  • Finds record on SELECTOR result for each id at PATH on each passed record
  • Returns an array with records attached
const getSelectedFoosWithBars = composableSelector(getSelectedFoos)
  .populate('links.bars', getBarHash);

3. Link many paths

.populate({ [PATH]: SELECTOR })
  • Operates on a list of records
  • Finds record on SELECTOR result for each id at PATH on each passed record
  • Returns an array with records attached
const getSelectedFoosWithBarsAndBaz = composableSelector(getSelectedFoos)
  .populate({
    'links.bars': getBarHash,
    'links.baz': getBazHash,
  });

More?

  • What other transformers are useful?
  • Allow registering custom transformers

Registering custom transformers

It is easy to add new, or custom transformers. As transformers become universal, they may be added to the standard transformers. Otherwise, they'll

registerTransformers({
  filter: {
    type: SELECTOR,
    fn: (task) => (args) => {
      const { deps, last } = splitDepsLast(args);
      return last.filter((item) => task.resultFunc(...deps, item));
    }
  }
});

Examples

getConversationsByThread

The new way

export const getConversationsByThread = composableSelector(getSelectedThreads)
  .populate('links.conversations', getConversations)
  .flatten('links.conversations')
  .create();

The old way

export const getConversationsByThread = createSelector(
  getConversations,
  getThreadIdsSelected,
  (conversations = {}, threadIds = []) => {
    const values = Object.keys(conversations)
      .map((key) => conversations[key]);

    const initialValue = [];
    return threadIds.reduce((prev, id) =>
      [...prev, ...deepFilter(values, 'links.shared_thread', id)],
      initialValue
    );
  }
);

getMessagesByThreadIds

The new way

Note how filter is formatted like a selector. Transformers which take functions have the option of including additional selector dependencies which will be used only in that function.

const getMessagesByThreadIds = composableSelector(getMessages)
  .values()
  .filter(
    (_, props) => props.threadIds,
    (ids, message) => ids.some((id) => id === get(message, 'links.thread'))
  )
  .create();

The old way

export const getMessagesByThreadIds = createSelector(
  getMessages,
  (_, props) => props.threadIds,
  (messages, threadIds) => filterMessagesByThreadIds(messages, threadIds)
);

export function filterMessagesByThreadIds(messages: MessagesHash, threadIds: ThreadIds) {
  if (!threadIds) return [];
  return Object
    .values(messages)
    .filter((message) => {
      if (!message.links || !message.links.thread) return false;
      const linkedThreadId = message.links.thread;
      return threadIds.some((threadId) => threadId === linkedThreadId);
    });
}

getMessagesByThreadSelected

The new way

export const getMessagesByThreadSelected = composableSelector(getThreadIdsSelected)
  .slice(0, 1)
  .populate(getThreadsHash)
  .populate('links.messages', getMessages)
  .flatten('links.messages')
  .fallback([])
  .create();

The old way

export const getMessagesByThreadSelected = createSelector(
  getThreadIdsSelected,
  getMessages,
  getThreadsHash,
  (threadIds = [], messagesHash = {}, threadsHash = {}) => {
    if (threadIds.length === 0 || threadIds.length > 1) return [];
    const selectedThread = threadsHash[threadIds[0]];
    if (!selectedThread) return [];
    const threadMessages = get(selectedThread, 'links.messages', []);
    const messages = threadMessages.map((id) => messagesHash[id]);
    return messages.filter((message) => message !== undefined);
  }
);

Challenges

  • Error handling / Debugging
  • Testing

DEPRECATED

The original api concept which led to comp-select

const getThreadMessages = createStrictSelector({
  transformer: flattenLinks,
  sources: getSelectedThreads,
  dependencies: {
    'links.messages': getMessageHash,
  },
  fallbackValue: [],
});