npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

async-phase

v0.1.9

Published

Detect low fan-out abstractions in TypeScript/React/Node codebases.

Downloads

89

Readme

modgraph

Detect low fan-out abstractions in your TypeScript/React/Node codebase.

modgraph is a small CLI that scans your project for single-use abstractions: exported functions, hooks, classes, and factories that are only used from a single other module.

The goal is to make hidden complexity visible, not to enforce a particular architecture. It is an informational + CI-enforceable tool that helps you have better conversations about premature abstractions.

Why this exists (premature abstraction)

Many codebases accumulate:

  • Small utility modules with a single caller
  • Hooks that wrap a single component’s logic
  • Factories that are only ever used once

These “abstractions” add indirection and maintenance cost without providing reuse. They:

  • Make code harder to navigate
  • Hide behavior behind extra files
  • Add overhead when refactoring

modgraph surfaces these cases so you can decide whether to:

  • Inline the abstraction back into its only caller, or
  • Invest in making it genuinely reusable

It never rewrites code or enforces a specific style; it just gives you data.

Install

npm install --save-dev modgraph

You can also run it via npx without installing globally:

npx modgraph scan

What it detects

modgraph parses your project and looks for exports that:

  • Are exported from a .ts or .tsx file
  • Are used from exactly one other module (fan-out = 1)

It considers:

  • Exported functions
  • Exported custom hooks (name starts with use)
  • Exported classes
  • Exported factory functions (name starts with create or ends with Factory)

It intentionally ignores:

  • Default-exported React components
  • Next.js page files and API route handlers (as long as they are default exports)
  • Barrel re-export-only files (index.ts that just re-export)
  • Type-only exports (files that only export types/interfaces)
  • Test files (*.test.ts, *.spec.ts, tests/, __tests__/)
  • Storybook files (*.stories.ts[x], .storybook/, etc.)

For each abstraction it keeps, it computes:

  • name
  • type (hook | function | class | factory)
  • file (full path)
  • loc (lines of code in the abstraction)
  • internalImports (number of import statements in the file that defines it)
  • fanOut (number of other files that import it)
  • score (Abstraction Cost Score)

Abstraction Cost Score

The score is a rough measure of how “expensive” the abstraction is to maintain:

score = (LOC * 0.5) + (internalImports * 2)

The result is rounded to the nearest integer.

Roughly:

  • More lines → higher score
  • More imports in that file → higher score

Combined with low fan-out, this can indicate over-abstracted code.

CLI usage

Basic scan (table output):

npx modgraph scan

Options:

  • --ci Exit with non-zero status code if the number of single-use abstractions exceeds the threshold.

  • --threshold <n> Maximum allowed single-use abstractions. Default: 10.

  • --json Output JSON instead of a table (useful for tooling/CI).

  • --root <path> Project root directory. Defaults to process.cwd().

Examples:

# Scan the current project and print a table
npx modgraph scan

# Scan another project directory
npx modgraph scan --root ../my-app

# Print JSON instead of a table
npx modgraph scan --json

# CI-style run with a stricter threshold
npx modgraph scan --ci --threshold 5

Sample table output

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Modgraph Report
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Detected Single-Use Abstractions: 3

┌───────────────────────┬───────────┬───────┬──────────┬──────┐
│ Name                  │ Kind      │ Lines │ Used by  │ Cost │
├───────────────────────┼───────────┼───────┼──────────┼──────┤
│ useFormattedDate      │ hook      │ 23    │ 1        │ 14   │
│ createApiClient       │ factory   │ 51    │ 1        │ 33   │
│ formatCurrency        │ function  │ 18    │ 1        │ 9    │
└───────────────────────┴───────────┴───────┴──────────┴──────┘

Lines = lines of code in the abstraction.
Used by = number of other files that import it.
Cost = rough maintenance cost (higher = more complex).

Recommendation:
Review abstractions with low reuse and high cost scores.

If no single-use abstractions are found:

No single-use abstractions detected. Architecture looks lean.

JSON output

When you pass --json, modgraph prints a machine-readable JSON report:

npx modgraph scan --json

Example:

{
  "total": 3,
  "threshold": 10,
  "abstractions": [
    {
      "name": "useFormattedDate",
      "type": "hook",
      "file": "/app/src/hooks/useFormattedDate.ts",
      "loc": 23,
      "internalImports": 1,
      "fanOut": 1,
      "score": 14
    },
    {
      "name": "createApiClient",
      "type": "factory",
      "file": "/app/src/api/createApiClient.ts",
      "loc": 51,
      "internalImports": 4,
      "fanOut": 1,
      "score": 33
    },
    {
      "name": "formatCurrency",
      "type": "function",
      "file": "/app/src/utils/formatCurrency.ts",
      "loc": 18,
      "internalImports": 0,
      "fanOut": 1,
      "score": 9
    }
  ]
}

This is stable enough to consume in:

  • Custom CI checks
  • Dashboards
  • Editor extensions

CI usage (GitHub Actions example)

You can enforce a maximum number of single-use abstractions in CI. For example, in GitHub Actions:

name: modgraph

on:
  push:
    branches: [main]
  pull_request:

jobs:
  modgraph:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies
        run: npm install

      - name: Run modgraph
        run: npx modgraph scan --ci --threshold 10

If the number of single-use abstractions is greater than the threshold:

  • The report is printed.
  • modgraph prints: CI FAILED: Single-use abstraction threshold exceeded.
  • The process exits with code 1, causing the job to fail.

If the count is within the threshold, it exits with code 0.

Philosophy

modgraph is intentionally:

  • Informational – it tells you what exists; it does not tell you what to do.
  • Non-prescriptive – it does not enforce a specific architecture or folder layout.
  • Safe – it never rewrites files or mutates your codebase.

Use it as a conversation starter:

  • Are these abstractions buying us anything?
  • Should we inline them?
  • Should we invest in making them reusable?

The tool does not know the answers, but it helps you ask better questions.

License

MIT