@mermanjs/node
v0.10.0
Published
Experimental native Node.js and static-site rendering bindings for Merman.
Maintainers
Readme
@mermanjs/node
Native Mermaid-compatible SVG rendering for Node.js 22+ and static-site build pipelines, without Chromium, a browser-WASM fallback, or a postinstall binary download.
Version 0.10.0 targets Mermaid 12.3.0 and uses Node binding API 1, Options JSON schema 3, and theme execution evidence schema 2. Keep the loader and native platform package at the same exact version and source revision. Check the host installation guide for verified versions and channel availability, and the stable upgrade guide for migration. The package remains experimental.
Version 0.10.0-alpha.1 retains Mermaid 12.2.0 and theme execution evidence schema 1; Node binding API 1 and Options JSON schema 3 are unchanged. Upgrade the loader and platform package together and update evidence consumers for the render-evidence changes.
Quick start
Install the loader package, not a platform package:
npm install --save-exact @mermanjs/[email protected]import { createNodeEngine } from "@mermanjs/node";
const engine = await createNodeEngine();
try {
const svg = await engine.renderSvg("flowchart TD\nA --> B");
console.log(svg);
} finally {
await engine.dispose();
}Create an engine once, reuse it for related work, and dispose it during teardown.
Compiled themes
The catalog groups 11 designs with 14 concrete presets, including paper-cut, carbon-copy, and
pop-art. Editor, Gruvbox, and Ayu provide both Light and Dark; other designs declare their own modes.
Select a preset through the top-level theme group in Options JSON schema 3. Discover preset
IDs through the initialized engine's theme catalog:
const catalog = JSON.parse(engine.metadataJson("theme-catalog"));
const themedSvg = await engine.renderSvg("flowchart LR\nA --> B", {
optionsJson: JSON.stringify({ version: 3, theme: { preset: "editor", appearance: "dark" } }),
});Each catalog preset represents one design. supported_appearances, default_appearance,
and appearances describe its modes; each mode contains the exact concrete_preset_id,
swatches, family scope, and application notes. Catalog available admits every appearance
under the current resource policy. All built-in presets remain alpha with empty appearance-specific
qualified_cells; availability and unknown
profile/admission IDs never establish portable output. Flowchart glow and shadow effects support
documented shapes in Classic and Neo; other families retain their own look and output restrictions.
SVG fonts resolve on the viewing host; exported recipes
do not embed them.
The async authoring helpers use the same queue, cancellation, and JSON operations as rendering:
const recipe = await engine.exportThemePreset("editor");
const editableJson = JSON.stringify(recipe, null, 2);
const importedRecipe = JSON.parse(editableJson);
const inspection = await engine.materializeTheme({
schema_version: 1, action: "inspect", recipe: importedRecipe,
});
const snapshot = await engine.materializeTheme({
schema_version: 1, action: "materialize", recipe: importedRecipe, appearance: "dark",
});
const svg = await engine.renderSvg("flowchart LR\nA[Review] --> B[Publish]", {
optionsJson: JSON.stringify({ theme: { recipe: importedRecipe, appearance: "dark" } }),
});Save editableJson, edit its design values, and reload the edited JSON in place of the
round-trip string above. Keep schema_version, kind, and the complete recipe for customization
and exchange. Shared values live in recipe.design;
local exceptions live in recipe.design.appearances. snapshot.spec is a concrete snapshot,
while inspect reports every declared mode and reference provenance. Omitted appearance uses
the declared default; an explicit unsupported value fails. Resolve Follow-app/editor in the
host before submitting light or dark. A supplied theme replaces the complete previous
selection; null clears it.
materializeTheme() also accepts raw design JSON or raw request text, preserving duplicate-key
admission in Rust. describeThemeSupport() queries renderer mechanisms. Generic and synchronous
callers can continue using executeOperation() or executeOperationSync() with the same three
operation IDs; no additional native entry point is required. See the
theme guide
for authoring and portability, and the
current Node changelog
for upgrade actions.
SVG output pipelines
renderSvg() and renderSvgSync() use the parity pipeline by default. This preserves the
Mermaid-compatible SVG structure for trusted input and parity-sensitive consumers. parity is
not a browser DOM-admission or sanitization check.
For a resvg/usvg consumer that cannot display HTML labels, select resvg-safe explicitly:
const options = {
optionsJson: JSON.stringify({ svg: { pipeline: "resvg-safe" } }),
};
const svg = await engine.renderSvg(source, options);The supported pipeline values are parity, readable, and resvg-safe. readable is an advanced
text overlay for consumers that ignore HTML labels; browsers can display both copies and show
overlapping text. Use parity for browser display. resvg-safe also removes unsupported styles,
so it is not a lossless replacement for readable. HTML embedding still requires a host-owned
admission policy; selecting a pipeline alone does not establish browser DOM safety.
The resvg-safe pipeline removes browser-only content such as active elements and foreignObject
labels. It does not
provide browser Document or owner-document admission APIs; use @mermanjs/web* for browser DOM
mounting.
Supported hosts
- macOS arm64 and x64
- Linux arm64 and x64 with glibc or musl
- Windows x64 with MSVC
- Node.js
>=22
The root loader selects one matching exact-version @mermanjs/node-<platform> optional dependency.
Do not install platform packages directly. The loader contains no native or WASM binary and never
downloads one during installation or runtime.
If the native addon is installed but the host dynamic loader rejects it (for example because the
glibc baseline is too new), construction throws MermanNativeLoadError with the target package
name and an explicit suggestion to use @mermanjs/node-wasm. It does not silently switch
transports.
Capability boundary
The package provides deterministic static SVG with math, Cytoscape and ELK layouts, metadata, layout plans, runtime-catalog inspection, and generic admitted operations. GNU Linux targets retain glibc 2.31 as their compatibility floor. Binary export, analysis, ASCII, text-measurement callbacks, browser fallback, and runtime downloads are outside this package surface. Requests for unavailable optional capabilities return Merman's typed missing-capability error.
Render formulas with the ordinary SVG methods:
const formula = await engine.renderSvg('flowchart LR\nA["$$x^2$$"]');The runtime catalog advertises math for both the native and explicit Node-WASM products.
The default interactive resource profile admits formulas; an explicitly constrained profile
rejects them even when math is compiled. For untrusted input, enforce host execution, memory,
and concurrency limits alongside the selected resource profile.
Lifecycle and concurrency
The Promise-based API uses a bounded queue. An AbortSignal cancels queued work immediately and
requests cooperative cancellation after Rust work starts; started work stops at the next renderer
or transport-admission checkpoint and reports Merman's typed cancellation details. Per-operation
timeoutMs values use a monotonic Rust deadline and must be integers from 0 through 4,294,967,295.
dispose() waits for active work to settle and rejects new work once teardown begins.
Use renderSvgSync() only for an explicitly synchronous static-site build path. It refuses to run
while asynchronous operations are active or queued.
Loaded icon packs
Load Iconify pack JSON in the host and pass its text as a constructor service:
import { readFile } from "node:fs/promises";
import { createNodeEngine } from "@mermanjs/node";
const json = await readFile("./icons.json", "utf8");
const engine = await createNodeEngine({
iconPacks: [{ json, registrationName: "app" }],
});
try {
const svg = await engine.renderSvg('flowchart TD\nA@{ icon: "app:rocket", label: "Launch" }');
console.log(svg);
} finally {
await engine.dispose();
}The example assumes the pack contains rocket. json must be the loaded JSON string.
Omit registrationName to use the pack's prefix. A registered icon takes precedence over a
matching built-in icon in that engine. Packs are admitted once into immutable engine-local
state; changing the original array or rendering through another engine does not change it.
Per-request options preserve the installed registry.
iconPacks belongs to CreateNodeEngineOptions, separately from bindingOptions.
An omitted value or [] retains ordinary rendering. The constructor performs no network or
filesystem access. The fixed pack/content limits advertised by the icon-registry constructor
contract apply across native and Node-WASM. Content failures retain
MermanOperationError.iconRegistryDetails, and resource failures also retain
resourceDetails. A failed constructor publishes no engine. Dispose engines when their build
or request scope ends.
See the Node.js package guide for source-checkout development, package verification, and release design.
