Esta documentación está disponible en inglés. La documentación en español comienza en la versión 1.2.

Get started

Start with schemd 0.5

se pronuncia como skemd /skɛmd/

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

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.

Compile 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.2 kΩR_f = 2.2\,\text{k}\Omega and Cf=4.7 nFC_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

Move 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.

Read 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.