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

aljabr

v0.3.10

Published

A TypeScript library for modeling data with tagged union types (algebraic sum types), consuming them with exhaustive pattern matching, and composing reactive computations with signals

Downloads

1,239

Readme

Al-jabr (الجبر) — the Arabic word that gave us "algebra." Bringing structure to chaos is, as it turns out, an ancient art.

aljabr is a TypeScript library built around one idea: that algebraic data types shouldn't live in isolation. Define your tagged unions once, then compose them — through pattern matching, schema validation, reactive state, and reactive UI — using the same model throughout.

It started as a pattern-matching utility. It grew into a small, coherent standard library: the union-centric toolkit for TypeScript, without a runtime, fibers, or a DI container. Zero dependencies.


What's in the box

aljabr ships independent entry points. Use what you need; ignore what you don't.

| Entry point | What it gives you | | ------------------ | ------------------------------------------------------------------------------------------------------------ | | aljabr | Tagged unions, exhaustive match(), structural patterns, is.* wildcards, select() extraction | | aljabr/prelude | Result, Option, Validation, Signal, Derived, Store, List, DerivedArray, Dispatcher, Scope, Resource, watch, Effect | | aljabr/schema | Type-safe decode/encode pipeline for external data; errors surface as a Validation | | aljabr/signals | SolidJS-style convenience layer over the reactive primitives | | aljabr/ui | Reactive UI layer — JSX, function components, pluggable renderer host | | aljabr/ui/dom | DOM rendering target (domHost) for browser apps | | aljabr/ui/canvas | Retained-mode 2D canvas renderer (createCanvasRenderer, Viewport, canvasHost) for diagramming / dataviz |

See the API Reference below for the full per-module surface, and the Guides for narrative docs.


A taste

A small reactive shape editor — touches unions, exhaustive matching, reactive state, and the UI layer in one go:

/** @jsxImportSource aljabr/ui */
import { union, match, type Union } from "aljabr";
import { Store, Derived } from "aljabr/prelude";
import { createRenderer } from "aljabr/ui";
import { domHost } from "aljabr/ui/dom";

const Shape = union({
    Circle: (id: number, radius: number) => ({ id, radius }),
    Rect:   (id: number, w: number, h: number) => ({ id, w, h }),
});
type Shape = Union<typeof Shape>;

const area = (s: Shape) => match(s, {
    Circle: ({ radius }) => Math.PI * radius ** 2,
    Rect:   ({ w, h })   => w * h,
});

const shapes = Store.create<Shape[]>([Shape.Circle(1, 5), Shape.Rect(2, 3, 4)]);
const total  = Derived.create(() => shapes.reduce((sum, s) => sum + area(s), 0));

const rows = shapes.map(
    s => <li>{area(s).toFixed(2)}</li>,
    { key: s => s.id },
);

const { mount } = createRenderer(domHost);
mount(() =>
    <div>
        <ul>{rows}</ul>
        <p>Total: {() => total.get()?.toFixed(2)}</p>
        <button onClick={() => shapes.push(Shape.Circle(Date.now(), 10))}>
            Add Circle
        </button>
    </div>,
    document.body,
);

Shape is the substrate. match checks exhaustively. Store.create<Shape[]>([...]) returns a List — a reactive root-level array with per-index subscriptions and pathless mutations (push, pop, splice, move, set). .map(fn, { key }) produces a keyed DerivedArray, so the renderer reconciles by id instead of position. Derived.create(...) recomputes the total only when the list changes. The () => total.get()?.toFixed(2) child is a reactive region: only that one text node updates when total changes.


Try the demo

A small todo app lives at public/ — unions, Store, List, the iterator chain, and the DOM renderer wired together end-to-end. It runs against the local source build, so it's also the fastest way to poke at the library while hacking on it.

git clone https://github.com/jasuperior/aljabr.git
cd aljabr
npm install
npm run dev

Then open the URL Vite prints (typically http://localhost:5173).


Motivation

TypeScript discriminated unions are powerful but verbose. You define the type, the discriminant field, the type guards — and then switch statements the compiler can only partially verify. aljabr eliminates the ceremony and tightens the guarantees, then extends the same union model out to error handling, reactive state, schema validation, and UI rendering. You're not buying a pattern-matching utility and then reaching for four other libraries — the composition stays in-model.

What aljabr is not

aljabr's surface area now overlaps with several other libraries. None of them are wrong; they're aimed at different things.

vs. Effect-ts. Effect is a full runtime: fibers, service layer, structured concurrency, mature ecosystem. aljabr has no runtime — it's algebraic data types and the things that compose through them. Reach for Effect when you want the framework; reach for aljabr when you don't.

vs. ts-pattern. ts-pattern is structural pattern matching over arbitrary objects. aljabr's dispatch is tag-first and nominal — better for unions you define yourself, with clean serialization and shared variant behavior. ts-pattern is the better fit for matching over third-party shapes.

vs. React / Solid. aljabr's UI layer has no virtual DOM and no diff cycle; it renders a static tree once and surgically updates only the regions whose signal dependencies change — closer to Solid than React. Unlike either, the renderer host is pluggable: DOM, canvas, SSR, or anything you implement against RendererHost<N, E> are equal peers. Components are plain functions; there are no hooks, no rules-of-hooks, and no registration.

vs. Preact Signals / standalone signal libraries. Signals are the smallest piece of aljabr's reactive system. The prelude also ships Store (per-path subscriptions over structured objects), List / DerivedArray (per-index reactive arrays with keyed reconciliation), Dispatcher (validated transactional state), Scope / Resource (structured cleanup), and watch / Effect (reactive async with retry policies, timeouts, cancellation). If you want signals only, a dedicated signals library is lighter; if you want the reactive substrate to extend to structured state and resource lifetimes, that's what aljabr is for.

vs. Awaitly. Workflow-first vs. ADT-first. Awaitly orients around typed async step composition; aljabr orients around tagged unions, with async and reactive as things that compose through them. Worth reading the author's post on algebraic thinking in TypeScript.


Installation

npm install aljabr
# pnpm add aljabr
# yarn add aljabr

API Reference

Core

  • union() — define a sum type and get variant constructors
  • match() — exhaustive pattern matching engine
  • Trait<R> — declare required payload properties on impl classes
  • pred() — wrap a predicate for use in when() patterns
  • is — type wildcards and combinators
  • select() — mark a pattern field for extraction
  • when() — construct a pattern match arm
  • getTag() — read the variant name from an instance
  • Type utilitiesUnion<T>, FactoryPayload<T>, Variant<Tag, Payload, Impl>

Schema (aljabr/schema)

Signals (aljabr/signals)

UI (aljabr/ui)

Prelude (aljabr/prelude)


Guides