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 🙏

© 2026 – Pkg Stats / Ryan Hefner

vellyr

v0.1.0

Published

Small signal library

Readme

Vellyr

This is a small signal library with fairly minimal API surface. The signals are not lazy, so all derived signals will be automatically re-computed on any sources change.

To mitigate that, you can pass a custom comparator when creating/deriving a new signal. Here is an example of how you can use it:

import { vellyr } from "vellyr";

const state$ = vellyr({
  items: [
    { active: true, name: "first" },
    { active: false, name: "second" },
  ],
});

const filteredIds$ = state$.map((state) =>
  state.items
    .filter((item) => item.active)
    .map((item) => item.id),
  { equality: shallowEqual }
);

filteredIds$.on(ids => {
    console.log(`active ids: ${ids}`)
})

Creating signals

  • vellyr(initialValue)
  • vellyr(initialValue, { equality: (value1, value2) => value1.id === value2.id })
  • vellyr.only(value)
  • vellyr(vellyr.empty)

By default, you probably want to provide the first value, and you can also provide the equality function. You can create a signal which cannot change its value (it simply does not have set() or update() calls), but can be used to derive values. Finally, you can manually provide an empty value, which will stop derived signals like .combine() from being triggered before you set the first value.

Reading and writing values

Signals provide straightforward imperative API to interact with the current value and to update it.

  • signal$.get()
  • signal$.getError() (more on the errors later)
  • signal$.set(value)
  • signal$.update(currentValue => currentValue + 1)

Deriving signals

The power of signals (or any observable primitive for that matter) comes from their composable nature. This library aims for a very small set of available methods to derive new signals from existing ones, but it should be enough for common use-cases and extending them.

  • signal$.map(value => value * 2)
  • signal$.map(item => sanitizeItem(item), { equality: equalityFn })

Map allows you to transform an existing signal into something else, which is very useful. You can provide a custom equality function, and the signal you are deriving from can have its own equality function as well.

  • clicks$.combine(item$, request$).map(([click, item, request]) => ...)

Combining multiple signals simply creates a signal with each value as a tuple. You cannot provide a custom equality function to this method (if you really need one, you can map the result with the identity and provide an equality function at this point).

  • values$.filter(value => value > 5)

Signal which will only receive a new value if the filtered function returns true. You can provide the equality function here as well.

  • const sum$ = values$.scan((acc, value) => acc + value, 0)

To achieve reduce functionality, use .scan() to store the accumulated value. You can also provide the custom equality function as the last argument.

Subscribing

To actually listen to value changes, there is .on() method, which comes in 2 flavours:

  • const unsub = value$.on((value) => { ... })
  • const unsub = value$.on({ next: ( value) => { ... }, error: e => { ... } })

They are equivalent, but the second form is the only way to subscribe for errors. The errors are propagated down, so an error on a parent signal will propagate down the children.

Disposing

To clean up parent/children dependencies correctly, you need to call signal$.dispose() when the signal is not relevant anymore. This is needed because parents/children need special tracking internally so that combining multiple signals with the same parent will only be updated once.