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

@sgummalla-works/sketchon

v0.3.2

Published

Sketchon — coordinate-free diagram rendering. A model authors the spec; a deterministic engine computes every pixel. Spec in, SVG out. Runs in Node and the browser.

Downloads

54

Readme

@sgummalla-works/sketchon

Coordinate-free diagram rendering. The model declares what the diagram is; a deterministic layout engine computes every pixel. No hand-coded coordinates, no overlapping labels.

Architecture

Ports & Adapters (Hexagonal Architecture) + Strategy pattern:

domain/      — DiagramSpec types + validation (pure, zero external deps)
ports/       — Technique interface (the Strategy contract)
application/ — routing + registry (orchestration layer)
adapters/    — concrete engines: Satori, ELK, Sequence, Baseline

Adding a new engine = implement Technique, register in application/registry.ts. No other file changes.

Install

npm install @sgummalla-works/sketchon

Quick start

import { renderDiagram, validateSpec } from '@sgummalla-works/sketchon';
import type { DiagramSpec } from '@sgummalla-works/sketchon';

const spec: DiagramSpec = {
  id: 'my-flow',
  kind: 'flow',
  nodes: [
    { id: 'a', label: 'Start',   shape: 'pill',    emphasis: 'muted'    },
    { id: 'b', label: 'Process', shape: 'rounded', emphasis: 'primary'  },
    { id: 'c', label: 'Done',    shape: 'pill',    emphasis: 'success'  },
  ],
  edges: [
    { from: 'a', to: 'b' },
    { from: 'b', to: 'c', label: 'success' },
  ],
};

// Validate before rendering
const issues = validateSpec(spec);
if (issues.length) throw new Error(issues.map(i => i.message).join(', '));

// Render — engine is chosen automatically from spec.kind
const { svg, width, height } = await renderDiagram(spec);
// svg is a standalone <svg> string, ready for display or rasterisation

Node vs browser

The package runs in both environments and bundlers pick the right build automatically (via the browser / node export conditions). The only difference is where the Satori engine gets its fonts + emoji:

| Environment | Default asset source | |---|---| | Node (index.js) | Bundled Inter TTFs + Twemoji read from disk (assets/) | | Browser (index.browser.js) | Inter weights + Twemoji SVGs fetched from a CDN, cached in memory |

Everything else — the spec, validation, routing, all four engines — is identical. The public API is the same on both.

// In a browser app (e.g. React), the import resolves to the browser build:
import { renderDiagram } from '@sgummalla-works/sketchon';
const { svg } = await renderDiagram(spec); // fonts/emoji fetched on first render

Overriding fonts / emoji (configureAssets)

The defaults work out of the box. To go fully offline (no CDN fetch) or use a custom font, inject your own provider — only the methods you pass are replaced:

import { configureAssets } from '@sgummalla-works/sketchon';

configureAssets({
  // e.g. bundle the font with your app and hand Satori the bytes directly:
  loadFonts: () => [{ name: 'Inter', data: myInterArrayBuffer, weight: 400, style: 'normal' }],
});

Diagram kinds and engines

| kind | Engine | Use for | |----------------|-------------|---------| | concept | Satori | Explainer / illustrative panels with rich cards, a "why" column, and a legend | | mindmap | Satori | Central topic + radiating branches | | sequence | Sequence | Actor lifelines + ordered message arrows | | flow | ELK | Flowcharts, decision trees | | architecture | ELK | System components + connections with group containers | | state | ELK | State machines | | er | ELK | Entity-relationship schemas |

Override the engine for a spec with spec.renderer: 'satori' | 'elk' | 'sequence'.

DiagramSpec reference

interface DiagramSpec {
  id:           string;          // stable id (used in filenames + gallery)
  kind:         DiagramKind;     // selects layout engine
  title?:       string;          // optional heading
  direction?:   'TB'|'BT'|'LR'|'RL'; // flow direction for graph kinds
  nodes:        DiagramNode[];   // at least one
  edges:        DiagramEdge[];   // may be empty
  groups?:      DiagramGroup[];  // visual containers / clusters
  annotations?: DiagramAnnotation[]; // callout text beside nodes
  categories?:  DiagramCategory[];   // legend colour groups (concept diagrams)
  columns?:     ColumnHeaders;   // left/right column headings (concept diagrams)
  renderer?:    'satori'|'elk'|'sequence'; // override auto-routing
  theme?:       ThemeHints;      // mode: 'light'|'dark', density
}

Node

interface DiagramNode {
  id:            string;
  label:         string;           // primary text
  sublabel?:     string;           // second line
  sublabelParts?: string[];        // dot-joined sub-line tokens (for card shape)
  shape?:        NodeShape;        // box | rounded | pill | circle | diamond |
                                   // cylinder | actor | note | icon | card | checkpoint
  emphasis?:     Emphasis;         // normal | primary | muted | danger | success | accent
  icon?:         string;           // emoji (e.g. '🔍') or semantic name
  group?:        string;           // group id this node belongs to
  category?:     string;           // colour category id (for legend)
}

Edge

interface DiagramEdge {
  from:      string;          // source node id
  to:        string;          // target node id
  label?:    string;          // edge text
  style?:    'solid'|'dashed'|'dotted';
  arrow?:    'none'|'open'|'filled';
  semantic?: 'default'|'dependency'|'dataflow'|'message'|'return'|'association';
}

Concept / panel diagram example

const spec: DiagramSpec = {
  id: 'agent-tools',
  kind: 'concept',
  columns: { primary: 'Tool', annotation: 'Why it is mandatory' },
  categories: [
    { id: 'retrieval', label: 'Retrieval', emphasis: 'primary' },
    { id: 'execution', label: 'Execution', emphasis: 'success' },
  ],
  nodes: [
    { id: 'search', icon: '🔍', label: 'Web search',
      sublabelParts: ['real-time lookup', 'citation'],
      shape: 'card', category: 'retrieval' },
    { id: 'exec', icon: '⚙️', label: 'Code execution',
      sublabelParts: ['sandboxed', 'formula checking'],
      shape: 'card', category: 'execution' },
  ],
  edges: [{ from: 'search', to: 'exec', arrow: 'filled' }],
  annotations: [
    { target: 'search', text: 'Prevents hallucinations by anchoring claims', placement: 'right' },
    { target: 'exec',   text: 'Validates numerical claims safely',            placement: 'right' },
  ],
};

Sequence diagram example

const spec: DiagramSpec = {
  id: 'oauth',
  kind: 'sequence',
  title: 'OAuth 2.0',
  nodes: [
    { id: 'u',    label: 'User',        emphasis: 'muted'   },
    { id: 'app',  label: 'Client app',  emphasis: 'primary' },
    { id: 'auth', label: 'Auth server', emphasis: 'accent'  },
  ],
  edges: [
    { from: 'u',   to: 'app',  label: 'click login' },
    { from: 'app', to: 'auth', label: 'redirect /authorize' },
    { from: 'auth',to: 'app',  label: 'auth code',
      style: 'dashed', semantic: 'return' },
  ],
};

Public API

| Export | Description | |--------|-------------| | renderDiagram(spec) | Main entry point. Auto-routes to the right engine. Returns Promise<RenderResult>. | | validateSpec(spec) | Pure validation. Returns ValidationIssue[] (empty = valid). | | selectEngineKey(spec) | Returns which engine key would be chosen ('satori'|'elk'|'sequence'|'baseline'). | | selectEngine(spec) | Returns the Technique that would render the spec. | | REGISTRY | Map of all registered engines (Record<EngineKey, Technique>). | | TECHNIQUES | Array of all registered engines (used by the lab harness). | | getTechnique(key) | Look up a specific engine by key. | | DiagramSpec + all spec types | Re-exported from domain/spec.ts. | | configureAssets(overrides) | Override the font/emoji source ({ loadFonts?, emojiToDataUri? }). Only the methods passed are replaced. | | setAssetProvider(provider) | Replace the whole asset provider (advanced; the entries call this to bind the env default). | | AssetProvider, SketchonFont | Types for the injection point. | | loadFonts | Node build only: loads bundled Inter TTFs from disk for Satori. | | emojiToDataUri | Node build only: resolves an emoji to a Twemoji SVG data-URI from disk. |

How to add a new layout engine

  1. Create src/adapters/my-engine.ts implementing the Technique interface.
  2. Add one entry to src/application/registry.ts — both REGISTRY and the EngineKey union.
  3. Add the new kind (if applicable) to KIND_ENGINE in src/application/router.ts.
  4. Write tests in src/adapters/my-engine.test.ts.

That is the only change required. The lab harness and all consumers pick up the new engine automatically.

Testing

npm test              # run all tests
npm run test:watch    # watch mode
npm run test:coverage # with coverage report

Tests: 85 unit tests across domain validation, application routing/registry, the adapters (incl. the asset provider + browser provider), and the architecture guard.