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 🙏

© 2025 – Pkg Stats / Ryan Hefner

lawful-typeclasses

v0.6.0

Published

Property-based testing library. Inspired by principled type classes.

Readme

Lawful Type Classes

lawful-typeclasses is a library designed to provide a way of asserting the behavior of your JavaScript classes.

"Lawful" here refers to a characteristic of principled type classes.

What it does

This library allows you to define two things: classes and instances. Perhaps a bit confusedly, classes are JavaScript objects and instances are JavaScript classes.

We'll be referring to the JavaScript classes that implement the behavior of a type class (and are thus instances of that class) as constructors and to the instances of those JavaScript classes as instance values.

What this library then allows you to do is to check if every constructor follows the rules defined in their classes, so that you are able to modularize your tests in a neat way.

Classes

A class is what defines the behavior that you want your instances to conform to.

For example, let's say that you want to define a class of things that can be added:

// We use a builder because it allows for type inferences that would not be
// possible with simple parameters.

// The class is named "Addable". this will be used in error messages.
const addable = new ClassBuilder('Addable')
  // In this example, we are using TypeScript, so we need to specify the type
  // of the objects we want to test. This should be one or more interfaces, for
  // composability. If, and only if you are not using static typing, this may
  // be omitted.
  .withType<Addable & Eq>()
  // Next, we define the properties we expect our instances to have.
  // We'll start out by using the `all` function to say that, in order to
  // be an Addable, the constructor must obey all of the following laws
  // (not just any).
  .withLaws(
    all(
      // Using named functions is not necessary, but it helps to improve error
      // messages.
      // Each parameter to an `obey` function will be a value generated by the
      // Generator, which we will go over shortly. Your function may ask for as
      // many as it wants, and the system will take care of providing them.
      obey(function commutativity(x, y) {
        // `x` and `y` will have type `Addable & Eq`, as we defined above.
        const a = x.add(y)
        const b = y.add(x)

        return a.equals(b)
      }),
      obey(function associativity(x, y, z) {
        const a = x.add(y.add(z))
        const b = x.add(y).add(z)

        return a.equals(b)
      }),
    ),
  )
  .build()

But, as you might have seen, we also expect our instances to implement an #equals method.

We could make use of another class:

const eq = new ClassBuilder('Eq')
  .withType<Eq>()
  .withLaws(
    obey(function reflexivity(x) {
      return x.equals(x)
    }),
  )
  .build()

And then the Addable class may extend Eq, meaning that, in order to be an instance of Addable, the constructor must also be an instance of Eq:

const addable = new ClassBuilder('Addable')
  // We don't need to specify the types of parent classes. They will be inferred.
  .withType<Addable>()
  .withParents(eq)
  // Laws will expect `Addable & Eq` just the same.
  .withLaws(/* ... */)
  .build()

Instances

Instances are JavaScript sets of values that behave according to some (type) class.

Let's start with the following:

class Number {
  constructor(public readonly n: number) {}

  equals(other: Number): boolean {
    return this.n === other.n
  }

  add(other: Number): Number {
    return new Number(this.n + other.n)
  }
}

In order to declare it as an instance of something, you must provide a way of generating values from it. These are the values that will be used for testing. (See How it works)

There are two ways of doing that:

// You may ask for as many parameters as you want, and to each one will be
// assigned a random number between 0 and 1 (inclusive).
// From these numbers you may generate an instance of your constructor.
// The name is used for error reporting.
const gen = continuous('Number', (n) => new Number(n))

// Note that, to increase the likelihood of catching edge cases, sometimes the
// generated numbers will be all 0s or 1s.
// Testing values will be sampled from the given array.
const gen = discrete('Number', [new Number(0), new Number(1), new Number(2)])

// This method would be more useful if we had a finite number of possible
// values, which is not the case.

And then you only need to call instance with the correct parameters and the validators will run. You should call this at some point in your tests.

// will throw an Error if it fails
instance(addable, gen)

Additionally, you may specify how many times each law will be tested (The default is 15 times):

instance(addable, gen, { sampleSize: 10 })

When instance is called, a sample of random instance values will be created using your provided generator, and each class property will be tested using those. If any of the laws fails to be asserted, an error is thrown, and you may be sure that the constructor in question is not an instance of the class you declared.

In case it passes, you may have a high confidence that it is.