TS-Pattern
TS-Pattern is an exhaustive pattern matching library for TypeScript, providing a typesafe and highly ergonomic API to handle complex conditional logic. It is currently at version 5.9.0 and receives regular updates, often including new pattern types, performance improvements, and type inference enhancements. Key differentiators include its extensive support for various data structures (objects, arrays, tuples, sets, maps, primitives), robust type inference, and crucial exhaustiveness checking, ensuring all possible cases are handled at compile-time. It aims to provide a user-land implementation of pattern matching, similar to those found in functional languages, anticipating a future TC39 proposal, while maintaining a tiny bundle footprint of around 2kB.
Common errors
-
Argument of type '{ type: "one"; } | { type: "two"; }' is not assignable to parameter of type 'never'.cause This error typically occurs when the `.exhaustive()` method cannot confirm that all possible cases of the input type have been handled by the preceding `.with()` or `.otherwise()` clauses, meaning some branches are implicitly 'never' reached by the patterns.fixAdd a `.with()` clause for any missing cases or use an `.otherwise(() => ...)` clause as a fallback. For deeply nested types, ensure type guards are precise or use `.narrow()` to guide inference. -
Type '{ type: "someType"; }' is not assignable to type 'P.Pattern<T>'cause This usually indicates a type mismatch between the expected input type of the `match` function and the pattern being provided in a `.with()` clause. The pattern you're trying to match is incompatible with the type `T` of the `match` input.fixReview your input type `T` for `match` and ensure your patterns accurately reflect the possible shapes within `T`. This might involve refining discriminated unions or correcting object structures. -
TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
cause This is a general TypeScript error that can arise if a `P.select()` pattern extracts a value with an unexpected type, or a callback function within `.with()` receives arguments of types that don't match its signature.fixInspect the `P.select()` pattern to confirm it extracts the expected type. Also, verify the type signature of your callback function in `.with()` to ensure it correctly handles the types inferred by `ts-pattern`.
Warnings
- breaking Symbol keys are now considered in object patterns. Previously, symbol keys in objects were ignored during pattern matching, meaning `isMatching({ [symbolA]: 'bar' }, obj)` would incorrectly return true if `obj` was `{ [symbolA]: 'foo' }` (matching the empty object part).
- breaking When using `isMatching` with two parameters (pattern and value), the pattern now undergoes type-checking against the value's type. This prevents passing patterns that could never match the provided value.
- gotcha The `.exhaustive()` method, by default, throws an error if no `with` clause matches the input value. While this indicates a type inconsistency, in rare scenarios, you might want to customize this behavior.
- gotcha When working with deeply nested union types or nullable properties, TypeScript's inference might not always narrow types as aggressively as desired within complex pattern matching scenarios.
Install
-
npm install ts-pattern -
yarn add ts-pattern -
pnpm add ts-pattern
Imports
- match
const match = require('ts-pattern').match;import { match } from 'ts-pattern'; - P
import * as P from 'ts-pattern';
import { P } from 'ts-pattern'; - isMatching
import { isMatching } from 'ts-pattern';
Quickstart
import { match, P } from 'ts-pattern';
type UserRole = 'admin' | 'editor' | 'viewer';
type UserProfile =
| { role: 'admin'; permissions: string[]; department: string }
| { role: 'editor'; projects: string[]; lastLogin: Date }
| { role: 'viewer'; lastViewed: string; theme: 'dark' | 'light' };
const getUserDashboardContent = (user: UserProfile): string => {
return match(user)
.with({ role: 'admin', department: P.select('dept') }, ({ dept }) =>
`Welcome Admin! You manage the ${dept} department.`
)
.with({ role: 'editor', projects: P.array(P.string) }, (editor) =>
`Hello Editor! Your active projects are: ${editor.projects.join(', ')}.`
)
.with({ role: 'viewer', theme: 'dark' }, () =>
`Viewer mode: Enjoy your dark theme.`
)
.with({ role: 'viewer', theme: 'light' }, () =>
`Viewer mode: Enjoy your light theme.`
)
.exhaustive();
};
const adminUser: UserProfile = { role: 'admin', permissions: ['full'], department: 'IT' };
const editorUser: UserProfile = { role: 'editor', projects: ['Project Alpha', 'Project Beta'], lastLogin: new Date() };
const viewerUserDark: UserProfile = { role: 'viewer', lastViewed: 'docs', theme: 'dark' };
console.log(getUserDashboardContent(adminUser));
console.log(getUserDashboardContent(editorUser));
console.log(getUserDashboardContent(viewerUserDark));