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

redux-indexers

v1.1.0

Published

Immutable, indexed data store without fancy container wrapper classes.

Downloads

107

Readme

redux-indexers

A set of functions for producing and dealing with indexes in a referentially safe way! Best with redux and similar non-mutative state modeling.

What does redux-indexers do?

Basically, it keeps data "in order". Like a database index. You can define multi-level indexes and use any computation you want to determine the sorting values. It's also referentially safe -- all modifications to the index produce shallowly copied data.

Why not just .sort()?

.sort() works great for sorting a big set of data once. But suppose you're keeping the same set of data ordered in multiple ways, such as by date created, by name, by id, by it's group_id, etc, etc. And suppose you're going to be adding and removing data constantly.

This can get surprisingly expensive to execute over and over when your list size gets to a thousand or more entries.

redux-indexers has decent performance for loading big sets of data in, but it really shines best when several, small incremental data sets are loaded in over a long time, such as paging over a JSON Api or receiving big sets of data over a websocket.

It also provides some pretty convenient query utilities, allowing you to easily search, slice, and iter based on even multi-leveled key data.

Plus, it's small. And simple. Combine it with your redux stores to create smart little client side databases in no time!

Usage

Suppose we have an object representing a single chat message.

interface ChatMessage {
  id: number
  text: string
  roomId: string
  userId: string
  createdAt: number
}

We might want to keep a collection of chat messages sorted in various ways:

  • By id, so we can find a specific chat message quickly.
  • By userId and createdAt, so we can produce a list of messages in order of creation per user.
  • By roomId, createdAt, and id, so we can produce a list of messages in order of creation per room, and break ties on createdAt with the id.

We'll keep each of these different 'orderings' as separate lists in a containing object.

interface ChatMessageIndexes {
  byId: Index<ChatMessage>,
  byUserIdAndCreatedAt: Index<ChatMessage>,
  byRoomIdCreatedAtAndId: Index<ChatMessage>,
}

To be clear, you can name these properties whatever you want! redux-indexer makes no assumptions about the names of your properties.

Which, speaking of, we now create a 'indexer' object that will define how each of these properties should order the data.

let indexer = new Indexer<ChatMessage>("byId");

The "byId" argument tells our indexer which index is the "primary" index. When updating a value in our indexes, that value is first looked up in the primary index to determine if it should replace some previous, existing value, or add it is a unique, new value.

But so far, we haven't told redux-indexers what each of our indexes is actually ordering. We define that by simply associating a function to each of the index property names.

indexer.addIndex("byId", m => [m.id]);
indexer.addIndex("byUserIdAndCreatedAt", m => [m.userId, m.createdAt]);
indexer.addIndex("byRoomIdCreatedAtAndId", m => [m.roomId, m.createdAt, m.id]);

Notice that we return arrays for our values to index; all keys are multi-level. This works much like multi-column indexes in a standard database, sorting first by elements near the head of the key array, followed by subsorting on each successively further element.

It's important to note that redux-indexer uses Javascript comparison operators to sort each individual element. As a result, it is best to index on numbers, strings, null / undefined, and Infinity only. This covers most cases pretty effectively in practice. For instance, you can sort on Data values by simply calling .getTime().

As an idea for how ordering works, check out this reference:

[2] < [2, null]
[3] > [2, null]

[2, null] < [2, "a"]
[2, "a", null] > [2, "a"]
[2, "a", null] < [2, "b"]

[3] < [4]
[Infinity] > [9999999, 9999999, 999999, "a"]

Now, let's actually create some redux state! Indexer objects themselves do not actually store any state -- they merely contain the logic for managing a group of indexes. But, the indexer provides a handy method to get our initial, empty state!

``typescript let indexes = indexer.empty();


Now let's add some data!

```typescript
indexes = indexer.update(indexes, [
  { id: 1, text: "Hello! 1", roomId: "room-1", userId: "user-1", createdAt: 400},
  { id: 2, text: "Hello! 2", roomId: "room-2", userId: "user-2", createdAt: 200},
  { id: 3, text: "Hello! 3", roomId: "room-2", userId: "user-1", createdAt: 700},
  { id: 4, text: "Hello! 4", roomId: "room-1", userId: "user-2", createdAt: 800}
]);

Note that indexes itself was not modified, but shallowly copied to reflect the new lists. This is the sauce that makes it nice for redux.

But once we put in data, we want to get it back out. How?

let messages = Indexer.getAllMatching(indexes.byRoomIdCreatedAtAndId, ["room-2"]);

Now we've got all the messages in room-2, in order of their createdAt and id! Not bad! Next, if we were to update the values again, our ordering would be updated as well.

indexes = indexer.update(indexes, [
  { id: 1, text: "Hello! 1", roomId: "room-2", userId: "user-1", createdAt: 400},
  { id: 2, text: "Hello! 2", roomId: "room-1", userId: "user-2", createdAt: 200},
]);

Because we defined our primary index to byId, the update will detect the existing values matching id 1 and 2, and replace their entries in each index with the new values.

By the way, we can also get all our messages out, in order by first user, then createdAt, by using the appropriate index, and providing an empty matching key.

let messages = Indexer.getAllMatching(indexes.byUserIdAndCreatedAt, []);

Checkout the index.ts file for more query / modification functions!