tmu 5ddbbdc183 ♻️ Keep the public API to the four factories
The builder types are already the inferred return types and travel into
the emitted .d.ts, so exporting them only made them nameable while
pinning the internal Strict/Widening split as API. Revert the type
exports and the public renames, drop the type-surface test, and document
only the factories in README and development/library.md.
2026-09-23 22:19:02 +00:00
2026-09-23 21:12:24 +00:00
2026-09-08 20:47:22 +02:00
2026-09-23 22:19:02 +00:00
2026-09-14 12:37:58 +00:00
2026-09-06 00:13:12 +02:00
2026-09-22 09:43:08 +00:00
2026-09-23 22:19:02 +00:00
2026-02-02 13:38:42 +01:00
2026-09-23 21:12:24 +00:00
2026-09-23 14:55:27 +00:00
2025-04-29 13:43:07 +02:00
2026-09-23 21:24:42 +00:00
2026-09-23 21:24:42 +00:00
2026-09-23 22:19:02 +00:00

tiny-pattern-ts

Pattern matching for TypeScript/ESM environments (F#-style, not regex).

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 for the design decisions and Caveats for the limits.

Requirements

  • Node.js >= 26 (engines field; pinned via .node-version).
  • TypeScript >= 5.0 to consume the published declarations. The emitted .d.ts use const type parameters (TS 5.0) and keep their relative .ts specifiers; both resolve on TS >= 5.0 in node10 / node16 / nodenext / bundler.
  • The package is ESM-only (no CommonJS shim).

API

The package exports four factories. Each comes in a strict variant (one common return type R) and a widening variant: the W suffix means widening — the return value is widened from one common R to the union of every handler's return type.

Factory Return of the matcher
getPrimitiveUnionMatcher<T>() one common R
getPrimitiveUnionMatcherW<T>() the union of the handler returns
getTaggedUnionMatcher<T>() one common R
getTaggedUnionMatcherW<T>() the union of the handler returns

Primitive-union matchers

getPrimitiveUnionMatcher<T>() takes the finite universe T and returns a builder. Calling the builder with a handler map keyed by T's members returns a matcher: a function from T to the common return type.

import { getPrimitiveUnionMatcher } from "tiny-pattern-ts";

const reply = getPrimitiveUnionMatcher<"yes" | "no">()({
    yes: () => "agreed",
    no: () => "declined",
});

reply("yes"); // "agreed"

Add a fallback as the second argument to leave members unhandled; the fallback receives the remainder:

const label = getPrimitiveUnionMatcher<"yes" | "no" | "maybe">()(
    { yes: () => "agreed", no: () => "declined" },
    (other) => `not sure: ${other}`, // other: "maybe"
);

getPrimitiveUnionMatcherW is the same builder, but the matcher's return type is the union of the handler return types rather than one common R.

Tagged-union matchers

getTaggedUnionMatcher<T>() takes a discriminated union T. The returned function takes the discriminant property's name and returns the handler-map builder, keyed by that property's tags.

import { getTaggedUnionMatcher } from "tiny-pattern-ts";

type Shape =
    { kind: "circle"; radius: number } | { kind: "square"; side: number };

const area = getTaggedUnionMatcher<Shape>()("kind")({
    circle: (s) => Math.PI * s.radius ** 2,
    square: (s) => s.side ** 2,
});

area({ kind: "circle", radius: 2 });

getTaggedUnionMatcherW is the widening counterpart, exactly as in the primitive-union pair. The discriminant key is restricted to properties whose values are tags; see Caveats for the supported tags and the boolean / null / undefined key projection.

The universe

The primitive universe T must be a finite union of literals with no value/stringification collision. A broad member (string, number, a template literal) or a colliding pair (true | "true", 1 | "1") is rejected at the factory. The reasons and the rejected alternatives are in development/library.md.

Caveats

  • Only finite universes are supported. The factory must be given a finite union of literals; string, number and template literals are rejected. This is what lets the exhaustive overload be proven, so the runtime dispatch throw stays unreachable through the typed API.
  • A value and its stringification must not both be present. Object keys stringify, so a universe containing both a member and the string it stringifies to — 1 | "1", true | "true", null | "null" — is rejected at the factory. Either form alone is fine, and one value's string form may coexist with a different value's bare form ("true" | false).
  • symbol and bigint are not supported. A symbol brand is a compile-time phantom with nothing to match at runtime, and a bigint is not a valid property key; neither satisfies the matcher's universe constraint.
  • NaN and -0 cannot be matched specifically. They have no literal type, so both stay part of number.

Why open universes are rejected

An open universe — one carrying a broad member, as in type Units = "s" | "ms" | "min" | (string & {}) — is not a dispatch concern. If the values arrive from outside the program, parse them at the boundary down to a finite union and match the narrowed result; the openness never reaches the matcher. If the domain is genuinely extensible, the right shape is a runtime Map of handlers, where "no handler" is a lookup, not a pattern. Either way an open matcher would abandon the one guarantee this library exists to give — provable exhaustiveness — to automate what a switch and a default arm already cover. The type-level cost of supporting open universes is recorded in development/library.md.

License

MIT © 2025 tmu. See LICENSE.

Contributing

Contributions are documented in CONTRIBUTING.md; the reasons behind the project's decisions, rejected alternatives, and known issues live in development/. AI coding agents start at AGENTS.md.

S
Description
No description provided
Readme MIT
1.5 MiB
0 Stars 1 Watchers 0 Forks
0.9.0
Latest
2026-09-29 23:57:54 +02:00
Languages
TypeScript 88.4%
Shell 9.9%
Dockerfile 1.7%