From 5ddbbdc183f108b3628203199c9d2be4069df945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 23 Sep 2026 22:14:04 +0000 Subject: [PATCH] :recycle: 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. --- CHANGELOG.md | 5 +---- README.md | 20 ++++++++--------- development/library.md | 28 +++++++++++------------- src/index.test.ts | 49 ------------------------------------------ src/index.ts | 14 ++---------- src/primitive-union.ts | 22 ++++--------------- src/tagged-union.ts | 44 ++++++------------------------------- 7 files changed, 37 insertions(+), 145 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 278efb6..fa2779d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- export the matcher builder types from the entry point — - `PrimitiveUnionMatcher` / `PrimitiveUnionMatcherW`, `TaggedUnionMatcher` / - `TaggedUnionMatcherW`, and the tagged-union factory types — and add TSDoc to - every public symbol +- add TSDoc to the four public matcher factories - document the public API in the README ## [0.8.1] - 2026-09-23 diff --git a/README.md b/README.md index 475c998..ed02e3f 100644 --- a/README.md +++ b/README.md @@ -21,17 +21,17 @@ for the design decisions and [Caveats](#caveats) for the limits. ## API -The package exports four factories and the builder types they return. Each -factory 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. +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 | Builder type | Return of the matcher | -| -------------------------------- | ------------------------------- | -------------------------------- | -| `getPrimitiveUnionMatcher()` | `PrimitiveUnionMatcher` | one common `R` | -| `getPrimitiveUnionMatcherW()` | `PrimitiveUnionMatcherW` | the union of the handler returns | -| `getTaggedUnionMatcher()` | `TaggedUnionMatcherFactory` | one common `R` | -| `getTaggedUnionMatcherW()` | `TaggedUnionMatcherWFactory` | the union of the handler returns | +| Factory | Return of the matcher | +| -------------------------------- | -------------------------------- | +| `getPrimitiveUnionMatcher()` | one common `R` | +| `getPrimitiveUnionMatcherW()` | the union of the handler returns | +| `getTaggedUnionMatcher()` | one common `R` | +| `getTaggedUnionMatcherW()` | the union of the handler returns | ### Primitive-union matchers diff --git a/development/library.md b/development/library.md index f1d3db1..909db0e 100644 --- a/development/library.md +++ b/development/library.md @@ -12,32 +12,30 @@ is implementation detail. #### Decision (2026-09) -`src/index.ts` exports the four factories and the builder types they return: -`PrimitiveUnionMatcher` / `PrimitiveUnionMatcherW`, `TaggedUnionMatcher` / -`TaggedUnionMatcherW`, and `TaggedUnionMatcherFactory` / -`TaggedUnionMatcherWFactory`. Every exported symbol carries TSDoc. The -universe-agnostic pieces in `matcher-shared.ts` stay internal. +`src/index.ts` exports the four factories and nothing else. Every exported +function carries TSDoc; the builder types and the `matcher-shared.ts` +vocabulary stay internal. #### Why -- The builder interfaces are already the inferred return types (they appear in - the emitted `.d.ts`), so exporting them only makes them nameable — a consumer - can annotate a factory result without restating its structure. -- The `Strict` / `Widening` suffixes were dropped for the public names because - the factory names already carry the `W` axis; the type and function names line - up (`getPrimitiveUnionMatcher` → `PrimitiveUnionMatcher`). +- The factories are the whole contract: a consumer calls one and never needs to + name the builder type it returns. +- The builder interfaces are already the inferred return types, so they travel + into the emitted `.d.ts` regardless. Exporting them would only make them + nameable while freezing the internal `Strict` / `Widening` overload split as + API. - TSDoc travels into the emitted declarations, so editor hovers and the published package document the API without a hand-written `.d.ts`. -- `matcher-shared.ts` stays internal: its types are plumbing (`Handlers`, - `Fallback`, `UniverseGate`, …) whose shape follows the implementation, and the - matchers are the stable contract. #### Rejected +- **Exporting the builder types** (`PrimitiveUnionMatcher`, …). Nameable, but it + grows the surface for no call-site benefit and pins the `Strict` / `Widening` + split. - **Exporting the `matcher-shared.ts` vocabulary** (`Matchable`, `UnaryFn`, `PatternKey`, `Member`, `PatternReturns`, …). They appear in the public signatures, but a consumer never needs to name them; exporting them would - freeze internals as API. + freeze plumbing as API. - **A hand-written `.d.ts` or a separate API document.** It would drift from the implementation; TSDoc is generated from the source. diff --git a/src/index.test.ts b/src/index.test.ts index ac45a15..861a941 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -8,18 +8,8 @@ import { getPrimitiveUnionMatcherW, getTaggedUnionMatcher, getTaggedUnionMatcherW, - type PrimitiveUnionMatcher, - type PrimitiveUnionMatcherW, - type TaggedUnionMatcher, - type TaggedUnionMatcherFactory, - type TaggedUnionMatcherW, - type TaggedUnionMatcherWFactory, } from "./index.ts"; -interface Shape { - readonly kind: "circle" | "square"; -} - // The published entry point is the barrel (`package.json` exports // `./dist/index.js`), so every factory must be reachable from here. Importing it // also loads the module, which is what lets c8's `--all` measure it — see @@ -35,42 +25,3 @@ test("index: the public entry point re-exports every matcher factory", () => { expectTypeOf(getTaggedUnionMatcherW).toBeFunction(); assert.equal(typeof getTaggedUnionMatcherW, "function"); }); - -// The builder types are part of the public contract: a consumer annotates a -// factory result with them, so the exported names must match the inferred -// return types. -test("index: the primitive-union factories return the exported builder types", () => { - // Act - const strict = getPrimitiveUnionMatcher<"a" | "b">(); - const widening = getPrimitiveUnionMatcherW<"a" | "b">(); - - // Assert - expectTypeOf(strict).toEqualTypeOf>(); - assert.equal(typeof strict, "function"); - expectTypeOf(widening).toEqualTypeOf>(); - assert.equal(typeof widening, "function"); -}); - -test("index: the tagged-union factories return the exported factory types", () => { - // Act - const strict = getTaggedUnionMatcher(); - const widening = getTaggedUnionMatcherW(); - - // Assert - expectTypeOf(strict).toEqualTypeOf>(); - assert.equal(typeof strict, "function"); - expectTypeOf(widening).toEqualTypeOf>(); - assert.equal(typeof widening, "function"); -}); - -test("index: the tagged-union key step returns the exported builder types", () => { - // Act - const strict = getTaggedUnionMatcher()("kind"); - const widening = getTaggedUnionMatcherW()("kind"); - - // Assert - expectTypeOf(strict).toEqualTypeOf>(); - assert.equal(typeof strict, "function"); - expectTypeOf(widening).toEqualTypeOf>(); - assert.equal(typeof widening, "function"); -}); diff --git a/src/index.ts b/src/index.ts index 62d22fd..be33df2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,8 @@ /** * The public entry point of `tiny-pattern-ts`. * - * Exports the four matcher factories and the builder types they return. - * Everything else in `src/` is an implementation detail. + * Exports the four matcher factories and nothing else; the builder types they + * return and the rest of `src/` are implementation detail. * * @module */ @@ -10,17 +10,7 @@ export { getPrimitiveUnionMatcher, getPrimitiveUnionMatcherW, } from "./primitive-union.ts"; -export type { - PrimitiveUnionMatcher, - PrimitiveUnionMatcherW, -} from "./primitive-union.ts"; export { getTaggedUnionMatcher, getTaggedUnionMatcherW, } from "./tagged-union.ts"; -export type { - TaggedUnionMatcher, - TaggedUnionMatcherFactory, - TaggedUnionMatcherW, - TaggedUnionMatcherWFactory, -} from "./tagged-union.ts"; diff --git a/src/primitive-union.ts b/src/primitive-union.ts index 34a95e9..4ef4a5f 100644 --- a/src/primitive-union.ts +++ b/src/primitive-union.ts @@ -44,14 +44,7 @@ type MustBePartial = // Strict returns: one common `R`. Overload order is load-bearing: // #1 Handlers (first) -> the exhaustive form and the autocomplete popup // #2 Fallback (last) -> accepts a partial handler map plus a fallback -/** - * The builder returned by {@link getPrimitiveUnionMatcher}: call it with a - * handler map for every member of `T` to get a strict matcher, or with a partial - * map plus a fallback. - * - * @typeParam T - The finite universe of primitive members to match. - */ -export interface PrimitiveUnionMatcher { +interface PrimitiveUnionMatcherStrict { (handlers: Handlers & UniverseGate): UnaryFn; < R, @@ -66,14 +59,7 @@ export interface PrimitiveUnionMatcher { // Widened returns: the union of every handler's return type. `P` is inferred // from the whole handler map, whose closed constraint supplies the // contextual/autocomplete type. -/** - * The builder returned by {@link getPrimitiveUnionMatcherW}: like - * {@link PrimitiveUnionMatcher}, but the result's return type is the union of - * every handler's return type instead of one common `R`. - * - * @typeParam T - The finite universe of primitive members to match. - */ -export interface PrimitiveUnionMatcherW { +interface PrimitiveUnionMatcherWidening {

, P>>( handlers: P & UniverseGate, ): UnaryFn>; @@ -130,7 +116,7 @@ const dispatch = */ export const getPrimitiveUnionMatcher = < T extends Matchable, ->(): PrimitiveUnionMatcher => dispatch; +>(): PrimitiveUnionMatcherStrict => dispatch; /** * Create a matcher for a finite primitive universe whose return type is the @@ -151,4 +137,4 @@ export const getPrimitiveUnionMatcher = < */ export const getPrimitiveUnionMatcherW = < T extends Matchable, ->(): PrimitiveUnionMatcherW => dispatch; +>(): PrimitiveUnionMatcherWidening => dispatch; diff --git a/src/tagged-union.ts b/src/tagged-union.ts index 58199b1..97fd2f6 100644 --- a/src/tagged-union.ts +++ b/src/tagged-union.ts @@ -87,15 +87,7 @@ type MustBePartial = // Strict returns: one common `R`. Overload order is load-bearing: // #1 Handlers (first) -> the exhaustive form and the autocomplete popup // #2 Fallback (last) -> accepts a partial handler map plus a fallback -/** - * The builder returned by the key step of {@link getTaggedUnionMatcher}: call it - * with a handler map for every tag under `K` to get a strict matcher, or with a - * partial map plus a fallback. - * - * @typeParam T - The discriminated-union type to match. - * @typeParam K - The discriminant property of `T`. - */ -export interface TaggedUnionMatcher { +interface TaggedUnionMatcherStrict { (handlers: Handlers & UniverseGate>): UnaryFn; < R, @@ -112,15 +104,7 @@ export interface TaggedUnionMatcher { // Widened returns: the union of every handler's return type. `P` is inferred // from the whole handler map, whose closed constraint supplies the // contextual/autocomplete type. -/** - * The builder returned by the key step of {@link getTaggedUnionMatcherW}: like - * {@link TaggedUnionMatcher}, but the result's return type is the union of every - * handler's return type instead of one common `R`. - * - * @typeParam T - The discriminated-union type to match. - * @typeParam K - The discriminant property of `T`. - */ -export interface TaggedUnionMatcherW { +interface TaggedUnionMatcherWidening {

, P>>( handlers: P & UniverseGate>, ): UnaryFn>; @@ -137,29 +121,15 @@ export interface TaggedUnionMatcherW { // The key-taking step of the curried factory. Naming it lets the factory return // `dispatch` directly, the tacit twin of the primitive-union factory's bare // `=> dispatch`. -/** - * The function returned by {@link getTaggedUnionMatcher}: call it with the - * discriminant property's name to get the handler-map builder. - * - * @typeParam T - The discriminated-union type to match. - */ -export type TaggedUnionMatcherFactory = < - K extends Discriminated, ->( +type TaggedUnionMatcherFactory = >( k: K, -) => TaggedUnionMatcher; +) => TaggedUnionMatcherStrict; -/** - * The function returned by {@link getTaggedUnionMatcherW}: call it with the - * discriminant property's name to get the widening handler-map builder. - * - * @typeParam T - The discriminated-union type to match. - */ -export type TaggedUnionMatcherWFactory = < +type TaggedUnionMatcherWideningFactory = < K extends Discriminated, >( k: K, -) => TaggedUnionMatcherW; +) => TaggedUnionMatcherWidening; const dispatch = (k: PropertyKey) => @@ -227,4 +197,4 @@ export const getTaggedUnionMatcher = < */ export const getTaggedUnionMatcherW = < T extends object, ->(): TaggedUnionMatcherWFactory => dispatch; +>(): TaggedUnionMatcherWideningFactory => dispatch;