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

keyed-set

v0.6.2

Published

Like Set() but with configurable equality, and it emits changes

Downloads

23

Readme

KeyedSet( )

NPM version Coverage Status

Like Set() but with configurable equality, and it emits changes

Motivation

Sometimes you want a Set( ) where "equivalent" members are treated as if they were equal, with at most one of them being in the Set, standing in for all of them. Equivalent objects might be ones with the same serialization, for example, or perhaps objects with the same value of an .id property.

Also, sometimes you want to listen for changes on a Set( ). While that's conceptually independent, it's much easier to provide both features in the same code, so KeyedSet does that, too.

This was created for webdup.

Overview

A KeyedSet() is very similar to a Set(), except:

  • When you add(), it wont do anything if there's already an element in the set with the same key.

  • When you delete(), it looks for an element in the set with the same key (there can't be more than one), and removes that.

  • When the set changes, events are emited

In general, it computes the key as needed for each item using the keyFunction you provide (or JSON.stringify by default). If you happen to already have the key computed, you can pass it in, to save some work. The key returned by keyFunction should be a string.

Not surprisingly, the current implementation is a Map() where the key for a value is what keyFunction returned for that value. So you can also think of a KeyedSet as a very constrained kind of map.

Examples

This uses the default keyFunction (JSON.stringify):

const KeyedSet = require('keyed-set')

const s = new KeyedSet()
s.on('change', event => { console.log(event) })

s.add({a: 1})
// => { type: 'add', key: '{"a":1}', item: { a: 1 } }

s.add({a: 1})
// no output, an equivalent object was already in the set

This uses the "id" property of each object as its key:

const KeyedSet = require('keyed-set')

const s = new KeyedSet(item => item.id)
s.on('change', event => { console.log(event) })

s.add({a: 1, id: 1000})
// => { type: 'add', key: 1000, item: { a: 1, id: 1000 } }

s.get(1000)
// => { a: 1, id: 1000 }

s.add({b: 2, id: 1000})
// no output, since an "equivalent" object was already in the set,
// nothing gets added now

s.deleteKey(1000)
// => { type: 'delete', key: 1000, item: { a: 1, id: 1000 } }

API

The API for KeyedSet is the same as the standard JavaScript API for Set, except:

  • The KeyedSet constructor takes an optional additional parameter, the keyFunction. This function takes an element of the set and returns a string to use as the equivalence key for that element.
  • It's an EventEmitter, with set.on/off/once, providing "change" events. The event objects passed to the event handler looks like one of these:
    • { type: 'add', key: ..., item: ... },
    • { type: 'delete', key: ..., item: ... },
    • { type: 'clear' }
  • set.clone() makes a copy with the same data and keyFunction; set.cloneEmpty() makes a copy with the same keyFunction but no data.

For performance, if the caller already has the key computed, there are some additional methods:

  • set.addKey(key, value)
  • set.deleteKey(key)
  • set.hasKey(key)
  • set.get(key) (here it's really acting as a map)

For performance, when a KeyedSet is doing an operation against another Set, if the other set has a .keyMap and a .keyFunction, they are read assuming they have the same semantics as in KeyedSet.

There are also some convenience functions, with reasonably efficient implementations:

  • setA.minus(setB) returns a new KeyedSet containing only those element in KeyedSet setA but not in KeyedSet setB. Behavior is unspecified if setA and setB have different keyFunction.

  • setA.diff(setB) returns a patch, an iterable of events that would be needed to turn setA into setB.

  • set.change(event) applies the change described in the event to this set

  • setA.changeAll(patch) applies a sequence of changes. setA.changeAll(setA.diff(setB)) would leave setA having the same members as setB. (Of course, so would setA.clear(); setA.addAll(setB), but presumably there are times you want to minimize the changes.)

SmartPatch

The helper class KeyedSet.SmartPatch acts like an array of change events, but it "cancels-out" events that would have no effect when combined. It can be used to record change events and then replay them more efficiently, especially in cases where there are rapid temporary changes which do not need to be propagated.

const KeyedSet = require('keyed-set')

const s = new KeyedSet()
s.addAll([1,2,3])  // before listening

const p = s.mark() // creates a SmartPatch watching s

s.addAll([4,5,6])
p.length  // => 3
s.delete(6)
p.length  // => 2  It cancelled the add
s.delete(1)
s.delete(2) 
p.length  // => 4  Need to remember these deletes
s.add(1)
p.length  // => 3  Well, only one of them
s.clear()
p.length  // => 1  No need to remember the adds at all

p.close() // stop watching s

Events can be de-queued using p.shift(). New events can safely be added between calls to p.shift(), which returns undefined whenever there are no queued events. Event order is retained, although theory it shouldn't matter.