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

@pulssse/react

v0.1.0

Published

React dashboard widgets for Pulse. You supply MetricData through a queryFn.

Readme

@pulssse/react

React widgets for a Pulse dashboard. You keep the data source. The package draws the charts.

You pass a queryFn that returns MetricData. Prometheus, Postgres, and Pulse auth stay in the host. Widgets refetch on a timer through TanStack Query.

import { DashboardView, PulseProvider, parseDashboard } from "@pulssse/react";
import { createHttpQueryFn } from "@pulssse/react/core";

const queryFn = createHttpQueryFn("/api/metrics/query");
const dashboard = parseDashboard(dashboardJson);

export function Ops() {
  return (
    <PulseProvider queryFn={queryFn}>
      <DashboardView dashboard={dashboard} />
    </PulseProvider>
  );
}

Install

npm install @pulssse/react

Peer dependencies, same majors as this package:

npm install react react-dom @tanstack/react-query recharts @base-ui/react \
  class-variance-authority clsx lucide-react tailwind-merge

zod is a real dependency. React 19, Recharts 3, TanStack Query 5.

The published import field points at TypeScript source so Tailwind can see class names. Compile that source in the host app.

Next.js

const nextConfig = {
  transpilePackages: ["@pulssse/react"],
};

Vite. Deduplicate React if you ever file: or link this package. Two copies of React produce a blank page and "invalid hook call":

import path from "node:path";
import { defineConfig } from "vite";

export default defineConfig({
  resolve: {
    dedupe: ["react", "react-dom", "@tanstack/react-query", "@base-ui/react"],
    alias: {
      react: path.resolve(import.meta.dirname, "node_modules/react"),
      "react-dom": path.resolve(import.meta.dirname, "node_modules/react-dom"),
    },
  },
});

CSS

Widgets use Tailwind v4 utility classes plus CSS variables from @pulssse/react/styles.css. If Tailwind never scans the package source, the charts render unstyled.

@import "tailwindcss";
@import "@pulssse/react/styles.css";
@source "../node_modules/@pulssse/react/src";

Adjust the @source path so it reaches node_modules/@pulssse/react/src from that CSS file. Dark mode uses a .dark ancestor.

queryFn

Widgets fetch only through queryFn.

type MetricQueryFn = (
  body: ExecuteQueryBody,
  signal?: AbortSignal,
) => Promise<MetricData>;

ExecuteQueryBody:

{
  query: string;
  variables?: Record<string, string>;
  range?: { start: string; end: string; step: string };
  transform?: TransformConfig | TransformConfig[];
  datasourceId?: string;
}

range.start and range.end are ISO timestamps. query is whatever string you put on the widget. Pulse does not interpret PromQL. Your function can treat it as a metric name, a SQL snippet, or a route key.

Keep queryFn stable. Module scope or useMemo. A new function every render rebuilds provider context and redraws every widget.

Throw MetricRequestError for a typed widget error:

import { MetricRequestError } from "@pulssse/react/core";

throw new MetricRequestError({
  type: "timeout",
  message: "Prometheus did not answer in time",
});

type is query | timeout | invalid-data | authorization | provider.

MetricData

Return one of three shapes. Timestamps are Unix seconds.

{ type: "scalar", value: 12.4, labels: { instance: "api-1" } }

{
  type: "series",
  series: [
    {
      id: "api-1",
      name: "api-1",
      labels: { instance: "api-1" },
      points: [{ timestamp: 1710000000, value: 0.42 }],
    },
  ],
}

{
  type: "table",
  columns: ["route", "method", "tps"],
  rows: [{ route: "query.index", method: "GET", tps: 0.0069 }],
}

| Widget type | Needs | | --- | --- | | line, area, bar | series with at least one point | | stat, gauge | scalar, or a single series (latest point) | | table | table, series, or scalar | | pie | series or table | | heatmap | accepted as series; no renderer yet |

HTTP helper

If your API already returns Pulse's envelope, use createHttpQueryFn(url). It POSTs JSON ExecuteQueryBody and unwraps:

{ "data": { "type": "scalar", "value": 1, "labels": {} } }

or

{ "error": { "type": "query", "message": "unknown metric" } }

Mapping a product payload

Map product JSON in queryFn. DashboardView only accepts MetricData. One HTTP envelope can feed several widgets if you branch on body.query.

Example product payload:

{
  "tps": 0.0069,
  "window": "5m",
  "range": "15m",
  "metric": "api_requests_total",
  "series": [{ "route": "query.index", "method": "GET", "tps": 0.0069 }],
  "history": [{ "timestamp": 1710000000, "tps": 0 }]
}
import type { MetricQueryFn } from "@pulssse/react/core";

const queryFn: MetricQueryFn = async (body, signal) => {
  const res = await fetch("/api/metrics", { signal });
  const json = await res.json();

  if (body.query === "tps") {
    return { type: "scalar", value: json.tps, labels: { window: json.window } };
  }

  if (body.query === "tps_history") {
    return {
      type: "series",
      series: [
        {
          id: "tps",
          name: "tps",
          labels: { metric: json.metric },
          points: json.history.map((p: { timestamp: number; tps: number }) => ({
            timestamp: p.timestamp,
            value: p.tps,
          })),
        },
      ],
    };
  }

  return {
    type: "table",
    columns: ["route", "method", "tps"],
    rows: json.series,
  };
};

Use display: { unit: "requests/sec" } on the stat and line widgets for that data.

Dashboard JSON

Parse unknown JSON with parseDashboard or loadDashboard (same thing). schemaVersion must be 1.

{
  "schemaVersion": 1,
  "id": "api",
  "name": "API",
  "description": "Edge request rate",
  "variables": [
    {
      "name": "env",
      "label": "Environment",
      "type": "custom",
      "options": ["production", "staging"],
      "default": "production"
    }
  ],
  "widgets": [
    {
      "id": "tps",
      "title": "TPS",
      "type": "stat",
      "query": "tps",
      "display": { "unit": "requests/sec" },
      "layout": { "x": 0, "y": 0, "w": 3, "h": 2 }
    },
    {
      "id": "history",
      "title": "TPS",
      "type": "line",
      "query": "tps_history",
      "display": { "unit": "requests/sec" },
      "layout": { "x": 0, "y": 2, "w": 12, "h": 4 }
    }
  ]
}

Layout is a 12-column grid. w is column span (clamped 1–12). h is stored but the grid currently sizes by content. Widgets sort by y then x.

Variables. The picker uses options. Values go into queryFn as body.variables. Names match /^[a-zA-Z_][a-zA-Z0-9_]*$/. type: "query" is stored on the schema and ignored here.

Transforms on a widget (optional): latest, average, sum, min, max, { type: "group", by: ["route"] }, { type: "sort", direction: "desc" }, { type: "limit", count: 10 }. The widget copies them onto body.transform. This package does not run them. Call applyTransform or transform from @pulssse/react/core inside queryFn, or do the work on the server.

Display. unit is none | percent | bytes | bytes/sec | seconds | milliseconds | requests/sec | operations/sec | short. labelTemplate interpolates labels, e.g. {{instance}}. min / max matter for gauges. decimals is 0–8.

DashboardView shows a 15m / 1h / 6h range picker unless you pass range or relativeRange. Default refetch is 15 seconds. Pass refetchInterval={false} to stop.

Pieces you can use without a full dashboard

import {
  MetricBar,
  MetricWidget,
  Sparkline,
  useMetricQuery,
} from "@pulssse/react";

<MetricWidget widget={widget} relativeRange="1h" />

<Sparkline values={[0.1, 0.2, null, 0.4]} />

<MetricBar label="CPU" value={72} warn={75} crit={90} />

useMetricQuery needs PulseProvider above it. It returns a TanStack Query result whose data is MetricData.

const q = useMetricQuery({
  query: "tps_history",
  relativeRange: "15m",
  variables: { env: "production" },
  refetchInterval: 15_000,
});

Pass queryKey on PulseProvider when two trees share a QueryClient and should not share cache entries. Pass your own queryClient if the host app already has one.

Exports

| Import | What | | --- | --- | | @pulssse/react | Provider, dashboard, widgets, charts, Sparkline, MetricBar | | @pulssse/react/core | Types, parseDashboard, createHttpQueryFn, transforms, formatters. No React components. | | @pulssse/react/styles.css | Tokens (--background, --chart-1 through --chart-5, --status-ok, --status-warn) |

@pulssse/react re-exports core, so import { parseDashboard } from "@pulssse/react" works. Use /core from Node or a non-React bundle.

Outside this package

Prometheus, dashboard files on disk, auth, and logs live in the Pulse app. This package is the widgets plus the MetricData contract.

Before npm publish, run npm run build in this directory so dist/*.d.ts exists. Scoped packages need publishConfig.access set to public, which is already in package.json.