@netlisian/dynamic-functions
v0.0.0
Published
A **Blender-inspired visual programming interface** for creating dynamic data transformations. Build complex transformation pipelines using an intuitive node-based editor powered by React Flow.
Readme
Dynamic Function Package
A Blender-inspired visual programming interface for creating dynamic data transformations. Build complex transformation pipelines using an intuitive node-based editor powered by React Flow.
🎨 NEW: React Flow Visual Programming System
- Quick Start Guide - Get started in 5 minutes
- Visual Programming Docs - Complete feature guide
- Implementation Summary - Architecture details
- Simple Example - Step-by-step tutorial
📚 Legacy Rete.js Documentation:
- Quick Reference - Essential patterns and code snippets
- React Integration Guide - Complete setup walkthrough
- Setup Summary - What we've implemented so far
Features
Visual Programming (React Flow)
- 🎨 Blender-Inspired Design: Professional dark theme with color-coded sockets
- 🔌 Smart Connections: Type-safe socket connections with visual feedback
- 📦 50+ Pre-built Operations: Arithmetic, logical, string, array, and object operations
- 🎯 Drag & Drop: Intuitive node creation from sidebar
- 💾 Serializable Graphs: Save and load complete graph state
- ⚡ Runtime Execution: Convert visual graphs to executable functions
- 🔍 Input Fields: Edit values directly in unconnected input sockets
- 🎭 Three Node Types:
- Source Nodes: Data input with configurable paths
- Transform Nodes: Operation nodes for data transformation
- Destination Nodes: Data output targets
Legacy Rete.js Support
- Visual Node Editor: Built with Rete.js for intuitive visual programming
- Three Node Types:
- From Nodes: Input fields that provide values to the transform function
- Operator Nodes: Processing nodes for transformations (math, logic, string operations)
- To Nodes: Output fields that receive transformed values
- Built-in Operators:
- Math: Add, Subtract, Multiply, Divide, Modulo, Power
- Logic: AND, OR, NOT, Equals, Not Equals, Greater/Less Than
- String: Concat, Uppercase, Lowercase, Trim, Split, Join, Replace, Substring
- Real-time Validation: Detects circular dependencies and validates connections
- TypeScript Support: Fully typed API with Puck integration
Installation
pnpm add @netlisian/dynamic-functionsQuick Start (React Flow)
import { DynamicFunctionModal } from '@netlisian/dynamic-functions';
import '@xyflow/react/dist/style.css';
function MyComponent() {
const [isOpen, setIsOpen] = useState(false);
const [mappings, setMappings] = useState([]);
return (
<>
<button onClick={() => setIsOpen(true)}>
Open Visual Editor
</button>
<DynamicFunctionModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
fromOptions={[
{ label: 'First Name', value: 'user.firstName' },
{ label: 'Last Name', value: 'user.lastName' },
]}
toOptions={[
{ label: 'Full Name', value: 'profile.fullName' },
]}
value={mappings}
onChange={setMappings}
/>
</>
);
}See the Quick Start Guide for a complete tutorial.
Usage (Legacy Rete.js)
Simple Editor (Getting Started)
For learning and testing, use the SimpleEditor which follows official Rete.js patterns:
import { SimpleEditorTest } from "@netlisian/dynamic-functions";
import "@netlisian/dynamic-functions/dist/styles.css";
function App() {
return (
<div>
<h1>Simple Rete.js Editor</h1>
<SimpleEditorTest />
</div>
);
}Or create your own editor programmatically:
import { SimpleEditor, createNumberNode, createAddNode } from "@netlisian/dynamic-functions";
import { ClassicPreset } from "rete";
function MyCustomEditor() {
const containerRef = useRef<HTMLDivElement>(null);
const editorRef = useRef<SimpleEditor | null>(null);
useEffect(() => {
if (!containerRef.current) return;
const initEditor = async () => {
const editor = new SimpleEditor(containerRef.current!);
// Add nodes
const num1 = createNumberNode(5);
const num2 = createNumberNode(10);
const add = createAddNode();
await editor.addNode(num1, { x: 50, y: 100 });
await editor.addNode(num2, { x: 50, y: 250 });
await editor.addNode(add, { x: 350, y: 170 });
// Connect nodes
await editor.addConnection(
new ClassicPreset.Connection(num1, "value", add, "left")
);
await editor.zoomToFit();
editorRef.current = editor;
};
initEditor();
return () => {
if (editorRef.current) {
editorRef.current.destroy();
}
};
}, []);
return <div ref={containerRef} style={{ width: "100%", height: "600px" }} />;
}Basic Example
import { DynamicFunctionModal } from "@netlisian/dynamic-functions/puck";
import "@netlisian/dynamic-functions/dist/styles.css";
function MyComponent() {
const [isOpen, setIsOpen] = useState(false);
const [mappings, setMappings] = useState([]);
const fromOptions = [
{ label: "First Name", value: "firstName", type: "text" },
{ label: "Last Name", value: "lastName", type: "text" },
{ label: "Age", value: "age", type: "number" },
];
const toOptions = [
{ label: "Full Name", value: "fullName", type: "text" },
{ label: "Display Name", value: "displayName", type: "text" },
];
return (
<>
<button onClick={() => setIsOpen(true)}>
Open Function Editor
</button>
<DynamicFunctionModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
fromOptions={fromOptions}
toOptions={toOptions}
value={mappings}
onChange={setMappings}
/>
</>
);
}With Puck (SoftConfig)
The package integrates seamlessly with the SoftConfig system:
import { SoftConfigProvider } from "@netlisian/softconfig/puck";
import { DynamicFunctionModal } from "@netlisian/dynamic-functions/puck";
<SoftConfigProvider
hardConfig={config}
softComponents={softComponents}
overrides={{
map: (props) => {
const [isModalOpen, setIsModalOpen] = useState(false);
return (
<div>
<button onClick={() => setIsModalOpen(true)}>
Configure Transform
</button>
<DynamicFunctionModal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
fromOptions={props.fromOptions}
toOptions={props.toOptions}
value={props.value || []}
onChange={props.onChange}
/>
</div>
);
},
}}
>
{/* Your Puck editor */}
</SoftConfigProvider>API Reference
DynamicFunctionModal
The main component for the visual function editor.
Props
isOpen: boolean- Controls modal visibilityonClose: () => void- Callback when modal closesfromOptions: Array<{ label: string; value: string; type: FieldType }>- Available input fieldstoOptions: Array<{ label: string; value: string; type: FieldType }>- Available output fieldsvalue?: MapConfig[]- Current transformation mappingsonChange: (value: MapConfig[]) => void- Callback when mappings changecurrentIndex?: number- Index of current mapping being edited (default: 0)
MapConfig
Configuration for a single transformation:
type MapConfig = {
from: string | string[];
to: string | string[];
transform?: TransformFunction;
};
type TransformFunction = (values: any[], props: any) => any;FieldType
Supported Puck field types:
type FieldType =
| "text"
| "textarea"
| "number"
| "select"
| "radio"
| "external"
| "custom"
| "array";How It Works
- Add Input Nodes: Click fields from the "Input Fields" sidebar to add "From" nodes
- Add Operator Nodes: Choose operators from Math, Logic, or String categories
- Add Output Nodes: Add "To" nodes from the "Output Fields" sidebar
- Connect Nodes: Drag from output ports to input ports to create connections
- Save Transform: Click "Save Transform" to generate the transformation function
The editor automatically:
- Validates connections between nodes
- Generates executable transformation functions
- Handles multiple inputs/outputs
- Detects circular dependencies
- Orders execution using topological sort
Operator Categories
Math Operators
- Add (+): Adds two numbers
- Subtract (-): Subtracts second from first
- Multiply (×): Multiplies two numbers
- Divide (÷): Divides first by second
- Modulo (%): Returns remainder
- Power (^): Raises first to power of second
Logic Operators
- AND: Logical AND of two booleans
- OR: Logical OR of two booleans
- NOT: Logical NOT of a boolean
- Equals (==): Checks equality
- Not Equals (!=): Checks inequality
- Greater Than (>): Numeric comparison
- Less Than (<): Numeric comparison
- Greater or Equal (≥): Numeric comparison
- Less or Equal (≤): Numeric comparison
String Operators
- Concatenate: Joins two strings
- Uppercase: Converts to uppercase
- Lowercase: Converts to lowercase
- Trim: Removes whitespace
- Split: Splits string by separator
- Join: Joins array with separator
- Replace: Replaces text in string
- Substring: Extracts substring
Architecture
dynamic-function/
├── src/
│ ├── types.ts # TypeScript type definitions
│ ├── operators.ts # Operator definitions and implementations
│ ├── nodes.ts # Node class definitions
│ ├── editor.ts # Core editor logic and function generation
│ ├── components/
│ │ ├── Modal.tsx # Generic modal component
│ │ └── DynamicFunctionModal.tsx # Main editor modal
│ ├── puck/
│ │ └── index.tsx # Puck-specific exports
│ └── styles.css # Component stylesAdvanced Usage
Custom Operators
You can extend the operator set by modifying operators.ts:
export const operators: Record<OperatorType, OperatorDefinition> = {
// ... existing operators
customOp: {
type: "customOp",
label: "Custom Operation",
inputs: 2,
category: "math",
execute: (a: number, b: number) => {
// Your custom logic
return a * 2 + b;
},
},
};Programmatic Editor Access
import { createDynamicFunctionEditor } from "@netlisian/dynamic-functions";
const editor = await createDynamicFunctionEditor(containerElement);
// Add nodes programmatically
await editor.addNode(someNode, { x: 100, y: 100 });
// Generate config
const config = editor.generateMapConfig();
// Clean up
editor.destroy();Browser Compatibility
- Chrome/Edge: ✅ Full support
- Firefox: ✅ Full support
- Safari: ✅ Full support
- IE11: ❌ Not supported
Dependencies
rete: Node editor frameworkrete-area-plugin: Area managementrete-connection-plugin: Connection handlingrete-react-plugin: React renderinglucide-react: Icons@measured/puck: Puck integration (peer dependency)
License
MIT
