📝 Restore and rewrite the README entry point

The README collapsed to API-only prose once the old match/P surface was
dropped. Rebuild its entry-point shape: a quick-start Synopsis walked through
a Contact union, a dedicated Installation section, and a real-world Examples
section (primitive-union dispatch, a fallback, a property union narrowed by
the tagged-union matcher, and the widening variant).

Set the tagline and Description to the library's identity — exhaustive,
type-safe pattern matching for TypeScript — and state the goal Moose-style:
type-safe pattern matching with a lean syntax, accomplished by exhaustive
branches and typed per-branch handler parameters, backed by autocomplete and
a tiny footprint. Drop the F#-style framing and the stale "matches method is
a type guard" and "(not regex)" copy from the README, AGENTS.md and the
package.json description. Every fence is a doc-test, so the examples cannot
drift from the API.
This commit is contained in:
tmu committed 2026-09-24 21:55:51 +00:00
1 parent 28d87b3387
commit 8261759190
5 files changed
+146 -11

No files matched your search

+140 -8
View File
@@ -1,15 +1,53 @@
# tiny-pattern-ts
Pattern matching for TypeScript/ESM environments (F#-style, not regex).
Exhaustive, type-safe pattern matching for TypeScript.
## Synopsis
```ts
import { getTaggedUnionMatcher } from "tiny-pattern-ts";
// 1. We have a union type
type Contact =
| { kind: "email"; address: string }
| { kind: "phone"; number: string }
| { kind: "messenger"; username: string };
// 2. Create a matcher providing the discriminant property
const matchContact = getTaggedUnionMatcher<Contact>()("kind");
// 3. Define handlers for each branch of the union
const formatContact = matchContact({
email: (e) => `MAIL: ${e.address}`,
phone: (p) => `PHONE: ${p.number}`,
messenger: (m) => `MESSENGER: @${m.username}`,
});
// 4. Call the matcher with a value
const mailOutput = formatContact({ kind: "email", address: "ada@example.com" });
assert.equal(mailOutput, "MAIL: ada@example.com");
const phoneOutput = formatContact({ kind: "phone", number: "+1 555 0100" });
assert.equal(phoneOutput, "PHONE: +1 555 0100");
```
## Description
`tiny-pattern-ts` brings F#-style pattern matching to TypeScript. Patterns are
ordinary objects whose `matches` method is a TypeScript type guard, so narrowing
composes the way any other guard does. It is deliberately not a regex engine and
not a macro: there is no transpiler and no DSL to learn, and the type-level
contract is the feature — see [development/library.md](./development/library.md)
for the design decisions and [Caveats](#caveats) for the limits.
`tiny-pattern-ts` is a pattern-matching library for TypeScript.
The main goal of `tiny-pattern-ts` is to make pattern matching type-safe with a
lean syntax. This is accomplished by being exhaustive and passing typed
parameters per branch to the handlers — supported by an outstanding
autocomplete and a tiny footprint.
See [development/library.md](./development/library.md) for the design decisions
and [Caveats](#caveats) for the limits.
## Installation
```sh
npm install tiny-pattern-ts
```
## Requirements
@@ -19,6 +57,100 @@ for the design decisions and [Caveats](#caveats) for the limits.
both resolve on TS >= 5.0 in `node10` / `node16` / `nodenext` / `bundler`.
- The package is **ESM-only** (no CommonJS shim).
## Examples
A few real-world recipes. Each binds the handler-map function once and reuses
it, so the matcher is allocated a single time.
### Dispatch on a primitive union
A result code is itself a finite union, so `getPrimitiveUnionMatcher` keys a
handler on each member:
```ts
import { getPrimitiveUnionMatcher } from "tiny-pattern-ts";
type ResultCode = "ok" | "created" | "no-content";
const toStatus = getPrimitiveUnionMatcher<ResultCode>()({
ok: () => 200,
created: () => 201,
"no-content": () => 204,
});
assert.equal(toStatus("ok"), 200);
assert.equal(toStatus("created"), 201);
assert.equal(toStatus("no-content"), 204);
```
### Leave cases to a fallback
Pass a fallback as the second argument to handle only part of the universe; it
receives the members the map leaves uncovered — here the parameter is
`"deprecated" | "gateway-timeout"`:
```ts
import { getPrimitiveUnionMatcher } from "tiny-pattern-ts";
type Status = "active" | "beta" | "deprecated" | "gateway-timeout";
const rollout = getPrimitiveUnionMatcher<Status>()(
{ active: () => "enabled", beta: () => "enabled" },
(status) => `blocked (${status})`,
);
assert.equal(rollout("active"), "enabled");
assert.equal(rollout("deprecated"), "blocked (deprecated)");
```
### Dispatch on a property union
The value does not have to be the union itself. When a single property carries a
finite union, the tagged-union matcher keys on it and narrows the whole record to
the selected value:
```ts
import { getTaggedUnionMatcher } from "tiny-pattern-ts";
interface Invoice {
readonly currency: "eur" | "usd" | "jpy";
readonly amount: number;
}
const matchCurrency = getTaggedUnionMatcher<Invoice>()("currency");
const symbolOf = matchCurrency({
eur: (i) => `€${i.amount.toFixed(2)}`,
usd: (i) => `$${i.amount.toFixed(2)}`,
jpy: (i) => `¥${i.amount.toFixed(0)}`,
});
assert.equal(symbolOf({ currency: "usd", amount: 12.5 }), "$12.50");
assert.equal(symbolOf({ currency: "jpy", amount: 900 }), "¥900");
```
### Widen the return type
When the handlers return different types, reach for the widening `W` variant: the
matcher's return is their union rather than one common type — here
`string | string[] | undefined`:
```ts
import { getPrimitiveUnionMatcherW } from "tiny-pattern-ts";
type Field = "name" | "tags" | "note";
const parse = getPrimitiveUnionMatcherW<Field>()({
name: () => "Ada",
tags: () => ["admin", "beta"],
note: () => undefined,
});
assert.equal(parse("name"), "Ada");
assert.deepEqual(parse("tags"), ["admin", "beta"]);
assert.equal(parse("note"), undefined);
```
## API
The package exports four factories. Two axes pick one:
@@ -38,7 +170,7 @@ The package exports four factories. Two axes pick one:
| `getTaggedUnionMatcherW<T>()` | the value is a discriminated object and handlers return different types | union of the handlers |
Bind the function that takes the handler map to a `match…` variable once and
reuse it; the examples below do this, so the builder is allocated once.
reuse it; the [Examples](#examples) do this, so the builder is allocated once.
### Primitive-union matchers