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

@izelnakri/patternmatch

v0.1.2

Published

Pattern matching for JavaScript and TypeScript with rich diff output, designed for assertions and runtime guards. Universal — runs in Node.js, Deno, and the browser.

Readme

patternmatch

Pattern matching for JavaScript and TypeScript — designed for assertions and runtime guards. Universal: Node.js, Deno, browsers. Zero dependencies.

import { match, isMatch, M, formatMatchFailure } from '@izelnakri/patternmatch';

const result = match(user, {
  id: M.string,
  email: /@/,
  age: M.gte(18),
  friends: M.arrayOf({ id: M.string }),
  createdAt: M.date,
});

if (!result.ok) {
  console.error(formatMatchFailure(result));
  // → String does not match /@/ at email
  //     Expected: /@/
  //     Actual:   "not-an-email"
}

Status

Alpha. API will change before 1.0. The intended consumer is qunitx for its assert.match API, but patternmatch is a standalone library with no test-runner dependencies.

Why another pattern-matching library?

The TypeScript ecosystem already has several pattern-matching tools, but each is built around a different goal:

| Library | Goal | Returns | Default object semantics | Diff output | Bundle | | ---------------------------------------------------------------------- | ----------------------------------- | ----------------- | ------------------------ | -------------- | ------ | | ts-pattern | Exhaustive control flow | match(...).with(...).exhaustive() → handler value | strict on objects | none | ~5 kB | | match-iz | TC39-proposal mimic | match(...)(when(...)) → handler value | strict | none | tiny | | @core/match | Structured binding + validation | bound values OR undefined | strict | none | tiny | | Effect/Match | Discriminated unions in Effect | handler value | tag-based | none | (large; part of Effect) | | lodash-match-pattern | JSON API testing | null or error string | strict, opt-out via ... DSL | string | medium | | chai-match-pattern | Wraps lodash-match-pattern for chai | chai assertion | inherited | string | medium | | assert-match | Asserting against matcher trees | throws | strict | basic | tiny | | patternmatch | Assertions + runtime guards | MatchResult ({ ok, path, expected, actual, reason }) | partial-by-default | structured + path | target ≤ 4 kB |

patternmatch is an assertion-first library. The control-flow-first design of ts-pattern is intentionally different — it solves a different problem (exhaustive case analysis on discriminated unions). For test assertions you want:

  1. Partial-by-default object matching (frameworks add fields you don't care about).
  2. Path-aware error reporting (at users[0].email is the difference between a 5-second debug and a 5-minute debug).
  3. A pattern-as-data design where matchers are values you can compose, store, and pass around — no builder API.
  4. A predicate form (isMatch(value, pattern): value is Infer<typeof pattern>) that doubles as a TypeScript type guard.

If you're building a switch-style state machine, reach for ts-pattern. If you're asserting that an arbitrary in-memory object satisfies an ad-hoc shape (testing, validating webhook payloads, gating runtime data), reach for patternmatch.

Quick start

npm install @izelnakri/patternmatch
# or: deno add jsr:@izelnakri/patternmatch
import { match, isMatch, M } from '@izelnakri/patternmatch';

// Boolean form — also a type predicate.
isMatch(value, { id: M.string, email: /@/ });

// Structured form — for diff output.
const result = match(value, { id: M.string });
if (!result.ok) {
  result.path;     // ['id']
  result.expected; // M.string matcher
  result.actual;   // whatever was at .id
  result.reason;   // 'Expected string'
}

Default semantics

| Pattern type | What it matches | | ----------------- | ----------------------------------------------------------------- | | primitive | strict equality (NaN matches NaN — SameValue) | | null/undefined| strict equality (no == coercion) | | RegExp | strings the regex accepts (no coercion of non-strings) | | Date | another Date with the same instant | | function | strict reference equality (use M.when for predicate semantics) | | […] literal | strict tuple — same length, same positions | | { … } literal | partial shape — every pattern key must match; extras allowed | | Matcher | delegates to matcher.test |

Use M.exact, M.tuple, M.arrayOf, M.deepEqual, etc. to override.

Matcher reference

Wildcards     : M.any  M.anything  M.never  M.defined  M.nullish
Type guards   : M.string  M.number  M.boolean  M.bigint  M.symbol
                M.fn  M.array  M.object  M.date
Numeric       : M.gt(n)  M.gte(n)  M.lt(n)  M.lte(n)  M.between(min, max)
Strings       : M.regex(re)  M.startsWith(s)  M.endsWith(s)
Sized         : M.length(n | { min, max })  M.includes(needle)
Class         : M.instanceOf(Class)
Structural    : M.shape({…})  M.exact({…})  M.tuple([…])
                M.arrayOf(p)  M.setOf(p)  M.mapOf(k, v)  M.recordOf(k, v)
Combinators   : M.union(...)  M.oneOf(...)  M.intersection(...)  M.allOf(...)
                M.not(p)  M.optional(p)
Custom        : M.when(predicate, message?)  M.satisfies(predicate)
                M.deepEqual(value)  M.lazy(() => pattern)

Each matcher carries a tag for diagnostics and the [MATCHER_BRAND] symbol — third-party validators (Zod, Valibot, …) can implement the same shape to interoperate without a wrapper.

Cycle handling

Self-referential values are matched co-recursively — once a (value, pattern) pair has been visited it is treated as already matching. This is the standard interpretation for recursive structures and avoids infinite loops without a depth limit.

const a: any = { id: 1 }; a.self = a;
const p: any = { id: 1 }; p.self = p;
isMatch(a, p); // true

TypeScript inference

isMatch(value, pattern) is typed as value is Infer<typeof pattern>, so a true result narrows the value:

declare const v: unknown;
if (isMatch(v, { id: M.string, age: M.gte(18) })) {
  v.id;  // string
  v.age; // number
}

Infer<P> walks the pattern recursively, extracting T from any Matcher<T> it encounters and preserving structural types for plain objects and tuples.

Roadmap

These features are on the path to 1.0 and not yet shipped:

  • M.bind(name, pattern) + M.ref(name) — back-references for asserting cross-field equality (e.g. { users[0].id === orders[0].userId }).
  • M.compile(pattern) — pre-walk a pattern into a closed-over predicate to skip dispatch on hot paths.
  • M.promiseResolves(p) / M.promiseRejects(errorPattern) — async matchers.
  • M.error(spec) — friendlier error matching for assert.throws / assert.rejects.
  • Standard Schema interop — accept any Standard Schema validator wherever a matcher is accepted.
  • LCS-based diff in formatMatchFailure for multi-line object printouts (similar to qunitx's existing assert.deepEqual output).
  • Benchmarks vs ts-pattern, match-iz, jest's expect.objectContaining, lodash deep-equal.

License

MIT © Izel Nakri