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

union-builder

v0.6.0

Published

A library for building and using union data types(a.k.a algebraic data types) in JS

Downloads

7

Readme

union-builder

A library for building and using union data types(a.k.a algebraic data types) in JS

why

after using similar libraries such as union-type in my flux/redux stores, I found that I often needed something that could do just a little more.

This library is designed with the following goals:

  • be able to, when given bad input values, produce higher quality human readable error messages with more context
  • to have a large built in set of validators, but also be support any user supplied validator with the same level of quality and contextual errors
  • to be able to provide default values, instead of just throw an error
  • to be able to check union types:
    • check if 'ANY' type of union
    • check if 'ONE OF' a specific set of unions
    • check if 'A' specific subtype

Creating a union type factory

To create a union type in union-builder, we must first create a union factory

function isNumber(n) { 
	return typeof n === 'number'; 
}
var Points = Type({
	Point: [isNumber, isNumber], //using ordered arguments
	PointXY: {x: isNumber, y: isNumber} //using named arguments
});

//which can then be used to create union instances
var point = new Points.Point(2, 4)
var point2 = Points.PointXY({y: 4, x: 2});

values can be retrieved from a union instance as follows:

//for record types, instances just use the prop name
point2.x === 2;
point2.y === 4;

point[0] === 2
point[1] === 4
//or

//for ordered arguments, use iterator protocol
for (let value of point) {
	//first iteration
	value === 2;
}
//Using the destructuring assignment in ECMAScript 6 it is possible to
//concisely extract all fields of a type.
var [x, y] = point;
var { x, y } = point2; 

Validators

Union Factories accept any function that returns a boolean. Additionally, union-builder will recognize JS primitives and automatically validate them

function isAny() {
	return true;
}

var Point = Type({
	Any: [isAny],           // use your own custom validator
	Point: [Number, Number],// js primitives are detected
	LazyPoint: [Promise]    // even advanced types are recognized
});
// Unions can also be used recursively
var Shape = Type({
	//using the built in type or subtype helper
	Rectangle: [Point.isType, Point.Point.isType],
	//or using the subtype directly
    Circle: [Number, Point]
});

there's support for the following primitives

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Function
  • RegExp
  • Date
  • Promise
  • Error

validators can also provide default values

function myValidator(propValue, propName, instanceName, instanceObj) {
	//propValue === null
	//propName === "x"
	//instanceName === "Type"
	if (!propValue) {
		instanceObj[propName] = 0;
	}
	
	//additionally, very precise error messages can be thrown
	if (propValue === Infinity) {
		throw new TypeError(`${instanceName} expected an integer for property ${propName}, but got: ${propValue}`)
	}
}

var Record = Union({
	WithX: {x: myValidator},
	WithY: {x:myValidator, y:Number}
});

var rec = new Record.WithX({y: 10 });
rec.x === 0;
rec.y === 10;

Usage in flux actions

detecting type

var Action = Union({
	Create: [],
	Delete: []
});

function reducer(state, action) {
	//detect if this is a union type we know how to handle 
	if (!Action.isType(action))
		return state;

	//detect if this is a specific subtype
	if (Action.Create.isType(action))
		return addItem(state);

	if (Action.Delete.isType(action))
    		return removeItem(state);
}

// it is also possible to perform high level checks
function reducer(state, action) {
	if(!Union.isUnionType(action)) {
		throw new TypeError("conventions require that all actions are a type of Union")
	}
}

using case switch

var Actions = Union({
	Update: {x: Number, y: Number},
	Create: [isAny],
	Delete: [isAny]
});

//case takes 2 arguments, but it also curried. so we can start with only 1
const updateState = Actions.case({
    Update: ({x, y}) => Object.assign(state, {x, y}),
    Create: () => ({x:0, y:0}),
    Delete: () => ({})
})

function reducer(state, action) {
	return updateState(action);
}

//wildcard or default fallbacks are also supported
function reducer(state, action) {
    return Actions.case({
        Update: ({x, y}) => Object.assign(state, {x, y}),
        _: () => state,
        // or
        "*": () => state
    }, action);
}

Instance methods

It is also possible to add shared methods to the instances. A Maybe type with a map function could thus be defined as follows:

var isAny = () => true;
var Maybe = Type({Just: [isAny], Nothing: []});
Maybe.prototype.map = function(fn) {
  return Maybe.case({
    Nothing: () => Maybe.Nothing(),
    Just: (v) => Maybe.Just(fn(v))
  }, this);
};

var just = Maybe.Just(1);
var nothing = Maybe.Nothing();
nothing.map(add(1)); // => Nothing
just.map(add(1)); // => Just(2)

Recursive union types

It is possible to define recursive union types. In the example below, List is being used in it's own definition, thus it is still undefined when being passed to Type. Therefore Type interprets undefined as being a recursive invocation of the type currently being defined.

var List = Type({Nil: [], Cons: [R.T, List]});

We can write a function that recursively prints the content of our cons list.

var toString = List.case({
  Cons: (head, tail) => head + ' : ' + toString(tail),
  Nil: () => 'Nil',
});

var list = List.Cons(1, List.Cons(2, List.Cons(3, List.Nil())));
console.log(toString(list)); // => '1 : 2 : 3 : Nil'