Lumen

Lumen

The editor stack for hlang graphs, in three packages that depend in one direction.

Lumen edits an IGraph and runs it with the interpreter. It is generic: it knows nodes, ports and graphs, and nothing about any one product. A product adds its own node types and panels on top.

Lumen is 0.x. Its APIs can change in any minor release.

Three layers

editor-coreeditor-reacteditor-ui
LayerModelBindingDesign
Depends onir, interpretereditor-core, React Floweditor-react
Knows React?NoYesYes

Dependencies point one way. editor-core is headless: it has no React, no DOM and no RxJS in its public surface, and it runs in Node for tests. editor-react maps the core's state to React Flow and back. editor-ui is the design system: nodes, edges, panels and the editor shell.

The core

Every change to a graph is a command, so undo and redo cover everything, and validation is never bypassed.

TypeScript
import { createEditorCore, AddNodeCommand } from '@hlang-org/editor-core';

const core = createEditorCore();
core.registry.register(numbers); // a NodeDescriptor: code, kind, ports, default parameters

const node = core.registry.createNode('numbers', { x: 0, y: 0 });
core.commands.execute(new AddNodeCommand(node));
core.history.undo();
core.history.redo();

const graph = core.serialization.toIGraph(core.model); // IGraph

Pass a graph runner to run the graph. InterpreterGraphRunner runs it with @hlang-org/interpreter:

TypeScript
import { Flow } from '@hlang-org/runtime';
import { NodeRegistry } from '@hlang-org/interpreter';
import { createEditorCore, InterpreterGraphRunner } from '@hlang-org/editor-core';

const runtimeNodes = new NodeRegistry().register('numbers', Numbers);
const core = createEditorCore({ graphRunner: new InterpreterGraphRunner(runtimeNodes, () => new Flow()) });

const handle = core.execution?.run(core.model);
handle?.dispose();

Connection rules

The connection validator accepts only output-to-input links that respect the node kinds, and always allows a cycle between Transform nodes. In editor-react, an edge's source is an output port and its target is an input port, the same direction as an IGraph connection.

Embedding the editor

The editor on the home page is these three packages with a kit of generic stream nodes. When the editor is one part of a page, pass scope to useEditorHotkeys, so its shortcuts answer only to keystrokes inside the editor:

TypeScript
const root = useRef<HTMLDivElement>(null);
useEditorHotkeys(core, { scope: root });

The full APIs are in the references for editor-core, editor-react and editor-ui.

The examples on this page run in CI. Edit this page on GitHub