20 KiB
Project Specifications for TypeScript NPM Module
- name of package: tiny-pattern-ts
0. References
typescript-lib-starter-tiny => https://github.com/tmueller/typescript-lib-starter-tiny/
1. Development Environment
-
TypeScript: Use strictest practical rules. Inline in
tsconfig.json(not via@tsconfig/strictest). The rule set is:strict,noImplicitAny,noImplicitThis,alwaysStrict,strictNullChecks,strictFunctionTypes,strictBindCallApply,strictPropertyInitialization,noImplicitReturns,noFallthroughCasesInSwitch,noUncheckedIndexedAccess,noImplicitOverride,noUnusedLocals,noUnusedParameters,forceConsistentCasingInFileNames,isolatedModules,verbatimModuleSyntax. -
EditorConfig: Use
.editorconfigfrom typescript-lib-starter-tiny. -
oxfmt: Rust-based formatter, Prettier-compatible. Replaces Prettier. Config in
.oxfmtrc.json(same shape as.prettierrc). -
oxlint: Rust-based linter. Replaces ESLint. Config in
.oxlintrc.jsonwithtypescript,unicorn,oxc,importplugins. Categories enabled as errors:correctness,suspicious,restriction. As warnings:perf,style.nurseryis off. -
oxlint rules disabled by design (in
.oxlintrc.json):eslint/no-undefined— we useundefinedas the no-match sentinel.eslint/sort-keys— handler/case order is semantic, not alphabetical.eslint/id-length—T,R,U,Vare standard TS generics.import/no-named-export— false positive on library entry re-exports.- Stylistic rules superseded by oxfmt (oxfmt is the canonical
formatter; these rules either conflict with its output or
duplicate features oxfmt already provides):
eslint/one-var— oxfmt uses comma-joinedconstdeclarations; oxlint wanted to split them.import/group-exports— oxfmt keeps separateexportstatements as-is.import/exports-last— statement ordering is up to oxfmt.eslint/sort-imports— replaced by oxfmt's built-insortImports(enabled in.oxfmtrc.json).import/consistent-type-specifier-style— inlineimport { type X, Y }is intentional for grouping.unicorn/prefer-export-from— conflicts with how the barrelsrc/index.tsre-exports throughsrc/match.ts.typescript/method-signature-style— method signatures ininterfaceare conventional TS ergonomics. For*.test.tsfiles additionally:no-unused-expressions(forexpectTypeOf(...)calls),no-empty-file(we have a single import per test file in some cases),import/no-nodejs-modules(we usenode:test/node:assert/node:fsintentionally),eslint/no-magic-numbers(literals in tests are fine).
-
Import sorting: built into oxfmt (no separate plugin).
-
cspell: Basic spelling configuration. Words dictionary in
cspell.jsoncovers tooling names (oxlint,oxfmt,oxc,nodenext,oxfmtrc,oxlintrc,EDITMSG,typescriptteam,gitmoji,dbaeumer,msvc) and a few library names (tslib,typefest,lefthook). -
Lefthook: Pre-commit checks (run in parallel, lefthook v2 schema). Single source of truth for the underlying commands is in
package.json#scripts; the lefthook config only describes what to run on which files. Hooks that should run on staged files (oxlint,oxfmt,cspell) set theLEFTHOOK_FILESenv var to the staged-files list via ash -cwrapper, and the npm script uses${LEFTHOOK_FILES:-<default>}to default to the full project when invoked manually:pre-commit.commands.oxlint(glob*.{ts,tsx,js,jsx,mjs,cjs}):sh -c 'LEFTHOOK_FILES="$0" npm run check:oxlint' {staged_files}pre-commit.commands.oxfmt(glob*.{ts,tsx,js,jsx,mjs,cjs}):sh -c 'LEFTHOOK_FILES="$0" npm run check:oxfmt' {staged_files}pre-commit.commands.cspell:sh -c 'LEFTHOOK_FILES="$0" npm run check:cspell' {staged_files}pre-commit.commands.typecheck:npm run check:tsc(no file args needed)outdatedis intentionally NOT a pre-commit check (it can flag upstream patch releases that aren't actionable locally); it runs in CI and via the explicitlefthook run outdatedcommand.
-
Additional Dev Dependencies:
- lefthook
- check-outdated
- c8 (coverage for
node --test) - expect-type (type-level assertions in tests)
- oxfmt, oxlint (with their native bindings as
optionalDependenciesso the correct binding is selected per platform automatically) - typescript (TS 7)
- @types/node
- tslib (available for future runtime helper imports)
- type-fest (utility types)
2. Build & Test
- Build Tool: TypeScript 7 (
tsc) — no bundler, no Vite. The build is a plaintsc -p tsconfig.build.jsoninvocation that emits ESM JavaScript and.d.tsdeclarations todist/. ESM only, no CJS. - Testing: Node's built-in
node --testwith--strip-types(Node 22.6+, unflagged on Node 24/26). Test files are co-located with source as*.test.ts. No Vitest. - TypeScript Build Output:
distdirectory (set intsconfig.build.json#outDir;tsconfig.jsonkeepsoutDirfor editor tooling but excludes tests from emission viatsconfig.build.json#exclude). - Import extensions: Source uses
.tsextensions in imports (e.g.from "./match.ts") sonode --strip-typesresolves them at test time. TypeScript'srewriteRelativeImportExtensions(intsconfig.build.json) rewrites these to.jsin the emitteddist/*output, so consumers see conventional ESM imports. - TypeScript Compiler Options Notes:
allowImportingTsExtensions: trueis set in the roottsconfig.json(works because the root config hasnoEmit: true).rewriteRelativeImportExtensions: trueis set intsconfig.build.jsonto rewrite.tsto.json emit.module: nodenext,moduleResolution: nodenextfor ESM-first Node packages.verbatimModuleSyntax: trueenforces explicitimport type.target: es2024,lib: ["es2024"](Node 26 supports all ES2024 features natively).
- Additional Dev Dependencies (for types and tests):
tslib— available for runtime helper imports; not currently used by source (kept for future use per the original spec).type-fest— utility types; not currently used by source (kept for future use per the original spec).@types/node— types fornode:test,node:assert,node:fs.
- Node Version:
>=26(engines field). Pinned via.node-versionfor fnm/nvm/volta/mise auto-switching and CI (actions/setup-node@v4withnode-version-file: .node-version).
3. Project Structure & Files
- .gitignore: Ignore
dist,node_modules,coverage, and other common files. - .node-version: Single line containing the Node major version
(currently
26). Used by version managers and CI. - .npmignore: Ignore
node_modules/,coverage/,*.log,*.tsbuildinfo,src/,.vscode/,.editorconfig,.oxfmtrc.json,.oxlintrc.json,.node-version,cspell.json,lefthook.yml,commit-message-template. (Note:dist/is included viapackage.json#files, not by absence from.npmignore.) - .oxfmtrc.json: oxfmt configuration (Prettier-shaped).
- .oxlintrc.json: oxlint configuration.
- .oxlintrc.json + .oxfmtrc.json replace the old
.eslintrc.cjsand.prettierrc. - LICENSE: MIT.
- README.md: Scaffolded.
- commit-message-template: From typescript-lib-starter-tiny.
- Target Environments: Node 26 LTS only (no browser target; this is a pure Node library, no DOM, no DOM lib in tsconfig).
- No React, No CJS, ESM only.
4. Automation & Quality
- Version Automation: Use standard
npm versionfor versioning. - Unused Dependency Check: Use
check-outdated(devDep, runs in CI and via the explicitlefthook run outdatedcommand). - No commitlint, no conventional commits. Commits use gitmoji
prefixes (e.g.
:sparkles:,:wrench:,:bug:,:fire:,:white_check_mark:,:tada:) for at-a-glance categorization.
5. Scripts
SETUP
use:git-commit-message: Set up commit message template (if needed).
TEST
test: Runtsc --noEmitthennode --test --strip-types src/.test:unit: Run unit tests withnode --test --strip-types src/(no preceding typecheck).test:ci: Run tests in CI mode with c8 coverage (text + lcov + html reporters), uploadingcoverage/as an artifact.
BUILD
build: Build the project usingtsc -p tsconfig.build.json(emitsdist/*.js+dist/*.d.ts+ sourcemaps, with.tsimports rewritten to.js).
CLEAN
clean: Removedist/vianode -e "fs.rmSync('dist', {recursive:true, force:true})". (Replacesclean:buildfrom the original spec — same effect, norimrafdep needed.)
CHECK
check: Run all checks in order —check:oxlint,check:oxfmt,check:tsc,check:cspell,check:outdated.check:oxlint:oxlint ${LEFTHOOK_FILES:-src}— lintssrc/by default; when invoked from the lefthook pre-commit hook withLEFTHOOK_FILESset to the staged-files list, lints only those files. This is the single source of truth for the oxlint command and is shared between the manualnpm run checkand the pre-commit hook. oxlint only understands JS/TS-family languages, so config files (JSON/YAML/Markdown) are intentionally outside its scope; they are checked only by oxfmt.check:oxfmt:oxfmt --check ${LEFTHOOK_FILES:-.}— formats the whole project (.) by default, including JS/TS, JSON/JSONC, YAML, Markdown, MDX and other supported file types. From lefthook pre-commit, theLEFTHOOK_FILESenv var scopes to the staged files matching*.{ts,tsx,js,jsx,mjs,cjs,json,jsonc,yaml,yml,md,mdx}. Built-insortPackageJson: truekeepspackage.jsonkeys alphabetized (replaces the formersort-package-jsontool). Built-in import sorting (enabled viasortImports: true) replaces any external import-sort plugin.check:tsc:tsc(noEmit is set intsconfig.json).check:cspell:cspell lint ${LEFTHOOK_FILES:-.}— walks the project root by default; from lefthook, only the staged files.check:outdated:check-outdated --ignore-pre-releases --ignore-packages @oxfmt/binding-*,@oxlint/binding-*. The oxc native bindings are declared asoptionalDependenciesso the correct one is selected per platform automatically; the*-platform bindings show as "not installed" on the current platform and are explicitly ignored here.
FIX
fix:oxlint:oxlint --fix src.fix:oxfmt:oxfmt ${LEFTHOOK_FILES:-.}— same scoping ascheck:oxfmt(whole project by default, staged files from lefthook). Writes changes in place. (There is nofixaggregator in the scripts; run thefix:*scripts individually.)
HOOKS
- Lefthook runs the relevant
check:*scripts on staged files in parallel for pre-commit. Seelefthook.yml.
6. Repository & CI/CD
- Repository: Hosted on GitHub.
- Build Pipeline: GitHub Actions (
.github/workflows/ci.yml).buildjob on push and pull_request tomainand onworkflow_dispatch. Steps:actions/checkout@v4,actions/setup-node@v4(withnode-version-file: .node-version,cache: npm),npm ci,npm run build,npm run check,npm run test:ci, then uploadcoverage/as an artifact.publishjob: only onrefs/tags/*, depends onbuild. Steps:actions/checkout@v4,actions/setup-node@v4(withnode-version-file: .node-versionandregistry-url),npm ci,npm run build,npm publish --access publicwithNODE_AUTH_TOKENfrom secrets.
7. Versioning & Publishing
- Version Update: Use
npm versionto bump version after merging to main and before publishing. - Publishing to npm: Only publish from CI on tagged commits.
- Recommended Workflow:
- Develop and merge PRs to main
- Run all checks via CI
- Bump version with
npm version <patch|minor|major> - Push tag to GitHub
- CI builds and publishes to npm on tag
8. NPM Keywords
- pattern-matching
- pattern
- match
- algebraic-data-types
- adt
- typescript
The library is for pattern matching (not regex), similar to F#'s pattern matching, for TypeScript/ESM environments.
9. Code Coverage
- Tool: c8 (V8-native coverage, no instrumentation step).
- Configuration: c8 has no project config; the report shape is
pinned in the
test:ciscript:
"test:ci": "c8 --reporter=text --reporter=lcov --reporter=html node --test --strip-types src/"
- Reporters: text summary, HTML, and lcov (matching the original spec).
- CI:
coverage/is uploaded as a workflow artifact viaactions/upload-artifact@v4(see.github/workflows/ci.yml). - Optional: Coverage thresholds can be added in c8 config when the library surface stabilizes.
10. Source Structure & Tree Shaking
- Source Directory: All source code resides in
src/and is exported viasrc/index.ts. - Configuration:
sideEffects: falseinpackage.json(set).- ESM-only exports;
package.json#exportsfield maps.to{"types": "./dist/index.d.ts", "import": "./dist/index.js"}. - Avoid top-level side effects in modules.
- Explicit re-exports in
index.tsfor best results.
- Source structure (current):
src/index.ts— public barrel.src/match.ts—match(value).with(...).exhaustive() / .otherwise(...)builder.src/pattern.ts—P.literal,P.type,P.when,P.any,P.shapeconstructors and theMatcher<T>interface.src/index.test.ts— runtime + type-level tests usingnode --test+expect-type.
- The human will implement the source code.
11. Included Templates from typescript-lib-starter-tiny
.editorconfig
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 4
insert_final_newline = true
max_line_length = 80
trim_trailing_whitespace = true
quote_type = double
[*.md]
max_line_length = 0
trim_trailing_whitespace = false
[COMMIT_EDITMSG]
max_line_length = 0
commit-message-template
# If applied, this commit will... (Max 50 char)
# Explain why this change is being made (Max 72 Char) [WHAT and WHY vs HOW]
# Provide links or keys to any relevant tickets, articles or other resources
Resolves #...
# --- COMMIT END ---
# Remember to
# Use the imperative mood in the subject line
# Capitalize the subject line
# Do not end the subject line with a period
# Separate subject from body with a blank line
# Use the body to explain what and why vs. how
# Can use multiple lines with "-" for bullet points in body
README.md Structure (scaffolded)
- Project title and description
- Development
- Build:
npm run build - Test:
npm run test,npm run test:ci - Checks:
npm run check,npm run fix:oxfmt,npm run fix:oxlint
- Build:
- Tooling section listing TypeScript 7, node --test, c8, oxlint, oxfmt, cspell, lefthook
- Requirements: Node.js >= 26
- VSCode integration
- Debugging
- Running tests
- oxc.oxc-vscode provides oxlint and oxfmt in-editor
- Workflows
- Version updates via
npm version - Publishing via GitHub Actions on tagged commits
- Version updates via
- Contribution guidelines
- Commit signing (GPG)
- How to set up commit message template (
npm run use:git-commit-message) - Reference to commit-message-template
- Type-level tests use
expect-type'sexpectTypeOf(...)insidenode --testcases
12. Project Initialization & Commit Strategy
- Start by initializing git with a
mainbranch. - Initial commit: empty README + LICENSE.
- Create a feature branch:
feature/setup. - For each technology or tool added (and its configuration), create a
separate commit:
- Prepend each commit message with a matching gitmoji (e.g.
:sparkles:for new features,:wrench:for config,:bug:for fixes,:fire:for removals,:white_check_mark:for tests,:tada:for initial commit). - Example commit messages used in this project:
:tada: Initial commit with empty README:wrench: Track .vscode/settings.json for workspace settings:construction_worker: Added GitHub Actions workflow for CI/CD:test_tube: Added Vitest configuration with coverage(later removed):sparkles: Scaffolded src/index.ts entry point for library code:wrench: Replace Vite/Vitest with TypeScript 7 and node --test:wrench: Replace ESLint and Prettier with oxlint and oxfmt:fire: Remove Vite scaffold leftovers:sparkles: Add initial pattern-matching API:white_check_mark: Add expect-type for type-level tests:wrench: Declare Node 26 as the supported runtime:bug: Use .ts extensions in imports for node --strip-types:wrench: Remove Prettier from editor formatter config
- Prepend each commit message with a matching gitmoji (e.g.
- Each commit should include only the relevant files and configuration for that technology/tool. This approach ensures a clean, understandable project history and makes it easy to review or revert specific setup steps.
13. Changelog Automation
- Not currently configured. The intended workflow, when adopted, is a
changesets-driven release process: feature PRs include a changeset,
which gets consumed by a release workflow, producing a
CHANGELOG.mdand a version bump on merge to main. - The changelog should be updated as part of the release process.
14. Publishing Public
- npm publishing is configured to be public by default.
publishConfig: { "access": "public" }is set inpackage.json.- The CI/CD pipeline publishes with
--access publicon tagged commits.
15. VSCode Integration
.vscode/settings.jsonusesoxc.oxc-vscodeas the default formatter for[typescript],[javascript],[json],[jsonc],[markdown],[mdx], and[yaml](oxfmt under the hood).
{
"[typescript]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[javascript]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[json]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[jsonc]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[markdown]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[mdx]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[yaml]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
}
.vscode/extensions.jsonrecommends:oxc.oxc-vscode(oxlint + oxfmt, replaces eslint/prettier/vitest)streetsidesoftware.code-spell-checker(cspell)typescriptteam.native-preview(TypeScript 7 nightly support; replaces the olderms-vscode.vscode-typescript-next)
{
"recommendations": [
"oxc.oxc-vscode",
"streetsidesoftware.code-spell-checker",
"typescriptteam.native-preview"
]
}
.vscode/tasks.jsonis not currently provided; common tasks (build, test, lint, typecheck, format, spell, check:outdated) are run via the npm scripts inpackage.jsonfrom the integrated terminal.- VSCode uses TypeScript 7 via the
typescriptteam.native-previewextension, withoxc.oxc-vscodefor formatting and linting.