320 Commits
Author SHA1 Message Date
tmu 3b58b06081 🚀 Release 0.9.0
CI / release-gate (push) Successful in 2s
CI / build (push) Successful in 38s
CI / compat (push) Successful in 26s
CI / maintain (push) Failing after 14s
CI / publish (push) Successful in 20s
0.9.0
2026-09-29 21:37:53 +00:00
tmu 36e317bf17 🔀 Merge feature/ts-compat-ci into main
CI / release-gate (push) Successful in 2s
CI / build (push) Successful in 29s
CI / maintain (push) Failing after 13s
CI / compat (push) Successful in 25s
CI / publish (push) Skipped
2026-09-29 21:24:16 +00:00
tmu 0e56fff59e 📝 Check off the CI TypeScript-floor task
The backlog item asked for a 5.0 baseline; the floor is 5.9 (set by type-fest
and the library's own gate), so the text now says "the TypeScript floor".
2026-09-29 21:24:05 +00:00
tmu 24cc007ab0 🔥 Drop redundant not.toBeAny assertions in the fixture
The exact-equality `toEqualTypeOf` assertions already reject `any`, so the
`.not.toBeAny()` guards were redundant. Refresh the fixture header and
development/ci.md to match.
2026-09-29 21:23:15 +00:00
tmu ebe58a9167 📝 Say compat is CI-only and run by hand locally
The pipeline bullet's "(push to `main` / tag)" read like a local push hook.
State plainly that `compat` has no local tier — it is not in `check`, `verify`,
`test:ci` or any hook — and is run manually with `build && test:compat`.
2026-09-29 21:15:01 +00:00
tmu 00ed6bee1e ✅ Assert handler params in the compat fixture
Give the primitive-union handlers a parameter and assert its member type, and
assert the tagged-union widening handler's parameter too. A matcher without
handler-parameter assertions cannot catch a wrong or `any` handler parameter:
the matcher's own `(shape: T) => R` type is built from `T` and the handler
return, so it stays correct even when `Member<T, K>` inversion is broken.
2026-09-29 21:10:15 +00:00
tmu 75807c4bd8 👷 Type-check the TS floor in CI
Add a `compat` job that type-checks the whole suite and a consumer fixture
against the minimum supported TypeScript (5.9), reusing the `dist/` artifact
`build` produced and gating `publish`. The compiler is resolved by npx, so it
never enters `devDependencies` or the local `check`/`verify` loop.

The fixture imports the package by name, resolving the emitted declarations
through the `exports` map; `expectTypeOf` / `.not.toBeAny()` make it reject an
`any`-typed declaration, which a bare compile would accept.

Correct the README consumer floor from >= 5.0 to >= 5.9 (set by `type-fest`)
and drop the `node10` resolution claim, which the exports-only entry never
satisfied.
2026-09-29 20:51:41 +00:00
tmu 45df45df4b 🚀 Release 0.8.3
CI / release-gate (push) Successful in 3s
CI / build (push) Successful in 36s
CI / maintain (push) Successful in 16s
CI / publish (push) Failing after 18s
0.8.3
2026-09-28 22:02:16 +00:00
tmu 21b628f91d 🔀 Merge chore/prune-backlog-and-upgrade-deps into main 2026-09-28 21:59:57 +00:00
tmu 3c802ad7df ⬆️ Bump the pi-lsp pin to 0.0.47
The agent-side LSP extension is declared in `.pi/settings.json`, not in
`package.json`, so neither `npm update` nor `maintain:outdated` sees it.
Bump it with `pi install npm:@spences10/pi-lsp@0.0.47 -l`, which
rewrites the pin and refreshes the gitignored `.pi/npm/` install cache,
then reformat the file: pi writes it back in its own two-space style,
which `check:oxfmt` rejects.

0.0.47 bounds the project-binary trust prompt — it follows tool
cancellation and times out after 30 s, returning a tool error instead of
leaving the session stuck in `Working`, and an allow-once decision now
survives an idle language-server restart. The read-only usage and the
`tsc --lsp` wiring are unchanged, so `development/tooling.md` only moves
the recorded version. No changelog note: the pin is agent tooling, not
shipped code, as when it was adopted (863198d).

The running pi session still has 0.0.46 loaded; restart pi to pick up
the new version.
2026-09-28 21:58:11 +00:00
tmu 5025fa3870 🔧 Drop the redundant knip entry pattern
knip derives the public entry from package.json `exports` and resolves
it to src/index.ts itself, so naming the file in `entry` is redundant:
maintain:knip reported it as a configuration hint, and the scan is now
silent. Verified the barrel is still treated as the entry — the scope
is clean with and without dist/, and with src/index.test.ts removed.

Supersedes the workaround recorded in development/tooling.md, which
silenced an unused-file report for the barrel that the current entry
derivation no longer produces. Rewrite the section to state the
behaviour and keep the rejected `paths` mapping.
2026-09-28 21:55:05 +00:00
tmu eb1acbd9d8 ⬆️ Upgrade dependencies
Bump every direct dependency to the latest registry version: cspell
10.3.5, oxfmt 0.71.0, oxlint 1.86.0, oxlint-tsgolint 7.0.2003,
@types/node 26.6.3, the LSP protocol types 3.18.4 and 85 transitive
entries. oxfmt is the only range widened (^0.70.0 -> ^0.71.0), because
0.x minors are breaking under semver and `npm update` stops at the
range; the rest moved within their existing ranges.

`npm run maintain:outdated` is clean and `npm run verify` is green with
no rule or format fallout. `maintain:knip` reports one configuration
hint — the redundant `src/index.ts` entry pattern in knip.json — but
knip did not move, so the hint predates this bump and stays advisory.
2026-09-28 21:47:05 +00:00
tmu df09d3ae61 📝 Prune the completed backlog items
Remove every done/cancelled item. Git history is the archive and each
item's rationale already lives in the README, in development/ or in a
released changelog entry; none was recorded only here, so no item had to
be rescued into development/ before it could go.

- drop the `v1.0:` and `Matcher:` projects too: their last open task is
  done, so those headers would stand empty
- summarise the change under `[Unreleased]` in the changelog
2026-09-27 20:41:59 +00:00
tmu ac1fd60043 🚀 Release 0.8.2
CI / release-gate (push) Successful in 2s
CI / build (push) Successful in 37s
CI / maintain (push) Failing after 15s
CI / publish (push) Failing after 15s
0.8.2
2026-09-25 23:20:02 +00:00
tmu f5871c37c8 🔀 Merge chore/readme-comparison into main 2026-09-25 23:17:33 +00:00
tmu 7a1eaa0d7f 📝 Add an alternatives section
Add a short README section under Alternatives, one heading per
alternative (ts-pattern, Effect.Match, match-iz and plain switch), each
saying what it is and what tiny-pattern-ts is or does differently. State
the data-last, pipe-friendly shape in Description.

- measure the footprint: ~0.2 kB minified + gzipped, not the
  unminified ~1.8 kB; ts-pattern is ~2 kB
- correct match-iz: it ships types, it just cannot prove exhaustiveness
- drop switchcase / ts-match (unmaintained)
- check off the backlog task

Resolves: backlog "Add comparison section vs. other TS pattern-matching libs"
2026-09-25 23:16:14 +00:00
tmu 09011d838f 🔀 Merge chore/cleanup-readme into main
CI / release-gate (push) Successful in 3s
CI / build (push) Successful in 27s
CI / publish (push) Skipped
CI / maintain (push) Failing after 15s
2026-09-24 21:56:33 +00:00
tmu 8261759190 📝 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.
2026-09-24 21:55:51 +00:00
tmu 28d87b3387 📝 Close out migration guide and backlog tasks 2026-09-24 20:51:42 +00:00
tmu ccbd0a297c 🔀 Merge feature/doc-tests into main
CI / release-gate (push) Successful in 3s
CI / build (push) Successful in 29s
CI / publish (push) Skipped
CI / maintain (push) Successful in 15s
2026-09-24 13:51:41 +00:00
tmu 400fe902e0 ♻️ Run doc-test generation as an explicit CI step
Drop the `pretest:ci` lifecycle hook and call `create:doc-tests` from the
`build` job before `test:ci`. The hook hid the step from the job log and
made `test:ci` behave differently under npm than when run directly.
2026-09-24 13:08:45 +00:00
tmu 4dc58395c0 ♻️ Run doc tests outside the coverage gate
An example that exercises a line no hand-written test reaches would let
the `--100` gate pass on documentation alone. Node has no file-level
exclusion, and c8's `--exclude` only filters the report, so run the two
in separate processes: `test:coverage` runs c8 over the tracked tests
only (`git ls-files`, which skips the untracked generated files), and
`test:doc` runs the generated examples without c8. `test:ci` chains
them.
2026-09-24 12:52:42 +00:00
tmu a9b588e1c3 ♻️ Drop assert imports from doc fences
`assert` is the sole injected exception: the generated test always binds
it, so a fence must not import it. Revert the README imports and the
generator's node:assert merging; the fences keep the assigned variables
and `assert.equal` calls, and the generator again rejects a fence that
imports node:assert.
2026-09-24 12:17:24 +00:00
tmu d0dd8641f2 ✅ Assert README example results
Assign every documented matcher result to a variable and assert it with
`assert.equal`, so the compiled README doc-tests verify behavior instead
of merely running the code. The examples import `node:assert` themselves
to stay copy-pasteable; the generator now merges that import into its
prelude assert rather than rejecting it (only `node:test` remains
reserved).
2026-09-24 12:13:36 +00:00
tmu 30d97a9209 ✨ Compile doc code fences into tests
Every `ts`-tagged fence in README.md / CONTRIBUTING.md now becomes an
executed `node:test` case in a gitignored generated file, so a
documented example cannot drift from the API. The generator hoists and
merges the leading imports, rewrites the library specifier to the
`#test-tiny-pattern-ts` alias, and rejects a fence with no describing
paragraph or one that re-imports a prelude module.

CI regenerates via `pretest:ci`; `create:doc-tests` runs the generator,
formats, then typechecks the output against a scoped tsconfig that
relaxes `noUnusedLocals`. c8's default excludes already omit the
generated `*.test.ts`, so `test:ci` is unchanged.
2026-09-24 11:53:29 +00:00
tmu d1963b0329 🔀 Merge feature/api-surface into main
CI / release-gate (push) Successful in 3s
CI / build (push) Successful in 26s
CI / publish (push) Skipped
CI / maintain (push) Successful in 15s
2026-09-23 22:51:21 +00:00
tmu 0bde38d6e4 📝 Summarize the API docs work in the changelog 2026-09-23 22:51:11 +00:00
tmu 38484aa16d 📝 Refresh license year, backlog notes and JSDoc
Bump the copyright to 2026 in LICENSE and README, add the
comparison-section notes to the backlog, and trim the primitive-union
factory JSDoc now that the universe rules live in README § Caveats.
2026-09-23 22:50:05 +00:00
tmu c878e60ff2 📝 Bind the handler-map function in every example
Each README and JSDoc example now assigns the function that takes the
handler object (the factory result, or the tagged key step) to a match…
variable and reuses it, so the builder is created once instead of per
call.
2026-09-23 22:33:43 +00:00
tmu c8bee95088 📝 Document when to use each matcher
Add the universe/return axes and a "Use when" column to the README API
table, and the same one-liner to each of the four factory JSDoc blocks.
2026-09-23 22:25:39 +00:00
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
tmu 9c9468fa3c 📝 Explain the W widening suffix in the API section
State that the `W` suffix means widening and what that widens: the
matcher's return value goes from one common `R` to the union of every
handler's return type.
2026-09-23 22:11:22 +00:00
tmu f485b1bae2 ✨ Finalize and document the public API surface
Export the matcher builder types from the barrel and rename them off the
internal Strict/Widening suffixes, so the type a factory returns is
nameable: PrimitiveUnionMatcher/W, TaggedUnionMatcher/W, and the
tagged-union factory types. Add TSDoc to every public symbol — the
declarations carry it into the published package — and pin the exported
names in src/index.test.ts.

Document the API in README and record the surface decision (and the
rejected matcher-shared export) in development/library.md.
2026-09-23 21:39:15 +00:00
tmu acf9d06ddd 🚀 Release 0.8.1
CI / release-gate (push) Successful in 2s
CI / build (push) Successful in 35s
CI / maintain (push) Successful in 14s
CI / publish (push) Failing after 16s
0.8.1
2026-09-23 21:24:42 +00:00
tmu eba60f6569 🔀 Merge chore/coverage-threshold into main 2026-09-23 21:23:37 +00:00
tmu 52ce655d8e 👷 Gate CI at 100% coverage
test:ci now runs c8 with --all --include "src/**/*.ts" --100, so the
build job fails when any runtime file under src/ is untested. --all is
what makes the gate non-vacuous: without it c8 counts only the files the
suite happened to load, and a new untested module stays invisible.

Add src/index.test.ts to load the public barrel, which was previously
never imported at runtime and so read as 0% under --all. matcher-shared.ts
is types-only (an empty runtime image) and carries a file-level c8 ignore
with the reason.

Why 100% and the rejected alternatives: development/ci.md § Coverage
threshold.
2026-09-23 21:12:24 +00:00
tmu 76327a8c47 🔀 Merge chore/duplicate-tag-union-test into main 2026-09-23 20:55:56 +00:00
tmu d7004c0f71 📝 Clarify the duplicate-tag known issue 2026-09-23 20:52:45 +00:00
tmu 69231eb9ac 📝 Note the duplicate-tag tests in the changelog 2026-09-23 20:30:44 +00:00
tmu 7ff371c569 ✅ Pin the duplicate-tag union collapse 2026-09-23 20:30:35 +00:00
tmu 0339a493a2 📝 Track the TypeScript 5.0 CI baseline 2026-09-23 20:28:53 +00:00
tmu 2c856f28e3 🔀 Merge chore/open-universe-guidance into main 2026-09-23 20:01:52 +00:00
tmu 4260a4732c 📝 Document why open universes are rejected
Add a README Caveats subsection with user-facing guidance for the two
open-universe shapes: parse external input at the boundary down to a
finite union, or use a runtime Map registry for extensible domains.
Point the finite-universe bullet at it, and point the rejected
fix/open-universe-* entry in development/library.md back at the new
guidance, so rule and decision cross-reference without duplicating each
other.
2026-09-23 19:51:19 +00:00
tmu 11d364e187 🚀 Release 0.8.0
CI / release-gate (push) Successful in 3s
CI / build (push) Successful in 34s
CI / maintain (push) Successful in 15s
CI / publish (push) Failing after 19s
0.8.0
2026-09-23 17:42:32 +00:00
tmu 8dc0b95320 🔀 Merge fix/union-valued-discriminant into main 2026-09-23 17:37:37 +00:00
tmu 7b90f898d3 ✅ Cover property-union autocomplete and rejection
Add property-union tests for the completion popup, a broad value in the
union, and value/stringification collisions. Rename the existing
property-union tests to a `property union:` prefix so they can be
selected with `--test-name-pattern`, and extend the autocomplete helper
with a `key` option so a probe can complete on a property other than
`kind`.
2026-09-23 16:32:16 +00:00
tmu 1bdf7f0315 🐛 Reject mixed literal and broad universes
`UnsupportedReason` tested `IsLiteral<PatternKey<T>> extends false`.
`IsLiteral` is `boolean` for a union that mixes a literal with a broad
type (`"a" | \`x-${number}\``), so `boolean extends false` is false and
the mix was accepted. Test `extends true` instead, which rejects it.

Found while adding property-union broad-universe coverage; the gate is
shared with the primitive-union matcher.
2026-09-23 16:32:09 +00:00
tmu 55bf6c24a7 🐛 Narrow union-valued and optional discriminants
The tagged-union matcher selected handler parameters with
`Extract<T, Record<K, V>>`, which only keeps a member whose
discriminant is a singleton literal. A property that is itself a
union (`{ color: "red" | "green" | "blue" }`) or optional
(`{ type?: "x" }`) matched no member, so every handler received
`never`, and the fallback saw the whole shape instead of the
unhandled tags.

Replace the selector with `Narrowed`, which distributes over `T`,
drops a member whose `K` cannot take the tag, and narrows `K`. The
`T[K] extends V` fast path keeps an exact member (a discriminated
union's declared interface) untouched. The fallback reuses
`Narrowed` over `Exclude<Tags, HandledTags>`, so a union-valued
property narrows to the unhandled tags rather than the whole shape.

Matching a defined tag on an optional property makes the key
required (`{ type: "x" }`); the `undefined` tag yields
`{ type?: never }` under `exactOptionalPropertyTypes` (absence) or
`{ type?: undefined }` when the property admits an explicit
`undefined`.
2026-09-23 16:04:00 +00:00
tmu e7b5c0284d 🔀 Merge chore/prune-backlog into main 2026-09-23 15:27:05 +00:00
tmu 013bfa883b 📝 Prune the backlog; document the LSP shutdown fix
Remove every done/cancelled backlog item; the reasons they recorded are
already in development/ (library.md, testing.md, tooling.md, workflow.md)
and git history is the archive.

The one exception was the TS 7 LSP shutdown workaround, which lived only in
a code comment. Record it under development/testing.md § Autocomplete
(Known issue) before dropping the bug item.
2026-09-23 15:12:54 +00:00