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

@azerothjs/devtools

v2.1.0

Published

AzerothJS devtools - the in-page reactive inspector (components, timeline, graph, performance, server bridge) plus an embeddable agent

Readme

@azerothjs/devtools

AzerothJS devtools - the in-page reactive inspector: components, timeline with causes, dependency graph, performance, and a live server bridge

npm License: MIT Node >= 22


Part of AzerothJS - the fine-grained fullstack framework. A development-time companion to azerothjs - install it as a dev dependency alongside the framework.

An inspector that speaks the language you wrote, not the runtime's internals. A component that declares form login, resource user, and state count shows up as exactly that - a form group with its fields and validity, a resource with a live pending/ready/error badge, a named state with its current value - instead of an anonymous pile of signals and effects. Every re-run answers the question that matters: WHAT triggered it.

[!NOTE] Dev-only. Install it behind an import.meta.env.DEV guard and it tree-shakes out of production builds. The reactivity core carries zero devtools cost until a hook attaches.


Install

[!NOTE] ESM-only, Node >= 22; azerothjs is a peer dependency:

npm install --save-dev @azerothjs/devtools

Overview

Install BEFORE mounting so every node created during the initial render is captured:

import { render } from 'azerothjs';
import App from './App.azeroth';

if (import.meta.env.DEV)
{
    const { installDevtools } = await import('@azerothjs/devtools');
    installDevtools({ server: import.meta.env.VITE_API_URL }); // server is optional
}

render(() => App(), document.getElementById('app')!);

A launcher pill with a live effect count appears bottom-right. Click it and the inspector opens: an icon rail (Components, Timeline, Graph, Performance, Server, Settings), a search-first toolbar (Ctrl+K from anywhere in the panel), and an inspector pane that sits to the right on a wide panel and below on a narrow one. The panel floats (drag, corner-resize) or docks to any edge; every geometry choice persists, and every restore is clamped so a saved layout can never come back off-screen. The whole panel lives in a shadow root - your app's CSS cannot touch it, and its CSS cannot touch your app.


What each view answers

  • Components - "what does my app hold right now?" Files own their declarations; each declared primitive is ONE collapsible group with a live status badge (a resource shows pending/ready/error, a stream shows streaming/idle, a form shows valid/invalid/ submitting), and bare state/derived/effect rows carry their names and current values. The list is windowed, so ten thousand nodes scroll as smoothly as fifty.
  • Timeline - "what just happened?" Events arrive newest first, and one batch reads as one expandable row. Every run names the DIRECT producer that triggered it - run values <- email - reported by the runtime, not inferred. Click a cause to jump to it. Pause and clear without losing the live model.
  • Graph - "who depends on whom?" Select any node for its dependency neighborhood (what it reads, what re-runs when it changes), plus the full consumer adjacency below. Amber marks a producer that changed since the consumer last ran.
  • Performance - "is anything wrong?" Liveness per kind, a sustained-growth leak detector (a startup ramp never false-positives), and the busiest nodes ranked with relative activity bars.
  • Server - the same inspector pointed at your backend. See below.
  • Settings - dock, pop-out, session export/import (attach the JSON to a bug report and anyone can load it read-only), and the shortcut list.

Click any node anywhere and the inspector shows its value (signals are editable live - Enter applies), a numeric history sparkline, the source location (click to open your editor via Vite), its primitive's sibling nodes, and both directions of its dependencies.


Names come from your code

On the dev server, the compiler passes every declared identifier through automatically: state count, form login, resource user label their nodes with no configuration, and with { name: '...' } overrides the label on any keyword. In plain TS, pass { name } to createSignal/createResource/createForm/createStore and friends for the same labels. Production output carries none of this.


Inspecting the server

Requests on an AzerothJS backend are reactive roots, so the SAME inspector reads the server's graph. Attach the dev-only bridge next to serve():

import { serve } from '@azerothjs/http/node';
import { attachDevtools } from '@azerothjs/devtools/server';

const served = await serve(handler, { port: 5200 });
if (process.env.NODE_ENV === 'development')
{
    const token = crypto.randomUUID();
    attachDevtools(served.server, { token });
    console.log(`devtools: ws://localhost:5200/__azeroth/devtools?token=${ token }`);
}

The panel's Server tab connects to it (installDevtools({ server }) seeds the URL; the tab remembers whatever you enter) and mirrors the server's components view live: request roots, their per-request state and effects, and long-lived server stores.

Treat that stream as the debugger port it is: a request on this framework IS a reactive root, so the graph carries whatever the request carries, tokens and account rows included. The bridge is gated four ways, and every one of them has to pass: it attaches only under NODE_ENV=development (a positive check, not "anything but production", so an unset or misspelled value refuses rather than opens), every upgrade must present the shared token, the peer must be loopback, and a browser's Origin must be localhost - a MISSING Origin, which is what a non-browser client sends, is refused too. allowNonDevelopment and allowRemoteClients exist for a container or a remote box, and each one is a deliberate sentence you have to write. Logs and traces stay @azerothjs/logger's job.


The agent (headless use)

The panel is one consumer of the agent - the piece that installs the runtime hook and keeps the model. Drive it yourself for a custom overlay, a test harness, or an extension transport:

import { createAgent } from '@azerothjs/devtools';

const agent = createAgent();
agent.subscribe(() => console.log(agent.getModel().counts));
agent.handle({ kind: 'peek', id: 42 }); // JSON-serializable request/response boundary

Everything the agent returns is JSON-serializable: getModel(), getGraph(), getTimeline(), getHealth(), exportSession(), and per-node peek/poke/getHistory.


Related

Part of the AzerothJS monorepo. Related packages: azerothjs (the runtime whose versioned hook this consumes), @azerothjs/http (the backend whose requests the Server tab inspects), and @azerothjs/ws (the WebSocket layer under the bridge).