Releases: gvergnaud/ts-pattern
v5.1.1
What's Changed
- Fix(P.nonNullable): narrowing of unions of objects by @gvergnaud in #237
Full Changelog: v5.1.0...v5.1.1
v5.1.0
New features
P.nonNullable
wildcard
Add a new P.nonNullable
pattern that will match any value except null
or undefined
.
import { match, P } from 'ts-pattern';
const input = null;
const output = match<number | null | undefined>(input)
.with(P.nonNullable, () => 'it is a number!')
.otherwise(() => 'it is either null or undefined!');
console.log(output);
// => 'it is either null or undefined!'
Closes #60 #154 #190 and will be a work-around for #143.
What's Changed
- feat: Add P.nonNullable patterns by @gvergnaud in #229
Full Changelog: v5.0.8...v5.1.0
v5.0.8
The main thing
This release includes type narrowing improvement to isMatching
when used in its curried form:
type Pizza = { type: 'pizza', topping: string };
type Sandwich = { type: 'sandwich', condiments: string[] }
type Food = Pizza | Sandwich;
declare const food: Food
const isPizza = isMatching({ type: 'pizza' })
if (isPizza(food)) {
x // Used to infer `food` as `Food`, no infers `Pizza`
}
This also improves type checking performance for complex patterns and fixes a small bug in the ES5 build of TS-Pattern.
What's Changed
- perf: support exhaustive match on larger unions by @gvergnaud in #214
- fix: make isMatching(p) infer the pattern as a const type parameter by @gvergnaud in #221
- fix: Make sure regeneratorRuntime isn't included in the cjs build by @gvergnaud in #224
Full Changelog: v5.0.6...v5.0.8
v5.0.6
Close issue issues
What's Changed
- Update README.md by @ndstephens in #161
- docs: add install instructions for bun and pnpm by @colinhacks in #186
- build(deps-dev): bump postcss from 8.4.23 to 8.4.31 by @dependabot in #199
- build(deps-dev): bump @babel/traverse from 7.21.5 to 7.23.2 by @dependabot in #200
- fix(readonly): exhaustive matching on readonly array by @gvergnaud in #207
New Contributors
- @ndstephens made their first contribution in #161
- @colinhacks made their first contribution in #186
Full Changelog: v5.0.5...v5.0.6
v5.0.5
Bug fixes
The P
module was mistakenly exposing some pattern methods that were intended to be namespaced by type. This release fixes this problem.
If you happened to use on of those following methods, here is where to find them now:
- P.between
+ P.number.between
- P.lt
+ P.number.lt
- P.gt
+ P.number.gt
- P.lte
+ P.number.lte
- P.gte
+ P.number.gte
- P.int
+ P.number.int
- P.finite
+ P.number.finite
- P.positive
+ P.number.positive
- P.negative
+ P.number.negative
- P.betweenBigInt
+ P.bigint.between
- P.ltBigInt
+ P.bigint.lt
- P.gtBigInt
+ P.bigint.gt
- P.lteBigInt
+ P.bigint.lte
- P.gteBigInt
+ P.bigint.gte
- P.positiveBigInt
+ P.bigint.positive
- P.negativeBigInt
+ P.bigint.negative
v5.0.4
What's Changed
- 🐛 fix: Accept branded primitive types as patterns by @gvergnaud in #180
Full Changelog: v5.0.3...v5.0.4
v5.0.3
What's Changed
- 🐛 fix: Allow re-exporting patterns from ES Modules by @gvergnaud in #175
Full Changelog: v5.0.2...v5.0.3
v5.0.2
What's Changed
- fix(P.infer): Fix inference of arrays containing tuples in c52af6a
- refactoring: simplify selection types in 1b7a36b
- perf: runtime perf improvement in .with in 9b27384
- fix: use
Symbol.for
to make sure two concurrent versions of ts-pattern are compatible with one-another in d6d2e23 - docs: fix typo by @juicyjusung in #170
New Contributors
- @juicyjusung made their first contribution in #170
Full Changelog: v5.0.0...v5.0.2
v5.0.0 ❤️
TS-Pattern v5 is finally out ❤️
Breaking changes
.with
is now evaluated eagerly
In the previous version of TS-Pattern, no code would execute until you called .exhaustive()
or .otherwise(...)
. For example, in the following code block, nothing would be logged to the console or thrown:
// TS-Pattern v4
type Input = { type: 'ok'; value: number } | { type: 'error'; error: Error };
// We don't call `.exhaustive`, so handlers don't run.
function someFunction(input: Input) {
match(input)
.with({ type: 'ok' }, ({ value }) => {
console.log(value);
})
.with({ type: 'error' }, ({ error }) => {
throw error;
});
}
someFunction({ type: 'ok', value: 42 }); // nothing happens
In TS-Pattern v5, however, the library will execute the matching handler as soon as it finds it:
// TS-Pattern v5
someFunction({ type: 'ok', value: 42 }); // logs "42" to the console!
Handlers are now evaluated eagerly instead of lazily. In practice, this shouldn't change anything as long as you always finish your pattern matching expressions by either .exhaustive
or .otherwise
.
Matching on Maps and Sets
Matching Set
and Map
instances using .with(new Set(...))
and .with(new Map(...))
is no longer supported. If you want to match specific sets and maps, you should now use the P.map(keyPattern, valuePattern)
and P.set(valuePattern)
patterns:
- import { match } from 'ts-pattern';
+ import { match, P } from 'ts-pattern';
const someFunction = (value: Set<number> | Map<string, number>) =>
match(value)
- .with(new Set([P.number]), (set) => `a set of numbers`)
- .with(new Map([['key', P.number]]), (map) => `map.get('key') is a number`)
+ .with(P.set(P.number), (set) => `a set of numbers`)
+ .with(P.map('key', P.number), (map) => `map.get('key') is a number`)
.otherwise(() => null);
- The subpattern we provide in
P.set(subpattern)
should match all values in the set. - The value subpattern we provide in
P.map(keyPattern, subpattern)
should only match the values matchingkeyPattern
for the wholeP.map(..)
pattern to match the input.
New features
chainable methods
TS-Pattern v5's major addition is the ability to chain methods to narrow down the values matched by primitive patterns, like P.string
or P.number
.
Since a few examples is worth a thousand words, here are a few ways you can use chainable methods:
P.number methods
const example = (position: { x: number; y: number }) =>
match(position)
.with({ x: P.number.gte(100) }, (value) => '🎮')
.with({ x: P.number.between(0, 100) }, (value) => '🎮')
.with(
{
x: P.number.positive().int(),
y: P.number.positive().int(),
},
(value) => '🎮'
)
.otherwise(() => 'x or y is negative');
Here is the full list of number methods:
P.number.between(min, max)
: matches numbers betweenmin
andmax
.P.number.lt(max)
: matches numbers smaller thanmax
.P.number.gt(min)
: matches numbers greater thanmin
.P.number.lte(max)
: matches numbers smaller than or equal tomax
.P.number.gte(min)
: matches numbers greater than or equal tomin
.P.number.int()
: matches integers.P.number.finite()
: matches all numbers exceptInfinity
and-Infinity
P.number.positive()
: matches positive numbers.P.number.negative()
: matches negative numbers.
P.string methods
const example = (query: string) =>
match(query)
.with(P.string.startsWith('SELECT'), (query) => `selection`)
.with(P.string.endsWith('FROM user'), (query) => `👯♂️`)
.with(P.string.includes('*'), () => 'contains a star')
// Methods can be chained:
.with(P.string.startsWith('SET').includes('*'), (query) => `🤯`)
.exhaustive();
Here is the full list of string methods:
P.string.startsWith(str)
: matches strings that start withstr
.P.string.endsWith(str)
: matches strings that end withstr
.P.string.minLength(min)
: matches strings with at leastmin
characters.P.string.maxLength(max)
: matches strings with at mostmax
characters.P.string.includes(str)
: matches strings that containstr
.P.string.regex(RegExp)
: matches strings if they match this regular expression.
Global methods
Some methods are available for all primitive type patterns:
P.{..}.optional()
: matches even if this property isn't present on the input object.P.{..}.select()
: injects the matched value into the handler function.P.{..}.and(pattern)
: matches if the current pattern and the provided pattern match.P.{..}.or(pattern)
: matches if either the current pattern or the provided pattern match.
const example = (value: unknown) =>
match(value)
.with(
{
username: P.string,
displayName: P.string.optional(),
},
() => `{ username:string, displayName?: string }`
)
.with(
{
title: P.string,
author: { username: P.string.select() },
},
(username) => `author.username is ${username}`
)
.with(
P.instanceOf(Error).and({ source: P.string }),
() => `Error & { source: string }`
)
.with(P.string.or(P.number), () => `string | number`)
.otherwise(() => null);
Variadic tuple patterns
With TS-Pattern, you are now able to create array (or more accurately tuple) pattern with a variable number of elements:
const example = (value: unknown) =>
match(value)
.with(
// non-empty list of strings
[P.string, ...P.array(P.string)],
(value) => `value: [string, ...string[]]`
)
.otherwise(() => null);
Array patterns that include a ...P.array
are called variadic tuple patterns. You may only have a single ...P.array
, but as many fixed-index patterns as you want:
const example = (value: unknown) =>
match(value)
.with(
[P.string, P.string, P.string, ...P.array(P.string)],
(value) => `value: [string, string, string, ...string[]]`
)
.with(
[P.string, P.string, ...P.array(P.string)],
(value) => `value: [string, string, ...string[]]`
)
.with([], (value) => `value: []`)
.otherwise(() => null);
Fixed-index patterns can also be set after the ...P.array
variadic, or on both sides!
const example = (value: unknown) =>
match(value)
.with(
[...P.array(P.number), P.string, P.number],
(value) => `value: [...number[], string, number]`
)
.with(
[P.boolean, ...P.array(P.string), P.number, P.symbol],
(value) => `value: [boolean, ...string[], number, symbol]`
)
.otherwise(() => null);
Lastly, argument of P.array
is now optional, and will default to P._
, which matches anything:
const example = (value: unknown) =>
match(value)
// 👇
.with([P.string, ...P.array()], (value) => `value: [string, ...unknown[]]`)
.otherwise(() => null);
.returnType
In TS-Pattern v4, the only way to explicitly set the return type of your match
expression is to set the two <Input, Output>
type parameters of match
:
// TS-Pattern v4
match<
{ isAdmin: boolean; plan: 'free' | 'paid' }, // input type
number // return type
>({ isAdmin, plan })
.with({ isAdmin: true }, () => 123)
.with({ plan: 'free' }, () => 'Oops!');
// ~~~~~~ ❌ not a number.
the main drawback is that you need to set the input type explicitly too, even though TypeScript should be able to infer it.
In TS-Pattern v5, you can use the .returnType<Type>()
method to only set the return type:
match({ isAdmin, plan })
.returnType<number>() // 👈 new
.with({ isAdmin: true }, () => 123)
.with({ plan: 'free' }, () => 'Oops!');
// ~~~~~~ ❌ not a number.
What's Changed
- TS-Pattern v5 base branch by @gvergnaud in #139
Full Changelog: v4.3.0...v5.0.0
v4.3.0
TS-Pattern and node16
TS-Pattern now fully supports moduleResolution: node16
, with both ES and CommonJS modules. This resolves the long standing issue number #110. Special thanks to @Andarist and @frankie303 for helping me understand and fix this issue ❤️
What's Changed
- build: better support moduleResolution: bundler by @gvergnaud in #158
- Gvergnaud/fix build for nodenext and commonjs by @gvergnaud in #160
Full Changelog: v4.2.2...v4.3.0