📝 Improve docs after split
This commit is contained in:
1 parent
74c39e1346
commit
5e7d40b013
6 files changed
+17
-228
No files matched your search
+2
-2
@@ -12,7 +12,7 @@ the rules so agents and humans don't diverge.
|
||||
1. Clone the repository.
|
||||
2. Install Node.js >= 26 — see [.node-version](./.node-version); the exact pinned
|
||||
version is what CI and the runner image use.
|
||||
3. `npm install`.
|
||||
3. `npm ci`.
|
||||
4. `npm run setup` — the one-time clone configuration (currently registers the
|
||||
commit-message template).
|
||||
|
||||
@@ -20,7 +20,7 @@ the rules so agents and humans don't diverge.
|
||||
|
||||
- **Build:** `npm run build`
|
||||
- **Test:** `npm run test`, `npm run test:ci`
|
||||
- **Watch:** `npm run watch`
|
||||
- **Watch:** `npm run watch` - re-runs tests on file save, humans only
|
||||
- **Checks:** `npm run check`, `npm run fix`
|
||||
- **Verify:** `npm run verify` — the definition of done
|
||||
- **Maintenance:** `npm run maintain` — advisory only
|
||||
|
||||
@@ -4,10 +4,6 @@ Pattern matching for TypeScript/ESM environments (F#-style, not regex).
|
||||
|
||||
## Synopsis
|
||||
|
||||
```sh
|
||||
npm install tiny-pattern-ts
|
||||
```
|
||||
|
||||
```ts
|
||||
import { match, P } from "tiny-pattern-ts";
|
||||
|
||||
@@ -20,14 +16,6 @@ const reply = (answer: "yes" | "no") =>
|
||||
reply("yes"); // "agreed"
|
||||
```
|
||||
|
||||
### 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).
|
||||
|
||||
## Description
|
||||
|
||||
`tiny-pattern-ts` gives TypeScript the shape of F#-style pattern matching:
|
||||
@@ -43,6 +31,14 @@ The type-level contract is the feature — see
|
||||
[development/library.md](./development/library.md) for the design decisions and
|
||||
the known limitations.
|
||||
|
||||
## 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).
|
||||
|
||||
## Examples
|
||||
|
||||
### Literal matching and `exhaustive()`
|
||||
@@ -163,108 +159,7 @@ const firstNumber = (items: readonly unknown[]): number | undefined =>
|
||||
|
||||
## API
|
||||
|
||||
### `match(value)`
|
||||
|
||||
```ts
|
||||
const match: <T>(value: T) => MatchBuilder<T, never>;
|
||||
```
|
||||
|
||||
Starts a matching chain for `value`. The builder is immutable: every `.with`
|
||||
returns a new builder, so a partially built chain can be reused.
|
||||
|
||||
#### `.with(pattern, handler)`
|
||||
|
||||
```ts
|
||||
with<U extends T, V>(pattern: Matcher<U>, handler: (value: U) => V): MatchBuilder<T, R | V>;
|
||||
```
|
||||
|
||||
Adds a case. `handler` receives the value narrowed to `U`, and its return type
|
||||
`V` is added to the builder's result union `R`. A pattern whose narrowed type is
|
||||
not assignable to the matched value's type is a compile error.
|
||||
|
||||
#### `.exhaustive()`
|
||||
|
||||
```ts
|
||||
exhaustive(): R;
|
||||
```
|
||||
|
||||
Returns the result of the first matching case. Throws
|
||||
`tiny-pattern-ts: match.exhaustive() called with no matching case` if none
|
||||
matched. It does not statically prove that every union member is covered.
|
||||
|
||||
#### `.otherwise(handler)`
|
||||
|
||||
```ts
|
||||
otherwise(handler: (value: T) => R): R;
|
||||
```
|
||||
|
||||
Like a final catch-all case: runs `handler` if no earlier case matched. Unlike
|
||||
`.exhaustive()`, it never throws.
|
||||
|
||||
### `P.literal(value)`
|
||||
|
||||
```ts
|
||||
const P.literal: <const L extends string | number | boolean | null | undefined>(
|
||||
value: L,
|
||||
) => Matcher<L>;
|
||||
```
|
||||
|
||||
Matches a single literal with `===` and narrows to its literal type.
|
||||
|
||||
### `P.type(type)`
|
||||
|
||||
```ts
|
||||
const P.type: <T>(
|
||||
type: "string" | "number" | "boolean" | "bigint" | "symbol" | "undefined" | "object" | "function",
|
||||
) => Matcher<T>;
|
||||
```
|
||||
|
||||
Matches a `typeof` result and narrows to the explicitly supplied `T`. `T` is not
|
||||
inferred from the name, so the type parameter and the runtime name must agree.
|
||||
|
||||
### `P.when(predicate)`
|
||||
|
||||
```ts
|
||||
const P.when: <T>(predicate: (value: unknown) => value is T) => Matcher<T>;
|
||||
```
|
||||
|
||||
Wraps a type guard as a matcher. This is the constructor to prefer when you can
|
||||
express the check as a guard.
|
||||
|
||||
### `P.any(predicate)`
|
||||
|
||||
```ts
|
||||
const P.any: <T>(predicate: (value: unknown) => boolean) => Matcher<T>;
|
||||
```
|
||||
|
||||
Wraps a boolean predicate and declares the narrowed type `T` yourself. Use it
|
||||
only when a type guard is not expressible; prefer `P.when`.
|
||||
|
||||
### `P.shape(shape, refine?)`
|
||||
|
||||
```ts
|
||||
const P.shape: <S extends object, T extends S>(
|
||||
shape: S,
|
||||
refine?: (value: S) => value is T,
|
||||
) => Matcher<T>;
|
||||
```
|
||||
|
||||
Matches an object that has every key of `shape`. A shape value that is a
|
||||
`Matcher` is applied; otherwise the value is compared with `===`. Pass `refine`
|
||||
to narrow to `T`; without it, the matched type is `S`.
|
||||
|
||||
### Types
|
||||
|
||||
```ts
|
||||
interface Matcher<T> {
|
||||
readonly matches: (value: unknown) => value is T;
|
||||
}
|
||||
|
||||
type Pattern<T> = Matcher<T>;
|
||||
```
|
||||
|
||||
Every pattern constructor returns a `Matcher<T>`. `Pattern<T>` is an alias kept
|
||||
for readability.
|
||||
Yet to be implemented
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+2
-2
@@ -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
@@ -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.
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user