Files
tiny-pattern-ts/project-specs.md
T

19 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 .editorconfig from 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.json with typescript, unicorn, oxc, import plugins. Categories enabled as errors: correctness, suspicious, restriction. As warnings: perf, style. nursery is off.

  • oxlint rules disabled by design (in .oxlintrc.json):

    • eslint/no-undefined — we use undefined as the no-match sentinel.
    • eslint/sort-keys — handler/case order is semantic, not alphabetical.
    • eslint/id-length — T, R, U, V are standard TS generics.
    • import/no-named-export — false positive on library entry re-exports. For *.test.ts files additionally: no-unused-expressions (for expectTypeOf(...) calls), no-empty-file (we have a single import per test file in some cases), import/no-nodejs-modules (we use node:test/node:assert/node:fs intentionally), 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.json covers 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 the LEFTHOOK_FILES env var to the staged-files list via a sh -c wrapper, 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) outdated is intentionally NOT a pre-commit check (it can flag upstream patch releases that aren't actionable locally); it runs in CI and via the explicit lefthook run outdated command.
  • 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 optionalDependencies so 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 plain tsc -p tsconfig.build.json invocation that emits ESM JavaScript and .d.ts declarations to dist/. ESM only, no CJS.
  • Testing: Node's built-in node --test with --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: dist directory (set in tsconfig.build.json#outDir; tsconfig.json keeps outDir for editor tooling but excludes tests from emission via tsconfig.build.json#exclude).
  • Import extensions: Source uses .ts extensions in imports (e.g. from "./match.ts") so node --strip-types resolves them at test time. TypeScript's rewriteRelativeImportExtensions (in tsconfig.build.json) rewrites these to .js in the emitted dist/* output, so consumers see conventional ESM imports.
  • TypeScript Compiler Options Notes:
    • allowImportingTsExtensions: true is set in the root tsconfig.json (works because the root config has noEmit: true).
    • rewriteRelativeImportExtensions: true is set in tsconfig.build.json to rewrite .ts to .js on emit.
    • module: nodenext, moduleResolution: nodenext for ESM-first Node packages.
    • verbatimModuleSyntax: true enforces explicit import 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 for node:test, node:assert, node:fs.
  • Node Version: >=26 (engines field). Pinned via .node-version for fnm/nvm/volta/mise auto-switching and CI (actions/setup-node@v4 with node-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 via package.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.cjs and .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 version for versioning.
  • Unused Dependency Check: Use check-outdated (devDep, runs in CI and via the explicit lefthook run outdated command).
  • 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: Run tsc --noEmit then node --test --strip-types src/.
  • test:unit: Run unit tests with node --test --strip-types src/ (no preceding typecheck).
  • test:ci: Run tests in CI mode with c8 coverage (text + lcov + html reporters), uploading coverage/ as an artifact.

BUILD

  • build: Build the project using tsc -p tsconfig.build.json (emits dist/*.js + dist/*.d.ts + sourcemaps, with .ts imports rewritten to .js).

CLEAN

  • clean: Remove dist/ via node -e "fs.rmSync('dist', {recursive:true, force:true})". (Replaces clean:build from the original spec — same effect, no rimraf dep needed.)

CHECK

  • check: Run all checks in order — check:oxlint, check:oxfmt, check:tsc, check:cspell, check:outdated.
  • check:oxlint: oxlint ${LEFTHOOK_FILES:-src} — lints src/ by default; when invoked from the lefthook pre-commit hook with LEFTHOOK_FILES set 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 manual npm run check and 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, the LEFTHOOK_FILES env var scopes to the staged files matching *.{ts,tsx,js,jsx,mjs,cjs,json,jsonc,yaml,yml,md,mdx}. Built-in sortPackageJson: true keeps package.json keys alphabetized (replaces the former sort-package-json tool). Built-in import sorting (enabled via sortImports: true) replaces any external import-sort plugin.
  • check:tsc: tsc --noEmit.
  • 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 as optionalDependencies so 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 as check:oxfmt (whole project by default, staged files from lefthook). Writes changes in place. (There is no fix aggregator in the scripts; run the fix:* scripts individually.)

HOOKS

  • Lefthook runs the relevant check:* scripts on staged files in parallel for pre-commit. See lefthook.yml.

6. Repository & CI/CD

  • Repository: Hosted on GitHub.
  • Build Pipeline: GitHub Actions (.github/workflows/ci.yml).
    • build job on push and pull_request to main and on workflow_dispatch. Steps: actions/checkout@v4, actions/setup-node@v4 (with node-version-file: .node-version, cache: npm), npm ci, npm run build, npm run check, npm run test:ci, then upload coverage/ as an artifact.
    • publish job: only on refs/tags/*, depends on build. Steps: actions/checkout@v4, actions/setup-node@v4 (with node-version-file: .node-version and registry-url), npm ci, npm run build, npm publish --access public with NODE_AUTH_TOKEN from secrets.

7. Versioning & Publishing

  • Version Update: Use npm version to bump version after merging to main and before publishing.
  • Publishing to npm: Only publish from CI on tagged commits.
  • Recommended Workflow:
    1. Develop and merge PRs to main
    2. Run all checks via CI
    3. Bump version with npm version <patch|minor|major>
    4. Push tag to GitHub
    5. 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:ci script:
"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 via actions/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 via src/index.ts.
  • Configuration:
    • sideEffects: false in package.json (set).
    • ESM-only exports; package.json#exports field maps . to {"types": "./dist/index.d.ts", "import": "./dist/index.js"}.
    • Avoid top-level side effects in modules.
    • Explicit re-exports in index.ts for 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.shape constructors and the Matcher<T> interface.
    • src/index.test.ts — runtime + type-level tests using node --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
  • 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
  • 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's expectTypeOf(...) inside node --test cases

12. Project Initialization & Commit Strategy

  • Start by initializing git with a main branch.
  • 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
  • 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.md and 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 in package.json.
  • The CI/CD pipeline publishes with --access public on tagged commits.

15. VSCode Integration

  • .vscode/settings.json uses oxc.oxc-vscode as 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.json recommends:
    • oxc.oxc-vscode (oxlint + oxfmt, replaces eslint/prettier/vitest)
    • streetsidesoftware.code-spell-checker (cspell)
    • typescriptteam.native-preview (TypeScript 7 nightly support; replaces the older ms-vscode.vscode-typescript-next)
{
    "recommendations": [
        "oxc.oxc-vscode",
        "streetsidesoftware.code-spell-checker",
        "typescriptteam.native-preview"
    ]
}
  • .vscode/tasks.json is not currently provided; common tasks (build, test, lint, typecheck, format, spell, check:outdated) are run via the npm scripts in package.json from the integrated terminal.
  • VSCode uses TypeScript 7 via the typescriptteam.native-preview extension, with oxc.oxc-vscode for formatting and linting.