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

awilix-graph

v0.3.2

Published

Inspect an Awilix DI container and generate a visual dependency graph (DOT/Mermaid/JSON/HTML)

Readme

awilix-graph

CI npm License: MIT Node.js

Inspect an Awilix DI container and generate a visual dependency graph. Useful for onboarding, debugging, and auditing complex dependency injection setups.

Features

  • Four output formats — Mermaid, Graphviz DOT, JSON, interactive HTML
  • Interactive HTML — vis.js graph with node click → detail panel, search, lifetime / type filters, physics toggle; no node-count limit
  • Lifetime annotations — SINGLETON / TRANSIENT / SCOPED shown in every node
  • Lifetime violation detection — catches captive dependency bugs statically (see below)
  • Missing dependency detection — unregistered deps appear as distinct nodes
  • Cycle detection — circular dependencies highlighted in every format
  • --focus <name> — zoom in on the subgraph around a single registration
  • --depth <n> — limit graph depth (works standalone or combined with --focus)
  • --stats — metrics table: fan-in, fan-out, and instability per node; surfaces god objects and stable foundations at a glance
  • --open — open the result in the browser instantly (Mermaid Live / GraphvizOnline / HTML)
  • --fail-on — exit code 1 on violations or cycles; CI-ready
  • Programmatic API — import and use as a library
  • ESM & CJS — loads .mjs, .cjs, .js, .ts, .mts container files

Requirements

  • Node.js ≥ 20.0.0
  • awilix ≥ 5.0.0 (peer dependency, installed in the project whose container you are inspecting)

Installation

# as a dev tool in your project
pnpm add -D awilix-graph

# or globally
pnpm add -g awilix-graph

awilix must be installed in the project whose container you are inspecting (peer dependency, v5+).

Quick start

# print a Mermaid diagram to stdout
awilix-graph -c src/container.ts

# open immediately in the browser
awilix-graph -c src/container.ts --open

# write a self-contained HTML file
awilix-graph -c src/container.ts -f html -o graph.html

# plain-text summary of all registrations
awilix-graph -c src/container.ts --list

# metrics table (fan-in, fan-out, instability)
awilix-graph -c src/container.ts --stats

# fail the build if lifetime violations or cycles are detected
awilix-graph -c src/container.ts --fail-on all

CLI reference

Usage: awilix-graph [options]

Options:
  -c, --container <path>   Path to the file that exports the Awilix container
  -f, --format <format>    Output format: dot | mermaid | json | html  (default: "mermaid")
  -o, --output <file>      Write output to a file instead of stdout
  --no-missing             Exclude unregistered (missing) dependency nodes
  --focus <name>           Show only the subgraph reachable from this registration
  --depth <n>              Max traversal depth (works with --focus or standalone)
  --list                   Print a plain-text summary instead of a graph
  --stats                  Print a metrics table (fan-in, fan-out, instability)
  --open                   Open the result in the browser after rendering
  --fail-on <checks>       Exit 1 when issues are found: cycles, violations, all
  -V, --version            Print version
  -h, --help               Show help

--focus + --depth

# everything reachable from authService in both directions
awilix-graph -c src/container.ts --focus authService

# only the immediate neighbours (distance ≤ 1)
awilix-graph -c src/container.ts --focus authService --depth 1

--depth standalone

Without --focus, --depth limits the graph starting from root nodes (registrations that nothing else depends on):

# show only the top two levels of the dependency tree
awilix-graph -c src/container.ts --depth 2

--open

| Format | Opens | |---|---| | mermaid | mermaid.live with the diagram pre-loaded (falls back to HTML if source > 50 000 chars) | | dot | GraphvizOnline with the source pre-loaded | | html | Temporary .html file in the OS default browser | | json | Temporary .json file with the OS default app |

--fail-on

Use this flag to block CI pipelines when structural problems are detected:

# fail on lifetime violations with severity "error" only
awilix-graph -c src/container.ts --fail-on violations

# fail on circular dependencies
awilix-graph -c src/container.ts --fail-on cycles

# fail on either
awilix-graph -c src/container.ts --fail-on all

Exit code is 1 when the specified condition is met, 0 otherwise (output is still written normally).

Lifetime violations

A captive dependency is a bug where a longer-lived service holds a reference to a shorter-lived one, preventing the shorter-lived service from being recreated as intended.

| From | To | Severity | Description | |------|-----|----------|-------------| | SINGLETON | SCOPED | error | Singleton captures one scoped instance forever, breaking per-scope isolation | | SINGLETON | TRANSIENT | error | Singleton captures one "transient" instance — it is never recreated | | SCOPED | TRANSIENT | warning | Scoped service gets one transient per scope instead of per call |

Violations are reported on stderr, rendered as coloured edges in every output format, and can be used to gate CI with --fail-on violations.

⚠  Lifetime violations detected (2 errors, 1 warning):
   ✗ userRepository [SINGLETON] → dbSession [SCOPED]
   ✗ cacheService [SINGLETON] → requestContext [TRANSIENT]
   ! orderHandler [SCOPED] → factory [TRANSIENT]

Only services with an explicit lifetime declaration are checked — registrations without a lifetime annotation are skipped to avoid false positives.

--stats

Prints a metrics table for every registered node, sorted by fan-in descending:

Container stats: 9 nodes · 1 missing · 13 edges · 0 cycles · 0 violations

  Name             Type      Lifetime    Fan-in  Fan-out  Instability
  ────────────────────────────────────────────────────────────────────
  config           value     —                3        0         0.00
  logger           class     SINGLETON        3        0         0.00
  database         class     SINGLETON        2        2         0.50
  authService      class     TRANSIENT        1        2         0.67
  userRepository   class     TRANSIENT        1        1         0.50
  tokenService     function  TRANSIENT        1        1         0.50
  emailService     class     TRANSIENT        0        3         1.00
  orderService     class     TRANSIENT        0        3         1.00

| Metric | Formula | What it tells you | |---|---|---| | Fan-in | incoming edges | How many services depend on this node — high = stable foundation | | Fan-out | outgoing edges | How many dependencies this node has — high = tightly coupled | | Instability | fanOut / (fanIn + fanOut) | 0 = pure provider (stable), 1 = pure consumer (unstable) |

Nodes with fan-in = fan-out = 0 are reported separately as isolated nodes — they are registered but nothing connects them to the rest of the graph.

Output formats

Mermaid (default)

graph LR
  config{{"config<br/>(value)"}}
  logger["logger<br/>(class · SINGLETON)"]
  database["database<br/>(class · SINGLETON)"]
  tokenService("tokenService<br/>(function · TRANSIENT)")
  authService["authService<br/>(class · TRANSIENT)"]
  ...
  database --> logger
  database --> config
  authService --> userRepository
  authService --> tokenService

Paste into mermaid.live or embed directly in Markdown / Notion / GitHub.

Violation edges are highlighted with linkStyle (red = error, orange = warning).

Graphviz DOT (-f dot)

awilix-graph -c src/container.ts -f dot | dot -Tsvg -o graph.svg

SINGLETON nodes have a double outline (peripheries=2); SCOPED nodes have a bold border (penwidth=2). Violation edges are coloured red or orange with a lifetime label.

JSON (-f json)

{
  "nodes": [
    { "name": "logger",   "type": "class", "dependencies": [], "missing": false, "lifetime": "SINGLETON" },
    { "name": "database", "type": "class", "dependencies": ["logger", "config"], "missing": false, "lifetime": "SINGLETON" }
  ],
  "edges": [
    { "from": "database", "to": "logger" }
  ],
  "cycles": [],
  "violations": []
}

Interactive HTML (-f html)

Generates a self-contained interactive .html file powered by vis.js Network. Open it in any browser — no server needed.

awilix-graph -c src/container.ts -f html -o docs/graph.html

Interactive features:

| Feature | Description | |---|---| | Click a node | Opens a detail panel — type, lifetime, dependencies, used-by, violations | | Click dep in panel | Navigates the graph to that node | | Search (/) | Filters nodes in real-time; non-matching nodes dim | | Lifetime filter | Toggle All / SINGLETON / SCOPED / TRANSIENT | | Type filter | Toggle All / class / function / value / alias / missing | | ⊞ Fit | Zoom to fit all visible nodes | | ⚡ Physics | Toggle force simulation on/off |

DAG-like graphs use a hierarchical left-to-right layout. Graphs with cycles start with force-directed layout (physics auto-stabilises then freezes).

Violation edges are coloured red/orange with a lifetime label. Violations and cycles are also listed in sections below the graph.

No node-count limit — vis.js renders arbitrarily large containers in a single unified view.

--list

CLASSES
  ◆ logger [SINGLETON]
  ◆ database [SINGLETON] → [logger, config]
  ◆ userRepository [TRANSIENT] → [database]
  ◆ authService [TRANSIENT] → [userRepository, tokenService]
  ◆ orderService [TRANSIENT] → [orderRepository, authService, logger]
  ◆ emailService [TRANSIENT] → [config, logger, smtpClient]

FUNCTIONS
  ◇ tokenService [TRANSIENT] → [config]

VALUES
  ● config

UNKNOWNS
  ? smtpClient

ERRORS
  ⚠ brokenService [resolver threw: ...]

Node types

| Symbol | Shape (Mermaid) | Type | Description | |---|---|---|---| | | ["…"] rectangle | class | asClass() registration | | | ("…") rounded | function | asFunction() registration | | | {{"…"}} double brace | value | asValue() registration | | | [/"…"/] parallelogram | alias | aliasTo() registration | | ? | ["…"] dashed | missing | dependency not registered in the container | | | ["…"] | error | resolver threw during inspection |

Lifetime encoding

| Lifetime | Mermaid label | DOT style | |---|---|---| | SINGLETON | (class · SINGLETON) | double outline (peripheries=2) | | SCOPED | (class · SCOPED) | bold border (penwidth=2) | | TRANSIENT | (class · TRANSIENT) | normal border | | (none) | (value) / (alias) | normal border |

Container file formats

The file passed to -c can export the container in any of these ways:

// direct export
module.exports = container              // CJS
export default container               // ESM

// named export
module.exports = { container }
export { container }

// factory function (sync or async)
module.exports = () => container
export default async function build() { return container }

Supported extensions: .js, .cjs, .mjs, .ts, .cts, .mts.

TypeScript and JavaScript files are analysed via the TypeScript Compiler API (bundled with the package) — no ts-node or tsx required.

Programmatic API

File-based analysis (recommended)

analyzeContainerFile reads the source file via the TypeScript Compiler API — no code is executed, no side effects occur, result is fully deterministic.

import { analyzeContainerFile, buildGraph, renderGraph } from 'awilix-graph'

const nodes = analyzeContainerFile('src/container.ts')
const graph = buildGraph(nodes)
const mermaid = renderGraph(graph, 'mermaid')

Known limitations of static analysis:

  • Dynamically computed registration keys ({ [name]: asClass(Foo) }) are skipped.
  • Classes/functions imported from node_modules yield dependencies: [].
  • Object spread inside register({ ...base }) is not followed.
  • Project-local tsconfig.json path aliases are not resolved.

In all these cases the node is still emitted (type and lifetime are known from the call site) but dependencies will be [].

Container-object API

Pass a live Awilix container directly — useful when you build the container programmatically and don't have a dedicated file to point to.

import { createContainer, asClass, asValue } from 'awilix'
import {
  render, inspect, renderGraph,
  focusSubgraph, limitDepth,
  detectViolations, computeStats,
} from 'awilix-graph'

const container = createContainer().register({ ... })

// highest-level: inspect + render in one call
const mermaid = render(container, 'mermaid')

// or work with the graph data structure directly
const graph = inspect(container)
console.log(graph.nodes)      // GraphNode[]
console.log(graph.edges)      // GraphEdge[]
console.log(graph.cycles)     // string[][]
console.log(graph.violations) // LifetimeViolation[]

// render to any format
const dot  = renderGraph(graph, 'dot')
const json = renderGraph(graph, 'json')
const html = renderGraph(graph, 'html')

// subgraph around one node
const sub = focusSubgraph(graph, 'authService', /* depth */ 2)

// limit to the first N levels from root nodes
const shallow = limitDepth(graph, 2)

// standalone violation analysis
const violations = detectViolations(graph.nodes, graph.edges)
const errors = violations.filter(v => v.severity === 'error')

// metrics: fan-in, fan-out, instability per node
const stats = computeStats(graph)
console.log(stats.nodeCount)    // total registered nodes
console.log(stats.nodes)        // NodeStats[] sorted by fan-in desc

Type reference

type NodeType         = 'class' | 'function' | 'value' | 'alias' | 'unknown' | 'error'
type Lifetime         = 'SINGLETON' | 'TRANSIENT' | 'SCOPED'
type ViolationSeverity = 'error' | 'warning'
type OutputFormat     = 'dot' | 'mermaid' | 'json' | 'html'

interface GraphNode {
  name:         string
  type:         NodeType
  dependencies: string[]
  missing:      boolean
  lifetime?:    Lifetime
  error?:       string        // present when type === 'error'
}

interface GraphEdge { from: string; to: string }

interface LifetimeViolation {
  from:          string
  to:            string
  fromLifetime:  Lifetime
  toLifetime:    Lifetime
  severity:      ViolationSeverity
}

interface DependencyGraph {
  nodes:       GraphNode[]
  edges:       GraphEdge[]
  cycles:      string[][]
  violations?: LifetimeViolation[]  // populated by buildGraph / inspect
}

interface NodeStats {
  name:         string
  type:         NodeType | 'error'
  lifetime?:    Lifetime
  fanIn:        number
  fanOut:       number
  instability:  number | null  // null when fanIn = fanOut = 0 (isolated node)
}

interface GraphStats {
  nodeCount:             number
  missingCount:          number
  edgeCount:             number
  cycleCount:            number
  violationErrorCount:   number
  violationWarningCount: number
  nodes:                 NodeStats[]  // sorted by fanIn desc
}

Contributing

Bug reports and pull requests are welcome. See CONTRIBUTING.md for setup instructions, project structure, and PR guidelines.

Changelog

See CHANGELOG.md.

License

MIT — see LICENSE.