@layerd/wasm
v0.1.0
Published
Blazing fast, low-memory layered graph layout for JavaScript.
Downloads
95
Maintainers
Readme
@layerd/wasm
Sugiyama-style hierarchical graph layout — Rust port of Eclipse Layout Kernel's layered algorithm, compiled to WebAssembly.
Designed to be a drop-in alternative to elkjs for the common "nodes and edges" case, with identical layout quality and much faster execution (native Rust → WASM, no JS interpreter on the hot path).
Install
bun add @layerd/wasmThis package targets modern ESM bundlers and runtimes that can handle
WebAssembly module imports, top-level await, and import.meta.url asset
resolution. It is not intended to be imported directly by Node's bare ESM
loader without a bundler.
Usage
import { layout } from '@layerd/wasm';
const result = layout({
nodes: [
{ id: 'n1', width: 30, height: 30 },
{ id: 'n2', width: 30, height: 30 },
{ id: 'n3', width: 30, height: 30 },
],
edges: [
{ id: 'e1', source: 'n1', target: 'n2' },
{ id: 'e2', source: 'n2', target: 'n3' },
],
});
console.log(result.nodes[0]);
// { id: 'n1', width: 30, height: 30, x: ..., y: ... }elkjs-compatible edge form
edges: [
{ id: 'e1', sources: ['n1'], targets: ['n2'] },
]Both { source, target } and { sources, targets } work. Only the first element of each array is used (v1 does not support hyperedges).
API
layout(graph): LayoutResult
Runs the layered layout algorithm with ELK defaults and returns a new graph object annotated with positions.
Input
graph.nodes(orgraph.childrenfor elkjs compatibility) — array of{ id, width, height }graph.edges— array of{ id, source, target }or{ id, sources, targets }
Output
result.nodes[i]—{ id, width, height, x, y }result.edges[i]—{ id, source, target, bends: [{ x, y }, ...] }result.width,result.height— overall graph bounding box
Throws on invalid input (missing node referenced by an edge) or on any error inside the Rust layout pipeline.
layoutFlat(graph): FlatLayoutResult
Runs layout and returns typed arrays instead of allocating one JavaScript object per laid-out node, edge, and bend point.
Output
nodeWireIds,nodeWidth,nodeHeight,nodeX,nodeYedgeWireIds,edgeSourceWireIds,edgeTargetWireIds,edgeBendStart,edgeBendLengthbendX,bendYwidth,height
layoutView(graph): Lrd1LayoutView
Runs layout and returns a zero-copy view over the LRD1 result bytes. The view
keeps the output Uint8Array alive and reads fields on demand, avoiding both
object-result allocation and flat typed-array copies.
layoutBytes(input): Uint8Array
Runs layout on pre-encoded LRD1 bytes and returns LRD1 result bytes. This is the performance-oriented entry point for callers that already produce LRD1 or can cache encoded graph bytes across repeated layouts.
layoutFlatBytes(input): FlatLayoutResult
Runs layout on pre-encoded LRD1 bytes and returns a flat typed-array result.
This is the result-side counterpart to layoutBytes for callers that want to
cache or produce LRD1 input bytes and avoid object-result allocation.
layoutViewBytes(input): Lrd1LayoutView
Runs layout on pre-encoded LRD1 bytes and returns a zero-copy result view. This is the lowest-allocation result consumption API in the JavaScript SDK.
encodeLrd1(graph): Uint8Array
Encodes the object API graph shape into LRD1 input bytes.
decodeLrd1(bytes, graph?): LayoutResult
Decodes LRD1 result bytes into the object API shape. Pass the original graph when string or number ids should be restored from positional LRD1 ids.
decodeFlatLrd1(bytes): FlatLayoutResult
Decodes LRD1 result bytes into the same flat typed-array shape as
layoutFlatBytes.
decodeViewLrd1(bytes): Lrd1LayoutView
Decodes LRD1 result bytes into a zero-copy result view.
version(): number
Returns the internal wire format version.
Benchmark
Build the WebAssembly package first, then run the TypeScript benchmark with Bun.
cargo xtask build-wasm creates the wasm-bindgen files under pkg/ and runs
the Rslib build that emits the public SDK entry under dist/.
cargo xtask build-wasm
cargo xtask wasm-bench -- --nodes 256 --fanout 2 --iters 3 --warmups 0For SDK-only checks:
cd wasm/sdk
bun install
bun run build
bun run lintThe benchmark entrypoint remains:
cargo xtask wasm-bench -- --nodes 256 --fanout 2 --iters 3 --warmups 0
cargo xtask wasm-bench -- --nodes 256 --fanout 2 --iters 50 --warmups 5
cargo xtask wasm-bench -- --input /path/to/graph.json --iters 50
cargo xtask wasm-bench -- --input /path/to/graph.json --iters 3 --warmups 1 --profile
cargo xtask wasm-bench -- --input /path/to/graph.json --iters 3 --warmups 0 --only public --skip-elkjs
cargo xtask wasm-bench -- --input /path/to/graph.json --iters 3 --warmups 0 --only bytes --skip-elkjs --memory
cargo xtask wasm-bench -- --input /path/to/graph.json --iters 3 --warmups 0 --only flat --skip-elkjs --memory
cargo xtask wasm-bench -- --input /path/to/graph.json --iters 3 --warmups 0 --only flat-bytes --skip-elkjs --memory
cargo xtask wasm-bench -- --input /path/to/graph.json --iters 3 --warmups 0 --only view --skip-elkjs --memory
cargo xtask wasm-bench -- --input /path/to/graph.json --iters 3 --warmups 0 --only view-bytes --skip-elkjs --memoryThe default benchmark reports JavaScript LRD1 encoding, raw wasm byte-buffer
layout, the internal TypeScript object path, and elkjs layered layout on the
same graph. With --profile, it also reports JavaScript result decoding as a
separate row. Use --only public or --only bytes for isolated public SDK
object/bytes measurements; wasm-bindgen's shared glue state makes those paths
unsafe to mix with raw wasm rows in the same process. The repo bench pins
[email protected] and runs with Bun only.
Use --only all|encode|raw|bytes|flat-bytes|view-bytes|decode|sdk|public|flat|view|elkjs
when a timing or memory probe needs a single isolated path. Add --memory to include Bun
process.memoryUsage() start/max/end columns for RSS, heap, external memory,
and ArrayBuffers.
The JSON graph format is the same v1 surface as the public SDK:
{
"nodes": [
{ "id": "n1", "width": 30, "height": 30 },
{ "id": "n2", "width": 30, "height": 30 }
],
"edges": [{ "id": "e1", "source": "n1", "target": "n2" }]
}ELKT fixtures can be converted to this JSON subset with cargo xtask ffi-json <input.elkt> <output.json>.
What v1 does not support
The v1 API is intentionally minimal. The following ELK features are not yet exposed and will be added in future versions:
- Labels
- Explicit ports with side constraints
- Nested subgraphs (compound graphs)
layoutOptions— all options use ELK defaults
If you need any of these today, use elkjs instead.
License
MIT
