Pattern Matching
A Result is a discriminated union — { tag: "Ok"; value } | { tag: "Err"; error } | { tag: "Defect"; cause } — so you can pattern-match it natively, no adapter required.
For the everyday exhaustive fold over a tagged error union, matchTags is the simplest tool. When you want ts-pattern's full power — guards, nested patterns, wildcards, selection — match the Result directly.
Matching a Result directly
Because tag is a real discriminant, ts-pattern matches a Result out of the box, and .exhaustive() works:
import { match } from "ts-pattern";
const status = match(result)
.with({ tag: "Ok" }, ({ value }) => 200)
.with({ tag: "Err" }, ({ error }) => 400)
.with({ tag: "Defect" }, ({ cause }) => 500)
.exhaustive(); // ✅ omit a variant and it won't compileThe payload (value / error / cause) is reachable only inside the matching arm — exactly like the type guards.
@unthrown/pattern — pattern sugar
@unthrown/pattern adds small constructors so you don't write the raw object patterns, plus tag for matching a TaggedError.
pnpm add @unthrown/pattern ts-patternnpm install @unthrown/pattern ts-patternimport { match } from "ts-pattern";
import * as P from "@unthrown/pattern";
const status = match(result)
.with(P.ok(), ({ value }) => 200)
.with(P.err(P.tag("NotFound")), () => 404)
.with(P.err(P.tag("Forbidden")), ({ error }) => {
audit(error.user); // narrowed to Forbidden — payload available
return 403;
})
.with(P.defect(), ({ cause }) => 500)
.exhaustive();P.ok(sub?)/P.err(sub?)/P.defect(sub?)— match a channel; pass a sub-pattern to constrain or select the payload: a literal, or anyts-patternpattern.P.tag(t)— sugar for{ _tag: t }; nested inP.err(...)it narrows to the matching tagged-error variant, payload and all.
Above, P is @unthrown/pattern. To also use ts-pattern's own patterns (wildcards, P.select(), P.string, …), import them from ts-pattern under a different name:
import { match, P as t } from "ts-pattern";
import * as P from "@unthrown/pattern";
match(result)
.with(P.ok(t.select()), (value) => value) // t.select() is ts-pattern's
.otherwise(() => 0);ts-pattern is a peer dependency.
Matching an AsyncResult
ts-pattern's match is synchronous, so await an AsyncResult first — the result is a plain, matchable Result:
const status = match(await asyncResult)
.with(P.ok(), () => 200)
.with(P.err(), () => 400)
.with(P.defect(), () => 500)
.exhaustive();Which should I use?
matchTags(core, zero-dep) — the everyday exhaustive fold over a tagged error union. No.exhaustive()to forget.ts-pattern(+@unthrown/patternsugar) — when you need guards, nested matching on payloads, wildcards, or to match on the success value's shape.