27 Commits
Author SHA1 Message Date
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 3b5269f9e1 🐛 Gate the universe to finite, literal keys
An open universe (`string`, `number`, a template literal) makes the
handler map index-like, so a partial object satisfied the exhaustive
overload and the runtime `dispatch` throw was reachable. A universe
containing a member and its stringification (`"true" | true`, `1 | "1"`)
collapsed two members onto one key.

- reject broad universes and value/stringification collisions at the
  factory, with a diagnostic naming the reason
- invert `PatternKey` against the universe (`Member<T, K>`) instead of
  `PatternParam<K>`, so a standalone `"true"` is typed `"true"`
- intersect `UniverseGate<T>` into the handler and fallback parameters,
  preserving `R` inference and the popup
2026-09-23 13:41:57 +00:00
tmu 2ff67801e0 ✨ Add VS Code node:test debugging and runner config
launch.json: 'Debug current test file' F5 config running
node --test --strip-types on the active file.

settings.json: register .ts with the connor4312.nodejs-testing
extension via nodejs-testing.extensions, using --strip-types (the
repo pins node >=26 with native type stripping) instead of tsx, so
the extension discovers src/*.test.ts and shows Run/Debug lenses
above test() calls in the native Testing UI.

extensions.json: recommend connor4312.nodejs-testing; cspell.json:
allow 'connor' for the extension id.

.gitignore: un-ignore .vscode/launch.json so the config is shared.
2026-09-16 17:34:53 +00:00
tmu 93cbfcf6b1 🐛 Expand the node base image through a named stage
CI / release-gate (pull_request) Successful in 2s
CI / build (pull_request) Successful in 37s
CI / publish (pull_request) Skipped
CI / maintain (pull_request) Failing after 34s
COPY --from= resolves its value as a stage name at parse time, before build
args exist, so COPY --from=node:${NODE_VERSION} collapsed to the invalid
'node:' and docker failed with 'failed to parse stage name'. ARGs in global
scope are expanded in FROM, so route through a named nodebase stage; the
per-stage ARG redeclaration keeps the tool-cache paths expanding.
2026-09-14 13:28:29 +00:00
tmu 245dfaf198 💚 Bake Node into the CI job image
setup-node never consults `node` on PATH; its only fast path is a probe of
/opt/hostedtoolcache, which the ephemeral act_runner job containers always
miss, so every job paid a ~50 MB Node download. docker/Dockerfile extends the
runner's default catthehacker/act image with the Node distribution overlaid at
the exact tool-cache layout, so setup-node finds 26.8.2 and skips the fetch
while node-version-file, cache: npm and registry-url keep working unchanged.

Image layers dedupe against the base the host already pulled and prune via
normal docker hygiene — the cleanup story a host bind of /opt/hostedtoolcache
lacks. To make the bake deterministic, .node-version is pinned to the exact
26.8.2 the image carries; scripts/runner-image.sh guards that coupling and
builds/pushes the tag the three node jobs now reference via container.image.

Ops follow-up (outside the repo): build once with
`npm run build:runner-image -- --push` on a machine with registry creds. If
the package is private, the runner needs container registry credentials in
its config.
2026-09-14 12:37:58 +00:00
tmu b29f924416 🔧 Clean dist before each build
tsc does not prune orphaned emit output, so dropping declarationMap left
stale *.d.ts.map files in dist/ until a manual clean. A prebuild hook now
empties dist/ first, keeping the build reproducible. It deliberately leaves
coverage/ alone, since the manual `clean` resets both.
2026-09-11 20:04:33 +00:00
tmu 9a08262076 📝 Track self-hosted coverage hosting plan in backlog
Record the decision to host CI coverage from a tiny dir-listing server in
the Gitea docker setup (shared volume keyed by repo/tag) instead of the
GitHub upload-artifact zip. This unblocks dropping the coverage artifact
from the Gitea CI port. Adds the lipanski docker-image author to cspell.
2026-09-10 19:40:35 +02:00
tmu 93cb37441d 📝 Document pi-lsp tooling and agent usage
README § Tooling and § Tooling decisions record the extension and the
reasoning behind it: read-only by design, auto-detects TypeScript 7,
and never a correctness gate — verify is. AGENTS.md § First action tells
agents the lsp_* tools exist, to prefer lsp_references over grep -w for
colliding identifiers, and to treat empty LSP output as inconclusive.

Close the backlog evaluation task with a resolved note, and allowlist
the tsgo/tsserver tool names for cspell.
2026-09-08 20:47:40 +02:00
tmu 75d605fabe 📝 Add backlog.tasks template and format rules
Introduce backlog.tasks, a project backlog in vscode-todotasks format
(not Markdown), seeded as a reusable template: Setup, v1.0, Bugs,
Enhancements, Documentation and Maintenance projects, with the
implementation tasks to be filled in later.

Document the format in AGENTS.md so agents can read and update the file,
recommend sandy081.todotasks in .vscode/extensions.json, and whitelist
"todotasks" for cspell, which both new mentions otherwise fail.

The branch, review and release policy is deliberately not asserted here:
it is unsettled, so it is tracked as a task-group under Setup and AGENTS.md
tells agents to take branch and merge instructions from the user until that
work is documented.
2026-09-08 11:42:46 +02:00
tmu 837203c29e 📝 Rename test-loop "blue" phase to "type"; cite TDD
The type-first spec step was labelled "Blue", which collides with the
Red-Green-Blue convention where blue means the refactor phase (and our
loop already has an explicit Refactor step). Rename phase 1 to Type and
describe the loop as type -> red -> green -> refactor.

Tie the discipline to its established name: Type-Driven Development
(Edwin Brady), quoting the Idris framing -- treat the type as the plan,
let the compiler/type-checker drive you to a program that satisfies it.
Section heading becomes "Testing discipline (type-driven)"; update the
AGENTS.md pointer + anchor. Add "idris" to the cspell dictionary.

De-duplicate: CONTRIBUTING.md no longer re-lists the banned directives
(@ts-ignore, as casts, ...); it links to AGENTS.md "Never do", the
single home for that rule, so the lists can't drift.
2026-09-07 23:44:35 +02:00
tmu 09c5b129bb ✨ Add pubv-based release.sh and npm run release
Release front-door: pubv runs preflight + the interactive semver
heuristic and graduates CHANGELOG.md [Unreleased] -> dated; npm version
syncs package.json + lockfile; git commit --amend folds them into
pubv's single release commit; the tag is created AFTER the amend so it
marks the exact commit on main that gets published; then push. Tags are
v-less (--tag-prefix=none) to match the changelog compare refs.

@runwisp/pubv added as a devDependency. The short "how we got here /
what was rejected" note lives as a comment atop the script; the docs/
exploration was removed as too much to read.
2026-09-07 18:17:28 +02:00
tmu b9fb054176 🔧 Point repo metadata at Gitea; drop redundant .npmignore
The repository.url shipped github.com/tmueller/... but the real remote
is gitea.e1nsnull.de/tmu/..., so the npm page's Repository link 404'd.
Add homepage + bugs, fix the URL, and rename the CONTRIBUTING "GitHub"
reference to the forge. Delete .npmignore: when a package.json `files`
allowlist is present npm never consults .npmignore, so it was dead
weight that would only drift. Add "gitea" to the cspell dictionary.
2026-09-06 14:42:35 +02:00
tmu 1a749b13d6 📝 Reconcile project-specs.md and cspell.json with the feature/setup branch 2026-09-05 00:13:10 +02:00
tmu 2b3ef0f721 ✨ Add oxlint-tsgolint for type-aware linting
Activates type-aware rules via oxlint --type-aware, backed by
oxlint-tsgolint (TypeScript-Go). Catches unsafe type assertions,
unnecessary type parameters, and other issues regular oxlint
cannot see.

- Add oxlint-tsgolint devDep + 6 platform-specific native bindings
  as optionalDependencies (same pattern as oxlint)
- Add --type-aware flag to check:oxlint
- Add 6 @oxlint-tsgolint/* platforms to check:outdated ignore list
- Disable 3 type-aware rules in .oxlintrc.json with rationale:
  - typescript/no-unsafe-type-assertion, typescript/no-unnecessary-type-parameters:
    fire on legitimate generic type machinery in keysMatch/MatchBuilder
    that needs type-system restructuring (deferred to a follow-up)
  - typescript/no-floating-promises in test files: expectTypeOf() is
    a sync type-assertion library that oxlint-tsgolint misidentifies

Code simplifications enabled by the new strict checks:
- src/match.ts: drop value as unknown casts (T is already assignable
  to unknown) and the redundant run(value) as R cast
- src/index.test.ts: drop unnecessary 'X' as 'X | Y' assertions in
  match<...>(...) calls (literals are already assignable to the union)

Documentation updates in project-specs.md and README.md. Add
'tsgolint' to cspell word list.
2026-09-04 23:20:02 +02:00
tmu ec98223f8d ✨ Add knip for unused dependency and dead code detection
- Add check:knip using --include dependencies,exports,files
  (skips the noisy 'types' category, which produces false positives
  for libraries whose exported types are part of the public API)
- Remove tslib and type-fest (both caught as unused by knip)
- Add 'knip' to cspell word list
- No knip config file: the --include flag keeps the scope targeted
  without boilerplate, matching the 'keep it simple' principle
- Document in project-specs.md (CHECK section) and README.md
2026-09-04 23:08:56 +02:00
tmu 3fa72820df ✨ Add @arethetypeswrong/cli (attw) as publish:attw
Validates the emitted .d.ts declarations against multiple TypeScript
module-resolution scenarios. Same rationale as publint: it validates
the publishable artifact, not the source, so it belongs in the
publish: prefix, not check:.

- Add publish:attw script using --profile esm-only (the package is
  intentionally ESM-only; CJS resolution is out of scope by design)
- Add step to CI publish job, after publish:publint and before
  npm publish
- Document the script and the esm-only rationale in project-specs.md
  and README.md
- Add 'attw' and 'arethetypeswrong' to cspell word list
- Remove unused 'stricter' word that was added speculatively before
2026-09-04 18:08:23 +02:00
tmu 01d1084e1f 🚚 Rename publint script to publish:publint and document prefix convention
publint validates the publishable artifact (dist/ vs package.json),
not the source. It should run only at publish time, in the CI publish
job, not on every commit.

- Rename check:publint -> publish:publint
- Remove from 'check' chain
- Add 'publish:publint' as a step in the CI publish job, right
  before 'npm publish'
- Introduce a 'publish:' script prefix for publish-time-only scripts
- Document the prefix convention (check:, fix:, test:, publish:)
  in project-specs.md (as a top-level subsection under Scripts)
  and in the README
- Add 'publint' and 'stricter' to cspell word list

A new script should pick the prefix that matches its lifecycle, not
invent a new one. The 'publish:' prefix has no 'npm run publish'
aggregator by design (publishing is CI-only).
2026-09-04 18:01:01 +02:00
tmu c6f7a5d1b0 🔧 Reconcile config/doc contradictions and remove stale entries 2026-09-03 22:08:01 +00:00
tmu 7349d0dec2 🔥 Remove sort-package-json in favor of oxfmt's built-in sortPackageJson 2026-09-03 20:58:17 +00:00
tmu 0c3e51c768 ✅ Ignore coverage directory in .gitignore 2026-09-03 13:54:34 +00:00
tmu ab6eb0f34c 📚 Sync project-specs.md and package.json with current state
project-specs.md:
- Replace Prettier/ESLint/Vite/Vitest references with oxfmt/oxlint/oxc
  and TypeScript 7 / node --test
- Drop @tsconfig/strictest note; rules are inlined in tsconfig.json,
  enumerated explicitly
- Document oxlint rule disables (no-undefined, sort-keys, id-length,
  no-named-export) and test-file overrides (no-unused-expressions,
  no-empty-file, no-nodejs-modules, no-magic-numbers)
- Document Node 26 / .node-version / engines.node / CI follow
- Document allowImportingTsExtensions + rewriteRelativeImportExtensions
  for the node --strip-types quirk
- Replace Vite/vitest coverage example with c8 + node --test
- Sync scripts section: drop check:eslint, check:prettier and their
  fix:* counterparts; add check:oxlint/check:oxfmt/fix:oxlint/fix:oxfmt
- Add current source layout (index.ts, match.ts, pattern.ts, index.test.ts)
- Update VSCode integration section with actual settings.json and
  extensions.json contents
- Document the actual commit history with gitmoji prefixes
- Fix 'initilizing' typo
- Drop 'changesets is a devDep' claims (changesets is not installed)

package.json:
- Move @oxfmt/binding-* and @oxlint/binding-* from devDependencies to
  optionalDependencies so the right binding is selected per platform
  (CI on Ubuntu gnu gets the gnu binding automatically, not the
  local musl one)
- Extend check:outdated to ignore the platform-specific bindings (they
  show as 'not installed' on the current platform, which is correct
- Add cspell words: gitmoji, dbaeumer, msvc (the latter for the Windows
  binding variant). Drop the unused 'nocheck' word

README.md:
- Drop the 'changesets is available as a devDep' line; changesets
  isn't installed
2026-09-03 13:42:37 +00:00
tmu cb5b302238 🔧 Remove Prettier from editor formatter config
Prettier is no longer a project dependency and oxc.oxc-vscode handles
formatting for all file types the project touches. Switch [json],
[jsonc], [markdown], [mdx], and [yaml] formatters from
esbenp.prettier-vscode to oxc.oxc-vscode (oxfmt under the hood,
Prettier-compatible for JS/TS and native for JSON/MD/YAML).

Drop 'esbenp' from cspell dictionary (no longer referenced).
2026-09-03 13:09:53 +00:00
tmu 77dc40da07 🔧 Declare Node 26 as the supported runtime
- Add .node-version (single line: '26') so fnm/nvm/volta/mise and the
  VS Code Node version manager extension auto-switch when entering the
  project directory
- engines.node in package.json: bump from '>=22.6' to '>=26' (npm-side
  install-time declaration)
- Drop --experimental-strip-types flag from node --test invocations:
  Node 26 has --strip-types unflagged
- .github/workflows/ci.yml: switch from hard-coded node-version: 24 to
  node-version-file: .node-version in both build and publish jobs so CI
  follows the .node-version file (single source of truth)
- .npmignore: add .node-version (developer-tool file, not for publish)
- .vscode/settings.json: pin js/ts.tsdk.path to node_modules/typescript/lib
  so the editor uses the project's TS 7, not a different global install
- .vscode/extensions.json: swap ms-vscode.vscode-typescript-next for
  typescriptteam.native-preview (the official TS team extension that
  ships nightly TS support, including TS 7)
- cspell: add 'esbenp' and 'typescriptteam' to dictionary
- package-lock.json: regenerated for engines.node >=26

tsconfig.json target/lib are unchanged: es2024 is the highest stable ES
target TS 7.0.2 ships, and Node 26 supports all ES2024 features natively.
2026-09-03 12:45:32 +00:00
tmu e5df17310e ✅ Add expect-type for type-level tests
- Install expect-type@1.4.0 (devDependency)
- New src/index.test.ts exercises the public API with both runtime
  assertions (node:assert/strict inside node:test) and type-level
  assertions (expectTypeOf().toEqualTypeOf, toMatchTypeOf, toHaveProperty)
- Tests are excluded from tsc -p tsconfig.build.json (no .d.ts pollution
  in dist/)
- Type-level checks happen during tsc --noEmit (run by 'npm run check' and
  the test script); expectTypeOf assertions have no runtime side effect,
  so they pair naturally with node --test in the same test() blocks
- cspell: add EDITMSG to dictionary (used in commit-message-template path)
- oxlint test-file overrides: silence no-nodejs-modules (we use node:
  builtins) and no-magic-numbers (literals in tests are fine)

Coverage:
- match() returns a builder with with/exhaustive/otherwise
- P.literal narrows to its literal type, P.type to its typeof target
- exhaustive() returns the union of handler return types
- otherwise() falls back when no case matches
- exhaustive() throws when no case matches
2026-09-03 10:39:15 +00:00
tmu 6c67e91e68 🔥 Remove Vite scaffold leftovers
- Delete public/vite.svg, src/typescript.svg, src/style.css
- Empty public/ directory removed
- .npmignore: drop 'public/' entry (directory no longer exists), add
  .oxlintrc.json and .oxfmtrc.json to ignore list
- cspell: add .oxfmtrc, .oxlintrc, .sortpackagerc to dictionary so
  config-file name references aren't flagged
- lefthook: drop 'outdated' from pre-commit (kept in commands block for
  explicit CI invocation); it shouldn't block commits for being behind on
  upstream patch releases
2026-09-03 10:28:08 +00:00
tmu 078a8c30fd 🔧 Replace ESLint and Prettier with oxlint and oxfmt
- Drop .eslintrc.cjs and .prettierrc, add .oxlintrc.json and .oxfmtrc.json
- oxlint covers correctness/suspicious/perf/style/restriction categories
  plus import and typescript plugins; oxfmt reads the same .prettierrc-style
  options we had before
- Update lefthook.yml pre-commit jobs to run oxlint/oxfmt instead of
  eslint/prettier
- Update .vscode/extensions.json (oxc replaces eslint/prettier/vitest) and
  settings.json (oxc becomes the default formatter for TS/JS)
- Add 'oxlint', 'oxfmt', 'oxc', 'nodenext' to cspell dictionary
- Rewrite README 'Development' section to describe the new toolchain
2026-09-03 10:25:47 +00:00
Thomas Müller 1d2e78dde7 🔧 Added cspell configuration for spelling checks 2025-04-29 13:42:20 +02:00