Clone
5
Creating a Node
Nicole Rappe edited this page 2025-05-16 15:11:14 -06:00

📦 How to Create a Custom Node in Borealis

This guide explains how to build your own node for the Borealis Workflow Automation Tool. It walks through the key structure, best practices, and configuration options. You'll find example code, inline commentary, and a copy-paste template at the bottom.


1. 📚 Node Anatomy: The Big Picture

Every Borealis node is a React component paired with an exported metadata object. Nodes communicate via the global window.BorealisValueBus (shared memory), and can specify configurable fields for the sidebar via a config array.

A node must:

  • Export an object with keys: type, label, description, content, and component
  • Implement a React component that uses ReactFlow handles and the value bus
  • (Optionally) add a config array for sidebar-driven configuration
  • (Optionally) provide Markdown documentation in usage_documentation

2. 🏗️ General Node File Structure

import React, { useEffect, useRef, useState } from "react";
import { Handle, Position, useReactFlow, useStore } from "reactflow";
import { IconButton } from "@mui/material";
import SettingsIcon from "@mui/icons-material/Settings";

// Node Component
const MyNode = ({ id, data }) => {
  // ...state and logic...
  return (
    <div className="borealis-node">
      <Handle type="target" position={Position.Left} className="borealis-handle" />
      {/* ...UI... */}
      <Handle type="source" position={Position.Right} className="borealis-handle" />
    </div>
  );
};

// Export node metadata object
export default {
  type: "MyNodeType",
  label: "My Node",
  description: "What this node does...",
  content: "Short content summary...",
  component: MyNode,
  // Optional for Node Configuration Sidebar:
  config: [ /* ... */ ],
  usage_documentation: `...`
};

3. ⚙️ Node Configuration Sidebar (config)

To enable sidebar configuration, add a config array to your export object:

config: [
  { key: "value", label: "Value", type: "text" },
  {
    key: "operator",
    label: "Operator",
    type: "select",
    options: ["Add", "Subtract", "Multiply", "Divide", "Average"]
  }
]

Supported field types:

  • text - simple input
  • select - dropdown (must specify options)
  • More can be added as needed

Each config field is automatically rendered in the sidebar when a node is selected.


4. 🧑‍🎨 Tips for Sidebar-Configurable Nodes

  • Use config for all user-tunable parameters.
  • Keys in config must match data keys in your node's data prop.
  • Sidebar will auto-update and write changes to window.BorealisValueBus and node state.
  • Add usage documentation in usage_documentation for help inside the editor.

5. 🧩 Copy-Paste Node Template

import React, { useEffect, useState, useRef } from "react";
import { Handle, Position, useReactFlow, useStore } from "reactflow";

/**
 * TemplateNode Component
 */
const TemplateNode = ({ id, data }) => {
  const { setNodes } = useReactFlow();
  const edges = useStore((state) => state.edges);
  const [renderValue, setRenderValue] = useState(data?.value || "");
  const valueRef = useRef(renderValue);

  // Manual input handler (when no upstream)
  const handleManualInput = (e) => {
    const newValue = e.target.value;
    valueRef.current = newValue;
    setRenderValue(newValue);
    window.BorealisValueBus[id] = newValue;
    setNodes((nds) =>
      nds.map((n) =>
        n.id === id ? { ...n, data: { ...n.data, value: newValue } } : n
      )
    );
  };

  useEffect(() => {
    let currentRate = window.BorealisUpdateRate;
    let intervalId;
    const runNodeLogic = () => {
      const inputEdge = edges.find((e) => e.target === id);
      const hasInput = Boolean(inputEdge && inputEdge.source);
      if (hasInput) {
        const upstreamValue = window.BorealisValueBus[inputEdge.source] || "";
        if (upstreamValue !== valueRef.current) {
          valueRef.current = upstreamValue;
          setRenderValue(upstreamValue);
          window.BorealisValueBus[id] = upstreamValue;
          setNodes((nds) =>
            nds.map((n) =>
              n.id === id ? { ...n, data: { ...n.data, value: upstreamValue } } : n
            )
          );
        }
      } else {
        window.BorealisValueBus[id] = valueRef.current;
      }
    };
    intervalId = setInterval(runNodeLogic, currentRate);
    const monitor = setInterval(() => {
      const newRate = window.BorealisUpdateRate;
      if (newRate !== currentRate) {
        clearInterval(intervalId);
        currentRate = newRate;
        intervalId = setInterval(runNodeLogic, currentRate);
      }
    }, 250);
    return () => {
      clearInterval(intervalId);
      clearInterval(monitor);
    };
  }, [id, edges, setNodes]);

  const inputEdge = edges.find((e) => e.target === id);
  const hasInput = Boolean(inputEdge && inputEdge.source);

  return (
    <div className="borealis-node">
      <Handle type="target" position={Position.Left} className="borealis-handle" />
      <div className="borealis-node-header">
        {data?.label || "Node Template"}
      </div>
      <div className="borealis-node-content">
        <div style={{ marginBottom: "8px", fontSize: "9px", color: "#ccc" }}>
          {data?.content || "Template acting as a design scaffold for designing nodes for Borealis."}
        </div>
        <label style={{ fontSize: "9px", display: "block", marginBottom: "4px" }}>
          Template Location:
        </label>
        <input
          type="text"
          value={renderValue}
          onChange={handleManualInput}
          disabled={hasInput}
          style={{
            fontSize: "9px",
            padding: "4px",
            background: hasInput ? "#2a2a2a" : "#1e1e1e",
            color: "#ccc",
            border: "1px solid #444",
            borderRadius: "2px",
            width: "100%"
          }}
        />
      </div>
      <Handle type="source" position={Position.Right} className="borealis-handle" />
    </div>
  );
};

export default {
  type: "Node_Template",
  label: "Node Template",
  description: `Node structure template to be used as a scaffold when building new nodes for Borealis.`,
  content: "Template acting as a design scaffold for designing nodes for Borealis.",
  component: TemplateNode,
  config: [
    { key: "value", label: "Value", type: "text" }
  ],
  usage_documentation: `
### Node Template

A reference implementation of a simple, single-value node for Borealis.

- Accepts manual or upstream input
- Configurable via sidebar
- Uses the value bus for live updates
`.trim()
};

Happy Node Building!

If you need help, check out the sample nodes and open a PR or issue with your questions.