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

@tabviz/core

v0.7.0

Published

Interactive forest plots and rich tables — JavaScript runtime for the tabviz R package

Readme

@tabviz/core

JavaScript runtime + authoring API for tabviz — the publication-quality forest plots and rich interactive tables otherwise consumed via the tabviz R package.

Status: 0.7.0 — pre-1.0, wire FROZEN at 1.10 (additive minors only from here). Authoring API (function builders mirroring R's tabviz() / col_*() / viz_*()), nine curated themes, the component-model editing verbs, and a published JSON Schema (dist/tabviz-spec.schema.json) all ship. See docs/dev/r-ts-parity-notes.md for per-helper R↔TS parity status and known gaps.

Quick start — build a spec, mount it

import {
  tabviz, createTabviz,
  colText, colInterval, vizForest,
} from "@tabviz/core";
import "@tabviz/core/style.css";

const spec = tabviz({
  data: rows,
  label: "study",
  theme: "nejm",
  columns: [
    colText({ field: "study", header: "Study" }),
    colInterval({ point: "hr", lower: "lcl", upper: "ucl" }),
    vizForest({ point: "hr", lower: "lcl", upper: "ucl", scale: "log" }),
  ],
  title: "Hazard ratios across studies",
});

const instance = createTabviz(document.querySelector("#plot")!, spec);

tabviz() returns a WebSpec (the wire-format payload). createTabviz mounts it. Every R helper has a TS mirror — argument names + defaults match; snake_case in R → camelCase in TS.

What this package will publish

@tabviz/core exposes five subpaths over a single versioned npm package — one repo, one version, one source of truth.

| Subpath | What it exports | Who reaches for it | |---|---|---| | @tabviz/core | createTabviz, createSplitTabviz factories + wire-format types | Web-app consumers wanting visual parity with R-rendered tabvizes; vanilla TS/JS in any framework | | @tabviz/core/svelte | ForestPlot, SplitForestPlot components, createForestStore factories | Svelte consumers composing the components directly | | @tabviz/core/export | exportToSVG, exportToPNG, computeNaturalDimensions | Headless rendering pipelines (V8, node-rsvg, server-side export) | | @tabviz/core/spec | TypeScript types + JSON Schema (v1.0.json) | Consumers validating wire payloads | | @tabviz/core/htmlwidgets | R-side htmlwidgets adapters | The R package's vendored build artifacts (not for direct npm use) |

Installation

npm install @tabviz/core svelte

Svelte is the underlying component runtime. You don't write Svelte code — createTabviz returns a plain imperative handle — but the main entry and the /svelte subpath both need Svelte 5 installed as a peer. The /export and /spec subpaths are framework-free and work without it.

Driving the mounted widget

import { createTabviz } from "@tabviz/core";
import "@tabviz/core/style.css";

const instance = createTabviz(document.querySelector("#plot")!, spec, {
  width: 800,
  height: 600,
});

instance.on("selected", (rowIds) => console.log("selection:", rowIds));
instance.sortBy({ column: "estimate", direction: "asc" });
instance.setTheme("terminal");   // live theme swap
instance.update(nextSpec);       // data/spec update (state survives by id)
instance.destroy();

Themes

Nine curated presets ship as zero-arg factories — themeNejm() (default), themeLedger(), themeBrutalist(), themeAurora(), themeTerminal(), themeNewsprint(), themeBlueprint(), themeSynthwave(), themeDwarven() — or pass the preset name as a string (theme: "nejm"). To author your own, give buildTheme Tier-1 inputs (anchors, fonts, geometry) and let the cascade derive the rest:

import { buildTheme, buildThemeWire, parseThemeWire, getThemeCSS } from "@tabviz/core";

const inputs = {
  anchors: {
    paper: { L: 0.98, C: 0.005, H: 90 },   // OKLCH
    ink:   { L: 0.22, C: 0.01,  H: 270 },
    brand: { L: 0.55, C: 0.12,  H: 250 },
  },
};
const house = buildTheme(inputs, { name: "house" });  // resolved theme → tabviz({ theme: house })

// Portable artifact round-trip (share/store/re-import):
const wire   = buildThemeWire(inputs, "house");       // {$schema: "tabviz-theme/v4", …}
const parsed = parseThemeWire(JSON.stringify(wire));  // THE validating ingress — never raw JSON.parse
const back   = buildTheme(parsed.inputs, {
  name: parsed.name,
  roleOverrides: parsed.roleOverrides,
  pins: parsed.pins,
  components: parsed.components,
});
const css = getThemeCSS(back);                        // the resolved token sheet

Themes travel as a portable envelope (tabviz-theme/v4) carrying authoring inputs + role re-tunes + component re-routes + pins — the same three editing verbs the in-widget settings panel and the studio use. Always re-enter through parseThemeWire; it validates untrusted wires at ingress.

Validating specs (the LLM/codegen path)

The published artifact ships its JSON Schema at @tabviz/core/dist/tabviz-spec.schema.json (2020-12 dialect): the hand-written top-level shape plus per-column-type option definitions generated from the schema registry. Unknown column types stay valid (additive minors); known types must satisfy their definitions. The scripts/consumer-fixture.mjs gate exercises the full third-party journey — author → schema-validate → headless SVG — against dist/ on every build:npm.

Spec shape is defined in @tabviz/core/spec; validate payloads against the shipped dist/tabviz-spec.schema.json (see "Validating specs" above).

MCP server (the LLM-driver path)

scripts/mcp-server.mjs is a dependency-free MCP server over stdio exposing the spec-first contract to LLM agents: column-type introspection (list_column_types, get_column_schema), the published JSON Schema (get_spec_schema), theme roster (list_themes), structured validation (validate_spec → {path, code, message, severity} diagnostics), and headless rendering (render_svg). It imports only from dist/ — build with npm run build:npm first.

# from the published package (the server ships as a bin):
claude mcp add tabviz -- npx -y -p @tabviz/core tabviz-mcp
# or from a checkout:
claude mcp add tabviz -- node <repo>/srcjs/scripts/mcp-server.mjs
npm run mcp:smoke   # drives the full journey over real stdio

Wire-format versioning

Every WebSpec carries an explicit version field. The runtime validates it on every render. See docs/dev/versioning.md for the SemVer policy and minor-evolution rules.

Consuming from non-Svelte frameworks

The createTabviz factory returns a typed TabvizInstance with update, sortBy, applyFilter, selectRows, setSemantic, setTheme, setZoom, setAspectRatio, on (typed event subscriptions), destroy. Internally it mounts the Svelte components into the host element; from the React / Vue / Solid side you treat the host element + instance handle as an opaque imperative widget. You still install Svelte as a peer dep (the runtime is bundled-less so the consumer's app and @tabviz/core share the same Svelte instance).

Source layout

| Path | Purpose | |---|---| | src/core/ | Framework-agnostic factories (createTabviz, createSplitTabviz) | | src/svelte/ | Top-level Svelte components published as /svelte | | src/export/ | Pure-function SVG/PNG generators + V8 shim | | src/spec/ | TypeScript types + JSON Schema per minor | | src/htmlwidgets/ | R-side htmlwidgets adapters (Shiny proxy, message channels) | | src/stores/ | Svelte 5 reactive stores | | src/lib/ | Cross-cutting utilities (formatters, scales, banding, etc.) | | src/components/ | Internal sub-components (controls, viz, table cells) |

The lib/ and components/ trees are implementation details bundled into the published chunks; they're not part of the public subpath surface.

Building

Four build paths produce four artifact sets:

npm run build           # the three R-side IIFE bundles
npm run build:widget    # inst/htmlwidgets/tabviz.js
npm run build:split     # inst/htmlwidgets/tabviz_split.js
npm run build:v8        # inst/js/svg-generator.js
npm run build:npm       # dist/ ESM bundles + style.css + .d.ts types

R-side bundles ship vendored inside inst/ for htmlwidgets consumers. The npm path emits per-subpath ESM into dist/:

dist/
├── index.mjs          # @tabviz/core entry
├── svelte.mjs         # @tabviz/core/svelte entry
├── export.mjs         # @tabviz/core/export entry
├── spec.mjs           # @tabviz/core/spec entry
├── style.css          # standalone CSS for npm consumers
├── chunks/            # shared chunks
├── core/, svelte/, export/, spec/, …   # emitted .d.ts trees

build:npm runs vite, then tsc --emitDeclarationOnly, then a small postprocessor (scripts/rewrite-dts-aliases.mjs) that rewrites the internal $lib/$stores/$types/… aliases in the published .d.ts files to relative paths so npm consumers don't need our tsconfig.paths.

CI gates

Two scripts gate publish-readiness:

npm run check:size       # fails on any bundle >10% over budget
npm run check:lockfiles  # validates package-lock.json + bun.lock both sync with package.json

The size budget lives in bundle-size-budget.json (raw bytes, intentional growth is bumped in the same PR with justification).

Toolchain

The build artifact ships from npm (npm ci in CI, package-lock.json is canonical). Local development with bun is supported and tracked — bun.lock is checked in so bun install + bun test work fast, but the published artifact is built from package-lock.json. The two lockfiles must agree on resolved versions; npm run check:lockfiles gates this in CI.

Testing

npm test               # bun + vitest
npm run test:bun       # bun only — pure-TS tests
npm run test:vitest    # vitest — Svelte-runes tests (.runes.ts)

Bun runs the bulk of fast pure-TS tests. Vitest runs tests that need the Svelte 5 runes compiler — those live under *.runes.ts so bun's default *.{test,spec}.ts glob skips them.

Related