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

@schemd/core

v1.3.1

Published

Zero-dependency text-to-SVG compiler for schematics and UML.

Readme

@schemd/core

schemd — pronounced like “skemd” (/skɛmd/) — is a strict, deterministic text-to-SVG compiler for electrical, digital, quantum, and UML diagrams. It has no runtime dependencies and never touches a DOM, Canvas, browser layout, external font, raster asset, or getBBox().

The documentation site is the reference. Every page there is versioned per release line and every example is compiled by the real engine. This file is the short tour.

Version 1.3.0 needs Node.js 24 or newer.

Docs · Playground · Inspector · Changelog · Roadmap

Install

npm i @schemd/core # or bun add / pnpm add / yarn add

Compile

import { compileSchematic, parseSchematicFence } from "@schemd/core";

const fence = parseSchematicFence('schemd bounds="760x460" title="RC low-pass filter"')!;

const { svg, metrics } = compileSchematic(
  `source:VIN "AC" at (90, 150) #blue [type=voltage-ac]
resistor:R1 "1 k\\Omega" right-of VIN by 190 #amber
junction:VOUT "V_{out}" right-of R1 by 190 #cyan
capacitor:C1 "100 nF" below VOUT by 140 #cyan [orientation=down]

VIN.positive -> R1.in #blue [line]
R1.out -> VOUT.node #amber [line]
VOUT.node -> C1.in #cyan [ortho]`,
  { ...fence, mode: "full" },
);

Two kinds of line, and a part may state its position either way:

kind:ID "label" at (x, y) #color [options]
kind:ID "label" right-of REF by 190 #color [options]
SOURCE.port -> TARGET.port #color [line|bezier|ortho options]

The quoted label may be left out: junction:J1 at (300, 120) #slate draws the designator alone.

A block you draw more than once can be written once as a sub-circuit and placed by name. Instances may nest, take parameters, and are reached through the terminals the block exposes:

subcircuit divider [top="10 k" bottom="10 k"] {
  resistor:RT "{{top}}" at (0, 0) #amber [orientation=down]
  resistor:RB "{{bottom}}" below RT by 40 #amber [orientation=down]
  RT.out -> RB.in #amber [ortho]
  expose mid = RT.out
}
divider:U1 at (200, 100) [top="22 k"]
U1.mid -> ADC.in #blue [ortho]

A net that reaches several terminals is drawn as a tree: each further wire leaves the copper its net already has, with a junction dot, instead of running back to its own pin.

A gate or block that spans several wires can take its height from them. fit=wires lines each of its terminals up with the terminal wired to it and centres the part between them, so a CNOT's rails sit on its qubits and an IC's pins on the wires that feed them — no jogs. A part placed by relation can line up one of its own terminals instead of its centre:

cnot:CX "CNOT" at (460, 150) #purple [fit=wires]
ic:U1 "Oracle" at (470, 230) #purple [left="i0,i1,i2" right="o0,o1,o2" fit=wires]
and:G2 "G" right-of R2 by 80 aligned-y with R2.out at in2 #blue [inputs=2]

Bad variants, duplicate options, unknown ports, mismatched bus widths, unsafe colours, and out-of-bounds geometry all fail with a line-accurate diagnostic before any SVG is written. diagnoseSchematic lists every problem in a document at once, each with its line and column range, for an editor to underline.

What it compiles

Electrical, digital, quantum, and UML families — the full vocabulary is in the component reference.

A compiled diagram is also a model. buildNetlist gives you nodes, nets, and edges; verifyNetlist runs seven design rules over them; describeSchematic turns the same connectivity into prose for a screen reader. The compilation also hands back its own working: sourceMap (every vector's declaration line), placements (what each relation resolved to), and routing (negotiation rounds, torn-up wires, per-cell congestion).

Every kind's options are also available as data. @schemd/core/schema exports SCHEMATIC_COMPONENT_SCHEMAS, which gives each option's type, its allowed values or range and its default, and describeComponentPorts(kind, options), which asks the parser for a part's terminals. A test proves the schema against the parser, so an editor can list what a kind accepts without copying a table.

And the output is readable back. snapshotSchematic writes a text digest of the geometry, so a routing change reviews as a handful of moved coordinates rather than an image diff; parseSchematicSvg reads a full-mode SVG back into declarations, naming what the markup does not carry rather than guessing at it.

Before you rely on it

Please read the limitations before treating a clean compile as an engineering result. The short version:

  • verifyNetlist is structural linting, not verification. It cannot tell you anything about analog correctness, timing, impedance, drive strength, metastability, or quantum validity. A clean result means no rule fired.
  • Routing is heuristic and bounded. Each net is routed once on a channel lattice in an order taken from the geometry, and any contention left over is negotiated away for a bounded number of rounds. Wires are placed where a draughtsman would put them in the common cases, not provably optimally, and a document crowded beyond what the lattice can separate is rejected with the wire that could not be placed.
  • The model is flat underneath. Sub-circuits are expanded into ordinary parts before anything else reads the document, so the netlist, the SVG and the decompiler all see the flattened drawing — there are no sub-sheets, no ports between pages, no simulation, no timing analysis, and no certification. It suits documentation, teaching, and schematics rather than large engineering designs.
  • Text is measured for Arial and placed without regard to its neighbours. Label widths match Arial, Helvetica, Liberation Sans and Arimo, which the SVG names as its font; on a crowded sheet a label can overlap another part or a wire. Space parts by at least a label's width.
  • Compiling source you did not write? Pass limits and a timeout of your own.
  • Published performance figures are narrow. Warm medians on one machine. Run bun run benchmark on yours.

Compatibility

1.0 reads the same language and keeps every entry point's signature; its API additions are diagnoseSchematic, SCHEMATIC_DIAGNOSTIC_CODES and the column, endColumn and code fields on SchematicSyntaxError. Drawings change, because the router is new: multi-terminal nets are trees, wires written to a junction part meet at it, a wire crossing its own net is dotted, bridges sit on vertical runs, text is measured rather than squeezed, every terminal of a digital block has its own slot, and geometry is exact to three decimals. A document 0.8 rejected is rejected with the same message, except that a crossing the new router can route around now compiles. There are two new rejections: a line or Bézier wire drawn through another component's pin, which reads as a connection it is not, and a flip-flop terminal named with another variant's letter, which 0.8 drew on the first input. The changelog lists every change.

Upgrading across 0.4 as well? See migration — port aliases now report their canonical terminal, and the component and connection ceilings are gone.

Contributing

bun install, then bun run test:visual:install once, then bun run release:check. CONTRIBUTING.md covers the invariants and what each gate protects.

Issues · MIT

Community

Contributing · Support · Security · Code of Conduct · Maintainers