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

ts-pattern-match

v0.2.0

Published

Pattern Matching for TypeScript.

Downloads

5

Readme

Pattern Matching for TypeScript

Extracted from the effect library.

Installation

npm install ts-pattern-match

Pattern matching is a method that allows developers to handle intricate conditions within a single, concise expression. It simplifies code, making it more concise and easier to understand. Additionally, it includes a process called exhaustiveness checking, which helps to ensure that no possible case has been overlooked.

Originating from functional programming languages, pattern matching stands as a powerful technique for code branching. It often offers a more potent and less verbose solution compared to imperative alternatives such as if/else or switch statements, particularly when dealing with complex conditions.

Although not yet a native feature in JavaScript, there's an ongoing tc39 proposal in its early stages to introduce pattern matching to JavaScript. However, this proposal is at stage 1 and might take several years to be implemented. Nonetheless, developers can implement pattern matching in their codebase. The ts-pattern-match module provides a reliable, type-safe pattern matching implementation that is available for immediate use.

Example (Handling Different Data Types with Pattern Matching)

import { Match } from "ts-pattern-match";

// Simulated dynamic input that can be a string or a number
const input: string | number = "some input";

//      ┌─── string
//      ▼
const result = Match.value(input).pipe(
  // Match if the value is a number
  Match.when(Match.number, (n) => `number: ${n}`),
  // Match if the value is a string
  Match.when(Match.string, (s) => `string: ${s}`),
  // Ensure all possible cases are covered
  Match.exhaustive,
);

console.log(result);
// Output: "string: some input"

How Pattern Matching Works

Pattern matching follows a structured process:

  1. Creating a matcher. Define a Matcher that operates on either a specific type or value.

  2. Defining patterns. Use combinators such as Match.when, Match.not, and Match.tag to specify matching conditions.

  3. Completing the match. Apply a finalizer such as Match.exhaustive, Match.orElse, or Match.option to determine how unmatched cases should be handled.

Creating a matcher

You can create a Matcher using either:

  • Match.type<T>(): Matches against a specific type.
  • Match.value(value): Matches against a specific value.

Matching by Type

The Match.type constructor defines a Matcher that operates on a specific type. Once created, you can use patterns like Match.when to define conditions for handling different cases.

Example (Matching Numbers and Strings)

import { Match } from "ts-pattern-match";

// Create a matcher for values that are either strings or numbers
//
//      ┌─── (u: string | number) => string
//      ▼
const match = Match.type<string | number>().pipe(
  // Match when the value is a number
  Match.when(Match.number, (n) => `number: ${n}`),
  // Match when the value is a string
  Match.when(Match.string, (s) => `string: ${s}`),
  // Ensure all possible cases are handled
  Match.exhaustive,
);

console.log(match(0));
// Output: "number: 0"

console.log(match("hello"));
// Output: "string: hello"

Matching by Value

Instead of creating a matcher for a type, you can define one directly from a specific value using Match.value.

Example (Matching an Object by Property)

import { Match } from "ts-pattern-match";

const input = { name: "John", age: 30 };

// Create a matcher for the specific object
const result = Match.value(input).pipe(
  // Match when the 'name' property is "John"
  Match.when(
    { name: "John" },
    (user) => `${user.name} is ${user.age} years old`,
  ),
  // Provide a fallback if no match is found
  Match.orElse(() => "Oh, not John"),
);

console.log(result);
// Output: "John is 30 years old"

Enforcing a Return Type

You can use Match.withReturnType<T>() to ensure that all branches return a specific type.

Example (Validating Return Type Consistency)

This example enforces that every matching branch returns a string.

import { Match } from "ts-pattern-match";

const match = Match.type<{ a: number } | { b: string }>().pipe(
  // Ensure all branches return a string
  Match.withReturnType<string>(),
  // ❌ Type error: returns a number
  // @errors: 2322
  Match.when({ a: Match.number }, (_) => _.a),
  // ✅ Correct: returns a string
  Match.when({ b: Match.string }, (_) => _.b),
  Match.exhaustive,
);

The Match.withReturnType<T>() call must be the first instruction in the pipeline. If placed later, TypeScript will not properly enforce return type consistency.

Defining patterns

when

The Match.when function allows you to define conditions for matching values. It supports both direct value comparisons and predicate functions.

Example (Matching with Values and Predicates)

import { Match } from "ts-pattern-match";

// Create a matcher for objects with an "age" property
const match = Match.type<{ age: number }>().pipe(
  // Match when age is greater than 18
  Match.when({ age: (age) => age > 18 }, (user) => `Age: ${user.age}`),
  // Match when age is exactly 18
  Match.when({ age: 18 }, () => "You can vote"),
  // Fallback case for all other ages
  Match.orElse((user) => `${user.age} is too young`),
);

console.log(match({ age: 20 }));
// Output: "Age: 20"

console.log(match({ age: 18 }));
// Output: "You can vote"

console.log(match({ age: 4 }));
// Output: "4 is too young"

not

The Match.not function allows you to exclude specific values while matching all others.

Example (Ignoring a Specific Value)

import { Match } from "ts-pattern-match";

// Create a matcher for string or number values
const match = Match.type<string | number>().pipe(
  // Match any value except "hi", returning "ok"
  Match.not("hi", () => "ok"),
  // Fallback case for when the value is "hi"
  Match.orElse(() => "fallback"),
);

console.log(match("hello"));
// Output: "ok"

console.log(match("hi"));
// Output: "fallback"

tag

The Match.tag function allows pattern matching based on the _tag field in a Discriminated Union. You can specify multiple tags to match within a single pattern.

Example (Matching a Discriminated Union by Tag)

import { Match } from "ts-pattern-match";

type Event =
  | { readonly _tag: "fetch" }
  | { readonly _tag: "success"; readonly data: string }
  | { readonly _tag: "error"; readonly error: Error }
  | { readonly _tag: "cancel" };

// Create a Matcher for Either<number, string>
const match = Match.type<Event>().pipe(
  // Match either "fetch" or "success"
  Match.tag("fetch", "success", () => `Ok!`),
  // Match "error" and extract the error message
  Match.tag("error", (event) => `Error: ${event.error.message}`),
  // Match "cancel"
  Match.tag("cancel", () => "Cancelled"),
  Match.exhaustive,
);

console.log(match({ _tag: "success", data: "Hello" }));
// Output: "Ok!"

console.log(match({ _tag: "error", error: new Error("Oops!") }));
// Output: "Error: Oops!"

Built-in Predicates

The Match module provides built-in predicates for common types, such as Match.number, Match.string, and Match.boolean. These predicates simplify the process of matching against primitive types.

Example (Using Built-in Predicates for Property Keys)

import { Match } from "ts-pattern-match";

const matchPropertyKey = Match.type<PropertyKey>().pipe(
  // Match when the value is a number
  Match.when(Match.number, (n) => `Key is a number: ${n}`),
  // Match when the value is a string
  Match.when(Match.string, (s) => `Key is a string: ${s}`),
  // Match when the value is a symbol
  Match.when(Match.symbol, (s) => `Key is a symbol: ${String(s)}`),
  // Ensure all possible cases are handled
  Match.exhaustive,
);

console.log(matchPropertyKey(42));
// Output: "Key is a number: 42"

console.log(matchPropertyKey("username"));
// Output: "Key is a string: username"

console.log(matchPropertyKey(Symbol("id")));
// Output: "Key is a symbol: Symbol(id)"

| Predicate | Description | | ------------------------- | ----------------------------------------------------------------------------- | | Match.string | Matches values of type string. | | Match.nonEmptyString | Matches non-empty strings. | | Match.number | Matches values of type number. | | Match.boolean | Matches values of type boolean. | | Match.bigint | Matches values of type bigint. | | Match.symbol | Matches values of type symbol. | | Match.date | Matches values that are instances of Date. | | Match.record | Matches objects where keys are string or symbol and values are unknown. | | Match.null | Matches the value null. | | Match.undefined | Matches the value undefined. | | Match.defined | Matches any defined (non-null and non-undefined) value. | | Match.any | Matches any value without restrictions. | | Match.is(...values) | Matches a specific set of literal values (e.g., Match.is("a", 42, true)). | | Match.instanceOf(Class) | Matches instances of a given class. |

Completing the match

exhaustive

The Match.exhaustive method finalizes the pattern matching process by ensuring that all possible cases are accounted for. If any case is missing, TypeScript will produce a type error. This is particularly useful when working with unions, as it helps prevent unintended gaps in pattern matching.

Example (Ensuring All Cases Are Covered)

import { Match } from "ts-pattern-match";

// Create a matcher for string or number values
const match = Match.type<string | number>().pipe(
  // Match when the value is a number
  Match.when(Match.number, (n) => `number: ${n}`),
  // Mark the match as exhaustive, ensuring all cases are handled
  // TypeScript will throw an error if any case is missing
  // @errors: 2345
  Match.exhaustive,
);

orElse

The Match.orElse method defines a fallback value to return when no other patterns match. This ensures that the matcher always produces a valid result.

Example (Providing a Default Value When No Patterns Match)

import { Match } from "ts-pattern-match";

// Create a matcher for string or number values
const match = Match.type<string | number>().pipe(
  // Match when the value is "a"
  Match.when("a", () => "ok"),
  // Fallback when no patterns match
  Match.orElse(() => "fallback"),
);

console.log(match("a"));
// Output: "ok"

console.log(match("b"));
// Output: "fallback"

option

Match.option wraps the match result in an Option. If a match is found, it returns Some(value), otherwise, it returns None.

Example (Extracting a User Role with Option)

import { Match } from "ts-pattern-match";

type User = { readonly role: "admin" | "editor" | "viewer" };

// Create a matcher to extract user roles
const getRole = Match.type<User>().pipe(
  Match.when({ role: "admin" }, () => "Has full access"),
  Match.when({ role: "editor" }, () => "Can edit content"),
  Match.option, // Wrap the result in an Option
);

console.log(getRole({ role: "admin" }));
// Output: { _id: 'Option', _tag: 'Some', value: 'Has full access' }

console.log(getRole({ role: "viewer" }));
// Output: { _id: 'Option', _tag: 'None' }

either

The Match.either method wraps the result in an Either, providing a structured way to distinguish between matched and unmatched cases. If a match is found, it returns Right(value), otherwise, it returns Left(no match).

Example (Extracting a User Role with Either)

import { Match } from "ts-pattern-match";

type User = { readonly role: "admin" | "editor" | "viewer" };

// Create a matcher to extract user roles
const getRole = Match.type<User>().pipe(
  Match.when({ role: "admin" }, () => "Has full access"),
  Match.when({ role: "editor" }, () => "Can edit content"),
  Match.either, // Wrap the result in an Either
);

console.log(getRole({ role: "admin" }));
// Output: { _id: 'Either', _tag: 'Right', right: 'Has full access' }

console.log(getRole({ role: "viewer" }));
// Output: { _id: 'Either', _tag: 'Left', left: { role: 'viewer' } }