UNPKG

serverless-workflow-builder-lib

Version:

A reusable library for building serverless workflow editors with React Flow

1,554 lines (1,302 loc) 41.5 kB
# Serverless Workflow Builder Library A reusable React library for building serverless workflow editors using React Flow. This library provides pre-built node components, hooks for state management, and utilities for workflow conversion. ## Features - **🎯 Pre-built Node Components**: Ready-to-use React Flow nodes for different workflow states (Start, Operation, Switch, Event, Sleep, End) - **🔄 State Management Hooks**: Comprehensive hooks for workflow state, history management, and edge connections - **🚀 Programmatic Node Creation**: Add, remove, and manipulate workflow nodes programmatically with useWorkflowActions hook - **🏭 Node Factory Utilities**: Low-level utilities for creating workflow nodes with custom configurations - **📊 Workflow Conversion**: Bidirectional conversion between React Flow data and Serverless Workflow specification - **🎨 Smart Edge Styling**: Automatic edge styling with animations, colors, and labels based on node types - **⏮️ Undo/Redo Support**: Built-in history management with undo/redo functionality - **📁 Import/Export**: Load and save workflows in standard Serverless Workflow JSON format - **🗺️ Layout Management**: Export, import, and preserve React Flow node positions and workflow layouts - **🎨 Pre-configured Styling**: Beautiful CSS styles for nodes and edges with theme support - **🔧 Extensible**: Easy to customize and extend with your own node types and styling - **📱 Responsive**: Works on desktop and mobile devices - **⚡ Performance Optimized**: Efficient state management and rendering - **🎛️ Node Properties Panel**: Built-in properties panel for editing node configurations with auto-save - **🔗 Smart Edge Label Sync**: Automatic synchronization of edge labels with node names and conditions - **⚙️ Advanced Switch States**: Support for both data and event-based conditions with timeout handling - **📝 Rich Node Editing**: Comprehensive forms for editing all node types with validation and real-time updates ## Installation ```bash npm install ./src/lib ``` ## Quick Start The Serverless Workflow Builder Library is designed to work with existing Serverless Workflow specifications. Here are two ways to get started: ### Option 1: Start from Scratch If you want to create a new workflow from scratch: ```jsx import React, { useState } from 'react'; import ReactFlow, { Background, Controls } from 'reactflow'; import { nodeTypes, defaultInitialNodes, defaultInitialEdges, useWorkflowState, useEdgeConnection, useHistory, useEdgeLabelSync, useNodePropertiesPanel, NodePropertiesPanel } from 'serverless-workflow-builder-lib'; import 'reactflow/dist/style.css'; import 'serverless-workflow-builder-lib/dist/style.css'; function App() { const { nodes, edges, updateNodes, updateEdges } = useWorkflowState( defaultInitialNodes, defaultInitialEdges ); const { onConnect } = useEdgeConnection(edges, updateEdges); const { undo, redo, canUndo, canRedo } = useHistory(); // Automatically sync edge labels with node names useEdgeLabelSync(nodes, edges, updateEdges); // Properties panel for editing nodes const nodePropertiesPanel = useNodePropertiesPanel({ onNodeUpdate: (nodeId, updatedData) => { const updatedNodes = nodes.map(node => node.id === nodeId ? { ...node, data: { ...node.data, ...updatedData } } : node ); updateNodes(updatedNodes); }, autoSave: true }); const onNodeClick = (event, node) => { nodePropertiesPanel.openPanel(node); }; return ( <div style={{ width: '100vw', height: '100vh', display: 'flex' }}> <div style={{ flex: 1 }}> <ReactFlow nodes={nodes} edges={edges} onNodesChange={(changes) => updateNodes(changes)} onEdgesChange={(changes) => updateEdges(changes)} onConnect={onConnect} onNodeClick={onNodeClick} nodeTypes={nodeTypes} fitView > <Background /> <Controls /> </ReactFlow> {/* Undo/Redo buttons */} <div style={{ position: 'absolute', top: 10, left: 10, zIndex: 1000 }}> <button onClick={undo} disabled={!canUndo}>Undo</button> <button onClick={redo} disabled={!canRedo}>Redo</button> </div> </div> {/* Node Properties Panel */} <NodePropertiesPanel {...nodePropertiesPanel} /> </div> ); } export default App; ``` ### Option 2: Import an Existing Serverless Workflow Converting an existing Serverless Workflow JSON into an editable visual workflow: ```jsx import React, { useState } from 'react'; import ReactFlow, { Background, Controls } from 'reactflow'; import { nodeTypes, useWorkflowState, useEdgeConnection, useHistory, useEdgeLabelSync, useNodePropertiesPanel, NodePropertiesPanel, convertWorkflowToReactFlow } from 'serverless-workflow-builder-lib'; import 'reactflow/dist/style.css'; import 'serverless-workflow-builder-lib/dist/style.css'; // Example Serverless Workflow JSON const sampleWorkflow = { "id": "loan-application", "name": "Loan Application Workflow", "description": "A workflow for processing loan applications", "version": "1.0", "specVersion": "0.8", "start": "GetLoanApplication", "states": [ { "name": "GetLoanApplication", "type": "operation", "actions": [ { "functionRef": { "refName": "getLoanApplicationFunction", "arguments": { "applicationId": "${ .applicationId }" } } } ], "transition": "CheckCreditScore" }, { "name": "CheckCreditScore", "type": "operation", "actions": [ { "functionRef": { "refName": "checkCreditScoreFunction", "arguments": { "ssn": "${ .applicant.ssn }" } } } ], "transition": "CreditDecision" }, { "name": "CreditDecision", "type": "switch", "dataConditions": [ { "name": "HighCreditScore", "condition": "${ .creditScore >= 700 }", "transition": "ApproveLoan" } ], "defaultCondition": { "transition": "RejectLoan" } }, { "name": "ApproveLoan", "type": "operation", "actions": [ { "functionRef": { "refName": "approveLoanFunction" } } ], "end": true }, { "name": "RejectLoan", "type": "operation", "actions": [ { "functionRef": { "refName": "rejectLoanFunction" } } ], "end": true } ] }; function App() { // Convert Serverless Workflow to React Flow format const { nodes: initialNodes, edges: initialEdges } = convertWorkflowToReactFlow(sampleWorkflow); const { nodes, edges, updateNodes, updateEdges } = useWorkflowState( initialNodes, initialEdges ); const { onConnect } = useEdgeConnection(edges, updateEdges); const { undo, redo, canUndo, canRedo } = useHistory(); // Automatically sync edge labels with node names and conditions useEdgeLabelSync(nodes, edges, updateEdges); // Properties panel for editing nodes const nodePropertiesPanel = useNodePropertiesPanel({ onNodeUpdate: (nodeId, updatedData) => { const updatedNodes = nodes.map(node => node.id === nodeId ? { ...node, data: { ...node.data, ...updatedData } } : node ); updateNodes(updatedNodes); }, autoSave: true }); const onNodeClick = (event, node) => { nodePropertiesPanel.openPanel(node); }; return ( <div style={{ width: '100vw', height: '100vh', display: 'flex' }}> <div style={{ flex: 1 }}> <ReactFlow nodes={nodes} edges={edges} onNodesChange={(changes) => updateNodes(changes)} onEdgesChange={(changes) => updateEdges(changes)} onConnect={onConnect} onNodeClick={onNodeClick} nodeTypes={nodeTypes} fitView > <Background /> <Controls /> </ReactFlow> {/* Undo/Redo buttons */} <div style={{ position: 'absolute', top: 10, left: 10, zIndex: 1000 }}> <button onClick={undo} disabled={!canUndo}>Undo</button> <button onClick={redo} disabled={!canRedo}>Redo</button> </div> </div> {/* Node Properties Panel */} <NodePropertiesPanel {...nodePropertiesPanel} /> </div> ); } export default App; ``` ### Enhanced Quick Start with Properties Panel For a more complete editor experience with node editing capabilities: ```jsx import React from 'react'; import ReactFlow, { Background, Controls } from 'reactflow'; import { nodeTypes, defaultInitialNodes, defaultInitialEdges, useWorkflowState, useEdgeConnection, useHistory, useEdgeLabelSync, useNodePropertiesPanel, NodePropertiesPanel } from 'serverless-workflow-builder-lib'; import 'reactflow/dist/style.css'; import 'serverless-workflow-builder-lib/dist/style.css'; function App() { const { nodes, edges, updateNodes, updateEdges } = useWorkflowState( defaultInitialNodes, defaultInitialEdges ); const { onConnect } = useEdgeConnection(edges, updateEdges); const { undo, redo, canUndo, canRedo } = useHistory(); useEdgeLabelSync(nodes, edges, updateEdges); // Properties panel for editing nodes const nodePropertiesPanel = useNodePropertiesPanel({ onNodeUpdate: (nodeId, updatedData) => { const updatedNodes = nodes.map(node => node.id === nodeId ? { ...node, data: { ...node.data, ...updatedData } } : node ); updateNodes(updatedNodes); }, autoSave: true }); const onNodeClick = (event, node) => { nodePropertiesPanel.openPanel(node); }; return ( <div style={{ width: '100vw', height: '100vh' }}> <ReactFlow nodes={nodes} edges={edges} onNodesChange={(changes) => updateNodes(changes)} onEdgesChange={(changes) => updateEdges(changes)} onConnect={onConnect} onNodeClick={onNodeClick} nodeTypes={nodeTypes} fitView > <Background /> <Controls /> </ReactFlow> {/* Undo/Redo buttons */} <div style={{ position: 'absolute', top: 10, left: 10, zIndex: 1000 }}> <button onClick={undo} disabled={!canUndo}>Undo</button> <button onClick={redo} disabled={!canRedo}>Redo</button> </div> {/* Properties Panel */} <NodePropertiesPanel isOpen={nodePropertiesPanel.isOpen} node={nodePropertiesPanel.selectedNode} formData={nodePropertiesPanel.formData} isDirty={nodePropertiesPanel.isDirty} onClose={nodePropertiesPanel.closePanel} onFieldChange={nodePropertiesPanel.updateField} onSave={nodePropertiesPanel.applyChanges} onReset={nodePropertiesPanel.resetChanges} autoSave={true} /> </div> ); } export default App; ``` ## Node Properties Panel The library includes a built-in properties panel for editing node configurations. This panel provides a comprehensive interface for modifying all node properties with real-time validation and auto-save functionality. ### Properties Panel Features - **Auto-save**: Automatically saves changes as you type - **Validation**: Real-time validation of node properties - **Type-specific forms**: Different forms for each node type (Operation, Switch, Event, etc.) - **Condition management**: Advanced editing for switch state conditions - **Timeout support**: Built-in timeout configuration for event-based states - **Metadata editing**: Support for custom metadata on all nodes ### useNodePropertiesPanel Hook ```jsx const nodePropertiesPanel = useNodePropertiesPanel({ onNodeUpdate: (nodeId, updatedData) => { // Handle node updates }, autoSave: true, // Enable auto-save (default: false) debounceMs: 300 // Auto-save debounce delay (default: 300ms) }); // Available methods and properties: const { isOpen, // Boolean: panel open state selectedNode, // Currently selected node formData, // Current form data isDirty, // Boolean: has unsaved changes openPanel, // Function: open panel for node closePanel, // Function: close panel updateField, // Function: update form field applyChanges, // Function: apply changes to node resetChanges // Function: reset form to original values } = nodePropertiesPanel; ``` ## Edge Label Synchronization The library automatically synchronizes edge labels with node names and switch conditions using the `useEdgeLabelSync` hook. ```jsx import { useEdgeLabelSync } from 'serverless-workflow-builder-lib'; function WorkflowEditor() { const { nodes, edges, updateEdges } = useWorkflowState(); // Automatically sync edge labels useEdgeLabelSync(nodes, edges, updateEdges); // Edge labels will automatically update when: // - Node names change // - Switch condition names change // - Event condition names change return ( <ReactFlow nodes={nodes} edges={edges} // ... other props /> ); } ``` ### Label Sync Features - **Simple edges**: Labels update to ` {targetNodeName}` - **Switch conditions**: Labels update to condition names or expressions - **Event conditions**: Labels update to event names or event references - **Real-time updates**: Changes are reflected immediately ## Common Use Cases ### Loading a Sample Workflow ```jsx // Sample Serverless Workflow JSON const sampleWorkflow = { "id": "greeting", "version": "1.0", "specVersion": "0.8", "name": "Greeting workflow", "description": "JSON based greeting workflow", "start": "ChooseOnLanguage", "states": [ { "name": "ChooseOnLanguage", "type": "switch", "dataConditions": [ { "name": "English", "condition": "${ .language == \"English\" }", "transition": "GreetInEnglish" }, { "name": "Spanish", "condition": "${ .language == \"Spanish\" }", "transition": "GreetInSpanish" } ], "defaultCondition": { "transition": "GreetInEnglish" } }, { "name": "GreetInEnglish", "type": "operation", "actions": [ { "functionRef": { "refName": "Greet", "arguments": { "message": "${ \"Hello\" + .name }" } } } ], "end": true }, { "name": "GreetInSpanish", "type": "operation", "actions": [ { "functionRef": { "refName": "Greet", "arguments": { "message": "${ \"Hola\" + .name }" } } } ], "end": true } ] }; // Load the workflow const loadSampleWorkflow = () => { try { const { nodes: convertedNodes, edges: convertedEdges } = convertWorkflowToReactFlow(sampleWorkflow); updateNodes(convertedNodes); updateEdges(convertedEdges); setWorkflowMetadata({ name: sampleWorkflow.name, description: sampleWorkflow.description, version: sampleWorkflow.version, }); } catch (error) { console.error('Error loading workflow:', error); } }; ``` ### Adding Toolbar Actions ```jsx function WorkflowToolbar({ onExport, onImport, onReset }) { return ( <div style={{ padding: '10px', borderBottom: '1px solid #ccc', display: 'flex', gap: '10px' }}> <button onClick={onExport} style={{ padding: '8px 16px', backgroundColor: '#4CAF50', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' }} > Export Workflow </button> <input type="file" accept=".json" onChange={onImport} style={{ display: 'none' }} id="workflow-import" /> <label htmlFor="workflow-import" style={{ padding: '8px 16px', backgroundColor: '#2196F3', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' }} > Import Workflow </label> <button onClick={onReset} style={{ padding: '8px 16px', backgroundColor: '#f44336', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' }} > Reset </button> </div> ); } ``` ### Custom Node Styling ```jsx // The library includes pre-built styles, but you can customize them import 'serverless-workflow-builder-lib/styles/NodeStyles.css'; import 'serverless-workflow-builder-lib/styles/EdgeStyles.css'; // Or add your own custom styles const customNodeStyle = { background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)', border: '2px solid #FF8E53', borderRadius: '10px', color: 'white' }; ``` ## Components ### Node Components The library provides the following pre-built node components: - **StartNode**: Entry point for workflows - **OperationNode**: Represents operation states with actions - **SwitchNode**: Conditional branching with data/event conditions - **EventNode**: Event-based states with timeouts - **SleepNode**: Delay states with configurable duration - **EndNode**: Terminal states for workflows #### Usage ```jsx import { nodeTypes } from 'serverless-workflow-builder-lib'; // Use with React Flow <ReactFlow nodeTypes={nodeTypes} /> ``` #### Individual Component Import ```jsx import { StartNode, OperationNode, SwitchNode, EventNode, EndNode, SleepNode } from 'serverless-workflow-builder-lib'; ``` ## Hooks in Action ### Complete State Management Example ```jsx import React from 'react'; import { useWorkflowState, useHistory, useEdgeConnection, defaultInitialNodes, defaultInitialEdges } from 'serverless-workflow-builder-lib'; function WorkflowWithFullStateManagement() { const [workflowMetadata, setWorkflowMetadata] = React.useState({ name: 'My Workflow', description: 'A sample workflow', version: '1.0' }); // Main workflow state management const { nodes, edges, updateNodes, updateEdges, hasChanges } = useWorkflowState( defaultInitialNodes, defaultInitialEdges, workflowMetadata ); // History management for undo/redo const { state: historyState, setState: setHistoryState, undo, redo, canUndo, canRedo } = useHistory({ nodes: defaultInitialNodes, edges: defaultInitialEdges, workflowMetadata }); // Smart edge connection handling const onConnect = useEdgeConnection( edges, updateEdges, setHistoryState, nodes, workflowMetadata ); // Handle node changes with history tracking const handleNodesChange = React.useCallback((changes) => { const updatedNodes = applyNodeChanges(changes, nodes); updateNodes(updatedNodes); setHistoryState({ nodes: updatedNodes, edges, workflowMetadata }); }, [nodes, edges, workflowMetadata, updateNodes, setHistoryState]); // Handle edge changes with history tracking const handleEdgesChange = React.useCallback((changes) => { const updatedEdges = applyEdgeChanges(changes, edges); updateEdges(updatedEdges); setHistoryState({ nodes, edges: updatedEdges, workflowMetadata }); }, [nodes, edges, workflowMetadata, updateEdges, setHistoryState]); return ( <div> {/* Toolbar with undo/redo */} <div style={{ padding: '10px', display: 'flex', gap: '10px' }}> <button onClick={undo} disabled={!canUndo} style={{ padding: '8px 16px', backgroundColor: canUndo ? '#2196F3' : '#ccc', color: 'white', border: 'none', borderRadius: '4px', cursor: canUndo ? 'pointer' : 'not-allowed' }} > Undo </button> <button onClick={redo} disabled={!canRedo} style={{ padding: '8px 16px', backgroundColor: canRedo ? '#2196F3' : '#ccc', color: 'white', border: 'none', borderRadius: '4px', cursor: canRedo ? 'pointer' : 'not-allowed' }} > Redo </button> {hasChanges && ( <span style={{ padding: '8px 16px', backgroundColor: '#ff9800', color: 'white', borderRadius: '4px' }}> Unsaved Changes </span> )} </div> {/* React Flow Canvas */} <ReactFlow nodes={nodes} edges={edges} onNodesChange={handleNodesChange} onEdgesChange={handleEdgesChange} onConnect={onConnect} nodeTypes={nodeTypes} fitView > <Controls /> <Background variant="dots" gap={12} size={1} /> </ReactFlow> </div> ); } ``` ### Hook Combinations for Different Use Cases ```jsx // Minimal setup - just basic workflow editing const { nodes, edges, updateNodes, updateEdges } = useWorkflowState( defaultInitialNodes, defaultInitialEdges ); // With history - adds undo/redo capability const { setState: setHistoryState, undo, redo } = useHistory({ nodes: defaultInitialNodes, edges: defaultInitialEdges }); // With smart edge connections - automatic styling and labeling const onConnect = useEdgeConnection( edges, updateEdges, setHistoryState, nodes, workflowMetadata ); // Full setup - all features enabled // (See complete example above) ``` ## Hooks API Reference ### useHistory Provides undo/redo functionality for workflow state management. ```jsx import { useHistory } from 'serverless-workflow-builder-lib'; const { state, // Current state setState, // Update state (adds to history) undo, // Undo last change redo, // Redo last undone change canUndo, // Boolean: can undo canRedo, // Boolean: can redo reset // Reset to initial state } = useHistory(initialState); ``` #### Parameters - `initialState`: Initial state object (typically contains nodes, edges, workflowMetadata) #### Example ```jsx const { state, setState, undo, redo, canUndo, canRedo } = useHistory({ nodes: [], edges: [], workflowMetadata: {} }); // Update state setState({ nodes: newNodes, edges: newEdges, workflowMetadata }); // Undo/Redo if (canUndo) undo(); if (canRedo) redo(); ``` ### useWorkflowState Tracks workflow state and React Flow configuration with change detection. ```jsx import { useWorkflowState } from 'serverless-workflow-builder-lib'; const { nodes, // Current nodes edges, // Current edges workflowMetadata, // Current metadata hasChanges, // Boolean: has unsaved changes lastUpdateTimestamp, // Last update timestamp updateNodes, // Update nodes updateEdges, // Update edges updateWorkflowMetadata, // Update metadata resetToInitialState, // Reset to initial state markAsSaved, // Mark current state as saved subscribeToChanges, // Subscribe to change events getReactFlowConfig, // Get React Flow configuration fitView, // Fit view to all nodes centerOnNode, // Center view on specific node getWorkflowStats, // Get workflow statistics reactFlowInstance, // React Flow instance // Layout management functions exportLayout, // Export current layout as object exportLayoutAsString, // Export current layout as JSON string downloadLayout, // Download layout as JSON file importLayout, // Import layout from JSON string/object copyLayoutToClipboard // Copy layout JSON to clipboard } = useWorkflowState(initialNodes, initialEdges, initialMetadata); ``` #### Parameters - `initialNodes`: Initial nodes array (default: []) - `initialEdges`: Initial edges array (default: []) - `initialMetadata`: Initial metadata object (default: {}) #### Example ```jsx const { nodes, edges, hasChanges, updateNodes, subscribeToChanges, getWorkflowStats } = useWorkflowState(defaultInitialNodes, defaultInitialEdges); // Subscribe to changes useEffect(() => { const unsubscribe = subscribeToChanges((state) => { console.log('Workflow changed:', state); }); return unsubscribe; }, [subscribeToChanges]); // Get statistics const stats = getWorkflowStats(); console.log(`Total nodes: ${stats.totalNodes}`); ``` #### Layout Management The `useWorkflowState` hook includes powerful layout management functions for preserving and restoring React Flow node positions and workflow structure: ```jsx const { exportLayout, exportLayoutAsString, downloadLayout, importLayout, copyLayoutToClipboard } = useWorkflowState(initialNodes, initialEdges, initialMetadata); // Export current layout as object const layoutData = exportLayout(); console.log('Current layout:', layoutData); // Export as JSON string const layoutString = exportLayoutAsString(); console.log('Layout JSON:', layoutString); // Download layout as file downloadLayout(); // Downloads 'workflow-layout.json' // Copy to clipboard try { await copyLayoutToClipboard(); console.log('Layout copied to clipboard!'); } catch (error) { console.error('Failed to copy:', error); } // Import layout from JSON try { const result = importLayout(layoutString); // Update workflow state with imported data updateNodes(result.nodes); updateEdges(result.edges); updateWorkflowMetadata(result.metadata); } catch (error) { console.error('Import failed:', error.message); } ``` **Layout Data Structure:** ```jsx { nodes: [...], // React Flow nodes with positions edges: [...], // React Flow edges metadata: {...}, // Workflow metadata exportedAt: "2024-01-01T12:00:00.000Z", version: "1.0" } ``` **Use Cases:** - **Workflow Templates**: Save and share workflow layouts - **Backup & Restore**: Preserve exact node positions - **Version Control**: Track layout changes over time - **Collaboration**: Share workflows with preserved positioning ### useWorkflowActions Provides functions to programmatically add, remove, and manipulate workflow nodes. ```jsx import { useWorkflowActions } from 'serverless-workflow-builder-lib'; const workflowActions = useWorkflowActions(workflowState, historyCallback); const { // Add specific node types addOperationNode, // Add operation node addSleepNode, // Add sleep node addEventNode, // Add event node addSwitchNode, // Add switch node addEndNode, // Add end node addStartNode, // Add start node // Generic functions addNode, // Add any node type removeNode, // Remove node by ID duplicateNode, // Duplicate existing node clearAllNodes, // Clear all nodes // Utility getDefaultPosition // Get default position for new nodes } = workflowActions; ``` #### Parameters - `workflowState`: The workflow state object from useWorkflowState - `historyCallback`: Optional callback to update history state #### Example ```jsx function WorkflowEditor() { const { nodes, edges, workflowMetadata, updateNodes, updateEdges } = useWorkflowState( defaultInitialNodes, defaultInitialEdges, workflowMetadata ); const { setState: setHistoryState } = useHistory({ nodes: defaultInitialNodes, edges: defaultInitialEdges, workflowMetadata }); const workflowActions = useWorkflowActions( { nodes, edges, workflowMetadata, updateNodes, updateEdges }, setHistoryState ); return ( <div> {/* Add state buttons */} <div style={{ padding: '10px', display: 'flex', gap: '10px' }}> <button onClick={() => workflowActions.addOperationNode()}> + Operation </button> <button onClick={() => workflowActions.addSleepNode()}> + Sleep </button> <button onClick={() => workflowActions.addEventNode()}> + Event </button> <button onClick={() => workflowActions.addSwitchNode()}> + Switch </button> <button onClick={() => workflowActions.addEndNode()}> + End </button> </div> {/* Custom positioned node */} <button onClick={() => workflowActions.addOperationNode({ position: { x: 200, y: 300 }, name: 'Custom Operation', actions: [{ name: 'customAction', functionRef: 'myFunction' }] })} > Add Custom Operation </button> {/* Remove node */} <button onClick={() => workflowActions.removeNode('node-id')}> Remove Node </button> {/* Duplicate node */} <button onClick={() => workflowActions.duplicateNode('node-id')}> Duplicate Node </button> {/* React Flow component */} <ReactFlow nodes={nodes} edges={edges} nodeTypes={nodeTypes} // ... other props /> </div> ); } ``` #### Node Creation Options Each add function accepts an optional options object: ```jsx // Basic usage workflowActions.addOperationNode(); // With custom options workflowActions.addOperationNode({ position: { x: 100, y: 200 }, // Custom position name: 'My Operation', // Custom name actions: [ // Custom actions { name: 'action1', functionRef: 'func1' } ], metadata: { custom: 'data' } // Custom metadata }); // Sleep node with duration workflowActions.addSleepNode({ name: 'Wait 5 seconds', duration: 'PT5S' }); // Event node with events workflowActions.addEventNode({ name: 'Wait for Order', onEvents: [{ eventRefs: ['order.created'] }] }); ``` ## Node Factory Utilities Low-level utilities for creating workflow nodes programmatically. ```jsx import { createOperationNode, createSleepNode, createEventNode, createSwitchNode, createEndNode, createStartNode, createNode, generateNodeId, getDefaultPosition } from 'serverless-workflow-builder-lib'; ``` ### Individual Node Creators ```jsx // Create specific node types const operationNode = createOperationNode({ position: { x: 100, y: 200 }, name: 'Process Order', actions: [{ name: 'processOrder', functionRef: 'orderProcessor' }] }); const sleepNode = createSleepNode({ position: { x: 200, y: 300 }, name: 'Wait', duration: 'PT30S' }); const eventNode = createEventNode({ position: { x: 300, y: 400 }, name: 'Wait for Event', onEvents: [{ eventRefs: ['user.action'] }] }); ``` ### Generic Node Creator ```jsx // Create any node type const node = createNode('operation', { position: { x: 100, y: 200 }, name: 'My Node', // ... type-specific options }); ``` ### Utility Functions ```jsx // Generate unique node ID const nodeId = generateNodeId(); // Returns: 'node-1234567890' // Get default position for new nodes const position = getDefaultPosition(existingNodes); // Returns: { x: number, y: number } ``` ## Utilities ### Workflow Conversion Utilities for converting between React Flow data and Serverless Workflow specification. ```jsx import { createServerlessWorkflow, createReactFlowData, convertNodeToState, getNodeStateName } from 'serverless-workflow-builder-lib'; ``` #### createServerlessWorkflow Converts React Flow data to Serverless Workflow specification. ```jsx const workflow = createServerlessWorkflow(nodes, edges, workflowMetadata, workflowInfo); ``` **Parameters:** - `nodes`: Array of React Flow nodes - `edges`: Array of React Flow edges - `workflowMetadata`: Workflow metadata object - `workflowInfo`: Workflow information (id, version, name, description) #### createReactFlowData Creates React Flow compatible data structure. ```jsx const reactFlowData = createReactFlowData(nodes, edges, workflowMetadata, workflowInfo); ``` #### convertNodeToState Converts a single React Flow node to a workflow state. ```jsx const state = convertNodeToState(node, edges, allNodes, workflowMetadata); ``` ## Default Exports The library provides several default exports for quick setup: ### nodeTypes Pre-configured node types for React Flow: ```jsx import { nodeTypes } from 'serverless-workflow-builder-lib'; // Contains: // { // start: StartNode, // operation: OperationNode, // switch: SwitchNode, // event: EventNode, // sleep: SleepNode, // end: EndNode // } ``` ### defaultInitialNodes and defaultInitialEdges Default workflow setup with Start and End nodes: ```jsx import { defaultInitialNodes, defaultInitialEdges } from 'serverless-workflow-builder-lib'; // Use as initial state for your workflow const [nodes, setNodes] = useState(defaultInitialNodes); const [edges, setEdges] = useState(defaultInitialEdges); ``` ## Complete API Reference ### Components - `StartNode` - Start state node component - `OperationNode` - Operation state node component - `SwitchNode` - Switch state node component - `EventNode` - Event state node component - `SleepNode` - Sleep state node component - `EndNode` - End state node component - `NodePropertiesPanel` - Properties panel for editing nodes ### Hooks - `useHistory` - Undo/redo functionality - `useWorkflowState` - Main workflow state management with layout import/export - `useEdgeConnection` - Edge connection handling - `useWorkflowActions` - Programmatic node manipulation - `useNodePropertiesPanel` - Properties panel state management - `useEdgeLabelSync` - Automatic edge label synchronization ### Utilities - `createServerlessWorkflow` - Convert React Flow data to Serverless Workflow - `createReactFlowData` - Convert Serverless Workflow to React Flow data - `convertWorkflowToReactFlow` - Enhanced workflow conversion with positioning - `convertNodeToState` - Convert individual nodes to workflow states - `createOperationNode` - Create operation nodes - `createNode` - Generic node creation utility ### Default Exports - `nodeTypes` - Pre-configured node types object - `defaultInitialNodes` - Default starting nodes - `defaultInitialEdges` - Default starting edges ## Styling The library includes pre-configured CSS styles. Import them in your application: ```jsx import 'serverless-workflow-builder-lib'; // Automatically imports styles ``` Or import styles manually: ```jsx import 'serverless-workflow-builder-lib/src/styles/NodeStyles.css'; ``` ## Node Data Structure Each node type expects specific data structures: ### StartNode ```jsx { id: 'start-1', type: 'start', position: { x: 100, y: 100 }, data: { label: 'Start' } } ``` ### OperationNode ```jsx { id: 'operation-1', type: 'operation', position: { x: 200, y: 100 }, data: { name: 'My Operation', actions: [{ name: 'action1', functionRef: { refName: 'myFunction' } }], metadata: {} } } ``` ### SwitchNode ```jsx { id: 'switch-1', type: 'switch', position: { x: 300, y: 100 }, data: { name: 'My Switch', conditionType: 'data', // or 'event' dataConditions: [{ condition: '${ .age > 18 }', transition: { nextState: 'adult' } }], defaultCondition: { transition: { nextState: 'default' } } } } ``` ## Dependencies Peer dependencies that must be installed in your project: - `react` (>=16.8.0) - `react-dom` (>=16.8.0) - `reactflow` (>=11.0.0) - `uuid` (>=9.0.0) ## Troubleshooting ### Common Issues #### "Module not found" errors ```bash # Make sure all peer dependencies are installed npm install react react-dom reactflow uuid # Or with yarn yarn add react react-dom reactflow uuid ``` #### Edges not connecting properly ```jsx // Make sure you're using the useEdgeConnection hook import { useEdgeConnection } from 'serverless-workflow-builder-lib'; const onConnect = useEdgeConnection( edges, updateEdges, setHistoryState, // Optional: for history tracking nodes, workflowMetadata ); // Pass it to ReactFlow <ReactFlow onConnect={onConnect} /> ``` #### Styles not loading ```jsx // Make sure to import the CSS file import 'serverless-workflow-builder-lib/styles/EdgeStyles.css'; // Or import all styles import 'serverless-workflow-builder-lib/styles/index.css'; ``` #### Undo/Redo not working ```jsx // Make sure to update history state when making changes const handleNodesChange = (changes) => { const newNodes = applyNodeChanges(changes, nodes); updateNodes(newNodes); // Important: Update history state setHistoryState({ nodes: newNodes, edges, workflowMetadata }); }; ``` #### Performance issues with large workflows ```jsx // Use React.memo for custom components const CustomNode = React.memo(({ data }) => { return <div>{data.label}</div>; }); // Debounce frequent updates import { debounce } from 'lodash'; const debouncedUpdate = debounce((newState) => { setHistoryState(newState); }, 300); ``` ### Best Practices 1. **Always use the provided hooks** - They handle state management and edge cases 2. **Import styles** - Don't forget to import the CSS files for proper styling 3. **Handle errors gracefully** - Wrap workflow operations in try-catch blocks 4. **Use TypeScript** - The library includes TypeScript definitions for better development experience 5. **Test your workflows** - Use the conversion utilities to validate your workflow structure ### Performance Tips - Use `React.memo` for custom node components - Debounce frequent state updates - Limit the number of nodes for better performance (recommended: <100 nodes) - Use the `fitView` prop on ReactFlow for better initial positioning ## Working Example A complete working example is available in the `test-library` directory of this repository. This example demonstrates all the library features in action: ### Features Demonstrated - **Complete workflow editor** with all node types - **Node properties panel** for editing node configurations - **Automatic edge label synchronization** - **Undo/redo functionality** with history management - **Import/export workflows** in Serverless Workflow format - **Programmatic node creation** with toolbar buttons - **Real-time workflow validation** - **Complex workflow examples** including loan processing workflows ### Running the Example ```bash # Navigate to the test library cd test-library # Install dependencies npm install # Start the development server npm start ``` The example will be available at `http://localhost:3001` and includes: - A fully functional workflow editor - Sample workflows you can load and modify - All node types with their respective property forms - Export functionality to download workflows as JSON - Comprehensive demonstration of all library capabilities ### Example Code Structure The test library shows how to: ```jsx // Complete integration example import { nodeTypes, defaultInitialNodes, defaultInitialEdges, useHistory, useWorkflowState, useEdgeConnection, useWorkflowActions, useNodePropertiesPanel, useEdgeLabelSync, NodePropertiesPanel, createServerlessWorkflow, convertWorkflowToReactFlow } from 'serverless-workflow-builder-lib'; function WorkflowEditor() { // State management const { nodes, edges, updateNodes, updateEdges } = useWorkflowState(); const { undo, redo, canUndo, canRedo } = useHistory(); // Edge connections and labels const { onConnect } = useEdgeConnection(edges, updateEdges); useEdgeLabelSync(nodes, edges, updateEdges); // Node actions const workflowActions = useWorkflowActions(nodes, edges, updateNodes, updateEdges); // Properties panel const nodePropertiesPanel = useNodePropertiesPanel({ onNodeUpdate: (nodeId, updatedData) => { const updatedNodes = nodes.map(node => node.id === nodeId ? { ...node, data: { ...node.data, ...updatedData } } : node ); updateNodes(updatedNodes); }, autoSave: true }); // ... rest of component implementation } ``` ## Examples Repository For more examples and advanced use cases, check out our examples repository: - Basic workflow editor - Advanced state management - Custom node types - Integration with external APIs - Workflow validation ## License MIT License - see LICENSE file for details. ## Contributing Contributions are welcome! Please read the contributing guidelines before submitting PRs. ## Support For issues and questions, please use the GitHub issue tracker.