plgg
plgg

Exhaustive match

match folds a value — a literal, an Option/Result, or any tagged union — into a single result, and the compiler checks that the cases are exhaustive: a missing variant is a type error, not a runtime surprise.

It is curried — match(value)(...cases) — where the value fixes the matched type and each case is a [pattern, handler] pair:

import { match } from "plgg";

const s1 = 1 as const,
  s2 = 2 as const,
  s3 = 3 as const;
type Status = typeof s1 | typeof s2 | typeof s3;

const describe = (a: Status): string =>
  match(a)(
    [s1, () => "one"],
    [s2, () => "two"],
    [s3, () => "three"],
  );
// Dropping a case makes the call a compile error.

Matching tagged variants

For boxed unions, the $-matchers build patterns by tag, and the handler receives the matched variant with its content typed — no as, no .content guesswork:

import { match, ok$, err$, otherwise } from "plgg";
import type { Result } from "plgg";

const render = (r: Result<string, number>): string =>
  match(r)(
    [ok$(), (b) => `ok: ${b.content}`],
    [err$(404), () => "not found"],
    [otherwise, () => "other error"],
  );

some$()/none$() match Option; pattern("Tag")() matches any box by tag for your own unions:

import { match, pattern, box } from "plgg";
import type { Box } from "plgg";

type HttpError =
  | Box<"NotFound", string>
  | Box<"ServerError", string>;

const status = (e: HttpError): string =>
  match(e)(
    [pattern("NotFound")(), (b) => `404 ${b.content}`],
    [pattern("ServerError")(), (b) => `500 ${b.content}`],
  );

otherwise and non-exhaustive matches

Pass otherwise as the last pattern for a catch-all. Without it, the cases must cover the union. If a match is somehow reached at runtime with no matching case (an untyped call, or a value outside the declared union), it returns a CoverageError value carrying the input — detectable with isCoverageError — rather than throwing.

For the common error types there are ergonomic folds built on match: matchResult, matchOption, and matchPlggError.