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

wyrm-math

v0.8.1

Published

Exact, conditionally-sound symbolic algebra engine for manipulative interfaces: immutable expression trees, rewrite rules with first-class assumptions, derivation trees, move enumeration, and DOM-free layout geometry.

Readme

wyrm-math

An exact, conditionally-sound symbolic algebra engine for building manipulative math interfaces — the kind where users solve equations by dragging terms across the equals sign, tapping a power to expand it, or pulling a shared factor out of two terms.

The core invariant: legal moves are possible, illegal moves are impossible. Equations are never validated — they are only ever transformed by rewrite rules, so every reachable state is sound by construction. And soundness is conditional: moves that are only valid under a condition (dividing by b requires b ≠ 0) or that can introduce extraneous solutions (multiplying both sides, squaring) are not forbidden — their conditions become first-class, visible Assumptions that travel with the equation.

Pure TypeScript, zero dependencies, zero DOM — runs in Node, browsers, workers, native webviews, anywhere.

wyrm-math is the engine behind Wyrm Math, a gesture-based algebra app for iOS and Android — try the in-browser demo or get the app. The engine is MIT; the app is how the project sustains itself.

import {
  parseEquation, Derivation,
  enumerateMoves, ruleById, layoutNode, exprToString,
} from "wyrm-math";

const d = new Derivation(parseEquation("2x + 3 = 11"));

// What can the user legally do right now?
const moves = enumerateMoves(d.current);

// Drag the 3 across the equals sign (the UI picks a Move; the engine
// guarantees it is legal — enumeration is precondition-checked):
const move = moves.find((m) => m.ruleId === "move-term-across")!;
d.apply(ruleById(move.ruleId), move.location, move.params);

console.log(exprToString(d.current.equation)); // 2x = 11 + -3

// Render it however you like: layoutNode gives positioned, id-keyed boxes
// and glyphs from static metric tables (no font measurement needed).
const layout = layoutNode(d.current.equation);

What's inside

The public API is src/index.ts, organized into ten documented groups — it reads as a table of contents:

| Group | What it gives you | | --- | --- | | Expression trees | Immutable AST with stable node ids. N-ary Sum/Product; no subtraction or division nodes (a − b is Sum(a, Neg(b)); division is a Fraction with numerator/denominator lists). Smart constructors maintain the structural invariants. | | Exact arithmetic | Rational over bigint. No floating point anywhere — √2 is an undefined point, not 1.4142. | | Evaluation | truthValue(equation, env) decides any relation (= < ≤ > ≥) at a sample point, exactly, or returns undefined where a side is undefined. | | Parsing & printing | parseEquation("2x + 3 = 11")exprToString — round-trip property-tested. Implicit multiplication, fractions, powers, radicals; decimals rejected (the engine is exact). | | Judgments & assumptions | The unit of state is { assumptions, equation }. Restrictions (moves that may LOSE solutions: b ≠ 0), Extensions (moves that may GAIN them: carry the original equation as an obligation, settled by checkSolution), Pinned (user what-ifs). Discharged assumptions are recorded, never deleted. | | Rules & derivations | Rule.apply is the only way an equation changes. The derivation log is an append-only tree: undo moves a pointer, abandoned branches stay live, case splits and disjunctions fork into live siblings. | | Built-in rules | ~25 rules covering linear equations, like terms, distribution, fractions, exponent laws, inequalities (sign-aware, relation-flipping), and quadratics (x² = 9 branches to x = ±3; zero-product). Every rule ships with a property test that it respects the solution set under its assumptions. | | Move enumeration | enumerateMoves(judgment) returns every legal affordance with gesture anchors (handle, dropTarget). Sound for all rules, complete for the finite ones. Pin x = 0 and every divide-by-x affordance disappears automatically. | | Layout geometry | layoutNode maps trees to positioned, id-keyed boxes and glyphs (fraction stacking, superscripts, radicals) from static metric tables. hitTest is a geometry query. Subtree geometry is context-independent up to translation+scale — which is what makes id-keyed animation possible. | | Rule-authoring toolkit | Id-preserving rebuilds, the invariant-repairing splice, diff bookkeeping, and assumption-lifecycle queries for writing new rules. |

ARCHITECTURE.md explains the invariants and contracts in depth.

Design commitments

  • Exactness. All arithmetic is bigint rationals. Points where an expression is undefined (division by zero, irrational roots) are treated as undefined, never approximated. The engine-wide soundness contract is truth-where-both-defined.
  • Stable ids. Every node has an id; operations preserve the ids of untouched subtrees. This is the currency of hit testing and animation: a renderer can match nodes across a rewrite and move them rigidly.
  • Conditional soundness. For ordinary and Restriction-emitting rules, property tests rejection-sample substitutions to those satisfying the result judgment's assumptions and assert truth preservation. For Extension-emitting rules the check weakens to one direction (solutions are never lost), with checkSolution covering the gain obligation.
  • Disjunction. Branching rules return several outcomes whose solution sets union to the original's (x² = 9x = 3 or x = −3); the derivation tree holds all arms as live, navigable states.

Development

pnpm install
pnpm test        # vitest + fast-check (property tests are the soul of this project)
pnpm typecheck
pnpm build       # emits dist/ (ESM + d.ts)

The engine must stay DOM-free: tsconfig.json has no DOM lib and test/boundary.test.ts scans the sources for browser globals.

License

MIT