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

tramejs

v0.3.0

Published

Google Maps for your frontend architecture — parse a TypeScript codebase and explore it as an interactive 3D map.

Readme

trame_

Google Maps for your frontend architecture.

Parse any TypeScript codebase and explore it as an interactive 3D map. Trace how two files got connected, see what breaks before you touch it, test an architectural decision before you make it, and fail CI when the architecture drifts.

See it running — trame's own source, parsed by trame. Open the viewerdub, 3547 files, with a year of its history to scrub through.

Why

Hand-drawn architecture diagrams go stale the week after you draw them, and nobody can read one past fifty nodes anyway. The generated kind route their arrows for you and give you no say in the result.

trame doesn't ask you to draw a diagram at all. It reads your source with ts-morph and turns the codebase itself into a map you can walk through, ask questions of, and hold to a set of rules.

The examples below are cal.com — 3,451 files across 114 folders, a codebase nobody reading this wrote. Every number is what trame printed.

Explore

npx tramejs --src ./src
trame serve

What opens is not a diagram. Files are nodes, shaped by what they are — page, component, hook, store, context, API endpoint, query key. Imports are edges you can bend and the curves persist. A clustering force pulls each folder into its own district, and past a certain distance the files fade into the folders that hold them, the way a map trades streets for cities.

On a codebase too large to draw at once, trame does not try. It picks out the files that hold the structure up, then opens on the worst thing they are caught in, with the neighbourhood around it already drawn.

Once you are there:

| | | |---|---| | shift-click a second file | lights the dependency chain between the two. Why does LoginPage depend on Chart? | | I | everything that transitively depends on this file, fading with distance | | W | what deleting it would break, what it would strand, which cycles it would resolve — without touching disk | | O | opens the file at its line in VS Code, Cursor, Windsurf or Zed | | ⌘KCopy link to this view | puts the selection and the active question in the URL, so a colleague opens where you left off |

trame watch re-parses on save and the viewer swaps the graph under you.

Understand

Ask what is worth fixing:

trame doctor --src ./packages
551 things worth fixing, top 2:

  ⟳ 106 files depend on each other in a loop: class → prismaNamespace → models … +100
    → Remove the import of models from prismaNamespace — verified to free 104 of them.

  ⟳ 8 files depend on each other in a loop: getCalendar → CalendarSubscriptionService
    → CalendarSyncService → handleCancelBooking → EventManager → CalendarManager
    → Remove the import of CalendarSubscriptionService from getCalendar — frees 6.

The cut is not a guess. A group of eight files can hold several distinct loops, so removing the import that looks load-bearing may leave the tangle intact. trame takes out each candidate in turn and recounts what stays caught, which is why "frees 104" is a measurement and not an opinion.

Dead code is reported the same way, and phrased as the inference it is. Files something else calls are left alone — router filenames, *.config.ts, and anything under scripts/, tests/ or playwright/, where no import exists to find. On dub that is the difference between 650 unimported files and 46. What survives is still an inference, since a dynamic import is invisible, so trame says how many private helpers would go with a deletion rather than telling you to delete anything.

Ask where the modules actually are:

trame modules --src ./packages
structure found: 0.685 · your folders: 0.427

trpc/ holds 2 groups that barely touch:
  · createContext, addNotificationsSubscription.handler, addSecondaryEmail.handler … +122
  · errorFormatter, perfMiddleware, sessionMiddleware, authedProcedure … +153

googlecalendar/ holds 2 groups that barely touch:
  · _metadata, CalendarAuth, CalendarService, google-calendar.e2e, testUtils
  · add, callback, index, getGoogleAppKeys

Handlers on one side, middleware on the other, inside one folder that claims to be one thing. The service layer and the OAuth routes, likewise. Louvain finds the groups from the imports alone; both groupings are then scored with Newman's modularity, so 0.685 against 0.427 says the folder tree explains this codebase about half as well as its own dependencies do.

Ask who introduced it:

trame blame --src ./packages
19 traced through 2642 commits, 58 read:

  cycle: embed-iframe → embed → react-hooks
    73f51920 · refactor: move Booker hooks from packages/features to apps/web/modules

  cycle: schema → fieldTypes → variantsConfig
    older than the history read — try --since

git blame answers that for a line. A cycle is not written on any line: it emerges from imports spread across the files it joins, and the commit that closed the loop may have touched none of them meaningfully. trame bisects the history instead, parsing the architecture at each probe, so the cost is logarithmic rather than linear — and since the probes are shared between questions, nineteen answers came out of fifty-eight checkouts rather than nineteen searches' worth.

And ask how it got this way. trame replay walks the history and lets you scrub the architecture as it grew; surviving files keep their position between frames, so the eye follows what appeared and what went away. trame diff --base a.json --head b.json does the same for one branch: additions green, removals as red ghosts.

Enforce

Write the rules down, in trame.config.ts:

export default {
  rules: [
    { type: "no-cycles", message: "Circular dependency" },
    {
      type: "no-direct-import",
      match: { sourceType: "page", targetType: "page" },
      message: "Pages should not import each other directly",
    },
  ],
}
trame check --src ./src
✗ [no-cycles] Circular dependency (CalendarEventBuilder → BookingRepository → IBookingRepository → CalendarEventBuilder)

Exit 1, so CI stops there. Violations glow red in the viewer with the message in the inspector, and the config is validated before it runs: a rule typed no-cycle instead of no-cycles used to match nothing, report nothing and pass — a green build that checked nothing at all.

.github/workflows/trame.yml also comments on every pull request with what the branch did to the architecture, and edits that comment in place rather than adding one. Details below.

Calm by design. Nodes rest grey. Colour only lands with your attention, in Catppuccin Mocha or Latte to match your terminal. Both grounds are held to the same measured bar: every lens clears 3:1 against the surface it is drawn on, and a test computes it from the palette that ships rather than trusting the eye.

Quickstart

npm i -g tramejs

trame --src ./src              # parse — writes trame.json
trame serve                    # explore it in the browser

Or without installing: npx tramejs --src ./src.

Two commands and nothing to clone. Your code never leaves the machine either: the parser reads it locally, and the viewer is a static bundle served off your own disk.

trame watch --src ./src        # re-parse on save, the viewer follows
trame check --src ./src        # exit 1 if an architecture rule broke

Working on trame itself

pnpm install && pnpm build
pnpm dev            # viewer   → http://localhost:5173
pnpm dev:site       # landing  → http://localhost:5174
pnpm parse -- --src ./path/to/src --out ./packages/viewer/public/trame.json
pnpm knip           # exports, files and dependencies nothing reaches
pnpm check:package  # publint + are-the-types-wrong on the published package

Tests

pnpm test

187 tests, on the parts where being wrong is silent.

In the parser: Tarjan's SCC detection, including a 20 000-node chain since the implementation promises to be iterative; the three constraint rules trame check exits on, and the validation that stops a misspelt rule passing quietly; the dead-code rule, which has to know that a route.ts nobody imports is a router convention rather than a corpse; the commit sampler that decides whether a replay reads as growth or as a slideshow.

In the viewer, mostly geometry and colour, because those fail quietly and look like taste: that a replay frame rebuilt from forty deltas equals the graph it came from, that a folder name lands on a file rather than in the gap between several, that an edge stays a line rather than a wash when the camera pulls back, and that every lens clears 3:1 against the ground it is drawn on. That last one reads tokens.css off disk and computes real contrast ratios, so it asserts the palette that actually ships.

Diagrams for docs and PRs

Yes, trame exports to Mermaid, the format its own pitch complains about. People need a diagram in their README, and GitHub renders Mermaid natively in issues, PR comments and Markdown files:

trame --src ./src --format mermaid --out docs/architecture.mmd
trame --src ./src --format dot     --out docs/architecture.dot

Nodes keep their shape-per-type and Catppuccin colour, and folders become subgraphs. From the viewer, ⌘K → Copy as Mermaid puts the diagram on your clipboard, scoped to the edge filter you are currently looking at.

In CI

.github/workflows/trame.yml comments on every pull request with what it did to the architecture: nodes added or removed, new dependency cycles, rule violations, and a Mermaid diagram of just the changed neighbourhood. There is one comment per PR and it gets edited in place. Then trame check fails the job if a rule broke.

Because GitHub renders Mermaid itself, this needs no headless browser, no image hosting and no artifact upload. The diagram is text in the comment body.

## trame

**+1** / **−0** nodes · +3 / −0 edges

### ✗ 1 rule violation
- `no-cycles` — Circular dependency (Card → Widget → Card)

<details><summary>Architecture diagram</summary>
…mermaid block GitHub renders inline…
</details>

Point TRAME_SRC and TRAME_CONFIG at your own paths and it works on any repo.

CLI

trame --src ./src [--out ./trame.json]     parse and write the graph
trame check --src ./src                      evaluate rules, exit 1 on violations
trame doctor --src ./src                     what to fix, worst first
trame blame --src ./src [--since]            which commit introduced each problem
trame modules --src ./src                    where the real module boundaries are
trame watch --src ./src [--out ...]          re-parse on file changes
trame serve --data ./trame.json [--port]   serve the built viewer
trame diff --base a.json --head b.json       what a branch did to the architecture
trame replay --src ./src [--since --max-frames]  how the architecture grew, across git history

--format json|mermaid|dot|markdown   output shape (default json)
--tsconfig ./tsconfig.json    resolve paths through a tsconfig
--config ./trame.config.ts    constraint rules (auto-detected in cwd)
--project name                project name in meta
--exclude a,b,c               extra path patterns to skip
--data ./trame.json           (serve) graph file to serve
--port 3000                   (serve) port
--dist ./path                 (serve) viewer build override
--since "6 months ago"        (replay) how far back to walk
--max-frames 40               (replay) frame budget — the stride follows from it
--repo .                      (replay) repository root

Keyboard

| Key | Action | |---|---| | drag | orbit · wheel zoom | | click node | select + inspector | | double-click node | focus camera | | drag node | reposition it (6px threshold — below that it's a click) | | click edge | show Bézier handles · drag to reshape · double-click reset | | I | impact of selection (transitive dependents) | | shift-click | trace dependency path from selection | | O | open selection in your editor | | ⌘K / / | command palette (search nodes, commands) | | ⌘E | export PNG | | E | cycle edge-type filter | | L | toggle labels | | G | toggle folder labels | | ? | all shortcuts, any time | | F | focus selection · Space reset camera · Esc deselect |

Node & edge language

| Node | Shape | Color | | Edge | Style | |---|---|---|---|---|---| | Page | octahedron | blue | | import | thin grey | | Component | rounded box | green | | component | green | | Hook | sphere | mauve | | api-call | peach | | API endpoint | cylinder | peach | | query-key | pink | | Query key | tetrahedron | pink | | context | yellow | | Context | torus | yellow | | violation | red | | Store | dodecahedron | teal | | | |

Nodes are sized by connectivity. At rest everything is grey — color is information, not decoration.

Rules

// trame.config.ts
export default {
  // paths to keep off the map, on top of node_modules, dist, tests and stories
  exclude: ["src/generated", ".gen.ts"],

  rules: [
    {
      type: "unique-caller",
      match: { edgeType: "api-call", targetType: "api" },
      message: "API endpoint called from multiple hooks — extract a shared hook",
    },
    {
      type: "unique-caller",
      match: { edgeType: "query-key" },
      message: "Query key used in multiple queries — consolidate",
    },
    {
      type: "no-direct-import",
      match: { sourceType: "page", targetType: "page" },
      message: "Pages should not import each other directly",
    },
    {
      type: "no-cycles",
      message: "Circular dependency",
    },
  ],
}

Violations show up red in the graph, with the message in the inspector, and make trame check exit 1. Wire that into CI and your architecture stops drifting.

Stack

| | | |---|---| | Three.js r185 | WebGPU renderer, automatic WebGL2 fallback | | React Three Fiber 9 + drei 10 | scene | | ts-morph 28 | TypeScript AST parsing | | d3-force-3d | 3D force layout + custom cluster force | | TypeScript 7 (native) · Vite 8 · Turborepo | toolchain | | zustand · cmdk · Tailwind 4 | state · palette · UI | | goey-toast | feedback for things you can't see happen |

Monorepo

packages/
├── parser/    # CLI — AST parsing, graph build, rules, watch, serve
├── viewer/    # Browser — 3D scene, inspector, command palette
└── site/      # Landing — the viewer's own meshes, no chrome

The landing imports the viewer's meshes and store rather than describing them, so it cannot advertise a behaviour the tool does not have: scrolling a section calls the same store action a keystroke would. It ships separately: set VITE_VIEWER_URL to point its CTA at wherever the viewer is deployed.

Dogfooded, and checkable: every node in the graph on the landing is a real file in packages/viewer/src, parsed by the real parser. NodeMesh.tsx and EdgeMesh.tsx are in there, so the code drawing the graph is part of what it draws.

License

MIT