Releases: gvergnaud/ts-pattern
v4.0.2
Patch release containing a few runtime performance improvements:
- Use a
Builder
class internally in match expression to rely on prototypal inheritance instead of defining method every time.with
is called. - Handle the
.with(pattern, handler)
case separately from.with(...pattern, handler)
to avoid iterating on params and make it faster.
✨ v4.0.1 ✨
⚠️ Breaking changes
Imports
type-specific wildcard patterns have moved from __.<pattern>
to a new Pattern
qualified module, also exported as P
by ts-pattern.
- import { match, __ } from 'ts-pattern';
+ import { match, Pattern } from 'ts-pattern';
const toString = (value: string | number) =>
match(value)
- .with(__.string, (v) => v)
- .with(__.number, (v) => `${v}`)
+ .with(Pattern.string, (v) => v)
+ .with(Pattern.number, (v) => `${v}`)
.exhaustive();
or
- import { match, __ } from 'ts-pattern';
+ import { match, P } from 'ts-pattern';
const toString = (value: string | number) =>
match(value)
- .with(__.string, (v) => v)
- .with(__.number, (v) => `${v}`)
+ .with(P.string, (v) => v)
+ .with(P.number, (v) => `${v}`)
.exhaustive();
__
The top level __
export was moved to P._
and P.any
:
- import { match, __ } from 'ts-pattern';
+ import { match, P } from 'ts-pattern';
const toString = (value: string | number) =>
match(value)
- .with(__, (v) => `${v}`)
+ .with(P._, (v) => `${v}`)
// OR
+ .with(P.any, (v) => `${v}`)
.exhaustive();
select()
, not()
, when()
Function to create patterns have been moved to the P
module.
- import { match, select, not, when } from 'ts-pattern';
+ import { match, P } from 'ts-pattern';
const toString = (value: number) =>
match(value)
- .with({ prop: select() }, (v) => `${v}`)
+ .with({ prop: P.select() }, (v) => `${v}`)
- .with({ prop: not(10) }, (v) => `${v}`)
+ .with({ prop: P.not(10) }, (v) => `${v}`)
- .with({ prop: when((x) => x < 5) }, (v) => `${v}`)
+ .with({ prop: P.when((x) => x < 5) }, (v) => `${v}`)
.exhaustive();
Pattern
type
the Pattern
type which used to be exported at the toplevel is now accessible at P.Pattern
.
- import { match, Pattern } from 'ts-pattern';
+ import { match, P } from 'ts-pattern';
- const pattern: Pattern<number> = P.when(x => x > 2);
+ const pattern: P.Pattern<number> = P.when(x => x > 2);
list patterns
The syntax for matching on a list of elements with an unknown length has changed from [subpattern]
to P.array(subpattern)
.
Example:
- import { match, __ } from 'ts-pattern';
+ import { match, P } from 'ts-pattern';
const parseUsers = (response: unknown) =>
match(response)
- .with({ data: [{ name: __.string }] }, (users) => users)
+ .with({ data: P.array({ name: P.string }) }, (users) => users)
.otherwise(() => []);
Now [subpattern]
matches arrays with 1 element in them. This is more consistent with native language features, like destructuring assignement and is overall more intuitive. This will resolve #69, #62 and #46.
NaN
The __.NaN
pattern has been replaced by simply using the NaN value in the pattern:
match<number>(NaN)
- .with(__.NaN, () => "this is not a number")
+ .with(NaN, () => "this is not a number")
.otherwise((n) => n);
⭐️ New features ⭐️
Here is the list of all new features which have been added in TS-Pattern v4.
Arrays and unary tuples
P.array(pattern)
To match an array of elements, you can now use P.array
:
import { match, P } from 'ts-pattern';
const responsePattern = {
data: P.array({
id: P.string,
post: P.array({
title: P.string,
content: P.string,
}),
}),
};
fetchSomething().then((value: unknown) =>
match(value)
.with(responsePattern, (value) => {
// value: { data: { id: string, post: { title: string, content: string }[] }[] }
return value;
})
.otherwise(() => {
throw new Error('unexpected response');
})
);
Optional object properties
P.optional(pattern)
If you want one of the keys of your pattern to be optional, you can now use P.optional(subpattern)
.
If you P.select()
something in an optional pattern, it's type will be infered as T | undefined
.
import { match, P } from 'ts-pattern';
const doSomethingWithUser = (user: User | Org) =>
match(user)
.with(
{
type: 'user',
detail: {
bio: P.optional(P.string),
socialLinks: P.optional({
twitter: P.select(),
}),
},
},
(twitterLink, value) => {
// twitterLink: string | undefined
/**
* value.detail: {
* bio?: string,
* socialLinks?: {
* twitter: string
* }
* }
**/
}
)
.otherwise(() => {
throw new Error('unexpected response');
});
Union & intersection patterns
P.union(...patterns)
and P.intersection(...patterns)
combine several patterns into a single one, either by checking that one of them match the input (p.union
) or all of them match it (P.intersection
).
P.union(...patterns)
type Input =
| { type: 'a'; value: string }
| { type: 'b'; value: number }
| {
type: 'c';
value:
| { type: 'd'; value: boolean }
| { type: 'e'; value: string[] }
| { type: 'f'; value: number[] };
};
const f = (input: Input) =>
match(input)
.with(
{ type: P.union('a', 'b') },
// x: { type: 'a'; value: string } | { type: 'b'; value: number }
(x) => 'branch 1'
)
.with(
// P.union can take any subpattern:
{
type: 'c',
value: { value: P.union(P.boolean, P.array(P.string)) },
},
(x) => 'branch 2' // x.value.value: boolean | string[]
)
.with({ type: 'c', value: { type: 'f' } }, () => 'branch 3')
.exhaustive();
P.intersection(...patterns)
class A {
constructor(public foo: 'bar' | 'baz') {}
}
class B {
constructor(public str: string) {}
}
const f = (input: { prop: A | B }) =>
match(input)
.with(
{ prop: P.intersection(P.instanceOf(A), { foo: 'bar' }) },
// prop: A & { foo: 'bar' }
({ prop }) => 'branch 1'
)
.with(
{ prop: P.intersection(P.instanceOf(A), { foo: 'baz' }) },
// prop: A & { foo: 'baz' }
({ prop }) => 'branch 2'
)
.with(
{ prop: P.instanceOf(B) },
// prop: B
({ prop }) => 'branch 3'
)
.exhaustive();
Select with sub pattern
P.select()
now can take a subpattern and match only what the subpattern matches:
type Img = { type: 'img'; src: string };
type Text = { type: 'text'; content: string; length: number };
type User = { type: 'user'; username: string };
type Org = { type: 'org'; orgId: number };
const post = (input: { author: User | Org; content: Text | Img }) =>
match(input)
.with(
{ author: P.select({ type: 'user' }) },
// user: User
(user) => {}
)
.with(
{
// This also works with named selections
author: P.select('org', { type: 'org' }),
content: P.select('text', { type: 'text' }),
},
// org: Org, text: Text
({ org, text }) => {}
)
.otherwise(() => {
// ...
});
Infer the matching types from a pattern
P.infer<typeof pattern>
TS-Pattern is pretty handy for parsing unknown payloads like HTTP responses. You can write a pattern for the shape you are expecting, and then use isMatching(pattern, response)
to make sure the response has the correct shape.
One limitation TS-Pattern had in its previous version was that it did not provide a way to get the TypeScript type of the value a given pattern matches. This is what P.infer<typeof pattern>
does :)
const postPattern = {
title: P.string,
description: P.optional(P.string),
content: P.string,
likeCount: P.number,
};
type Post = P.infer<typeof postPattern>;
// Post: { title: string, description?: string, content: string, likeCount: number }
const userPattern = {
name: P.string,
postCount: P.number,
bio: P.optional(P.string),
posts: P.optional(P.array(postPattern)),
};
type User = P.infer<typeof userPattern>;
// User: { name: string, postCount: number, bio?: string, posts?: Post[] }
const isUserList = isMatching(P.array(userPattern));
const res = await fetchUsers();
if (isUserList(res)) {
// res: User
}
New type specific wildcards
P.symbol
P.symbol
is a wildcard pattern matching any symbol.
match(Symbol('Hello'))
.with(P.symbol, () => 'this is a symbol!')
.exhaustive();
P.bigint
P.bigint
is a wildcard pattern matching any bigint.
match(200n)
.with(P.bigint, () => 'this is a bigint!')
.exhaustive();
v3.3.5
v3.3.4
v3.3.2
This patch contains some compile time perf improvements.
@ahejlsberg recently implemented tail call elimination for recursive conditional types (microsoft/TypeScript#45711). This release is preparation work to take advantage of this new feature by making most type helper functions tail recursive. From the non scientific tests I made on my machine, this also improves the compilation time of the tests/
folder quite significantly on our current TS version (4.4). Compilation is ~ 20% faster.
v3.3.1
Features
Add a __.NaN
pattern, matching only NaN
s values. Thanks @mhintz for adding this
const res = match<number | null>(NaN)
.with(null, () => 'null!')
.with(__.NaN, () => 'NaN!')
.with(__.number, (x) => 'a number!')
.exhaustive();
console.log(res)
// => 'NaN!'
Bugfix
Update the __.number
pattern to also match on NaN
values.
Since NaN
has type number
in TypeScript, there is no way to distinguish a NaN
from a regular number at the type level. This was causing an issue where .exhaustive()
considered all numbers handled by the __.number
pattern even though NaN
wasn't matched by it, resulting in possible runtime errors.
const res = match<number | null>(NaN)
.with(null, () => 'null!')
.with(__.number, (x) => 'a number!')
// This used to throw at runtime because NaN wasn't matched by __.number
.exhaustive();
console.log(res)
// => 'a number!'
v3.2.5
Bugfixes
- Fix a bug with exhaustiveness checking, where a case could wrongfully be considered handled even if the pattern was containing additional keys that aren't present in the input type. This can happen when making a typo when declaring your pattern for instance, as demonstrated in the issue #44 .
type Person = {
sex: "Male" | "Female";
age: "Adult" | "Child";
};
function summary(person: Person): string {
return (
match(person)
// Typo – "agf" should be "age"
.with({ sex: "Female", agf: "Adult" }, () => "Woman")
.with({ sex: "Female", age: "Child" }, () => "Girl")
.with({ sex: "Male", age: "Adult" }, () => "Man")
.with({ sex: "Male", age: "Child" }, () => "Boy")
// Bugfix: This pattern is no longer considered exhaustive!
.exhaustive()
);
}
v3.2.4
Improvements
.otherwise
now passes the input value to the handler function. This can be useful when your default handler is a statically defined function and you want to avoid writing an intermediary anonymous function passing the input to it. PR #37 from @oguimbal 🙏
Bug fix
- It's now possible to pattern-match on native
Error
objects. To save some compilation time, deep pattern matching on builtin objects is disabled, but it turns out this can be pretty handy to pattern match on nativeError
objects because some libs (like@apollo/client
) enhance them with additional properties.
v3.2.2
Fix a bug reported in issue #40, where object pattern containing a single wildcard would match any object, even if the key used by the pattern wasn't present on the input object.
type Id = { teamId: number } | { storeId: number };
const selectedId: Id = { teamId: 1 };
match<Id>(selectedId)
.with({ storeId: __ }, () => "storeId")
.with({ teamId: __ }, () => "teamId")
.exhaustive()
// Now outputs "teamId" instead of "storeId"
v3.2.1
This minor release adds 3 new features to the lib.
__.nullish
A new wildcard pattern, matching null | undefined
. Thanks a lot @m-rutter for implementing this and fixing some issues with pattern inference along the way! 🎉
import { match, __ } from 'ts-pattern';
const input = null;
const output = match<number | string | boolean | null | undefined>(input)
.with(__.string, () => 'it is a string!')
.with(__.number, () => 'it is a number!')
.with(__.boolean, () => 'it is a boolean!')
.with(__.nullish, () => 'it is either null or undefined!')
.with(null, () => 'it is null!')
.with(undefined, () => 'it is undefined!')
.run();
console.log(output);
// => 'it is either null or undefined!'
instanceOf
A new function creating a pattern, checking if a value is an instance of a particular class. This feature was requests in #24, it was possible implement it in userland using a when
pattern, but the types were a bit tricky so I thought it made sense to add first class support for this.
import { match, instanceOf } from 'ts-pattern';
class A {
a = 'a';
}
class B {
b = 'b';
}
const output = match<{ value: A | B }>({ value: new A() })
.with({ value: instanceOf(A) }, (a) => 'instance of A!')
.with({ value: instanceOf(B) }, (b) => 'instance of B!')
.exhaustive();
console.log(output);
// => 'instance of A!'
isMatching
A helper function to create a type guard function from a pattern. This can be really useful when making a runtime type assertion, for instance if you want to check that an API response matches what you are expecting.
import { isMatching, __ } from 'ts-pattern';
const isBlogPost = isMatching({
title: __.string,
content: __.string,
});
const res: unknown = await fetch(...)
if (isBlogPost(res)) {
// res: { title: string, content: string }
}