Get started

Start with schemd 0.5

pronounced like skemd /skɛmd/

Compile multi-domain engineering diagrams into deterministic accessible SVG, and inspect the circuit behind them.

sheet
1 / 14
rev
v0.5
sections
3
compiled
3

schemd is pronounced like “skemd” (/skɛmd/). Version 0.4 removes the component and connection ceilings, makes the remaining budgets configurable per compilation, and reports the canonical terminal behind every port alias.

01 / InstallCompile on the server

Install the compiler and hand it a string. There is no DOM to wait for, no font to load, no layout pass to measure — so this runs during a build, inside a request handler, or in a test.

npm i @schemd/core
import { compileSchematic, parseSchematicFence } from '@schemd/core';

const fence = parseSchematicFence('schemd bounds="980x420" title="Front end"')!;
const { svg, document, metrics } = compileSchematic(source, fence);

Three things come back: svg is the vector, document is the validated model behind it, and metrics is what the compilation cost. Note that we get all three from one pass — the model is never re-derived by parsing our own output.

The diagram beside this is an instrumentation front end: a bridge sensor into a differential amplifier, a single-pole anti-alias filter, then the converter. Because the topology is declared rather than drawn, we can reason about it. The filter's corner sits at

fc=12πRfCff_c = \frac{1}{2\pi R_f C_f}

and with Rf=2.2kΩR_f = 2.2\,\text{k}\Omega and Cf=4.7nFC_f = 4.7\,\text{nF} that lands near 15.4 kHz15.4\ \text{kHz} — above the signal band, below half the sample rate.

source:BRIDGE "V_{sense}" at (80, 140) #blue [type=voltage-dc]
amplifier:A1 "INA, G = 100" right-of BRIDGE by 120 #cyan [type=instrumentation]
resistor:RF "2.2 k\Omega" right-of A1 by 120 #amber
junction:NODE "f_c" right-of RF by 110 #cyan
capacitor:CF "4.7 nF" below NODE by 60 aligned-x with NODE #cyan [orientation=down]
ground:GND "AGND" below RF by 90 aligned-x with RF #slate
port:ADC "to ADC" right-of NODE by 150 #emerald

BRIDGE.positive -> A1.in #blue [ortho]
BRIDGE.negative -> GND.in #slate [ortho]
A1.out -> RF.in #cyan [line]
RF.out -> NODE.node #amber [line]
NODE.node -> CF.in #cyan [ortho]
CF.out -> GND.in #cyan [ortho]
NODE.node -> ADC.in #emerald [line marker-end=arrow]
compiled by @schemd/core → shown in the rail

02 / MigrationMove to 0.5 deliberately

Existing documents keep compiling. Two changes are worth reading before we lean on them.

A port alias is accepted, not preserved. We may still write R1.r or X1.out; 0.5 rewrites both endpoints to the terminal they resolve to before topology, the netlist, and the design rules ever see them. That is what makes R1.out -> A.in and R1.r -> B.in one net rather than two colliding ones. The consequence is concrete: anything a host keys on the spelling we typed — an overlay, a hover card, a simulation timeline — must key on the emitted name instead. canonicalPortName is exported so we can ask for the same answer the compiler used.

The component and connection ceilings are gone. MAX_SCHEMATIC_COMPONENTS, MAX_SCHEMATIC_CONNECTIONS, and their SCHEMATIC_LIMITS entries were removed; the compiler is linear in both, and sixty-four thousand components compile in about a second. A per-compilation limits option replaces them — see resource budgets.

port:A "A" at (70, 100) #blue
port:B "B" below A by 60 aligned-x with A #blue
xor:X1 "A \oplus B" right-of A by 220 below A by 25 #cyan
port:Q "Q" right-of X1 by 280 #emerald

A.out -> X1.in1 #blue [line]
B.out -> X1.in2 #blue [line]
X1.out -> Q.in #emerald [line marker-end=arrow]
compiled by @schemd/core → shown in the rail

That last connection is written with the out alias and emits data-wire-source="X1.out1". Open it in the playground and read the raw SVG if that matters to us.

03 / BoundariesRead this before trusting a clean compile

A document that compiles is a drawing the compiler could place and route. It is not a claim that the circuit works. The failure mode of a tool like this is a confident picture of something wrong, so here is where the confidence stops.

  • verifyNetlist is structural linting, not verification. It runs deterministic rules over a flat connectivity model. It establishes nothing about analog correctness, timing, impedance, drive strength, metastability, quantum validity, or behaviour. A clean result means no rule fired.
  • Routing is greedy and never rips up. Each trace is placed against the ones already laid, so congestion is order-dependent: a route can be unroutable because of a choice made earlier, not because no arrangement exists.
  • The model is flat. No hierarchy, no sub-sheets, no simulation, no standards conformance. That suits documentation, teaching, and schematics-as-source — not large engineering designs.
  • Descriptions report connectivity, not intent. describeSchematic states what the netlist proves and deliberately names no archetype, because a confident wrong label is worse for a screen-reader user than an accurate structural one.
  • Published performance figures are narrow. Warm medians on one Apple Silicon and Node configuration. Run bun run benchmark on our own hardware.
port:CLK "CLK" at (70, 110) #amber
port:DATA "DATA" below CLK by 90 aligned-x with CLK #blue
port:DATAOUT "DATA'" right-of CLK by 620 aligned-y with CLK #emerald
port:CLKOUT "CLK'" below DATAOUT by 90 aligned-x with DATAOUT #amber

CLK.out -> CLKOUT.in #amber [ortho net=CLOCK]
DATA.out -> DATAOUT.in #blue [ortho net=PAYLOAD]
compiled by @schemd/core → shown in the rail

Those two traces must cross, and they belong to different nets — so the compiler draws a bridge rather than a junction. Had they carried the same net=, the crossing would have stayed continuous, because then it would be one conductor.