📝 Improve docs after split

This commit is contained in:
tmu committed 2026-09-15 21:46:41 +00:00
1 parent 74c39e1346
commit 5e7d40b013
6 files changed
+17 -228

No files matched your search

+2 -2
View File
@@ -36,8 +36,8 @@ overlaid at the exact `/opt/hostedtoolcache` layout `actions/setup-node` probes.
#### Rejected
- Downloading Node in every job — the ~50 MB fetch was the original problem.
- Gitea Pages / Codecov for coverage — neither was confirmed available or wanted
(see [Coverage serving](#coverage-serving)).
- Caching Proxy (Squid or similar) — adds complexity to global setup
- Mounting the tool cache - No invalidation will fill the cache with stale versions
## Bumping Node
+1 -101
View File
@@ -3,104 +3,4 @@
The type-level design of the public API and the limitations it carries. The
user-facing reference is [README § API](../README.md#api).
## A type guard is the single primitive
#### Decision (2026-09)
Every pattern constructor returns a `Matcher<T>` whose only member is
`matches: (value: unknown) => value is T`; `Pattern<T>` is an alias.
#### Why
- A type guard is the one TypeScript construct that both narrows in an `if` and
composes into a chain, so the library needs no DSL and no transpiler.
- Because `matches` narrows, `match(value).with(pattern, handler)` types the
handler with no cast and no runtime tag.
- `Pattern<T>` is an alias, so either name is the same type.
## The builder is immutable
#### Decision (2026-09)
`.with` returns a new builder rather than mutating the current one.
#### Why
- A partially built chain can be stored and reused without one call site
affecting another.
- `.with` widens the result type to `R | V`, which cannot be represented by a
value that changes type in place.
## `exhaustive()` is a runtime check
#### Decision (2026-09)
`.exhaustive()` throws when no case matched; it does not statically prove that
every member of the input union has a case.
#### Why
- Proving coverage through a fluent chain would need a compiler plugin or a
builder that tracks an uncovered-member set — out of proportion to this
library's size.
- The union of handler return types is still type-safe; only the chain's
completeness is unchecked.
#### Known issue
- A missing case is a runtime `Error`, not a compile error. A caller wants
either an `.otherwise(...)` (which never throws) or to prove coverage
themselves. Tracking uncovered members is a possible future change.
## `P.type` takes the type as a parameter
#### Decision (2026-09)
`P.type<T>(name)` requires the caller to supply `T`; it is not inferred from the
`typeof` string.
#### Why
- A runtime string cannot carry a TypeScript type; the caller pairs the two, and
the compiler only checks that `T` is used consistently afterwards.
#### Known issue
- `T` and the runtime `name` can disagree (`P.type<number>("string")` compiles),
because nothing ties them together. Prefer `P.when` with a real type guard. A
name-to-type conditional mapping is a possible future change.
## `P.shape` narrows only through `refine`
#### Decision (2026-09)
`P.shape<S, T>(shape, refine?)` returns `Matcher<T>`, defaulting to `Matcher<S>`
without a `refine`.
#### Why
- The shape's values may be matchers, so `S` describes the check, not the value;
the `refine` guard is the explicit point where the value narrows to `T`.
- Keys are checked with `in` rather than requiring an exact match, so a shape can
match a wider object — which is how discriminated unions are handled.
#### Known issue
- Without `refine` a discriminated-union case does not narrow, so `P.shape`
alone is easy to misuse. Prefer `P.when` when a guard is already available.
## `P.any` exists for non-guard predicates
#### Decision (2026-09)
`P.any<T>(predicate)` takes a boolean predicate and a declared `T`.
#### Why
- Some checks are cheap as a boolean but awkward as a type guard (for example an
`every` over an array); `P.any` is the escape hatch.
#### Known issue
- As with `P.type`, the declared `T` is not proven by the predicate. Prefer
`P.when` whenever the check can be written as a guard.
Currently the library is placeholder code.
+1 -5
View File
@@ -82,16 +82,12 @@ release commit.
The version is derived from the graduated `## [x.y.z]` heading in
[CHANGELOG.md](../CHANGELOG.md) and written to `package.json` +
`package-lock.json` by `npm version`. The README has no version line and does
not link `package.json`.
`package-lock.json` by `npm version`.
#### Why
- `release.sh` reads the version from the changelog, so the changelog is the
input and `package.json` the derived copy — one direction, no drift.
- npm and Gitea render the version from package metadata, so a README copy would
be a third place to update for no reader benefit, and a link would only move
the lookup, not remove the copy.
#### Rejected
+2 -4
View File
@@ -174,18 +174,16 @@ rules disabled in `.oxlintrc.json`.
- The package is intentionally ESM-only (no CommonJS shim), so CJS resolution
scenarios are out of scope by design, not a bug.
### `tslib` and `type-fest` are deliberately not used
### `tslib` is deliberately not used
#### Decision (2026-09)
Neither `tslib` nor `type-fest` is a dependency.
`tslib` is not a dependency.
#### Why
- `tslib` is a runtime helper for old ES3/ES5 targets; this project targets
ES2024.
- `type-fest` was never imported.
- `knip` flagged both, the same signal that keeps the list honest.
## Git hooks and script wiring