
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
ts-pattern-match
Advanced tools
Extracted from the effect library.
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"
Pattern matching follows a structured process:
Creating a matcher.
Define a Matcher that operates on either a specific type or value.
Defining patterns.
Use combinators such as Match.when, Match.not, and Match.tag to specify matching conditions.
Completing the match.
Apply a finalizer such as Match.exhaustive, Match.orElse, or Match.option to determine how unmatched cases should be handled.
You can create a Matcher using either:
Match.type<T>(): Matches against a specific type.Match.value(value): Matches against a specific value.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"
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"
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.
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"
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"
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!"
The `Match.tag` function relies on the convention within the Effect
ecosystem of naming the tag field as `"_tag"`. Ensure that your
discriminated unions follow this naming convention for proper
functionality.
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. |
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,
);
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"
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' }
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' } }
FAQs
Pattern Matching for TypeScript.
We found that ts-pattern-match demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.