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

@knowvah/dot-engine

v1.6.0

Published

Faithful TypeScript port of Graphviz (dot, neato, fdp, sfdp, circo, twopi, osage, patchwork) — no C: no native binary, no WASM. Oracle-verified against the C implementation, runs in the browser.

Readme

@knowvah/dot-engine

A faithful TypeScript port of Graphviz — the graph-visualization toolkit that originated at AT&T Bell Labs (a foundational technical report dates to 1991). It parses the DOT language, runs Graphviz's layout engines, and emits SVG. Ported line-by-line from the canonical C source; see Graphviz on Wikipedia for background.

The defining property: pure TypeScript — no C. No compiled Graphviz binary, no WASM build of it, no native dependencies. It runs in a browser or in Node with zero external dependencies at runtime. The goal is the closest achievable fidelity to the C implementation, which is treated as the canonical specification (see CLAUDE.md). In practice the dot engine is conformant with the C binary on the golden corpus: numeric coordinates agree to a tight deterministic tolerance (±0.01) and non-numeric content is exactly equal. This is the measured "match" bar, not a claim of literal byte-for-byte SVG output — see Conformance for the exact definition and the comparison code, and known divergences for the documented exceptions.

Status: published and semver-stable from 1.0.0. The public API — renderSvg, /api, /render — follows semver: a breaking change gets a major bump. The version line does not track C feature coverage, and the two are independent signals.

The rendering surface is settled: parsing, all eight layout engines, SVG, and the json / xdot / dot / plain / imagemap text formats. It does not cover the whole C feature surface, and the remainder is out of scope rather than pending — the unported C areas stay inventoried in the port catalog so you can check a specific feature before depending on it. The dot engine is the primary fidelity target. See Status & coverage below.

Why this exists

Existing ways to render DOT in a JS environment shell out to a Graphviz binary, a rendering server, or a WASM build — none of which run everywhere a browser does, and all of which add deployment friction. @knowvah/dot-engine removes that dependency entirely: the layout engine is TypeScript.

Install

npm i @knowvah/dot-engine

Ships as ESM bundles with TypeScript declarations, zero runtime dependencies. Entry points: @knowvah/dot-engine (core), @knowvah/dot-engine/api (graph-building API), @knowvah/dot-engine/render (renderers).

To build from source instead: clone, npm install, npm run build (esbuild bundles + .d.ts declarations into dist/).

Quick start

import { renderSvg } from '@knowvah/dot-engine';

const dot = `
  digraph {
    a -> b;
    b -> c;
    a -> c;
  }
`;

const svg = renderSvg(dot, 'dot');
console.log(svg); // <svg ...>...</svg>

renderSvg(dotSource, engine) parses the DOT source, runs the named layout engine, renders to SVG, and returns the SVG string. On failure it throws a structured error — see Error handling.

Error handling

renderSvg throws on any failure; for a result-style alternative that never throws, use tryRenderSvg:

import { tryRenderSvg } from '@knowvah/dot-engine';

const result = tryRenderSvg('digraph { a ->', 'dot');
if (result.svg) {
  // success
} else {
  const err = result.errors[0];
  console.error(err.code, err.friendlyMessage, err.location);
  // 'SYNTAX_UNEXPECTED_EOF' · 'The DOT source ended unexpectedly …' · { line, column, offset }
}

A RenderResult is { svg } or { errors } (never both); errors holds at most the first failure. Each entry is a plain, JSON-serializable GvError:

| Field | Meaning | |-------|---------| | type | 'syntax' · 'semantic' · 'render' | | code | Stable machine key (an i18n key) — branch on this | | message | Concise technical text | | friendlyMessage | Approachable, non-localized English for end users | | location? | { line, column, offset? } — the real error position | | expected? | Parser expectation list, for syntax errors only |

The code values are a closed union: SYNTAX_ERROR, SYNTAX_UNEXPECTED_EOF, EDGE_OP_DIRECTED_IN_UNDIRECTED, EDGE_OP_UNDIRECTED_IN_DIRECTED, HTML_PARSE_ERROR, RENDER_ERROR, GENERIC_ERROR.

The throwing renderSvg raises the same structured values as real Error subclasses — ParseError (syntax) and RenderError (render) — each carrying code, type, friendlyMessage, and (for ParseError) location/expected. Branch on .code/.type rather than instanceof per subclass.

Layout engines

All eight Graphviz layout engines are registered. Pass the name as the second argument to renderSvg:

| Engine | Layout style | |--------------|-----------------------------------------------| | dot | Hierarchical / layered directed graphs | | neato | Spring-model (Kamada–Kawai) | | fdp | Force-directed | | sfdp | Multiscale force-directed (large graphs) | | circo | Circular | | twopi | Radial | | osage | Clustered | | patchwork | Squarified treemap |

dot receives the most fidelity attention because the primary consumer is DOT-centric. Per-engine coverage against the C source is tracked in the port catalog.

Browser usage

The library uses no Node-only APIs and is safe to bundle for the browser. One caller-supplied hook may be required:

  • Image sizing. When a graph references external images (e.g. node [image="foo.png"]), Graphviz needs each image's intrinsic dimensions. Because the library cannot read the filesystem, provide a sizer via setImageSizer:

    import { setImageSizer } from '@knowvah/dot-engine';
    
    setImageSizer((src) => ({ w: 64, h: 64 })); // return null if unknown

Text measurement

Layout needs to know how wide each label is. By default this uses a built-in, deterministic metric model — no font files, identical output on every platform. In the browser the library automatically measures with the page's own canvas (the same font the browser renders the SVG with).

For host-faithful Node measurement (real kerning/shaping, matching the local fonts the SVG will be rendered with), install the optional canvas peer and wire it once via setTextMeasurer:

import { setTextMeasurer, CanvasTextMeasurer } from '@knowvah/dot-engine';
import { createCanvas } from 'canvas'; // optional peer: `npm i canvas`

setTextMeasurer(new CanvasTextMeasurer(createCanvas(0, 0).getContext('2d')));

Trade-off: the built-in model is reproducible across machines; the host-faithful path matches the rendering font but is platform-dependent (as native graphviz is). See the Text measurement guide for the full contract.

CanvasTextMeasurer accepts any 2D context that implements font + measureText().width — the canvas package is one provider, not a requirement. @napi-rs/canvas (prebuilt N-API binaries, no prebuild-install) works too:

import { createCanvas } from '@napi-rs/canvas';
setTextMeasurer(new CanvasTextMeasurer(createCanvas(0, 0).getContext('2d') as unknown as CanvasRenderingContext2D));

Pick the engine you render with: the two libraries resolve system fonts differently (measured on macOS: bold Helvetica and Times/Courier substitution diverge by 4–14% between them, while regular-weight Helvetica/Arial agree to <0.2%). "Host-faithful" means measuring with the same font stack that later draws the text — mixing engines reintroduces the mismatch you opted in to avoid.

Security

Treat rendered output as attacker-controlled markup whenever the DOT source is untrusted. The SVG and image-map strings this library produces embed graph attribute values (labels, id, class, href/URL, image, stylesheet, tooltips) directly. All such values are XML-escaped exactly as native Graphviz does, so they cannot break out of an element or attribute — but, matching upstream Graphviz, the library does not filter URL schemes or validate resource origins. A DOT source you did not author can therefore contain:

  • href="javascript:…" / URL="javascript:…" on a node or edge (executes on click),
  • image="…" (usershape) or an image-map href pointing at an arbitrary external origin,
  • a stylesheet="…" referencing an external CSS origin.

This is deliberate — scheme/origin policy belongs to the page embedding the output, not to the layout library. If you render untrusted DOT and embed the result inline (innerHTML, dangerouslySetInnerHTML, an inline <svg>), apply a Content-Security-Policy on the host page as the control point:

  • script-src (without 'unsafe-inline') — neutralizes javascript: hrefs and any inline event handlers,
  • img-src — constrains <image> / usershape origins,
  • style-src — constrains the stylesheet processing instruction.

If you cannot set a CSP, sanitize the returned markup (e.g. DOMPurify with an SVG profile) before inserting it, or render from trusted DOT only.

Public API

// Primary entry point. Throws a structured GvError (ParseError / RenderError).
function renderSvg(dotSource: string, engine: string): string;

// Result-style entry point: returns { svg } or { errors: [GvError] }, never throws.
function tryRenderSvg(dotSource: string, engine: string): RenderResult;

// Structured error contract (see "Error handling").
interface GvError { type; code; message; friendlyMessage; location?; expected?; }
interface RenderResult { svg?: string; errors?: GvError[]; }
type GvErrorType = 'syntax' | 'semantic' | 'render';
type GvErrorCode = 'SYNTAX_ERROR' | 'SYNTAX_UNEXPECTED_EOF' | /* …7 total */ 'GENERIC_ERROR';
class ParseError extends Error implements GvError { /* type:'syntax' */ }
class RenderError extends Error implements GvError { /* type:'render' */ }

// Parse DOT into the in-memory graph model (without laying it out).
function parse(dotSource: string): Graph;

// Supply intrinsic dimensions for external image references (browser/Node).
function setImageSizer(sizer: ImageSizer | null): void;
type ImageSizer = (src: string) => { w: number; h: number } | null;

// Multi-format render + structured xdot draw-ops (from `@knowvah/dot-engine/render`,
// also re-exported from the root package).
function render(g: Graph, format: OutputFormat, opts?: { engine?: string }): string;
function getDrawOps(g: Graph, opts?: { engine?: string }): XdotOp[];

// Programmatic graph construction and computed-geometry readback (from
// `@knowvah/dot-engine/api`, also re-exported from the root package) — build a graph
// without writing DOT source, or read back node/edge/bbox coordinates after
// layout.
function createGraph(opts?: CreateGraphOptions): GvGraphBuilder;
function addEdge(g: Graph, tail: Node, head: Node, name?: string): Edge;
function getLayout(g: Graph, opts?: { yAxis?: 'up' | 'down' }): LayoutSnapshot;

// Lower-level orchestration, for callers that need engine/render control.
class GvcContext { /* register engines/renderers, layout, render */ }
function renderWithContext(ctx: GvcContext, graph: Graph, format: string): string;

Most callers only need renderSvg (or tryRenderSvg for result-style error handling). parse, GvcContext, and renderWithContext are exposed for advanced use — e.g. inspecting the parsed model, or driving layout and rendering as separate steps. createGraph/addEdge, getLayout, render, and getDrawOps are the graph-building, geometry-readback, multi-format render, and structured-draw-op surfaces respectively — see the API guide for full walkthroughs of each.

Development

npm test            # run the test suite (vitest)
npm run coverage    # run with coverage (v8)
npm run typecheck   # tsc --noEmit, strict mode, zero errors required
npm run build       # bundle to dist/index.js

The test suite verifies port fidelity by comparing generated SVG against output from the canonical C Graphviz. New behavior is pinned to the C source — see CLAUDE.md for the porting rules and the port catalog for status.

Status & coverage

  • What works: parsing, all eight layout engines, SVG output, and the intermediate json / xdot / dot / plain / imagemap text formats.
  • Conformance bar: a render is conformant when it matches the C oracle within a ±0.01 deterministic tolerance (dot, circo, twopi, osage, patchwork) or is characterized at a looser ±0.5 tolerance for the iterative force-directed engines (neato, fdp, sfdp) — never literal byte equality. Full definition: Conformance.
  • Current parity: dot SVG 762/788 conformant (0 unaccepted tracked gaps — every remaining non-conformant graph is a documented, accepted divergence); dot xdot 761/761; patchwork xdot 762/762; osage xdot 755/762; circo xdot 752/762; twopi xdot 746/762 (all deterministic, ±0.01). neato/fdp/sfdp are characterized at ±0.5 rather than gated at the deterministic bar, per the tolerance split above. These figures are a snapshot of one row each from test/corpus/PARITY.md, which is generated by test/corpus/parity-report.ts and covers every engine × surface track (SVG, xdot, json, plain, imagemap); read it, or the docs-site parity pages, rather than this bullet for current counts.
  • What's tracked: every C algorithm and its port status is inventoried in the port catalog. Items marked [ ] there are genuinely unported — not footnotes, and not a roadmap either. Treat the catalog as the authoritative answer to "is feature X in?".
  • Known behavioral divergences from C (differences investigated, root-caused, and deliberately not chased) are listed in docs/known-divergences.md.

Known limitations

  • The feature surface is narrower than C's, by design. The C source defines completeness, and this port does not reach it. The uncovered areas are listed in the port catalog rather than hidden — check it before depending on a specific C feature. They are not a backlog; absence from the shipped surface is a scope decision, not a pending item.

  • Very large graphs are impractical to lay out at runtime. Graphs beyond roughly 10k nodes or a few MB of DOT source hit a scale ceiling — layout (mincross, ranking, spline routing) is superlinear. This is shared with upstream Graphviz, not a port-specific defect: on such inputs native dot, the WASM builds (@hpcc-js/wasm-graphviz), and this engine all time out or run out of memory alike (see the large-source note in test/corpus/PERF.md). This engine does not leak — its per-render heap is flat; the limit is strictly graph size.

    For graphs at that scale, pre-render to SVG once at build time and serve the static .svg rather than laying out in the browser on every view. The build-time site adapters in knowvah/dot-plugins (published on NPM) do exactly this — e.g. @knowvah/vitepress-plugin-dot, @knowvah/eleventy-plugin-dot, @knowvah/docusaurus-plugin-dot, and the framework-agnostic @knowvah/dot-markdown-it.

License

Eclipse Public License v2.0 (EPL-2.0), matching upstream Graphviz. Every source file carries an SPDX-License-Identifier: EPL-2.0 header.